diff --git a/src/routers/sources.py b/src/routers/sources.py index 4d0f5b5..dea28ed 100644 --- a/src/routers/sources.py +++ b/src/routers/sources.py @@ -72,26 +72,48 @@ async def list_global_sources( pro Zeile zeigen, ohne separate Health-Tab-Abfrage. """ cursor = await db.execute(""" + WITH article_stats AS ( + -- Match per source-Name (case-insensitive). source_url im articles ist die + -- Artikel-URL, nicht die Feed-URL - daher matcht das nicht mit sources.url. + SELECT LOWER(source) AS s_lower, + SUM(CASE WHEN collected_at > datetime('now', '-7 days') THEN 1 ELSE 0 END) AS a7d, + SUM(CASE WHEN collected_at > datetime('now', '-30 days') THEN 1 ELSE 0 END) AS a30d + FROM articles + WHERE collected_at > datetime('now', '-30 days') + AND source IS NOT NULL + GROUP BY LOWER(source) + ), + excluded_counts AS ( + SELECT LOWER(ued.domain) AS dom, + COUNT(DISTINCT u.organization_id) AS cnt + FROM user_excluded_domains ued + JOIN users u ON u.id = ued.user_id + WHERE ued.domain IS NOT NULL + GROUP BY LOWER(ued.domain) + ), + health_agg AS ( + SELECT source_id, + MAX(CASE WHEN status = 'error' THEN 3 + WHEN status = 'warning' THEN 2 + WHEN status = 'ok' THEN 1 + ELSE 0 END) AS rank + FROM source_health_checks + GROUP BY source_id + ) SELECT s.*, - COALESCE(( - SELECT CASE - WHEN MAX(CASE WHEN h.status = 'error' THEN 3 - WHEN h.status = 'warning' THEN 2 - WHEN h.status = 'ok' THEN 1 - ELSE 0 END) = 3 THEN 'error' - WHEN MAX(CASE WHEN h.status = 'error' THEN 3 - WHEN h.status = 'warning' THEN 2 - WHEN h.status = 'ok' THEN 1 - ELSE 0 END) = 2 THEN 'warning' - WHEN MAX(CASE WHEN h.status = 'error' THEN 3 - WHEN h.status = 'warning' THEN 2 - WHEN h.status = 'ok' THEN 1 - ELSE 0 END) = 1 THEN 'ok' - ELSE NULL - END - FROM source_health_checks h WHERE h.source_id = s.id - ), NULL) AS health_status + CASE ha.rank + WHEN 3 THEN 'error' + WHEN 2 THEN 'warning' + WHEN 1 THEN 'ok' + ELSE NULL + END AS health_status, + COALESCE(ast.a7d, 0) AS articles_7d, + COALESCE(ast.a30d, 0) AS articles_30d, + COALESCE(ec.cnt, 0) AS tenant_excluded_count FROM sources s + LEFT JOIN article_stats ast ON ast.s_lower = LOWER(s.name) + LEFT JOIN excluded_counts ec ON ec.dom = LOWER(s.domain) + LEFT JOIN health_agg ha ON ha.source_id = s.id WHERE s.tenant_id IS NULL ORDER BY s.category, s.source_type, s.name """) diff --git a/src/static/css/style.css b/src/static/css/style.css index c9047ac..fc102ba 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -934,3 +934,31 @@ input[type="date"].filter-select { padding: 6px 10px; } max-height: 240px; } +/* === Verwendungs-Sicht (Phase 6) === */ +.activity-cell { + font-variant-numeric: tabular-nums; + color: #94a3b8; + font-size: 12px; +} +.activity-cell strong { + color: #e2e8f0; + font-weight: 600; +} +.activity-cell.activity-zero { + color: #475569; +} +.exclude-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 600; + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} +.exclude-badge.exclude-zero { + background: transparent; + color: #475569; + font-weight: 400; +} + diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 6e6cb35..b13790f 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -328,6 +328,8 @@