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:
@@ -833,3 +833,48 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// === Source-Meta (Kategorien + Typen aus dem Backend) ===
|
||||
window.META = { categories: [], types: [] };
|
||||
window.CATEGORY_LABELS = {};
|
||||
window.TYPE_LABELS = {};
|
||||
|
||||
async function loadMeta() {
|
||||
try {
|
||||
const data = await API.get("/api/sources/meta");
|
||||
window.META = data;
|
||||
window.CATEGORY_LABELS = Object.fromEntries((data.categories || []).map(c => [c.key, c.label]));
|
||||
window.TYPE_LABELS = Object.fromEntries((data.types || []).map(t => [t.key, t.label]));
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.warn("loadMeta:", err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function categoryLabel(key) {
|
||||
return window.CATEGORY_LABELS[key] || key || "";
|
||||
}
|
||||
function typeLabel(key) {
|
||||
return window.TYPE_LABELS[key] || key || "";
|
||||
}
|
||||
|
||||
function populateSelect(el, items, allLabel) {
|
||||
if (!el) return;
|
||||
const current = el.value;
|
||||
el.innerHTML = '<option value="">' + (allLabel || "Alle") + '</option>';
|
||||
items.forEach(it => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = it.key;
|
||||
opt.textContent = it.label;
|
||||
el.appendChild(opt);
|
||||
});
|
||||
if (current && items.some(it => it.key === current)) el.value = current;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Beim Page-Load Meta einmalig laden (asynchron, blockiert nicht)
|
||||
if (window.API && (localStorage.getItem("token") || window.location.pathname === "/dashboard")) {
|
||||
loadMeta();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren