feat(admin): Bulk-Wipe inkl. Notruf-Protokolle + Statistik-Tabelle alphabetisch

- wipe_all_user_data loescht zusaetzlich emergency_events (Notruf-Protokolle).
  Audit-Log bleibt unberuehrt (Logger-Stream, kein DB-Eintrag).
- Nutzungsstatistik-Tabelle alphabetisch (case-insensitive); die "Anfragen je
  Nutzer"-Balken bleiben bewusst nach Aktivitaet sortiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-30 18:56:35 +02:00
commit ce9ada63c9
3 changed files with 7 additions and 3 deletions

View file

@ -618,6 +618,7 @@ class SQLiteStore(Store):
"messages": conn.execute("DELETE FROM messages").rowcount,
"sessions": conn.execute("DELETE FROM sessions").rowcount,
"memories": conn.execute("DELETE FROM memories").rowcount,
"emergencies": conn.execute("DELETE FROM emergency_events").rowcount,
}
stats = 0
for tbl in ("usage", "web_search_usage", "cost_usage"):

View file

@ -1182,7 +1182,7 @@ $("#btn-wipe-all").addEventListener("click", async () => {
if (!confirm("Letzte Warnung: Alle Nutzerdaten werden jetzt gelöscht. Fortfahren?")) return;
const res = await adminFetch("/api/admin/user-data", "DELETE");
if (res) {
alert(`Gelöscht: ${res.messages ?? 0} Nachrichten, ${res.sessions ?? 0} Sessions, ${res.stats ?? 0} Statistik-Zeilen, ${res.memories ?? 0} Erinnerungen.`);
alert(`Gelöscht: ${res.messages ?? 0} Nachrichten, ${res.sessions ?? 0} Sessions, ${res.stats ?? 0} Statistik-Zeilen, ${res.memories ?? 0} Erinnerungen, ${res.emergencies ?? 0} Notruf-Protokolle.`);
loadAdminUsers();
}
});
@ -1881,6 +1881,9 @@ async function loadMetrics() {
const totalEstimated = data.reduce((s, u) => s + (u.month_cost_estimated_usd || 0), 0);
const fmtUsd = (n) => "$" + (Number(n) || 0).toFixed(4);
const maxReq = Math.max(...data.map((u) => u.total_requests || 0), 1);
// Tabelle alphabetisch (case-insensitive); die Balken bleiben nach Aktivität.
const tableData = [...data].sort((a, b) =>
(a.display_name || "").toLowerCase().localeCompare((b.display_name || "").toLowerCase()));
const bars = data.map((u) => {
const pct = Math.round(((u.total_requests || 0) / maxReq) * 100);
@ -1909,7 +1912,7 @@ async function loadMetrics() {
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800 bg-white dark:bg-slate-800">
${data.map((u) => `
${tableData.map((u) => `
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/40 transition-colors">
<td class="px-4 py-2.5 font-medium">${escHtml(u.display_name)}</td>
<td class="px-4 py-2.5 text-right font-mono text-xs">${u.total_requests ?? 0}</td>

View file

@ -88,7 +88,7 @@
<!-- Wartung: alle Nutzerdaten löschen (Konten bleiben) -->
<div class="bg-white dark:bg-slate-800 rounded-xl border border-red-200 dark:border-red-900 p-4">
<h3 class="text-sm font-medium text-red-700 dark:text-red-400 mb-1">Alle Nutzerdaten löschen</h3>
<p class="text-xs text-slate-500 dark:text-slate-400 mb-3">Löscht für <strong>alle</strong> Nutzer Verlauf, Statistik (Anfragen/Web-Suchen/Kosten) und Erinnerungen. Die Konten selbst bleiben erhalten. Nicht umkehrbar.</p>
<p class="text-xs text-slate-500 dark:text-slate-400 mb-3">Löscht für <strong>alle</strong> Nutzer Verlauf, Statistik (Anfragen/Web-Suchen/Kosten), Erinnerungen und Notruf-Protokolle. Die Konten selbst bleiben erhalten. Nicht umkehrbar. (Das Audit-Log liegt in den System-Logs und bleibt unberührt.)</p>
<button id="btn-wipe-all" class="text-xs rounded-lg border border-red-300 dark:border-red-800 text-red-600 dark:text-red-400 px-3 py-1.5 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">Alle Daten löschen (Konten behalten)</button>
</div>