homelab: eigener Live-Strom (keine Getrennt-Anzeige mehr), Hinweise des Homelab-Waechters auf der Seite, Verbindungsanzeige je Instanz

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-24 19:10:02 +02:00
co-authored by Claude Opus 5.5
parent 83c33584e4
commit 160381d8b3
17 changed files with 148 additions and 19 deletions
+4 -1
View File
@@ -2,6 +2,7 @@ import { lazy, Suspense, useEffect, useState } from "react"
import { Link, Outlet } from "@tanstack/react-router"
import { Cpu, Ellipsis, ExternalLink, House, RefreshCw, ScrollText, Server, Settings } from "lucide-react"
import { cn } from "cn"
import { useInstanz } from "@/lib/abfragen"
import { useStrom, useStromLage } from "@/lib/strom"
import { Meldungen } from "./Meldungen"
@@ -40,6 +41,8 @@ function Uhr() {
function Verbindung() {
const lage = useStromLage()
const live = lage === "live"
// Die Anzeige gilt der Instanz, die die Seite ausliefert: der KI-Box oder dem Homelab-Teil.
const wer = useInstanz().data?.rolle === "homelab" ? "Homelab" : "Box"
return (
<div
role="status"
@@ -52,7 +55,7 @@ function Verbindung() {
aria-hidden
className={cn("size-2 shrink-0 rounded-full", live ? "bg-gruen shadow-[0_0_8px_rgba(61,220,138,0.7)]" : "bg-bernstein")}
/>
<span className="whitespace-nowrap">{live ? "Box online" : lage === "verbinde" ? "Verbinde" : "Getrennt"}</span>
<span className="whitespace-nowrap">{live ? `${wer} online` : lage === "verbinde" ? "Verbinde" : "Getrennt"}</span>
</div>
)
}
+11 -1
View File
@@ -9,7 +9,8 @@ import { fehlerText, laeuftNoch } from "./anzeige"
import { melden } from "./meldungen"
import { stromVerbunden } from "./strom"
import type {
AktionsErgebnis, AufraeumKandidat, HomelabLauf, HomelabStand, Instanz, Job, Modell, Nutzung, PartnerStatus, Radar,
AktionsErgebnis, AufraeumKandidat, Hinweis, HomelabLauf, HomelabStand, Instanz, Job, Modell, Nutzung, PartnerStatus,
Radar,
Sicherung, Start, UpdateDetails, ZielAktion, ZielStand,
} from "./typen"
import { gbText } from "./zeit"
@@ -291,3 +292,12 @@ export const useZielAktion = () =>
erfolg: (a) => `${a.label}: gestartet. Das Ergebnis kommt als Meldung.`,
neuLaden: ["homelab", "homelab-laeufe", "ziele-box", "jobs", "start"],
})
/** Was der Wächter des Homelab-Teils gerade sieht (Ausführer schweigt, ein Gast antwortet nicht …). */
export const useHomelabHinweise = (aktiv: boolean) =>
useQuery({
queryKey: ["homelab-hinweise"],
queryFn: () => api<{ hinweise: Hinweis[]; aktiv: boolean }>("/api/homelab/hinweise"),
enabled: aktiv,
refetchInterval: takt(60_000),
})
+26 -2
View File
@@ -1,8 +1,10 @@
import { Chip, type ChipArt, Zustandsfeld } from "@/components/cockpit/Chip"
import { Panel } from "@/components/cockpit/Panel"
import { ZielKarte } from "@/components/cockpit/ZielKarte"
import { useBoxZiele, useHomelab, useHomelabLaeufe, useInstanz, usePartner, useZielAktion } from "@/lib/abfragen"
import type { HomelabLauf, PartnerStatus, ZielStand } from "@/lib/typen"
import {
useBoxZiele, useHomelab, useHomelabHinweise, useHomelabLaeufe, useInstanz, usePartner, useZielAktion,
} from "@/lib/abfragen"
import type { Hinweis, HomelabLauf, PartnerStatus, ZielStand } from "@/lib/typen"
import { flugplanZeit } from "@/lib/zeit"
/** Was der Homelab-Teil übernimmt (Bestandsaufnahme vom 24.09.2026). */
@@ -60,6 +62,25 @@ function HomelabFehlt({ partner }: { partner: PartnerStatus }) {
)
}
/** Hinweise des Homelab-Wächters (die der KI-Box stehen auf dem Start). Rot zuerst. */
function HomelabHinweise({ hinweise }: { hinweise: Hinweis[] }) {
if (hinweise.length === 0) return null
return (
<Panel titel="Hinweise im Homelab" rechts={<Chip art={hinweise.some((h) => h.stufe === "rot") ? "rot" : "bernstein"}>{hinweise.length}</Chip>}>
<ul className="flex flex-col gap-3">
{hinweise.map((h) => (
<li key={h.id} className="flex flex-col gap-1 border-t border-linie pt-3 first:border-t-0 first:pt-0">
<span className={h.stufe === "rot" ? "text-[15px] font-medium text-rot-text" : "text-[15px] font-medium text-bernstein"}>
{h.titel}
</span>
<span className="text-sm text-text-2">{h.text}</span>
</li>
))}
</ul>
</Panel>
)
}
function LetzteLaeufe({ laeufe }: { laeufe: HomelabLauf[] }) {
const fertig = laeufe.filter((l) => l.status !== "laeuft").slice(0, 6)
if (fertig.length === 0) return null
@@ -94,6 +115,7 @@ export function HomelabSeite() {
const aktiv = homelabHier || !!(partner.data?.eingerichtet && partner.data.erreichbar)
const homelab = useHomelab(aktiv)
const laeufe = useHomelabLaeufe(aktiv)
const hinweise = useHomelabHinweise(aktiv)
const aktion = useZielAktion()
if (instanz.isPending || partner.isPending) return <Zustandsfeld text="Wird gelesen …" />
@@ -131,6 +153,8 @@ export function HomelabSeite() {
{homelab.isError && <p role="alert" className="text-sm text-rot-text">Der Homelab-Teil antwortet gerade nicht: {homelab.error.message}</p>}
</Panel>
{aktiv && <HomelabHinweise hinweise={hinweise.data?.hinweise ?? []} />}
{ziele.length > 0 && (
<div className="grid gap-5 md:gap-6 xl:grid-cols-2">
{ziele.map((z) => (