{% extends "base.html" %} {% block title %}Preferences{% endblock %} {% block breadcrumb %}Preferences{% endblock %} {% block extra_head %} {% endblock %} {% block content %} {# ── Stats ── #}
{{ preferences|length }}
Namespaces
0
Total Settings
{# ── Info Card ── #}
About Preferences
Namespaces group related settings: ui for theme, notifications for alerts, dashboard for layout.
Merge mode shallow-merges new keys into existing data instead of replacing.
{# ── Namespace Cards ── #}
{% for pref in preferences %}
{{ pref.namespace }} {% set key_count = pref.data.keys()|list|length if pref.data is mapping else 0 %} {{ key_count }} key{{ 's' if key_count != 1 else '' }}
Last updated: {{ pref.updated_at if pref.updated_at else 'Never' }}
{% else %}
No preferences yet
{% endfor %}
{# ═══ Create Namespace Modal ═══ #} {% endblock %} {% block extra_js %} {# ── Count total keys across all namespaces ── #} (function() { var total = 0; {% for pref in preferences %} try { var d = JSON.parse(document.getElementById('pref-data-{{ loop.index }}').value); total += Object.keys(d).length; } catch(e) {} {% endfor %} var el = document.getElementById('totalKeysCount'); if (el) el.textContent = total; })(); function savePref(namespace, idx, merge) { var textarea = document.getElementById('pref-data-' + idx); if (!textarea) return; var raw = textarea.value.trim(); try { JSON.parse(raw); } catch(e) { showFlashMessage('Invalid JSON: ' + e.message, 'error'); return; } var fd = new FormData(); var csrfMeta = document.querySelector('meta[name="csrf-token"]'); if (csrfMeta) fd.append('_csrf_token', csrfMeta.content); fd.append('namespace', namespace); fd.append('data', raw); fd.append('merge', merge ? 'true' : 'false'); fetch('{{ url_prefix|default("/admin") }}/preferences/update', { method: 'POST', body: fd, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.error) { showFlashMessage(data.error, 'error'); return; } showFlashMessage('Preferences for "' + namespace + '" ' + (merge ? 'merged' : 'saved') + '.', 'success'); if (data.data) { textarea.value = JSON.stringify(data.data, null, 2); } }) .catch(function(err) { showFlashMessage('Failed: ' + err, 'error'); }); } function confirmDeletePref(namespace) { showConfirmModal( 'Delete namespace "' + namespace + '"?', 'All preferences in this namespace will be permanently removed.', function() { var form = document.createElement('form'); form.method = 'POST'; form.action = '{{ url_prefix|default("/admin") }}/preferences/delete'; var _t = document.querySelector('meta[name="csrf-token"]'); if (_t) { var c = document.createElement('input'); c.type='hidden'; c.name='_csrf_token'; c.value=_t.content; form.appendChild(c); } var input = document.createElement('input'); input.type = 'hidden'; input.name = 'namespace'; input.value = namespace; form.appendChild(input); document.body.appendChild(form); form.submit(); }, true ); } {# ── Intercept create form for inline feedback ── #} (function() { var form = document.getElementById('createPrefForm'); if (!form) return; form.addEventListener('submit', function(e) { e.preventDefault(); var fd = new FormData(form); fetch(form.action, { method: 'POST', body: fd, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.error) { showFlashMessage(data.error, 'error'); return; } showFlashMessage('Namespace "' + (data.namespace || '') + '" created.', 'success'); document.getElementById('createPrefModal').style.display = 'none'; setTimeout(function() { location.reload(); }, 600); }) .catch(function(err) { showFlashMessage('Failed: ' + err, 'error'); }); }); })(); {% endblock %}