Jede Gerätekachel (Homelab-Cockpit und Übersicht) zeigt eine Live-Zahl, wo es eine gibt: Proxmox-Host „8 von 8 Gästen an“, PBS „letzte Sicherung vor 13 h“, NPMplus „10 Zertifikate gültig“ (aus der Zertifikats-Wache), Arcane „11 von 12 Containern an“ (Arcane-API mit dem vorhandenen Schlüssel) und AdGuard „12 % geblockt“. Für AdGuard gibt es unter Einstellungen → Homelab einen Zugang, der vor dem Speichern gegen AdGuard geprüft wird und nur im Homelab-Teil liegt (0600, das Passwort steht in keiner Antwort). Arcane und AdGuard werden höchstens alle zwei Minuten gefragt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
5.4 KiB
TypeScript
113 lines
5.4 KiB
TypeScript
import { kennzahlText, lage, lampeFuer, listenAlter, offeneUpdates, rueckwegText } from "@/lib/homelab"
|
|
import type { BausteinStand, Hinweis, HomelabStand, ZielStand } from "@/lib/typen"
|
|
|
|
const baustein = (b: Partial<BausteinStand> & Pick<BausteinStand, "id" | "zustand">): BausteinStand => ({
|
|
name: b.id, installiert: null, verfuegbar: null, kurz: "", grund: null, aktion: null, ...b,
|
|
})
|
|
|
|
const ziel = (z: Partial<ZielStand> & Pick<ZielStand, "id" | "name" | "bausteine">): ZielStand => ({
|
|
art: "container", instanz: "homelab", erreichbar: true, geprueft: null, rueckweg: "Snapshot vor jedem Update",
|
|
zaehlung: {}, offen: 0, ...z,
|
|
})
|
|
|
|
const knopf = { methode: "POST" as const, pfad: "/api/homelab/ziele/x/update", frage: "Wirklich?", label: "Jetzt updaten" }
|
|
|
|
const netbird = ziel({
|
|
id: "ct-102", name: "NetBird",
|
|
bausteine: [
|
|
baustein({ id: "os", zustand: "unbekannt", grund: "Die Paketlisten sind vom 22.08.2026; was fehlt, weiß erst eine neue Suche." }),
|
|
baustein({ id: "app", zustand: "neu", installiert: "0.77.1", verfuegbar: "0.79.0", aktion: knopf }),
|
|
],
|
|
})
|
|
const arcane = ziel({
|
|
id: "vm-106", name: "Arcane (Docker)", art: "vm",
|
|
bausteine: [baustein({ id: "arcane", zustand: "neu", installiert: "2.12.0", verfuegbar: "2.13.1", grund: "eigene Oberfläche" })],
|
|
})
|
|
const npm = ziel({
|
|
id: "ct-101", name: "NPMplus",
|
|
bausteine: [baustein({ id: "app", zustand: "aktuell", installiert: "Image vom 2026-07-24" })],
|
|
})
|
|
|
|
const stand = (ziele: ZielStand[], verbunden = true): HomelabStand => ({
|
|
ziele, bericht: { empfangen: 1_790_000_000, veraltet: false }, ausfuehrer: { verbunden, zuletzt: null },
|
|
})
|
|
|
|
const hinweis = (stufe: Hinweis["stufe"]): Hinweis => ({
|
|
id: "h", stufe, titel: "t", text: "x", quelle: "q", seit: 0, zuletzt: 0, aktionen: [],
|
|
})
|
|
|
|
describe("offeneUpdates", () => {
|
|
it("setzt, was per Knopf geht, vor das, was die App selbst erledigt", () => {
|
|
const liste = offeneUpdates([arcane, netbird])
|
|
expect(liste.map((u) => u.ziel.id)).toEqual(["ct-102", "vm-106"])
|
|
})
|
|
})
|
|
|
|
describe("lampeFuer", () => {
|
|
it("zeigt die neue Version einer App als Info", () => {
|
|
expect(lampeFuer(netbird, false)).toMatchObject({ label: "NetBird", zustand: "info", wert: "→ 0.79.0" })
|
|
})
|
|
|
|
it("kürzt Namen und Image-Datum für die Lampe", () => {
|
|
expect(lampeFuer(npm, false)).toMatchObject({ zustand: "ok", wert: "2026-07-24" })
|
|
expect(lampeFuer(arcane, false).label).toBe("Arcane")
|
|
})
|
|
|
|
it("warnt bei voller Platte vor allem anderen außer Stille", () => {
|
|
expect(lampeFuer({ ...netbird, platte: { belegt: 98, gesamt: 100 } }, true)).toMatchObject({ zustand: "fehler", wert: "Platte voll" })
|
|
expect(lampeFuer({ ...npm, platte: { belegt: 85, gesamt: 100 } }, false)).toMatchObject({ zustand: "warn", wert: "Platte 85 %" })
|
|
expect(lampeFuer({ ...npm, platte: { belegt: 50, gesamt: 100 } }, false).zustand).toBe("ok")
|
|
})
|
|
|
|
it("meldet ein stummes Gerät rot und ein laufendes Update als Info", () => {
|
|
expect(lampeFuer({ ...npm, erreichbar: false }, false).zustand).toBe("fehler")
|
|
expect(lampeFuer(npm, true)).toMatchObject({ zustand: "info", wert: "Update läuft" })
|
|
})
|
|
})
|
|
|
|
describe("lage", () => {
|
|
it("zählt nur Updates mit Knopf", () => {
|
|
expect(lage(stand([netbird, arcane]), [], 0, null)).toMatchObject({ art: "info", mitte: "1 Update", ziel: "updates" })
|
|
})
|
|
|
|
it("stellt Störung vor Hinweis vor Update", () => {
|
|
expect(lage(stand([{ ...npm, erreichbar: false }, netbird]), [hinweis("gelb")], 0, null)).toMatchObject({
|
|
art: "rot", mitte: "1 Gerät stumm", unten: "NPMplus",
|
|
})
|
|
expect(lage(stand([netbird]), [hinweis("gelb")], 0, null)).toMatchObject({ art: "gelb", mitte: "1 Hinweis" })
|
|
expect(lage(stand([netbird]), [hinweis("rot"), hinweis("gelb")], 0, null)).toMatchObject({ art: "rot", mitte: "2 Hinweise" })
|
|
expect(lage(stand([netbird], false), [], 0, null)).toMatchObject({ art: "gelb", mitte: "Ausführer stumm" })
|
|
expect(lage(stand([netbird]), [], 1, null)).toMatchObject({ art: "info", mitte: "Update läuft" })
|
|
})
|
|
|
|
it("sagt „alles aktuell“ nur ohne offene Updates", () => {
|
|
expect(lage(stand([npm]), [], 0, "HEUTE 20:14")).toMatchObject({ art: "ok", mitte: "Alles aktuell", unten: "Stand HEUTE 20:14" })
|
|
})
|
|
})
|
|
|
|
describe("rueckwegText", () => {
|
|
it("fasst den Rückweg in wenigen Worten", () => {
|
|
expect(rueckwegText(netbird)).toBe("Snapshot vorher, bei Rot zurück")
|
|
expect(rueckwegText({ ...netbird, rueckweg: "Sicherung vor jedem Update" })).toBe("Sicherung vorher, bei Rot zurück")
|
|
expect(rueckwegText({ ...netbird, rueckweg_art: "sicherung", rueckweg: null })).toBe("Sicherung vorher, bei Rot zurück")
|
|
expect(rueckwegText({ ...netbird, art: "proxmox-host", rueckweg: "Kein Snapshot möglich" })).toBe("Kein Rückweg, Neustart getrennt")
|
|
})
|
|
})
|
|
|
|
describe("listenAlter", () => {
|
|
it("holt das Datum der Paketlisten aus dem Grund", () => {
|
|
expect(listenAlter(netbird.bausteine[0].grund)).toBe("22.08.2026")
|
|
expect(listenAlter(null)).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe("kennzahlText (seit 25.09.2026)", () => {
|
|
it("setzt „vor …“ hinter Zahlen mit Zeitpunkt", () => {
|
|
const jetzt = Math.floor(Date.now() / 1000)
|
|
expect(kennzahlText({ text: "12 % geblockt" })).toBe("12 % geblockt")
|
|
expect(kennzahlText({ text: "letzte Sicherung", zeit: jetzt - 13 * 3600 })).toBe("letzte Sicherung vor 13 h")
|
|
expect(kennzahlText(null)).toBeNull()
|
|
expect(kennzahlText(undefined)).toBeNull()
|
|
})
|
|
})
|