diff --git a/backend/routes/admin.py b/backend/routes/admin.py index ae379a8..b3bee12 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -534,6 +534,40 @@ async def scheduler_trigger(job_id: str, user=Depends(require_admin)): return {"ok": True, "job_id": job_id} +# ------------------------------------------------------------------ +# GET /api/admin/ki/history — 30-Tage-Verlauf + Top-User (all-time) +# ------------------------------------------------------------------ +@router.get("/ki/history") +async def ki_history(user=Depends(require_mod)): + with db() as conn: + daily = conn.execute(""" + SELECT date, + SUM(count) AS total, + SUM(CASE WHEN source='cloud' THEN count ELSE 0 END) AS cloud, + SUM(CASE WHEN source='local' THEN count ELSE 0 END) AS local, + SUM(CASE WHEN source='luna' THEN count ELSE 0 END) AS luna + FROM ki_daily_calls + WHERE date >= DATE('now', '-29 days') + GROUP BY date ORDER BY date ASC + """).fetchall() + top_users = conn.execute(""" + SELECT u.name, u.email, + SUM(k.count) AS total, + SUM(CASE WHEN k.source='cloud' THEN k.count ELSE 0 END) AS cloud, + MAX(k.date) AS last_date + FROM ki_daily_calls k JOIN users u ON u.id = k.user_id + GROUP BY k.user_id ORDER BY total DESC LIMIT 15 + """).fetchall() + return { + "daily_history": [{"date": r["date"], "total": r["total"], + "cloud": r["cloud"], "local": r["local"], "luna": r["luna"]} + for r in daily], + "top_users": [{"name": r["name"], "email": r["email"], + "total": r["total"], "cloud": r["cloud"], "last_date": r["last_date"]} + for r in top_users], + } + + # ------------------------------------------------------------------ # GET /api/admin/ki/status — lokale LLM-Erreichbarkeit prüfen # ------------------------------------------------------------------ diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 210e83a..4294f1c 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '462'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '463'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index f54865d..24d651a 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -311,9 +311,10 @@ window.Page_admin = (() => { // TAB: ÜBERSICHT // ------------------------------------------------------------------ async function _renderStats(el) { - const [s, ki] = await Promise.all([ + const [s, ki, kiH] = await Promise.all([ API.get('/admin/stats'), API.get('/admin/ki/status').catch(() => null), + API.get('/admin/ki/history').catch(() => null), ]); const _kiStatusBadge = () => { @@ -364,37 +365,79 @@ window.Page_admin = (() => {
KI-Nutzung
- ${_kiStatusBadge()} -- User-Limit: ${s.ki_cloud_weekly_limit ?? 20} Cloud-Anfragen / Woche + +
+ + KI-Nutzung
- ${(s.ki_top_users || []).length ? ` -Top Cloud-User (7 Tage)
- ${s.ki_top_users.map((u, i) => ` -Noch keine Verlaufsdaten
'; + const max = Math.max(...hist.map(d => d.total), 1); + const W = 400, H = 60, n = hist.length; + const pts = hist.map((d, i) => { + const x = n === 1 ? W/2 : (i / (n - 1)) * W; + const y = H - 5 - (d.total / max) * (H - 10); + return `${x.toFixed(1)},${y.toFixed(1)}`; + }).join(' '); + const first = hist[0]?.date?.slice(5) || ''; + const last = hist[hist.length-1]?.date?.slice(5) || ''; + return `+ Cloud-Limit: ${s.ki_cloud_weekly_limit ?? 20} Anfragen / Woche pro User +
+ + ${(kiH?.top_users || []).length ? ` +Aktivste Nutzer
+| Name | ☁️ Cloud | Gesamt | Zuletzt | +|
|---|---|---|---|---|
| ${_esc(u.name)} | +${_esc(u.email.length > 22 ? u.email.split('@')[1] : u.email)} | +${u.cloud} | +${u.total} | +${u.last_date?.slice(5) || '—'} | +