feat: role-based UI access control
- Hide Admin sidebar section for non-ADMIN users - Add RequireAdmin route guard — /admin/* redirects to / for non-admin - Move App Config from admin section to main Config tab (per-app, visible when app selected). VIEWER sees read-only, OPERATOR+ can edit - Hide diagram node toolbar for VIEWER (onNodeAction conditional) - Add useIsAdmin/useCanControl helpers to centralize role checks - Remove App Config from admin sidebar tree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ import { useAgents } from '../api/queries/agents';
|
||||
import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions';
|
||||
import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac';
|
||||
import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac';
|
||||
import { useAuthStore } from '../auth/auth-store';
|
||||
import { useAuthStore, useIsAdmin } from '../auth/auth-store';
|
||||
import { useEnvironmentStore } from '../api/environment-store';
|
||||
import { useState, useMemo, useCallback, useEffect, useRef, createElement } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -277,6 +277,9 @@ function LayoutContent() {
|
||||
const queryClient = useQueryClient();
|
||||
const { timeRange, autoRefresh, refreshTimeRange } = useGlobalFilters();
|
||||
|
||||
// --- Role checks ----------------------------------------------------
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
// --- Environment filtering -----------------------------------------
|
||||
const selectedEnv = useEnvironmentStore((s) => s.environment);
|
||||
const setSelectedEnvRaw = useEnvironmentStore((s) => s.setEnvironment);
|
||||
@@ -668,25 +671,27 @@ function LayoutContent() {
|
||||
</Sidebar.Section>
|
||||
)}
|
||||
|
||||
{/* Admin section — stays in place, expands when on admin pages */}
|
||||
<Sidebar.Section
|
||||
icon={createElement(Settings, { size: 16 })}
|
||||
label="Admin"
|
||||
open={adminOpen}
|
||||
onToggle={toggleAdmin}
|
||||
active={isAdminPage}
|
||||
>
|
||||
<SidebarTree
|
||||
nodes={adminTreeNodes}
|
||||
selectedPath={location.pathname}
|
||||
isStarred={isStarred}
|
||||
onToggleStar={toggleStar}
|
||||
filterQuery={filterQuery}
|
||||
persistKey="admin"
|
||||
autoRevealPath={sidebarRevealPath}
|
||||
onNavigate={handleSidebarNavigate}
|
||||
/>
|
||||
</Sidebar.Section>
|
||||
{/* Admin section — only visible to ADMIN role */}
|
||||
{isAdmin && (
|
||||
<Sidebar.Section
|
||||
icon={createElement(Settings, { size: 16 })}
|
||||
label="Admin"
|
||||
open={adminOpen}
|
||||
onToggle={toggleAdmin}
|
||||
active={isAdminPage}
|
||||
>
|
||||
<SidebarTree
|
||||
nodes={adminTreeNodes}
|
||||
selectedPath={location.pathname}
|
||||
isStarred={isStarred}
|
||||
onToggleStar={toggleStar}
|
||||
filterQuery={filterQuery}
|
||||
persistKey="admin"
|
||||
autoRevealPath={sidebarRevealPath}
|
||||
onNavigate={handleSidebarNavigate}
|
||||
/>
|
||||
</Sidebar.Section>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<Sidebar.Footer>
|
||||
|
||||
Reference in New Issue
Block a user