Fix: Benutzer-Löschen im Browser kaputt (JS), Gruppen-Rechtetabelle überarbeitet
Bugfix (von dir gemeldet, live auf Port 5000 gefunden): - initConfirmables() in app.js rief beim bestätigten Absenden form.requestSubmit() OHNE den ursprünglichen Submitter auf. Laut Spec wird dann KEIN Submit-Button-Name/Value mitgeschickt. users.html trug die Benutzer-ID als name/value des Löschen-Buttons selbst (statt eines verstecken Inputs wie überall sonst) — dadurch kam "delete_user" nie im Request an, der Klick tat scheinbar nichts. Fix: e.submitter beim ersten Klick merken und an requestSubmit(submitter) weiterreichen (behebt es grundsätzlich für alle Formulare dieser Art); zusätzlich users.html auf das robustere hidden-input-Muster umgestellt wie credentials/devices/groups. Mit echtem Playwright-Browserklick (nicht nur curl) verifiziert. Gruppen-Rechtetabelle, mehrere Korrekturrunden nach deinem Mockup: - Bereichsname + Kill-Switch-Kästchen jetzt eng nebeneinander in der Tabellen-Kopfzeile (nicht mehr an den Rand gestellt), R/W/E/D-Spalten in derselben Zeile statt einer zweiten. - Nur die von einem Bereich tatsächlich genutzten Spalten werden gezeigt (GROUP_ROW_TYPES) — Logs zeigt nur R, Einstellungen nur R/W/E, nur Geräte zeigt zusätzlich D. - Bugfix: D-Spalte bei Geräte verschwand durch zu schmale Flex-Breite der Spalte im Overflow, obwohl serverseitig korrekt gerendert — behoben durch flex:0 0 auto statt fester Breite. - Checkbox-Zentrierung robust per display:block+margin:auto statt sich auf native Checkbox-Ränder zu verlassen (waren leicht asymmetrisch). - Unterpunkte deutlich weiter eingerückt (34px) für klare Hierarchie. - Sichtbares Ausgrauen (nicht nur natives disabled) der Unterpunkte, solange der Bereich-Kill-Switch aus ist (JS setzt .permission-locked). - Legende jetzt UNTER der Rechtetabelle statt darüber. - Rechte lassen sich jetzt direkt beim Anlegen einer neuen Gruppe setzen (Modal zeigt dieselbe Rechtetabelle, add_group-Route verarbeitet mitgesendete permissions[] im selben Request) statt sie erst danach über die Gruppentabelle nachzupflegen. Alles inkl. Erstellen-mit-Rechten und Bearbeiten-Regression live per Playwright verifiziert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,43 +109,41 @@ 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.
|
||||
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:
|
||||
Der Baum hat genau zwei Ebenen, exakt gespiegelt an den drei Sidebar-Gruppen
|
||||
(Geräte/Logs/Einstellungen). Auf der Gruppen-Seite stehen sie als drei
|
||||
kompakte Spalten nebeneinander, mit einer kurzen Legende direkt darüber.
|
||||
Jede Spalte ist eine kleine Tabelle: die Kopfzeile enthält in EINER Zeile
|
||||
sowohl den Bereichsnamen mit seinem Kästchen (dem Kill-Switch, dicht
|
||||
nebeneinander statt an den Rand gestellt) als auch die Buchstaben-Spalten
|
||||
**R**(ead/Lesen), **W**(rite/Schreiben, Anlegen), **E**(dit/Ändern) — nur
|
||||
die Rechte, die dieser Bereich tatsächlich nutzt. Darunter je eine
|
||||
eingerückte Zeile pro Unterpunkt. **„Ändern“ deckt Löschen überall mit
|
||||
ab** — es gibt bewusst kein eigenes Löschen-Recht. Eine vierte Spalte **D**
|
||||
gibt es nur bei Geräte → Clients, und sie 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 ohne passendes Recht (z.B. „D“ bei Switche/
|
||||
Zugangsdaten/Benutzer/Gruppen) wird eine ausgegraute, nicht anklickbare
|
||||
Checkbox angezeigt statt die Zelle wegzulassen:
|
||||
|
||||
```
|
||||
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)
|
||||
☑ Geräte R W E D ☑ Logs R W E ☑ Einstellungen R W E
|
||||
Clients ☑ ☑ ☑ ☑ Live ☑ ☑ ☑ Benutzer ☑ ☑ ☑
|
||||
Switche ☑ ☑ ☑ ▢ Änd. ☑ ☑ ☑ Gruppen ☑ ☑ ☑
|
||||
Zugangsd. ☑ ☑ ☑ ▢ Systemeinst. ☑ ▢ ☑
|
||||
Im-/Export ☑ ▢ ☑
|
||||
```
|
||||
|
||||
Das jeweilige „Bereich an/aus“-Recht (`devices_group.view` /
|
||||
`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.
|
||||
Das jeweilige „Bereich sichtbar“-Recht (`devices_group.view` /
|
||||
`logs_group.view` / `settings_group.view`, das Kästchen in der Kopfzeile
|
||||
einer Spalte) 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 Spalte per JavaScript so lange gesperrt UND
|
||||
sichtbar ausgegraut (nicht nur das dezente native disabled-Styling), bis
|
||||
das Kopfzeilen-Kästchen angehakt ist.
|
||||
|
||||
### Systemgruppen
|
||||
|
||||
|
||||
+24
-2
@@ -268,6 +268,18 @@ for _group in PERMISSIONS.values():
|
||||
ALL_PERMISSION_KEYS.append(_key)
|
||||
PERMISSION_PARENT_GROUP[_key] = _group["view_key"]
|
||||
|
||||
# Welche Spalten (R/W/E/D) für einen Bereich überhaupt vorkommen — z.B. hat
|
||||
# nur "Geräte" (wegen Clients' Dashboard-Neustart) eine D-Spalte, Logs/
|
||||
# Einstellungen brauchen sie gar nicht erst anzuzeigen. Auf der Gruppen-
|
||||
# Seite bekommt so jeder Bereich nur die Spalten, die er tatsächlich nutzt,
|
||||
# statt überall gleich viele (teils komplett ungenutzte) Spalten zu zeigen.
|
||||
GROUP_ROW_TYPES = {}
|
||||
for _group_key, _group in PERMISSIONS.items():
|
||||
_used_row_keys = {"view"}
|
||||
for _child in _group["children"].values():
|
||||
_used_row_keys.update(_child["rows"].keys())
|
||||
GROUP_ROW_TYPES[_group_key] = [rt for rt in PERMISSION_ROW_TYPES if rt[0] in _used_row_keys]
|
||||
|
||||
DEFAULT_GROUP_NAME = "Benutzer"
|
||||
DEFAULT_GROUP_PERMISSIONS = [
|
||||
"devices_group.view", "devices.view", "switches.view",
|
||||
@@ -2738,9 +2750,18 @@ def groups():
|
||||
name = request.form.get("name", "").strip()
|
||||
if name:
|
||||
try:
|
||||
conn.execute("INSERT INTO groups (name) VALUES (?)", (name,))
|
||||
cur = conn.execute("INSERT INTO groups (name) VALUES (?)", (name,))
|
||||
# Rechte direkt beim Anlegen setzbar (dieselbe Tabelle wie
|
||||
# beim Bearbeiten, hier ohne eigenes "_submitted"-Flag, da
|
||||
# eine neue Gruppe ohnehin noch keine Rechte hat).
|
||||
selected_permissions = set(request.form.getlist("permissions")) & set(ALL_PERMISSION_KEYS)
|
||||
if selected_permissions:
|
||||
conn.executemany(
|
||||
"INSERT INTO group_permissions (group_id, permission) VALUES (?, ?)",
|
||||
[(cur.lastrowid, p) for p in selected_permissions],
|
||||
)
|
||||
conn.commit()
|
||||
log_action("group.create", name)
|
||||
log_action("group.create", name, f"{len(selected_permissions)} Rechte" if selected_permissions else None)
|
||||
flash(f"Gruppe '{name}' angelegt.", "success")
|
||||
except sqlite3.IntegrityError:
|
||||
flash("Eine Gruppe mit diesem Namen existiert bereits!", "danger")
|
||||
@@ -2906,6 +2927,7 @@ def groups():
|
||||
all_users_all=all_users_all,
|
||||
permission_catalog=PERMISSIONS,
|
||||
permission_row_types=PERMISSION_ROW_TYPES,
|
||||
group_row_types=GROUP_ROW_TYPES,
|
||||
permission_labels=PERMISSION_LABELS,
|
||||
)
|
||||
|
||||
|
||||
@@ -1057,23 +1057,55 @@ select {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 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; }
|
||||
/* Rechte-Übersicht (Gruppen-Detailansicht): die Top-Level-Bereiche
|
||||
(Geräte/Logs/Einstellungen) stehen als kompakte, schmale Spalten
|
||||
nebeneinander statt untereinander. Kopfzeile der Tabelle = Bereichsname
|
||||
+ Kill-Switch-Kästchen (eng beieinander, linksbündig) in der ERSTEN
|
||||
Zelle, gefolgt von den Buchstaben-Spalten (R/W/E/D) — Name/Kästchen und
|
||||
Buchstaben stehen also in derselben Zeile. Tabellenkörper = Unterpunkte
|
||||
als eingerückte Zeilen, Spalten je Bereich nur die dort tatsächlich
|
||||
genutzten Rechte. Legende steht direkt unter der Zeile (.permission-legend). */
|
||||
.permission-legend {
|
||||
font-size: 12px;
|
||||
color: var(--text-faint);
|
||||
margin-top: 14px;
|
||||
}
|
||||
.permission-groups-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 28px;
|
||||
}
|
||||
.permission-group-col {
|
||||
/* Nicht auf eine feste Breite zwingen — Bereiche mit mehr Spalten (z.B.
|
||||
Geräte mit R/W/E/D) brauchen mehr Platz als welche mit weniger (z.B.
|
||||
Logs mit nur R). Feste/zu schmale Breiten haben die D-Spalte bei
|
||||
Geräte zuvor lautlos in den Overflow geschoben. */
|
||||
flex: 0 0 auto;
|
||||
padding-right: 28px;
|
||||
border-right: 1px solid var(--border-soft);
|
||||
}
|
||||
.permission-group-col:last-child { border-right: none; padding-right: 0; }
|
||||
.permission-group-header-cell { padding-left: 0 !important; }
|
||||
.permission-group-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.permission-group-name { font-size: 14px; font-weight: 650; color: var(--text); text-transform: none; letter-spacing: normal; font-family: inherit; }
|
||||
.permission-table {
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.permission-table th, .permission-table td {
|
||||
padding: 7px 14px;
|
||||
text-align: center;
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
}
|
||||
.permission-table th:not(:first-child), .permission-table td:not(:first-child) {
|
||||
text-align: center;
|
||||
}
|
||||
.permission-table thead th { padding-bottom: 10px; border-bottom: 1px solid var(--border-soft); }
|
||||
.permission-table th {
|
||||
font-size: 11.5px;
|
||||
font-weight: 650;
|
||||
@@ -1083,14 +1115,26 @@ select {
|
||||
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 td:first-child, .permission-table th:first-child { text-align: left; padding-right: 14px; padding-left: 0; }
|
||||
.permission-table tbody tr:last-child td { border-bottom: none; }
|
||||
.permission-row-label { color: var(--text-dim); }
|
||||
.permission-row-label { color: var(--text-dim); padding-left: 34px !important; }
|
||||
/* Sichtbares Ausgrauen, solange der Kill-Switch des Bereichs aus ist —
|
||||
zusätzlich zum disabled-Attribut, das der Browser nur dezent abblendet
|
||||
(siehe applyPermissionGating() im Script-Block). */
|
||||
.permission-table tbody.permission-locked { opacity: 0.35; }
|
||||
.permission-table input[type="checkbox"] {
|
||||
width: 16px; height: 16px;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
/* Explizit zentrieren (statt sich auf text-align + native Checkbox-Ränder
|
||||
zu verlassen, die je nach Browser/OS leicht asymmetrisch sind) — nur für
|
||||
die Rechte-Checkboxen im Tabellenkörper, nicht für das Bereich-Kästchen
|
||||
in der Kopfzeile (das steht bewusst inline neben dem Namen). */
|
||||
.permission-table tbody input[type="checkbox"] {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.permission-table input[type="checkbox"]:disabled { cursor: not-allowed; }
|
||||
.permission-cb-na { opacity: 0.25; accent-color: var(--text-faint) !important; }
|
||||
|
||||
|
||||
@@ -160,9 +160,16 @@
|
||||
form.addEventListener("submit", function (e) {
|
||||
if (form.dataset.confirmed === "1") return;
|
||||
e.preventDefault();
|
||||
// Welcher Button hat den Submit ausgelöst? Muss beim erneuten
|
||||
// requestSubmit() explizit mitgegeben werden — sonst geht bei
|
||||
// Formularen, die ihre ID über name/value des Submit-Buttons
|
||||
// transportieren (statt über ein <input type="hidden">), dieses
|
||||
// Feld beim Neu-Absenden stillschweigend verloren (requestSubmit()
|
||||
// ohne Argument zählt als "kein Button aktiviert").
|
||||
const submitter = e.submitter;
|
||||
window.confirmAction(form.getAttribute("data-confirm"), () => {
|
||||
form.dataset.confirmed = "1";
|
||||
form.requestSubmit ? form.requestSubmit() : form.submit();
|
||||
form.requestSubmit ? form.requestSubmit(submitter) : form.submit();
|
||||
}, form.getAttribute("data-confirm-title"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,53 +5,50 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{# 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).
|
||||
{# Rendert einen Bereich (Geräte/Logs/Einstellungen) als kompakte, schmale
|
||||
Spalte: eigene Kopfzeile mit Bereichsname + einem Kästchen (der
|
||||
Kill-Switch, "Bereich sichtbar"), darunter eine kleine Tabelle mit den
|
||||
Unterpunkten (Clients, Switche, ...) als Zeilen und nur den für diesen
|
||||
Bereich tatsächlich genutzten Rechten (group_row_types[group_key]) als
|
||||
Spalten — Logs/Einstellungen zeigen z.B. nur R/W/E, nur Geräte zeigt
|
||||
zusätzlich D (PoE-Neustart über Dashboard, ausschließlich bei Clients).
|
||||
"Ä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
|
||||
Löschen-Recht. Zellen ohne passendes Recht für die jeweilige Zeile (z.B.
|
||||
"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) %}
|
||||
<div class="permission-area">
|
||||
(Kill-Switch) nicht gesetzt ist. Mehrere Bereiche stehen per Flexbox
|
||||
nebeneinander (siehe .permission-groups-row), damit die ganze
|
||||
Rechteübersicht einer Gruppe in einer kompakten Zeile Platz hat. #}
|
||||
{% macro permission_table(group, group_key, checked_keys, readonly) %}
|
||||
{% set row_types = group_row_types[group_key] %}
|
||||
<div class="permission-group-col">
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="permission-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ group['label'] }}</th>
|
||||
{% for row_key, row_letter, row_label in permission_row_types %}
|
||||
<th class="permission-group-header-cell">
|
||||
<label class="permission-group-toggle">
|
||||
<input type="checkbox" name="permissions" value="{{ group['view_key'] }}"
|
||||
title="{{ permission_labels.get(group['view_key'], group['label']) }}"
|
||||
{% if group['view_key'] in checked_keys %}checked{% endif %}
|
||||
{% if readonly %}disabled{% endif %}
|
||||
class="permission-area-toggle-cb">
|
||||
<span class="permission-group-name">{{ group['label'] }}</span>
|
||||
</label>
|
||||
</th>
|
||||
{% for row_key, row_letter, row_label in row_types %}
|
||||
<th title="{{ row_label }}">{{ row_letter }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="permission-row-label">{{ group['label'] }} (gesamt)</td>
|
||||
{% for row_key, row_letter, row_label in permission_row_types %}
|
||||
<td>
|
||||
{% if row_key == 'view' %}
|
||||
<input type="checkbox" name="permissions" value="{{ group['view_key'] }}"
|
||||
title="{{ permission_labels.get(group['view_key'], group['label']) }}"
|
||||
{% if group['view_key'] in checked_keys %}checked{% endif %}
|
||||
{% if readonly %}disabled{% endif %}
|
||||
class="permission-area-toggle-cb">
|
||||
{% else %}
|
||||
<input type="checkbox" disabled class="permission-cb-na" tabindex="-1">
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for child_key, child in group['children'].items() %}
|
||||
<tr>
|
||||
<td class="permission-row-label">{{ child['label'] }}</td>
|
||||
{% for row_key, row_letter, row_label in permission_row_types %}
|
||||
{% for row_key, row_letter, row_label in row_types %}
|
||||
{% set perm_key = child['rows'].get(row_key) %}
|
||||
<td>
|
||||
{% if perm_key %}
|
||||
@@ -74,9 +71,16 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro permission_tree(checked_keys, readonly) %}
|
||||
<div class="permission-groups-row">
|
||||
{% for group_key, group in permission_catalog.items() %}
|
||||
{{ permission_table(group, checked_keys, readonly) }}
|
||||
{{ permission_table(group, group_key, checked_keys, readonly) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="permission-legend">
|
||||
<strong>R</strong> = Read (Lesen) · <strong>W</strong> = Write (Anlegen) ·
|
||||
<strong>E</strong> = Edit (Ändern, inkl. Löschen) ·
|
||||
<strong>D</strong> = Dashboard (PoE-Neustart über das Dashboard-Popup, nur bei Clients)
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div class="section-head">
|
||||
@@ -86,12 +90,9 @@
|
||||
Ü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. Die Zeile „... (gesamt)“
|
||||
muss gelesen werden dürfen, bevor die Zeilen darunter greifen — ausgegraute Kästchen gelten für
|
||||
die jeweilige Zeile nicht.
|
||||
<strong>R</strong> = Lesen (Read) · <strong>W</strong> = Schreiben/Anlegen (Write) ·
|
||||
<strong>E</strong> = Ändern inkl. Löschen (Edit) ·
|
||||
<strong>D</strong> = Dashboard-Neustart (nur bei Clients; automatische Neustarts bei Ausfall bleiben davon unberührt)
|
||||
„Admin“ und „Benutzer“ sind feste Systemgruppen und nicht veränderbar. Das Kästchen neben dem
|
||||
Bereichsnamen (Geräte/Logs/Einstellungen) muss gesetzt sein, bevor die Rechte darunter greifen —
|
||||
automatische Neustarts bei Ausfall bleiben von „D“ unberührt. Legende direkt bei den Rechten.
|
||||
</div>
|
||||
</div>
|
||||
{% if current_user.has_permission('groups.create') %}
|
||||
@@ -263,9 +264,11 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Modal: Neue Gruppe -->
|
||||
<!-- Modal: Neue Gruppe — Rechte direkt beim Anlegen setzbar, Mitglieder
|
||||
werden danach über die Gruppentabelle zugeordnet (die Gruppe muss
|
||||
dafür erst existieren). -->
|
||||
<div class="modal-overlay" id="addGroupModal">
|
||||
<div class="modal" style="max-width:380px;">
|
||||
<div class="modal" style="max-width:900px;">
|
||||
<form method="post">
|
||||
<div class="modal-header">
|
||||
<h3>Neue Gruppe</h3>
|
||||
@@ -276,8 +279,9 @@
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" required placeholder="z.B. Facility-Team">
|
||||
<div class="field-hint">Rechte und Mitglieder werden danach über die Gruppentabelle eingestellt.</div>
|
||||
<div class="field-hint">Mitglieder werden danach über die Gruppentabelle zugeordnet.</div>
|
||||
</div>
|
||||
{{ permission_tree([], false) }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
|
||||
@@ -306,15 +310,20 @@ function toggleDetail(id) {
|
||||
// (Admin/Systemgruppen) bleiben unangetastet, deren Checkboxen sind ohnehin
|
||||
// alle disabled.
|
||||
function applyPermissionGating() {
|
||||
document.querySelectorAll(".permission-area").forEach(function (area) {
|
||||
document.querySelectorAll(".permission-group-col").forEach(function (area) {
|
||||
const toggle = area.querySelector(".permission-area-toggle-cb");
|
||||
if (!toggle || toggle.disabled) return;
|
||||
const tbody = area.querySelector("tbody");
|
||||
const children = area.querySelectorAll(".permission-child-cb");
|
||||
const sync = function () {
|
||||
children.forEach(function (cb) {
|
||||
cb.disabled = !toggle.checked;
|
||||
if (!toggle.checked) cb.checked = false;
|
||||
});
|
||||
// Zusätzlich zum disabled-Attribut (das der Browser nur dezent
|
||||
// abblendet) sichtbar ausgrauen, wie gefordert — sonst fällt
|
||||
// "Bereich gesperrt" auf den ersten Blick kaum auf.
|
||||
if (tbody) tbody.classList.toggle("permission-locked", !toggle.checked);
|
||||
};
|
||||
toggle.addEventListener("change", sync);
|
||||
sync();
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
{% endif %}
|
||||
{% if current_user.has_permission('users.edit') and may_touch_target %}
|
||||
<form method="post" data-confirm="Willst du „{{ u['username'] }}“ wirklich löschen?">
|
||||
<button type="submit" name="delete_user" value="{{ u['id'] }}" class="icon-btn" style="color:var(--danger);" title="Löschen">
|
||||
<input type="hidden" name="delete_user" value="{{ u['id'] }}">
|
||||
<button type="submit" class="icon-btn" style="color:var(--danger);" title="Löschen">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0l-1 14a2 2 0 01-2 2H7a2 2 0 01-2-2L4 6"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user