Files
ss-tools/frontend/src/lib/components/layout/sidebarNavigation.js

178 lines
5.2 KiB
JavaScript

// [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]