Aktivitaetenliste: Installationsereignisse + zusammengefasste Agent-Laeufe
fetch_aktivitaeten(): ein Agent-Lauf meldet je Merkmal ein Ereignis (~56,
gleicher Zeitstempel) - hier zu einer Zeile zusammengefasst, Fehler
benannt. Neuer Endpunkt /organizations/{id}/aktivitaeten, beide
Dashboards liefern zusaetzlich 'aktivitaeten'. GUI-Runde 10/2026 Punkt 4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3381b2b13c
commit
b038350e72
1 file changed
+92
@@ -2560,6 +2560,7 @@ def fetch_customer_dashboard(organization_id: str):
|
||||
"hostname_konvention_aktiv": bool(hostname_praefix),
|
||||
},
|
||||
"flotten_gesundheit": _dashboard_flotten_gesundheit(geraete_rows),
|
||||
"aktivitaeten": fetch_aktivitaeten(organization_id, DASHBOARD_LETZTE_AKTIONEN_LIMIT),
|
||||
"letzte_aktionen": [
|
||||
{
|
||||
"occurred_at": occurred_at.isoformat(),
|
||||
@@ -2572,6 +2573,88 @@ def fetch_customer_dashboard(organization_id: str):
|
||||
}
|
||||
|
||||
|
||||
AKTIVITAETEN_LIMIT = 50
|
||||
|
||||
INSTALLATIONSEREIGNIS_TEXTE = {
|
||||
"activation_confirmed": "Aktivierung bestätigt",
|
||||
"installation_started": "Installation gestartet",
|
||||
"installation_completed": "Installation abgeschlossen",
|
||||
"installation_failed": "Installation fehlgeschlagen",
|
||||
}
|
||||
|
||||
|
||||
def fetch_aktivitaeten(organization_id: str | None = None, limit: int = AKTIVITAETEN_LIMIT):
|
||||
"""
|
||||
Aktivitätenliste (GUI-Runde 09.10.2026, Punkt 4): Installationsereignisse
|
||||
einzeln, Agent-Läufe zusammengefasst. Ein Lauf meldet je Merkmal ein
|
||||
Ereignis (rund 56) mit demselben Zeitstempel - die Liste bliebe sonst
|
||||
unlesbar. Ohne organization_id über alle Organisationen (Admin-Dashboard).
|
||||
"""
|
||||
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
WITH laeufe AS (
|
||||
SELECT e.device_id, e.occurred_at, count(*) AS anzahl,
|
||||
array_remove(array_agg(m.name ORDER BY m.name)
|
||||
FILTER (WHERE e.event_type LIKE '%%failed'), NULL) AS fehler
|
||||
FROM device_merkmal_events e
|
||||
JOIN merkmale m ON m.id = e.merkmal_id
|
||||
JOIN devices d ON d.id = e.device_id
|
||||
WHERE (%(org)s::uuid IS NULL OR d.organization_id = %(org)s::uuid)
|
||||
GROUP BY e.device_id, e.occurred_at
|
||||
ORDER BY e.occurred_at DESC
|
||||
LIMIT %(limit)s
|
||||
),
|
||||
installationen AS (
|
||||
SELECT ie.device_id, ie.occurred_at, ie.event_type
|
||||
FROM device_installation_events ie
|
||||
JOIN devices d ON d.id = ie.device_id
|
||||
WHERE (%(org)s::uuid IS NULL OR d.organization_id = %(org)s::uuid)
|
||||
ORDER BY ie.occurred_at DESC
|
||||
LIMIT %(limit)s
|
||||
)
|
||||
SELECT x.occurred_at, x.art, x.event_type, x.anzahl, x.fehler,
|
||||
d.id, d.hostname, o.name
|
||||
FROM (
|
||||
SELECT occurred_at, device_id, 'agent' AS art, NULL AS event_type, anzahl, fehler FROM laeufe
|
||||
UNION ALL
|
||||
SELECT occurred_at, device_id, 'installation', event_type, NULL, NULL FROM installationen
|
||||
) x
|
||||
JOIN devices d ON d.id = x.device_id
|
||||
JOIN organizations o ON o.id = d.organization_id
|
||||
ORDER BY x.occurred_at DESC
|
||||
LIMIT %(limit)s
|
||||
""",
|
||||
{"org": organization_id, "limit": limit},
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
|
||||
ergebnis = []
|
||||
for occurred_at, art, event_type, anzahl, fehler, device_id, hostname, org_name in rows:
|
||||
fehler = fehler or []
|
||||
if art == "agent":
|
||||
text = f"Agent-Lauf: {anzahl} Merkmale geprüft" + (
|
||||
f", {len(fehler)} fehlgeschlagen ({', '.join(fehler[:5])}{' …' if len(fehler) > 5 else ''})"
|
||||
if fehler else ", ohne Fehler"
|
||||
)
|
||||
problem = bool(fehler)
|
||||
else:
|
||||
text = INSTALLATIONSEREIGNIS_TEXTE.get(event_type, event_type)
|
||||
problem = event_type == "installation_failed"
|
||||
ergebnis.append({
|
||||
"zeit": occurred_at.isoformat(),
|
||||
"art": art,
|
||||
"text": text,
|
||||
"problem": problem,
|
||||
"device_id": str(device_id),
|
||||
"hostname": hostname or "unbenannt",
|
||||
"organization_name": org_name,
|
||||
})
|
||||
return ergebnis
|
||||
|
||||
|
||||
def fetch_admin_dashboard():
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -2625,6 +2708,7 @@ def fetch_admin_dashboard():
|
||||
}
|
||||
for org_id, name, geraete_anzahl, letzte_aktivitaet in organisationen
|
||||
],
|
||||
"aktivitaeten": fetch_aktivitaeten(None, DASHBOARD_LETZTE_AKTIONEN_LIMIT),
|
||||
"letzte_aktionen": [
|
||||
{
|
||||
"occurred_at": occurred_at.isoformat(),
|
||||
@@ -5689,6 +5773,14 @@ def set_hostname_konvention_endpoint(
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@app.get("/api/v1/organizations/{organization_id}/aktivitaeten")
|
||||
def list_aktivitaeten(organization_id: str, limit: int = AKTIVITAETEN_LIMIT, authorization: str | None = Header(default=None)):
|
||||
if not require_service_token(authorization):
|
||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||
|
||||
return {"success": True, "aktivitaeten": fetch_aktivitaeten(organization_id, max(1, min(limit, 500)))}
|
||||
|
||||
|
||||
@app.get("/api/v1/organizations/{organization_id}/activation-codes")
|
||||
def list_activation_codes(
|
||||
organization_id: str,
|
||||
|
||||
Reference in new issue
Block a user