Wartung: Cockpit-Restrukturierung (zweispaltiges Grid, ToolListCard, ungenutzte Imports entfernt)

This commit is contained in:
Hitonabi
2026-07-09 12:03:11 +02:00
parent 0dc9caa955
commit 51c0c868b2
6 changed files with 317 additions and 263 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title> <title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-BquQKOWt.js"></script> <script type="module" crossorigin src="/assets/index--f4XyXiY.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BlX5e5eL.css"> <link rel="stylesheet" crossorigin href="/assets/index-DnWvM6fs.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+129 -70
View File
@@ -1,15 +1,14 @@
import { useState } from "react" import { useState } from "react"
import { import {
Boxes, Brain, Server, ShieldAlert, Plug, Bot, TerminalSquare, ScrollText, Boxes, Brain, Server, ShieldAlert, Plug,
HeartPulse, AlertTriangle, Loader2, Wrench, Check, HardDrive, ChevronRight, HeartPulse, AlertTriangle, Loader2, Wrench, Check, HardDrive, ChevronRight,
Inbox, History, Library, Inbox,
} from "lucide-react" } from "lucide-react"
import { api } from "@/lib/api" import { api } from "@/lib/api"
import { import {
useModels, useServices, useUpdates, useMemory, useAgentStatus, useConnectHealth, useModels, useServices, useUpdates, useMemory, useAgentStatus, useConnectHealth,
useAuftragsbuch, useQueryClient, qk, useAuftragsbuch, useQueryClient, qk,
} from "@/lib/queries" } from "@/lib/queries"
import { SquareTerminal } from "lucide-react"
import { useLucyHealth, type Repair } from "@/lib/useLucyHealth" import { useLucyHealth, type Repair } from "@/lib/useLucyHealth"
import { useDialog } from "@/lib/useDialog" import { useDialog } from "@/lib/useDialog"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -90,7 +89,7 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
const updateSummary = upParts.length > 0 ? upParts.join(" + ") : "" const updateSummary = upParts.length > 0 ? upParts.join(" + ") : ""
return ( return (
<div className="space-y-7"> <div className="space-y-6">
{/* Kopf */} {/* Kopf */}
<div> <div>
<h1 className="bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent"> <h1 className="bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent">
@@ -99,13 +98,12 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
<p className="text-sm text-muted-foreground">Deine Box auf einen Blick Details hinter jeder Kachel.</p> <p className="text-sm text-muted-foreground">Deine Box auf einen Blick Details hinter jeder Kachel.</p>
</div> </div>
{/* Ehrliche Status-Zeile */} {/* Top Grid: StatusLine (2/3) und NeedsYou (1/3) */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-stretch">
<div className="lg:col-span-2">
<StatusLine verdict={health.verdict} memory={health.memory} warns={health.warns} /> <StatusLine verdict={health.verdict} memory={health.memory} warns={health.warns} />
</div>
{/* Morgenlage — das jüngste Nacht-Gutachten (verschwindet, wenn keines da ist) */} <div>
<MorgenlageCard />
{/* „Braucht dich" — offene Aktionen, sonst Ruhe */}
<NeedsYou <NeedsYou
problems={health.problems} problems={health.problems}
pendingCount={pendingCount} pendingCount={pendingCount}
@@ -114,16 +112,25 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
onRepair={runRepair} onRepair={runRepair}
onNavigate={onNavigate} onNavigate={onNavigate}
/> />
</div>
</div>
{/* Bereichs-Kacheln */} {/* Main Grid: 2/3 Links (Inhalte & Performance), 1/3 Rechts (Shortcuts & Kalender) */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-start">
{/* Hauptspalte Links */}
<div className="lg:col-span-2 space-y-6">
{/* Morgenlage — das jüngste Nacht-Gutachten (verschwindet, wenn keines da ist) */}
<MorgenlageCard />
{/* Bereichs-Kacheln (6 statt 7 - Logs ist in die Werkzeug-Liste gewandert) */}
<section> <section>
<ZoneLabel>Bereiche</ZoneLabel> <ZoneLabel>Bereiche</ZoneLabel>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-4 grid-cols-2 md:grid-cols-3">
<CockpitTile <CockpitTile
icon={Inbox} title="Auftragsbuch" value={auftraege?.available ? openAuftraege : "—"} icon={Inbox} title="Auftragsbuch" value={auftraege?.available ? openAuftraege : "—"}
unit={openAuftraege === 1 ? "Vorschlag" : "Vorschläge"} unit={openAuftraege === 1 ? "Vorschlag" : "Vorschläge"}
tone={!auftraege ? "loading" : openAuftraege > 0 ? "warn" : "ok"} tone={!auftraege ? "loading" : openAuftraege > 0 ? "warn" : "ok"}
hint={openAuftraege > 0 ? `${openAuftraege} offene Patches prüfen` : "Keine ausstehenden Vorschläge"} hint={openAuftraege > 0 ? `${openAuftraege} offene Patches` : "Keine ausstehenden Vorschläge"}
onClick={() => onNavigate("auftraege")} onClick={() => onNavigate("auftraege")}
/> />
<CockpitTile <CockpitTile
@@ -157,27 +164,13 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
hint={agent?.pc_executor_reachable ? "IDE & PC online (:7777)" : "IDE-Kopplung (PC offline)"} hint={agent?.pc_executor_reachable ? "IDE & PC online (:7777)" : "IDE-Kopplung (PC offline)"}
onClick={() => onNavigate("connect")} onClick={() => onNavigate("connect")}
/> />
<CockpitTile
icon={ScrollText} title="Logs" value={svc ? svcErrors : "…"} unit={svcErrors === 1 ? "Fehler" : "Fehler"}
tone={!svc ? "loading" : svcErrors > 0 ? "alert" : "ok"}
hint={svcErrors > 0 ? `${svcErrors} Dienst-Fehler gefunden` : "Keine Log-Warnungen"}
onClick={() => openDrawer("logs")}
/>
</div> </div>
</section> </section>
{/* Anstehendes — Erinnerungen & Routinen (GUI-Hälfte der Wecker-API) */} {/* Leistung — die Live-Graphen */}
<section>
<ZoneLabel>Anstehendes</ZoneLabel>
<ErinnerungenCard />
</section>
{/* Leistung die Live-Graphen zurück auf der Startseite (User-Wunsch 07.07.:
System-Verlauf, tok/s und Turn-Latenz waren nach dem UI-v3-Umbau nur noch in
der alten Zentrale erreichbar). Karten sind autark (eigene Hooks/Polling). */}
<section> <section>
<ZoneLabel>Leistung</ZoneLabel> <ZoneLabel>Leistung</ZoneLabel>
<div className="grid gap-4 lg:grid-cols-2"> <div className="grid gap-4 md:grid-cols-2">
<SystemStatusCard /> <SystemStatusCard />
<TokenPerformanceCard /> <TokenPerformanceCard />
</div> </div>
@@ -185,39 +178,24 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
<LatencyCard /> <LatencyCard />
</div> </div>
</section> </section>
{/* Sekundäre Bereiche */}
<section>
<ZoneLabel>Mehr</ZoneLabel>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<CockpitTile
icon={Bot} title="Hermes" value={agent ? (agent.gateway_reachable ? "bereit" : "offline") : "…"}
tone={!agent ? "loading" : agent.gateway_reachable ? "ok" : "alert"} hint="Agent-Status & Verdrahtung"
onClick={() => onNavigate("agent")}
/>
<CockpitTile
icon={TerminalSquare} title="Terminal" value="öffnen"
tone="muted" hint="Interaktives Agent-Terminal"
onClick={() => onNavigate("terminal")}
/>
<CockpitTile
icon={SquareTerminal} title="Konsole" value={agent ? (agent.box_console_reachable ? "bereit" : "offline") : "…"}
tone={!agent ? "loading" : agent.box_console_reachable ? "ok" : "warn"} hint="Direkte Box-Shell (SSH)"
onClick={() => onNavigate("konsole")}
/>
<CockpitTile
icon={Library} title="Wissen" value="öffnen"
tone="muted" hint="Lucys Wissens-Vault (Traum-Notizen)"
onClick={() => onNavigate("wissen")}
/>
<CockpitTile
icon={History} title="Chronik" value="öffnen"
tone="muted" hint="Autonome Taten + Zeitmaschine"
onClick={() => onNavigate("chronik")}
/>
</div> </div>
{/* Seitenspalte Rechts */}
<div className="space-y-6">
{/* Anstehendes — Erinnerungen & Routinen */}
<section>
<ZoneLabel>Anstehendes</ZoneLabel>
<ErinnerungenCard />
</section> </section>
{/* Werkzeuge & Shortcuts */}
<section>
<ZoneLabel>Werkzeuge & Diagnose</ZoneLabel>
<ToolListCard agent={agent} svcErrors={svcErrors} onNavigate={onNavigate} />
</section>
</div>
</div>
{dialogElement} {dialogElement}
</div> </div>
) )
@@ -228,8 +206,6 @@ function ZoneLabel({ children }: { children: React.ReactNode }) {
} }
// Ehrliche Status-Zeile: großes Verdikt + Speicher-Pille. Erzählrahmen = Box. // Ehrliche Status-Zeile: großes Verdikt + Speicher-Pille. Erzählrahmen = Box.
// Bei „warn" wird der ECHTE Hinweis gezeigt (vorher stand hier nur „nur ein Hinweis" —
// ohne zu sagen, welcher; User-Feedback 08.07.).
function StatusLine({ verdict, memory, warns }: { function StatusLine({ verdict, memory, warns }: {
verdict: ReturnType<typeof useLucyHealth>["verdict"] verdict: ReturnType<typeof useLucyHealth>["verdict"]
memory: ReturnType<typeof useLucyHealth>["memory"] memory: ReturnType<typeof useLucyHealth>["memory"]
@@ -249,7 +225,7 @@ function StatusLine({ verdict, memory, warns }: {
const memBar = { ok: "bg-emerald-500", warn: "bg-amber-500", full: "bg-red-500", unknown: "bg-muted-foreground/40" }[memory.status] const memBar = { ok: "bg-emerald-500", warn: "bg-amber-500", full: "bg-red-500", unknown: "bg-muted-foreground/40" }[memory.status]
return ( return (
<div className={cn("flex flex-col gap-4 rounded-2xl border p-5 shadow-lg shadow-black/15 backdrop-blur-md transition-colors sm:flex-row sm:items-center sm:justify-between", b.ring)}> <div className={cn("flex flex-col gap-4 rounded-2xl border p-5 shadow-lg shadow-black/15 backdrop-blur-md transition-colors sm:flex-row sm:items-center sm:justify-between h-full", b.ring)}>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border border-border/40 bg-background/30"> <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border border-border/40 bg-background/30">
<Icon className={cn("h-6 w-6", b.iconCls)} /> <Icon className={cn("h-6 w-6", b.iconCls)} />
@@ -279,7 +255,7 @@ function StatusLine({ verdict, memory, warns }: {
} }
// „Braucht dich": kritische Probleme (mit 1-Klick-Reparatur) + bereitliegende Updates // „Braucht dich": kritische Probleme (mit 1-Klick-Reparatur) + bereitliegende Updates
// + offene Vorschläge im Auftragsbuch. Nichts offen → ruhige Bestätigung statt leerer Fläche. // + offene Vorschläge im Auftragsbuch.
function NeedsYou({ function NeedsYou({
problems, pendingCount, openAuftraege, busy, onRepair, onNavigate, problems, pendingCount, openAuftraege, busy, onRepair, onNavigate,
}: { }: {
@@ -295,10 +271,9 @@ function NeedsYou({
const nothing = problems.length === 0 && !hasUpdates && !hasAuftraege const nothing = problems.length === 0 && !hasUpdates && !hasAuftraege
return ( return (
<section> <div className="h-full flex flex-col justify-between">
<ZoneLabel>Braucht dich</ZoneLabel>
{nothing ? ( {nothing ? (
<div className="flex items-center gap-3 rounded-2xl border border-emerald-500/25 bg-emerald-500/5 p-4"> <div className="flex items-center gap-3 rounded-2xl border border-emerald-500/25 bg-emerald-500/5 p-4 h-full">
<Check className="h-5 w-5 shrink-0 text-emerald-400" /> <Check className="h-5 w-5 shrink-0 text-emerald-400" />
<div> <div>
<p className="text-sm font-semibold text-foreground">Nichts zu tun</p> <p className="text-sm font-semibold text-foreground">Nichts zu tun</p>
@@ -306,7 +281,7 @@ function NeedsYou({
</div> </div>
</div> </div>
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2 h-full flex flex-col justify-center">
{problems.map((c) => ( {problems.map((c) => (
<div key={c.id} className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 rounded-2xl border border-red-500/25 bg-red-500/5 p-4"> <div key={c.id} className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 rounded-2xl border border-red-500/25 bg-red-500/5 p-4">
<div className="flex min-w-0 items-center gap-3 w-full"> <div className="flex min-w-0 items-center gap-3 w-full">
@@ -341,7 +316,7 @@ function NeedsYou({
</span> </span>
<div> <div>
<p className="text-sm font-bold text-foreground">{openAuftraege} Vorschl{openAuftraege === 1 ? "ag" : "äge"} im Auftragsbuch</p> <p className="text-sm font-bold text-foreground">{openAuftraege} Vorschl{openAuftraege === 1 ? "ag" : "äge"} im Auftragsbuch</p>
<p className="text-xs text-muted-foreground">Die Box hat etwas gebaut oder sich ausgedacht annehmen oder ablehnen.</p> <p className="text-xs text-muted-foreground">Annehmen oder ablehnen.</p>
</div> </div>
</div> </div>
<ChevronRight className="h-4 w-4 shrink-0 text-primary/70" /> <ChevronRight className="h-4 w-4 shrink-0 text-primary/70" />
@@ -358,7 +333,7 @@ function NeedsYou({
</span> </span>
<div> <div>
<p className="text-sm font-bold text-foreground">{pendingCount} Update{pendingCount === 1 ? "" : "s"} bereit</p> <p className="text-sm font-bold text-foreground">{pendingCount} Update{pendingCount === 1 ? "" : "s"} bereit</p>
<p className="text-xs text-muted-foreground">Ansehen und entscheiden, ob du es einspielst.</p> <p className="text-xs text-muted-foreground">Ansehen und entscheiden.</p>
</div> </div>
</div> </div>
<ChevronRight className="h-4 w-4 shrink-0 text-amber-300/70" /> <ChevronRight className="h-4 w-4 shrink-0 text-amber-300/70" />
@@ -366,6 +341,90 @@ function NeedsYou({
)} )}
</div> </div>
)} )}
</section> </div>
) )
} }
function ToolListCard({ agent, svcErrors, onNavigate }: {
agent: any
svcErrors: number
onNavigate: (v: string) => void
}) {
const tools = [
{
label: "Hermes Agent",
status: agent ? (agent.gateway_reachable ? "Bereit" : "Offline") : "…",
tone: !agent ? "loading" : agent.gateway_reachable ? "ok" : "alert",
action: () => onNavigate("agent"),
},
{
label: "Direkte Box-Shell (SSH)",
status: agent ? (agent.box_console_reachable ? "Bereit" : "Offline") : "…",
tone: !agent ? "loading" : agent.box_console_reachable ? "ok" : "warn",
action: () => onNavigate("konsole"),
},
{
label: "Systemlogs & Diagnose",
status: svcErrors > 0 ? `${svcErrors} Fehler` : "Keine Fehler",
tone: svcErrors > 0 ? "alert" : "ok",
action: () => openDrawer("logs"),
},
{
label: "Interaktives Terminal",
status: "Öffnen",
tone: "muted",
action: () => onNavigate("terminal"),
},
{
label: "Wissens-Vault",
status: "Öffnen",
tone: "muted",
action: () => onNavigate("wissen"),
},
{
label: "Chronik & Zeitmaschine",
status: "Öffnen",
tone: "muted",
action: () => onNavigate("chronik"),
},
]
return (
<div className="rounded-2xl border border-border/60 bg-card/30 p-4 space-y-3 shadow-sm">
<div className="divide-y divide-border/20">
{tools.map((t, idx) => {
const dotColor = {
loading: "bg-muted-foreground/45 animate-pulse",
ok: "bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.4)]",
warn: "bg-amber-500 shadow-[0_0_8px_rgba(245,158,11,0.4)]",
alert: "bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.4)]",
muted: "bg-muted-foreground/30",
}[t.tone]
return (
<button
key={idx}
onClick={t.action}
className="w-full py-2.5 px-1.5 flex items-center justify-between text-left hover:bg-card/45 rounded-lg transition-all group cursor-pointer text-xs"
>
<span className="text-muted-foreground group-hover:text-foreground transition-colors font-medium">
{t.label}
</span>
<div className="flex items-center gap-2">
<span className={cn("text-[10px] font-semibold font-mono",
t.tone === "ok" ? "text-emerald-400" :
t.tone === "warn" ? "text-amber-400" :
t.tone === "alert" ? "text-red-400" : "text-muted-foreground/60"
)}>
{t.status}
</span>
<span className={cn("h-1.5 w-1.5 rounded-full", dotColor)} />
</div>
</button>
)
})}
</div>
</div>
)
}