This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/frontend/components/admin_stats.py
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
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>
2026-02-09 09:51:32 +01:00

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>`;
}
}
"""