Files
tessera-ctl/.planning/quick/260924-h7x-proxmox-seite-status-design-und-dashboar/260924-h7x-PLAN.md
T
schalli 8bfa4fc467
Tessera CI/CD / Lint & Type Check (push) Successful in 52s
Tessera CI/CD / Tests (push) Successful in 1m14s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m7s
docs(quick-260924-h7x): Proxmox-Status-Design und Reiter in der Kopfzeile
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:48:56 +02:00

174 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)