1. Manuelle Reservierungen (dhcp_manual_reservations) fuer Geraete, die nicht als Client in dieser App gepflegt werden -- bewusst eine eigene, explizite Aktion (MAC/IP/Name), damit kein externes Geraet unueberwacht automatisch eine feste IP bekommt. _dhcp_reservation_candidates fuehrt automatische (aus devices) und manuelle Reservierungen jetzt zusammen, case-insensitiv nach MAC dedupliziert (automatisch gewinnt bei Konflikt). Automatische Reservierungen sind in der Tabelle nicht loeschbar (Schloss-Symbol statt Button, sie entstehen/verschwinden mit den Client-Stammdaten), manuelle schon. Live verifiziert: Hinzufuegen, doppelte MAC ablehnen, ungueltige MAC ablehnen, generierte Kea-Config mit gemischten Auto-/manuellen Reservierungen besteht kea-dhcp4 -t. 2. Standard-DHCP-Optionen um 10 weitere verifizierte Optionen erweitert (u.a. host-name/12 -- Kea-Name mit Bindestrich, NICHT "hostname", live gegen echtes Kea geprueft) -- jetzt 57 insgesamt. Standard-Optionen sind nicht mehr loeschbar (Backend lehnt es ab, UI zeigt ein Schloss-Symbol statt Loeschen-Button) -- vorher liess sich eine vorbefuellte Options- Definition versehentlich entfernen. 3. Options-Tabellen (global UND pro Reservierung) zeigen jetzt nur noch tatsaechlich genutzte Options -- eigene immer, Standard-Optionen nur mit gesetztem Wert. Weitere Standard-Optionen kommen ueber ein Dropdown "+ Option hinzufuegen" dazu statt permanent alle 57 als leere Felder zu zeigen. Das neu eingeblendete Feld wird automatisch in den sichtbaren Bereich gescrollt und fokussiert (bei bis zu 57 moeglichen Optionen kann das Feld sonst weit unterhalb des sichtbaren Modal-Ausschnitts liegen und es wirkt so, als waere nichts passiert). 4. Lease-Countdown-Fix: die Ablaufzeit wird jetzt explizit als UTC ausgewiesen (Kea arbeitet intern in UTC-Epoch) und zusaetzlich als live tickender Countdown angezeigt (gleiches Prinzip wie der bestehende "Naechste Pruefung"-Timer). Vorher wirkte eine reine Absolutzeit auf der nicht selbst aktualisierenden Seite bei den kurzen Standard-Lease-Zeiten (600s) schnell "schon abgelaufen", sobald etwas Zeit vergangen war, bis man tatsaechlich hinschaut -- live im echten Betrieb genau als dieses Missverstaendnis beobachtet und anhand der rohen Kea-CSV verifiziert, dass die richtige Spalte (expire) korrekt gelesen wird. 5. UI-Feinschliff: "In Datei schreiben" bei den Reservierungen heisst jetzt "Speichern" und ist der primaere (orange) Button, "Reservierung hinzufuegen" sekundaer -- das Hinzufuegen legt nur einen Datensatz an, erst "Speichern" schreibt die tatsaechlich wirksame Kea-Config. Alles live auf der Test-VM deployt und verifiziert (Playwright fuer die Dropdown-Sichtbarkeit/Fokus-Interaktion, curl fuer die Backend-Validierung, kea-dhcp4 -t fuer die generierte Konfiguration), keine Fehler im journalctl-Log.
1234 lines
37 KiB
CSS
1234 lines
37 KiB
CSS
/* ==========================================================================
|
|
PoE Manager — Design System
|
|
========================================================================== */
|
|
|
|
:root {
|
|
--bg: #0e1015;
|
|
--bg-elevated: #14171e;
|
|
--bg-card: #1a1e27;
|
|
--bg-card-hover: #20242f;
|
|
--bg-input: #12151b;
|
|
--border: #2a2f3b;
|
|
--border-soft: #21252f;
|
|
--text: #eef0f4;
|
|
--text-dim: #9aa2b4;
|
|
--text-faint: #656d80;
|
|
--accent: #ff7a1a;
|
|
--accent-strong: #ff8f3d;
|
|
--accent-dim: rgba(255, 122, 26, 0.14);
|
|
--accent-ring: rgba(255, 122, 26, 0.35);
|
|
--success: #2fd07a;
|
|
--success-dim: rgba(47, 208, 122, 0.14);
|
|
--danger: #f0475c;
|
|
--danger-dim: rgba(240, 71, 92, 0.14);
|
|
--warning: #f5a623;
|
|
--warning-dim: rgba(245, 166, 35, 0.14);
|
|
--muted: #6b7280;
|
|
--muted-dim: rgba(107, 114, 128, 0.16);
|
|
--radius-sm: 8px;
|
|
--radius: 14px;
|
|
--radius-lg: 20px;
|
|
--shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.55);
|
|
--shadow-sm: 0 4px 14px -6px rgba(0, 0, 0, 0.4);
|
|
--sidebar-w: 244px;
|
|
--topbar-h: 64px;
|
|
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
|
|
color-scheme: dark;
|
|
}
|
|
|
|
[data-theme="light"] {
|
|
--bg: #f3f4f7;
|
|
--bg-elevated: #ffffff;
|
|
--bg-card: #ffffff;
|
|
--bg-card-hover: #f8f8fb;
|
|
--bg-input: #ffffff;
|
|
--border: #e3e5ec;
|
|
--border-soft: #ebedf2;
|
|
--text: #1a1d24;
|
|
--text-dim: #565d6d;
|
|
--text-faint: #8a90a0;
|
|
--accent-dim: rgba(255, 122, 26, 0.1);
|
|
--muted-dim: rgba(107, 114, 128, 0.12);
|
|
--shadow: 0 10px 30px -14px rgba(20, 20, 30, 0.18);
|
|
--shadow-sm: 0 4px 14px -8px rgba(20, 20, 30, 0.14);
|
|
color-scheme: light;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
html, body {
|
|
height: 100%;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: var(--font);
|
|
font-size: 14.5px;
|
|
line-height: 1.5;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
a { color: inherit; text-decoration: none; }
|
|
|
|
h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
|
p { margin: 0; }
|
|
|
|
button { font-family: inherit; }
|
|
|
|
::selection { background: var(--accent-dim); }
|
|
|
|
/* Scrollbars */
|
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
|
|
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }
|
|
|
|
/* ==========================================================================
|
|
App shell
|
|
========================================================================== */
|
|
|
|
.app-shell {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* ---- Sidebar ---- */
|
|
|
|
.sidebar {
|
|
width: var(--sidebar-w);
|
|
flex-shrink: 0;
|
|
background: var(--bg-elevated);
|
|
border-right: 1px solid var(--border-soft);
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: fixed;
|
|
top: 0; bottom: 0; left: 0;
|
|
z-index: 40;
|
|
transition: transform 0.25s ease;
|
|
}
|
|
|
|
.sidebar-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 22px 20px 16px;
|
|
}
|
|
|
|
.sidebar-brand .brand-name {
|
|
font-weight: 700;
|
|
font-size: 15px;
|
|
color: var(--text);
|
|
letter-spacing: -0.01em;
|
|
}
|
|
.sidebar-brand .brand-sub {
|
|
display: block;
|
|
font-size: 11px;
|
|
color: var(--text-faint);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sidebar-nav {
|
|
flex: 1;
|
|
padding: 8px 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 10px 12px;
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text-dim);
|
|
font-weight: 550;
|
|
font-size: 13.5px;
|
|
transition: background 0.15s ease, color 0.15s ease;
|
|
}
|
|
|
|
.nav-item svg { width: 18px; height: 18px; flex-shrink: 0; opacity: 0.85; }
|
|
|
|
.nav-item:hover { background: var(--bg-card-hover); color: var(--text); }
|
|
|
|
.nav-item.active {
|
|
background: var(--accent-dim);
|
|
color: var(--accent-strong);
|
|
}
|
|
.nav-item.active svg { opacity: 1; }
|
|
|
|
.nav-divider {
|
|
height: 1px;
|
|
background: var(--border-soft);
|
|
margin: 10px 8px;
|
|
}
|
|
|
|
/* Nav-Gruppe mit Untermenü (z.B. "Logs" -> Live-Log / Änderungslog) */
|
|
.nav-group-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
padding: 10px 12px;
|
|
border: none;
|
|
background: none;
|
|
color: var(--text-dim);
|
|
font-weight: 550;
|
|
font-size: 13.5px;
|
|
font-family: inherit;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
transition: background 0.15s ease, color 0.15s ease;
|
|
}
|
|
.nav-group-toggle svg { width: 18px; height: 18px; flex-shrink: 0; opacity: 0.85; }
|
|
.nav-group-toggle:hover { background: var(--bg-card-hover); color: var(--text); }
|
|
.nav-group-toggle .nav-chevron { margin-left: auto; width: 14px; height: 14px; transition: transform 0.15s ease; opacity: 0.6; }
|
|
.nav-group.expanded .nav-group-toggle .nav-chevron { transform: rotate(90deg); }
|
|
.nav-group.active-group .nav-group-toggle { color: var(--accent-strong); }
|
|
.nav-group-children {
|
|
display: none;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
padding-left: 30px;
|
|
margin-top: 2px;
|
|
}
|
|
.nav-group.expanded .nav-group-children { display: flex; }
|
|
.nav-group-children .nav-item { font-size: 13px; padding: 8px 12px; }
|
|
|
|
/* Kacheln nebeneinander (z.B. Im-/Export, Konto-Seite) */
|
|
.settings-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
|
gap: 24px;
|
|
align-items: start;
|
|
}
|
|
|
|
/* Navbar-Reihenfolge (Settings) */
|
|
.nav-order-list {
|
|
list-style: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
margin-bottom: 16px;
|
|
}
|
|
.nav-order-list li {
|
|
padding: 8px 12px;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius-sm);
|
|
font-size: 13.5px;
|
|
font-weight: 550;
|
|
}
|
|
.nav-order-row { display: flex; align-items: center; justify-content: space-between; }
|
|
.nav-order-list .nav-order-actions { display: flex; gap: 4px; }
|
|
.nav-order-list .icon-btn { width: 28px; height: 28px; }
|
|
/* Unterpunkte je Gruppe — eingerückt, innerhalb ihrer Gruppe umsortierbar
|
|
(moveNavItem() bewegt nur innerhalb der jeweiligen <ul>, mischt also nie
|
|
Unterpunkte verschiedener Gruppen). */
|
|
.nav-order-sublist {
|
|
list-style: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
margin: 10px 0 2px 22px;
|
|
}
|
|
.nav-order-sublist li { font-weight: 500; font-size: 12.5px; background: var(--bg-elevated); }
|
|
|
|
.sidebar-footer {
|
|
padding: 14px 12px 18px;
|
|
border-top: 1px solid var(--border-soft);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.user-chip {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 10px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 30px; height: 30px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--accent), #ff4d6d);
|
|
display: flex; align-items: center; justify-content: center;
|
|
color: #fff; font-weight: 700; font-size: 12.5px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.user-meta { flex: 1; min-width: 0; overflow: hidden; }
|
|
.user-chip .icon-btn { flex-shrink: 0; width: 30px; height: 30px; }
|
|
.user-chip .icon-btn svg { width: 15px; height: 15px; }
|
|
.user-meta .u-name { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.user-meta .u-role { font-size: 11px; color: var(--text-faint); }
|
|
|
|
.footer-actions { display: flex; gap: 6px; }
|
|
.footer-actions .icon-btn { flex: 1; }
|
|
|
|
.sidebar-toggle {
|
|
display: none;
|
|
}
|
|
|
|
.sidebar-backdrop {
|
|
display: none;
|
|
position: fixed; inset: 0;
|
|
background: rgba(0,0,0,0.5);
|
|
z-index: 39;
|
|
}
|
|
|
|
/* ---- Main area ---- */
|
|
|
|
.main {
|
|
margin-left: var(--sidebar-w);
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.main.no-sidebar { margin-left: 0; }
|
|
|
|
.topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 10px 28px;
|
|
min-height: var(--topbar-h);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 20;
|
|
background: color-mix(in srgb, var(--bg) 82%, transparent);
|
|
backdrop-filter: blur(10px);
|
|
border-bottom: 1px solid var(--border-soft);
|
|
}
|
|
|
|
.topbar-left { display: flex; align-items: center; gap: 14px; min-width: 0; flex: 1; z-index: 1; }
|
|
.topbar-title { font-size: 19px; font-weight: 700; letter-spacing: -0.015em; }
|
|
.topbar-sub { color: var(--text-faint); font-size: 12.5px; margin-top: 2px; }
|
|
|
|
.topbar-right { display: flex; align-items: center; gap: 10px; flex: 1; justify-content: flex-end; z-index: 1; }
|
|
|
|
/* Logo mittig über die volle Höhe der Topbar, unabhängig vom Padding —
|
|
passt sich per height:100% der Balkenhöhe an, egal wie breit es dabei
|
|
wird. pointer-events:none, damit es Klicks auf Titel/Buttons nicht
|
|
blockiert, falls es auf schmalen Screens überlappt. */
|
|
.topbar-logo {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 0; bottom: 0;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 9px 0;
|
|
pointer-events: none;
|
|
}
|
|
.topbar-logo img {
|
|
height: 100%;
|
|
width: auto;
|
|
display: block;
|
|
}
|
|
|
|
.hamburger {
|
|
display: inline-flex;
|
|
width: 36px; height: 36px;
|
|
align-items: center; justify-content: center;
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid var(--border);
|
|
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 {
|
|
padding: 24px 28px 48px;
|
|
max-width: 1320px;
|
|
width: 100%;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Buttons
|
|
========================================================================== */
|
|
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
padding: 9px 16px;
|
|
font-size: 13.5px;
|
|
font-weight: 600;
|
|
border-radius: 10px;
|
|
border: 1px solid transparent;
|
|
cursor: pointer;
|
|
transition: transform 0.06s ease, background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
|
white-space: nowrap;
|
|
user-select: none;
|
|
}
|
|
.btn:active { transform: translateY(1px); }
|
|
.btn svg { width: 15px; height: 15px; }
|
|
|
|
.btn-primary {
|
|
background: var(--accent);
|
|
color: #1a0e03;
|
|
box-shadow: 0 6px 18px -6px rgba(255, 122, 26, 0.55);
|
|
}
|
|
.btn-primary:hover { background: var(--accent-strong); }
|
|
|
|
.btn-secondary {
|
|
background: var(--bg-card);
|
|
color: var(--text);
|
|
border-color: var(--border);
|
|
}
|
|
.btn-secondary:hover { background: var(--bg-card-hover); border-color: var(--text-faint); }
|
|
|
|
.btn-success { background: var(--success); color: #06210f; }
|
|
.btn-success:hover { filter: brightness(1.08); }
|
|
|
|
.btn-danger { background: transparent; color: var(--danger); border-color: var(--danger-dim); }
|
|
.btn-danger:hover { background: var(--danger-dim); }
|
|
|
|
.btn-ghost { background: transparent; color: var(--text-dim); border-color: transparent; }
|
|
.btn-ghost:hover { background: var(--bg-card-hover); color: var(--text); }
|
|
|
|
.btn-sm { padding: 6px 12px; font-size: 12.5px; border-radius: 8px; }
|
|
.btn-block { width: 100%; }
|
|
.btn:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
|
|
.icon-btn {
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
width: 34px; height: 34px;
|
|
border-radius: 9px;
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-card);
|
|
color: var(--text-dim);
|
|
cursor: pointer;
|
|
}
|
|
.icon-btn:hover { color: var(--text); border-color: var(--text-faint); }
|
|
.icon-btn svg { width: 16px; height: 16px; }
|
|
|
|
/* ==========================================================================
|
|
Cards / surfaces
|
|
========================================================================== */
|
|
|
|
.card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.card-pad { padding: 20px 22px; }
|
|
|
|
.section-head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
margin-bottom: 18px;
|
|
}
|
|
/* Beschreibungstext darf mehrzeilig umbrechen, ohne dass der Button
|
|
(z.B. "+ Neue Zugangsdaten") mit in die nächste Zeile rutscht — der
|
|
bleibt immer rechts oben in derselben Zeile. */
|
|
.section-head > div:first-child { flex: 1 1 auto; min-width: 0; }
|
|
.section-head > .btn { flex-shrink: 0; }
|
|
|
|
.section-head h2 { font-size: 20px; }
|
|
.section-head .hint { color: var(--text-faint); font-size: 12.5px; margin-top: 3px; }
|
|
|
|
.stat-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 12px;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius);
|
|
padding: 16px 18px;
|
|
}
|
|
.stat-card .stat-label { font-size: 12px; color: var(--text-faint); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
.stat-card .stat-value { font-size: 26px; font-weight: 700; margin-top: 6px; letter-spacing: -0.02em; }
|
|
.stat-card .stat-value.online { color: var(--success); }
|
|
.stat-card .stat-value.offline { color: var(--danger); }
|
|
.stat-card .stat-value.disabled { color: var(--muted); }
|
|
|
|
/* ==========================================================================
|
|
Badges / pills
|
|
========================================================================== */
|
|
|
|
.pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
font-weight: 650;
|
|
}
|
|
.pill svg { width: 12px; height: 12px; }
|
|
.pill::before {
|
|
content: "";
|
|
width: 6px; height: 6px;
|
|
border-radius: 50%;
|
|
background: currentColor;
|
|
flex-shrink: 0;
|
|
}
|
|
.pill.online { background: var(--success-dim); color: var(--success); }
|
|
.pill.offline { background: var(--danger-dim); color: var(--danger); }
|
|
.pill.unknown { background: var(--warning-dim); color: var(--warning); }
|
|
.pill.disabled { background: var(--muted-dim); color: var(--muted); }
|
|
.pill.admin { background: var(--accent-dim); color: var(--accent-strong); }
|
|
.pill.user { background: var(--muted-dim); color: var(--text-dim); }
|
|
.pill.action-pill { background: var(--muted-dim); color: var(--text-dim); }
|
|
.pill.action-pill::before { display: none; }
|
|
.pill.action-pill svg { width: 13px; height: 13px; }
|
|
|
|
.avatar-sm {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 26px;
|
|
height: 26px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
background: var(--muted-dim);
|
|
color: var(--text-dim);
|
|
font-size: 11.5px;
|
|
font-weight: 700;
|
|
flex-shrink: 0;
|
|
}
|
|
.avatar-placeholder { text-transform: uppercase; }
|
|
|
|
.timer-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
padding: 6px 13px;
|
|
border-radius: 999px;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
font-size: 12.5px;
|
|
font-weight: 600;
|
|
color: var(--text-dim);
|
|
}
|
|
.timer-pill .dot {
|
|
width: 7px; height: 7px; border-radius: 50%;
|
|
background: var(--success);
|
|
box-shadow: 0 0 0 0 var(--success-dim);
|
|
animation: pulse-dot 2s infinite;
|
|
}
|
|
@keyframes pulse-dot {
|
|
0% { box-shadow: 0 0 0 0 rgba(47,208,122,0.5); }
|
|
70% { box-shadow: 0 0 0 6px rgba(47,208,122,0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(47,208,122,0); }
|
|
}
|
|
|
|
.timer-pill-refresh {
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
width: 18px; height: 18px;
|
|
margin-left: 2px;
|
|
padding: 0;
|
|
border: none;
|
|
background: none;
|
|
color: var(--text-faint);
|
|
cursor: pointer;
|
|
border-radius: 50%;
|
|
}
|
|
.timer-pill-refresh svg { width: 12px; height: 12px; }
|
|
.timer-pill-refresh:hover { color: var(--accent-strong); background: var(--accent-dim); }
|
|
.timer-pill-refresh.spinning svg { animation: spin 0.8s linear infinite; }
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
/* ==========================================================================
|
|
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(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;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius);
|
|
padding: 16px 16px 14px;
|
|
cursor: pointer;
|
|
transition: border-color 0.15s ease, transform 0.1s ease, background 0.15s ease;
|
|
overflow: hidden;
|
|
}
|
|
.device-card:hover { border-color: var(--text-faint); background: var(--bg-card-hover); transform: translateY(-2px); }
|
|
.device-card:active { transform: translateY(0); }
|
|
.device-card.is-disabled { cursor: default; opacity: 0.65; }
|
|
.device-card.is-disabled:hover { transform: none; border-color: var(--border-soft); background: var(--bg-card); }
|
|
.device-card.is-readonly { cursor: default; }
|
|
.device-card.is-readonly:hover { transform: none; border-color: var(--border-soft); background: var(--bg-card); }
|
|
|
|
.device-card::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0; top: 0; bottom: 0;
|
|
width: 3px;
|
|
background: var(--muted);
|
|
}
|
|
.device-card[data-status="online"]::before { background: var(--success); }
|
|
.device-card[data-status="offline"]::before { background: var(--danger); }
|
|
.device-card[data-status="unbekannt"]::before { background: var(--warning); }
|
|
.device-card[data-status="disabled"]::before { background: var(--muted); }
|
|
|
|
.device-card .dc-top {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
|
|
.device-card .dc-icon {
|
|
width: 34px; height: 34px;
|
|
border-radius: 9px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: var(--muted-dim);
|
|
color: var(--muted);
|
|
flex-shrink: 0;
|
|
}
|
|
.device-card[data-status="online"] .dc-icon { background: var(--success-dim); color: var(--success); }
|
|
.device-card[data-status="offline"] .dc-icon { background: var(--danger-dim); color: var(--danger); }
|
|
.device-card[data-status="disabled"] .dc-icon { background: var(--muted-dim); color: var(--muted); }
|
|
.device-card .dc-icon svg { width: 17px; height: 17px; }
|
|
|
|
.device-card .dc-name {
|
|
margin-top: 12px;
|
|
font-weight: 650;
|
|
font-size: 14px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.device-card .dc-ip { color: var(--text-faint); font-size: 11.5px; margin-top: 2px; font-family: var(--font-mono); }
|
|
|
|
.device-card .dc-status { margin-top: 12px; }
|
|
|
|
.spinner {
|
|
width: 13px; height: 13px;
|
|
border-radius: 50%;
|
|
border: 2px solid currentColor;
|
|
border-right-color: transparent;
|
|
display: inline-block;
|
|
animation: spin 0.65s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
/* ==========================================================================
|
|
Tables
|
|
========================================================================== */
|
|
|
|
.table-wrap {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
padding: 14px 16px;
|
|
border-bottom: 1px solid var(--border-soft);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.search-input {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
min-width: 220px;
|
|
}
|
|
.search-input svg {
|
|
position: absolute; left: 11px;
|
|
width: 15px; height: 15px;
|
|
color: var(--text-faint);
|
|
pointer-events: none;
|
|
}
|
|
.search-input input[type="text"] { padding-left: 34px; }
|
|
|
|
table.data-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 13.5px;
|
|
}
|
|
|
|
.data-table thead th.sortable-col {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
white-space: nowrap;
|
|
}
|
|
.data-table thead th.sortable-col:hover { color: var(--text); }
|
|
.data-table thead th.sortable-col::after {
|
|
content: "⇅";
|
|
display: inline-block;
|
|
margin-left: 5px;
|
|
font-size: 10px;
|
|
opacity: 0.35;
|
|
}
|
|
.data-table thead th.sortable-col.sort-asc::after { content: "▲"; opacity: 0.85; }
|
|
.data-table thead th.sortable-col.sort-desc::after { content: "▼"; opacity: 0.85; }
|
|
|
|
.data-table thead th {
|
|
text-align: left;
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--text-faint);
|
|
font-weight: 650;
|
|
padding: 11px 16px;
|
|
border-bottom: 1px solid var(--border-soft);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.data-table tbody td {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--border-soft);
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.data-table tbody tr:last-child td { border-bottom: none; }
|
|
.data-table tbody tr { transition: background 0.12s ease; }
|
|
.data-table tbody tr:hover { background: var(--bg-card-hover); }
|
|
|
|
.mono { font-family: var(--font-mono); font-size: 12.5px; color: var(--text-dim); }
|
|
.cell-name { font-weight: 600; }
|
|
.row-actions { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
|
|
.row-actions form { display: flex; }
|
|
|
|
.empty-row td {
|
|
text-align: center;
|
|
padding: 40px 16px;
|
|
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
|
|
========================================================================== */
|
|
|
|
.field { margin-bottom: 15px; }
|
|
.field label {
|
|
display: block;
|
|
font-size: 12.5px;
|
|
font-weight: 650;
|
|
color: var(--text-dim);
|
|
margin-bottom: 6px;
|
|
}
|
|
.field .field-hint { font-size: 11.5px; color: var(--text-faint); margin-top: 5px; }
|
|
|
|
input[type="text"], input[type="password"], input[type="number"], input[type="email"], select, textarea {
|
|
width: 100%;
|
|
background: var(--bg-input);
|
|
border: 1px solid var(--border);
|
|
color: var(--text);
|
|
padding: 9px 12px;
|
|
border-radius: 9px;
|
|
font-size: 13.5px;
|
|
font-family: inherit;
|
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
input::placeholder { color: var(--text-faint); }
|
|
|
|
/* Datei-Auswahl (z.B. Import/Export) — der native Button/"keine Datei
|
|
ausgewählt"-Text folgt sonst nicht dem Farbschema und ist im Dark Mode
|
|
kaum lesbar (dunkler Text auf dunklem Grund). */
|
|
input[type="file"] {
|
|
width: 100%;
|
|
background: var(--bg-input);
|
|
border: 1px solid var(--border);
|
|
color: var(--text);
|
|
border-radius: 9px;
|
|
font-size: 13px;
|
|
padding: 6px 8px;
|
|
}
|
|
input[type="file"]::file-selector-button,
|
|
input[type="file"]::-webkit-file-upload-button {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
background: var(--bg-card-hover);
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
border-radius: 7px;
|
|
padding: 7px 12px;
|
|
margin-right: 10px;
|
|
font-size: 12.5px;
|
|
font-weight: 600;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease, border-color 0.15s ease;
|
|
}
|
|
input[type="file"]::file-selector-button:hover,
|
|
input[type="file"]::-webkit-file-upload-button:hover {
|
|
background: var(--bg-card);
|
|
border-color: var(--text-faint);
|
|
}
|
|
input:focus, select:focus, textarea:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px var(--accent-dim);
|
|
}
|
|
input.is-invalid { border-color: var(--danger) !important; box-shadow: 0 0 0 3px var(--danger-dim) !important; }
|
|
.invalid-feedback { display: none; color: var(--danger); font-size: 11.5px; margin-top: 5px; }
|
|
input.is-invalid + .invalid-feedback,
|
|
input.is-invalid ~ .invalid-feedback { display: block; }
|
|
|
|
select {
|
|
appearance: none;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%239aa2b4'%3E%3Cpath fill-rule='evenodd' d='M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z' clip-rule='evenodd'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 10px center;
|
|
padding-right: 32px;
|
|
}
|
|
|
|
.switch-check {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 9px;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.switch-check input { display: none; }
|
|
.switch-check .track {
|
|
width: 38px; height: 22px;
|
|
border-radius: 999px;
|
|
background: var(--muted-dim);
|
|
border: 1px solid var(--border);
|
|
position: relative;
|
|
transition: background 0.15s ease;
|
|
flex-shrink: 0;
|
|
}
|
|
.switch-check .track::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 2px; left: 2px;
|
|
width: 16px; height: 16px;
|
|
border-radius: 50%;
|
|
background: var(--text-faint);
|
|
transition: transform 0.15s ease, background 0.15s ease;
|
|
}
|
|
.switch-check input:checked + .track { background: var(--success-dim); border-color: var(--success); }
|
|
.switch-check input:checked + .track::after { transform: translateX(16px); background: var(--success); }
|
|
.switch-check input:disabled + .track { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
/* ==========================================================================
|
|
Modal
|
|
========================================================================== */
|
|
|
|
.modal-overlay {
|
|
position: fixed; inset: 0;
|
|
background: rgba(6, 7, 10, 0.6);
|
|
backdrop-filter: blur(3px);
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 100;
|
|
padding: 20px;
|
|
}
|
|
.modal-overlay.open { display: flex; }
|
|
|
|
.modal {
|
|
background: var(--bg-elevated);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
width: 100%;
|
|
max-width: 440px;
|
|
max-height: 88vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
animation: modal-in 0.16s ease;
|
|
}
|
|
@keyframes modal-in {
|
|
from { opacity: 0; transform: translateY(8px) scale(0.98); }
|
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 18px 20px;
|
|
border-bottom: 1px solid var(--border-soft);
|
|
}
|
|
.modal-header h3 { font-size: 16px; }
|
|
.modal-body { padding: 20px; overflow-y: auto; }
|
|
.modal-footer {
|
|
display: flex; justify-content: flex-end; gap: 8px;
|
|
padding: 16px 20px;
|
|
border-top: 1px solid var(--border-soft);
|
|
}
|
|
|
|
.modal-close {
|
|
width: 28px; height: 28px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
border-radius: 8px;
|
|
color: var(--text-faint);
|
|
cursor: pointer;
|
|
border: none; background: transparent;
|
|
}
|
|
.modal-close:hover { background: var(--bg-card-hover); color: var(--text); }
|
|
|
|
.detail-list { display: flex; flex-direction: column; gap: 12px; }
|
|
.detail-row { display: flex; justify-content: space-between; gap: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--border-soft); }
|
|
.detail-row:last-child { border-bottom: none; padding-bottom: 0; }
|
|
.detail-row .k { color: var(--text-faint); font-size: 12.5px; font-weight: 600; }
|
|
.detail-row .v { font-weight: 600; text-align: right; }
|
|
|
|
/* ==========================================================================
|
|
Toasts
|
|
========================================================================== */
|
|
|
|
.toast-stack {
|
|
position: fixed;
|
|
top: 18px; right: 18px;
|
|
z-index: 200;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
max-width: 340px;
|
|
}
|
|
|
|
.toast {
|
|
background: var(--bg-elevated);
|
|
border: 1px solid var(--border);
|
|
border-left: 3px solid var(--accent);
|
|
border-radius: 10px;
|
|
padding: 12px 14px;
|
|
box-shadow: var(--shadow);
|
|
font-size: 13px;
|
|
color: var(--text);
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
animation: toast-in 0.18s ease;
|
|
}
|
|
.toast.success { border-left-color: var(--success); }
|
|
.toast.danger { border-left-color: var(--danger); }
|
|
.toast.info { border-left-color: var(--accent); }
|
|
.toast .toast-close { margin-left: auto; cursor: pointer; color: var(--text-faint); flex-shrink: 0; }
|
|
.toast.hide { animation: toast-out 0.18s ease forwards; }
|
|
@keyframes toast-in { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: translateX(0); } }
|
|
@keyframes toast-out { to { opacity: 0; transform: translateX(16px); } }
|
|
|
|
/* ==========================================================================
|
|
Login page
|
|
========================================================================== */
|
|
|
|
.login-page {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background:
|
|
radial-gradient(circle at 15% 15%, rgba(255, 122, 26, 0.14), transparent 45%),
|
|
radial-gradient(circle at 85% 85%, rgba(255, 122, 26, 0.08), transparent 40%),
|
|
var(--bg);
|
|
padding: 20px;
|
|
}
|
|
|
|
.login-card {
|
|
width: 100%;
|
|
max-width: 380px;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
padding: 34px 32px 28px;
|
|
}
|
|
|
|
.login-logo { display: flex; justify-content: center; margin-bottom: 22px; }
|
|
.login-logo img { height: 34px; }
|
|
|
|
.login-card h1 { font-size: 18px; text-align: center; margin-bottom: 4px; }
|
|
.login-card .login-sub { text-align: center; color: var(--text-faint); font-size: 12.5px; margin-bottom: 24px; }
|
|
|
|
/* ==========================================================================
|
|
Logs page
|
|
========================================================================== */
|
|
|
|
.log-shell {
|
|
background: #0a0c10;
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius);
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: calc(100vh - 210px);
|
|
min-height: 320px;
|
|
}
|
|
[data-theme="light"] .log-shell { background: #0e1116; }
|
|
|
|
.log-toolbar {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 10px 14px;
|
|
border-bottom: 1px solid #1f232c;
|
|
background: #101319;
|
|
}
|
|
.log-dots { display: flex; gap: 6px; }
|
|
.log-dots span { width: 10px; height: 10px; border-radius: 50%; }
|
|
.log-dots span:nth-child(1) { background: #f0475c; }
|
|
.log-dots span:nth-child(2) { background: #f5a623; }
|
|
.log-dots span:nth-child(3) { background: #2fd07a; }
|
|
|
|
#log-box {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 14px 18px;
|
|
font-family: var(--font-mono);
|
|
font-size: 12.5px;
|
|
line-height: 1.7;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
color: #c7ccd6;
|
|
}
|
|
.code-preview {
|
|
background: #0a0c10;
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: 8px;
|
|
padding: 14px 18px;
|
|
font-family: var(--font-mono);
|
|
font-size: 12.5px;
|
|
line-height: 1.6;
|
|
color: #c7ccd6;
|
|
max-height: 360px;
|
|
overflow: auto;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
.log-line { display: block; }
|
|
.log-line.online { color: #4ade80; }
|
|
.log-line.offline { color: #f87171; }
|
|
.log-line.sep { color: #3a4050; }
|
|
.log-line.restart { color: #ffb454; }
|
|
|
|
/* ==========================================================================
|
|
Permission checklists (Gruppen)
|
|
========================================================================== */
|
|
|
|
.permission-group-title {
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--text-faint);
|
|
font-weight: 650;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
/* 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: 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;
|
|
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: 14px; padding-left: 0; }
|
|
.permission-table tbody tr:last-child td { border-bottom: none; }
|
|
.permission-row-label { color: var(--text-dim); padding-left: 48px !important; }
|
|
/* Kompakte Variante nur für das Gruppen-Dropdown auf der Hauptseite
|
|
(permission_tree(..., compact=true)) — schmalere Zellen und niedrigere
|
|
Zeilen als im "Neue Gruppe"-Modal, das unverändert bleibt. */
|
|
.permission-groups-row--compact { gap: 20px; }
|
|
.permission-groups-row--compact .permission-group-col { padding-right: 20px; }
|
|
.permission-groups-row--compact .permission-table th,
|
|
.permission-groups-row--compact .permission-table td { padding: 3px 7px; }
|
|
.permission-groups-row--compact .permission-table td:first-child,
|
|
.permission-groups-row--compact .permission-table th:first-child { padding-right: 10px; }
|
|
.permission-groups-row--compact .permission-row-label { padding-left: 32px !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; }
|
|
|
|
.check-list { display: flex; flex-direction: column; gap: 9px; }
|
|
.check-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 9px;
|
|
font-size: 13px;
|
|
color: var(--text-dim);
|
|
cursor: pointer;
|
|
}
|
|
.check-row input[type="checkbox"] {
|
|
width: 16px; height: 16px;
|
|
accent-color: var(--accent);
|
|
flex-shrink: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
SSH terminal panel (switch connection test)
|
|
========================================================================== */
|
|
|
|
.term-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
padding: 12px 18px;
|
|
border-bottom: 1px solid var(--border-soft);
|
|
}
|
|
|
|
.xterm-container {
|
|
height: 380px;
|
|
background: #0a0c10;
|
|
padding: 10px 12px;
|
|
}
|
|
.xterm-container .xterm { height: 100%; }
|
|
|
|
/* ==========================================================================
|
|
Utilities
|
|
========================================================================== */
|
|
|
|
.flex { display: flex; }
|
|
.gap-2 { gap: 8px; }
|
|
.mt-3 { margin-top: 14px; }
|
|
.text-dim { color: var(--text-dim); }
|
|
.text-faint { color: var(--text-faint); }
|
|
.text-danger { color: var(--danger); }
|
|
.hidden { display: none !important; }
|
|
|
|
/* ==========================================================================
|
|
Responsive
|
|
========================================================================== */
|
|
|
|
@media (max-width: 900px) {
|
|
.sidebar {
|
|
transform: translateX(-100%);
|
|
}
|
|
.sidebar.open { transform: translateX(0); box-shadow: var(--shadow); }
|
|
.sidebar-backdrop.open { display: block; }
|
|
.main { margin-left: 0; }
|
|
.hamburger { display: inline-flex; }
|
|
.content { padding: 18px 16px 40px; }
|
|
.topbar { padding: 10px 16px; }
|
|
.topbar-logo { opacity: 0.5; }
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.topbar-logo { display: none; }
|
|
}
|
|
|
|
@media (max-width: 560px) {
|
|
.device-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
|
|
.topbar-title { font-size: 16px; }
|
|
.stat-row { grid-template-columns: repeat(2, 1fr); }
|
|
}
|