mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-19 06:46:23 +00:00
feat(rbac): make Settings authorization permission-aware (#1738)
* feat(rbac): make Settings authorization permission-aware Align Settings visibility and mutations with the existing permission matrix so Node Admin can edit node-scoped operational settings while system and credential surfaces stay Admin-protected. * fix(rbac): tighten settings permission buckets and tests Collapse settings key permission maps into one source of truth, and cover mixed PATCH atomicity plus image-update enabled writes. * fix(rbac): tighten Settings scoped grants and CI assertions Empty settings PATCH fails closed, node:manage is scoped to the active node, system-only Settings stay hidden without system:settings, and Check updates / webhooks mutate gates follow the permission matrix. * fix(rbac): defer Settings section fallback until authz is ready Keep deep links to permission-gated sections (e.g. license) intact while can() is still fail-closed during permission metadata load. * docs(settings): clarify Notifications channels vs routing authz Channels use node:manage via /api/agents; routing and mute stay Admin-only.
This commit is contained in:
@@ -22,6 +22,7 @@ function makeCtx(overrides: Partial<StackMenuCtx> = {}): StackMenuCtx {
|
||||
openAlertSheet: vi.fn(),
|
||||
openAutoHeal: vi.fn(),
|
||||
checkUpdates: vi.fn(),
|
||||
canCheckUpdates: true,
|
||||
openStackApp: vi.fn(),
|
||||
deploy: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
@@ -84,6 +85,18 @@ describe('useStackMenuItems', () => {
|
||||
expect(inspect.items.find(i => i.id === 'open-app')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('shows Check updates when canCheckUpdates', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('web.yml', makeCtx({ canCheckUpdates: true })));
|
||||
const inspect = result.current.find(g => g.id === 'inspect')!;
|
||||
expect(inspect.items.find(i => i.id === 'check-updates')).toBeDefined();
|
||||
});
|
||||
|
||||
it('hides Check updates when !canCheckUpdates', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('web.yml', makeCtx({ canCheckUpdates: false })));
|
||||
const inspect = result.current.find(g => g.id === 'inspect')!;
|
||||
expect(inspect.items.find(i => i.id === 'check-updates')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('toggles Pin / Unpin label based on isPinned', () => {
|
||||
const pinned = renderHook(() => useStackMenuItems('web.yml', makeCtx({ isPinned: true })));
|
||||
const unpinned = renderHook(() => useStackMenuItems('web.yml', makeCtx({ isPinned: false })));
|
||||
|
||||
@@ -60,6 +60,7 @@ export function useStackKeyboardShortcuts(
|
||||
e.preventDefault();
|
||||
ctx.openAutoHeal();
|
||||
} else if (key === 'u') {
|
||||
if (!ctx.canCheckUpdates) return;
|
||||
e.preventDefault();
|
||||
ctx.checkUpdates();
|
||||
} else if (key === 'p') {
|
||||
|
||||
@@ -21,7 +21,7 @@ import type { MenuGroup, MenuItem, StackMenuCtx } from '@/components/sidebar/sid
|
||||
export function useStackMenuItems(_file: string, ctx: StackMenuCtx): MenuGroup[] {
|
||||
const {
|
||||
stackStatus, isSelfStack, canOpenApp, isBusy, isAdmin, canDelete, canDeploy, canEditLabels, isPinned, labels,
|
||||
openAlertSheet, openAutoHeal, checkUpdates, openStackApp,
|
||||
openAlertSheet, openAutoHeal, canCheckUpdates, checkUpdates, openStackApp,
|
||||
deploy, stop, restart, update, takeDown, remove, pin, unpin, toggleLabel,
|
||||
menuVisibility, openScheduleTask,
|
||||
canMuteNotifications, muteStackAll, muteStackDeploySuccess, muteStackMonitor, openStackMuteRules,
|
||||
@@ -35,7 +35,9 @@ export function useStackMenuItems(_file: string, ctx: StackMenuCtx): MenuGroup[]
|
||||
{ id: 'alerts', label: 'Alerts', icon: BellRing, shortcut: 'A', onSelect: openAlertSheet },
|
||||
{ id: 'auto-heal', label: 'Auto-Heal', icon: Activity, shortcut: 'H', onSelect: openAutoHeal },
|
||||
];
|
||||
inspect.push({ id: 'check-updates', label: 'Check updates', icon: RefreshCw, shortcut: 'U', onSelect: checkUpdates });
|
||||
if (canCheckUpdates) {
|
||||
inspect.push({ id: 'check-updates', label: 'Check updates', icon: RefreshCw, shortcut: 'U', onSelect: checkUpdates });
|
||||
}
|
||||
if (stackStatus === 'running' && canOpenApp) {
|
||||
inspect.push({ id: 'open-app', label: 'Open App', icon: ArrowUpRight, shortcut: '↗', onSelect: openStackApp });
|
||||
}
|
||||
@@ -108,7 +110,7 @@ export function useStackMenuItems(_file: string, ctx: StackMenuCtx): MenuGroup[]
|
||||
}, [
|
||||
stackStatus, isSelfStack, canOpenApp, isBusy, isAdmin, canDelete, canDeploy, canEditLabels, isPinned, labels,
|
||||
showDeploy, showStop, showRestart, showUpdate, showTakeDown,
|
||||
openAlertSheet, openAutoHeal, checkUpdates, openStackApp,
|
||||
openAlertSheet, openAutoHeal, canCheckUpdates, checkUpdates, openStackApp,
|
||||
deploy, stop, restart, update, takeDown, remove, pin, unpin, toggleLabel, openScheduleTask,
|
||||
canMuteNotifications, muteStackAll, muteStackDeploySuccess, muteStackMonitor, openStackMuteRules,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user