From 68d1ccb196960319e1afc20352fa9bf4156ef8aa Mon Sep 17 00:00:00 2001 From: keyhan Date: Mon, 18 May 2026 22:15:43 +0330 Subject: [PATCH] Track cancelled deployments separately. Co-authored-by: Cursor --- .../migrations/011_deployment_cancelled_status.sql | 10 ++++++++++ backend/src/build/build.service.ts | 6 +++--- backend/src/common/enums.ts | 1 + backend/src/deployments/deployments.service.ts | 7 +++++-- frontend/src/app/dashboard/admin/apps/page.tsx | 1 + frontend/src/app/dashboard/apps/[id]/page.tsx | 1 + frontend/src/app/dashboard/apps/page.tsx | 1 + frontend/src/app/dashboard/page.tsx | 2 ++ frontend/src/components/build-progress-modal.tsx | 13 +++++++++---- frontend/src/types/index.ts | 1 + 10 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 backend/migrations/011_deployment_cancelled_status.sql diff --git a/backend/migrations/011_deployment_cancelled_status.sql b/backend/migrations/011_deployment_cancelled_status.sql new file mode 100644 index 0000000..182c49e --- /dev/null +++ b/backend/migrations/011_deployment_cancelled_status.sql @@ -0,0 +1,10 @@ +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_type + WHERE typname = 'deployments_status_enum' + ) THEN + ALTER TYPE deployments_status_enum ADD VALUE IF NOT EXISTS 'cancelled'; + END IF; +END $$; diff --git a/backend/src/build/build.service.ts b/backend/src/build/build.service.ts index bd6e6e9..5c61858 100644 --- a/backend/src/build/build.service.ts +++ b/backend/src/build/build.service.ts @@ -32,7 +32,7 @@ interface ActiveBuildSession { } export interface BuildProgress { - phase: 'uploading' | 'building' | 'deploying' | 'done' | 'failed'; + phase: 'uploading' | 'building' | 'deploying' | 'done' | 'failed' | 'cancelled'; percent: number; bytesUploaded?: number; totalBytes?: number; @@ -98,7 +98,7 @@ export class BuildService { async cancelBuild(deploymentId: string): Promise { const session = this.activeBuilds.get(deploymentId); if (!session) { - this.setProgress(deploymentId, { phase: 'failed', percent: 0, message: 'Cancelled by user' }); + this.setProgress(deploymentId, { phase: 'cancelled', percent: 0, message: 'Cancelled by user' }); return; } @@ -139,7 +139,7 @@ export class BuildService { this.logger.log(`Cleaned up K8s build resources for deployment ${deploymentId}`); } - this.setProgress(deploymentId, { phase: 'failed', percent: 0, message: 'Cancelled by user' }); + this.setProgress(deploymentId, { phase: 'cancelled', percent: 0, message: 'Cancelled by user' }); this.activeBuilds.delete(deploymentId); } diff --git a/backend/src/common/enums.ts b/backend/src/common/enums.ts index 2745a98..fb1e211 100644 --- a/backend/src/common/enums.ts +++ b/backend/src/common/enums.ts @@ -72,6 +72,7 @@ export enum DeploymentStatus { DEPLOYING = 'deploying', RUNNING = 'running', FAILED = 'failed', + CANCELLED = 'cancelled', STOPPED = 'stopped', DELETING = 'deleting', } diff --git a/backend/src/deployments/deployments.service.ts b/backend/src/deployments/deployments.service.ts index c98c060..169754b 100644 --- a/backend/src/deployments/deployments.service.ts +++ b/backend/src/deployments/deployments.service.ts @@ -117,7 +117,7 @@ export class DeploymentsService { if (error instanceof BuildCancelledError || error?.name === 'BuildCancelledError') { this.logger.log(`Deployment ${deploymentId} cancelled by user`); await this.deploymentsRepository.update(deploymentId, { - status: DeploymentStatus.FAILED, + status: DeploymentStatus.CANCELLED, errorMessage: 'Cancelled by user', finishedAt: new Date(), }); @@ -209,6 +209,9 @@ export class DeploymentsService { if (latest.status === DeploymentStatus.FAILED) { return { phase: 'failed', percent: 0, message: latest.errorMessage || 'Deployment failed' }; } + if (latest.status === DeploymentStatus.CANCELLED) { + return { phase: 'cancelled', percent: 0, message: latest.errorMessage || 'Cancelled by user' }; + } if (latest.status === DeploymentStatus.BUILDING) { return { phase: 'building', percent: 0, message: 'Building...' }; } @@ -242,7 +245,7 @@ export class DeploymentsService { await this.buildService.cancelBuild(latest.id); await this.buildService.cleanupBuildResourcesForApp(app); - latest.status = DeploymentStatus.FAILED; + latest.status = DeploymentStatus.CANCELLED; latest.errorMessage = 'Cancelled by user'; latest.finishedAt = new Date(); return this.deploymentsRepository.save(latest); diff --git a/frontend/src/app/dashboard/admin/apps/page.tsx b/frontend/src/app/dashboard/admin/apps/page.tsx index 2ec34d4..cf867d5 100644 --- a/frontend/src/app/dashboard/admin/apps/page.tsx +++ b/frontend/src/app/dashboard/admin/apps/page.tsx @@ -17,6 +17,7 @@ const statusColors: Record = { deploying: 'badge-blue', failed: 'badge-red', build_failed: 'badge-red', + cancelled: 'badge-gray', stopped: 'badge-gray', }; diff --git a/frontend/src/app/dashboard/apps/[id]/page.tsx b/frontend/src/app/dashboard/apps/[id]/page.tsx index f4dbda2..5791ca5 100644 --- a/frontend/src/app/dashboard/apps/[id]/page.tsx +++ b/frontend/src/app/dashboard/apps/[id]/page.tsx @@ -22,6 +22,7 @@ const statusColors: Record = { failed: 'badge-red', build_failed: 'badge-red', stopped: 'badge-gray', + cancelled: 'badge-gray', }; /** diff --git a/frontend/src/app/dashboard/apps/page.tsx b/frontend/src/app/dashboard/apps/page.tsx index b7c7548..7cbc54b 100644 --- a/frontend/src/app/dashboard/apps/page.tsx +++ b/frontend/src/app/dashboard/apps/page.tsx @@ -15,6 +15,7 @@ const statusColors: Record = { deploying: 'badge-blue', failed: 'badge-red', build_failed: 'badge-red', + cancelled: 'badge-gray', stopped: 'badge-gray', }; diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index a409d8d..180616f 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -16,6 +16,7 @@ const statusColors: Record = { deploying: 'bg-blue-100 text-blue-700', failed: 'bg-red-100 text-red-700', build_failed: 'bg-red-100 text-red-700', + cancelled: 'bg-gray-100 text-gray-600', stopped: 'bg-gray-100 text-gray-600', }; @@ -26,6 +27,7 @@ const statusIcons: Record = { deploying: , failed: , build_failed: , + cancelled: , stopped: , }; diff --git a/frontend/src/components/build-progress-modal.tsx b/frontend/src/components/build-progress-modal.tsx index 908858e..6572efd 100644 --- a/frontend/src/components/build-progress-modal.tsx +++ b/frontend/src/components/build-progress-modal.tsx @@ -7,7 +7,7 @@ import { Loader2, Upload, Hammer, Rocket, CheckCircle, XCircle, X } from 'lucide import { toast } from 'react-toastify'; export interface BuildProgress { - phase: 'uploading' | 'building' | 'deploying' | 'done' | 'failed'; + phase: 'uploading' | 'building' | 'deploying' | 'done' | 'failed' | 'cancelled'; percent: number; bytesUploaded?: number; totalBytes?: number; @@ -26,6 +26,7 @@ const phaseConfig = { deploying: { label: 'Deploying to Kubernetes', icon: Rocket, bg: 'bg-purple-500' }, done: { label: 'Deployment complete', icon: CheckCircle, bg: 'bg-green-500' }, failed: { label: 'Deployment failed', icon: XCircle, bg: 'bg-red-500' }, + cancelled: { label: 'Deployment cancelled', icon: XCircle, bg: 'bg-gray-500' }, }; export function BuildProgressModal({ appId, enabled }: { appId: string; enabled: boolean }) { @@ -60,7 +61,7 @@ export function BuildProgressModal({ appId, enabled }: { appId: string; enabled: const cfg = phaseConfig[progress.phase]; const PhaseIcon = cfg.icon; - const isActive = progress.phase !== 'failed'; + const isActive = progress.phase !== 'failed' && progress.phase !== 'cancelled'; const showBytes = progress.phase === 'uploading' && progress.totalBytes; const isCancelling = cancelMutation.isPending; @@ -70,7 +71,7 @@ export function BuildProgressModal({ appId, enabled }: { appId: string; enabled: