Die Statistik-Kacheln springen jetzt zu ihren Zielen. Organisationen und Nutzer zur Organisations-Verwaltung, Aktive Lizenzen zum Lizenzen-Tab, Vorfälle öffnen den Monitor in einem neuen Tab (im Portal gibt es keine Vorfalls-Seite). Die Karte "Bald ablaufende Lizenzen" führt zum Lizenzen-Tab, "Letzte Aktivität" zum Audit-Log. Hover-Optik mit Gold-Rahmen und Chevron, bedienbar auch per Tastatur (Enter/Leertaste). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1252 Zeilen
65 KiB
HTML
1252 Zeilen
65 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AegisSight Monitor-Verwaltung</title>
|
|
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
|
<link rel="apple-touch-icon" href="/static/favicon.svg">
|
|
<link rel="stylesheet" href="/static/css/style.css?v=20260725b">
|
|
<script>(function(){var t=localStorage.getItem('portal_theme');if(t==='light')document.documentElement.setAttribute('data-theme','light');try{var a=JSON.parse(localStorage.getItem('osint_a11y')||'{}');Object.keys(a).forEach(function(k){if(a[k])document.documentElement.setAttribute('data-a11y-'+k,'true');});}catch(e){}})()</script>
|
|
|
|
<style>
|
|
.source-badge { display:inline-block; padding:2px 8px; border-radius:4px; font-size:12px; font-weight:600; }
|
|
.badge-monitor { background:rgba(59,130,246,0.15); color:#93c5fd; }
|
|
.badge-globe { background:rgba(16,185,129,0.15); color:#86efac; }
|
|
[data-theme="light"] .badge-monitor { background:#e3f2fd; color:#1565c0; }
|
|
[data-theme="light"] .badge-globe { background:#e8f5e9; color:#2e7d32; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Header -->
|
|
<header class="app-header">
|
|
<div class="logo">
|
|
<span class="logo-plaque"><img src="/static/favicon.svg" alt="AegisSight Logo"></span>
|
|
AegisSight <span>Monitor-Verwaltung</span>
|
|
</div>
|
|
<div class="header-right">
|
|
<div class="theme-switch dark" id="theme-toggle" onclick="ThemeManager.toggle()" role="switch" aria-checked="true" aria-label="Dark Mode" title="Theme wechseln">
|
|
<span class="theme-switch-icon theme-switch-sun">☀︎</span>
|
|
<div class="theme-switch-track">
|
|
<div class="theme-switch-knob"></div>
|
|
</div>
|
|
<span class="theme-switch-icon theme-switch-moon">☽</span>
|
|
</div>
|
|
<span class="header-user" id="headerUser"></span>
|
|
<button class="btn btn-secondary btn-small" id="logoutBtn">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/></svg>
|
|
Abmelden
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="app-content">
|
|
<!-- Navigation (Lucide-Icons, Strichstaerke 2 bei 15px) -->
|
|
<nav class="nav-tabs">
|
|
<button class="nav-tab active" data-section="dashboard">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
|
|
Dashboard
|
|
</button>
|
|
<button class="nav-tab" data-section="orgs">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"/><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"/><path d="M10 6h4"/><path d="M10 10h4"/><path d="M10 14h4"/><path d="M10 18h4"/></svg>
|
|
Organisationen
|
|
</button>
|
|
<button class="nav-tab" data-section="licenses">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 18v3c0 .6.4 1 1 1h4v-3h3v-3h2l1.4-1.4a6.5 6.5 0 1 0-4-4Z"/><circle cx="16.5" cy="7.5" r=".5"/></svg>
|
|
Lizenzen
|
|
</button>
|
|
<button class="nav-tab" data-section="sources">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/></svg>
|
|
Quellen
|
|
</button>
|
|
<button class="nav-tab" data-section="audit">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>
|
|
Audit-Log
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Dashboard Section -->
|
|
<div class="section active" id="sec-dashboard">
|
|
<div class="stats-grid" id="statsGrid"></div>
|
|
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
|
<div class="card card-link" onclick="gotoSection('licenses')" role="link" tabindex="0" title="Zu den Lizenzen">
|
|
<div class="card-header">
|
|
<h2>Bald ablaufende Lizenzen</h2>
|
|
<svg class="card-link-chevron" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
<div class="card-body">
|
|
<ul class="expiring-list" id="expiringList">
|
|
<li class="text-muted" style="padding: 8px 0;">Laden...</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card card-link" onclick="gotoSection('audit')" role="link" tabindex="0" title="Zum Audit-Log">
|
|
<div class="card-header">
|
|
<h2>Letzte Aktivität</h2>
|
|
<svg class="card-link-chevron" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
<div class="card-body" id="recentActivity">
|
|
<div class="text-muted">Laden...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Artikel-Übersetzung -->
|
|
<div class="card" id="translationCard" style="margin-top:16px;">
|
|
<div class="card-header">
|
|
<h2>Artikel-Übersetzung</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted" style="margin-top:0;">
|
|
Die automatische Übersetzung im Monitor ist deaktiviert. Hier lassen sich
|
|
fremdsprachige Artikel ohne deutsche Fassung manuell übersetzen.
|
|
</p>
|
|
<p id="translationInfo" style="margin:12px 0;">Status wird geladen…</p>
|
|
|
|
<div id="translationProgressWrap" style="display:none; margin:12px 0;">
|
|
<div style="background:rgba(128,128,128,0.25); border-radius:6px; height:14px; overflow:hidden;">
|
|
<div id="translationProgressBar" style="background:#1565c0; height:100%; width:0%; transition:width .3s;"></div>
|
|
</div>
|
|
<p class="text-muted" id="translationProgressText" style="margin:6px 0 0;"></p>
|
|
</div>
|
|
|
|
<div style="margin-top:12px; display:flex; gap:8px;">
|
|
<button class="btn btn-primary" id="translationRunBtn">Übersetzung starten</button>
|
|
<button class="btn btn-danger" id="translationCancelBtn" style="display:none;">Abbrechen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Organizations Section -->
|
|
<div class="section" id="sec-orgs">
|
|
<!-- Org List -->
|
|
<div id="orgListView">
|
|
<div class="action-bar">
|
|
<input type="text" class="search-input" id="orgSearch" placeholder="Organisation suchen...">
|
|
<button class="btn btn-primary" id="newOrgBtn"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>Neue Organisation</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Slug</th>
|
|
<th>Nutzer</th>
|
|
<th>Lizenz</th>
|
|
<th>Status</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="orgTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Org Detail -->
|
|
<div class="detail-panel" id="orgDetail">
|
|
<button class="detail-back" id="orgBackBtn">← Zurück</button>
|
|
<div id="orgDetailHeader"></div>
|
|
|
|
<div class="nav-tabs mt-16" id="orgDetailTabs">
|
|
<button class="nav-tab active" data-subtab="users">Nutzer</button>
|
|
<button class="nav-tab" data-subtab="org-licenses">Lizenzen</button>
|
|
<button class="nav-tab" data-subtab="settings">Einstellungen</button>
|
|
<button class="nav-tab" data-subtab="tokens">Verbrauch</button>
|
|
</div>
|
|
|
|
<!-- Users Sub-Tab -->
|
|
<div class="section active" id="sub-users">
|
|
<div class="action-bar">
|
|
<span class="text-secondary" id="userLimitInfo"></span>
|
|
<button class="btn btn-primary" id="newUserBtn"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>Nutzer anlegen</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>E-Mail</th>
|
|
<th>Rolle</th>
|
|
<th>Status</th>
|
|
<th>Globe</th>
|
|
<th>Netzwerk</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="userTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Licenses Sub-Tab -->
|
|
<div class="section" id="sub-org-licenses">
|
|
<div class="action-bar">
|
|
<span></span>
|
|
<button class="btn btn-primary" id="newLicenseBtn"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>Neue Lizenz</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Typ</th>
|
|
<th>Max Nutzer</th>
|
|
<th>Gültig ab</th>
|
|
<th>Gültig bis</th>
|
|
<th>Status</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="licenseTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Sub-Tab -->
|
|
<div class="section" id="sub-settings">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<form id="orgEditForm">
|
|
<div class="form-group">
|
|
<label for="editOrgName">Name</label>
|
|
<input type="text" id="editOrgName">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Status</label>
|
|
<select id="editOrgActive">
|
|
<option value="true">Aktiv</option>
|
|
<option value="false">Deaktiviert</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editOrgLanguage">Pipeline-Sprache</label>
|
|
<select id="editOrgLanguage">
|
|
<option value="de">Deutsch</option>
|
|
<option value="en">English</option>
|
|
</select>
|
|
<small class="text-secondary">Bestimmt die Ausgabesprache der KI (Lagebild, Faktencheck, Recherche) und der sichtbarsten UI-Elemente fuer alle Nutzer dieser Organisation.</small>
|
|
</div>
|
|
<div style="display: flex; gap: 8px; margin-top: 16px;">
|
|
<button type="submit" class="btn btn-primary">Speichern</button>
|
|
<button type="button" class="btn btn-danger" id="deleteOrgBtn">Organisation löschen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="section" id="sub-tokens">
|
|
<div class="token-overview">
|
|
<!-- Kundensicht: das, was der Kunde im Monitor auch sieht -->
|
|
<h3 style="margin-bottom:8px;">Kundensicht (Credits)</h3>
|
|
<div class="token-stats-row" id="tokenStatsRow">
|
|
<div class="token-stat-card">
|
|
<div class="token-stat-label">Verbrauchte Credits (Periode)</div>
|
|
<div class="token-stat-value" id="tokenCreditsUsed">-</div>
|
|
</div>
|
|
<div class="token-stat-card">
|
|
<div class="token-stat-label">Verbleibend</div>
|
|
<div class="token-stat-value" id="tokenCreditsRemaining">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="token-budget-bar-section">
|
|
<div class="token-budget-label">
|
|
<span>Kontingent-Auslastung (laufende Periode)</span>
|
|
<span id="tokenBudgetPercent">0%</span>
|
|
</div>
|
|
<div class="token-budget-bar-container">
|
|
<div class="token-budget-bar" id="tokenBudgetBar"></div>
|
|
</div>
|
|
</div>
|
|
<p style="margin-top:10px; font-size:12px; color:var(--text-muted); display:flex; align-items:center; gap:6px;">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
|
|
<span>Zur Einordnung. Ein Live-Lauf kostet 45 Credits, das Anlegen einer Recherche 120 (drei Durchläufe), jede Vertiefung 40, Chat und Beschreibungs-Assistent je 1 Credit.
|
|
<a href="#" onclick="openRechnerTab(); return false;" style="color:var(--accent);">Verbrauchsrechner öffnen</a></span>
|
|
</p>
|
|
|
|
<!-- Interne Sicht: was uns der Betrieb im Einkauf kostet -->
|
|
<h3 style="margin-top:24px; margin-bottom:8px;">Interne Kosten (Einkauf, USD)</h3>
|
|
<div class="token-stats-row">
|
|
<div class="token-stat-card">
|
|
<div class="token-stat-label">API-Kosten aktueller Monat</div>
|
|
<div class="token-stat-value" id="tokenCostUsd">-</div>
|
|
</div>
|
|
<div class="token-stat-card">
|
|
<div class="token-stat-label">Budget-Limit (USD, Altmodus)</div>
|
|
<div class="token-stat-value" id="tokenBudgetUsd">-</div>
|
|
</div>
|
|
</div>
|
|
<div id="tokenSourceSplit" style="margin-top:12px;font-size:14px;"></div>
|
|
<h3 style="margin-top:24px;">Monatlicher Verlauf</h3>
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Monat</th>
|
|
<th>Quelle</th>
|
|
<th>Refreshes</th>
|
|
<th>API-Calls</th>
|
|
<th>Input-Tokens</th>
|
|
<th>Output-Tokens</th>
|
|
<th>Kosten (USD)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tokenMonthlyTable"></tbody>
|
|
</table>
|
|
|
|
<h3 style="margin-top:24px;">Credits-Kontingent bearbeiten</h3>
|
|
<div class="card" style="margin-top:12px;">
|
|
<div class="card-body">
|
|
<form id="tokenBudgetForm" style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
|
<div style="grid-column:1/-1; padding:8px 12px; background:var(--bg-tertiary); border-radius:6px; border:1px solid var(--border);">
|
|
<label style="display:flex; align-items:center; gap:8px; cursor:pointer; margin:0;">
|
|
<input type="checkbox" id="editUnlimitedBudget" onchange="onUnlimitedToggle()">
|
|
<span><strong>Unbegrenztes Budget</strong> — Verbrauch wird trotzdem getrackt, aber kein Limit/Hard-Stop</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editCreditsTotal">Credits-Kontingent</label>
|
|
<input type="number" id="editCreditsTotal" placeholder="z.B. 10000">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editCreditsPeriod">Abrechnungszeitraum</label>
|
|
<select id="editCreditsPeriod" onchange="onEditPeriodChange()">
|
|
<option value="monthly">Monatlich (füllt sich zum Monatswechsel neu)</option>
|
|
<option value="total">Gesamtlaufzeit</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="display:flex; align-items:flex-end;">
|
|
<label style="display:flex; align-items:center; gap:8px; cursor:pointer; margin:0;">
|
|
<input type="checkbox" id="editCreditsRollover">
|
|
<span>Übertrag ungenutzter Credits in den Folgemonat</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editWarnPercent">Warnschwelle (%)</label>
|
|
<input type="number" id="editWarnPercent" min="1" max="100" placeholder="80">
|
|
</div>
|
|
<details class="adv-section">
|
|
<summary>
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
|
|
Erweitert (Altmodus und Korrektur)
|
|
</summary>
|
|
<p class="adv-hint">
|
|
Die beiden USD-Felder gehören zum alten Abrechnungsmodus (BILLING_MODE=actual)
|
|
und haben im heutigen Pauschalmodus keine Wirkung auf den Kunden. Das
|
|
Korrektur-Feld überschreibt den Verbrauch der laufenden Periode direkt,
|
|
nur für bewusste Eingriffe gedacht.
|
|
</p>
|
|
<div class="adv-grid" style="margin-top:10px;">
|
|
<div class="form-group">
|
|
<label for="editCostPerCredit">Kosten pro Credit (USD, nur Altmodus)</label>
|
|
<input type="number" id="editCostPerCredit" step="0.01" placeholder="0.20">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editBudgetUsd">Budget-Limit (USD, nur Altmodus)</label>
|
|
<input type="number" id="editBudgetUsd" step="0.01" placeholder="z.B. 2000">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editCreditsUsed">Verbrauch korrigieren (Credits, laufende Periode)</label>
|
|
<input type="number" id="editCreditsUsed" step="0.01" placeholder="Aktueller Verbrauch">
|
|
</div>
|
|
</div>
|
|
</details>
|
|
<div style="grid-column:1/-1; display:flex; gap:8px; align-items:center;">
|
|
<button type="submit" class="btn btn-primary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
|
Speichern
|
|
</button>
|
|
<span id="tokenBudgetMsg" style="font-size:13px; color:var(--text-secondary);"></span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 style="margin-top:24px;">Laufzeitwerte</h3>
|
|
<div class="card" style="margin-top:12px;">
|
|
<div class="card-body">
|
|
<p class="text-secondary" style="margin:0 0 12px; font-size:13px;">
|
|
Diese Werte verwaltet der Monitor selbst, sie sind hier nicht änderbar.
|
|
Stand ist die letzte Lizenzprüfung des Monitors, nach einem Monatswechsel
|
|
können sie kurzzeitig noch die Vorperiode zeigen.
|
|
</p>
|
|
<div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px;">
|
|
<div class="form-group">
|
|
<label>Übertrag aus dem Vormonat</label>
|
|
<div id="viewCreditsCarried" style="font-size:16px; font-weight:600;">-</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Laufende Periode</label>
|
|
<div id="viewCreditsPeriodStart" style="font-size:16px; font-weight:600;">-</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Verfügbar gesamt (Kontingent + Übertrag)</label>
|
|
<div id="viewCreditsAvailable" style="font-size:16px; font-weight:600;">-</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Licenses Section (global overview) -->
|
|
<div class="section" id="sec-licenses">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>Bald ablaufende Lizenzen</h2>
|
|
<select id="expiringDays" style="background: var(--bg-primary); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text-primary); padding: 4px 8px; font-size: 13px;">
|
|
<option value="14">14 Tage</option>
|
|
<option value="30" selected>30 Tage</option>
|
|
<option value="90">90 Tage</option>
|
|
</select>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Organisation</th>
|
|
<th>Typ</th>
|
|
<th>Max Nutzer</th>
|
|
<th>Läuft ab</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="expiringTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sources Section -->
|
|
<div class="section" id="sec-sources">
|
|
<div class="nav-tabs" id="sourceSubTabs">
|
|
<button class="nav-tab active" data-subtab="global-sources">Grundquellen</button>
|
|
<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-scraper">X-Recherche-Konten</button>
|
|
</div>
|
|
|
|
<!-- Grundquellen -->
|
|
<div class="section active" id="sub-global-sources">
|
|
<div class="sources-stats-bar" id="globalStatsBar"></div>
|
|
<div class="action-bar">
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
<input type="text" class="search-input" id="globalSourceSearch" placeholder="Grundquelle suchen...">
|
|
<select class="filter-select" id="globalFilterType" onchange="filterGlobalSources()">
|
|
<option value="">Alle Typen</option>
|
|
</select>
|
|
<select class="filter-select" id="globalFilterCategory" onchange="filterGlobalSources()">
|
|
<option value="">Alle Kategorien</option>
|
|
</select>
|
|
<select class="filter-select" id="globalFilterStatus" onchange="filterGlobalSources()">
|
|
<option value="">Alle Status</option>
|
|
<option value="active">Aktiv</option>
|
|
<option value="inactive">Inaktiv</option>
|
|
</select>
|
|
<select class="filter-select" id="globalFilterLanguage" onchange="filterGlobalSources()">
|
|
<option value="">Alle Sprachen</option>
|
|
</select>
|
|
<span class="text-secondary" id="globalSourceCount"></span>
|
|
</div>
|
|
<button class="btn btn-secondary" id="discoverSourceBtn">Erkennen</button>
|
|
<button class="btn btn-secondary" id="newPdfSourceBtn" style="margin-right:8px;">+ PDF hochladen</button>
|
|
<button class="btn btn-primary" id="newGlobalSourceBtn">+ Neue Grundquelle</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th class="sortable" data-sort="name" onclick="sortGlobalSources('name')">Name <span class="sort-icon"></span></th>
|
|
<th>URL</th>
|
|
<th class="sortable" data-sort="domain" onclick="sortGlobalSources('domain')">Domain <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="source_type" onclick="sortGlobalSources('source_type')">Typ <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="article_count" onclick="sortGlobalSources('article_count')">Artikel <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="articles_30d" onclick="sortGlobalSources('articles_30d')">Aktivität <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="tenant_excluded_count" onclick="sortGlobalSources('tenant_excluded_count')">Sperren <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="language" onclick="sortGlobalSources('language')">Sprache <span class="sort-icon"></span></th>
|
|
<th>Bias</th>
|
|
<th class="sortable" data-sort="last_seen_at" onclick="sortGlobalSources('last_seen_at')">Letzter Treffer <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="health_status" onclick="sortGlobalSources('health_status')">Health <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="status" onclick="sortGlobalSources('status')">Status <span class="sort-icon"></span></th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="globalSourceTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kundenquellen -->
|
|
<div class="section" id="sub-tenant-sources">
|
|
<div class="action-bar">
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
<input type="text" class="search-input" id="tenantSourceSearch" placeholder="Kundenquelle suchen...">
|
|
<select class="filter-select" id="tenantFilterType" onchange="filterTenantSources()">
|
|
<option value="">Alle Typen</option>
|
|
</select>
|
|
<select class="filter-select" id="tenantFilterCategory" onchange="filterTenantSources()">
|
|
<option value="">Alle Kategorien</option>
|
|
</select>
|
|
<select class="filter-select" id="tenantFilterOrg" onchange="filterTenantSources()">
|
|
<option value="">Alle Organisationen</option>
|
|
</select>
|
|
<select class="filter-select" id="tenantFilterLanguage" onchange="filterTenantSources()">
|
|
<option value="">Alle Sprachen</option>
|
|
</select>
|
|
<span class="text-secondary" id="tenantSourceCount"></span>
|
|
</div>
|
|
<button class="btn btn-primary" id="tenantBulkPromoteBtn" disabled onclick="bulkPromoteSelected()">
|
|
Ausgewählte übernehmen (0)
|
|
</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="width:32px;"><input type="checkbox" id="tenantSelectAll" onchange="toggleTenantSelectAll(this.checked)"></th>
|
|
<th class="sortable" data-sort="name" onclick="sortTenantSources('name')">Name <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="domain" onclick="sortTenantSources('domain')">Domain <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="source_type" onclick="sortTenantSources('source_type')">Typ <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="category" onclick="sortTenantSources('category')">Kategorie <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="org_name" onclick="sortTenantSources('org_name')">Organisation <span class="sort-icon"></span></th>
|
|
<th class="sortable" data-sort="language" onclick="sortTenantSources('language')">Sprache <span class="sort-icon"></span></th>
|
|
<th>Bias</th>
|
|
<th>Hinzugefügt von</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tenantSourceTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quellen-Health (Sub-Tab) - drei Bereiche als Sub-Sub-Tabs;
|
|
source-health.js rendert pro Bereich in den jeweiligen Container. -->
|
|
<div class="section" id="sub-source-health">
|
|
<div class="nav-tabs" id="healthSubTabs" style="margin-top:0;">
|
|
<button class="nav-tab active" data-healthtab="suggestions">Vorschläge</button>
|
|
<button class="nav-tab" data-healthtab="checks">Health-Status</button>
|
|
<button class="nav-tab" data-healthtab="verlauf">Verlauf</button>
|
|
</div>
|
|
<div id="ht-suggestions" class="health-pane active"></div>
|
|
<div id="ht-checks" class="health-pane" style="display:none;"></div>
|
|
<div id="ht-verlauf" class="health-pane" style="display:none;"></div>
|
|
</div>
|
|
|
|
<!-- Klassifikations-Review -->
|
|
<div class="section" id="sub-classification-review">
|
|
<div class="action-bar review-toolbar">
|
|
<div class="review-toolbar-info">
|
|
<span><strong id="reviewPendingCount">0</strong> Vorschläge ausstehend</span>
|
|
<label class="review-conf-filter">
|
|
Mindest-Konfidenz:
|
|
<select class="filter-select" id="reviewMinConfidence" onchange="loadClassificationQueue()">
|
|
<option value="0">alle</option>
|
|
<option value="0.5">0.5+</option>
|
|
<option value="0.7">0.7+</option>
|
|
<option value="0.85">0.85+</option>
|
|
<option value="0.9">0.9+</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
<div class="review-toolbar-actions">
|
|
<button class="btn btn-secondary btn-small" onclick="triggerExternalReputationSync()" title="IFCN-Faktenchecker und EUvsDisinfo neu syncen">Externe Daten syncen</button>
|
|
<button class="btn btn-secondary btn-small" onclick="triggerBulkClassify()" title="LLM-Klassifikation für noch unklassifizierte Quellen starten">+ Klassifikation starten</button>
|
|
<button class="btn btn-primary btn-small" onclick="bulkApproveHighConfidence()" title="Alle Vorschläge ab 0.85 Konfidenz übernehmen">Alle ≥ 0.85 genehmigen</button>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="review-list" id="classificationReviewList">
|
|
<div class="text-muted" style="padding:24px;text-align:center;">Lade Review-Queue…</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- X-Recherche-Konten (Sub-Tab) -->
|
|
<div class="section" id="sub-x-scraper">
|
|
<div class="action-bar">
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
<span class="text-secondary" id="xScraperCount"></span>
|
|
</div>
|
|
<div style="display:flex;gap:8px;">
|
|
<button class="btn btn-secondary" onclick="resetXScraperLocks()">Sperren zurücksetzen</button>
|
|
<button class="btn btn-primary" onclick="openXScraperAddModal()">+ Konto hinzufügen</button>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<p class="text-secondary" style="padding:0 4px 12px;">X-Login-Konten, mit denen der Monitor bei X recherchiert. Mehr Konten bedeuten paralleleres, schnelleres Scrapen. Cookies laufen periodisch ab und müssen dann erneuert werden.</p>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Benutzername</th>
|
|
<th>E-Mail</th>
|
|
<th>Status</th>
|
|
<th>Anfragen</th>
|
|
<th>Letzte Nutzung</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="xScraperTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div> <!-- /sec-sources -->
|
|
|
|
<!-- Audit-Log Section -->
|
|
<div class="section" id="sec-audit">
|
|
<div class="action-bar" style="flex-wrap:wrap;gap:8px;">
|
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
|
<select class="filter-select" id="auditFilterAction">
|
|
<option value="">Alle Aktionen</option>
|
|
</select>
|
|
<select class="filter-select" id="auditFilterResource">
|
|
<option value="">Alle Ressourcen</option>
|
|
</select>
|
|
<input type="number" class="filter-select" id="auditFilterResourceId" placeholder="Ressourcen-ID" min="1" style="width:130px;">
|
|
<select class="filter-select" id="auditFilterAdmin">
|
|
<option value="">Alle Admins</option>
|
|
</select>
|
|
<input type="date" class="filter-select" id="auditFilterFrom" title="Von (Datum)">
|
|
<input type="date" class="filter-select" id="auditFilterTo" title="Bis (Datum)">
|
|
<button class="btn btn-secondary btn-small" id="auditFilterReset">Filter zuruecksetzen</button>
|
|
<span class="text-secondary" id="auditCount"></span>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:8px;">
|
|
<button class="btn btn-secondary btn-small" id="auditPrevBtn" disabled>← Zurueck</button>
|
|
<button class="btn btn-secondary btn-small" id="auditNextBtn" disabled>Weiter →</button>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="width:170px;">Zeitpunkt</th>
|
|
<th style="width:120px;">Admin</th>
|
|
<th style="width:140px;">IP</th>
|
|
<th style="width:140px;">Aktion</th>
|
|
<th>Ressource</th>
|
|
<th style="width:50px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="auditTable"><tr><td colspan="6" class="text-muted">Lade...</td></tr></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Modal: New Organization -->
|
|
<div class="modal-overlay" id="modalNewOrg">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Neue Organisation</h3>
|
|
<button class="modal-close" onclick="closeModal('modalNewOrg')">×</button>
|
|
</div>
|
|
<form id="newOrgForm">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="newOrgName">Name</label>
|
|
<input type="text" id="newOrgName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newOrgSlug">Slug (URL-freundlich)</label>
|
|
<input type="text" id="newOrgSlug" required pattern="[a-z0-9-]+" placeholder="z.B. bundespolizei">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newOrgLanguage">Pipeline-Sprache</label>
|
|
<select id="newOrgLanguage">
|
|
<option value="de" selected>Deutsch</option>
|
|
<option value="en">English</option>
|
|
</select>
|
|
<small class="text-secondary">Steuert die Ausgabesprache der KI-Pipeline (Lagebild, Faktencheck, Recherche) und die sichtbarsten UI-Strings im Monitor.</small>
|
|
</div>
|
|
|
|
<div class="form-group" style="background:var(--bg-tertiary,#0f172a); padding:8px 12px; border-radius:6px; border:1px solid var(--border);">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
|
<input type="checkbox" id="newOrgWithLicense" checked onchange="onNewOrgLicenseToggle()">
|
|
<span><strong>Direkt eine Lizenz mit Credits-Kontingent anlegen</strong></span>
|
|
</label>
|
|
</div>
|
|
<div id="newOrgLicenseFields">
|
|
<div class="form-group">
|
|
<label for="newOrgLicType">Lizenztyp</label>
|
|
<select id="newOrgLicType" onchange="onNewOrgLicTypeChange()">
|
|
<option value="trial" selected>Trial</option>
|
|
<option value="annual">Jahreslizenz</option>
|
|
<option value="permanent">Permanent</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newOrgLicMaxUsers">Maximale Nutzer</label>
|
|
<input type="number" id="newOrgLicMaxUsers" value="5" min="1" max="1000">
|
|
</div>
|
|
<div class="form-group" id="newOrgLicDurationGroup">
|
|
<label for="newOrgLicDuration">Laufzeit (Tage)</label>
|
|
<input type="number" id="newOrgLicDuration" value="14" min="1" max="3650">
|
|
<div class="text-muted mt-8" style="font-size: 12px;">Trial: Standard 14 Tage, Jahreslizenz: Standard 365 Tage</div>
|
|
</div>
|
|
<div class="form-group" style="background:var(--bg-tertiary,#0f172a); padding:8px 12px; border-radius:6px; border:1px solid var(--border);">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
|
<input type="checkbox" id="newOrgLicUnlimited" onchange="onNewOrgLicUnlimitedToggle()">
|
|
<span><strong>Unbegrenztes Budget</strong> — getrackt, aber kein Hard-Stop</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newOrgLicCreditsTotal">Credits-Kontingent</label>
|
|
<input type="number" id="newOrgLicCreditsTotal" value="10000" placeholder="z.B. 10000">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newOrgLicCreditsPeriod">Abrechnungszeitraum</label>
|
|
<select id="newOrgLicCreditsPeriod" onchange="onNewOrgLicPeriodChange()">
|
|
<option value="monthly" selected>Monatlich (Kontingent füllt sich zum Monatswechsel neu)</option>
|
|
<option value="total">Gesamtlaufzeit</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
|
<input type="checkbox" id="newOrgLicRollover">
|
|
<span>Übertrag ungenutzter Credits in den Folgemonat (gedeckelt auf ein Monatskontingent)</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newOrgLicWarnPercent">Warnschwelle (%)</label>
|
|
<input type="number" id="newOrgLicWarnPercent" value="80" min="1" max="100">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newOrgLicCostPerCredit">Kosten pro Credit (USD)</label>
|
|
<input type="number" id="newOrgLicCostPerCredit" step="0.01" value="0.20" placeholder="0.20">
|
|
</div>
|
|
</div>
|
|
<div id="newOrgError" class="error-msg" style="display:none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalNewOrg')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Anlegen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: New User -->
|
|
<div class="modal-overlay" id="modalNewUser">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Neuen Nutzer anlegen</h3>
|
|
<button class="modal-close" onclick="closeModal('modalNewUser')">×</button>
|
|
</div>
|
|
<form id="newUserForm">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="newUserEmail">E-Mail</label>
|
|
<input type="email" id="newUserEmail" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newUserRole">Rolle</label>
|
|
<select id="newUserRole">
|
|
<option value="member">Mitglied</option>
|
|
<option value="org_admin">Org-Admin</option>
|
|
</select>
|
|
</div>
|
|
<div id="newUserError" class="error-msg" style="display:none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalNewUser')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Anlegen & Einladung senden</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: New License -->
|
|
<div class="modal-overlay" id="modalNewLicense">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Neue Lizenz</h3>
|
|
<button class="modal-close" onclick="closeModal('modalNewLicense')">×</button>
|
|
</div>
|
|
<form id="newLicenseForm">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="newLicType">Lizenztyp</label>
|
|
<select id="newLicType">
|
|
<option value="trial">Trial</option>
|
|
<option value="annual">Jahreslizenz</option>
|
|
<option value="permanent">Permanent</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newLicMaxUsers">Maximale Nutzer</label>
|
|
<input type="number" id="newLicMaxUsers" value="5" min="1" max="1000">
|
|
</div>
|
|
<div class="form-group" id="durationGroup">
|
|
<label for="newLicDuration">Laufzeit (Tage)</label>
|
|
<input type="number" id="newLicDuration" value="14" min="1" max="3650">
|
|
<div class="text-muted mt-8" style="font-size: 12px;">Trial: Standard 14 Tage, Jahreslizenz: Standard 365 Tage</div>
|
|
</div>
|
|
|
|
<div class="form-group" style="background:var(--bg-tertiary,#0f172a); padding:8px 12px; border-radius:6px; border:1px solid var(--border);">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
|
<input type="checkbox" id="newLicUnlimited" onchange="onNewLicUnlimitedToggle()">
|
|
<span><strong>Unbegrenztes Budget</strong> — getrackt, aber kein Hard-Stop</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newLicCreditsTotal">Credits-Kontingent</label>
|
|
<input type="number" id="newLicCreditsTotal" value="10000" placeholder="z.B. 10000">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newLicCreditsPeriod">Abrechnungszeitraum</label>
|
|
<select id="newLicCreditsPeriod" onchange="onNewLicPeriodChange()">
|
|
<option value="monthly" selected>Monatlich (Kontingent füllt sich zum Monatswechsel neu)</option>
|
|
<option value="total">Gesamtlaufzeit</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
|
<input type="checkbox" id="newLicRollover">
|
|
<span>Übertrag ungenutzter Credits in den Folgemonat (gedeckelt auf ein Monatskontingent)</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newLicWarnPercent">Warnschwelle (%)</label>
|
|
<input type="number" id="newLicWarnPercent" value="80" min="1" max="100">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newLicCostPerCredit">Kosten pro Credit (USD)</label>
|
|
<input type="number" id="newLicCostPerCredit" step="0.01" placeholder="0.20">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newLicBudgetUsd">Budget-Limit (USD)</label>
|
|
<input type="number" id="newLicBudgetUsd" step="0.01" placeholder="z.B. 2000">
|
|
</div>
|
|
<div id="newLicError" class="error-msg" style="display:none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalNewLicense')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Lizenz erstellen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Modal: Lizenz verlängern (ersetzt das alte Browser-prompt) -->
|
|
<div class="modal-overlay" id="modalExtendLicense">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Lizenz verlängern</h3>
|
|
<button class="modal-close" onclick="closeModal('modalExtendLicense')">×</button>
|
|
</div>
|
|
<form id="extendLicenseForm">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="extendDays">Um wie viele Tage verlängern?</label>
|
|
<input type="number" id="extendDays" value="365" min="1" max="3650" required>
|
|
<div class="text-muted mt-8" style="font-size:12px;">Die Verlängerung setzt am aktuellen Ablaufdatum an und aktiviert die Lizenz, falls sie abgelaufen war.</div>
|
|
</div>
|
|
<div id="extendLicenseError" class="error-msg" style="display:none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalExtendLicense')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Verlängern</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Source (Create/Edit) -->
|
|
<div class="modal-overlay" id="modalSource">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3 id="sourceModalTitle">Neue Grundquelle</h3>
|
|
<button class="modal-close" onclick="closeModal('modalSource')">×</button>
|
|
</div>
|
|
<form id="sourceForm">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="sourceName">Name</label>
|
|
<input type="text" id="sourceName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceUrl">Feed-URL</label>
|
|
<input type="url" id="sourceUrl" placeholder="https://...">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceDomain">Domain</label>
|
|
<input type="text" id="sourceDomain" placeholder="z.B. tagesschau.de">
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
|
<div class="form-group">
|
|
<label for="sourceType">Typ</label>
|
|
<select id="sourceType">
|
|
<option value="rss_feed">RSS-Feed</option>
|
|
<option value="web_source">Webquelle</option>
|
|
<option value="telegram_channel">Telegram-Kanal</option>
|
|
<option value="podcast_feed">Podcast-Feed</option>
|
|
<option value="excluded">Ausgeschlossen</option>
|
|
<option value="pdf_document" disabled>PDF-Dokument (nur Upload)</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceCategory">Kategorie</label>
|
|
<select id="sourceCategory">
|
|
<option value="nachrichtenagentur">Nachrichtenagentur</option>
|
|
<option value="oeffentlich-rechtlich">Öffentlich-Rechtlich</option>
|
|
<option value="qualitaetszeitung">Qualitätszeitung</option>
|
|
<option value="behoerde">Behörde</option>
|
|
<option value="fachmedien">Fachmedien</option>
|
|
<option value="think-tank">Think-Tank</option>
|
|
<option value="international">International</option>
|
|
<option value="regional">Regional</option>
|
|
<option value="boulevard">Boulevard</option>
|
|
<option value="sonstige" selected>Sonstige</option>
|
|
<option value="cybercrime">Cybercrime / Hacktivismus</option>
|
|
<option value="cybercrime-leaks">Cybercrime / Leaks</option>
|
|
<option value="ukraine-russland-krieg">Ukraine-Russland-Krieg</option>
|
|
<option value="irankonflikt">Irankonflikt</option>
|
|
<option value="osint-international">OSINT International</option>
|
|
<option value="extremismus-deutschland">Extremismus Deutschland</option>
|
|
<option value="russische-staatspropaganda">Russische Staatspropaganda</option>
|
|
<option value="russische-opposition">Russische Opposition / Exilmedien</option>
|
|
<option value="syrien-nahost">Syrien / Nahost</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
|
<div class="form-group">
|
|
<label for="sourceStatus">Status</label>
|
|
<select id="sourceStatus">
|
|
<option value="active">Aktiv</option>
|
|
<option value="inactive">Inaktiv</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceLanguage">Sprache</label>
|
|
<input type="text" id="sourceLanguage" list="languageSuggestions" placeholder="z.B. Deutsch, Englisch, Russisch">
|
|
<datalist id="languageSuggestions"></datalist>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceBias">Bias / Einordnung</label>
|
|
<input type="text" id="sourceBias" placeholder="z.B. Nachrichtenagentur, faktenbasiert-neutral" maxlength="500">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceFetchStrategy">Fetch-Strategie (Health-Check)</label>
|
|
<select id="sourceFetchStrategy">
|
|
<option value="default">Standard (UA + Retry mit Googlebot bei 403)</option>
|
|
<option value="googlebot">Googlebot (direkt - fuer SEO-freundliche Sites)</option>
|
|
<option value="paywall">Paywall (via removepaywalls.com - z.B. FT, Spiegel+)</option>
|
|
<option value="skip">Skip (Health-Check ueberspringen)</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceNotes">Notizen</label>
|
|
<input type="text" id="sourceNotes" placeholder="Optional">
|
|
</div>
|
|
|
|
<div class="sources-classification-section">
|
|
<div class="sources-classification-header">Einordnung (Klassifikation)</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;">
|
|
<div class="form-group">
|
|
<label for="sourcePolitical">Politische Ausrichtung</label>
|
|
<select id="sourcePolitical">
|
|
<option value="">— unverändert —</option>
|
|
<option value="na">Nicht eingeordnet</option>
|
|
<option value="links_extrem">Links (extrem)</option>
|
|
<option value="links">Links</option>
|
|
<option value="mitte_links">Mitte-Links</option>
|
|
<option value="liberal">Liberal</option>
|
|
<option value="mitte">Mitte</option>
|
|
<option value="konservativ">Konservativ</option>
|
|
<option value="mitte_rechts">Mitte-Rechts</option>
|
|
<option value="rechts">Rechts</option>
|
|
<option value="rechts_extrem">Rechts (extrem)</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceMediaType">Medientyp</label>
|
|
<select id="sourceMediaType">
|
|
<option value="">— unverändert —</option>
|
|
<option value="sonstige">Sonstige</option>
|
|
<option value="tageszeitung">Tageszeitung</option>
|
|
<option value="wochenzeitung">Wochenzeitung</option>
|
|
<option value="magazin">Magazin</option>
|
|
<option value="tv_sender">TV-Sender</option>
|
|
<option value="radio">Radio</option>
|
|
<option value="oeffentlich_rechtlich">Öffentlich-Rechtlich</option>
|
|
<option value="nachrichtenagentur">Nachrichtenagentur</option>
|
|
<option value="online_only">Online-only</option>
|
|
<option value="blog">Blog</option>
|
|
<option value="telegram_kanal">Telegram-Kanal</option>
|
|
<option value="telegram_bot">Telegram-Bot</option>
|
|
<option value="podcast">Podcast</option>
|
|
<option value="social_media">Social Media</option>
|
|
<option value="imageboard">Imageboard</option>
|
|
<option value="think_tank">Think Tank</option>
|
|
<option value="ngo">NGO</option>
|
|
<option value="behoerde">Behörde</option>
|
|
<option value="staatsmedium">Staatsmedium</option>
|
|
<option value="fachmedium">Fachmedium</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="sourceReliability">Glaubwürdigkeit</label>
|
|
<select id="sourceReliability">
|
|
<option value="">— unverändert —</option>
|
|
<option value="na">Nicht eingeordnet</option>
|
|
<option value="sehr_hoch">Sehr hoch</option>
|
|
<option value="hoch">Hoch</option>
|
|
<option value="gemischt">Gemischt</option>
|
|
<option value="niedrig">Niedrig</option>
|
|
<option value="sehr_niedrig">Sehr niedrig</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:8px;">
|
|
<div class="form-group">
|
|
<label for="sourceCountryCode">Land (ISO 3166)</label>
|
|
<input type="text" id="sourceCountryCode" maxlength="2" placeholder="z.B. DE, RU, US" style="text-transform:uppercase;">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label" style="display:flex;align-items:center;gap:8px;margin-top:24px;">
|
|
<input type="checkbox" id="sourceStateAffiliated">
|
|
<span>Staatsnah / staatlich kontrolliert</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group" style="margin-top:8px;">
|
|
<label>Geopolitische Nähe (Mehrfachauswahl)</label>
|
|
<div id="sourceAlignmentChips" class="alignment-chips" onclick="handleAlignmentChipClick(event)">
|
|
<button type="button" class="alignment-chip" data-alignment="prorussisch">prorussisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="proiranisch">proiranisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="prowestlich">prowestlich</button>
|
|
<button type="button" class="alignment-chip" data-alignment="proukrainisch">proukrainisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="prochinesisch">prochinesisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="projapanisch">projapanisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="proisraelisch">proisraelisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="propalaestinensisch">propalästinensisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="protuerkisch">protürkisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="panarabisch">panarabisch</button>
|
|
<button type="button" class="alignment-chip" data-alignment="neutral">neutral</button>
|
|
<button type="button" class="alignment-chip" data-alignment="sonstige">sonstige</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="sourceError" class="error-msg" style="display:none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalSource')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Speichern</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: PDF hochladen -->
|
|
<div class="modal-overlay" id="modalPdfUpload">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>PDF als Quelle hochladen</h3>
|
|
<button class="modal-close" onclick="closeModal('modalPdfUpload')">×</button>
|
|
</div>
|
|
<form id="pdfUploadForm" enctype="multipart/form-data">
|
|
<div class="modal-body">
|
|
<p class="text-secondary" style="margin-top:0;">
|
|
Die PDF wird gespeichert und vom Monitor automatisch verarbeitet:
|
|
Text extrahieren (OCR-Fallback fuer gescannte Dokumente),
|
|
Übersetzung nach Deutsch und Englisch.
|
|
</p>
|
|
<div class="form-group">
|
|
<label for="pdfFile">PDF-Datei (max. 50 MB)</label>
|
|
<input type="file" id="pdfFile" accept="application/pdf,.pdf" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="pdfName">Anzeige-Name (optional)</label>
|
|
<input type="text" id="pdfName" maxlength="200" placeholder="leer = Dateiname">
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
|
<div class="form-group">
|
|
<label for="pdfCategory">Kategorie</label>
|
|
<select id="pdfCategory">
|
|
<option value="sonstige" selected>Sonstige</option>
|
|
<option value="behoerde">Behörde</option>
|
|
<option value="think-tank">Think-Tank</option>
|
|
<option value="fachmedien">Fachmedien</option>
|
|
<option value="international">International</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="pdfLanguage">Sprache (optional)</label>
|
|
<input type="text" id="pdfLanguage" list="languageSuggestions" placeholder="z.B. Deutsch, Englisch">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="pdfNotes">Notizen</label>
|
|
<input type="text" id="pdfNotes" placeholder="Optional">
|
|
</div>
|
|
<div id="pdfUploadError" class="error-msg" style="display:none"></div>
|
|
<div id="pdfUploadProgress" class="text-secondary" style="display:none;margin-top:8px;">Lädt hoch …</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalPdfUpload')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary" id="pdfUploadSubmitBtn">Hochladen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Discover Sources -->
|
|
<div class="modal-overlay" id="modalDiscover">
|
|
<div class="modal" style="max-width:600px;">
|
|
<div class="modal-header">
|
|
<h3>Quellen erkennen</h3>
|
|
<button class="modal-close" onclick="closeModal('modalDiscover')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="discoverUrl">Website-URL</label>
|
|
<div style="display:flex;gap:8px;">
|
|
<input type="url" id="discoverUrl" placeholder="https://www.example.de" style="flex:1;" required>
|
|
<button class="btn btn-primary" id="discoverBtn" onclick="runDiscover()">Erkennen</button>
|
|
</div>
|
|
</div>
|
|
<div id="discoverStatus" style="display:none;padding:12px 0;color:var(--text-secondary);font-size:13px;"></div>
|
|
<div id="discoverResults" style="display:none;">
|
|
<div id="discoverExisting" style="display:none;margin-bottom:12px;"></div>
|
|
<div id="discoverFeeds"></div>
|
|
<div style="margin-top:12px;display:flex;justify-content:flex-end;">
|
|
<button class="btn btn-primary" id="addDiscoveredBtn" style="display:none;" onclick="addDiscoveredFeeds()">Ausgewählte hinzufügen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Modal: Audit-Spur einer Ressource (Phase 5) -->
|
|
<div class="modal-overlay" id="modalAudit">
|
|
<div class="modal modal-large">
|
|
<div class="modal-header">
|
|
<h3 id="auditTitle">Audit-Spur</h3>
|
|
<button class="modal-close" onclick="closeModal('modalAudit')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="auditContent" class="audit-content">Lade...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Confirm -->
|
|
<div class="modal-overlay" id="modalConfirm">
|
|
<div class="modal" style="max-width: 400px;">
|
|
<div class="modal-header">
|
|
<h3 id="confirmTitle">Bestätigung</h3>
|
|
<button class="modal-close" onclick="closeModal('modalConfirm')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="confirm-text" id="confirmText"></p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" id="confirmCancelBtn" onclick="closeModal('modalConfirm')">Abbrechen</button>
|
|
<button class="btn btn-danger" id="confirmOkBtn">Bestätigen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: X-Recherche-Konto hinzufügen -->
|
|
<div class="modal-overlay" id="modalXScraperAdd">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>X-Recherche-Konto hinzufügen</h3>
|
|
<button class="modal-close" onclick="closeModal('modalXScraperAdd')">×</button>
|
|
</div>
|
|
<form id="xScraperAddForm">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="xsUsername">X-Benutzername</label>
|
|
<input type="text" id="xsUsername" required placeholder="Login-Handle des Kontos, ohne @">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="xsPassword">X-Passwort</label>
|
|
<input type="password" id="xsPassword" placeholder="optional">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="xsEmail">E-Mail</label>
|
|
<input type="text" id="xsEmail" placeholder="optional, z.B. konto@protonmail.com">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="xsEmailPassword">E-Mail-Passwort</label>
|
|
<input type="password" id="xsEmailPassword" placeholder="optional">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="xsCookies">Cookies</label>
|
|
<textarea id="xsCookies" rows="3" required placeholder="auth_token=...; ct0=..."></textarea>
|
|
<small class="text-secondary">Aus dem eingeloggten X-Browser exportiert, mindestens auth_token und ct0.</small>
|
|
</div>
|
|
<div id="xScraperAddError" class="error-msg" style="display:none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalXScraperAdd')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Konto anlegen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: X-Recherche-Konto Cookies erneuern -->
|
|
<div class="modal-overlay" id="modalXScraperCookies">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Cookies erneuern</h3>
|
|
<button class="modal-close" onclick="closeModal('modalXScraperCookies')">×</button>
|
|
</div>
|
|
<form id="xScraperCookiesForm">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="xsCookiesUsername">Konto</label>
|
|
<input type="text" id="xsCookiesUsername" readonly>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="xsCookiesValue">Neue Cookies</label>
|
|
<textarea id="xsCookiesValue" rows="3" required placeholder="auth_token=...; ct0=..."></textarea>
|
|
<small class="text-secondary">Frisch aus dem eingeloggten X-Browser exportieren.</small>
|
|
</div>
|
|
<div id="xScraperCookiesError" class="error-msg" style="display:none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('modalXScraperCookies')">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Cookies setzen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/static/js/a11y.js?v=20260725a"></script>
|
|
<script src="/static/js/app.js?v=20260725c"></script>
|
|
<script src="/static/js/sources.js?v=20260522x2"></script>
|
|
<script src="/static/js/x-scraper.js?v=20260522a"></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>
|
|
</body>
|
|
</html>
|