diff --git a/app/api/admin.py b/app/api/admin.py index 4f8a089..3a24509 100644 --- a/app/api/admin.py +++ b/app/api/admin.py @@ -17,7 +17,7 @@ from app.auth import is_admin_user, require_admin, require_admin_or_user from app.config import settings from app.dependencies import get_store from app.runtime_config import RUNTIME_SETTABLE, invalidate_cache, runtime_settings -from app.schemas import MemoryCreate, MemoryOut, UserCreate, UserCreated, UserUpdate +from app.schemas import AdminUserPrefsUpdate, MemoryCreate, MemoryOut, UserCreate, UserCreated, UserUpdate router = APIRouter() @@ -94,6 +94,18 @@ async def reset_token(user_id: str): return UserCreated(user_id=user.id, display_name=user.display_name, token=token) +@router.put("/admin/users/{user_id}/prefs", dependencies=[Depends(require_admin)]) +async def update_user_prefs(user_id: str, payload: AdminUserPrefsUpdate): + """Setzt Nutzer-Einstellungen (z. B. erlaubte Sprachen). Merge mit bestehenden Prefs.""" + store = get_store() + current = store.get_user(user_id) + if current is None: + raise HTTPException(status_code=404, detail=f"Nutzer {user_id!r} nicht gefunden.") + new = {k: v for k, v in payload.model_dump().items() if v is not None} + updated = store.set_user_prefs(user_id, {**current.prefs, **new}) + return {"user_id": updated.id, "prefs": updated.prefs} + + @router.put("/admin/users/{user_id}", dependencies=[Depends(require_admin)]) async def update_user(user_id: str, payload: UserUpdate): """Aktualisiert den Anzeigenamen eines Nutzers (z. B. nach erstem SSO-Login).""" @@ -122,6 +134,7 @@ async def list_users(): "display_name": u.display_name, "external_id": u.external_id, "created_at": u.created_at, + "allowed_languages": (u.prefs or {}).get("allowed_languages", ""), } for u in get_store().list_users() ] diff --git a/app/dependencies.py b/app/dependencies.py index 8f4097f..f9ac40c 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -297,7 +297,12 @@ def resolve_route( if value is not None: resolved[key] = value - return ResolvedRoute(**resolved) + route = ResolvedRoute(**resolved) + # Admin-Vorgabe „erlaubte Sprachen pro Nutzer": auf eine erlaubte Sprache klemmen. + allowed = [s.strip() for s in str(user_prefs.get("allowed_languages") or "").split(",") if s.strip()] + if allowed and route.language not in allowed: + route.language = allowed[0] + return route _FALLBACK_CLASS = { diff --git a/app/schemas.py b/app/schemas.py index e872bb2..34c008e 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -108,3 +108,8 @@ class MemoryOut(BaseModel): class EmergencyRequest(BaseModel): """Vom Nutzer ausgelöster Notruf (Knopf). language für den Hinweistext.""" language: str | None = None + + +class AdminUserPrefsUpdate(BaseModel): + """Admin setzt Nutzer-Einstellungen (z. B. erlaubte Sprachen, CSV: "de,en").""" + allowed_languages: str | None = None diff --git a/app/web/app.js b/app/web/app.js index 5a220db..14fd7fa 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -319,6 +319,25 @@ let mePromise = null; let currentUserId = null; const sessionKey = (uid) => "va-session-" + uid; +// Sprachen (Code -> Kurzlabel) für Filter + Admin-Auswahl. +const ALL_LANGS = [["de","DE"],["en","EN"],["fr","FR"],["es","ES"],["it","IT"], + ["pt","PT"],["pl","PL"],["ar","AR"],["ru","RU"],["zh","ZH"]]; + +// Wendet die vom Admin erlaubten Sprachen auf das Sprachmenü an. Leer = alle erlaubt. +// Genau eine erlaubte Sprache -> Menü ausblenden (kein Stress für den Nutzer). +function applyAllowedLanguages(prefs) { + if (!langSel) return; + const allowed = String((prefs || {}).allowed_languages || "") + .split(",").map((s) => s.trim()).filter(Boolean); + Array.from(langSel.options).forEach((opt) => { + opt.hidden = allowed.length > 0 && !allowed.includes(opt.value); + }); + let val = (prefs || {}).language || "de"; + if (allowed.length && !allowed.includes(val)) val = allowed[0]; + langSel.value = val; + langSel.classList.toggle("hidden", allowed.length === 1); +} + // ---------- Identitaet / Menue ---------- function loadMe() { mePromise = (async () => { @@ -343,9 +362,7 @@ function loadMe() { } // Gespeicherte Nutzer-Präferenzen in die Dropdowns laden. const prefs = me.prefs || {}; - if (langSel) { - langSel.value = prefs.language || "de"; - } + applyAllowedLanguages(prefs); applyPlaybackChoice(prefs.tts_provider); applyGenderChoice(prefs.voice_gender || 'f'); return me; @@ -1043,6 +1060,12 @@ function buildUserCard(u) { + +
Erlaubte Sprachen (leer = alle):
+ +