feat(sources): X-Account-Verwaltung im Verwaltungsportal

Neuer Sub-Tab "X-Accounts" unter Quellen: die als Recherchequelle
eingebundenen X-Accounts anzeigen, hinzufuegen, bearbeiten und entfernen.
Schreibt source_type=x_account in die geteilte sources-Tabelle, von wo
der Monitor sie pro Lage nutzt.

- x_account im source_type-Pattern von GlobalSourceCreate/Update
- primary_language in Create/Update plus INSERT (Keyword-Matching)
- x_account-Typ und x-Kategorie in source_meta.py
- Sub-Tab, Tabelle und Modal in dashboard.html, Logik in sources.js

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-05-22 11:06:07 +00:00
Ursprung ed38d68db7
Commit bd476edb13
4 geänderte Dateien mit 235 neuen und 7 gelöschten Zeilen

Datei anzeigen

@@ -329,6 +329,7 @@
<button class="nav-tab" data-subtab="tenant-sources">Kundenquellen</button>
<button class="nav-tab" data-subtab="source-health">Quellen-Health</button>
<button class="nav-tab" data-subtab="classification-review">Klassifikation <span class="sources-tab-badge" id="classificationPendingBadge">0</span></button>
<button class="nav-tab" data-subtab="x-accounts">X-Accounts</button>
</div>
<!-- Grundquellen -->
@@ -471,6 +472,36 @@
</div>
</div>
<!-- X-Accounts (Sub-Tab) -->
<div class="section" id="sub-x-accounts">
<div class="action-bar">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<input type="text" class="search-input" id="xAccountSearch" placeholder="X-Account suchen..." oninput="filterXAccounts()">
<span class="text-secondary" id="xAccountCount"></span>
</div>
<button class="btn btn-primary" id="newXAccountBtn" onclick="openXAccountModal()">+ X-Account hinzufügen</button>
</div>
<div class="card">
<p class="text-secondary" style="padding:0 4px 12px;">X-Accounts (Twitter), die der Monitor als Recherchequelle nutzt. Pro Lage über die Option „X (Twitter) einbeziehen" zuschaltbar.</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Account</th>
<th>URL</th>
<th>Sprache</th>
<th>Notiz</th>
<th>Artikel</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody id="xAccountTable"></tbody>
</table>
</div>
</div>
</div>
</div> <!-- /sec-sources -->
<!-- Audit-Log Section -->
@@ -938,8 +969,59 @@
</div>
</div>
<!-- Modal: X-Account -->
<div class="modal-overlay" id="modalXAccount">
<div class="modal">
<div class="modal-header">
<h3 id="xAccountModalTitle">X-Account hinzufügen</h3>
<button class="modal-close" onclick="closeModal('modalXAccount')">&times;</button>
</div>
<form id="xAccountForm">
<div class="modal-body">
<div class="form-group">
<label for="xAccountHandle">X-Handle oder URL</label>
<input type="text" id="xAccountHandle" required placeholder="z.B. bellingcat oder https://x.com/bellingcat">
<small class="text-secondary">Account-Name mit oder ohne @, oder die volle x.com-URL.</small>
</div>
<div class="form-group">
<label for="xAccountName">Anzeigename</label>
<input type="text" id="xAccountName" placeholder="optional, z.B. Bellingcat">
</div>
<div class="form-group">
<label for="xAccountLanguage">Sprache</label>
<select id="xAccountLanguage">
<option value="en">Englisch</option>
<option value="de">Deutsch</option>
<option value="fr">Französisch</option>
<option value="ru">Russisch</option>
<option value="uk">Ukrainisch</option>
<option value="">Unbestimmt</option>
</select>
<small class="text-secondary">Steuert das Keyword-Matching im Monitor.</small>
</div>
<div class="form-group">
<label for="xAccountNotes">Notiz</label>
<textarea id="xAccountNotes" rows="2" placeholder="Kurzbeschreibung, hilft der KI bei der Account-Auswahl pro Lage"></textarea>
</div>
<div class="form-group">
<label for="xAccountStatus">Status</label>
<select id="xAccountStatus">
<option value="active">Aktiv</option>
<option value="inactive">Inaktiv</option>
</select>
</div>
<div id="xAccountError" class="error-msg" style="display:none"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('modalXAccount')">Abbrechen</button>
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</form>
</div>
</div>
<script src="/static/js/app.js?v=20260522a"></script>
<script src="/static/js/sources.js?v=20260509d"></script>
<script src="/static/js/sources.js?v=20260522x"></script>
<script src="/static/js/source-health.js?v=20260509l"></script>
<script src="/static/js/audit.js?v=20260509d"></script>
<div id="toastContainer" class="toast-container" aria-live="polite" aria-atomic="true"></div>