/** * Statusanzeige — die wichtigste Komponente der Konsole. * * Sie bildet die Fachbegriffe der API auf die fünf Bedeutungen aus PROMPT.md * §106 ab. Dass diese Abbildung **an einer einzigen Stelle** steht, ist der * eigentliche Punkt: Wäre sie über die Seiten verteilt, würde früher oder * später irgendwo "partial_failure" grün erscheinen — und ein Betreiber hält * einen Teilfehler dann für einen Erfolg. * * Die Zuordnungen sind keine Geschmacksfrage, sondern folgen den * Entscheidungen der Phasen: * * - "partial_failure" ist **niemals** grün (Entwicklungsregel 1). * - "unverified" ist neutral, nicht grün: Ohne Wiederherstellungstest ist die * Wiederherstellbarkeit unbekannt, und Unbekanntes zählt nie als gut * (Phase 10). * - "corrupted" ist kritisch, nicht nur eine Warnung: Ein beschädigtes Backup * ist nicht zu 70 % wiederherstellbar, sondern gar nicht. * - "advisory" beim Löschschutz ist eine Warnung: Der Schutz ist eine * Software-Regel, kein Schutz des Dateisystems (Phase 11). */ import type { ReactNode } from 'react'; import { cn } from '@/lib/utils'; /** Die fünf Bedeutungen. Mehr gibt es nicht. */ export type StatusTone = 'healthy' | 'warning' | 'high' | 'critical' | 'info' | 'neutral'; interface StatusDefinition { readonly tone: StatusTone; readonly label: string; } /** * Abbildung der Fachbegriffe auf Ton und deutsche Beschriftung. * * Ein unbekannter Schlüssel wird bewusst **neutral** dargestellt und mit dem * Rohwert beschriftet — nicht grün. Ein neuer Serverzustand, den diese Tabelle * noch nicht kennt, darf nicht als "in Ordnung" durchgehen. */ const statusDefinitions: Readonly> = { // --- Läufe --- succeeded: { tone: 'healthy', label: 'Erfolgreich' }, running: { tone: 'info', label: 'Läuft' }, queued: { tone: 'info', label: 'Eingereiht' }, pending: { tone: 'neutral', label: 'Ausstehend' }, partial_failure: { tone: 'warning', label: 'Teilfehler' }, failed: { tone: 'critical', label: 'Fehlgeschlagen' }, cancelled: { tone: 'neutral', label: 'Abgebrochen' }, // --- Aufträge --- enabled: { tone: 'healthy', label: 'Aktiv' }, paused: { tone: 'warning', label: 'Angehalten' }, disabled: { tone: 'neutral', label: 'Deaktiviert' }, // --- Einstufung der Wiederherstellungspunkte (Phase 10) --- // // Das Vokabular lautet: failed, successful, verified, recoverable, corrupted. // // `successful` ist die Falle dieser Tabelle und deshalb **neutral**, nicht // grün: Es bedeutet "der Sicherungslauf ist durchgelaufen" — nicht, dass // sich daraus etwas wiederherstellen lässt. Ein grünes Abzeichen läse sich // hier als "geprüft und in Ordnung", und genau diese Verwechslung soll die // ganze Anlage verhindern. Erst ein Wiederherstellungstest hebt auf // `recoverable`; alles davor ist ein Indiz. recoverable: { tone: 'healthy', label: 'Wiederherstellbar' }, verified: { tone: 'info', label: 'Geprüft' }, successful: { tone: 'neutral', label: 'Gesichert, ungeprüft' }, corrupted: { tone: 'critical', label: 'Beschädigt' }, // --- Prüfergebnisse --- clean: { tone: 'healthy', label: 'Ohne Befund' }, findings: { tone: 'critical', label: 'Mit Befund' }, // --- Repositories --- active: { tone: 'healthy', label: 'Aktiv' }, read_only: { tone: 'warning', label: 'Nur lesend' }, unavailable: { tone: 'critical', label: 'Nicht erreichbar' }, maintenance: { tone: 'warning', label: 'Wartung' }, // --- Durchsetzungsstufe des Löschschutzes (Phase 11) --- filesystem: { tone: 'healthy', label: 'Dateisystem' }, storage: { tone: 'healthy', label: 'Speichersystem' }, advisory: { tone: 'warning', label: 'Nur Software' }, none: { tone: 'critical', label: 'Kein Schutz' }, // --- Gesundheit --- healthy: { tone: 'healthy', label: 'Fehlerfrei' }, degraded: { tone: 'warning', label: 'Eingeschränkt' }, critical: { tone: 'critical', label: 'Kritisch' }, unknown: { tone: 'neutral', label: 'Unbekannt' }, // --- Meldungen --- open: { tone: 'critical', label: 'Offen' }, acknowledged: { tone: 'warning', label: 'Bestätigt' }, resolved: { tone: 'healthy', label: 'Erledigt' }, suppressed: { tone: 'neutral', label: 'Unterdrückt' }, // --- Schweregrade --- info: { tone: 'info', label: 'Information' }, low: { tone: 'info', label: 'Gering' }, medium: { tone: 'warning', label: 'Mittel' }, warning: { tone: 'warning', label: 'Warnung' }, high: { tone: 'high', label: 'Hoch' }, // --- Agenten --- online: { tone: 'healthy', label: 'Verbunden' }, offline: { tone: 'critical', label: 'Nicht erreichbar' }, stale: { tone: 'warning', label: 'Veraltet' }, revoked: { tone: 'neutral', label: 'Gesperrt' }, // --- Ransomware-Einstufung (Phase 16) --- elevated: { tone: 'warning', label: 'Auffällig' }, // "unknown" oben deckt den Fall unter fünf Vergleichsläufen mit ab. }; const toneClasses: Readonly> = { healthy: 'bg-healthy-bg text-healthy', warning: 'bg-warning-bg text-warning', high: 'bg-high-bg text-high', critical: 'bg-critical-bg text-critical', info: 'bg-info-bg text-info', neutral: 'bg-neutral-bg text-neutral', }; /** Liefert Ton und Beschriftung zu einem Fachbegriff. */ export function resolveStatus(rawStatus: string | null | undefined): StatusDefinition { if (!rawStatus) { return { tone: 'neutral', label: 'Unbekannt' }; } const normalizedStatus = rawStatus.toLowerCase(); return ( statusDefinitions[normalizedStatus] ?? { // Unbekannt bleibt neutral. Grün wäre hier der bequeme und gefährliche // Standard: Ein neuer Fehlerzustand des Servers sähe aus wie Erfolg. tone: 'neutral', label: rawStatus, } ); } export function StatusBadge({ status, label, className, showDot = true, }: { readonly status: string | null | undefined; /** Überschreibt die Beschriftung, der Ton bleibt. */ readonly label?: ReactNode | undefined; readonly className?: string | undefined; readonly showDot?: boolean | undefined; }) { const resolved = resolveStatus(status); return ( {showDot ? ( ) : null} {label ?? resolved.label} ); } /** Nur der farbige Punkt — für dichte Tabellen. */ export function StatusDot({ status, className, }: { readonly status: string | null | undefined; readonly className?: string | undefined; }) { const resolved = resolveStatus(status); return ( {resolved.label} ); }