Dringende Hinweise gehen zusätzlich per Telegram raus, alles andere bleibt hier; was nachts anfällt, kommt um
07:00 als eine Morgenmeldung. Einfache Probleme wie einen abgestürzten Dienst behebt der Wächter selbst und
@@ -363,7 +386,7 @@ function Stand({ offen }: { offen: boolean }) {
const [hier, partner] = useSoftwareStand(offen, partnerDa)
const gleich = gleicherStand(hier.data?.stand?.commit, partner.data?.stand?.commit)
return (
-
+
{partnerDa && (
@@ -379,30 +402,173 @@ function Stand({ offen }: { offen: boolean }) {
)
}
-// --- Schublade ------------------------------------------------------------------------------------------
+// --- Homelab: Arcane und Betrieb ------------------------------------------------------------------------
-export function Einstellungen({ offen, onSchliessen }: { offen: boolean; onSchliessen: () => void }) {
- const box = useEinstellungenStand(offen)
+function ArcaneSchluessel({ h }: { h: HomelabEinstellungen }) {
+ const speichern = useArcaneSchluessel()
+ const echt = useArcaneEcht()
+ const [wert, setWert] = useState("")
+ const [loeschFrage, setLoeschFrage] = useState(false)
+ const [echtFrage, setEchtFrage] = useState(null)
+ const a = h.arcane
+ const stand: Record = {
+ hinterlegt: hinterlegt,
+ umgebung: aus der Dienst-Einstellung,
+ fehlt: fehlt,
+ }
+ return (
+ <>
+
+
+
+ Mit Schlüssel sieht der Orchestrator, welche Docker-Images neuer sind. Anlegen in Arcane unter Einstellungen →
+ API-Schlüssel; Lesen reicht, solange Docker-Updates nur als Probelauf laufen.
+
+ {a.schluessel !== "umgebung" && (
+
+ )}
+
+ Der Schlüssel wird vor dem Speichern an Arcane geprüft und nie wieder angezeigt. Er liegt nur im Homelab-Teil.
+
+ {a.schluessel === "hinterlegt" && (
+ loeschFrage ? (
+
+
+
+
+ ) : (
+
+ )
+ )}
+
+
+
+ {a.echt
+ ? "Echt: „Jetzt updaten“ bei Arcane zieht neue Images und startet die Container neu."
+ : "Probelauf: Arcane zeigt nur, was es tun würde — kein Container wird angefasst."}
+ {a.echt_quelle === "umgebung" ? " Festgelegt in der Dienst-Einstellung (MC_ARCANE_ECHT)." : ""}
+
+
+ {a.schluessel === "fehlt" && Erst den Schlüssel eintragen.
}
+ setEchtFrage(null)}
+ onJa={() => {
+ if (echtFrage === null) return
+ echt.mutate(echtFrage)
+ setEchtFrage(null)
+ }}
+ />
+
+ >
+ )
+}
+
+function HomelabBetrieb({ h }: { h: HomelabEinstellungen }) {
+ const f = h.ausfuehrer
+ return (
+
+
+ - Ausführer
+ -
+ {f.verbunden ? "verbunden" : "meldet sich nicht"}
+ {f.zuletzt ? ` · zuletzt ${flugplanZeit(new Date(f.zuletzt * 1000).toISOString()).toLowerCase()}` : ""}
+
+ - Modus
+ - {f.nur_lesen ? "nur lesen — Updates sind gesperrt" : "voll: Snapshots, Sicherungen und Updates"}
+ - Sicherungen
+ - {f.sicherung_speicher ? `auf „${f.sicherung_speicher}“ (wo kein Snapshot geht)` : "kein Speicher gefunden"}
+ - Wartezeit
+ -
+ {h.karenz_h > 0 ? `${h.karenz_h} Stunden nach einer Änderung am Update-Skript einer App` : "aus"}
+
+
+
+ )
+}
+
+function HomelabKarte() {
+ const instanz = useInstanz().data
+ const partner = usePartner().data
+ const aktiv = instanz?.rolle === "homelab" || !!(partner?.eingerichtet && partner.erreichbar)
+ const h = useHomelabEinstellungen(aktiv)
+ let inhalt: ReactNode
+ if (!aktiv) inhalt = Der Homelab-Teil antwortet gerade nicht.
+ else if (!h.data) inhalt =
+ else inhalt = <>>
+ return (
+
+ {inhalt}
+
+ )
+}
+
+// --- Seite ----------------------------------------------------------------------------------------------
+
+export function EinstellungenSeite() {
+ const box = useEinstellungenStand(true)
const boxProps = { e: box.data, laedt: box.isPending, fehler: box.error?.message }
return (
- !o && onSchliessen()}>
-
-
- Einstellungen
- Was die Box sich merkt und wann sie was tut.
-
-
-
+
+
-
-
+
+
Hermes-Dashboard öffnen
Chat, Sitzungen und Cron-Jobs verwaltet Hermes selbst. Es fragt nach seiner eigenen Anmeldung.
-
-
+
+
+
+
+
+
+
+
+
)
}
diff --git a/frontend/src/views/Homelab.tsx b/frontend/src/views/Homelab.tsx
index f7f10ff..afdf094 100644
--- a/frontend/src/views/Homelab.tsx
+++ b/frontend/src/views/Homelab.tsx
@@ -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() {
Stand {berichtZeit}