Neue Kategorie russische-opposition in Verwaltungs-UI + 10 neue Telegram-Kanaele (Wave 3)

Dieser Commit ist enthalten in:
Claude Dev
2026-03-13 19:08:44 +01:00
Ursprung 29f3e73480
Commit 1d9de549ec
7 geänderte Dateien mit 975 neuen und 850 gelöschten Zeilen

Datei anzeigen

@@ -4,7 +4,7 @@
let globalSourcesCache = [];
let tenantSourcesCache = [];
let editingSourceId = null;
let globalSortField = "name";
let globalSortField = "category";
let globalSortAsc = true;
const CATEGORY_LABELS = {
@@ -18,11 +18,21 @@ const CATEGORY_LABELS = {
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",
excluded: "Ausgeschlossen",
};
@@ -65,25 +75,49 @@ async function loadGlobalSources() {
function renderGlobalSources(sources) {
const tbody = document.getElementById("globalSourceTable");
const cols = 7;
if (sources.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="text-muted">Keine Grundquellen</td></tr>';
tbody.innerHTML = `<tr><td colspan="${cols}" class="text-muted">Keine Grundquellen</td></tr>`;
return;
}
tbody.innerHTML = sources.map((s) => `
<tr>
<td>${esc(s.name)}</td>
<td class="text-secondary" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(s.url || '')}">${esc(s.url || "-")}</td>
<td>${esc(s.domain || "-")}</td>
<td>${TYPE_LABELS[s.source_type] || s.source_type}</td>
<td>${CATEGORY_LABELS[s.category] || s.category}</td>
<td class="text-right">${s.article_count || 0}</td>
<td><span class="badge badge-${s.status === "active" ? "active" : "inactive"}">${s.status === "active" ? "Aktiv" : "Inaktiv"}</span></td>
<td>
<button class="btn btn-secondary btn-small" onclick="editGlobalSource(${s.id})">Bearbeiten</button>
<button class="btn btn-danger btn-small" onclick="confirmDeleteGlobalSource(${s.id}, '${esc(s.name)}')">Löschen</button>
</td>
</tr>
`).join("");
// Nach Kategorie gruppieren (Reihenfolge beibehalten)
const grouped = {};
const order = [];
sources.forEach((s) => {
const cat = s.category || "sonstige";
if (!grouped[cat]) { grouped[cat] = []; order.push(cat); }
grouped[cat].push(s);
});
let html = "";
order.forEach((cat) => {
const label = CATEGORY_LABELS[cat] || cat;
const count = grouped[cat].length;
html += `<tr class="cat-header-row"><td colspan="${cols}"><span class="cat-header-label">${esc(label)}</span><span class="cat-header-count">${count}</span></td></tr>`;
grouped[cat].forEach((s) => {
const hasNotes = s.notes && s.notes.trim();
const infoBtn = hasNotes
? `<span class="src-info-toggle" onclick="toggleSourceInfo(${s.id})" title="Info einblenden">&#9432;</span>`
: '';
const notesRow = hasNotes
? `<tr class="src-notes-row" id="notes-${s.id}" style="display:none;"><td colspan="${cols}" class="src-notes-cell">${esc(s.notes)}</td></tr>`
: '';
html += `<tr>
<td>${infoBtn} ${esc(s.name)}</td>
<td class="text-secondary" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(s.url || '')}">${esc(s.url || "-")}</td>
<td>${esc(s.domain || "-")}</td>
<td>${TYPE_LABELS[s.source_type] || s.source_type}</td>
<td class="text-right">${s.article_count || 0}</td>
<td><span class="badge badge-${s.status === "active" ? "active" : "inactive"}">${s.status === "active" ? "Aktiv" : "Inaktiv"}</span></td>
<td>
<button class="btn btn-secondary btn-small" onclick="editGlobalSource(${s.id})">Bearbeiten</button>
<button class="btn btn-danger btn-small" onclick="confirmDeleteGlobalSource(${s.id}, '${esc(s.name)}')">Löschen</button>
</td>
</tr>${notesRow}`;
});
});
tbody.innerHTML = html;
document.getElementById("globalSourceCount").textContent = `${sources.length} Grundquellen`;
@@ -384,3 +418,15 @@ async function addDiscoveredFeeds() {
btn.textContent = "Ausgewählte hinzufügen";
}
}
function toggleSourceInfo(id) {
const row = document.getElementById("notes-" + id);
if (!row) return;
const isVisible = row.style.display !== "none";
row.style.display = isVisible ? "none" : "table-row";
const mainRow = row.previousElementSibling;
if (mainRow) {
const btn = mainRow.querySelector(".src-info-toggle");
if (btn) btn.classList.toggle("active", !isVisible);
}
}