Files
sencho/frontend/src/components/EditorLayout/operation-phase.ts
T
Anso 3f1f15a6f4 fix: keep running containers until stack pull/build succeeds (#1657)
* fix: keep running containers until stack pull/build succeeds

Acquire images before reconcile, capture a recovery generation for
compensation, and only remove classified orphans after handoff.

* fix: address recovery audit blockers for safe stack updates

Retire abandoned and expired recovery artifacts, probe compensated
runtimes before reporting rollback success, preserve local Docker when
deleting a node, validate the exact Compose invocation before capture,
and repair updateStack return-contract fixtures.

* fix: resolve ESLint errors blocking CI on this branch

Unused-import and unused-variable errors left over from the stack
deletion refactor: MeshService in stacks.ts (its opt-out cascade moved
into DeployedStackDeletionService), a redundant pruneVolumes
destructure in deleteDeployedStack (the real one is re-derived from
the same input object inside runDeletionBody), and an unused beforeAll
import in a Docker-integration test stub. Also scopes the webhook
pull-action case body in a block to satisfy no-case-declarations;
purely syntactic, no behavior change.

* fix: harden recovery probe, cleanup retry, and failed-pull Docker test

Reject absent or unhealthy expected replicas before reporting rollback
success, keep cleanup records until artifacts are actually removed, fail
closed when a mesh override cannot be generated, and assert a real
failed pull leaves the original container running.

* fix: verify recovery probe image identity and stack-scoped override paths

Reject recovered runtimes that use the wrong image or leave scale-zero
services running, and confine tombstone override deletion to the intent
stack directory so forged cross-stack paths cannot be swept.

* test: batch notification cap fixtures in a SQLite transaction

Unbatched 1200-row inserts were timing out at the default 30s under
CI load even though the same assertions pass in under 2s when green.
2026-07-21 12:18:01 -04:00

47 lines
2.3 KiB
TypeScript

import type { ParsedLogRow } from '@/components/log-rendering/composeLogParser';
import type { ActionVerb } from '@/context/DeployFeedbackContext';
// Classify the current operation phase from streamed compose output, returning a
// display label or null before any phase marker. The backend emits explicit
// `=== ... ===` phase banners during update (pull / recreate / prune), and docker
// compose emits `[+] Pulling/Creating/Starting` lines that the log parser tags as
// PULL/CREATE/START. Scanning newest-first returns the latest recognized phase,
// since phases run in sequence. Labels are action-aware: "Recreating containers"
// is update wording; deploy/install show "Creating containers".
export function classifyOperationPhase(rows: ParsedLogRow[], action: ActionVerb): string | null {
for (let i = rows.length - 1; i >= 0; i--) {
const { message, stage } = rows[i];
if (message.includes('Pruned dangling images') || message.includes('Pruning')) {
return 'Pruning images';
}
if (message.includes('Recreating containers')) {
return 'Recreating containers';
}
if (stage === 'START') {
return 'Starting containers';
}
if (stage === 'CREATE') {
return action === 'update' ? 'Recreating containers' : 'Creating containers';
}
// The update banner and the parser's `[+] Pulling` tag cover the headline,
// but compose v2's per-layer progress (`<service> Pulling`, `Downloading`,
// `Extracting`, ...) arrives as plain lines; match them so the phase reads
// "Pulling images" throughout the download rather than lagging behind.
if (
message.includes('Pulling latest images') ||
message.includes('Pulling from') ||
stage === 'PULL' ||
/\b(Pulling|Downloading|Extracting|Verifying Checksum|Pull complete|Download complete|Pulled)\b/.test(message)
) {
return 'Pulling images';
}
if (stage === 'BUILD') {
return 'Building images';
}
if (message.includes('Backup created for atomic') || message.includes('Validating stack for update') || message.includes('Capturing rollback generation')) {
return 'Preparing';
}
}
return null;
}