// [DEF:frontend.src.lib.components.layout.sidebarNavigation:Module] // @COMPLEXITY: 3 // @SEMANTICS: navigation, sidebar, rbac, menu, filtering // @PURPOSE: Build sidebar navigation categories filtered by current user permissions. // @LAYER: UI // @RELATION: DEPENDS_ON -> frontend.src.lib.auth.permissions.hasPermission // @RELATION: USED_BY -> frontend.src.lib.components.layout.Sidebar // @INVARIANT: Admin role can access all categories and subitems through permission utility. import { hasPermission } from "$lib/auth/permissions.js"; // [DEF:isItemAllowed:Function] // @PURPOSE: Check whether a single menu node can be shown for a given user. // @PRE: item can contain optional requiredPermission/requiredAction. // @POST: Returns true when no permission is required or permission check passes. function isItemAllowed(user, item) { if (!item?.requiredPermission) return true; return hasPermission( user, item.requiredPermission, item.requiredAction || "READ", ); } // [/DEF:isItemAllowed:Function] // [DEF:buildSidebarCategories:Function] // @PURPOSE: Build translated sidebar categories and filter them by RBAC permissions. // @PRE: i18nState provides nav labels; user can be null. // @POST: Returns only categories/subitems available for provided user. export function buildSidebarCategories(i18nState, user) { const nav = i18nState?.nav || {}; const categories = [ { id: "dashboards", label: nav.dashboards, icon: "dashboard", tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200", path: "/dashboards", requiredPermission: "plugin:migration", requiredAction: "READ", subItems: [ { label: nav.overview, path: "/dashboards", requiredPermission: "plugin:migration", requiredAction: "READ", }, { label: nav.health_center, path: "/dashboards/health", requiredPermission: "plugin:migration", requiredAction: "READ", }, ], }, { id: "datasets", label: nav.datasets, icon: "database", tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200", path: "/datasets", requiredPermission: "plugin:migration", requiredAction: "READ", subItems: [ { label: nav.all_datasets, path: "/datasets", requiredPermission: "plugin:migration", requiredAction: "READ", }, { label: nav.dataset_review_workspace, path: "/datasets/review", requiredPermission: "plugin:migration", requiredAction: "READ", }, ], }, { id: "storage", label: nav.storage, icon: "storage", tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200", path: "/storage", requiredPermission: "plugin:storage", requiredAction: "READ", subItems: [ { label: nav.backups, path: "/storage/backups", requiredPermission: "plugin:storage", requiredAction: "READ", }, { label: nav.repositories, path: "/storage/repos", requiredPermission: "plugin:storage", requiredAction: "READ", }, ], }, { id: "reports", label: nav.reports, icon: "reports", tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200", path: "/reports", requiredPermission: "tasks", requiredAction: "READ", subItems: [ { label: nav.reports, path: "/reports", requiredPermission: "tasks", requiredAction: "READ", }, ], }, { id: "profile", label: nav.profile, icon: "admin", tone: "from-indigo-100 to-indigo-200 text-indigo-700 ring-indigo-200", path: "/profile", subItems: [{ label: nav.profile, path: "/profile" }], }, { id: "admin", label: nav.admin, icon: "admin", tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200", path: "/admin", subItems: [ { label: nav.admin_users, path: "/admin/users", requiredPermission: "admin:users", requiredAction: "READ", }, { label: nav.admin_roles, path: "/admin/roles", requiredPermission: "admin:roles", requiredAction: "READ", }, { label: nav.settings, path: "/settings", requiredPermission: "admin:settings", requiredAction: "READ", }, ], }, ]; return categories .map((category) => { const visibleSubItems = (category.subItems || []).filter((subItem) => isItemAllowed(user, subItem), ); return { ...category, subItems: visibleSubItems, }; }) .filter((category) => { const categoryVisible = isItemAllowed(user, category); if (!categoryVisible) return false; const hasVisibleSubItems = Array.isArray(category.subItems) && category.subItems.length > 0; return hasVisibleSubItems; }); } // [/DEF:buildSidebarCategories:Function] // [/DEF:frontend.src.lib.components.layout.sidebarNavigation:Module]