From bff934d673ea813b28c57fc3034edacb1f5e9069 Mon Sep 17 00:00:00 2001 From: claude-dev Date: Sat, 9 May 2026 04:47:05 +0000 Subject: [PATCH] Phase 17: Health-Tab Filter + Org-Spalte + History-View + URL-Schema-Fix Backend: - shared/services/source_health.py: URL ohne https://-Prefix wird normalisiert bevor httpx.get() aufgerufen wird (Bug-Fix: t.me/kanal liess httpx mit ValueError crashen, Synchron mit Monitor-Fix 1ee6c4d). - routers/sources.py /health: Query erweitert um tenant_id, category, language, bias, org_name (LEFT JOIN organizations) - Frontend kann jetzt pro Issue Tenant-Info anzeigen. - routers/sources.py /health/history NEU: letzte N Runs aus source_health_history aggregiert (run_id, archived_at, errors/warnings/ok). Frontend (source-health.js): - healthFilters State: status / check_type / org. - applyHealthFilter() reduziert die Anzeige. - Filter-Bar mit 3 Dropdowns + Counter "X / Y Ergebnisse". - Tabelle erweitert: Org-Spalte ("global" oder Org-Name), Sprache-Spalte. - History-View neu: letzte 10 Runs als Tabelle (Zeitpunkt, Run-ID, Counts). Cache-Buster auf 20260509c gebumpt. --- src/routers/sources.py | 37 +++++++++++ src/shared/services/source_health.py | 4 ++ src/static/dashboard.html | 10 +-- src/static/index.html | 2 +- src/static/js/source-health.js | 99 +++++++++++++++++++++++++--- 5 files changed, 136 insertions(+), 16 deletions(-) diff --git a/src/routers/sources.py b/src/routers/sources.py index 276e953..5a46c6b 100644 --- a/src/routers/sources.py +++ b/src/routers/sources.py @@ -578,9 +578,12 @@ async def get_health( cursor = await db.execute(""" SELECT h.id, h.source_id, s.name, s.domain, s.url, s.source_type, + s.tenant_id, s.category, s.language, s.bias, + o.name AS org_name, h.check_type, h.status, h.message, h.details, h.checked_at FROM source_health_checks h JOIN sources s ON s.id = h.source_id + LEFT JOIN organizations o ON o.id = s.tenant_id ORDER BY CASE h.status WHEN 'error' THEN 0 WHEN 'warning' THEN 1 ELSE 2 END, s.name @@ -605,6 +608,40 @@ async def get_health( } + + + +@router.get("/health/history") +async def get_health_history( + limit: int = 20, + admin: dict = Depends(get_current_admin), + db: aiosqlite.Connection = Depends(db_dependency), +): + """Liefert die letzten N Health-Check-Runs aus source_health_history. + + Pro Run: run_id, archived_at (Run-Zeitpunkt), Counts pro Status. + """ + cursor = await db.execute(""" + SELECT name FROM sqlite_master WHERE type='table' AND name='source_health_history' + """) + if not await cursor.fetchone(): + return [] + + cursor = await db.execute(""" + SELECT run_id, + MIN(archived_at) AS archived_at, + COUNT(*) AS total, + SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS errors, + SUM(CASE WHEN status = 'warning' THEN 1 ELSE 0 END) AS warnings, + SUM(CASE WHEN status = 'ok' THEN 1 ELSE 0 END) AS ok + FROM source_health_history + GROUP BY run_id + ORDER BY archived_at DESC + LIMIT ? + """, (max(1, min(limit, 100)),)) + return [dict(row) for row in await cursor.fetchall()] + + @router.get("/suggestions") async def get_suggestions( admin: dict = Depends(get_current_admin), diff --git a/src/shared/services/source_health.py b/src/shared/services/source_health.py index e6b1cdd..9837cda 100644 --- a/src/shared/services/source_health.py +++ b/src/shared/services/source_health.py @@ -112,6 +112,10 @@ async def _check_source_reachability( checks = [] url = source["url"] + # URL-Schema sicherstellen: t.me-Kanaele und andere Domains koennen ohne https:// vorkommen + if url and not url.startswith(("http://", "https://")): + url = "https://" + url.lstrip("/") + try: resp = await client.get(url) diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 7a1a9e7..6bab51b 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -6,7 +6,7 @@ AegisSight Monitor-Verwaltung - +