mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-25 01:37:05 +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:
@@ -133,6 +133,8 @@ export interface StackIdentityHeaderProps {
|
||||
rollbackStack: () => Promise<void>;
|
||||
scanStackConfig: () => Promise<void>;
|
||||
requestDeleteStack: () => void;
|
||||
/** True when this stack is the running Sencho instance on the active node. */
|
||||
isSelfStack?: boolean;
|
||||
stackMuteActions?: ReturnType<typeof useStackMuteActions>;
|
||||
}
|
||||
|
||||
@@ -159,8 +161,10 @@ export function StackIdentityHeader({
|
||||
rollbackStack,
|
||||
scanStackConfig,
|
||||
requestDeleteStack,
|
||||
isSelfStack = false,
|
||||
stackMuteActions,
|
||||
}: StackIdentityHeaderProps) {
|
||||
const selfProtected = isSelfStack;
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Identity block */}
|
||||
@@ -248,18 +252,18 @@ export function StackIdentityHeader({
|
||||
{loadingAction === 'restart' ? 'Restarting...' : 'Restart'}
|
||||
</Button>
|
||||
) : (
|
||||
<Button type="button" size="sm" data-testid="stack-deploy-button" className="rounded-lg max-md:h-11 bg-brand text-brand-foreground hover:bg-brand/90" onClick={deployStack} disabled={loadingAction !== null}>
|
||||
<Button type="button" size="sm" data-testid="stack-deploy-button" className="rounded-lg max-md:h-11 bg-brand text-brand-foreground hover:bg-brand/90" onClick={deployStack} disabled={loadingAction !== null || selfProtected}>
|
||||
<Play className="w-4 h-4 mr-2" strokeWidth={1.5} />
|
||||
{loadingAction === 'deploy' ? 'Starting...' : 'Start'}
|
||||
</Button>
|
||||
)}
|
||||
{isRunning && (
|
||||
<Button type="button" size="sm" variant="outline" className="rounded-lg max-md:h-11" onClick={stopStack} disabled={loadingAction !== null}>
|
||||
<Button type="button" size="sm" variant="outline" className="rounded-lg max-md:h-11" onClick={stopStack} disabled={loadingAction !== null || selfProtected}>
|
||||
<Square className="w-4 h-4 mr-2" strokeWidth={1.5} />
|
||||
{loadingAction === 'stop' ? 'Stopping...' : 'Stop'}
|
||||
</Button>
|
||||
)}
|
||||
<Button type="button" size="sm" variant="outline" className="rounded-lg max-md:h-11" onClick={updateStack} disabled={loadingAction !== null}>
|
||||
<Button type="button" size="sm" variant="outline" className="rounded-lg max-md:h-11" onClick={updateStack} disabled={loadingAction !== null || selfProtected}>
|
||||
<CloudDownload className="w-4 h-4 mr-2" strokeWidth={1.5} />
|
||||
{loadingAction === 'update' ? 'Updating...' : 'Update'}
|
||||
</Button>
|
||||
@@ -274,7 +278,7 @@ export function StackIdentityHeader({
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
{canRollback && (
|
||||
<DropdownMenuItem onClick={rollbackStack} disabled={loadingAction !== null}>
|
||||
<DropdownMenuItem onClick={rollbackStack} disabled={loadingAction !== null || selfProtected}>
|
||||
<Undo2 className="w-4 h-4 mr-2" strokeWidth={1.5} />
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span>{loadingAction === 'rollback' ? 'Rolling back...' : 'Rollback'}</span>
|
||||
@@ -299,7 +303,7 @@ export function StackIdentityHeader({
|
||||
{canDelete && (
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive focus:bg-destructive/10"
|
||||
disabled={loadingAction !== null}
|
||||
disabled={loadingAction !== null || selfProtected}
|
||||
onClick={requestDeleteStack}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" strokeWidth={1.5} />
|
||||
|
||||
Reference in New Issue
Block a user