Merge pull request 'feat: add UI to edit role default page permissions' (#11) from dev into main
Deploy Dashboard / deploy (push) Successful in 46s
Deploy Dashboard / deploy (push) Successful in 46s
This commit was merged in pull request #11.
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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 -->
|
||||
<div class="mb-4">
|
||||
<p class="text-xs font-medium text-surface-500 mb-2">Role Defaults</p>
|
||||
<div class="space-y-1.5">
|
||||
<div class="space-y-2">
|
||||
{#each Object.entries(rbacConfig.role_defaults || {}) as [role, cfg]}
|
||||
{#if editingRole === role}
|
||||
<div class="p-3 bg-surface-50 dark:bg-surface-700 rounded-lg space-y-2">
|
||||
<p class="text-xs font-medium text-surface-700 dark:text-surface-200">{role}</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button onclick={() => toggleRolePage("*")} class="px-2 py-0.5 text-xs rounded-md transition-colors {editRolePages.includes('*') ? 'bg-primary-600 text-white' : 'bg-surface-200 dark:bg-surface-600 text-surface-500'}">* (all)</button>
|
||||
{#each allPages as p}
|
||||
<button onclick={() => toggleRolePage(p)} class="px-2 py-0.5 text-xs rounded-md transition-colors {editRolePages.includes(p) ? 'bg-primary-600 text-white' : 'bg-surface-200 dark:bg-surface-600 text-surface-500'}">{p}</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button onclick={saveRoleDefault} class="px-3 py-1 text-xs font-medium text-white bg-primary-600 hover:bg-primary-700 rounded-md transition-colors">Save</button>
|
||||
<button onclick={cancelEdit} class="px-3 py-1 text-xs font-medium text-surface-600 hover:text-surface-800 dark:text-surface-400 dark:hover:text-surface-200">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-between p-2 bg-surface-50 dark:bg-surface-700 rounded-lg">
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="font-medium text-surface-700 dark:text-surface-300 w-16">{role}</span>
|
||||
<span class="text-surface-400">{cfg.pages === "*" ? "All pages" : (cfg.pages || []).join(", ")}</span>
|
||||
</div>
|
||||
<button onclick={() => startEditRole(role, cfg.pages)} class="text-xs text-primary-500 hover:text-primary-700">Edit</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user