umbau(boxwart): neue Oberflaeche im Cockpit-Stil (Richtung A), Werkzeuge angehoben

Drei Seiten statt zehn: Start, Updates, Modelle — am PC mit Kopfnavigation, am Handy
mit Leiste unten. Umsetzung von Mockup A („Cockpit“), vom User am 23.09. gewaehlt.

- Start: Hauptwarnleuchte + 8 Warnlampen, Rundinstrumente mit Live-Werten aus dem
  Strom (Speicher, Temperatur, Platte) und Laufzeit-Zaehlwerk, Checkliste der
  Waechter-Hinweise mit ihren Knoepfen, Flugplan (heute gelaufen / geplant), Radar-Kasten.
- Updates: Bausteine mit „Laeuft → Neu“ und Zusammenfassung, laufende Auftraege,
  Verlauf der Sonntagslaeufe (neu: GET /api/updates/verlauf), Sicherungen samt
  Zurueckspielen mit Rueckfrage.
- Modelle: Speicherbalken, Rollen Hirn/Coder/Dritte Rolle, wer die Modelle nutzt
  (7 Tage + 24 h je Stunde), Modell-Radar, weitere Eintraege, Modelle selbst suchen.
- Schubladen: Dienste mit Protokoll und Neustart, Einstellungen (HF-Zugang), Hermes-Link.
- Werkzeuge: Vite 8, React 19.3 mit React Compiler 1.0 (Babel), vitest 5, Tailwind 4.3,
  shadcn 4 (Radix) fuer Dialog/Schublade/Knopf, Schriften Barlow/Barlow Condensed/
  JetBrains Mono. Entfernt: recharts, cmdk, Kraftgraph, zustand, Inter, Space Grotesk.
- Startbuendel 115 KB gzip (Budget 140); Updates/Modelle/Schubladen laden bei Bedarf.
- 16 Oberflaechen-Tests (Instrument-Bogen, Hauptleuchte, Zeitformate, Versionen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-23 21:39:07 +02:00
co-authored by Claude Opus 5.5
parent 3d2c9549fb
commit 907289d7dc
223 changed files with 8572 additions and 15393 deletions
+68
View File
@@ -0,0 +1,68 @@
import { useState } from "react"
import { useQueryClient } from "@tanstack/react-query"
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
import { useDienste, useProtokoll } from "@/lib/abfragen"
import { post } from "@/lib/api"
import { melden } from "@/lib/meldungen"
import { cn } from "cn"
/** Dienste der Box mit Protokoll und Neustart — die „Log-Ansicht der Dienste“. */
export function Dienste({ offen, onSchliessen }: { offen: boolean; onSchliessen: () => void }) {
const qc = useQueryClient()
const dienste = useDienste()
const [gewaehlt, setGewaehlt] = useState<string | null>(null)
const [bestaetigen, setBestaetigen] = useState<string | null>(null)
const protokoll = useProtokoll(gewaehlt)
async function neustarten(unit: string) {
setBestaetigen(null)
try {
const e = await post<{ ok: boolean; err?: string }>("/api/maintenance/restart", { service: unit })
if (e.ok) melden("erfolg", `${unit} startet neu.`)
else melden("fehler", e.err || `${unit} ließ sich nicht neu starten.`)
qc.invalidateQueries({ queryKey: ["dienste"] })
} catch (f) {
melden("fehler", (f as Error).message)
}
}
return (
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
<SheetContent side="right" className="w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-2xl">
<SheetHeader>
<SheetTitle className="schild text-lg">Dienste und Protokolle</SheetTitle>
<SheetDescription className="text-text-2">Welcher Dienst läuft, was er zuletzt geschrieben hat.</SheetDescription>
</SheetHeader>
<ul className="m-0 flex list-none flex-col px-4 pb-2">
{(dienste.data?.services ?? []).map((d) => (
<li key={d.unit} className="flex flex-wrap items-center justify-between gap-2 border-t border-linie py-2.5">
<span className="flex min-w-0 items-center gap-2.5">
<span className={cn("size-2.5 shrink-0 rounded-full", d.ok ? "bg-gruen" : "bg-rot")} aria-hidden />
<span className="min-w-0">
<span className="block text-[15px]">{d.name}</span>
<span className="ziffern text-xs text-text-3">{d.unit} · {d.ok ? "läuft" : "antwortet nicht"}</span>
</span>
</span>
<span className="flex gap-2">
<Button variant={gewaehlt === d.unit ? "info" : "ghost"} size="sm" onClick={() => setGewaehlt(d.unit)}>
Protokoll
</Button>
{bestaetigen === d.unit ? (
<Button variant="gefahr" size="sm" onClick={() => neustarten(d.unit)}>Wirklich neu starten?</Button>
) : (
<Button variant="outline" size="sm" onClick={() => setBestaetigen(d.unit)}>Neu starten</Button>
)}
</span>
</li>
))}
</ul>
{gewaehlt && (
<pre className="ziffern mx-4 mb-4 max-h-[50vh] overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap text-text-2">
{protokoll.isFetching ? "Wird gelesen …" : protokoll.data?.text || protokoll.data?.err || "Kein Protokoll."}
</pre>
)}
</SheetContent>
</Sheet>
)
}