mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 06:23:18 +00:00
feat: block self-stack lifecycle ops with UI and preflight guardrails (#1569)
* feat: block self-stack lifecycle ops with UI and preflight guardrails Refuse update, deploy, down, stop, and delete when the stack matches Sencho's compose project. Return 409 self_stack_protected. Expose isSelf on /statuses and disable guarded UI actions. Add SelfStackProtectedDialog and self-managed-stack preflight warning. Closes #1564 * fix: add missing stackSelfFlags mock to useSidebarContextMenu test The production hook now reads stackListState.stackSelfFlags[file], but the test mock did not include it, causing 6 tests to fail with TypeError: Cannot read properties of undefined (reading 'web.yml'). * fix: harden self-stack protection during startup Add a global environment preflight warning when Sencho is managed inside COMPOSE_DIR. Align status decoration and route guards on Docker label fallback detection. Block rollback and service-level stop on the protected self stack. * fix: add self_stack_location to diagnostics-route expected check IDs
This commit is contained in:
@@ -7,6 +7,7 @@ import type { StackMenuCtx } from '@/components/sidebar/sidebar-types';
|
||||
function makeCtx(overrides: Partial<StackMenuCtx> = {}): StackMenuCtx {
|
||||
return {
|
||||
stackStatus: 'running',
|
||||
isSelfStack: false,
|
||||
canOpenApp: true,
|
||||
isBusy: false,
|
||||
isAdmin: true,
|
||||
@@ -179,4 +180,10 @@ describe('useStackMenuItems', () => {
|
||||
const scheduleItem = lifecycle.items.find(i => i.id === 'schedule')!;
|
||||
expect(scheduleItem.disabled).toBeFalsy();
|
||||
});
|
||||
|
||||
it('disables delete for the self stack', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('sencho.yml', makeCtx({ isSelfStack: true })));
|
||||
const destructive = result.current.find(g => g.id === 'destructive')!;
|
||||
expect(destructive.items[0].disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { MenuGroup, MenuItem, StackMenuCtx } from '@/components/sidebar/sid
|
||||
|
||||
export function useStackMenuItems(_file: string, ctx: StackMenuCtx): MenuGroup[] {
|
||||
const {
|
||||
stackStatus, canOpenApp, isBusy, isAdmin, canDelete, canEditLabels, isPinned, labels,
|
||||
stackStatus, isSelfStack, canOpenApp, isBusy, isAdmin, canDelete, canEditLabels, isPinned, labels,
|
||||
openAlertSheet, openAutoHeal, checkUpdates, openStackApp,
|
||||
deploy, stop, restart, update, remove, pin, unpin, toggleLabel,
|
||||
menuVisibility, openScheduleTask,
|
||||
@@ -88,13 +88,21 @@ export function useStackMenuItems(_file: string, ctx: StackMenuCtx): MenuGroup[]
|
||||
if (canDelete) {
|
||||
groups.push({
|
||||
id: 'destructive',
|
||||
items: [{ id: 'delete', label: 'Delete', icon: Trash2, shortcut: '⌘⌫', destructive: true, onSelect: remove }],
|
||||
items: [{
|
||||
id: 'delete',
|
||||
label: 'Delete',
|
||||
icon: Trash2,
|
||||
shortcut: '⌘⌫',
|
||||
destructive: true,
|
||||
disabled: isSelfStack,
|
||||
onSelect: remove,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
}, [
|
||||
stackStatus, canOpenApp, isBusy, isAdmin, canDelete, canEditLabels, isPinned, labels,
|
||||
stackStatus, isSelfStack, canOpenApp, isBusy, isAdmin, canDelete, canEditLabels, isPinned, labels,
|
||||
showDeploy, showStop, showRestart, showUpdate,
|
||||
openAlertSheet, openAutoHeal, checkUpdates, openStackApp,
|
||||
deploy, stop, restart, update, remove, pin, unpin, toggleLabel, openScheduleTask,
|
||||
|
||||
Reference in New Issue
Block a user