--- 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 `