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:
co-authored by
Claude Opus 5.5
parent
3d2c9549fb
commit
907289d7dc
@@ -0,0 +1,173 @@
|
||||
// Alle Abfragen und Aktionen der Oberfläche an einem Ort (TanStack Query).
|
||||
//
|
||||
// Takte: Steht der Live-Strom, meldet er Änderungen selbst — die Abfragen sind dann nur
|
||||
// Sicherheitsnetz und laufen fünfmal seltener.
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import { api, post } from "./api"
|
||||
import { melden } from "./meldungen"
|
||||
import { stromVerbunden } from "./strom"
|
||||
import type {
|
||||
AktionsErgebnis, Job, Modell, Nutzung, Radar, Sicherung, Start,
|
||||
} from "./typen"
|
||||
|
||||
const takt = (ms: number) => () => (stromVerbunden() ? ms * 5 : ms)
|
||||
|
||||
export const useStart = () =>
|
||||
useQuery({ queryKey: ["start"], queryFn: () => api<Start>("/api/start"), refetchInterval: takt(20_000) })
|
||||
|
||||
export const useModelle = () =>
|
||||
useQuery({
|
||||
queryKey: ["models"],
|
||||
queryFn: () => api<{ models: Modell[]; running: string[] }>("/api/models"),
|
||||
refetchInterval: takt(30_000),
|
||||
})
|
||||
|
||||
export const useNutzung = () =>
|
||||
useQuery({ queryKey: ["nutzung"], queryFn: () => api<Nutzung>("/api/modelle/nutzung"), refetchInterval: 600_000 })
|
||||
|
||||
export const useRadar = () =>
|
||||
useQuery({ queryKey: ["radar"], queryFn: () => api<Radar>("/api/radar"), retry: false, refetchInterval: 300_000 })
|
||||
|
||||
export const useJobs = () =>
|
||||
useQuery({
|
||||
queryKey: ["jobs"],
|
||||
queryFn: () => api<{ jobs: Job[] }>("/api/jobs"),
|
||||
// Läuft etwas, wollen wir den Fortschritt sehen; sonst reicht der Strom.
|
||||
refetchInterval: (q) =>
|
||||
q.state.data?.jobs.some((j) => j.state === "running" || j.state === "queued") ? 2000 : takt(30_000)(),
|
||||
})
|
||||
|
||||
export const useSicherungen = () =>
|
||||
useQuery({
|
||||
queryKey: ["sicherungen"],
|
||||
queryFn: () => api<{ available: boolean; backups: Sicherung[] }>("/api/zeitmaschine"),
|
||||
})
|
||||
|
||||
export interface UpdateDetails {
|
||||
kind: string
|
||||
installed_build?: number | null
|
||||
latest_build?: number | null
|
||||
latest_tag?: string | null
|
||||
count?: number
|
||||
packages?: { name: string; current: string; candidate: string }[]
|
||||
summary?: string
|
||||
action_needed?: boolean | null
|
||||
action_text?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export const useUpdateDetails = (art: string | null) =>
|
||||
useQuery({
|
||||
queryKey: ["update-details", art],
|
||||
queryFn: () => api<UpdateDetails>(`/api/maintenance/update-details?kind=${art}`),
|
||||
enabled: !!art,
|
||||
staleTime: 600_000,
|
||||
})
|
||||
|
||||
export interface UpdateLauf {
|
||||
start: string | null
|
||||
ende: string | null
|
||||
status: string
|
||||
fehler: string | null
|
||||
job: string
|
||||
}
|
||||
|
||||
export const useUpdateVerlauf = () =>
|
||||
useQuery({ queryKey: ["updates-verlauf"], queryFn: () => api<{ laeufe: UpdateLauf[] }>("/api/updates/verlauf") })
|
||||
|
||||
export const useProtokoll = (dienst: string | null) =>
|
||||
useQuery({
|
||||
queryKey: ["protokoll", dienst],
|
||||
queryFn: () => api<{ ok: boolean; text?: string; err?: string }>(
|
||||
`/api/maintenance/logs?service=${encodeURIComponent(dienst ?? "")}&lines=200`),
|
||||
enabled: !!dienst,
|
||||
})
|
||||
|
||||
export interface Dienst {
|
||||
name: string
|
||||
unit: string
|
||||
ok: boolean
|
||||
scope: string
|
||||
}
|
||||
|
||||
export const useDienste = () =>
|
||||
useQuery({ queryKey: ["dienste"], queryFn: () => api<{ services: Dienst[] }>("/api/system/services") })
|
||||
|
||||
// --- Aktionen ----------------------------------------------------------------
|
||||
|
||||
function useAktion<V, E extends AktionsErgebnis | Record<string, unknown>>(
|
||||
fn: (v: V) => Promise<E>,
|
||||
opt: { erfolg: (v: V, e: E) => string; neuLaden: string[] },
|
||||
) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: fn,
|
||||
onSuccess: (e, v) => {
|
||||
const ergebnis = e as AktionsErgebnis
|
||||
if (ergebnis && ergebnis.ok === false) {
|
||||
melden("fehler", ergebnis.detail || ergebnis.err || "Das hat nicht geklappt.")
|
||||
} else {
|
||||
melden("erfolg", opt.erfolg(v, e))
|
||||
}
|
||||
for (const k of opt.neuLaden) qc.invalidateQueries({ queryKey: [k] })
|
||||
},
|
||||
onError: (f: Error) => melden("fehler", f.message),
|
||||
})
|
||||
}
|
||||
|
||||
export const useHinweisAktion = () =>
|
||||
useAktion(
|
||||
({ hinweis, aktion }: { hinweis: string; aktion: string; label: string }) =>
|
||||
post<AktionsErgebnis>(`/api/hinweise/${encodeURIComponent(hinweis)}/aktion/${aktion}`),
|
||||
{ erfolg: (v) => `${v.label}: erledigt. Der Wächter prüft in einer Minute nach.`, neuLaden: ["start"] },
|
||||
)
|
||||
|
||||
const UPDATE_PFADE: Record<string, string> = {
|
||||
alle: "/api/maintenance/update-all",
|
||||
pruefen: "/api/maintenance/check-updates",
|
||||
os: "/api/maintenance/os-update",
|
||||
engine: "/api/maintenance/engine-update",
|
||||
swap: "/api/maintenance/swap-update",
|
||||
hermes: "/api/maintenance/hermes-update",
|
||||
}
|
||||
|
||||
export const useUpdateStarten = () =>
|
||||
useAktion((art: string) => post<AktionsErgebnis>(UPDATE_PFADE[art]), {
|
||||
erfolg: (art) => (art === "pruefen" ? "Suche nach Neuem läuft." : "Update läuft. Den Fortschritt siehst du unten."),
|
||||
neuLaden: ["jobs", "start"],
|
||||
})
|
||||
|
||||
export const useJetztSichern = () =>
|
||||
useAktion(() => post<AktionsErgebnis>("/api/system/backup"), {
|
||||
erfolg: () => "Sicherung erstellt.",
|
||||
neuLaden: ["sicherungen", "start"],
|
||||
})
|
||||
|
||||
export const useZurueckspielen = () =>
|
||||
useAktion((datei: string) => post<AktionsErgebnis>("/api/zeitmaschine/restore", { file: datei }), {
|
||||
erfolg: () => "Zurückspielen läuft. Die Dienste starten gleich neu.",
|
||||
neuLaden: ["sicherungen"],
|
||||
})
|
||||
|
||||
export const useModellLaden = () =>
|
||||
useAktion((name: string) => post<AktionsErgebnis>(`/api/models/${encodeURIComponent(name)}/load`), {
|
||||
erfolg: (name) => `${name} wird geladen.`,
|
||||
neuLaden: ["models", "start"],
|
||||
})
|
||||
|
||||
export const useRadarAktion = () =>
|
||||
useAktion(
|
||||
({ id, aktion }: { id: string; aktion: "uebernehmen" | "verwerfen"; name: string }) =>
|
||||
post<AktionsErgebnis>(`/api/radar/${encodeURIComponent(id)}/${aktion}`),
|
||||
{
|
||||
erfolg: (v) => (v.aktion === "uebernehmen" ? `${v.name} übernommen.` : `${v.name} verworfen.`),
|
||||
neuLaden: ["radar", "models", "start"],
|
||||
},
|
||||
)
|
||||
|
||||
export const useRadarSuche = () =>
|
||||
useAktion(() => post<AktionsErgebnis>("/api/radar/suche"), {
|
||||
erfolg: () => "Radar sucht nach neuen Modellen.",
|
||||
neuLaden: ["radar"],
|
||||
})
|
||||
Reference in New Issue
Block a user