feat: access control UI in Settings + sidebar drag-and-drop reordering
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 1m13s

- Add Access Control card in Settings (admin-only): role defaults display, user overrides CRUD
- Add sidebar drag-and-drop reordering with per-user persistence in rbac.json
- Backend: GET/PUT /api/auth/preferences endpoints, sidebar order helpers in rbac.py
- Frontend: HTML5 drag API with grip handles, smart order merge, debounced save
- Preserve sidebar_order when updating page overrides
This commit is contained in:
Gan, Jimmy
2026-03-01 14:48:35 +08:00
parent 9992105b49
commit a8debcfb4b
6 changed files with 334 additions and 20 deletions
+12
View File
@@ -61,6 +61,18 @@ def get_pages(username: str, role: str) -> list | str:
return rbac.get("role_defaults", {}).get(role, {}).get("pages", [])
def get_sidebar_order(username: str) -> dict | None:
rbac = load_rbac()
overrides = rbac.get("user_overrides", {})
return overrides.get(username, {}).get("sidebar_order")
def save_sidebar_order(username: str, order: dict):
rbac = load_rbac()
rbac.setdefault("user_overrides", {}).setdefault(username, {})["sidebar_order"] = order
save_rbac(rbac)
def has_page_access(user: User, page: str) -> bool:
if user.pages == "*":
return True
+19 -1
View File
@@ -364,10 +364,28 @@ async def rbac_set_override(username: str, request: Request, current_user = Depe
if pages is None:
raise HTTPException(status_code=400, detail="Missing pages field")
rbac = load_rbac()
rbac.setdefault("user_overrides", {})[username] = {"pages": pages}
existing = rbac.setdefault("user_overrides", {}).get(username, {})
existing["pages"] = pages
rbac["user_overrides"][username] = existing
save_rbac(rbac)
return {"ok": True}
@router.get("/preferences")
async def get_preferences(current_user = Depends(auth.get_current_user)):
from rbac import get_sidebar_order
order = get_sidebar_order(current_user.username)
return {"sidebar_order": order}
@router.put("/preferences")
async def save_preferences(request: Request, current_user = Depends(auth.get_current_user)):
from rbac import save_sidebar_order
body = await request.json()
order = body.get("sidebar_order")
if order is None or not isinstance(order, dict):
raise HTTPException(status_code=400, detail="Missing sidebar_order dict")
save_sidebar_order(current_user.username, order)
return {"ok": True}
@router.delete("/rbac/overrides/{username}")
async def rbac_delete_override(username: str, current_user = Depends(auth.get_current_user)):
if current_user.role != "admin":