Single Source of Truth: Frontend/Backend-Ungereimtheiten ausgeraeumt (Review 15.07.)
Backend (Wahrheit reparieren): - gateway_reachable() prueft jetzt den ECHTEN mc2-gateway (:9010 via V1_UPSTREAM) statt nur die Engine - toter Denkpfad war in Health/Cockpit unsichtbar - /api/system/services vollstaendig: + LLM-Gateway, + Steuerpult, + Waechter (mc2-steward via is-active); scope-Feld (user/system); mc2-gateway/mc2-steward in die Restart-Allowlist - Connect-Health Leitung 1 prueft den Client-Pfad (V1_UPSTREAM statt llama-swap) - agent_status liefert pc_executor_url (UI zeigte hartcodierte IP/Ports) - SSE-Endlosschleife gefixt: /api/auftragsbuch schrieb announce-branches bei JEDEM Aufruf neu -> mtime-Bump -> invalidate -> Refetch im 3-s-Takt je Client; jetzt nur noch bei echter Aenderung Frontend (ein Datenweg): - SystemDrawer komplett auf React-Query-Hooks (vorher eigenes 3-s-setInterval auf dieselben Endpunkte inkl. teurem Updates-Check); Dienste-Liste = Backend-Antwort (SERVICES-UI-Kopie geloescht); useDialog statt Eigenbau; fmtBytes statt Duplikat - useLucyHealth: Dienst-Matching per systemd-unit statt Namensfragment; Gateway- Reparatur zielt auf mc2-gateway; neuer Waechter-Check; Backend-tot => ehrliches rotes Verdikt statt eingefrorener letzter Stand (auch Sidebar) - Toter Code raus: views/models/Cockpit.tsx (908 Z.) + RoleAssignModal + Placeholder; LaneEditor (Routing-Policy) + WarmSetManager damit ZURUECK im UI als Tab "Routing & Warm-Set" im Modell-Manager - Cockpit: blockierte Ideen-Karten erscheinen in "Braucht dich" + Kachel-Hint; Verbinden-Kachel zeigt 3 Leitungen live statt hartem "Zed" - Maschinenraum: ehrliches "frei" (reale Belegung) + eigenes KV-Cache-Segment - AgentView: Ports/PC-Adresse aus der API; Guide lehrt Lanes chat/coding - queries.ts: useModels nutzt SSE-relax; Chronik-Limit im Query-Key; useJobs/useZeitmaschine mit enabled-Schalter Verifiziert: tsc+vite gruen, Backend-Smoke beide Gateway-Modi, UI live gegen die Box (Cockpit/Werkbank/Routing-Tab/Drawer rendern mit Echtdaten). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,12 @@ import { useState } from "react"
|
||||
import {
|
||||
Boxes, Brain, Server, ShieldAlert, Plug,
|
||||
HeartPulse, AlertTriangle, Loader2, Wrench, Check, HardDrive, ChevronRight,
|
||||
Inbox,
|
||||
Inbox, HelpCircle,
|
||||
} from "lucide-react"
|
||||
import { api } from "@/lib/api"
|
||||
import {
|
||||
useModels, useServices, useUpdates, useMemory, useAgentStatus, useConnectHealth,
|
||||
useAuftragsbuch, useQueryClient, qk,
|
||||
useAuftragsbuch, useIdeen, useQueryClient, qk,
|
||||
} from "@/lib/queries"
|
||||
import { useLucyHealth, type Repair } from "@/lib/useLucyHealth"
|
||||
import { useDialog } from "@/lib/useDialog"
|
||||
@@ -34,6 +34,7 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
const { data: agent } = useAgentStatus()
|
||||
const { data: connect } = useConnectHealth()
|
||||
const { data: auftraege } = useAuftragsbuch()
|
||||
const { data: ideen } = useIdeen()
|
||||
const health = useLucyHealth()
|
||||
|
||||
const { showAlert, dialogElement } = useDialog()
|
||||
@@ -71,8 +72,17 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
const pendingCount =
|
||||
(updates ? (updates.os > 0 ? 1 : 0) + (updates.engine > 0 ? 1 : 0) + (updates.swap > 0 ? 1 : 0) + (updates.models > 0 ? 1 : 0) : 0) +
|
||||
(updates?.components?.filter((c) => c.update === true).length ?? 0)
|
||||
const connectOk = connect?.gateway.ok && connect?.memory.ok
|
||||
const openAuftraege = auftraege?.available ? auftraege.open_count : 0
|
||||
// Hängende Karten warten auf DEINE Antwort — bisher sah das Cockpit sie gar nicht
|
||||
// („Nichts zu tun" trotz roter Karte im Auftragsbuch, Review 15.07.).
|
||||
const blockedIdeen = ideen?.available ? ideen.items.filter((i) => i.status === "blocked").length : 0
|
||||
|
||||
// Verbinden: die drei Leitungen (Modell-Gateway · Gedächtnis · Desktop-Gateway).
|
||||
const connectLines = connect ? [connect.gateway, connect.memory, connect.desktop_gateway] : []
|
||||
const connectOkCount = connectLines.filter((l) => l?.ok).length
|
||||
const connectBad = connect
|
||||
? (!connect.gateway.ok ? "Modell-Leitung" : !connect.memory.ok ? "Gedächtnis-Leitung" : !connect.desktop_gateway.ok ? "Desktop-Gateway" : "")
|
||||
: ""
|
||||
|
||||
// Zusätzliche reaktive Livedaten für die Kacheln
|
||||
const activeBrain = models?.models?.find((m: any) => m.role === "hermes")
|
||||
@@ -103,7 +113,7 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
{/* Hauptspalte Links */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* Status-Zeile */}
|
||||
<StatusLine verdict={health.verdict} memory={health.memory} warns={health.warns} />
|
||||
<StatusLine verdict={health.verdict} memory={health.memory} warns={health.warns} problems={health.problems} />
|
||||
|
||||
{/* Morgenlage — das jüngste Nacht-Gutachten (verschwindet, wenn keines da ist) */}
|
||||
<MorgenlageCard />
|
||||
@@ -127,8 +137,10 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
<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` : "Keine ausstehenden Vorschläge"}
|
||||
tone={!auftraege ? "loading" : blockedIdeen > 0 || openAuftraege > 0 ? "warn" : "ok"}
|
||||
hint={blockedIdeen > 0
|
||||
? `${blockedIdeen} Karte${blockedIdeen === 1 ? "" : "n"} wartet auf deine Antwort`
|
||||
: openAuftraege > 0 ? `${openAuftraege} offene Patches` : "Keine ausstehenden Vorschläge"}
|
||||
onClick={() => onNavigate("auftraege")}
|
||||
/>
|
||||
<CockpitTile
|
||||
@@ -157,9 +169,10 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
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)"}
|
||||
icon={Plug} title="Verbinden" value={connect ? `${connectOkCount}/${connectLines.length}` : "…"}
|
||||
unit="Leitungen"
|
||||
tone={!connect ? "loading" : connectOkCount === connectLines.length ? "ok" : "warn"}
|
||||
hint={connectBad ? `Gestört: ${connectBad}` : "Hermes Desktop & IDEs bereit"}
|
||||
onClick={() => onNavigate("connect")}
|
||||
/>
|
||||
</div>
|
||||
@@ -173,6 +186,7 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
problems={health.problems}
|
||||
pendingCount={pendingCount}
|
||||
openAuftraege={openAuftraege}
|
||||
blockedIdeen={blockedIdeen}
|
||||
busy={busy}
|
||||
onRepair={runRepair}
|
||||
onNavigate={onNavigate}
|
||||
@@ -202,19 +216,23 @@ function ZoneLabel({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
// Ehrliche Status-Zeile: großes Verdikt + Speicher-Pille. Erzählrahmen = Box.
|
||||
function StatusLine({ verdict, memory, warns }: {
|
||||
function StatusLine({ verdict, memory, warns, problems }: {
|
||||
verdict: ReturnType<typeof useLucyHealth>["verdict"]
|
||||
memory: ReturnType<typeof useLucyHealth>["memory"]
|
||||
warns: ReturnType<typeof useLucyHealth>["warns"]
|
||||
problems: ReturnType<typeof useLucyHealth>["problems"]
|
||||
}) {
|
||||
const warnText = warns.length
|
||||
? `${warns[0].label}: ${warns[0].detail}${warns.length > 1 ? ` (+${warns.length - 1} weitere)` : ""}`
|
||||
: memory.status === "full" ? memory.text : "Nichts Schlimmes — nur ein Hinweis."
|
||||
const problemText = problems.length
|
||||
? `${problems[0].label}: ${problems[0].detail}${problems.length > 1 ? ` (+${problems.length - 1} weitere)` : ""}`
|
||||
: "Ein wichtiger Dienst hakt."
|
||||
const b = {
|
||||
loading: { ring: "border-border/60 bg-card/45", icon: Loader2, iconCls: "text-muted-foreground animate-spin", title: "Box wird geprüft …", sub: "Einen Moment." },
|
||||
gut: { ring: "border-emerald-500/40 bg-emerald-500/5", icon: HeartPulse, iconCls: "text-emerald-400", title: "Box gesund", sub: "Alle wichtigen Dienste laufen." },
|
||||
warn: { ring: "border-amber-500/40 bg-amber-500/5", icon: AlertTriangle, iconCls: "text-amber-400", title: "Kleinigkeit an der Box", sub: warnText },
|
||||
problem: { ring: "border-red-500/50 bg-red-500/5", icon: AlertTriangle, iconCls: "text-red-400", title: "Box braucht Hilfe", sub: "Ein wichtiger Dienst hakt." },
|
||||
problem: { ring: "border-red-500/50 bg-red-500/5", icon: AlertTriangle, iconCls: "text-red-400", title: "Box braucht Hilfe", sub: problemText },
|
||||
}[verdict]
|
||||
const Icon = b.icon
|
||||
const memTone = { ok: "text-emerald-400", warn: "text-amber-400", full: "text-red-400", unknown: "text-muted-foreground" }[memory.status]
|
||||
@@ -253,18 +271,20 @@ function StatusLine({ verdict, memory, warns }: {
|
||||
// „Braucht dich": kritische Probleme (mit 1-Klick-Reparatur) + bereitliegende Updates
|
||||
// + offene Vorschläge im Auftragsbuch.
|
||||
function NeedsYou({
|
||||
problems, pendingCount, openAuftraege, busy, onRepair, onNavigate,
|
||||
problems, pendingCount, openAuftraege, blockedIdeen, busy, onRepair, onNavigate,
|
||||
}: {
|
||||
problems: ReturnType<typeof useLucyHealth>["problems"]
|
||||
pendingCount: number
|
||||
openAuftraege: number
|
||||
blockedIdeen: number
|
||||
busy: Record<string, boolean>
|
||||
onRepair: (id: string, r: Repair) => void
|
||||
onNavigate: (v: string) => void
|
||||
}) {
|
||||
const hasUpdates = pendingCount > 0
|
||||
const hasAuftraege = openAuftraege > 0
|
||||
const nothing = problems.length === 0 && !hasUpdates && !hasAuftraege
|
||||
const hasBlocked = blockedIdeen > 0
|
||||
const nothing = problems.length === 0 && !hasUpdates && !hasAuftraege && !hasBlocked
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col justify-between">
|
||||
@@ -301,6 +321,23 @@ function NeedsYou({
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{hasBlocked && (
|
||||
<button
|
||||
onClick={() => onNavigate("auftraege")}
|
||||
className="flex w-full items-center justify-between gap-3 rounded-2xl border border-red-500/30 bg-red-500/5 p-4 text-left transition-all hover:bg-red-500/10 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-red-500/30 bg-background/30 text-red-300">
|
||||
<HelpCircle className="h-4.5 w-4.5" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-foreground">{blockedIdeen} Karte{blockedIdeen === 1 ? "" : "n"} braucht deine Antwort</p>
|
||||
<p className="text-xs text-muted-foreground">Die Box hängt an einer Frage und wartet auf dich.</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-red-300/70" />
|
||||
</button>
|
||||
)}
|
||||
{hasAuftraege && (
|
||||
<button
|
||||
onClick={() => onNavigate("auftraege")}
|
||||
|
||||
Reference in New Issue
Block a user