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),
|
"hostname_konvention_aktiv": bool(hostname_praefix),
|
||||||
},
|
},
|
||||||
"flotten_gesundheit": _dashboard_flotten_gesundheit(geraete_rows),
|
"flotten_gesundheit": _dashboard_flotten_gesundheit(geraete_rows),
|
||||||
|
"aktivitaeten": fetch_aktivitaeten(organization_id, DASHBOARD_LETZTE_AKTIONEN_LIMIT),
|
||||||
"letzte_aktionen": [
|
"letzte_aktionen": [
|
||||||
{
|
{
|
||||||
"occurred_at": occurred_at.isoformat(),
|
"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():
|
def fetch_admin_dashboard():
|
||||||
with get_database_connection() as conn:
|
with get_database_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
@@ -2625,6 +2708,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),
|
||||||
"letzte_aktionen": [
|
"letzte_aktionen": [
|
||||||
{
|
{
|
||||||
"occurred_at": occurred_at.isoformat(),
|
"occurred_at": occurred_at.isoformat(),
|
||||||
@@ -5689,6 +5773,14 @@ def set_hostname_konvention_endpoint(
|
|||||||
return {"success": True}
|
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")
|
@app.get("/api/v1/organizations/{organization_id}/activation-codes")
|
||||||
def list_activation_codes(
|
def list_activation_codes(
|
||||||
organization_id: str,
|
organization_id: str,
|
||||||
|
|||||||
Reference in new issue
Block a user