oberflaeche v4: Uebersicht als Startseite, KI-Box und Homelab mit Monitoring, Homelab-Updates je Geraet mit Alle aktualisieren, Protokoll, Einstellungsseite, neues Tab-Symbol

- Startseite "Alles auf einen Blick": Gesamtlage, je Bereich Messwerte mit Verlauf, wer welche Updates hat
  (Kurzform, Klick fuehrt in den Bereich), Hinweise, als Naechstes und zuletzt eingespielt
- Menue: Uebersicht, KI-Box (Cockpit, Updates, Modelle, Monitoring), Homelab (Cockpit, Updates, Monitoring,
  Protokoll), Einstellungen; Bereichsfarben Indigo und Violett; alte Adressen /updates und /modelle leiten weiter
- Homelab-Updates je Geraet: App-Version laeuft -> neu (mit Grund, wenn sie wartet), Pakete, Rueckweg, Knoepfe;
  Alle aktualisieren mit Plan in der Rueckfrage und Fortschritt; Cockpit zeigt die Gesundheit der Geraete
- Einstellungen als Seite: KI-Box, Homelab (Arcane-Schluessel, Docker echt/Probelauf, Ausfuehrer), Meldungen,
  Software-Stand
- Diagramme: Verlauf mit Fadenkreuz, Legende und Tabellenansicht, Kennzahlen mit Linie; Farben geprueft
- Tab-Symbol: favicon.ico/.svg, apple-touch-icon, Manifest-Icons

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-24 22:18:56 +02:00
co-authored by Claude Opus 5.5
parent bf1153cb66
commit 1104200a30
37 changed files with 2244 additions and 493 deletions
+41 -17
View File
@@ -1,18 +1,22 @@
import { useNavigate } from "@tanstack/react-router"
import { Link, useNavigate } from "@tanstack/react-router"
import { ArrowRight } from "lucide-react"
import { Chip, type ChipArt, Zustandsfeld } from "@/components/cockpit/Chip"
import { Panel } from "@/components/cockpit/Panel"
import { GeraeteListe } from "@/components/homelab/GeraeteListe"
import { Gesundheit } from "@/components/homelab/Gesundheit"
import { Lagebild } from "@/components/homelab/Lagebild"
import { UpdateListe } from "@/components/homelab/UpdateListe"
import { AlleKnopf, SammellaufFortschritt } from "@/components/homelab/Sammellauf"
import { UpdateTabelle } from "@/components/homelab/UpdateTabelle"
import {
useHomelab, useHomelabHinweise, useHomelabLaeufe, useInstanz, usePartner, useZielAktion,
useHomelab, useHomelabHinweise, useHomelabLaeufe, useHomelabMesswerte, useInstanz, usePartner, useSammellauf,
useZielAktion,
} from "@/lib/abfragen"
import { lage, lampeFuer, offeneUpdates } from "@/lib/homelab"
import type { Hinweis, HomelabLauf, HomelabStand, PartnerStatus } from "@/lib/typen"
import { flugplanZeit } from "@/lib/zeit"
// Der Bereich Homelab hat zwei Seiten wie die KI-Box: das Cockpit (Lage, Hinweise, Geräte) und die
// Updates (was ansteht, was gelaufen ist). Aus der KI-Box steht hier nichts — die hat ihren eigenen Bereich.
// Der Bereich Homelab: das Cockpit zeigt, wie es den Geräten geht (Lage, Hinweise, Netz, CPU, Speicher,
// Platte); die Updates-Seite zeigt je Gerät die Versionen, was ansteht und was gelaufen ist. Monitoring und
// Protokoll haben eigene Seiten. Aus der KI-Box steht hier nichts — die hat ihren eigenen Bereich.
/** Was der Homelab-Teil übernimmt (Bestandsaufnahme vom 24.09.2026). */
const GERAETE = ["Proxmox-Host", "AdGuard", "NPMplus", "NetBird", "PVE Scripts Local", "Gitea", "PBS", "Arcane mit Docker"]
@@ -178,10 +182,11 @@ function useHomelabBereich() {
}
}
/** Homelab · Cockpit: die Lage auf einen Blick, Hinweise und alle Geräte. */
/** Homelab · Cockpit: die Lage auf einen Blick, Hinweise und wie es jedem Gerät geht. */
export function HomelabSeite() {
const navigate = useNavigate()
const { ersatz, stand, laufend, hinweise, aktion } = useHomelabBereich()
const { ersatz, stand, laufend, hinweise } = useHomelabBereich()
const mess = useHomelabMesswerte("1h", !!stand)
if (ersatz || !stand) return ersatz
const berichtZeit = stand.bericht ? zeitAus(stand.bericht.empfangen) : null
@@ -201,31 +206,50 @@ export function HomelabSeite() {
<Panel
titel="Geräte"
id="geraete"
rechts={berichtZeit ? <span className="ziffern text-sm text-text-3">Stand {berichtZeit}</span> : undefined}
rechts={
<span className="flex items-center gap-3">
{berichtZeit && <span className="ziffern text-sm text-text-3">Stand {berichtZeit}</span>}
<Link to="/homelab/monitoring" className="inline-flex items-center gap-1 text-sm text-lab-text hover:underline">
Monitoring <ArrowRight className="size-3.5" aria-hidden />
</Link>
</span>
}
>
<GeraeteListe ziele={stand.ziele} laufend={laufend} gesperrt={aktion.isPending} onAktion={(a) => aktion.mutate(a)} />
<Gesundheit ziele={stand.ziele} messwerte={mess.data?.geraete} />
{mess.isError && (
<p className="text-xs text-text-3">CPU und Speicher erscheinen, sobald der Ausführer Messwerte schickt.</p>
)}
</Panel>
</>
)
}
/** Homelab · Updates: was ansteht — ein Knopf je Update — und was gelaufen ist. */
/** Homelab · Updates: je Gerät die Versionen, „Alle aktualisieren“ und was gelaufen ist. */
export function HomelabUpdatesSeite() {
const { ersatz, stand, laufend, laeufe, aktion } = useHomelabBereich()
const sammel = useSammellauf(!!stand)
if (ersatz || !stand) return ersatz
const lauf = sammel.data?.sammellauf ?? null
const sammelLaeuft = lauf?.status === "laeuft"
const knopf = offeneUpdates(stand.ziele).filter((u) => u.baustein.aktion).length
return (
<>
<Panel titel="Updates">
<Panel
titel="Updates"
rechts={<AlleKnopf anzahl={knopf} gesperrt={aktion.isPending || sammelLaeuft || !stand.ausfuehrer.verbunden} />}
>
<p className="-mt-2 text-[15px] text-text-2">
Du startest jedes Update selbst. Vorher wird gesichert, danach geprüft; ist die Prüfung rot, geht es von
selbst zurück.
Je Gerät, was läuft und was neu ist. Du startest jedes Update selbst — einzeln oder alle nacheinander.
Vorher wird gesichert, danach geprüft; ist die Prüfung rot, geht es von selbst zurück.
</p>
<AusfuehrerHinweis stand={stand} />
<UpdateListe
updates={offeneUpdates(stand.ziele)}
{/* Ein fertiger Lauf bleibt sechs Stunden stehen — gemessen am letzten Bericht, nicht an der Uhr beim Zeichnen. */}
{lauf && (sammelLaeuft || (lauf.ende && (stand.bericht?.empfangen ?? 0) - lauf.ende < 6 * 3600)) && <SammellaufFortschritt lauf={lauf} />}
<UpdateTabelle
ziele={stand.ziele}
laufend={laufend}
gesperrt={aktion.isPending || !stand.ausfuehrer.verbunden}
gesperrt={aktion.isPending || sammelLaeuft || !stand.ausfuehrer.verbunden}
onAktion={(a) => aktion.mutate(a)}
/>
</Panel>