diff --git a/src/routers/sources.py b/src/routers/sources.py index 3d7f737..ef3522c 100644 --- a/src/routers/sources.py +++ b/src/routers/sources.py @@ -565,17 +565,46 @@ async def add_discovered_sources( @router.get("/health") async def get_health( + limit: int = 100, + offset: int = 0, admin: dict = Depends(get_current_admin), db: aiosqlite.Connection = Depends(db_dependency), ): - """Health-Check-Ergebnisse abrufen.""" + """Health-Check-Ergebnisse abrufen. + + Default-Limit 100, sortiert nach Status (errors first, dann warnings, dann ok). + Counters (errors/warnings/ok/total_checks) beziehen sich auf den GESAMTEN + Datenbestand, nicht nur auf die zurückgegebene Page. Damit kann das Frontend + den vollen Status anzeigen, ohne alle Zeilen rendern zu müssen. + has_more zeigt an, ob es weitere Items zum Nachladen gibt. + all_orgs liefert die Liste aller Tenants mit Health-Checks (für Filter-Dropdown). + """ + limit = max(1, min(int(limit or 100), 5000)) + offset = max(0, int(offset or 0)) + # Prüfen ob Tabelle existiert cursor = await db.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='source_health_checks'" ) if not await cursor.fetchone(): - return {"last_check": None, "total_checks": 0, "errors": 0, "warnings": 0, "ok": 0, "checks": []} + return { + "last_check": None, "total_checks": 0, + "errors": 0, "warnings": 0, "ok": 0, + "checks": [], "all_orgs": [], + "limit": limit, "offset": offset, "has_more": False, + } + # Aggregate über GESAMTEN Bestand (eine GROUP-BY-Query, billig) + cursor = await db.execute( + "SELECT status, COUNT(*) AS n FROM source_health_checks GROUP BY status" + ) + counts = {row["status"]: row["n"] for row in await cursor.fetchall()} + error_count = counts.get("error", 0) + warning_count = counts.get("warning", 0) + ok_count = counts.get("ok", 0) + total_checks = error_count + warning_count + ok_count + + # Paginierte Daten cursor = await db.execute(""" SELECT h.source_id, s.name, s.domain, s.tenant_id, s.language, @@ -587,12 +616,20 @@ async def get_health( ORDER BY CASE h.status WHEN 'error' THEN 0 WHEN 'warning' THEN 1 ELSE 2 END, s.name - """) + LIMIT ? OFFSET ? + """, (limit, offset)) checks = [dict(row) for row in await cursor.fetchall()] - error_count = sum(1 for c in checks if c["status"] == "error") - warning_count = sum(1 for c in checks if c["status"] == "warning") - ok_count = sum(1 for c in checks if c["status"] == "ok") + # Org-Liste (alle Tenants mit Health-Checks, für Frontend-Filter-Dropdown) + cursor = await db.execute(""" + SELECT DISTINCT s.tenant_id AS id, o.name AS name + FROM source_health_checks h + JOIN sources s ON s.id = h.source_id + LEFT JOIN organizations o ON o.id = s.tenant_id + WHERE s.tenant_id IS NOT NULL + ORDER BY o.name + """) + all_orgs = [dict(row) for row in await cursor.fetchall()] cursor = await db.execute("SELECT MAX(checked_at) as last_check FROM source_health_checks") row = await cursor.fetchone() @@ -600,11 +637,15 @@ async def get_health( return { "last_check": last_check, - "total_checks": len(checks), + "total_checks": total_checks, "errors": error_count, "warnings": warning_count, "ok": ok_count, "checks": checks, + "all_orgs": all_orgs, + "limit": limit, + "offset": offset, + "has_more": (offset + len(checks)) < total_checks, } diff --git a/src/static/dashboard.html b/src/static/dashboard.html index e4e7a8c..1d1448c 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -708,7 +708,7 @@ - +