+{text}) } -/** Dienste der Box mit Protokoll und Neustart — die „Log-Ansicht der Dienste“. */ -export function Dienste({ offen, onSchliessen }: { offen: boolean; onSchliessen: () => void }) { +function Zeile({ d, gesamt, offen, startet, onProtokoll, onFrage }: { + d: Dienst + gesamt: number | null | undefined + offen: boolean + startet: string | null + onProtokoll: () => void + onFrage: () => void +}) { + const z = ZUSTAND[zustandVon(d)] + const zusatz = zustandZusatz(d) + const anteil = speicherAnteil(d, gesamt) + const neustarts = d.neustarts ?? 0 + const protokollId = `protokoll-${d.unit}` + return ( +++ ) +} + +function Tabelle({ stand, gewaehlt, startet, onProtokoll, onFrage }: { + stand: DiensteStand + gewaehlt: string | null + startet: string | null + onProtokoll: (unit: string) => void + onFrage: (d: Dienst) => void +}) { + return ( ++ + + {d.name} + + {d.unit} + {d.scope === "system" ? " · Systemdienst" : ""} + + ++ +Zustand + {z.wort} + {zusatz && {zusatz}} + {/* Ohne Tabelle stehen die Neustarts hier — und nur, wenn es welche gab. */} + {neustarts > 0 && ( + {neustarts === 1 ? "1 Neustart" : `${neustarts} Neustarts`} + )} + + +CPU + {d.cpu_prozent == null + ? – + :{prozent(d.cpu_prozent)} } + + +Speicher + {d.speicher_bytes == null ? ( + – + ) : ( ++ {groesse(d.speicher_bytes + (d.grafik_bytes ?? 0))} + {anteil !== null && · {prozent(anteil)}} + + )} + {d.speicher_bytes != null && !!d.grafik_bytes && ( + davon {groesse(d.grafik_bytes)} Modelle + )} + + +Läuft seit + {dauer(d.laeuft_seit_s)} + {d.laeuft_seit_s != null && gestartet {zeitpunkt(d.laeuft_seit_s)}} + + + 0 ? "text-bernstein" : "text-text-3")}> + {d.neustarts ?? "–"} + + + {/* Am Handy unter den Zahlen; ab sm rechts neben dem Namen, ab xl in der letzten Spalte. */} + + + + + {offen && ( +++ )} ++ ++ ) +} + +/** Oben rechts: wann zuletzt gemessen wurde und ob etwas nicht stimmt. */ +function Lage({ dienste, stand }: { dienste: Dienst[]; stand: number }) { + const probleme = dienste.filter(istProblem).length + const wechsel = dienste.filter((d) => rang(d) === 1) + return ( + + {stand > 0 && Stand {uhrzeit.format(stand)}} + {probleme > 0 ? ( ++ Dienst + Zustand + CPU + Speicher + Läuft seit + Neustarts + Aktionen ++ {sortiert(stand.services).map((d) => ( +onProtokoll(d.unit)} + onFrage={() => onFrage(d)} + /> + ))} + {probleme === 1 ? "1 Problem" : `${probleme} Probleme`} + ) : wechsel.length > 0 ? ( +{wechsel.some((d) => zustandVon(d) === "startet") ? "Startet gerade" : "Stoppt gerade"} + ) : ( +Alles in Ordnung + )} + + ) +} + +/** KI-Box · Dienste und Protokolle: welcher Dienst läuft, was er braucht und was er zuletzt geschrieben hat. */ +export function DiensteSeite() { const qc = useQueryClient() const dienste = useDienste() const [gewaehlt, setGewaehlt] = useState(null) - const [bestaetigen, setBestaetigen] = useState (null) + // Die Rückfrage behält ihren Dienst, bis die nächste kommt — sonst stünde sie beim Ausblenden leer da. + const [frage, setFrage] = useState (null) + const [fragt, setFragt] = useState(false) const [startet, setStartet] = useState (null) - async function neustarten(d: Dienst) { + async function starten(d: Dienst) { if (startet) return - setBestaetigen(null) setStartet(d.unit) try { const e = await post ("/api/maintenance/restart", { service: d.unit }) - if (e.ok) melden("erfolg", `${d.name} ${d.schlaeft ? "wird geweckt" : "startet neu"}.`) + if (e.ok) melden("erfolg", erfolgText(d)) else melden("fehler", `${d.name} ließ sich nicht starten: ${fehlerText(e)}`) - qc.invalidateQueries({ queryKey: ["dienste"] }) } catch (f) { - melden("fehler", (f as Error).message) + // Startet der Box-Wart sich selbst neu, reißt seine Antwort ab — das ist dann kein Fehler. + if (d.unit === "mission-control-2" && f instanceof ApiFehler) melden("info", "Der Box-Wart startet neu. Die Seite ist gleich wieder da.") + else melden("fehler", (f as Error).message) } finally { setStartet(null) + qc.invalidateQueries({ queryKey: ["dienste"] }) + qc.invalidateQueries({ queryKey: ["protokoll", d.unit] }) } } - let inhalt - if (dienste.isPending) { - inhalt = Dienste werden gelesen …
- } else if (dienste.isError && !dienste.data) { - inhalt =Die Dienste lassen sich gerade nicht lesen: {dienste.error.message}
- } else if (!dienste.data?.services.length) { - inhalt =Keine Dienste gefunden.
- } else { - inhalt = ( -- {dienste.data.services.map((d) => ( -
- ) + if (dienste.isPending) return- - - - - {d.name} - - {d.unit} · {d.ok ? "läuft" : d.schlaeft ? "schläft (abgeschaltet)" : "antwortet nicht"} - - - - - - {bestaetigen === d.unit ? ( - <> - - - > - ) : ( - - )} - - {gewaehlt === d.unit &&
- ))} -} - + // Scheitert nur ein späteres Nachladen, bleibt der letzte Stand stehen. + if (!dienste.data) { + return } + const stand = dienste.data + const rueckfrage = frage ? neustartFrage(frage) : null + const speicher = stand.speicher_gesamt ? `den ${gbText(stand.speicher_gesamt)}` : "dem Arbeitsspeicher" + const kerne = stand.kerne ? `allen ${stand.kerne} Kernen` : "allen Kernen" return ( - !o && onSchliessen()}> - +- -- - {inhalt} -Dienste und Protokolle -Welcher Dienst läuft, was er zuletzt geschrieben hat. -}> + + Die Dienste der KI-Box: ob sie laufen, was sie gerade an CPU und Speicher brauchen, seit wann sie laufen und was + sie zuletzt ins Protokoll geschrieben haben. Probleme stehen oben. +
+ {stand.services.length === 0 ? ( +Keine Dienste gefunden.
+ ) : ( +setGewaehlt(gewaehlt === unit ? null : unit)} + onFrage={(d) => { + setFrage(d) + setFragt(true) + }} + /> + )} + + CPU: Anteil an {kerne} der Box, gemessen zwischen zwei Abfragen (alle zehn Sekunden). Speicher: was systemd dem + Dienst zurechnet; der Balken zeigt den Anteil an {speicher} der Box. Bei der Engine zählen die geladenen Modelle + mit — sie liegen im Grafikspeicher, den systemd keinem Dienst zurechnet. Neustarts: wie oft systemd den Dienst + nach einem Absturz von selbst wieder gestartet hat. „Schläft“ heißt: mit Absicht abgeschaltet, kein Fehler. +
+{ + setFragt(false) + if (frage) void starten(frage) + }} + onNein={() => setFragt(false)} + /> + ) }