boxwart: Update-Verlauf zeigt das echte Ergebnis je Baustein, festgehaltene Bausteine sichtbar
Ampel / ampel (push) Failing after 22s
Ampel / ampel (push) Failing after 22s
Der Verlauf las bisher nur den Hermes-Cron-Status ("Skript lief") und zeigte die
Laeufe vom 06. und 13.09. als "durchgelaufen", obwohl dort zurueckgerollt und
festgehalten wurde; ein abgebrochener Lauf stand als "laeuft" da. Jetzt kommt der
Verlauf aus dem Meldeprotokoll (~/mc2-notify.log), also aus dem, was autoupdate.sh
selbst je Baustein meldet.
Festgehaltene Bausteine (Pin-Register) werden zum Waechter-Hinweis mit Knopf
"Freigeben", auf der Updates-Seite markiert und in der Updates-Lampe gezaehlt.
Hermes zeigt seine laufende Version und die Zahl der neuen Aenderungen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f3af2adec3
commit
790de19f1a
@@ -1,14 +1,15 @@
|
||||
import { useState } from "react"
|
||||
import { cn } from "cn"
|
||||
import { Bestaetigen } from "@/components/cockpit/Bestaetigen"
|
||||
import { Chip, Zustandsfeld } from "@/components/cockpit/Chip"
|
||||
import { Chip, type ChipArt, Zustandsfeld } from "@/components/cockpit/Chip"
|
||||
import { Panel } from "@/components/cockpit/Panel"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
useJetztSichern, useJobs, useSicherungen, useStart, useUpdateDetails, useUpdateStarten, useUpdateVerlauf,
|
||||
useZurueckspielen,
|
||||
type UpdateLauf, useFreigeben, useJetztSichern, useJobs, useSicherungen, useStart, useUpdateDetails,
|
||||
useUpdateStarten, useUpdateVerlauf, useZurueckspielen,
|
||||
} from "@/lib/abfragen"
|
||||
import { versionen } from "@/lib/anzeige"
|
||||
import type { Job, Sicherung } from "@/lib/typen"
|
||||
import type { Festgehalten, Job, Sicherung } from "@/lib/typen"
|
||||
import { flugplanZeit } from "@/lib/zeit"
|
||||
|
||||
const BAUSTEINE = [
|
||||
@@ -18,11 +19,13 @@ const BAUSTEINE = [
|
||||
{ id: "hermes", name: "Hermes" },
|
||||
] as const
|
||||
|
||||
function BausteinZeile({ id, name, hatUpdate, onStart, laeuftGerade }: {
|
||||
function BausteinZeile({ id, name, hatUpdate, fest, onStart, onFreigeben, laeuftGerade }: {
|
||||
id: string
|
||||
name: string
|
||||
hatUpdate: boolean
|
||||
fest: Festgehalten | undefined
|
||||
onStart: () => void
|
||||
onFreigeben: () => void
|
||||
laeuftGerade: boolean
|
||||
}) {
|
||||
const { data, isPending } = useUpdateDetails(id)
|
||||
@@ -37,19 +40,63 @@ function BausteinZeile({ id, name, hatUpdate, onStart, laeuftGerade }: {
|
||||
<td className="ziffern py-3.5 pr-4 text-sm text-text-2">{v.laeuft}</td>
|
||||
<td className="ziffern py-3.5 pr-4 text-sm">{neu ? <span className="text-cyan">{neu}</span> : <span className="text-text-3">–</span>}</td>
|
||||
<td className="py-3.5 pr-4 text-sm leading-relaxed text-text-2">
|
||||
{fest && (
|
||||
<span className="mb-1.5 block text-bernstein">
|
||||
Festgehalten seit {fest.seit} auf {fest.version}: {fest.grund}. Der Sonntags-Lauf überspringt {name}.
|
||||
</span>
|
||||
)}
|
||||
<span className="line-clamp-3 whitespace-pre-line">{was}</span>
|
||||
</td>
|
||||
<td className="py-2.5 text-right">
|
||||
{hatUpdate && (
|
||||
<Button variant="outline" size="sm" onClick={onStart} disabled={laeuftGerade}>
|
||||
Aktualisieren
|
||||
</Button>
|
||||
)}
|
||||
<span className="inline-flex flex-col items-end gap-2">
|
||||
{fest && (
|
||||
<Button variant="outline" size="sm" onClick={onFreigeben}>
|
||||
Freigeben
|
||||
</Button>
|
||||
)}
|
||||
{hatUpdate && (
|
||||
<Button variant="outline" size="sm" onClick={onStart} disabled={laeuftGerade}>
|
||||
Aktualisieren
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
const LAUF_ART: Record<UpdateLauf["stufe"], ChipArt> = {
|
||||
rot: "rot", gelb: "bernstein", gruen: "gruen", info: "cyan", grau: "grau",
|
||||
}
|
||||
const PUNKT: Record<UpdateLauf["stufe"], string> = {
|
||||
rot: "bg-rot", gelb: "bg-bernstein", gruen: "bg-gruen", info: "bg-cyan", grau: "bg-text-3",
|
||||
}
|
||||
|
||||
/** Ein Update-Lauf mit dem Ergebnis je Baustein — so, wie autoupdate.sh es gemeldet hat. */
|
||||
function LaufEintrag({ lauf }: { lauf: UpdateLauf }) {
|
||||
return (
|
||||
<li className="flex flex-col gap-2 border-t border-linie py-3">
|
||||
<div className="grid grid-cols-[112px_minmax(0,1fr)_auto] items-center gap-3">
|
||||
<span className="ziffern text-sm text-text-3">{flugplanZeit(lauf.start)}</span>
|
||||
<span className="min-w-0 text-[15px]">{lauf.anlass}</span>
|
||||
<Chip art={LAUF_ART[lauf.stufe]}>{lauf.titel}</Chip>
|
||||
</div>
|
||||
{lauf.stufe !== "grau" && lauf.zeilen.length > 0 && (
|
||||
<ul className="m-0 flex list-none flex-col gap-1 p-0 sm:pl-[124px]">
|
||||
{lauf.zeilen.map((z) => (
|
||||
<li key={`${z.baustein}-${z.text}`} className="flex items-baseline gap-2 text-sm leading-snug text-text-2">
|
||||
<span aria-hidden className={cn("size-1.5 shrink-0 -translate-y-px rounded-full", PUNKT[z.stufe])} />
|
||||
<span className="min-w-0">
|
||||
<span className="text-foreground">{z.baustein}</span> · {z.text}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function JobKasten({ job }: { job: Job & { group?: string; log?: string[] } }) {
|
||||
const art = job.state === "done" ? "gruen" : job.state === "failed" ? "rot" : "cyan"
|
||||
const text = job.state === "done" ? "Fertig" : job.state === "failed" ? "Fehler" : job.state === "queued" ? "Wartet" : "Läuft"
|
||||
@@ -78,11 +125,13 @@ export function UpdatesSeite() {
|
||||
const verlauf = useUpdateVerlauf()
|
||||
const sicherungen = useSicherungen()
|
||||
const updateStarten = useUpdateStarten()
|
||||
const freigeben = useFreigeben()
|
||||
const sichern = useJetztSichern()
|
||||
const zurueck = useZurueckspielen()
|
||||
const [frage, setFrage] = useState<null | { art: "alle" } | { art: "zurueck"; datei: string; zeit: string }>(null)
|
||||
|
||||
const mitUpdate = new Set(start.data?.updates.bausteine.map((b) => b.id) ?? [])
|
||||
const festgehalten = new Map((start.data?.updates.festgehalten ?? []).map((f) => [f.baustein, f]))
|
||||
const wartungsJobs = (jobs.data?.jobs ?? []).filter((j) => (j as { group?: string }).group === "maintenance")
|
||||
const laeuft = wartungsJobs.some((j) => j.state === "running" || j.state === "queued")
|
||||
|
||||
@@ -125,8 +174,10 @@ export function UpdatesSeite() {
|
||||
id={b.id}
|
||||
name={b.name}
|
||||
hatUpdate={mitUpdate.has(b.id)}
|
||||
fest={festgehalten.get(b.id)}
|
||||
laeuftGerade={laeuft}
|
||||
onStart={() => updateStarten.mutate(b.id)}
|
||||
onFreigeben={() => freigeben.mutate(b.id)}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -145,20 +196,7 @@ export function UpdatesSeite() {
|
||||
<Panel titel="Verlauf">
|
||||
{verlauf.data?.laeufe.length ? (
|
||||
<ol className="m-0 flex list-none flex-col p-0">
|
||||
{verlauf.data.laeufe.map((l) => {
|
||||
const ok = l.status === "completed"
|
||||
const art = ok ? "gruen" : l.status === "failed" ? "rot" : "cyan"
|
||||
return (
|
||||
<li key={`${l.start}-${l.job}`} className="grid grid-cols-[112px_minmax(0,1fr)_auto] items-start gap-3 border-t border-linie py-3">
|
||||
<span className="ziffern text-sm text-text-3">{l.start ? flugplanZeit(l.start) : "–"}</span>
|
||||
<span className="min-w-0 text-[15px]">
|
||||
{l.job}
|
||||
{l.fehler && <span className="block text-sm text-rot-text">{l.fehler}</span>}
|
||||
</span>
|
||||
<Chip art={art}>{ok ? "Durchgelaufen" : l.status === "failed" ? "Fehlgeschlagen" : "Läuft"}</Chip>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{verlauf.data.laeufe.map((l) => <LaufEintrag key={l.start} lauf={l} />)}
|
||||
</ol>
|
||||
) : (
|
||||
<p className="text-[15px] text-text-2">
|
||||
|
||||
Reference in New Issue
Block a user