Wartung: Cockpit-Restrukturierung (zweispaltiges Grid, ToolListCard, ungenutzte Imports entfernt)
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+127
-132
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -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>
|
||||||
|
|||||||
@@ -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,124 +98,103 @@ 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) */}
|
||||||
<StatusLine verdict={health.verdict} memory={health.memory} warns={health.warns} />
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-stretch">
|
||||||
|
<div className="lg:col-span-2">
|
||||||
{/* Morgenlage — das jüngste Nacht-Gutachten (verschwindet, wenn keines da ist) */}
|
<StatusLine verdict={health.verdict} memory={health.memory} warns={health.warns} />
|
||||||
<MorgenlageCard />
|
</div>
|
||||||
|
<div>
|
||||||
{/* „Braucht dich" — offene Aktionen, sonst Ruhe */}
|
<NeedsYou
|
||||||
<NeedsYou
|
problems={health.problems}
|
||||||
problems={health.problems}
|
pendingCount={pendingCount}
|
||||||
pendingCount={pendingCount}
|
openAuftraege={openAuftraege}
|
||||||
openAuftraege={openAuftraege}
|
busy={busy}
|
||||||
busy={busy}
|
onRepair={runRepair}
|
||||||
onRepair={runRepair}
|
onNavigate={onNavigate}
|
||||||
onNavigate={onNavigate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Bereichs-Kacheln */}
|
|
||||||
<section>
|
|
||||||
<ZoneLabel>Bereiche</ZoneLabel>
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<CockpitTile
|
|
||||||
icon={Inbox} title="Auftragsbuch" value={auftraege?.available ? openAuftraege : "—"}
|
|
||||||
unit={openAuftraege === 1 ? "Vorschlag" : "Vorschläge"}
|
|
||||||
tone={!auftraege ? "loading" : openAuftraege > 0 ? "warn" : "ok"}
|
|
||||||
hint={openAuftraege > 0 ? `${openAuftraege} offene Patches prüfen` : "Keine ausstehenden Vorschläge"}
|
|
||||||
onClick={() => onNavigate("auftraege")}
|
|
||||||
/>
|
|
||||||
<CockpitTile
|
|
||||||
icon={Boxes} title="Modelle" value={running} unit="warm"
|
|
||||||
tone={running > 0 ? "ok" : "muted"}
|
|
||||||
hint={brainName ? `Hirn: ${brainName}` : "Kein Hermes-Modell geladen"}
|
|
||||||
onClick={() => onNavigate("models")}
|
|
||||||
/>
|
|
||||||
<CockpitTile
|
|
||||||
icon={Brain} title="Gedächtnis" value={memory ? notes : "…"} unit="Notizen"
|
|
||||||
tone={memory ? "ok" : "loading"}
|
|
||||||
hint={memory ? `${notes} Fakten (${autoNotes} auto gelernt)` : "Gedächtnis-Pool laden..."}
|
|
||||||
onClick={() => onNavigate("memory")}
|
|
||||||
/>
|
|
||||||
<CockpitTile
|
|
||||||
icon={Server} title="Dienste" value={svc ? `${svcOk}/${svcTotal}` : "…"} unit="laufen"
|
|
||||||
tone={!svc ? "loading" : svcErrors > 0 ? "warn" : "ok"}
|
|
||||||
hint={failedSvc ? `Ausfall: ${failedSvc}` : "Alle Dienste online"}
|
|
||||||
onClick={() => openDrawer("maintenance")}
|
|
||||||
/>
|
|
||||||
<CockpitTile
|
|
||||||
icon={ShieldAlert} title="Updates" value={updates ? (pendingCount > 0 ? pendingCount : "0") : "…"}
|
|
||||||
unit={pendingCount > 0 ? "bereit" : "aktuell"}
|
|
||||||
tone={!updates ? "loading" : pendingCount > 0 ? "warn" : "ok"}
|
|
||||||
hint={pendingCount > 0 ? `Verfügbar: ${updateSummary}` : "Alles auf dem neuesten Stand"}
|
|
||||||
onClick={() => openDrawer("maintenance")}
|
|
||||||
/>
|
|
||||||
<CockpitTile
|
|
||||||
icon={Plug} title="Verbinden" value="Zed" unit={connectOk ? "· bereit" : ""}
|
|
||||||
tone={!connect ? "loading" : connectOk ? "ok" : "warn"}
|
|
||||||
hint={agent?.pc_executor_reachable ? "IDE & PC online (:7777)" : "IDE-Kopplung (PC offline)"}
|
|
||||||
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>
|
</div>
|
||||||
|
|
||||||
{/* Anstehendes — Erinnerungen & Routinen (GUI-Hälfte der Wecker-API) */}
|
{/* Main Grid: 2/3 Links (Inhalte & Performance), 1/3 Rechts (Shortcuts & Kalender) */}
|
||||||
<section>
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-start">
|
||||||
<ZoneLabel>Anstehendes</ZoneLabel>
|
{/* Hauptspalte Links */}
|
||||||
<ErinnerungenCard />
|
<div className="lg:col-span-2 space-y-6">
|
||||||
</section>
|
{/* Morgenlage — das jüngste Nacht-Gutachten (verschwindet, wenn keines da ist) */}
|
||||||
|
<MorgenlageCard />
|
||||||
|
|
||||||
{/* Leistung — die Live-Graphen zurück auf der Startseite (User-Wunsch 07.07.:
|
{/* Bereichs-Kacheln (6 statt 7 - Logs ist in die Werkzeug-Liste gewandert) */}
|
||||||
System-Verlauf, tok/s und Turn-Latenz waren nach dem UI-v3-Umbau nur noch in
|
<section>
|
||||||
der alten Zentrale erreichbar). Karten sind autark (eigene Hooks/Polling). */}
|
<ZoneLabel>Bereiche</ZoneLabel>
|
||||||
<section>
|
<div className="grid gap-4 grid-cols-2 md:grid-cols-3">
|
||||||
<ZoneLabel>Leistung</ZoneLabel>
|
<CockpitTile
|
||||||
<div className="grid gap-4 lg:grid-cols-2">
|
icon={Inbox} title="Auftragsbuch" value={auftraege?.available ? openAuftraege : "—"}
|
||||||
<SystemStatusCard />
|
unit={openAuftraege === 1 ? "Vorschlag" : "Vorschläge"}
|
||||||
<TokenPerformanceCard />
|
tone={!auftraege ? "loading" : openAuftraege > 0 ? "warn" : "ok"}
|
||||||
</div>
|
hint={openAuftraege > 0 ? `${openAuftraege} offene Patches` : "Keine ausstehenden Vorschläge"}
|
||||||
<div className="mt-4">
|
onClick={() => onNavigate("auftraege")}
|
||||||
<LatencyCard />
|
/>
|
||||||
</div>
|
<CockpitTile
|
||||||
</section>
|
icon={Boxes} title="Modelle" value={running} unit="warm"
|
||||||
|
tone={running > 0 ? "ok" : "muted"}
|
||||||
|
hint={brainName ? `Hirn: ${brainName}` : "Kein Hermes-Modell geladen"}
|
||||||
|
onClick={() => onNavigate("models")}
|
||||||
|
/>
|
||||||
|
<CockpitTile
|
||||||
|
icon={Brain} title="Gedächtnis" value={memory ? notes : "…"} unit="Notizen"
|
||||||
|
tone={memory ? "ok" : "loading"}
|
||||||
|
hint={memory ? `${notes} Fakten (${autoNotes} auto gelernt)` : "Gedächtnis-Pool laden..."}
|
||||||
|
onClick={() => onNavigate("memory")}
|
||||||
|
/>
|
||||||
|
<CockpitTile
|
||||||
|
icon={Server} title="Dienste" value={svc ? `${svcOk}/${svcTotal}` : "…"} unit="laufen"
|
||||||
|
tone={!svc ? "loading" : svcErrors > 0 ? "warn" : "ok"}
|
||||||
|
hint={failedSvc ? `Ausfall: ${failedSvc}` : "Alle Dienste online"}
|
||||||
|
onClick={() => openDrawer("maintenance")}
|
||||||
|
/>
|
||||||
|
<CockpitTile
|
||||||
|
icon={ShieldAlert} title="Updates" value={updates ? (pendingCount > 0 ? pendingCount : "0") : "…"}
|
||||||
|
unit={pendingCount > 0 ? "bereit" : "aktuell"}
|
||||||
|
tone={!updates ? "loading" : pendingCount > 0 ? "warn" : "ok"}
|
||||||
|
hint={pendingCount > 0 ? `Verfügbar: ${updateSummary}` : "Alles auf dem neuesten Stand"}
|
||||||
|
onClick={() => openDrawer("maintenance")}
|
||||||
|
/>
|
||||||
|
<CockpitTile
|
||||||
|
icon={Plug} title="Verbinden" value="Zed" unit={connectOk ? "· bereit" : ""}
|
||||||
|
tone={!connect ? "loading" : connectOk ? "ok" : "warn"}
|
||||||
|
hint={agent?.pc_executor_reachable ? "IDE & PC online (:7777)" : "IDE-Kopplung (PC offline)"}
|
||||||
|
onClick={() => onNavigate("connect")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Sekundäre Bereiche */}
|
{/* Leistung — die Live-Graphen */}
|
||||||
<section>
|
<section>
|
||||||
<ZoneLabel>Mehr</ZoneLabel>
|
<ZoneLabel>Leistung</ZoneLabel>
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<CockpitTile
|
<SystemStatusCard />
|
||||||
icon={Bot} title="Hermes" value={agent ? (agent.gateway_reachable ? "bereit" : "offline") : "…"}
|
<TokenPerformanceCard />
|
||||||
tone={!agent ? "loading" : agent.gateway_reachable ? "ok" : "alert"} hint="Agent-Status & Verdrahtung"
|
</div>
|
||||||
onClick={() => onNavigate("agent")}
|
<div className="mt-4">
|
||||||
/>
|
<LatencyCard />
|
||||||
<CockpitTile
|
</div>
|
||||||
icon={TerminalSquare} title="Terminal" value="öffnen"
|
</section>
|
||||||
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>
|
||||||
</section>
|
|
||||||
|
{/* Seitenspalte Rechts */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Anstehendes — Erinnerungen & Routinen */}
|
||||||
|
<section>
|
||||||
|
<ZoneLabel>Anstehendes</ZoneLabel>
|
||||||
|
<ErinnerungenCard />
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user