Phase 6: Verwendungs-Sicht pro Grundquelle
Backend
- /api/sources/global liefert pro Quelle articles_7d, articles_30d und
tenant_excluded_count (eine aggregierte Query mit CTEs, kein N+1).
- Match-Logik fuer Articles: LOWER(articles.source) = LOWER(sources.name)
- articles.source_url ist Artikel-URL, NICHT Feed-URL, daher matcht das
nicht mit sources.url. source-Name-Match liefert sinnvolle Treffer.
- tenant_excluded_count zaehlt distinct organization_ids aus
user_excluded_domains (per LOWER(domain)-Match).
Frontend
- dashboard.html: zwei neue sortierbare Spalten Aktivitaet (7d/30d) +
Sperren in der Grundquellen-Tabelle.
- style.css: .activity-cell + .exclude-badge Styles (mit zero-Variante
fuer ruhigen Look bei keiner Aktivitaet/Sperre).
- sources.js:
- cols 9 -> 11
- Render: 7d-Wert fett, 30d-Wert dezent, Tooltip 7 Tage / 30 Tage
- Sort-Logik: NUMERIC_FIELDS um articles_7d/articles_30d/tenant_excluded_count
erweitert (numerischer Compare statt localeCompare)
Dieser Commit ist enthalten in:
@@ -138,7 +138,7 @@ function renderGlobalStats(stats) {
|
||||
|
||||
function renderGlobalSources(sources) {
|
||||
const tbody = document.getElementById("globalSourceTable");
|
||||
const cols = 9;
|
||||
const cols = 11;
|
||||
if (sources.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="${cols}" class="text-muted">Keine Grundquellen</td></tr>`;
|
||||
return;
|
||||
@@ -176,6 +176,8 @@ function renderGlobalSources(sources) {
|
||||
<td>${esc(s.domain || "-")}</td>
|
||||
<td>${typeLabel(s.source_type)}</td>
|
||||
<td class="text-right">${s.article_count || 0}</td>
|
||||
<td class="${(s.articles_30d || 0) === 0 ? "activity-cell activity-zero" : "activity-cell"}" title="7 Tage / 30 Tage"><strong>${s.articles_7d || 0}</strong> / ${s.articles_30d || 0}</td>
|
||||
<td class="text-right"><span class="${(s.tenant_excluded_count || 0) === 0 ? "exclude-badge exclude-zero" : "exclude-badge"}">${s.tenant_excluded_count || 0}</span></td>
|
||||
<td class="text-secondary">${lastSeen}</td>
|
||||
<td><span class="health-badge ${hsClass}">${hsLabel}</span></td>
|
||||
<td><span class="badge badge-${s.status === "active" ? "active" : "inactive"}">${s.status === "active" ? "Aktiv" : "Inaktiv"}</span></td>
|
||||
@@ -223,8 +225,10 @@ function filterGlobalSources() {
|
||||
filtered.sort((a, b) => {
|
||||
let va = a[globalSortField] ?? "";
|
||||
let vb = b[globalSortField] ?? "";
|
||||
if (globalSortField === "article_count") {
|
||||
va = va || 0; vb = vb || 0;
|
||||
const NUMERIC_FIELDS = ["article_count", "articles_7d", "articles_30d", "tenant_excluded_count"];
|
||||
if (NUMERIC_FIELDS.includes(globalSortField)) {
|
||||
va = parseInt(va) || 0;
|
||||
vb = parseInt(vb) || 0;
|
||||
return globalSortAsc ? va - vb : vb - va;
|
||||
}
|
||||
va = String(va).toLowerCase();
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren