diff --git a/dashboard/backend/routers/auth.py b/dashboard/backend/routers/auth.py index 46e98ba..55aeec7 100644 --- a/dashboard/backend/routers/auth.py +++ b/dashboard/backend/routers/auth.py @@ -403,3 +403,19 @@ async def rbac_delete_override(username: str, current_user = Depends(auth.get_cu rbac.get("user_overrides", {}).pop(username, None) save_rbac(rbac) return {"ok": True} + +@router.put("/rbac/roles/{role}") +async def rbac_update_role(role: str, request: Request, current_user = Depends(auth.get_current_user)): + if current_user.role != "admin": + raise HTTPException(status_code=403, detail="Admin only") + from rbac import load_rbac, save_rbac + body = await request.json() + pages = body.get("pages") + if pages is None: + raise HTTPException(status_code=400, detail="Missing pages field") + if pages != "*" and not isinstance(pages, list): + raise HTTPException(status_code=400, detail="pages must be '*' or a list") + rbac = load_rbac() + rbac.setdefault("role_defaults", {})[role] = {"pages": pages} + save_rbac(rbac) + return {"ok": True} diff --git a/dashboard/frontend/src/routes/Settings.svelte b/dashboard/frontend/src/routes/Settings.svelte index f6fbf01..f11f198 100644 --- a/dashboard/frontend/src/routes/Settings.svelte +++ b/dashboard/frontend/src/routes/Settings.svelte @@ -140,6 +140,8 @@ let editingUser = $state(""); let editPages = $state([]); let newUsername = $state(""); + let editingRole = $state(""); + let editRolePages = $state([]); async function loadRbac() { rbacLoading = true; @@ -153,18 +155,38 @@ editingUser = username; editPages = [...pages]; newUsername = ""; + editingRole = ""; } function startAdd() { editingUser = "__new__"; editPages = ["dashboard"]; newUsername = ""; + editingRole = ""; } function cancelEdit() { editingUser = ""; editPages = []; newUsername = ""; + editingRole = ""; + editRolePages = []; + } + + function startEditRole(role, pages) { + editingRole = role; + editRolePages = pages === "*" ? ["*"] : [...pages]; + editingUser = ""; + } + + function toggleRolePage(p) { + if (p === "*") { + editRolePages = ["*"]; + } else { + if (editRolePages.includes("*")) editRolePages = []; + if (editRolePages.includes(p)) editRolePages = editRolePages.filter(x => x !== p); + else editRolePages = [...editRolePages, p]; + } } function togglePage(p) { @@ -199,6 +221,21 @@ } } + async function saveRoleDefault() { + rbacMsg = ""; rbacErr = ""; + if (!editingRole) return; + if (!editRolePages.length) { rbacErr = "Select at least one page or '*' for all"; return; } + try { + const pages = editRolePages.includes("*") ? "*" : editRolePages; + await put(`/auth/rbac/roles/${encodeURIComponent(editingRole)}`, { pages }); + rbacMsg = `Updated role default for ${editingRole}`; + cancelEdit(); + await loadRbac(); + } catch (e) { + rbacErr = e.body?.detail || "Failed to save role"; + } + } + onMount(() => { loadPasskeys(); if (currentUser.role === "admin") loadRbac(); @@ -299,12 +336,31 @@
Role Defaults
-{role}
+