Welle 1 · Nachschliff: Proxmox-Kachel zählt die Gäste wie die Homelab-Karte, der eigene Container heißt lesbar

„8 von 8 Gästen an“ neben „7 Gäste“: Die Kachel zählte den eigenen Container (Etikett mc2) mit, die Karte nicht.
In „Sicherungen je Gerät“ heißt er „Homelab Orchestrator (dieser Container)“ statt homelab-orchestrator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-25 17:16:22 +02:00
co-authored by Claude Opus 5.5
parent 371dcc7ce3
commit f2bffc7203
4 changed files with 15 additions and 6 deletions
+4 -1
View File
@@ -13,6 +13,7 @@ höchstens alle zwei Minuten gefragt.
import logging
from services.homelab import adguard, apps, arcane, zertifikate
from services.homelab.inventar import EIGENES_ETIKETT
log = logging.getLogger(__name__)
NPM = {"npmplus", "nginxproxymanager"}
@@ -36,7 +37,9 @@ def _npm(vmid: int) -> dict | None:
def fuer_bericht(bericht: dict) -> dict[str, dict]:
"""Ziel-ID → Kennzahl. Ein Fehler bei einem Gerät kostet nur dessen Zahl."""
zahlen: dict[str, dict] = {}
gaeste = [g for g in bericht.get("gaeste") or [] if isinstance(g, dict) and "vmid" in g]
# Der eigene Container (Etikett „mc2“) zählt nicht mit — wie überall, wo die Geräte des Homelabs stehen.
gaeste = [g for g in bericht.get("gaeste") or [] if isinstance(g, dict) and "vmid" in g
and EIGENES_ETIKETT not in (g.get("etiketten") or [])]
if gaeste:
laufend = sum(1 for g in gaeste if g.get("status") == "running")
zahlen["pve"] = {"text": f"{laufend} von {len(gaeste)} Gästen an"}
+3 -1
View File
@@ -375,8 +375,10 @@ def _je_geraet(bericht: dict, ziele: dict, fehlend: set[str], ohne: set[int], pr
zuletzt = max([t for z in ziele.values() if (t := (z.get("neueste") or {}).get(vmid))], default=None)
pruefungen = [d["geprueft"] for d in details if d.get("geprueft")]
eigene_probe = ((probe or {}).get("gaeste") or {}).get(vmid)
eigener = EIGENES_ETIKETT in (g.get("etiketten") or [])
zeilen.append({
"ziel": _zid(g), "name": _name(g), "vmid": g["vmid"], "auftrag": g["vmid"] not in ohne,
"ziel": _zid(g), "name": "Homelab Orchestrator (dieser Container)" if eigener else _name(g),
"vmid": g["vmid"], "auftrag": g["vmid"] not in ohne,
"zuletzt": zuletzt, "frisch": bool(zuletzt) and g["vmid"] not in ohne and _zid(g) not in fehlend,
"geprueft": max(pruefungen, key=lambda p: (p["zeit"], p["sicherung"]), default=None),
"defekt": sorted({t for d in details for t in d.get("defekt") or []}),
+3 -1
View File
@@ -38,7 +38,9 @@ def test_zahlen_je_geraet(monkeypatch):
monkeypatch.setattr(zertifikate, "lage", lambda jetzt=None: {"zertifikate": [npm] * 10})
monkeypatch.setattr(arcane, "container_zahlen", lambda url: {"laufend": 11, "gesamt": 12})
monkeypatch.setattr(adguard, "geblockt", lambda: {"anfragen": 84_211, "geblockt": 10_523, "anteil": 12.5})
zahlen = kennzahlen.fuer_bericht(_bericht())
b = _bericht()
b["gaeste"].append({"vmid": 107, "art": "lxc", "status": "running", "etiketten": ["mc2"]}) # der eigene zählt nicht
zahlen = kennzahlen.fuer_bericht(b)
laufend = sum(1 for g in BERICHT["gaeste"] if g["status"] == "running")
assert zahlen["pve"] == {"text": f"{laufend} von {len(BERICHT['gaeste'])} Gästen an"}
assert zahlen["ct-105"] == {"text": "letzte Sicherung", "zeit": 1790296281}
+5 -3
View File
@@ -121,7 +121,7 @@ def _bericht(**host) -> dict:
"neueste": {k: v["zeit"] for k, v in details.items()},
"schaetzung": {"voll_am": 1887231717, "belegt": 1_517_685_571_584,
"gesamt": 3_847_503_609_856}}},
"nicht_gesichert": [104],
"nicht_gesichert": [104], # 107 (der eigene) ist gesichert
"sicherung_probe": {"zeit": 1790330000, "gaeste": {
"102": {"speicher": "pbs-qnap", "sicherung": 1790296227, "ok": True, "fehler": None},
"106": {"speicher": "pbs-qnap", "sicherung": 1790296281, "ok": False,
@@ -130,7 +130,8 @@ def _bericht(**host) -> dict:
return {"host": {**basis, **host}, "gaeste": [
{"vmid": 102, "art": "lxc", "name": "netbird", "app": {"kennung": "netbird"}},
{"vmid": 104, "art": "lxc", "name": "gitea", "app": {"kennung": "gitea"}},
{"vmid": 106, "art": "qemu", "name": "arcane"}]}
{"vmid": 106, "art": "qemu", "name": "arcane"},
{"vmid": 107, "art": "lxc", "name": "homelab-orchestrator", "etiketten": ["mc2"]}]}
def test_abdeckung_je_geraet():
@@ -144,6 +145,7 @@ def test_abdeckung_je_geraet():
"probe": {"speicher": "pbs-qnap", "sicherung": 1790296227, "ok": True, "fehler": None,
"zeit": 1790330000}}
assert zeilen["vm-106"]["defekt"] == [1789864278] and zeilen["vm-106"]["probe"]["ok"] is False
assert zeilen["ct-107"]["name"] == "Homelab Orchestrator (dieser Container)"
# Die letzte Prüfung von Arcane fand Beschädigtes: rot, auch wenn alles frisch gesichert ist.
assert (s["stufe_frische"], s["stufe"]) == ("ok", "rot")
assert s["pruefung"] == {"zuletzt": GEPRUEFT, "noetig": False} # älteste Sicherung erst 5,5 Tage alt
@@ -194,7 +196,7 @@ def test_aelterer_ausfuehrer_ohne_neue_angaben():
b["host"].pop(schluessel)
b["host"]["sicherungsziele"]["pbs-qnap"].pop("details")
s = speicher.sicherungen(b, JETZT)
assert [g["ziel"] for g in s["ohne_auftrag"]] == ["ct-104"] # selbst ausgerechnet aus den Aufträgen
assert [g["ziel"] for g in s["ohne_auftrag"]] == ["ct-104", "ct-107"] # selbst ausgerechnet aus den Aufträgen
assert s["stufe"] == "gelb" and s["probe"] is None
assert all(g["geprueft"] is None and g["probe"] is None for g in s["geraete"])