diff --git a/README.md b/README.md index 777de79..8dcae6a 100644 --- a/README.md +++ b/README.md @@ -109,34 +109,43 @@ Ein Benutzer kann mehreren Gruppen angehören, die Rechte addieren sich ### Zweistufiger Rechtebaum -Der Baum hat genau zwei Ebenen, exakt gespiegelt an den drei Sidebar-Gruppen: +Der Baum hat genau zwei Ebenen, exakt gespiegelt an den drei Sidebar-Gruppen. +Pro Bereich gibt es eine kompakte Tabelle: Zeilen = der Bereich selbst +„(gesamt)“ + je ein Unterpunkt, Spalten = die Rechte **R**(ead/Lesen), +**W**(rite/Schreiben, Anlegen), **E**(dit/Ändern) nebeneinander. **„Ändern“ +deckt Löschen überall mit ab** — es gibt bewusst kein eigenes +Löschen-Recht. Eine vierte Spalte **D** existiert nur bei Clients und +bedeutet dort **nicht** Löschen, sondern *PoE-Neustart über das +Dashboard-Popup auslösen* (automatische Neustarts bei Ausfall über +`poe.sh` sind davon komplett unabhängig und laufen immer). Für Zeilen/Spalten +ohne passendes Recht (z.B. „W“ beim Bereich selbst, oder „D“ bei +Switche/Zugangsdaten/Benutzer/Gruppen) wird eine ausgegraute, nicht +anklickbare Checkbox angezeigt statt die Zelle wegzulassen: ``` -Geräte (devices_group.view — Bereich an/aus) - ├─ Clients Lesen · Schreiben (Anlegen) · Ändern (inkl. Akt./Deakt.) · Löschen · PoE-Neustart - ├─ Switche Lesen · Schreiben · Ändern · Löschen - └─ Zugangsdaten Lesen · Schreiben · Ändern · Löschen -Logs (logs_group.view — Bereich an/aus) - ├─ Live Lesen - └─ Änderungen Lesen -Einstellungen (settings_group.view — Bereich an/aus) - ├─ Benutzer Lesen · Schreiben · Ändern · Löschen - ├─ Gruppen Lesen · Schreiben · Ändern · Löschen - ├─ Systemeinstellungen Lesen · Ändern - └─ Im-/Export Lesen (Export) · Ändern (Import) +Geräte (gesamt) R + ├─ Clients R · W · E · D (D = PoE-Neustart über Dashboard) + ├─ Switche R · W · E + └─ Zugangsdaten R · W · E +Logs (gesamt) R + ├─ Live R + └─ Änderungen R +Einstellungen (gesamt) R + ├─ Benutzer R · W · E + ├─ Gruppen R · W · E + ├─ Systemeinstellungen R · E + └─ Im-/Export R (Export) · E (Import) ``` Das jeweilige „Bereich an/aus“-Recht (`devices_group.view` / -`logs_group.view` / `settings_group.view`) wirkt als **Kill-Switch**: ist es -für eine Gruppe nicht gesetzt, greift kein einziges Recht darunter mehr — -selbst wenn z.B. `devices.view` einzeln noch angehakt ist. So lässt sich ein -ganzer Bereich mit einem Klick sperren, ohne jedes Unterrecht einzeln -zurücknehmen zu müssen (`User.has_permission()` in `app.py`). - -Auf der **Gruppen**-Seite wird der Baum als Akkordeon pro Gruppe angezeigt: -eine Zeile pro Top-Level-Bereich mit eigenem Kästchen, darunter eingerückt -die Unterpunkte mit ihren Einzelrechten — ohne den Bereichsnamen in jedem -Unterpunkt zu wiederholen. +`logs_group.view` / `settings_group.view`, die „(gesamt)“-Zeile) wirkt als +**Kill-Switch**: ist es für eine Gruppe nicht gesetzt, greift kein einziges +Recht darunter mehr — selbst wenn z.B. `devices.view` einzeln noch angehakt +ist. So lässt sich ein ganzer Bereich mit einem Klick sperren, ohne jedes +Unterrecht einzeln zurücknehmen zu müssen (`User.has_permission()` in +`app.py`). Auf der Gruppen-Seite sind die Unterpunkt-Zeilen einer Tabelle per +JavaScript so lange gesperrt (ausgegraut, nicht anklickbar), bis die +„(gesamt)“-Zeile angehakt ist. ### Systemgruppen @@ -150,8 +159,8 @@ beiden frei verwaltbar: angelegte Nicht-Admin automatisch zugeordnet wird. Fester Rechtesatz: Geräte-Bereich + Clients/Switche lesen, Logs-Bereich + Live-Log lesen. -Alle anderen Gruppen sind vom jeweiligen Rechteinhaber (`groups.edit` bzw. -`groups.create`/`groups.delete`) frei konfigurierbar. +Alle anderen Gruppen sind vom jeweiligen Rechteinhaber (`groups.edit`, das +auch Anlegen und Löschen von Gruppen umfasst) frei konfigurierbar. ### Eskalationsschutz @@ -174,10 +183,12 @@ Formulare) ist an das jeweilige Recht gekoppelt — im Frontend ausgeblendet Datenmodell: `groups` (inkl. `is_default`- und `is_system`-Flag), `group_permissions` (Gruppe → Recht), `user_groups` (Benutzer → Gruppe). Bestehende Datenbanken werden beim App-Start automatisch migriert -(`_ensure_schema()` in `app.py`, u.a. `devices.toggle` → `devices.edit`, +(`_ensure_schema()` in `app.py`): `devices.toggle` → `devices.edit`, `switches.*` → gespiegelte `credentials.*`, nachträgliches Setzen der neuen -Bereichs-Rechte für bereits vergebene Unterrechte) — läuft **nur einmalig** -über einen Guard in der `settings`-Tabelle, damit ein bewusst deaktiviertes +Bereichs-Rechte für bereits vergebene Unterrechte, sowie (spätere Revision) +sämtliche `*.delete`-Rechte → `*.edit` (Löschen wurde nachträglich überall in +Ändern gefaltet). Jede dieser Migrationen läuft **nur einmalig** über einen +eigenen Guard in der `settings`-Tabelle, damit z.B. ein bewusst deaktiviertes Bereichs-Recht nicht bei jedem Neustart automatisch wieder gesetzt wird. ## Zugangsdaten (wiederverwendbare SSH-Logins) diff --git a/srv/poe_manager/app.py b/srv/poe_manager/app.py index fd03721..4bafaf1 100644 --- a/srv/poe_manager/app.py +++ b/srv/poe_manager/app.py @@ -125,10 +125,20 @@ login_manager.init_app(app) # --------------------------------------------------------------------------- # Zweistufig verschachtelt, gespiegelt an der Navbar-Struktur: jeder # Top-Level-Bereich (Geräte/Logs/Einstellungen) hat ein eigenes "Bereich -# anzeigen"-Recht (view_key) und darunter mehrere Unterpunkte mit jeweils -# eigenen Lesen/Schreiben/Ändern/Löschen-Rechten. Der Bereichs-Name wird -# dadurch nicht in jedem Unterpunkt wiederholt (siehe groups.html: eine -# Zeile pro Top-Level-Bereich, Unterpunkte darunter eingerückt). +# anzeigen"-Recht (view_key) und darunter mehrere Unterpunkte (Spalten in der +# Gruppen-Tabelle). Jeder Unterpunkt liefert "rows": eine Teilmenge der vier +# kanonischen Rechte-Zeilen (view/create/edit/delete = L/S/Ä/D, siehe +# PERMISSION_ROW_TYPES) — fehlt eine Zeile für einen Unterpunkt (z.B. +# "delete" bei Systemeinstellungen), wird die Zelle in der Tabelle grau und +# nicht setzbar dargestellt statt einfach zu fehlen, damit alle Spalten +# eines Bereichs bündig nebeneinander in einer kompakten Tabelle stehen. +PERMISSION_ROW_TYPES = [ + ("view", "R", "Lesen (Read)"), + ("create", "W", "Schreiben/Anlegen (Write)"), + ("edit", "E", "Ändern inkl. Löschen (Edit)"), + ("extra", "D", "Dashboard-Neustart (nur bei Clients — kein Löschen-Recht)"), +] + PERMISSIONS = { "devices_group": { "label": "Geräte", @@ -136,30 +146,33 @@ PERMISSIONS = { "children": { "devices": { "label": "Clients", - "items": { - "devices.view": "Lesen", - "devices.create": "Schreiben (Anlegen)", - "devices.edit": "Ändern (inkl. Aktivieren/Deaktivieren)", - "devices.delete": "Löschen", - "devices.restart": "PoE-Neustart über das Dashboard", + # "Ändern" deckt Löschen überall mit ab, auch hier — es gibt + # nirgends ein eigenes Löschen-Recht. Die vierte Spalte (D) + # ist bei Clients KEIN Löschen, sondern das Recht, einen + # PoE-Neustart über das Dashboard-Popup auszulösen + # (automatische Neustarts bei Ausfall bleiben davon unberührt). + "rows": { + "view": "devices.view", + "create": "devices.create", + "edit": "devices.edit", + "extra": "devices.restart", }, }, "switches": { "label": "Switche", - "items": { - "switches.view": "Lesen", - "switches.create": "Schreiben (Anlegen)", - "switches.edit": "Ändern", - "switches.delete": "Löschen", + # Kein eigenes Löschen-Recht — "Ändern" deckt das mit ab. + "rows": { + "view": "switches.view", + "create": "switches.create", + "edit": "switches.edit", }, }, "credentials": { "label": "Zugangsdaten", - "items": { - "credentials.view": "Lesen", - "credentials.create": "Schreiben (Anlegen)", - "credentials.edit": "Ändern", - "credentials.delete": "Löschen", + "rows": { + "view": "credentials.view", + "create": "credentials.create", + "edit": "credentials.edit", }, }, }, @@ -168,14 +181,8 @@ PERMISSIONS = { "label": "Logs", "view_key": "logs_group.view", "children": { - "logs_live": { - "label": "Live", - "items": {"logs_live.view": "Lesen"}, - }, - "logs_activity": { - "label": "Änderungen", - "items": {"logs_activity.view": "Lesen"}, - }, + "logs_live": {"label": "Live", "rows": {"view": "logs_live.view"}}, + "logs_activity": {"label": "Änderungen", "rows": {"view": "logs_activity.view"}}, }, }, "settings_group": { @@ -184,40 +191,55 @@ PERMISSIONS = { "children": { "users": { "label": "Benutzer", - "items": { - "users.view": "Lesen", - "users.create": "Schreiben (Anlegen)", - "users.edit": "Ändern", - "users.delete": "Löschen", + "rows": { + "view": "users.view", + "create": "users.create", + "edit": "users.edit", }, }, "groups": { "label": "Gruppen", - "items": { - "groups.view": "Lesen", - "groups.create": "Schreiben (Anlegen)", - "groups.edit": "Ändern", - "groups.delete": "Löschen", + "rows": { + "view": "groups.view", + "create": "groups.create", + "edit": "groups.edit", }, }, "settings_system": { "label": "Systemeinstellungen", - "items": { - "settings_system.view": "Lesen", - "settings_system.edit": "Ändern", - }, + "rows": {"view": "settings_system.view", "edit": "settings_system.edit"}, }, "settings_importexport": { "label": "Im-/Export", - "items": { - "settings_importexport.view": "Lesen (Export)", - "settings_importexport.edit": "Ändern (Import)", - }, + # L = Export (nur lesend), Ä = Import (verändert Bestand). + "rows": {"view": "settings_importexport.view", "edit": "settings_importexport.edit"}, }, }, }, } +# Menschenlesbare Einzel-Labels je Recht (Tooltips auf den Checkboxen in der +# Tabelle) — aus PERMISSIONS abgeleitet, damit es nur eine Quelle gibt. +PERMISSION_LABELS = { + "devices_group.view": "Geräte-Bereich anzeigen", + "logs_group.view": "Logs-Bereich anzeigen", + "settings_group.view": "Einstellungen-Bereich anzeigen", + "devices.view": "Clients lesen", "devices.create": "Clients anlegen", + "devices.edit": "Clients ändern (inkl. Aktivieren/Deaktivieren, Löschen)", + "devices.restart": "PoE-Neustart über das Dashboard ausführen (automatische Neustarts bei Ausfall bleiben unberührt)", + "switches.view": "Switche lesen", "switches.create": "Switche anlegen", + "switches.edit": "Switche ändern (inkl. Löschen)", + "credentials.view": "Zugangsdaten lesen", "credentials.create": "Zugangsdaten anlegen", + "credentials.edit": "Zugangsdaten ändern (inkl. Löschen)", + "logs_live.view": "Live-Log lesen", "logs_activity.view": "Änderungslog lesen", + "users.view": "Benutzer lesen", "users.create": "Benutzer anlegen", + "users.edit": "Benutzer ändern (inkl. Löschen)", + "groups.view": "Gruppen lesen", "groups.create": "Gruppen anlegen", + "groups.edit": "Gruppen ändern (inkl. Löschen)", + "settings_system.view": "Systemeinstellungen lesen", "settings_system.edit": "Systemeinstellungen ändern", + "settings_importexport.view": "Daten exportieren", "settings_importexport.edit": "Daten importieren", +} + # Aus der verschachtelten Struktur abgeleitet: alle vergebbaren Keys, sowie # eine Zuordnung jedes Kind-Rechts zum "Bereich anzeigen"-Recht seines # Top-Level-Bereichs. Ist das übergeordnete Recht einer Gruppe nicht @@ -230,7 +252,7 @@ PERMISSION_PARENT_GROUP = {} for _group in PERMISSIONS.values(): ALL_PERMISSION_KEYS.append(_group["view_key"]) for _child in _group["children"].values(): - for _key in _child["items"]: + for _key in _child["rows"].values(): ALL_PERMISSION_KEYS.append(_key) PERMISSION_PARENT_GROUP[_key] = _group["view_key"] @@ -352,13 +374,13 @@ class User(UserMixin): @property def can_manage_switches(self): return self.has_any_permission( - ["switches.view", "switches.create", "switches.edit", "switches.delete"] + ["switches.view", "switches.create", "switches.edit"] ) @property def can_manage_credentials(self): return self.has_any_permission( - ["credentials.view", "credentials.create", "credentials.edit", "credentials.delete"] + ["credentials.view", "credentials.create", "credentials.edit"] ) @property @@ -372,13 +394,13 @@ class User(UserMixin): @property def can_manage_users(self): return self.has_any_permission( - ["users.view", "users.create", "users.edit", "users.delete"] + ["users.view", "users.create", "users.edit"] ) @property def can_manage_groups(self): return self.has_any_permission( - ["groups.view", "groups.create", "groups.edit", "groups.delete"] + ["groups.view", "groups.create", "groups.edit"] ) @property @@ -595,6 +617,38 @@ def _ensure_schema(): "INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key,) ) + # Migration v2: "Löschen" als eigenes Recht wieder abgeschafft — "Ändern" + # deckt das Löschen jetzt überall mit ab (auch bei Clients, keine + # Ausnahme mehr). Gruppen, die bisher nur *.delete ohne *.edit hatten, + # würden sonst durch das Update ihr Löschen-Recht stillschweigend + # verlieren — deshalb erst *.edit nachtragen, dann die veralteten + # *.delete-Zeilen entfernen. Ebenfalls einmalig per Guard, aus demselben + # Grund wie bei v1 (kein Zurückschreiben bei jedem Neustart nötig, da + # *.delete als Recht komplett entfällt und nie wieder vorkommen kann). + _migration_key_v2 = "_migrated_permission_delete_fold_v2" + if not conn.execute("SELECT 1 FROM settings WHERE key=?", (_migration_key_v2,)).fetchone(): + _delete_to_edit = { + "devices.delete": "devices.edit", + "switches.delete": "switches.edit", + "credentials.delete": "credentials.edit", + "users.delete": "users.edit", + "groups.delete": "groups.edit", + } + for delete_perm, edit_perm in _delete_to_edit.items(): + rows = conn.execute( + "SELECT DISTINCT group_id FROM group_permissions WHERE permission=?", (delete_perm,) + ).fetchall() + for row in rows: + conn.execute( + "INSERT OR IGNORE INTO group_permissions (group_id, permission) VALUES (?, ?)", + (row["group_id"], edit_perm), + ) + conn.execute("DELETE FROM group_permissions WHERE permission=?", (delete_perm,)) + + conn.execute( + "INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key_v2,) + ) + # Standardgruppe 'Benutzer' — SYSTEMGRUPPE: Name und Rechte sind fix und # werden bei jedem Start auf den vorgegebenen Satz zurückgesetzt (nicht # nur bei Erstanlage). Das ist unbedenklich, weil is_system die Gruppe in @@ -1366,7 +1420,8 @@ def devices(): flash("Fehler beim Aktualisieren des Geräts!", "danger") elif "delete_device" in request.form: - if not current_user.has_permission("devices.delete"): + # "Ändern" deckt Löschen mit ab (kein eigenes devices.delete-Recht). + if not current_user.has_permission("devices.edit"): flash("Keine Berechtigung, Geräte zu löschen.", "danger") return redirect(url_for("devices")) @@ -1528,7 +1583,8 @@ def switches(): @app.route("/switches/delete/", methods=["POST"]) @login_required def delete_switch(hostname): - if not current_user.has_permission("switches.delete"): + # "Ändern" deckt Löschen mit ab (kein eigenes switches.delete-Recht). + if not current_user.has_permission("switches.edit"): flash("Keine Berechtigung, Switche zu löschen.", "danger") return redirect(url_for("switches")) @@ -1613,7 +1669,8 @@ def credentials(): flash("Name und Username müssen ausgefüllt sein!", "danger") elif request.method == "POST" and "delete_credential" in request.form: - if not current_user.has_permission("credentials.delete"): + # "Ändern" deckt Löschen mit ab (kein eigenes credentials.delete-Recht). + if not current_user.has_permission("credentials.edit"): flash("Keine Berechtigung, Zugangsdaten zu löschen.", "danger") return redirect(url_for("credentials")) cred_id = request.form.get("delete_credential") @@ -2098,7 +2155,8 @@ def users(): flash("Gruppe zugewiesen!", "success") elif "delete_user" in request.form: - if not current_user.has_permission("users.delete"): + # "Ändern" deckt Löschen mit ab (kein eigenes users.delete-Recht). + if not current_user.has_permission("users.edit"): flash("Keine Berechtigung, Benutzer zu löschen.", "danger") return redirect(url_for("users")) @@ -2254,7 +2312,8 @@ def groups(): flash("Admin-Zuweisung aktualisiert.", "success") elif "delete_group" in request.form: - if not current_user.has_permission("groups.delete"): + # "Ändern" deckt Löschen mit ab (kein eigenes groups.delete-Recht). + if not current_user.has_permission("groups.edit"): flash("Keine Berechtigung, Gruppen zu löschen.", "danger") return redirect(url_for("groups")) group_id = request.form.get("delete_group") @@ -2318,6 +2377,8 @@ def groups(): all_users=all_users, all_users_all=all_users_all, permission_catalog=PERMISSIONS, + permission_row_types=PERMISSION_ROW_TYPES, + permission_labels=PERMISSION_LABELS, ) diff --git a/srv/poe_manager/static/css/style.css b/srv/poe_manager/static/css/style.css index 0c904bd..bd65f36 100644 --- a/srv/poe_manager/static/css/style.css +++ b/srv/poe_manager/static/css/style.css @@ -1043,26 +1043,42 @@ select { margin-bottom: 8px; } -/* Zweistufiger Rechte-Baum (Gruppen-Detailansicht): eine Zeile pro - Top-Level-Bereich (Geräte/Logs/Einstellungen) mit eigenem "Bereich - anzeigen"-Kästchen, darunter eingerückt die Unterpunkte. */ -.permission-area { margin-bottom: 22px; } +/* Rechte-Tabelle (Gruppen-Detailansicht): eine kompakte Tabelle pro + Top-Level-Bereich (Geräte/Logs/Einstellungen). Erste Zeile = der Bereich + selbst "(gesamt)" (nur die R-Spalte aktiv, das ist der Kill-Switch), + weitere Zeilen = je ein Unterpunkt (Clients, Switche, ...). Spalten = + R/W/E/D nebeneinander (siehe Legende im Seitenkopf). */ +.permission-area { margin-bottom: 20px; } .permission-area:last-child { margin-bottom: 0; } -.permission-area-toggle { - font-size: 14.5px; - padding-bottom: 8px; - margin-bottom: 14px; +.permission-table { + border-collapse: collapse; + font-size: 13px; + white-space: nowrap; +} +.permission-table th, .permission-table td { + padding: 7px 14px; + text-align: center; border-bottom: 1px solid var(--border-soft); } -.permission-area-toggle strong { font-weight: 700; } -.permission-area-children { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 20px; - padding-left: 22px; - border-left: 2px solid var(--border-soft); - margin-left: 5px; +.permission-table th { + font-size: 11.5px; + font-weight: 650; + color: var(--text-faint); + text-transform: uppercase; + letter-spacing: 0.03em; + font-family: var(--font-mono); } +.permission-table th:first-child { text-transform: none; font-family: inherit; letter-spacing: normal; } +.permission-table td:first-child, .permission-table th:first-child { text-align: left; padding-right: 20px; } +.permission-table tbody tr:last-child td { border-bottom: none; } +.permission-row-label { color: var(--text-dim); } +.permission-table input[type="checkbox"] { + width: 16px; height: 16px; + accent-color: var(--accent); + cursor: pointer; +} +.permission-table input[type="checkbox"]:disabled { cursor: not-allowed; } +.permission-cb-na { opacity: 0.25; accent-color: var(--text-faint) !important; } .check-list { display: flex; flex-direction: column; gap: 9px; } .check-row { diff --git a/srv/poe_manager/templates/credentials.html b/srv/poe_manager/templates/credentials.html index caecf1a..5323cd7 100644 --- a/srv/poe_manager/templates/credentials.html +++ b/srv/poe_manager/templates/credentials.html @@ -2,7 +2,7 @@ {% set active_page = "credentials" %} {% set can_create = current_user.has_permission('credentials.create') %} {% set can_edit = current_user.has_permission('credentials.edit') %} -{% set can_delete = current_user.has_permission('credentials.delete') %} +{% set can_delete = current_user.has_permission('credentials.edit') %} {% block page_title %}Zugangsdaten{% endblock %} {% block page_sub %}
{{ credentials|length }} Zugangsdaten
{% endblock %} diff --git a/srv/poe_manager/templates/devices.html b/srv/poe_manager/templates/devices.html index 3069677..35749c6 100644 --- a/srv/poe_manager/templates/devices.html +++ b/srv/poe_manager/templates/devices.html @@ -3,7 +3,7 @@ {% set can_toggle = current_user.has_permission('devices.edit') %} {% set can_create = current_user.has_permission('devices.create') %} {% set can_edit = current_user.has_permission('devices.edit') %} -{% set can_delete = current_user.has_permission('devices.delete') %} +{% set can_delete = current_user.has_permission('devices.edit') %} {% set show_actions_col = can_edit or can_delete %} {% set col_count = 5 + (1 if can_toggle else 0) + (1 if show_actions_col else 0) %} {% block page_title %}Clients{% endblock %} diff --git a/srv/poe_manager/templates/groups.html b/srv/poe_manager/templates/groups.html index cadeb71..68b15b0 100644 --- a/srv/poe_manager/templates/groups.html +++ b/srv/poe_manager/templates/groups.html @@ -5,38 +5,77 @@ {% block content %} -{# Rendert den zweistufigen Rechte-Baum: eine Zeile pro Top-Level-Bereich - (Geräte/Logs/Einstellungen) mit eigenem "Bereich anzeigen"-Kästchen, - darunter eingerückt die Unterpunkte mit ihren jeweiligen Einzelrechten. - readonly=true zeigt nur den aktuellen Stand (Admin/Systemgruppen bzw. - fehlendes groups.edit), sonst ist der Baum Teil eines
. #} +{# Rendert einen Bereich (Geräte/Logs/Einstellungen) als kompakte Tabelle: + Zeilen = Bereich selbst (nur die R-Spalte aktiv, das ist der Kill-Switch) + + je eine Zeile pro Unterpunkt (Clients, Switche, ...). Spalten = die + kanonischen Rechte R/W/E/D nebeneinander (permission_row_types). + "Ändern" (E) deckt Löschen überall mit ab — es gibt nirgends ein eigenes + Löschen-Recht. "D" existiert nur bei Clients und bedeutet dort NICHT + Löschen, sondern PoE-Neustart über das Dashboard-Popup. Zellen ohne + passendes Recht für die jeweilige Zeile (z.B. "W" bei "Geräte" selbst, + oder "D" bei Switche/Zugangsdaten) werden als ausgegraute, nicht + anklickbare Checkbox dargestellt statt zu fehlen — dadurch bleiben alle + Spalten bündig untereinander. readonly=true zeigt nur den aktuellen Stand + (Admin/Systemgruppen bzw. fehlendes groups.edit). Die JS-Funktion + applyPermissionGating() sperrt Kind-Rechte, solange das Bereich-Lesen + (Kill-Switch) nicht gesetzt ist. #} +{% macro permission_table(group, checked_keys, readonly) %} +
+
+ + + + + {% for row_key, row_letter, row_label in permission_row_types %} + + {% endfor %} + + + + + + {% for row_key, row_letter, row_label in permission_row_types %} + + {% endfor %} + + {% for child_key, child in group['children'].items() %} + + + {% for row_key, row_letter, row_label in permission_row_types %} + {% set perm_key = child['rows'].get(row_key) %} + + {% endfor %} + + {% endfor %} + +
{{ group['label'] }}{{ row_letter }}
{{ group['label'] }} (gesamt) + {% if row_key == 'view' %} + + {% else %} + + {% endif %} +
{{ child['label'] }} + {% if perm_key %} + + {% else %} + + {% endif %} +
+
+
+{% endmacro %} + {% macro permission_tree(checked_keys, readonly) %} {% for group_key, group in permission_catalog.items() %} -
- -
- {% for child_key, child in group['children'].items() %} -
-
{{ child['label'] }}
-
- {% for key, label in child['items'].items() %} - - {% endfor %} -
-
- {% endfor %} -
-
+ {{ permission_table(group, checked_keys, readonly) }} {% endfor %} {% endmacro %} @@ -47,7 +86,12 @@ Über Gruppen lassen sich Rechte für jeden Bereich der App granular vergeben. Ein Benutzer kann mehreren Gruppen angehören — die Rechte addieren sich. Auf „Rechte“ klicken, um eine Gruppe aufzuklappen und die Berechtigungen im Detail zu sehen bzw. zu ändern. - „Admin“ und „Benutzer“ sind feste Systemgruppen und nicht veränderbar. + „Admin“ und „Benutzer“ sind feste Systemgruppen und nicht veränderbar. Die Zeile „... (gesamt)“ + muss gelesen werden dürfen, bevor die Zeilen darunter greifen — ausgegraute Kästchen gelten für + die jeweilige Zeile nicht. + R = Lesen (Read) · W = Schreiben/Anlegen (Write) · + E = Ändern inkl. Löschen (Edit) · + D = Dashboard-Neustart (nur bei Clients; automatische Neustarts bei Ausfall bleiben davon unberührt) {% if current_user.has_permission('groups.create') %} @@ -112,7 +156,7 @@ - {% if current_user.has_permission('groups.delete') and not g.is_default and not g.is_system %} + {% if current_user.has_permission('groups.edit') and not g.is_default and not g.is_system %} {% endif %} - {% if current_user.has_permission('users.delete') and may_touch_target %} + {% if current_user.has_permission('users.edit') and may_touch_target %}