{% extends "base.html" %} {% block title %}Permissions{% endblock %} {% block breadcrumb %}Permissions{% endblock %} {% block content %} {# ── Tabs ── #}
{# ── Roles tab ── #}
Admin Roles
{% for role in roles %}
{{ role.name }} Level {{ role.level }} {% if role.name == 'superadmin' %} IMMUTABLE {% endif %}
{{ role.description }}
{{ role.permissions|length }} permission{{ 's' if role.permissions|length != 1 else '' }}
{% for perm in role.permissions %} {{ perm.split('.')[-1] }} {% endfor %}
{% endfor %}
{# ── Permission Matrix tab (editable) ── #}
Role × Permission Matrix
Toggle checkboxes to grant or revoke permissions per role. Superadmin permissions cannot be modified.
{% if csrf_token %}{% endif %}
{% for role in roles %} {% endfor %} {% for perm in all_permissions %} {% for role in roles %} {% endfor %} {% endfor %}
Permission {{ role.name }}
admin.{{ perm.replace('admin.', '') }} {% if role.name == 'superadmin' %} {% else %} {% endif %}
{# ── Model Permissions tab (editable) ── #}
Per-Model Access Control
{% if model_permissions %}
Override default role-based permissions for specific models. Changes apply to all non-superadmin roles.
{% if csrf_token %}{% endif %}
{% for mp in model_permissions %} {% for action in ['view', 'add', 'change', 'delete', 'export'] %} {% endfor %} {% endfor %}
Model View Add Change Delete Export
{{ mp.name }}
{% else %}
No models registered for permission management
{% endif %}
{% endblock %} {% block extra_js %} // ── Permission matrix change tracker ──────────────────────────────── function _snapshotForm(form) { var snap = {}; form.querySelectorAll('input[type="checkbox"]').forEach(function(cb) { snap[cb.name] = cb.checked; }); return snap; } function trackMatrixChanges() { var form = document.getElementById('matrixForm'); if (!form) return; var changed = 0; form.querySelectorAll('input[type="checkbox"]').forEach(function(cb) { if (window._matrixInitial && window._matrixInitial[cb.name] !== cb.checked) changed++; }); var el = document.getElementById('matrixChanges'); if (el) { el.textContent = changed ? changed + ' change' + (changed > 1 ? 's' : '') + ' pending' : ''; el.style.color = changed ? 'var(--warning)' : ''; } } (function() { // Role matrix — track changes and fix reset var matrixForm = document.getElementById('matrixForm'); if (matrixForm) { window._matrixInitial = _snapshotForm(matrixForm); matrixForm.addEventListener('change', trackMatrixChanges); // Override reset button so it restores to server state (not browser default) var resetBtn = document.getElementById('matrixResetBtn'); if (resetBtn) { resetBtn.addEventListener('click', function(e) { e.preventDefault(); // Restore each checkbox to its initial (server-saved) state matrixForm.querySelectorAll('input[type="checkbox"]').forEach(function(cb) { cb.checked = window._matrixInitial[cb.name] || false; }); trackMatrixChanges(); }); } } // Model perms form — track unsaved changes var modelForm = document.getElementById('modelPermsForm'); if (modelForm) { window._modelPermsInitial = _snapshotForm(modelForm); modelForm.addEventListener('change', function() { var changed = 0; modelForm.querySelectorAll('input[type="checkbox"]').forEach(function(cb) { if (window._modelPermsInitial[cb.name] !== cb.checked) changed++; }); // Re-use matrixChanges element if on same tab, or find a sibling var submitBtn = modelForm.querySelector('button[type="submit"]'); if (submitBtn && changed > 0) { submitBtn.textContent = '\u2713 Save (' + changed + ' change' + (changed > 1 ? 's' : '') + ')'; } else if (submitBtn) { submitBtn.innerHTML = ' Save Model Permissions'; } }); } })(); {% endblock %}