feat(zugaenge): Telegram-Session-Status im Reiter Recherche-Zugaenge
Der Reiter X-Recherche-Konten heisst jetzt Recherche-Zugaenge und zeigt oben eine Telegram-Karte: Session gueltig/Problem, verbunden als, Stand. Die Daten meldet der Monitor (Commit 7dae63e) in die geteilte Tabelle system_status, das Portal liest nur an (GET /api/x-scraper/telegram-status, tolerant wenn Tabelle/Eintrag fehlt). Migration fuer die Portal-Staging-DB liegt bei. Promote-Reihenfolge: Monitor VOR Portal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -54,7 +54,8 @@ src/:
|
||||
token_usage.py: "Token-Verbrauch pro Org/Monat, Credits-Stellwerte je Lizenz (PUT /budget). Laufzeitfelder (credits_used-Reset, credits_carried, credits_period_start, budget_warning_sent) verwaltet der MONITOR, das Portal setzt nur Stellwerte"
|
||||
pricing.py: "Preis-Empfehlung aus Ist-Kosten (GET /cost-basis) und pflegbare Preistabelle billing_tariff (GET/PUT /tariff). Die Tabelle ist die Quelle der Buchung im Monitor, der Rechner kann sie speichern"
|
||||
statistik.py: "Betriebsstatistik (GET /api/statistik?months=N). Kosten/Marge je Monat und Org, Betrieb, Fehler-Ranking, MAU/DAU aus user_activity_days, Wachstum. Aggregation als pure Funktionen (unit-testbar)"
|
||||
audit.py: "Audit-Log-Abfrage, Filter"
|
||||
audit.py: "Audit-Log-Abfrage, Filter (action/resource_type/admin_id akzeptieren kommagetrennte Mehrfachwerte)"
|
||||
x_scraper.py: "Recherche-Zugänge: twscrape-X-Konten (eigener Store, X_ACCOUNTS_DB_PATH) + GET /telegram-status (liest system_status, gemeldet vom Monitor)"
|
||||
|
||||
email_utils/:
|
||||
sender.py: "Async SMTP Versand"
|
||||
@@ -70,12 +71,14 @@ src/:
|
||||
a11y.js: "Barrierefreiheits-Panel (Kontrast, Focus, Schrift, Animationen). IDENTISCHE Datei wie im Monitor-Repo, bei Aenderungen dort mitziehen"
|
||||
statistik.js: "Statistik-Tab, selbst-injizierend wie rechner.js. Inline-SVG-Charts (keine Bibliothek), Serienfarben mit dem dataviz-Validator gegen beide Themes geprüft"
|
||||
sources.js: "Vereinte Quellenliste (Grund- + Kundenquellen, Herkunfts-Filter, Health-Ausklapp mit Deaktivieren, Bulk-Promote, Modal, Discovery, PDF-Upload)"
|
||||
aufgaben.js: "Aufgaben-Posteingang (Health-Vorschläge + Klassifikations-Review + Verlauf, Badge via GET /tasks/summary)"
|
||||
aufgaben.js: "Aufgaben-Posteingang (Health-Vorschläge + Klassifikations-Review + Verlauf, Badge via GET /tasks/summary, Sammel-Bearbeitung via PUT /suggestions/bulk)"
|
||||
x-scraper.js: "Recherche-Zugänge (X-Konten-Pool + Telegram-Session-Status-Karte)"
|
||||
audit.js: "Audit-Log Tab"
|
||||
|
||||
migrations/:
|
||||
einmal_migrationen: "Backfill-Skripte (DE-Übersetzungen, Umlaute, HTML-Strip etc.)"
|
||||
2026-07-25_user_activity.py: "Tabelle user_activity_days anlegen (MAU/DAU). Nötig für die Portal-Staging-DB, auf Live legt sie der Monitor an."
|
||||
2026-07-25_system_status.py: "Tabelle system_status anlegen (Key-Value, u.a. Telegram-Session-Status). Nötig für die Portal-Staging-DB, auf Live legt sie der Monitor an. Reihenfolge: Monitor VOR Portal promoten."
|
||||
2026-07-25_billing_tariff.py: "Preistabelle billing_tariff anlegen und mit den beschlossenen Sätzen befüllen (idempotent, überschreibt nichts). Nötig für die Portal-Staging-DB, auf Live legt sie der Monitor an."
|
||||
2026-07-25_credits_period.py: "Credits-Perioden-Spalten in licenses (idempotent, identisch zur Monitor-Migration). Nötig für die Portal-Staging-DB, auf Live legt sie normalerweise der Monitor an. Reihenfolge: Monitor VOR Portal promoten."
|
||||
```
|
||||
|
||||
46
migrations/2026-07-25_system_status.py
Normale Datei
46
migrations/2026-07-25_system_status.py
Normale Datei
@@ -0,0 +1,46 @@
|
||||
"""Migration 2026-07-25: Tabelle system_status anlegen (Telegram-Session-Status).
|
||||
|
||||
Der Monitor legt die Tabelle beim Start selbst an und meldet dort u.a. den
|
||||
Status seiner Telegram-Session (Key telegram_session). Die
|
||||
Portal-Staging-Umgebung nutzt eine EIGENE Datenbank ohne laufenden Monitor,
|
||||
deshalb dieses Skript. Idempotent (CREATE TABLE IF NOT EXISTS), es werden
|
||||
keine Daten eingefügt.
|
||||
|
||||
Ausführung:
|
||||
DB_PATH=/home/claude-dev/AegisSight-Monitor-staging/data/osint.db python3 migrations/2026-07-25_system_status.py
|
||||
DB_PATH=/home/claude-dev/osint-data/osint.db python3 migrations/2026-07-25_system_status.py
|
||||
"""
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
|
||||
def main(db_path: str) -> int:
|
||||
if not os.path.exists(db_path):
|
||||
print(f"FEHLER: DB nicht gefunden: {db_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
conn = sqlite3.connect(db_path, timeout=60)
|
||||
conn.execute("PRAGMA busy_timeout = 60000")
|
||||
conn.execute("PRAGMA journal_mode = WAL")
|
||||
|
||||
print(f"Migration auf {db_path}")
|
||||
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS system_status (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
print(" + system_status angelegt (oder vorhanden)")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("Migration abgeschlossen.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
db_path = os.environ.get("DB_PATH", "/home/claude-dev/osint-data/osint.db")
|
||||
sys.exit(main(db_path))
|
||||
@@ -76,6 +76,38 @@ class XScraperActive(BaseModel):
|
||||
active: bool
|
||||
|
||||
|
||||
@router.get("/telegram-status")
|
||||
async def telegram_status(
|
||||
admin: dict = Depends(get_current_admin),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Status der Telegram-Session, gemeldet vom Monitor (system_status).
|
||||
|
||||
Der Monitor prüft beim Start und täglich um 04:30 und schreibt das
|
||||
Ergebnis in die geteilte DB. Das Portal liest nur an. Fehlt der
|
||||
Eintrag (z.B. Portal-Staging mit eigener DB-Kopie), meldet der
|
||||
Endpoint available=false statt eines Fehlers.
|
||||
"""
|
||||
import json as _json
|
||||
|
||||
cur = await db.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='system_status'"
|
||||
)
|
||||
if not await cur.fetchone():
|
||||
return {"available": False}
|
||||
cur = await db.execute(
|
||||
"SELECT value, updated_at FROM system_status WHERE key = 'telegram_session'"
|
||||
)
|
||||
row = await cur.fetchone()
|
||||
if not row:
|
||||
return {"available": False}
|
||||
try:
|
||||
data = _json.loads(row["value"]) if row["value"] else {}
|
||||
except Exception:
|
||||
data = {}
|
||||
return {"available": True, "updated_at": row["updated_at"], **data}
|
||||
|
||||
|
||||
@router.get("/accounts")
|
||||
async def list_accounts(admin: dict = Depends(get_current_admin)):
|
||||
"""Alle X-Scraper-Konten auflisten (ohne Passwoerter/Cookies)."""
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<div class="nav-tabs" id="sourceSubTabs">
|
||||
<button class="nav-tab active" data-subtab="global-sources">Quellenliste</button>
|
||||
<button class="nav-tab" data-subtab="aufgaben">Aufgaben <span class="sources-tab-badge" id="tasksPendingBadge">0</span></button>
|
||||
<button class="nav-tab" data-subtab="x-scraper">X-Recherche-Konten</button>
|
||||
<button class="nav-tab" data-subtab="x-scraper">Recherche-Zugänge</button>
|
||||
</div>
|
||||
|
||||
<!-- Vereinte Quellenliste (Grund- + Kundenquellen) -->
|
||||
@@ -355,8 +355,12 @@
|
||||
<div id="aufgabenVerlauf"></div>
|
||||
</div>
|
||||
|
||||
<!-- X-Recherche-Konten (Sub-Tab) -->
|
||||
<!-- Recherche-Zugänge (Sub-Tab): X-Konten + Telegram-Session-Status -->
|
||||
<div class="section" id="sub-x-scraper">
|
||||
<div class="card" style="margin-bottom:16px;">
|
||||
<div class="card-header"><h2>Telegram-Zugang</h2></div>
|
||||
<div class="card-body" id="telegramStatusBox">Lade…</div>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
||||
<span class="text-secondary" id="xScraperCount"></span>
|
||||
@@ -972,7 +976,7 @@
|
||||
<script src="/static/js/app.js?v=20260725v"></script>
|
||||
<script src="/static/js/sources.js?v=20260725f"></script>
|
||||
<script src="/static/js/aufgaben.js?v=20260725d"></script>
|
||||
<script src="/static/js/x-scraper.js?v=20260725a"></script>
|
||||
<script src="/static/js/x-scraper.js?v=20260725b"></script>
|
||||
<script src="/static/js/audit.js?v=20260725a"></script>
|
||||
<div id="toastContainer" class="toast-container" aria-live="polite" aria-atomic="true"></div>
|
||||
</body>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* X-Recherche-Konten: Verwaltung des twscrape-Account-Pools */
|
||||
/* Recherche-Zugänge: twscrape-Account-Pool (X) + Telegram-Session-Status */
|
||||
"use strict";
|
||||
|
||||
let xScraperCache = [];
|
||||
|
||||
async function loadXScraperAccounts() {
|
||||
setupXScraperForms();
|
||||
loadTelegramStatus();
|
||||
const tbody = document.getElementById("xScraperTable");
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-muted">Lade...</td></tr>';
|
||||
try {
|
||||
@@ -15,6 +16,32 @@ async function loadXScraperAccounts() {
|
||||
}
|
||||
}
|
||||
|
||||
// Telegram-Session-Status (vom Monitor in die geteilte DB gemeldet)
|
||||
async function loadTelegramStatus() {
|
||||
const box = document.getElementById("telegramStatusBox");
|
||||
if (!box) return;
|
||||
try {
|
||||
const st = await API.get("/api/x-scraper/telegram-status");
|
||||
if (!st.available) {
|
||||
box.innerHTML = '<span class="text-muted">Noch keine Statusmeldung vom Monitor vorhanden. '
|
||||
+ 'Der Monitor meldet den Status beim Start und täglich um 04:30 Uhr.</span>';
|
||||
return;
|
||||
}
|
||||
const stand = `<span class="text-secondary" style="margin-left:10px;font-size:12px;">Stand ${formatDateTime(st.updated_at)}</span>`;
|
||||
if (st.ok) {
|
||||
box.innerHTML = `<span class="badge badge-active">Session gültig</span> `
|
||||
+ `<span style="margin-left:6px;">Verbunden${st.account ? " als " + esc(st.account) : ""}. `
|
||||
+ `Die Telegram-Anmeldung läuft nicht periodisch ab, hier ist normalerweise nichts zu tun.</span>${stand}`;
|
||||
} else {
|
||||
box.innerHTML = `<span class="badge badge-inactive">Problem</span> `
|
||||
+ `<span style="margin-left:6px;">${esc(st.error || "Session nicht autorisiert")}. `
|
||||
+ `Die Telegram-Recherche ist pausiert, bis auf dem Server neu eingeloggt wurde.</span>${stand}`;
|
||||
}
|
||||
} catch (err) {
|
||||
box.innerHTML = '<span class="text-muted">Status konnte nicht geladen werden. ' + esc(err.message || "") + '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderXScraperAccounts(list) {
|
||||
const tbody = document.getElementById("xScraperTable");
|
||||
const cnt = document.getElementById("xScraperCount");
|
||||
|
||||
@@ -67,6 +67,8 @@ AUTH_PROTECTED = [
|
||||
("POST", "/api/sources/1/classification/reclassify"),
|
||||
("POST", "/api/sources/external-reputation/sync"),
|
||||
("POST", "/api/sources/global/upload-pdf"),
|
||||
("GET", "/api/x-scraper/accounts"),
|
||||
("GET", "/api/x-scraper/telegram-status"),
|
||||
("GET", "/api/statistik"),
|
||||
("GET", "/api/token-usage/overview"),
|
||||
("GET", "/api/token-usage/1"),
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren