/** * Seitenverzeichnis der Oberfläche. * * Der Grundsatz aus PROMPT.md §139 gilt unverändert: Ein Menü, das nur die * fertigen Bereiche zeigt, verschweigt den Ausbaustand; eines, dessen Einträge * in leere Masken führen, täuscht ihn vor. Jeder Eintrag erscheint, und ein * noch nicht verfügbarer nennt, was fehlt und was stattdessen gilt. * * Die Gliederung folgt dem Weg durch die Anlage, nicht der Reihenfolge der * Umsetzung: Was täglich beobachtet wird, steht oben; was einmal eingerichtet * und dann selten angefasst wird, unten. */ import type { LucideIcon } from 'lucide-react'; import { Activity, Archive, BadgeCheck, Bell, ChartLine, Clock, Database, FileText, HardDrive, KeyRound, LayoutDashboard, MonitorCog, RotateCcw, ScrollText, Server, Settings, ShieldCheck, Users, } from 'lucide-react'; /** Bereich der Navigation, dem eine Seite zugeordnet ist. */ export type NavigationSection = | 'betrieb' | 'daten' | 'infrastruktur' | 'analyse' | 'verwaltung'; /** Beschriftung der Bereiche in Anzeigereihenfolge. */ export const SECTION_LABELS: ReadonlyArray = [ ['betrieb', 'Betrieb'], ['daten', 'Daten'], ['infrastruktur', 'Infrastruktur'], ['analyse', 'Analyse'], ['verwaltung', 'Verwaltung'], ]; /** Beschreibung einer Seite der Oberfläche. */ export interface PageDefinition { /** Stabiler Bezeichner; er steht auch im Pfad. */ readonly id: string; /** Beschriftung im Menü. */ readonly label: string; /** Bereich, unter dem die Seite erscheint. */ readonly section: NavigationSection; /** Sinnbild im Menü. */ readonly icon: LucideIcon; /** * Berechtigung, die der Zugriff verlangt. * * Rein zur Anzeige: Die Prüfung erfolgt immer serverseitig (PROMPT.md §42). * Ein Menüpunkt, den der Server ohnehin ablehnt, führt sonst nur zu einer * Fehlermeldung statt zu einer verständlichen Oberfläche. */ readonly requiredPermission: string | null; /** Meldet, ob hinter der Seite ein Backend steht. */ readonly available: boolean; /** * Erklärt eine fehlende Seite. * * Nicht „in Arbeit", sondern: welche Funktion fehlt und wie man heute an die * Auskunft kommt. */ readonly unavailableReason?: string; } /** Alle Seiten in Anzeigereihenfolge. */ export const ALL_PAGES: readonly PageDefinition[] = [ // --- Betrieb: was täglich beobachtet und bedient wird --- { id: 'dashboard', label: 'Übersicht', section: 'betrieb', icon: LayoutDashboard, requiredPermission: 'backups.read', available: true, }, { id: 'jobs', label: 'Sicherungsaufträge', section: 'betrieb', icon: Clock, requiredPermission: 'jobs.read', available: true, }, { id: 'restores', label: 'Wiederherstellung', section: 'betrieb', icon: RotateCcw, requiredPermission: 'restores.read', available: true, }, { id: 'alerts', label: 'Meldungen', section: 'betrieb', icon: Bell, requiredPermission: 'alerts.read', available: true, }, // --- Daten: die Sicherungen selbst und ihr Schutz --- { id: 'recovery-points', label: 'Wiederherstellungspunkte', section: 'daten', icon: Archive, requiredPermission: 'backups.read', available: true, }, { id: 'verification', label: 'Prüfung', section: 'daten', icon: BadgeCheck, requiredPermission: 'verification.read', available: true, }, { id: 'repositories', label: 'Repositories', section: 'daten', icon: Database, requiredPermission: 'repositories.read', available: true, }, { id: 'retention', label: 'Aufbewahrung', section: 'daten', icon: HardDrive, requiredPermission: 'repositories.read', available: true, }, // --- Infrastruktur: was gesichert wird --- { id: 'agents', label: 'Agenten', section: 'infrastruktur', icon: MonitorCog, requiredPermission: 'agents.read', available: true, }, { id: 'proxmox', label: 'Proxmox', section: 'infrastruktur', icon: Server, requiredPermission: 'providers.read', available: true, }, { id: 'protected-systems', label: 'Geschützte Systeme', section: 'infrastruktur', icon: Activity, requiredPermission: 'providers.read', available: true, }, // --- Analyse: Auswertung und Nachweis --- { id: 'metrics', label: 'Kennzahlen', section: 'analyse', icon: ChartLine, requiredPermission: 'monitoring.read', available: true, }, { id: 'reports', label: 'Berichte', section: 'analyse', icon: FileText, requiredPermission: 'reports.read', available: true, }, { id: 'security', label: 'Security Center', section: 'analyse', icon: ShieldCheck, requiredPermission: 'security.read', available: true, }, // --- Verwaltung: einmal einrichten, selten anfassen --- { id: 'users', label: 'Benutzer', section: 'verwaltung', icon: Users, requiredPermission: 'users.read', available: true, }, { id: 'roles', label: 'Rollen', section: 'verwaltung', icon: KeyRound, requiredPermission: 'roles.read', available: true, }, { id: 'events', label: 'Ereignisprotokoll', section: 'verwaltung', icon: ScrollText, requiredPermission: 'audit.read', available: true, }, { id: 'settings', label: 'Einstellungen', section: 'verwaltung', icon: Settings, requiredPermission: 'settings.read', available: true, }, ]; /** Die Seite, die nach der Anmeldung erscheint. */ export const DEFAULT_PAGE_ID = 'dashboard'; /** Sucht eine Seite anhand ihres Bezeichners. */ export function findPage(pageIdentifier: string): PageDefinition | undefined { return ALL_PAGES.find((page) => page.id === pageIdentifier); } /** * Meldet, ob ein Benutzer eine Seite sehen darf. * * Ausschließlich zur Anzeige — sie verhindert Sackgassen im Menü. Die * verbindliche Prüfung findet auf dem Server statt (PROMPT.md §42). */ export function mayViewPage( page: PageDefinition, grantedPermissions: readonly string[], ): boolean { if (page.requiredPermission === null) { return true; } // Der Super-Administrator trägt einen Platzhalter statt aller Einzelrechte. if (grantedPermissions.includes('*')) { return true; } return grantedPermissions.includes(page.requiredPermission); }