From b97952567d23448fcc5c0ae3fe2a34e3c704c326 Mon Sep 17 00:00:00 2001 From: SaelixCode Date: Fri, 6 Mar 2026 14:45:37 -0500 Subject: [PATCH] feat: implement pre-deploy collision checks and universal two-stage teardown --- CHANGELOG.md | 3 ++ backend/src/index.ts | 34 ++++++++++++-------- frontend/src/components/AppStoreView.tsx | 41 ++++++++++++++---------- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e50879..f69d7b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- **Added:** Pre-deploy folder collision check to prevent silent configuration overwrites in the App Store. +- **Added:** UI subtitle during deployment to reassure users during long image downloads. +- **Changed:** Standardized manual stack deletion to use the Two-Stage Teardown (Compose Down -> File Wipe) to prevent ghost networks. - **Fixed:** Atomic Rollback failure where non-empty directories caused silent file system errors. - **Added:** Two-Stage Teardown mechanism to ensure `docker compose down` sweeps up ghost networks before deployment files are deleted. - **Added:** Smart Error Parser with telemetry-ready rule IDs to translate cryptic Docker output. diff --git a/backend/src/index.ts b/backend/src/index.ts index 1f673376..2cd56825 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -22,7 +22,7 @@ import { MonitorService } from './services/MonitorService'; import { templateService } from './services/TemplateService'; import { ErrorParser } from './utils/ErrorParser'; import YAML from 'yaml'; -import { promises as fsPromises } from 'fs'; +import fs, { promises as fsPromises } from 'fs'; const execAsync = promisify(exec); @@ -568,24 +568,22 @@ app.post('/api/stacks', async (req: Request, res: Response) => { } }); -app.delete('/api/stacks/:stackName', async (req: Request, res: Response) => { +app.delete('/api/stacks/:name', async (req: Request, res: Response) => { + const stackName = req.params.name as string; try { - const stackName = req.params.stackName as string; - - // Tear down the stack first to avoid ghost containers + // Stage 1: Tell Docker to clean up ghost networks/containers try { - console.log(`Tearing down stack: ${stackName}`); - // Send the down command synchronously before deleting the files - await composeService.runCommand(stackName, 'down', terminalWs || undefined); - } catch (downError) { - console.warn(`Failed to tear down stack ${stackName}, proceeding with file deletion.`, downError); + await composeService.downStack(stackName); + } catch (downErr) { + console.warn(`[Teardown] Docker down failed or nothing to clean up for ${stackName}`); } + // Stage 2: Obliterate the files await fileSystemService.deleteStack(stackName); - res.json({ message: 'Stack deleted successfully' }); - } catch (error) { - console.error('Failed to delete stack:', error); - res.status(500).json({ error: 'Failed to delete stack' }); + + res.json({ success: true }); + } catch (error: any) { + res.status(500).json({ error: error.message || 'Failed to delete stack' }); } }); @@ -1069,6 +1067,14 @@ app.post('/api/templates/deploy', async (req: Request, res: Response) => { return res.status(400).json({ error: 'stackName and template are required' }); } + const stackPath = path.join(fileSystemService.getBaseDir(), stackName); + if (fs.existsSync(stackPath)) { + return res.status(409).json({ + error: `A stack directory named '${stackName}' already exists. Please choose a different Stack Name.`, + rolledBack: false + }); + } + // 1. Create stack directory await fileSystemService.createStack(stackName); diff --git a/frontend/src/components/AppStoreView.tsx b/frontend/src/components/AppStoreView.tsx index a5507037..18065fdd 100644 --- a/frontend/src/components/AppStoreView.tsx +++ b/frontend/src/components/AppStoreView.tsx @@ -415,24 +415,31 @@ export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) { - + {isDeploying && ( +

+ This may take a few minutes for large images. +

)} - +
)}