modelle: Installieren mit Rueckfrage, Doppelklick-Sperre und Rollen-Hinweis, Auftraege mit Fortschritt
- "Installieren" fragt vorher nach (Modell, Quantisierung, Groesse falls bekannt) und sperrt, solange der Start unterwegs ist - ein Doppelklick startet keinen zweiten Download. - Mit Rolle "Coder" steht deutlich da, welches Modell ersetzt wird (unter der Auswahl und in der Rueckfrage), dass es auf der Platte bleibt und der Bild-Zwilling vorerst beim bisherigen Modell liest. - Die Modelle-Seite zeigt laufende Downloads als Auftraege mit Fortschritt und Abbrechen. - Suche und Quantisierungen haben Lade-, Fehler- und Leerzustaende; Nutzung, Radar und Aufraeumen melden Fehler statt still zu verschwinden. Ein 404 heisst "Radar nicht eingerichtet", alles andere ist ein Fehler. Scheitert nur ein spaeteres Nachladen, bleibt der letzte Stand der Modell-Liste stehen. - Anzeige-Logik der Wartungsseiten liegt jetzt in lib/wartung.ts statt in anzeige.ts, die im Start-Buendel steckt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5ddc952ecd
commit
3c1d8bf88f
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react"
|
||||
import { Search } from "lucide-react"
|
||||
import { Auftraege } from "@/components/cockpit/Auftraege"
|
||||
import { Bestaetigen } from "@/components/cockpit/Bestaetigen"
|
||||
import { Chip, type ChipArt, Zustandsfeld } from "@/components/cockpit/Chip"
|
||||
import { Panel } from "@/components/cockpit/Panel"
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
useRadarSuche, useStart,
|
||||
} from "@/lib/abfragen"
|
||||
import { messwert, RADAR_STATUS_TEXT, rolleVon } from "@/lib/anzeige"
|
||||
import { ApiFehler } from "@/lib/api"
|
||||
import { useLetzteMetrik } from "@/lib/strom"
|
||||
import type { AufraeumKandidat, Modell, Nutzung, RadarKandidat, RadarTest } from "@/lib/typen"
|
||||
import { flugplanZeit, gb, gbText, zahl } from "@/lib/zeit"
|
||||
@@ -98,8 +100,18 @@ function Rolle({ titel, modell, zwilling, laeuft, nutzer }: {
|
||||
)
|
||||
}
|
||||
|
||||
function NutzungsPanel({ nutzung }: { nutzung: Nutzung | undefined }) {
|
||||
if (!nutzung) return null
|
||||
function NutzungsPanel({ nutzung, laedt, fehler }: { nutzung: Nutzung | undefined; laedt: boolean; fehler: string | null }) {
|
||||
if (!nutzung) {
|
||||
return (
|
||||
<Panel titel="Wer nutzt die Modelle">
|
||||
{laedt ? (
|
||||
<p className="text-[15px] text-text-2">Wird gelesen …</p>
|
||||
) : (
|
||||
<p role="alert" className="text-[15px] text-rot-text">Die Nutzung lässt sich gerade nicht lesen{fehler ? `: ${fehler}` : "."}</p>
|
||||
)}
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
const max = Math.max(1, ...nutzung.absender.map((a) => a.anfragen))
|
||||
const stunden = nutzung.letzte_24h.map((s) => Object.values(s.je_absender).reduce((a, b) => a + b, 0))
|
||||
const maxStunde = Math.max(1, ...stunden)
|
||||
@@ -226,10 +238,16 @@ function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | unde
|
||||
function RadarPanel({ hirn, coder }: { hirn: string | undefined; coder: string | undefined }) {
|
||||
const radar = useRadar()
|
||||
const suche = useRadarSuche()
|
||||
if (radar.isError) {
|
||||
if (radar.isError && !radar.data) {
|
||||
// 404 heißt: Diese Box hat kein Radar. Alles andere ist ein Fehler, kein fehlendes Radar.
|
||||
const fehlt = radar.error instanceof ApiFehler && radar.error.status === 404
|
||||
return (
|
||||
<Panel titel="Modell-Radar" id="radar">
|
||||
<p className="text-[15px] text-text-2">Das Radar ist auf dieser Box noch nicht eingerichtet.</p>
|
||||
<Panel titel="Modell-Radar" id="radar" className="scroll-mt-4">
|
||||
{fehlt ? (
|
||||
<p className="text-[15px] text-text-2">Das Radar ist auf dieser Box noch nicht eingerichtet.</p>
|
||||
) : (
|
||||
<p role="alert" className="text-[15px] text-rot-text">Das Radar antwortet gerade nicht: {radar.error.message}</p>
|
||||
)}
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
@@ -238,7 +256,12 @@ function RadarPanel({ hirn, coder }: { hirn: string | undefined; coder: string |
|
||||
<Panel
|
||||
titel="Modell-Radar"
|
||||
id="radar"
|
||||
rechts={<Button variant="info" size="sm" onClick={() => suche.mutate(undefined)} disabled={suche.isPending}>Jetzt suchen</Button>}
|
||||
className="scroll-mt-4"
|
||||
rechts={
|
||||
<Button variant="info" size="sm" onClick={() => suche.mutate(undefined)} disabled={suche.isPending}>
|
||||
{suche.isPending ? "Sucht …" : "Jetzt suchen"}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<p className="-mt-2 text-sm text-text-2">
|
||||
Testet nachts zwischen 00:30 und 02:30 selbst, höchstens einen Kandidaten pro Woche.
|
||||
@@ -281,7 +304,13 @@ function AufraeumenPanel() {
|
||||
const [frage, setFrage] = useState<AufraeumKandidat | null>(null)
|
||||
const kandidaten = liste.data?.kandidaten ?? []
|
||||
const summe = kandidaten.reduce((s, k) => s + k.groesse_gb, 0)
|
||||
if (liste.isError) return null
|
||||
if (liste.isError && !liste.data) {
|
||||
return (
|
||||
<Panel titel="Aufräumen">
|
||||
<p role="alert" className="text-[15px] text-rot-text">Was sich aufräumen ließe, lässt sich gerade nicht lesen: {liste.error.message}</p>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Panel
|
||||
titel="Aufräumen"
|
||||
@@ -339,7 +368,8 @@ export function ModelleSeite() {
|
||||
const [suche, setSuche] = useState(false)
|
||||
|
||||
if (modelle.isPending) return <Zustandsfeld text="Modelle werden gelesen …" />
|
||||
if (modelle.error || !modelle.data) return <Zustandsfeld fehler text={`Die Modell-Liste fehlt: ${modelle.error?.message ?? "keine Daten"}`} />
|
||||
// Scheitert nur ein späteres Nachladen, bleibt der letzte Stand stehen.
|
||||
if (!modelle.data) return <Zustandsfeld fehler text={`Die Modell-Liste fehlt: ${modelle.error?.message ?? "keine Daten"}`} />
|
||||
|
||||
const liste = modelle.data.models
|
||||
const laufend = modelle.data.running
|
||||
@@ -356,19 +386,21 @@ export function ModelleSeite() {
|
||||
return (
|
||||
<>
|
||||
<Speicher modelle={liste} laufend={laufend} />
|
||||
<Auftraege alsPanel />
|
||||
<section aria-label="Rollen" className="grid gap-5 md:grid-cols-3 md:gap-6">
|
||||
<Rolle titel="Hirn" modell={hirn} zwilling={hirnZwilling} laeuft={!!hirn && laufend.includes(hirn.name)} nutzer="Lucy, NerdQuiz und OpenChamber für Kleinkram" />
|
||||
<Rolle titel="Coder" modell={coder} zwilling={coderZwilling} laeuft={!!coder && laufend.includes(coder.name)} nutzer="OpenChamber zum Planen und Bauen und Lucys Delegation" />
|
||||
<Rolle titel="Dritte Rolle" laeuft={false} nutzer="" />
|
||||
</section>
|
||||
<div className="grid gap-5 md:gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.3fr)]">
|
||||
<NutzungsPanel nutzung={nutzung.data} />
|
||||
<NutzungsPanel nutzung={nutzung.data} laedt={nutzung.isPending} fehler={nutzung.error?.message ?? null} />
|
||||
<RadarPanel hirn={hirn?.name} coder={coder?.name} />
|
||||
</div>
|
||||
<Panel
|
||||
titel="Weitere Einträge"
|
||||
rechts={<Button variant="outline" size="sm" onClick={() => setSuche(true)}><Search aria-hidden /> Modelle selbst suchen</Button>}
|
||||
>
|
||||
{weitere.length === 0 && <p className="text-[15px] text-text-2">Keine weiteren Einträge.</p>}
|
||||
<ul className="m-0 flex list-none flex-col p-0">
|
||||
{weitere.map((m) => (
|
||||
<li key={m.name} className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 border-t border-linie py-2.5 sm:grid-cols-[minmax(0,1fr)_120px_160px]">
|
||||
@@ -385,7 +417,11 @@ export function ModelleSeite() {
|
||||
</ul>
|
||||
</Panel>
|
||||
<AufraeumenPanel />
|
||||
<ModellSuche offen={suche} onSchliessen={() => setSuche(false)} />
|
||||
<ModellSuche
|
||||
offen={suche}
|
||||
onSchliessen={() => setSuche(false)}
|
||||
rollenJetzt={{ coder: coder ? { name: coder.name, zwilling: !!coderZwilling } : undefined }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user