A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
215 lines
9.0 KiB
Python
215 lines
9.0 KiB
Python
"""
|
|
Admin Stats Component - Statistics & GDPR Export
|
|
"""
|
|
|
|
def get_admin_stats_css() -> str:
|
|
"""CSS für Statistics (inkludiert in admin_panel.css)"""
|
|
return ""
|
|
|
|
|
|
def get_admin_stats_html() -> str:
|
|
"""HTML für Statistics (inkludiert in admin_panel.html)"""
|
|
return ""
|
|
|
|
|
|
def get_admin_stats_js() -> str:
|
|
"""JavaScript für Statistics & GDPR Export zurückgeben"""
|
|
return """
|
|
let dataCategories = [];
|
|
|
|
async function loadAdminStats() {
|
|
const container = document.getElementById('admin-stats-container');
|
|
container.innerHTML = '<div class="admin-loading">Lade Statistiken & DSGVO-Informationen...</div>';
|
|
|
|
try {
|
|
// Lade Datenkategorien
|
|
const catRes = await fetch('/api/consent/privacy/data-categories');
|
|
if (catRes.ok) {
|
|
const catData = await catRes.json();
|
|
dataCategories = catData.categories || [];
|
|
}
|
|
|
|
renderStatsPanel();
|
|
} catch(e) {
|
|
container.innerHTML = '<div class="admin-empty">Fehler beim Laden: ' + e.message + '</div>';
|
|
}
|
|
}
|
|
|
|
function renderStatsPanel() {
|
|
const container = document.getElementById('admin-stats-container');
|
|
|
|
// Kategorisiere Daten
|
|
const essential = dataCategories.filter(c => c.is_essential);
|
|
const optional = dataCategories.filter(c => !c.is_essential);
|
|
|
|
const html = `
|
|
<div style="display: grid; gap: 24px;">
|
|
<!-- GDPR Export Section -->
|
|
<div class="admin-form" style="padding: 20px;">
|
|
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: var(--bp-text);">
|
|
<span style="margin-right: 8px;">📋</span> DSGVO-Datenauskunft (Art. 15)
|
|
</h3>
|
|
<p style="color: var(--bp-text-muted); font-size: 13px; margin-bottom: 16px;">
|
|
Exportieren Sie alle personenbezogenen Daten eines Nutzers als PDF-Dokument.
|
|
Dies erfüllt die Anforderungen der DSGVO Art. 15 (Auskunftsrecht).
|
|
</p>
|
|
|
|
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
|
<input type="text" id="gdpr-export-user-id" class="admin-form-input"
|
|
placeholder="Benutzer-ID (optional für eigene Daten)"
|
|
style="flex: 1; min-width: 200px;">
|
|
<button class="admin-btn admin-btn-primary" onclick="exportUserDataPdf()">
|
|
PDF exportieren
|
|
</button>
|
|
<button class="admin-btn" onclick="previewUserDataHtml()">
|
|
HTML-Vorschau
|
|
</button>
|
|
</div>
|
|
|
|
<div id="gdpr-export-status" style="margin-top: 12px; font-size: 13px;"></div>
|
|
</div>
|
|
|
|
<!-- Data Retention Overview -->
|
|
<div class="admin-form" style="padding: 20px;">
|
|
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: var(--bp-text);">
|
|
<span style="margin-right: 8px;">🗄️</span> Datenkategorien & Löschfristen
|
|
</h3>
|
|
|
|
<div style="margin-bottom: 20px;">
|
|
<h4 style="font-size: 14px; color: var(--bp-primary); margin: 0 0 12px 0;">
|
|
Essentielle Daten (Pflicht für Betrieb)
|
|
</h4>
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Kategorie</th>
|
|
<th>Beschreibung</th>
|
|
<th>Löschfrist</th>
|
|
<th>Rechtsgrundlage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${essential.map(cat => `
|
|
<tr>
|
|
<td><strong>${cat.name_de}</strong></td>
|
|
<td>${cat.description_de}</td>
|
|
<td><span class="admin-badge admin-badge-published">${cat.retention_period}</span></td>
|
|
<td style="font-size: 11px; color: var(--bp-text-muted);">${cat.legal_basis}</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 style="font-size: 14px; color: var(--bp-warning); margin: 0 0 12px 0;">
|
|
Optionale Daten (nur bei Einwilligung)
|
|
</h4>
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Kategorie</th>
|
|
<th>Beschreibung</th>
|
|
<th>Cookie-Kategorie</th>
|
|
<th>Löschfrist</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${optional.map(cat => `
|
|
<tr>
|
|
<td><strong>${cat.name_de}</strong></td>
|
|
<td>${cat.description_de}</td>
|
|
<td><span class="admin-badge">${cat.cookie_category || '-'}</span></td>
|
|
<td><span class="admin-badge admin-badge-optional">${cat.retention_period}</span></td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Stats -->
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
|
|
<div class="admin-form" style="padding: 16px; text-align: center;">
|
|
<div style="font-size: 28px; font-weight: bold; color: var(--bp-primary);">${dataCategories.length}</div>
|
|
<div style="color: var(--bp-text-muted); font-size: 13px;">Datenkategorien</div>
|
|
</div>
|
|
<div class="admin-form" style="padding: 16px; text-align: center;">
|
|
<div style="font-size: 28px; font-weight: bold; color: var(--bp-success);">${essential.length}</div>
|
|
<div style="color: var(--bp-text-muted); font-size: 13px;">Essentiell</div>
|
|
</div>
|
|
<div class="admin-form" style="padding: 16px; text-align: center;">
|
|
<div style="font-size: 28px; font-weight: bold; color: var(--bp-warning);">${optional.length}</div>
|
|
<div style="color: var(--bp-text-muted); font-size: 13px;">Optional (Opt-in)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
async function exportUserDataPdf() {
|
|
const userIdInput = document.getElementById('gdpr-export-user-id');
|
|
const statusDiv = document.getElementById('gdpr-export-status');
|
|
const userId = userIdInput?.value?.trim();
|
|
|
|
statusDiv.innerHTML = '<span style="color: var(--bp-primary);">Generiere PDF...</span>';
|
|
|
|
try {
|
|
let url = '/api/consent/privacy/export-pdf';
|
|
|
|
// Wenn eine User-ID angegeben wurde, verwende den Admin-Endpoint
|
|
if (userId) {
|
|
url = `/api/consent/admin/privacy/export-pdf/${userId}`;
|
|
}
|
|
|
|
const res = await fetch(url, { method: 'POST' });
|
|
|
|
if (!res.ok) {
|
|
const error = await res.json();
|
|
throw new Error(error.detail?.message || error.detail || 'Export fehlgeschlagen');
|
|
}
|
|
|
|
// PDF herunterladen
|
|
const blob = await res.blob();
|
|
const downloadUrl = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = downloadUrl;
|
|
a.download = userId ? `datenauskunft_${userId.slice(0,8)}.pdf` : 'breakpilot_datenauskunft.pdf';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
window.URL.revokeObjectURL(downloadUrl);
|
|
|
|
statusDiv.innerHTML = '<span style="color: var(--bp-success);">✓ PDF erfolgreich generiert!</span>';
|
|
} catch(e) {
|
|
statusDiv.innerHTML = `<span style="color: var(--bp-danger);">Fehler: ${e.message}</span>`;
|
|
}
|
|
}
|
|
|
|
async function previewUserDataHtml() {
|
|
const statusDiv = document.getElementById('gdpr-export-status');
|
|
statusDiv.innerHTML = '<span style="color: var(--bp-primary);">Lade Vorschau...</span>';
|
|
|
|
try {
|
|
const res = await fetch('/api/consent/privacy/export-html');
|
|
|
|
if (!res.ok) {
|
|
throw new Error('Vorschau konnte nicht geladen werden');
|
|
}
|
|
|
|
const html = await res.text();
|
|
|
|
// In neuem Tab öffnen
|
|
const win = window.open('', '_blank');
|
|
win.document.write(html);
|
|
win.document.close();
|
|
|
|
statusDiv.innerHTML = '<span style="color: var(--bp-success);">✓ Vorschau in neuem Tab geöffnet</span>';
|
|
} catch(e) {
|
|
statusDiv.innerHTML = `<span style="color: var(--bp-danger);">Fehler: ${e.message}</span>`;
|
|
}
|
|
}
|
|
"""
|