Zugangsdaten-Entitaet, Gruppen-Tabelle mit Aufklappansicht, Vor-/Nachname, Dashboard-Sektionen

- Zugangsdaten (SSH-Logins) als eigenstaendige, wiederverwendbare Entitaet
  statt direkt am Switch; inline Neuanlage beim Switch-Erstellen moeglich;
  automatische Migration bestehender Switch-Logins
- Gruppen-Seite als Tabelle mit Aufklapp-Zeile fuer Rechte (Akkordeon),
  Admin als feste Systemzeile (Mitgliederverwaltung ueber is_admin),
  Standardgruppe 'Benutzer' mit allen Ansichtsrechten (devices.view,
  switches.view), automatische Zuordnung neuer/verwaister Benutzer
- Users-Seite: Icon-Buttons statt Text, Bearbeiten+Passwortaenderung in
  einem Modal zusammengefuehrt, Gruppe/Admin-Zuweisung ueber eigenen
  Zuweisen-Button (wie Switch-Zuordnung bei Devices), Vor-/Nachname
- Dashboard: einheitliche Kachelansicht mit/ohne Login, drei sortierte
  Abschnitte (Offline/Online/Deaktiviert), Kachel-Suchfilter, Bootstrap-
  artiges Grid (max. 6 Spalten), Aktivieren-Option im Popup fuer
  deaktivierte Geraete, Countdown serverseitig korrekt geseedet
- Sidebar dauerhaft einklappbar (Desktop, persistent via localStorage)
- Devices-Tabelle: Aktions-Buttons nebeneinander statt untereinander

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 13:30:47 +02:00
co-authored by Claude Sonnet 5
parent 08d8d1f738
commit fa6bb2935f
12 changed files with 1108 additions and 345 deletions
+39 -3
View File
@@ -265,7 +265,7 @@ button { font-family: inherit; }
}
.hamburger {
display: none;
display: inline-flex;
width: 36px; height: 36px;
align-items: center; justify-content: center;
border-radius: var(--radius-sm);
@@ -273,6 +273,14 @@ button { font-family: inherit; }
background: var(--bg-card);
color: var(--text);
cursor: pointer;
flex-shrink: 0;
}
/* Desktop: dauerhaftes Einklappen der Sidebar (nicht die mobile
Off-Canvas-Logik unten, die bleibt für schmale Screens bestehen). */
@media (min-width: 901px) {
.sidebar.collapsed { transform: translateX(-100%); }
.main.sidebar-collapsed { margin-left: 0; }
}
.content {
@@ -442,11 +450,31 @@ button { font-family: inherit; }
Device grid (dashboard)
========================================================================== */
/* Bootstrap-artiges Raster: feste Spaltenzahl je Breakpoint statt frei
fließendem auto-fill — dadurch maximal 6 Kacheln nebeneinander auf
breiten Screens, dafür beliebig viele Zeilen untereinander. */
.device-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
grid-template-columns: repeat(2, 1fr);
gap: 14px;
}
@media (min-width: 480px) { .device-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 720px) { .device-grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 960px) { .device-grid { grid-template-columns: repeat(5, 1fr); } }
@media (min-width: 1200px) { .device-grid { grid-template-columns: repeat(6, 1fr); } }
.dash-section { margin-bottom: 26px; }
.dash-section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
font-weight: 650;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 12px;
}
.device-card {
position: relative;
@@ -582,7 +610,8 @@ table.data-table {
.mono { font-family: var(--font-mono); font-size: 12.5px; color: var(--text-dim); }
.cell-name { font-weight: 600; }
.row-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.row-actions { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.row-actions form { display: flex; }
.empty-row td {
text-align: center;
@@ -590,6 +619,13 @@ table.data-table {
color: var(--text-faint);
}
.group-detail-row td {
padding: 20px 24px 22px;
background: var(--bg-elevated);
}
.group-detail-row:hover { background: transparent; }
#chev-admin, [id^="chev-"] { transition: transform 0.15s ease; }
/* ==========================================================================
Forms
========================================================================== */
+20 -1
View File
@@ -39,9 +39,14 @@
function initSidebar() {
const sidebar = document.querySelector(".sidebar");
const backdrop = document.querySelector(".sidebar-backdrop");
const main = document.querySelector(".main");
const toggles = document.querySelectorAll("[data-sidebar-toggle]");
if (!sidebar) return;
const DESKTOP_BREAKPOINT = 900;
const COLLAPSE_KEY = "poe-sidebar-collapsed";
// Mobile: temporäres Überlagern per Hamburger + Backdrop.
function open() {
sidebar.classList.add("open");
backdrop && backdrop.classList.add("open");
@@ -50,8 +55,22 @@
sidebar.classList.remove("open");
backdrop && backdrop.classList.remove("open");
}
// Desktop: dauerhaftes Ein-/Ausklappen, über Neuladen hinweg gemerkt.
function setCollapsed(collapsed) {
sidebar.classList.toggle("collapsed", collapsed);
main && main.classList.toggle("sidebar-collapsed", collapsed);
localStorage.setItem(COLLAPSE_KEY, collapsed ? "1" : "0");
}
if (localStorage.getItem(COLLAPSE_KEY) === "1") setCollapsed(true);
toggles.forEach((btn) => btn.addEventListener("click", () => {
sidebar.classList.contains("open") ? close() : open();
if (window.innerWidth <= DESKTOP_BREAKPOINT) {
sidebar.classList.contains("open") ? close() : open();
} else {
setCollapsed(!sidebar.classList.contains("collapsed"));
}
}));
backdrop && backdrop.addEventListener("click", close);
document.querySelectorAll(".nav-item").forEach((a) => a.addEventListener("click", close));