174 lines
10 KiB
Markdown
174 lines
10 KiB
Markdown
---
|
||
quick_id: 260924-h7x
|
||
type: quick
|
||
wave: 1
|
||
autonomous: true
|
||
files_modified:
|
||
- apps/web/src/app/globals.css
|
||
- apps/web/src/app/(portal)/modules/proxmox/page.tsx
|
||
- apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.tsx
|
||
- apps/web/src/app/(portal)/modules/proxmox/components/proxmox-status.ts (new)
|
||
- apps/web/src/app/(portal)/modules/proxmox/components/*.test.tsx
|
||
- apps/web/src/components/layout/header.tsx
|
||
- apps/web/src/components/dashboard/dashboard-tabs.tsx
|
||
- apps/web/src/app/(portal)/page.tsx
|
||
- apps/web/src/messages/de.json, en.json
|
||
- CHANGELOG.md
|
||
---
|
||
|
||
# Quick 260924-h7x — Proxmox-Seite: Status-Design mit Tiefe; Dashboard-Reiter in die Kopfzeile
|
||
|
||
Nutzerauftrag (24.09.): „Kennzeichnen als offline & verwaist. Das Proxmox-Modul scheint zu
|
||
funktionieren, aber es gefällt mir optisch gar nicht. Benutze den Design-Skill und überarbeite
|
||
etwas — mehr Tiefe, Farben nach Status etc. Die Reiter sind da, aber die müssen woanders hin,
|
||
die nehmen zu viel Platz ein. Auch mit Design.“
|
||
|
||
Die frühere Sperre „KEIN Umbau der Proxmox-Modulseite“ (23.09.) ist damit durch den Nutzer
|
||
selbst aufgehoben.
|
||
|
||
Design-Plan vom Orchestrator (frontend-design-Skill) — verbindlich, nicht neu erfinden.
|
||
Keine neuen Pakete. App-Texte deutsch in Sie-Form, alle Schlüssel in de.json UND en.json.
|
||
Keine Großbuchstaben-Etiketten (kein `uppercase`, kein `tracking-widest`), keine
|
||
Mittelpunkt-Ketten „A · B · C“ in neuen Texten, kein „→“ an Knöpfen.
|
||
|
||
## Design-Tokens (in `globals.css`, `:root` und `.dark`)
|
||
|
||
Statusfarben als OKLCH-Variablen plus Tailwind-Abbildung in `@theme inline`
|
||
(`--color-status-ok` usw., damit `bg-status-ok`, `text-status-ok`, `border-status-ok/40` gehen):
|
||
|
||
| Token | hell | dunkel | Bedeutung |
|
||
|---|---|---|---|
|
||
| `--status-ok` | `oklch(0.62 0.15 152)` | `oklch(0.72 0.15 152)` | in Ordnung |
|
||
| `--status-warn` | `oklch(0.72 0.15 70)` | `oklch(0.80 0.14 75)` | Warnung |
|
||
| `--status-down` | `oklch(0.58 0.21 27)` | `oklch(0.68 0.19 27)` | nicht erreichbar / Fehler |
|
||
| `--status-idle` | `oklch(0.62 0.02 260)` | `oklch(0.60 0.02 260)` | noch nicht abgefragt |
|
||
| `--status-orphan` | `oklch(0.55 0.01 260)` | `oklch(0.52 0.01 260)` | offline & verwaist |
|
||
| `--well` | `oklch(0.975 0.003 260)` | `oklch(0.235 0.01 260)` | eingelassene Messfelder |
|
||
|
||
Kontrast prüfen: Text in Statusfarbe auf `--card` muss ≥ 4.5:1 (Fließtext) bzw. ≥ 3:1
|
||
(große/fette Zahlen) erreichen — sonst Helligkeit anpassen und die gemessene Zahl in der
|
||
SUMMARY nennen.
|
||
|
||
## Task 1 — Statuslogik als reine Funktionen (TDD)
|
||
|
||
Neue Datei `components/proxmox-status.ts`:
|
||
|
||
- `serverHealth(server): 'ok' | 'warn' | 'down' | 'idle' | 'orphan'`
|
||
- `!server.isActive` → `orphan` (VORRANG vor allem anderen — „offline & verwaist“, auch wenn
|
||
alte Messwerte im Zwischenlager liegen)
|
||
- kein `status` oder `status.lastPolledAt === null` → `idle`
|
||
- `!status.reachable` → `down`
|
||
- erreichbar + irgendein Messwert über Warnschwelle → `warn`
|
||
- sonst `ok`
|
||
- Schwellen als EINE benannte Konstante `THRESHOLDS`: Last/Arbeitsspeicher/Datenspeicher
|
||
warn ≥ 0.80, kritisch ≥ 0.92; PBS letzte Sicherung älter als 26 h → warn; `lastVerifyState`
|
||
ungleich `ok` (und nicht null) → warn; PMG `virusCount > 0` → warn.
|
||
- `meterLevel(fraction | null): 'ok' | 'warn' | 'crit' | 'unknown'` für die Balken.
|
||
- `formatAge(epochSeconds|iso, now)` → „vor 3 Min.“, „vor 5 Std.“, „vor 2 Tagen“ über
|
||
`Intl.RelativeTimeFormat('de')` bzw. next-intl — keine neue Abhängigkeit.
|
||
- Tests für jede Verzweigung inkl. `null`-Werte (unbekannt ≠ 0, nie Warnung aus `null`).
|
||
|
||
## Task 2 — Proxmox-Seite und Karte neu gestalten
|
||
|
||
**Seitenkopf:** Titel „Proxmox“ links, darunter die Beschreibung. Rechts „Jetzt aktualisieren“
|
||
(nur Admins, wie heute) als richtiger Knopf mit Kreispfeil-Symbol (dreht sich während des Laufs;
|
||
`motion-reduce:animate-none`) und „Einstellungen“ als ruhiger Textlink. Breite `max-w-5xl`.
|
||
|
||
**Gesundheitsbalken (das eine prägnante Element der Seite):** direkt unter dem Kopf ein
|
||
waagrechter Balken, 8 px hoch, voll gerundet, anteilig in Segmente je Zustand geteilt
|
||
(Reihenfolge down, warn, ok, idle, orphan; leere Zustände entfallen). Darunter eine Zeile mit
|
||
Legende: farbiger Punkt + Zahl + Wort („2 in Ordnung“, „1 nicht erreichbar“, „1 offline &
|
||
verwaist“ …, ICU-Plural). Segmente `role="img"` mit `aria-label`, das die Zusammenfassung vorliest.
|
||
Keine Zahl-in-riesig-plus-Verlauf-Heldenkachel.
|
||
|
||
**Kartenraster:** `grid gap-4 lg:grid-cols-2`. Sortierung: down, warn, ok, idle, orphan, darin
|
||
`position`. (Die Einstellungsseite behält ihre Reihenfolge.)
|
||
|
||
**Karte (Tiefe über Status, nicht Einheits-Schatten):**
|
||
- Karte `rounded-xl bg-card` mit Rand `border-border`, links eine 4 px breite Statusleiste
|
||
(absolut positioniert, volle Höhe, Farbe = Status).
|
||
- Schatten zweischichtig und im Statuston getönt, z. B.
|
||
`shadow-[0_1px_2px_oklch(0_0_0/0.06),0_12px_28px_-14px_var(--status-x)]` — bei `orphan` KEIN
|
||
farbiger Schatten, stattdessen gestrichelter Rand (`border-dashed`), Inhalt
|
||
`opacity-70 saturate-50`.
|
||
- Kopfzeile: Produktsymbol (kleines inline-SVG je Typ: PVE = Server-Einschübe, PBS =
|
||
Archivkiste/Datenträger, PMG = Briefumschlag) in einem 32 px Feld mit `bg-well`, daneben Name
|
||
(fett) und darunter Produktname ausgeschrieben („Virtualisierung“, „Datensicherung“,
|
||
„Mail-Gateway“) plus Adresse klein und gedämpft. Rechts eine Statuspille: Punkt + Wort
|
||
(„In Ordnung“, „Warnung“, „Nicht erreichbar“, „Noch nicht abgefragt“, „Offline & verwaist“),
|
||
Hintergrund Statusfarbe /12, Text in Statusfarbe. Bei `ok` pulsiert der Punkt EINMAL beim
|
||
Laden nicht — keine Dauer-Animation.
|
||
- Fuß der Karte: „Letzte Abfrage vor 4 Min.“ (relativ, `title` mit exaktem Zeitpunkt).
|
||
|
||
**Messbereich je Produkt, als eingelassene Felder** (`bg-well`, `rounded-lg`,
|
||
`shadow-[inset_0_1px_2px_oklch(0_0_0/0.06)]`):
|
||
- **PVE:** oben zwei Kennzahlen nebeneinander: laufende Gäste (groß, `tabular-nums`) und
|
||
gestoppte Gäste (gedämpft), Knotenzahl klein. Darunter je Knoten ein „Einschub“: Knotenname
|
||
links, rechts zwei schmale Balken „Prozessor“ und „Arbeitsspeicher“ mit Prozentzahl
|
||
(`tabular-nums`) und bei Speicher „12,0 / 64,0 GB“ als `title`/kleine Zeile. Balkenfarbe
|
||
nach `meterLevel`. Unbekannter Wert: Balken schraffiert/leer + Text „unbekannt“, NIE 0 %.
|
||
Deutsche Zahlformate (Komma) über `Intl.NumberFormat('de-DE')`.
|
||
- **PBS:** je Datenspeicher ein Einschub: Name, Füllstandsbalken mit „1,2 / 4,0 TB“, darunter
|
||
„Letzte Sicherung vor 5 Std.“ (warn-Farbe, wenn > 26 h; „noch keine Sicherung“ gedämpft) und
|
||
Prüfstatus als kleine Pille (ok grün, sonst warn, null „unbekannt“ grau).
|
||
- **PMG:** vier Zahlfelder im 2×2-Raster: Eingehend, Ausgehend, Spam, Viren; Viren > 0 in
|
||
down-Farbe, sonst normal. `null` → „unbekannt“.
|
||
|
||
**Zustände ohne Messwerte:**
|
||
- `down`: roter Hinweisblock im Well (`bg-status-down/8`, Rand links in Statusfarbe) mit der
|
||
bekannten Fehlermeldung (`errors.*`) und darunter gedämpft „Zuletzt erreichbar: vor 2 Tagen“
|
||
bzw. nichts, wenn nie. `errorDetail` klein und gedämpft in eigener Zeile, nicht in Klammern
|
||
an den Satz gehängt.
|
||
- `idle`: ruhiger Text wie heute (Admin-/Nicht-Admin-Variante bleibt, 260923-le6).
|
||
- `orphan`: Text „Dieser Server ist deaktiviert und wird nicht mehr abgefragt.“ plus für Admins
|
||
der Hinweis, dass er in den Einstellungen wieder aktiviert werden kann (Link). Alte Messwerte
|
||
werden bei `orphan` NICHT angezeigt.
|
||
|
||
**Leer-, Lade-, Fehlerzustand der Seite:** Leerzustand als Well mit Serversymbol und Satz +
|
||
Link (Admins); Laden als zwei Skelett-Karten (`animate-pulse`, `motion-reduce:animate-none`).
|
||
|
||
Tests: bestehende ServerCard-/Seitentests anpassen (Texte/Strukturen), neue Tests für
|
||
orphan (Vorrang, keine alten Werte), Sortierung, Gesundheitsbalken-Zusammenfassung, unbekannte
|
||
Werte als „unbekannt“. Die Nur-Admin-Regeln aus 260923-le6 müssen weiter getestet sein.
|
||
|
||
## Task 3 — Dashboard-Reiter in die Kopfzeile
|
||
|
||
Heute belegt `DashboardTabs` eine eigene Zeile über dem Raster (`app/(portal)/page.tsx`), die
|
||
Kopfzeilenmitte (`components/layout/header.tsx`) zeigt nur den Text „Startseite“.
|
||
|
||
- Kopfzeile bekommt in der Mitte einen Einhängepunkt `<div id="header-center-slot">`. Auf der
|
||
Startseite (`pathname === '/'`) entfällt der Text „Startseite“; auf allen anderen Seiten bleibt
|
||
alles wie heute.
|
||
- `DashboardTabs` rendert per `createPortal` in diesen Einhängepunkt (sobald er im DOM ist —
|
||
`useEffect` + State; Rückfall: solange kein Einhängepunkt da ist, nichts rendern). Die eigene
|
||
Zeile über dem Raster entfällt ersatzlos.
|
||
- Gestaltung als kompakter Umschalter mit Tiefe: eine eingelassene Spur (`bg-muted`, `rounded-lg`,
|
||
`p-0.5`, `shadow-[inset_0_1px_2px_oklch(0_0_0/0.08)]`, Höhe 32 px), darin die Reiter als
|
||
Textknöpfe (`text-sm`, `px-3`, `h-7`); der aktive Reiter liegt erhaben darauf (`bg-card`,
|
||
`shadow-sm`, `font-medium`, Text `foreground`), inaktive gedämpft mit Hover. Keine
|
||
Unterstreichung, kein Gelb-Flächen-Reiter.
|
||
- Viele Reiter: Spur höchstens `max-w-[min(56vw,720px)]`, waagrecht scrollbar ohne sichtbare
|
||
Leiste, an den Rändern weich ausgeblendet (`mask-image` nur wenn überläuft), aktiver Reiter
|
||
wird ins Bild gescrollt.
|
||
- Bearbeitungsmodus: Umbenennen/Löschen/Ziehen/Anlegen bleiben funktional gleich (alle
|
||
bestehenden Tests grün halten bzw. nur Selektoren anpassen). „+“ als 28-px-Rundknopf am Ende
|
||
der Spur (immer sichtbar, wie heute der Anlegen-Knopf sichtbar ist — Verhalten beibehalten).
|
||
Löschen-Kreuz erscheint im Bearbeitungsmodus klein im Reiter.
|
||
- Genau EIN Reiter: Umschalter trotzdem zeigen (sonst weiß niemand, dass es Reiter gibt), aber
|
||
nur mit „+“ daneben.
|
||
- Mobil (< md): die Spur darf die Kopfzeilenmitte füllen; Logo-Schriftzug bleibt, Aktionen rechts
|
||
bleiben erreichbar — nichts darf die Kopfzeile sprengen (overflow prüfen).
|
||
- Tastatur: Pfeiltasten links/rechts zwischen Reitern (`role="tablist"`/`tab`, `aria-selected`),
|
||
sichtbarer Fokusring.
|
||
- `navigation "Dashboard-Reiter"`-Beschriftung bleibt als `aria-label`.
|
||
|
||
Tests: dashboard-tabs.test.tsx an Portal anpassen (Einhängepunkt im Test anlegen), neuer Test:
|
||
Kopfzeile zeigt „Startseite“ nicht auf `/`, aber auf anderen Pfaden; Pfeiltasten.
|
||
|
||
## Tore
|
||
|
||
- `pnpm --filter web exec vitest run` komplett grün
|
||
- `pnpm turbo run type-check lint` grün, Biome-Warnungen web nicht mehr als 53
|
||
- CHANGELOG „Unveröffentlicht“: je ein Eintrag unter „Neu“/„Geändert“ in Nutzersprache
|
||
- Browser-Nachweis macht der Orchestrator (hell UND dunkel, 1400 px und 390 px Breite)
|