Frontend: SystemDrawer entflochten (Review P2-14, Teil 1) — 934 -> 665 Zeilen
- components/system/rows.tsx: SERVICES + UpdateRow + ServiceRow + formatBytes - components/system/SettingsTab.tsx: Einstellungen-Tab inkl. eigenem State (localStorage) - components/system/UpdateDetailModal.tsx: Update-Detail-Fenster (os/engine/swap/hermes) - Preview-verifiziert gegen die Live-Box: alle 3 Tabs rendern, Speichern-Flow (Dialog + localStorage) und Logs-Tab (Service-Select) funktionieren Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { RefreshCw, FileText } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
// Präsentations-Atome des SystemDrawers (Review P2-14: aus dem 934-Z-Monolithen extrahiert).
|
||||
|
||||
// systemd-Units (restart/logs) + reach = Stichwort zum Mappen auf /api/system/services.
|
||||
export const SERVICES = [
|
||||
{ id: "mission-control-2", label: "Mission Control", type: "user", reach: "gateway (integr" },
|
||||
{ id: "hermes-gateway", label: "Hermes Gateway", type: "user", reach: "hermes-gateway" },
|
||||
{ id: "mem0-service", label: "Mem0 (Gedächtnis)", type: "user", reach: "mem0" },
|
||||
{ id: "hermes-terminal", label: "Hermes Terminal", type: "user", reach: "hermes-terminal" },
|
||||
{ id: "llama-swap", label: "Llama Swap", type: "system", reach: "llama-swap" },
|
||||
]
|
||||
|
||||
export function UpdateRow({ icon: Icon, iconClass, name, status, available, busy, actionLabel, onAction }: {
|
||||
icon: any; iconClass: string; name: string; status: string; available: boolean; busy?: boolean; actionLabel: string; onAction: () => void
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex items-center gap-3 rounded-lg border px-3 py-2",
|
||||
available ? "border-amber-500/30 bg-amber-500/5" : "border-border/50 bg-background/20")}>
|
||||
<Icon className={cn("h-4 w-4 shrink-0", iconClass)} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-foreground truncate">{name}</div>
|
||||
<div className={cn("text-[10px] truncate", available ? "text-amber-400/90" : "text-muted-foreground")}>{status}</div>
|
||||
</div>
|
||||
<button onClick={onAction} disabled={!available || busy}
|
||||
className={cn("text-[11px] font-semibold rounded-lg px-2.5 py-1 transition-colors shrink-0",
|
||||
available ? "bg-primary text-primary-foreground hover:opacity-90 cursor-pointer" : "border border-border/50 text-muted-foreground/40 cursor-default")}>
|
||||
{busy ? "…" : actionLabel}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ServiceRow({ label, ok, system, busy, onRestart, onLogs }: {
|
||||
label: string; ok?: boolean; system?: boolean; busy?: boolean; onRestart: () => void; onLogs: () => void
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-2.5 rounded-lg border border-border/50 bg-background/20 px-3 py-2">
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full shrink-0",
|
||||
ok === true ? "bg-emerald-500" : ok === false ? "bg-red-500" : "bg-muted-foreground/40")} />
|
||||
<span className="flex-1 text-xs text-foreground truncate">{label}{system && <span className="text-[9px] text-muted-foreground"> (root)</span>}</span>
|
||||
<button onClick={onRestart} disabled={busy} title="Neu starten" className="text-muted-foreground hover:text-primary transition-colors disabled:opacity-50">
|
||||
<RefreshCw className={cn("h-3.5 w-3.5", busy && "animate-spin")} />
|
||||
</button>
|
||||
<button onClick={onLogs} title="Logs ansehen" className="text-muted-foreground hover:text-primary transition-colors">
|
||||
<FileText className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function formatBytes(bytes?: number) {
|
||||
if (bytes == null) return ""
|
||||
if (bytes > 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(2)} GB`
|
||||
return `${(bytes / 1024 ** 2).toFixed(1)} MB`
|
||||
}
|
||||
Reference in New Issue
Block a user