Welle 2 · Zeitleiste je Gerät: Protokoll nach Gerät, Updates von → nach, Hinweise mit ihrem Gerät
/homelab/protokoll#ct-104 zeigt die Zeitleiste eines Geräts (Auswahl oben, Link „Zeitleiste“ je Gerät in der Geräte-Tabelle); die Punkte eines Tages hängen an einer Linie. Das Protokoll filtert dafür im Backend (?ziel=…), Update-Läufe heißen „Update eingespielt · Gitea 1.27.2 → 1.27.3“, und Hinweise finden ihr Gerät über ihre Quelle (der Wächter schreibt sie seit heute in seinen Verlauf) — auch Zertifikate, Docker und Sicherungen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1c66daad0b
commit
83bb227969
@@ -77,7 +77,11 @@ export function Gesundheit({ ziele, messwerte }: { ziele: ZielStand[]; messwerte
|
||||
<Link to="/homelab/monitoring" hash={z.id} className="text-[15px] font-semibold break-words hover:text-lab-text">
|
||||
{z.name}
|
||||
</Link>
|
||||
{artText(z) !== z.name && <span className="text-[13px] text-text-3">{artText(z)}</span>}
|
||||
<span className="flex flex-wrap gap-x-2 text-[13px] text-text-3">
|
||||
{artText(z) !== z.name && <span>{artText(z)}</span>}
|
||||
{/* Zeitleiste je Gerät (seit 25.09.2026). */}
|
||||
<Link to="/homelab/protokoll" hash={z.id} className="text-lab-text hover:underline">Zeitleiste</Link>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span role="cell" className={cn("flex items-center gap-1.5 text-[13px]", z.erreichbar === false ? "text-rot-text" : "text-text-3")}>
|
||||
|
||||
@@ -479,10 +479,13 @@ export const useSammellaufQuittieren = () =>
|
||||
neuLaden: ["homelab-sammellauf"],
|
||||
})
|
||||
|
||||
export const useHomelabProtokoll = (aktiv: boolean, berichte: boolean) =>
|
||||
/** Das Protokoll des Homelab-Teils; mit ziel nur ein Gerät (Zeitleiste je Gerät, seit 25.09.2026). */
|
||||
export const useHomelabProtokoll = (aktiv: boolean, berichte: boolean, ziel: string | null = null) =>
|
||||
useQuery({
|
||||
queryKey: ["homelab-protokoll", berichte],
|
||||
queryFn: () => api<{ eintraege: ProtokollEintrag[] }>(`/api/homelab/protokoll?grenze=300&berichte=${berichte ? 1 : 0}`),
|
||||
queryKey: ["homelab-protokoll", berichte, ziel],
|
||||
queryFn: () => api<{ eintraege: ProtokollEintrag[] }>(
|
||||
`/api/homelab/protokoll?grenze=300&berichte=${berichte ? 1 : 0}${ziel ? `&ziel=${encodeURIComponent(ziel)}` : ""}`,
|
||||
),
|
||||
enabled: aktiv,
|
||||
refetchInterval: takt(30_000),
|
||||
retry: false,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react"
|
||||
import { useNavigate, useRouterState } from "@tanstack/react-router"
|
||||
import { Bell, ChevronRight, Cog, History, RefreshCw, Search, TriangleAlert, type LucideIcon } from "lucide-react"
|
||||
import { cn } from "cn"
|
||||
import { Zustandsfeld } from "@/components/cockpit/Chip"
|
||||
@@ -7,7 +8,9 @@ import { useHomelab, useHomelabProtokoll, useInstanz, usePartner } from "@/lib/a
|
||||
import type { ProtokollEintrag } from "@/lib/typen"
|
||||
|
||||
// Was der Homelab-Teil getan hat: Aufträge an den Ausführer (Snapshot, Update, Suchen …), Update-Läufe,
|
||||
// Hinweise des Wächters, gesendete Meldungen und die nächtliche Pflege — neueste zuerst, nach Tagen.
|
||||
// Hinweise des Wächters, gesendete Meldungen und die nächtliche Pflege — neueste zuerst, nach Tagen. Seit 25.09.2026
|
||||
// (Ausbauplan Welle 2, Punkt 12) auch je Gerät: /homelab/protokoll#ct-104 ist die Zeitleiste von Gitea — Updates
|
||||
// von → nach, Snapshots, Rückwege, Neustarts und Hinweise auf einer Linie.
|
||||
|
||||
type Art = ProtokollEintrag["art"]
|
||||
|
||||
@@ -55,8 +58,10 @@ function Eintrag({ e, geraet }: { e: ProtokollEintrag; geraet: string | null })
|
||||
return (
|
||||
<li className="grid grid-cols-[64px_18px_minmax(0,1fr)] gap-x-3 border-t border-white/[0.06] py-2.5 first:border-t-0">
|
||||
<span className="ziffern pt-0.5 text-xs text-text-3">{uhr.format(new Date(e.zeit * 1000))}</span>
|
||||
<span className="flex flex-col items-center pt-1">
|
||||
<span aria-hidden className={cn("size-2.5 rounded-full", s.punkt)} />
|
||||
{/* Die Linie verbindet die Punkte eines Tages zur Zeitleiste. */}
|
||||
<span className="relative flex flex-col items-center pt-1">
|
||||
<span aria-hidden className="absolute -top-2.5 -bottom-2.5 w-px bg-white/10" />
|
||||
<span aria-hidden className={cn("relative size-2.5 rounded-full ring-4 ring-[var(--panel)]", s.punkt)} />
|
||||
</span>
|
||||
<div className="flex min-w-0 flex-col gap-0.5">
|
||||
<span className="flex flex-wrap items-center gap-x-2 text-[15px]">
|
||||
@@ -91,8 +96,12 @@ export function HomelabProtokollSeite() {
|
||||
const aktiv = instanz.data?.rolle === "homelab" || !!(partner.data?.eingerichtet && partner.data.erreichbar)
|
||||
const [art, setArt] = useState<Art | "alle">("alle")
|
||||
const [berichte, setBerichte] = useState(false)
|
||||
const protokoll = useHomelabProtokoll(aktiv, berichte)
|
||||
const namen = new Map((useHomelab(aktiv).data?.ziele ?? []).map((z) => [z.id, z.name]))
|
||||
const navigate = useNavigate()
|
||||
const hash = useRouterState({ select: (s) => s.location.hash })
|
||||
const ziele = useHomelab(aktiv).data?.ziele ?? []
|
||||
const namen = new Map(ziele.map((z) => [z.id, z.name]))
|
||||
const geraet = hash && /^(pve|(ct|vm)-\d+)$/.test(hash) ? hash : null
|
||||
const protokoll = useHomelabProtokoll(aktiv, berichte, geraet)
|
||||
|
||||
if (instanz.isPending || partner.isPending) return <Zustandsfeld text="Wird gelesen …" />
|
||||
if (!aktiv) return <Zustandsfeld fehler text="Der Homelab-Teil antwortet gerade nicht." />
|
||||
@@ -108,11 +117,21 @@ export function HomelabProtokollSeite() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel titel="Protokoll">
|
||||
<Panel titel={geraet ? `Zeitleiste · ${namen.get(geraet) ?? geraet}` : "Protokoll"}>
|
||||
<p className="-mt-2 text-[15px] text-text-2">
|
||||
Was der Homelab-Teil getan hat: Aufträge an den Ausführer auf dem Proxmox-Host, Update-Läufe, Snapshots,
|
||||
Hinweise, gesendete Meldungen und die nächtliche Paketsuche.
|
||||
{geraet
|
||||
? "Alles, was an diesem Gerät geschah: Updates von → nach, Snapshots, Rückwege, Neustarts und Hinweise."
|
||||
: "Was der Homelab-Teil getan hat: Aufträge an den Ausführer auf dem Proxmox-Host, Update-Läufe, Snapshots, " +
|
||||
"Hinweise, gesendete Meldungen und die nächtliche Paketsuche."}
|
||||
</p>
|
||||
<label className="flex max-w-sm flex-col gap-1 text-sm text-text-2">
|
||||
Gerät
|
||||
<select value={geraet ?? ""} onChange={(ev) => void navigate({ to: "/homelab/protokoll", hash: ev.target.value || undefined })}
|
||||
className="h-10 rounded-xl border border-white/12 bg-white/[0.04] px-3 text-[15px] text-foreground outline-none focus:border-lab/60">
|
||||
<option value="">Alle Geräte</option>
|
||||
{ziele.map((z) => <option key={z.id} value={z.id}>{z.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<div className="flex flex-wrap items-center gap-2" role="group" aria-label="Filter">
|
||||
{ARTEN.map((a) => (
|
||||
<button key={a.id} type="button" onClick={() => setArt(a.id)} aria-pressed={art === a.id}
|
||||
@@ -142,7 +161,7 @@ export function HomelabProtokollSeite() {
|
||||
<ol className="m-0 flex list-none flex-col p-0">
|
||||
{t.eintraege.map((e, i) => {
|
||||
// Das Gerät als Schild — außer, der Titel nennt es schon.
|
||||
const name = e.ziel ? namen.get(e.ziel) ?? e.ziel : null
|
||||
const name = e.ziel && !geraet ? namen.get(e.ziel) ?? e.ziel : null
|
||||
return <Eintrag key={`${e.zeit}-${e.titel}-${i}`} e={e} geraet={name && !e.titel.includes(name) ? name : null} />
|
||||
})}
|
||||
</ol>
|
||||
|
||||
Reference in New Issue
Block a user