feat(admin): vollständiges Admin-Panel in der Web-UI (Phase 1)
Ersetzt das rudimentäre Nutzer-Dropdown durch ein vollwertiges
Admin-Panel-Overlay mit 5 Tabs:
Nutzer: Anlegen (Token einmalig), Umbenennen, Token-Reset,
Löschen, Erinnerungen verwalten (anzeigen/add/delete)
Gespräche: Nutzer → Sessions → Transkript (Chat-Bubble-Ansicht)
Notfälle: Tabelle aller emergency_events mit Zeitstempel/Kategorie
Status: Gateway-Status, Provider-Config, Laufzeit-Metriken
Metriken: Nutzungsstatistik pro Nutzer + Gesamtsumme
Backend: 7 neue Admin-Endpunkte in admin.py, 5 neue Store-Methoden.
Design: Tailwind CSS, Tag/Nacht-Modus, responsiv.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8e48ab6d66
commit
b38992cf28
4 changed files with 710 additions and 34 deletions
67
app/store.py
67
app/store.py
|
|
@ -137,6 +137,21 @@ class Store(ABC):
|
|||
def log_emergency(self, user_id: str, category: str, snippet: str) -> None:
|
||||
"""Protokolliert ein erkanntes Notfall-Signal (sensibel!)."""
|
||||
|
||||
@abstractmethod
|
||||
def list_sessions_for_user(self, user_id: str) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
def get_messages_for_session(self, session_id: str, limit: int = 200) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
def list_emergency_events(self, limit: int = 50) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
def get_usage_for_user(self, user_id: str) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
def get_all_usage(self) -> list[dict]: ...
|
||||
|
||||
|
||||
class SQLiteStore(Store):
|
||||
def __init__(self, db_path: str):
|
||||
|
|
@ -484,3 +499,55 @@ class SQLiteStore(Store):
|
|||
" VALUES (?, ?, ?, ?)",
|
||||
(user_id, category, snippet, _now()),
|
||||
)
|
||||
|
||||
# ----- Admin-Abfragen ---------------------------------------------------
|
||||
def list_sessions_for_user(self, user_id: str) -> list[dict]:
|
||||
with self._connect() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT s.id, s.created_at, s.updated_at,"
|
||||
" (SELECT COUNT(*) FROM messages m WHERE m.session_id = s.id) AS msg_count"
|
||||
" FROM sessions s WHERE s.user_id = ?"
|
||||
" ORDER BY s.updated_at DESC LIMIT 50",
|
||||
(user_id,),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def get_messages_for_session(self, session_id: str, limit: int = 200) -> list[dict]:
|
||||
with self._connect() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT role, content, created_at FROM messages"
|
||||
" WHERE session_id = ? ORDER BY id LIMIT ?",
|
||||
(session_id, limit),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def list_emergency_events(self, limit: int = 50) -> list[dict]:
|
||||
with self._connect() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT e.id, e.user_id, e.category, e.snippet, e.created_at,"
|
||||
" COALESCE(u.display_name, e.user_id) AS display_name"
|
||||
" FROM emergency_events e LEFT JOIN users u ON u.id = e.user_id"
|
||||
" ORDER BY e.id DESC LIMIT ?",
|
||||
(limit,),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def get_usage_for_user(self, user_id: str) -> list[dict]:
|
||||
with self._connect() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT day, requests, units FROM usage"
|
||||
" WHERE user_id = ? ORDER BY day DESC LIMIT 30",
|
||||
(user_id,),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def get_all_usage(self) -> list[dict]:
|
||||
with self._connect() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT u.user_id, COALESCE(usr.display_name, u.user_id) AS display_name,"
|
||||
" SUM(u.requests) AS total_requests, SUM(u.units) AS total_units,"
|
||||
" MAX(u.day) AS last_active"
|
||||
" FROM usage u LEFT JOIN users usr ON usr.id = u.user_id"
|
||||
" GROUP BY u.user_id ORDER BY total_requests DESC",
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue