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:
2026-08-11 09:52:01 +02:00
co-authored by Claude Sonnet 5
parent 8e4a874943
commit 7d7239e1af
6 changed files with 172 additions and 91 deletions
+55 -11
View File
@@ -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; }
+8 -1
View File
@@ -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"));
});
});