Dashboards: Verteilungen fuer Ringdiagramme (Zustand, Distro, Workspace, Organisation)
_dashboard_verteilungen(): Geraetezustand ausschliessend (nie gemeldet, >7 Tage nicht gemeldet, Ressourcen knapp, in Ordnung), Distro und wirksamer Workspace je Geraet ueber geraete_basis(), Organisationen im Admin-Dashboard. Hoechstens 5 Teile + 'Weitere', Sonderfaelle (ohne Installation/Workspace) immer am Ende. GUI-Runde 10/2026 Punkt 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0561691f20
commit
465910f700
1 file changed
+88
@@ -2519,6 +2519,90 @@ def _dashboard_flotten_gesundheit(geraete_rows) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DASHBOARD_VERTEILUNG_MAX = 5
|
||||||
|
VERTEILUNG_SONDERFAELLE = {"ohne Installation", "ohne Workspace", "unbekannt"}
|
||||||
|
|
||||||
|
|
||||||
|
def _verteilung(zaehler: dict, max_teile: int = DASHBOARD_VERTEILUNG_MAX) -> list[dict]:
|
||||||
|
"""Zähler -> [{name, anzahl}] absteigend; ab max_teile+1 Einträgen werden
|
||||||
|
die kleinsten zu "Weitere" zusammengefasst (Ringdiagramm: höchstens
|
||||||
|
sechs Teile, GUI-Runde 09.10.2026)."""
|
||||||
|
|
||||||
|
# Keine echten Kategorien (grau dargestellt) immer ans Ende, damit die
|
||||||
|
# Kategoriefarben für echte Einträge lückenlos in fester Reihenfolge bleiben.
|
||||||
|
sonder = {n: a for n, a in zaehler.items() if n in VERTEILUNG_SONDERFAELLE}
|
||||||
|
teile = sorted(((n, a) for n, a in zaehler.items() if n not in sonder), key=lambda e: (-e[1], e[0]))
|
||||||
|
if len(teile) + len(sonder) > max_teile + 1:
|
||||||
|
behalten = max(max_teile - len(sonder), 1)
|
||||||
|
rest = sum(anzahl for _, anzahl in teile[behalten:])
|
||||||
|
teile = teile[:behalten] + ([("Weitere", rest)] if rest else [])
|
||||||
|
teile += sorted(sonder.items())
|
||||||
|
return [{"name": name, "anzahl": anzahl} for name, anzahl in teile]
|
||||||
|
|
||||||
|
|
||||||
|
def _dashboard_verteilungen(cur, organization_id: str | None = None) -> dict:
|
||||||
|
"""
|
||||||
|
Verteilungen für die Dashboard-Ringdiagramme (GUI-Runde 09.10.2026,
|
||||||
|
Punkt 6): Gerätezustand, Distro, wirksamer Workspace, bei
|
||||||
|
organisationsübergreifender Sicht auch Organisation. Zustand ist
|
||||||
|
ausschließend (jedes Gerät genau einmal): nicht gemeldet hat Vorrang vor
|
||||||
|
knappen Ressourcen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT d.id, o.name, d.agent_last_checkin, d.health_disk_used_percent, d.health_ram_used_percent
|
||||||
|
FROM devices d
|
||||||
|
JOIN organizations o ON o.id = d.organization_id
|
||||||
|
WHERE d.archived_at IS NULL AND (%(org)s::uuid IS NULL OR d.organization_id = %(org)s::uuid)
|
||||||
|
""",
|
||||||
|
{"org": organization_id},
|
||||||
|
)
|
||||||
|
geraete = cur.fetchall()
|
||||||
|
|
||||||
|
cur.execute("SELECT id, name FROM backends")
|
||||||
|
backend_namen = {str(i): n for i, n in cur.fetchall()}
|
||||||
|
cur.execute("SELECT v.id, w.name FROM workspace_versionen v JOIN workspaces w ON w.id = v.workspace_id")
|
||||||
|
version_workspace = {str(i): n for i, n in cur.fetchall()}
|
||||||
|
|
||||||
|
schwelle = datetime.now(timezone.utc) - timedelta(days=DASHBOARD_KEIN_CHECKIN_TAGE)
|
||||||
|
zustand = {"in Ordnung": 0, "Ressourcen knapp": 0, f"länger als {DASHBOARD_KEIN_CHECKIN_TAGE} Tage nicht gemeldet": 0, "noch nie gemeldet": 0}
|
||||||
|
distros, workspaces, organisationen = {}, {}, {}
|
||||||
|
|
||||||
|
for device_id, org_name, checkin, disk, ram in geraete:
|
||||||
|
if checkin is None:
|
||||||
|
zustand["noch nie gemeldet"] += 1
|
||||||
|
elif checkin < schwelle:
|
||||||
|
zustand[f"länger als {DASHBOARD_KEIN_CHECKIN_TAGE} Tage nicht gemeldet"] += 1
|
||||||
|
elif (disk or 0) >= DASHBOARD_DISK_KRITISCH_PROZENT or (ram or 0) >= DASHBOARD_RAM_KRITISCH_PROZENT:
|
||||||
|
zustand["Ressourcen knapp"] += 1
|
||||||
|
else:
|
||||||
|
zustand["in Ordnung"] += 1
|
||||||
|
|
||||||
|
basis = geraete_basis(cur, str(device_id))
|
||||||
|
if basis is None:
|
||||||
|
distro, workspace = "ohne Installation", "ohne Installation"
|
||||||
|
else:
|
||||||
|
version_id, backend_id = basis
|
||||||
|
distro = backend_namen.get(str(backend_id), "unbekannt")
|
||||||
|
workspace = version_workspace.get(str(version_id), "ohne Workspace") if version_id else "ohne Workspace"
|
||||||
|
distros[distro] = distros.get(distro, 0) + 1
|
||||||
|
workspaces[workspace] = workspaces.get(workspace, 0) + 1
|
||||||
|
organisationen[org_name] = organisationen.get(org_name, 0) + 1
|
||||||
|
|
||||||
|
ergebnis = {
|
||||||
|
"geraete_gesamt": len(geraete),
|
||||||
|
# feste Reihenfolge (Statusfarben), leere Zustände bleiben drin -
|
||||||
|
# die Oberfläche blendet sie in der Legende aus
|
||||||
|
"zustand": [{"name": n, "anzahl": a} for n, a in zustand.items()],
|
||||||
|
"distros": _verteilung(distros),
|
||||||
|
"workspaces": _verteilung(workspaces),
|
||||||
|
}
|
||||||
|
if organization_id is None:
|
||||||
|
ergebnis["organisationen"] = _verteilung(organisationen)
|
||||||
|
return ergebnis
|
||||||
|
|
||||||
|
|
||||||
def fetch_customer_dashboard(organization_id: str):
|
def fetch_customer_dashboard(organization_id: str):
|
||||||
with get_database_connection() as conn:
|
with get_database_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
@@ -2538,6 +2622,7 @@ def fetch_customer_dashboard(organization_id: str):
|
|||||||
(organization_id,),
|
(organization_id,),
|
||||||
)
|
)
|
||||||
geraete_rows = cur.fetchall()
|
geraete_rows = cur.fetchall()
|
||||||
|
verteilungen = _dashboard_verteilungen(cur, organization_id)
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
@@ -2570,6 +2655,7 @@ def fetch_customer_dashboard(organization_id: str):
|
|||||||
},
|
},
|
||||||
"flotten_gesundheit": _dashboard_flotten_gesundheit(geraete_rows),
|
"flotten_gesundheit": _dashboard_flotten_gesundheit(geraete_rows),
|
||||||
"aktivitaeten": fetch_aktivitaeten(organization_id, DASHBOARD_LETZTE_AKTIONEN_LIMIT),
|
"aktivitaeten": fetch_aktivitaeten(organization_id, DASHBOARD_LETZTE_AKTIONEN_LIMIT),
|
||||||
|
"verteilungen": verteilungen,
|
||||||
"letzte_aktionen": [
|
"letzte_aktionen": [
|
||||||
{
|
{
|
||||||
"occurred_at": occurred_at.isoformat(),
|
"occurred_at": occurred_at.isoformat(),
|
||||||
@@ -2677,6 +2763,7 @@ def fetch_admin_dashboard():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
geraete_rows = cur.fetchall()
|
geraete_rows = cur.fetchall()
|
||||||
|
verteilungen = _dashboard_verteilungen(cur, None)
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
@@ -2718,6 +2805,7 @@ def fetch_admin_dashboard():
|
|||||||
for org_id, name, geraete_anzahl, letzte_aktivitaet in organisationen
|
for org_id, name, geraete_anzahl, letzte_aktivitaet in organisationen
|
||||||
],
|
],
|
||||||
"aktivitaeten": fetch_aktivitaeten(None, DASHBOARD_LETZTE_AKTIONEN_LIMIT),
|
"aktivitaeten": fetch_aktivitaeten(None, DASHBOARD_LETZTE_AKTIONEN_LIMIT),
|
||||||
|
"verteilungen": verteilungen,
|
||||||
"letzte_aktionen": [
|
"letzte_aktionen": [
|
||||||
{
|
{
|
||||||
"occurred_at": occurred_at.isoformat(),
|
"occurred_at": occurred_at.isoformat(),
|
||||||
|
|||||||
Reference in new issue
Block a user