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
+78
View File
@@ -0,0 +1,78 @@
// Reine Anzeige-Logik der Cockpit-Seiten — getrennt von den Komponenten, damit sie ohne
// DOM testbar bleibt und Vites Hot-Reload die Komponenten-Dateien sauber tauschen kann.
import type { UpdateDetails } from "./abfragen"
import type { Modell, Start } from "./typen"
// --- Rundinstrument -----------------------------------------------------------------
// 240°-Bogen wie ein Zeigerinstrument, Mittelpunkt (100|95) in einer 200×150-Zeichnung.
const MX = 100
const MY = 95
const START = 210 // Grad, links unten
const SPANNE = 240
function punkt(grad: number, r: number): [number, number] {
const rad = (grad * Math.PI) / 180
return [MX + r * Math.cos(rad), MY - r * Math.sin(rad)]
}
const r2 = (n: number) => Math.round(n * 100) / 100
/** SVG-Pfad eines Bogens von Anteil `von` bis `bis` (0…1) auf Radius `r`. */
export function bogenPfad(von: number, bis: number, r: number): string {
const a = Math.max(0, Math.min(1, von))
const b = Math.max(a, Math.min(1, bis))
const [x0, y0] = punkt(START - SPANNE * a, r)
const [x1, y1] = punkt(START - SPANNE * b, r)
const gross = (b - a) * SPANNE > 180 ? 1 : 0
return `M ${r2(x0)} ${r2(y0)} A ${r} ${r} 0 ${gross} 1 ${r2(x1)} ${r2(y1)}`
}
/** Farbe der Temperatur: grün bis 70 °C, dann Bernstein, ab 85 °C rot. */
export function temperaturFarbe(grad: number | null | undefined) {
if (grad == null) return "var(--text-3)"
if (grad >= 85) return "var(--rot)"
if (grad >= 70) return "var(--bernstein)"
return "var(--gruen)"
}
// --- Hauptwarnleuchte ---------------------------------------------------------------
/** Was die Hauptwarnleuchte sagt. Der schweigende Wächter geht allem anderen vor. */
export function hauptleuchte(z: Start["zustand"]) {
if (!z.waechter_wach) {
return { art: "stumm" as const, oben: "Wächter", mitte: "SCHWEIGT", unten: "Seit über 5 Minuten kein Prüflauf" }
}
if (z.update_laeuft) {
return { art: "info" as const, oben: "Wartung", mitte: "UPDATE LÄUFT", unten: "Hinweise ruhen bis zum Ende" }
}
if (z.anzahl === 0) {
return { art: "ok" as const, oben: "Alles", mitte: "IN ORDNUNG", unten: "Keine offenen Hinweise" }
}
const mitte = `${z.anzahl} HINWEIS${z.anzahl === 1 ? "" : "E"}`
return z.stufe === "rot"
? { art: "rot" as const, oben: "Störung", mitte, unten: "Drücken zum Ansehen" }
: { art: "gelb" as const, oben: "Achtung", mitte, unten: "Drücken zum Ansehen" }
}
// --- Updates ------------------------------------------------------------------------
/** „Läuft“ und „Neu“ aus den Update-Details lesbar machen. */
export function versionen(id: string, d: UpdateDetails | undefined): { laeuft: string; neu: string | null } {
if (!d) return { laeuft: "…", neu: null }
if (id === "os") return { laeuft: "Ubuntu", neu: d.count ? `${d.count} Pakete` : null }
const pre = id === "engine" ? "b" : "v"
const laeuft = d.installed_build != null ? `${pre}${d.installed_build}` : "–"
const neuer = d.latest_build != null && d.installed_build != null && d.latest_build > d.installed_build
return { laeuft, neu: neuer ? `${pre}${d.latest_build}` : null }
}
// --- Modelle ------------------------------------------------------------------------
/** Rolle eines Modells aus seinen llama-swap-Aliasen. */
export function rolleVon(m: Modell): "hirn" | "coder" | null {
const a = m.aliases.map((x) => x.toLowerCase())
if (a.includes("hermes") || a.includes("fast")) return "hirn"
if (a.includes("coder")) return "coder"
return null
}