Welle 1 · Live-Zahlen je Dienst auf den Kacheln, AdGuard-Zugang in den Einstellungen
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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
53ea0cd9b6
commit
ad8dbbc1b3
@@ -0,0 +1,68 @@
|
||||
"""Eine Live-Zahl je Gerät für die Kacheln des Homelab-Cockpits und der Übersicht (Ausbauplan Welle 1, seit 25.09.2026).
|
||||
|
||||
Proxmox-Host wie viele Gäste laufen (aus dem Bericht)
|
||||
PBS wann zuletzt gesichert wurde (jüngste Sicherung auf den Zielen der Aufträge)
|
||||
NPMplus wie es um seine Zertifikate steht (Zertifikats-Wache, zertifikate.py)
|
||||
Arcane laufende von allen Containern (Arcane-API mit dem Schlüssel aus den Einstellungen)
|
||||
AdGuard Home Anteil geblockter Anfragen (nur mit hinterlegtem Zugang, Einstellungen; adguard.py)
|
||||
Je Ziel-ID {"text", "zeit"?, "stufe"?}: „zeit“ setzt die Oberfläche als „vor …“ dahinter, „stufe“ (gelb/rot) färbt die
|
||||
Zeile. Was sich nicht ermitteln lässt, fehlt — die Kachel zeigt dann nur ihren Stand. Arcane und AdGuard werden
|
||||
höchstens alle zwei Minuten gefragt.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from services.homelab import adguard, apps, arcane, zertifikate
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
NPM = {"npmplus", "nginxproxymanager"}
|
||||
|
||||
|
||||
def _komma(zahl: float) -> str:
|
||||
return f"{zahl:.1f}".replace(".", ",") if zahl < 10 else str(round(zahl))
|
||||
|
||||
|
||||
def _npm(vmid: int) -> dict | None:
|
||||
eigene = [z for z in zertifikate.lage()["zertifikate"] if z["quelle"] == "npm" and z["gast"] == vmid]
|
||||
if not eigene:
|
||||
return None
|
||||
probleme = [z for z in eigene if z["stufe"] != "ok"]
|
||||
if not probleme:
|
||||
return {"text": f"{len(eigene)} Zertifikat{'e' if len(eigene) != 1 else ''} gültig"}
|
||||
stufe = "rot" if any(z["stufe"] == "rot" for z in probleme) else "gelb"
|
||||
return {"text": f"{len(probleme)} von {len(eigene)} Zertifikaten mit Problem", "stufe": stufe}
|
||||
|
||||
|
||||
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]
|
||||
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"}
|
||||
ziele = (bericht.get("host") or {}).get("sicherungsziele") or {}
|
||||
for g in gaeste:
|
||||
app = apps.app_fuer((g.get("app") or {}).get("kennung"), g.get("name"))
|
||||
if not app or g.get("status") != "running":
|
||||
continue
|
||||
zid = f"{'ct' if g.get('art') == 'lxc' else 'vm'}-{g['vmid']}"
|
||||
try:
|
||||
if app.kennung == "proxmox-backup-server":
|
||||
zuletzt = max((t for z in ziele.values() if isinstance(z, dict)
|
||||
for t in (z.get("neueste") or {}).values() if isinstance(t, int)), default=None)
|
||||
if zuletzt:
|
||||
zahlen[zid] = {"text": "letzte Sicherung", "zeit": zuletzt}
|
||||
elif app.kennung in NPM:
|
||||
if zahl := _npm(g["vmid"]):
|
||||
zahlen[zid] = zahl
|
||||
elif app.kennung == "arcane" and (url := apps.adresse(app, g.get("ip"))):
|
||||
container = arcane.container_zahlen(url.rstrip("/"))
|
||||
if container and container["gesamt"]:
|
||||
zahlen[zid] = {"text": f"{container['laufend']} von {container['gesamt']} Containern an"}
|
||||
elif app.kennung == "adguard":
|
||||
statistik = adguard.geblockt()
|
||||
if statistik and statistik.get("anteil") is not None:
|
||||
zahlen[zid] = {"text": f"{_komma(statistik['anteil'])} % geblockt"}
|
||||
except Exception:
|
||||
log.warning("kennzahlen: %s ließ sich nicht ermitteln", zid, exc_info=True)
|
||||
return zahlen
|
||||
Reference in New Issue
Block a user