mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 22:25:30 +00:00
feat: add confirmed Take down stack action with optional volume removal (#1599)
* feat: add confirmed Take down stack action with optional volume removal Expose Take down in the stack header and sidebar with a confirmation dialog that runs compose down while keeping the stack definition on disk. Optional volume removal is gated by node capability and stack:deploy permission, with remote gateway preflight before proxying removeVolumes requests. Closes #1582 * fix: reset take-down volume checkbox when dialog closes * test: align getStackMenuVisibility assertions with showTakeDown key getStackMenuVisibility now returns a fifth lifecycle flag, showTakeDown, but three exhaustive toEqual assertions still listed only the prior four keys and failed. Add the expected showTakeDown value to each: true for the partial and exited running-stack cases, false for the self stack. * test: cover Take down visibility for running non-self stacks The getStackMenuVisibility assertions exercised the partial and exited branches and the self-stack guard, but not the raw === 'running' literal that drives showTakeDown for a normal running stack. Add a case so a regression dropping 'running' from that check is caught. * fix: drop Take down from header overflow and wire activity shortcut Remove duplicate Take down from More actions. Keep inline button when running, sidebar menu, and Cmd+ArrowDown. Record stack_taken_down in activity on successful POST /down.
This commit is contained in:
@@ -12,12 +12,13 @@ function makeCtx(overrides: Partial<StackMenuCtx> = {}): StackMenuCtx {
|
||||
isBusy: false,
|
||||
isAdmin: true,
|
||||
canDelete: true,
|
||||
canDeploy: true,
|
||||
canEditLabels: true,
|
||||
canCreateLabels: true,
|
||||
isPinned: false,
|
||||
labels: [],
|
||||
assignedLabelIds: [],
|
||||
menuVisibility: { showDeploy: false, showStop: true, showRestart: true, showUpdate: false },
|
||||
menuVisibility: { showDeploy: false, showStop: true, showRestart: true, showUpdate: false, showTakeDown: true },
|
||||
openAlertSheet: vi.fn(),
|
||||
openAutoHeal: vi.fn(),
|
||||
checkUpdates: vi.fn(),
|
||||
@@ -26,6 +27,7 @@ function makeCtx(overrides: Partial<StackMenuCtx> = {}): StackMenuCtx {
|
||||
stop: vi.fn(),
|
||||
restart: vi.fn(),
|
||||
update: vi.fn(),
|
||||
takeDown: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
pin: vi.fn(),
|
||||
unpin: vi.fn(),
|
||||
@@ -162,17 +164,52 @@ describe('useStackMenuItems', () => {
|
||||
|
||||
it('lifecycle items follow menuVisibility flags', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('web.yml', makeCtx({
|
||||
menuVisibility: { showDeploy: true, showStop: false, showRestart: false, showUpdate: true },
|
||||
menuVisibility: { showDeploy: true, showStop: false, showRestart: false, showUpdate: true, showTakeDown: false },
|
||||
})));
|
||||
const lifecycle = result.current.find(g => g.id === 'lifecycle')!;
|
||||
const ids = lifecycle.items.map(i => i.id);
|
||||
expect(ids).toEqual(['deploy', 'update', 'schedule']);
|
||||
});
|
||||
|
||||
it('shows Take down in lifecycle when showTakeDown and canDeploy', () => {
|
||||
const takeDown = vi.fn();
|
||||
const { result } = renderHook(() => useStackMenuItems('web.yml', makeCtx({
|
||||
takeDown,
|
||||
menuVisibility: { showDeploy: false, showStop: true, showRestart: true, showUpdate: false, showTakeDown: true },
|
||||
})));
|
||||
const lifecycle = result.current.find(g => g.id === 'lifecycle')!;
|
||||
const item = lifecycle.items.find(i => i.id === 'take-down');
|
||||
expect(item?.label).toBe('Take down');
|
||||
expect(item?.shortcut).toBe('⌘↓');
|
||||
item?.onSelect();
|
||||
expect(takeDown).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('hides deploy lifecycle items when canDeploy is false', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('web.yml', makeCtx({
|
||||
canDeploy: false,
|
||||
menuVisibility: { showDeploy: true, showStop: true, showRestart: true, showUpdate: true, showTakeDown: true },
|
||||
})));
|
||||
const lifecycle = result.current.find(g => g.id === 'lifecycle')!;
|
||||
const ids = lifecycle.items.map(i => i.id);
|
||||
expect(ids).not.toContain('deploy');
|
||||
expect(ids).not.toContain('take-down');
|
||||
expect(ids).toEqual(['schedule']);
|
||||
});
|
||||
|
||||
it('disables take down for the self stack', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('sencho.yml', makeCtx({
|
||||
isSelfStack: true,
|
||||
menuVisibility: { showDeploy: false, showStop: true, showRestart: true, showUpdate: false, showTakeDown: true },
|
||||
})));
|
||||
const lifecycle = result.current.find(g => g.id === 'lifecycle')!;
|
||||
expect(lifecycle.items.find(i => i.id === 'take-down')?.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('disables action lifecycle items when isBusy but leaves schedule enabled', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('web.yml', makeCtx({
|
||||
isBusy: true,
|
||||
menuVisibility: { showDeploy: true, showStop: true, showRestart: true, showUpdate: true },
|
||||
menuVisibility: { showDeploy: true, showStop: true, showRestart: true, showUpdate: true, showTakeDown: false },
|
||||
})));
|
||||
const lifecycle = result.current.find(g => g.id === 'lifecycle')!;
|
||||
const actionItems = lifecycle.items.filter(i => i.id !== 'schedule');
|
||||
|
||||
Reference in New Issue
Block a user