Phase 3b: Kategorien/Typen aus Backend (/api/sources/meta)

- src/source_meta.py NEU: SOURCE_CATEGORIES + SOURCE_TYPES als
  Single Source of Truth (Liste mit {key, label}). category_label/type_label
  Lookup-Funktionen, get_meta() liefert das gesamte Set.
- src/routers/sources.py: GET /api/sources/meta ergänzt (admin-auth,
  liefert Kategorien + Typen)
- src/static/js/app.js: window.META + loadMeta() + categoryLabel/typeLabel +
  populateSelect Helper. Beim DOMContentLoaded wird Meta geladen, befüllt
  globale CATEGORY_LABELS und TYPE_LABELS.
- src/static/js/sources.js: hardcoded const CATEGORY_LABELS und TYPE_LABELS
  entfernt - werden jetzt aus app.js loadMeta() global gesetzt.
  loadGlobalSources() ruft populateSelect() für die Filter-Dropdowns auf.
- src/static/js/source-health.js: gleiche hardcoded Listen entfernt.
- src/static/dashboard.html: <option>-Listen für globalFilterCategory und
  globalFilterType entfernt (nur noch default Alle). JS befüllt sie dynamisch.

Ergebnis: Bei einer neuen Kategorie nur source_meta.py anpassen,
keine 3-fach-Pflege mehr in HTML+sources.js+source-health.js.
Dieser Commit ist enthalten in:
claude-dev
2026-05-09 03:05:16 +00:00
Ursprung 5a87168416
Commit eda60f9299
5 geänderte Dateien mit 138 neuen und 53 gelöschten Zeilen

Datei anzeigen

@@ -7,36 +7,8 @@ let editingSourceId = null;
let globalSortField = "category";
let globalSortAsc = true;
const CATEGORY_LABELS = {
nachrichtenagentur: "Nachrichtenagentur",
"oeffentlich-rechtlich": "Öffentlich-Rechtlich",
qualitaetszeitung: "Qualitätszeitung",
behoerde: "Behörde",
fachmedien: "Fachmedien",
"think-tank": "Think-Tank",
international: "International",
regional: "Regional",
boulevard: "Boulevard",
sonstige: "Sonstige",
"cybercrime": "Cybercrime / Hacktivismus",
"cybercrime-leaks": "Cybercrime / Leaks",
"ukraine-russland-krieg": "Ukraine-Russland-Krieg",
"irankonflikt": "Irankonflikt",
"osint-international": "OSINT International",
"extremismus-deutschland": "Extremismus Deutschland",
"russische-staatspropaganda": "Russische Staatspropaganda",
"russische-opposition": "Russische Opposition / Exilmedien",
"syrien-nahost": "Syrien / Nahost",
};
const TYPE_LABELS = {
rss_feed: "RSS-Feed",
web_source: "Webquelle",
telegram_channel: "Telegram-Kanal",
podcast_feed: "Podcast-Feed",
excluded: "Ausgeschlossen",
};
// CATEGORY_LABELS jetzt global (aus app.js loadMeta)
// TYPE_LABELS jetzt global (aus app.js loadMeta)
// --- Init ---
document.addEventListener("DOMContentLoaded", () => {
setupSourceSubTabs();
@@ -67,6 +39,12 @@ function setupSourceSubTabs() {
// --- Grundquellen ---
async function loadGlobalSources() {
try {
// Kategorien/Typen-Dropdowns aus META befüllen (idempotent)
if (window.META && window.META.categories && window.META.categories.length) {
populateSelect(document.getElementById("globalFilterCategory"), window.META.categories, "Alle Kategorien");
populateSelect(document.getElementById("globalFilterType"),
(window.META.types || []).filter(t => t.key !== "excluded"), "Alle Typen");
}
globalSourcesCache = await API.get("/api/sources/global");
renderGlobalSources(globalSourcesCache);
} catch (err) {