diff --git a/dashboard/backend/rbac.py b/dashboard/backend/rbac.py
index 20da62e..a385ee7 100644
--- a/dashboard/backend/rbac.py
+++ b/dashboard/backend/rbac.py
@@ -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
diff --git a/dashboard/backend/routers/auth.py b/dashboard/backend/routers/auth.py
index 9246002..0e3775f 100644
--- a/dashboard/backend/routers/auth.py
+++ b/dashboard/backend/routers/auth.py
@@ -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":
diff --git a/dashboard/frontend/src/App.svelte b/dashboard/frontend/src/App.svelte
index eeb0f2b..e0dd134 100644
--- a/dashboard/frontend/src/App.svelte
+++ b/dashboard/frontend/src/App.svelte
@@ -11,7 +11,7 @@
import Security from "./routes/Security.svelte";
import Login from "./routes/Login.svelte";
import { onMount } from "svelte";
- import { getToken, checkAuth, setToken, setRefreshToken, currentUser, setCurrentUser } from "./lib/api.js";
+ import { getToken, checkAuth, setToken, setRefreshToken, currentUser, setCurrentUser, getPreferences, savePreferences } from "./lib/api.js";
let page = $state("dashboard");
let dark = $state(false);
@@ -20,6 +20,8 @@
let loading = $state(true);
let userRole = $state("");
let allowedPages = $state([]);
+ let sidebarOrder = $state(null);
+ let orderSaveTimer = null;
async function fetchUserInfo() {
try {
@@ -27,11 +29,24 @@
setCurrentUser(data);
userRole = data.role || "admin";
allowedPages = data.pages || "*";
+ // Fetch sidebar preferences
+ try {
+ const prefs = await getPreferences();
+ sidebarOrder = prefs.sidebar_order || null;
+ } catch {}
} catch (e) {
console.error("Failed to fetch user info:", e);
}
}
+ function handleOrderChange(newOrder) {
+ sidebarOrder = newOrder;
+ clearTimeout(orderSaveTimer);
+ orderSaveTimer = setTimeout(() => {
+ savePreferences({ sidebar_order: newOrder }).catch(() => {});
+ }, 500);
+ }
+
function hasPageAccess(pageId) {
if (allowedPages === "*") return true;
return Array.isArray(allowedPages) && allowedPages.includes(pageId);
@@ -108,12 +123,12 @@
{:else if !authorized}