feat: add redeploy endpoint — rebuild from latest git/zip source and deploy new version
- Backend: added redeployApplication() to DeploymentsService that creates a new deployment record and re-runs the full build+deploy pipeline - Backend: added POST /deployments/applications/:appId/redeploy endpoint - Frontend: added Redeploy button on app detail page, visible after first deploy when no build is in progress - For git-based apps: pulls latest code from repo on each redeploy - For zip-based apps: rebuilds from last uploaded source code
This commit is contained in:
@@ -62,4 +62,11 @@ export class DeploymentsController {
|
|||||||
await this.deploymentsService.restartDeployment(appId, req.user.id);
|
await this.deploymentsService.restartDeployment(appId, req.user.id);
|
||||||
return { message: 'Application restarted' };
|
return { message: 'Application restarted' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('applications/:appId/redeploy')
|
||||||
|
@ApiOperation({ summary: 'Redeploy: rebuild from latest source (git pull / zip) and deploy new version' })
|
||||||
|
async redeploy(@Param('appId') appId: string, @Request() req: any) {
|
||||||
|
const deployment = await this.deploymentsService.redeployApplication(appId, req.user.id);
|
||||||
|
return { message: 'Redeploy triggered', deployment };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,36 @@ export class DeploymentsService {
|
|||||||
await this.kubernetesService.restartDeployment(app);
|
await this.kubernetesService.restartDeployment(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redeploy: re-build from latest source (git pull or existing zip) and deploy new version.
|
||||||
|
* For git-based apps this pulls the latest code, for zip-based it rebuilds from last uploaded zip.
|
||||||
|
*/
|
||||||
|
async redeployApplication(applicationId: string, userId: string): Promise<Deployment> {
|
||||||
|
const app = await this.applicationsService.findOne(applicationId, userId);
|
||||||
|
|
||||||
|
if (!app.codePath && !app.gitUrl) {
|
||||||
|
throw new NotFoundException('No source code available. Upload code or set a git URL first.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new deployment record
|
||||||
|
const deployment = this.deploymentsRepository.create({
|
||||||
|
applicationId: app.id,
|
||||||
|
triggeredBy: userId,
|
||||||
|
imageTag: `${app.name}:${Date.now()}`,
|
||||||
|
status: DeploymentStatus.PENDING,
|
||||||
|
version: `v${Date.now()}`,
|
||||||
|
});
|
||||||
|
const saved = await this.deploymentsRepository.save(deployment);
|
||||||
|
|
||||||
|
// Trigger async build & deploy pipeline (same as initial deploy)
|
||||||
|
this.executePipeline(saved.id, app).catch((error) => {
|
||||||
|
this.logger.error(`Redeploy pipeline failed for deployment ${saved.id}:`, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`Redeploy triggered for ${app.name} (${app.gitUrl ? 'git: ' + app.gitUrl : 'zip'})`);
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
async deleteAllForApplication(applicationId: string): Promise<void> {
|
async deleteAllForApplication(applicationId: string): Promise<void> {
|
||||||
await this.deploymentsRepository.delete({ applicationId });
|
await this.deploymentsRepository.delete({ applicationId });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,15 @@ export default function AppDetailPage() {
|
|||||||
onError: () => toast.error('Failed to restart application'),
|
onError: () => toast.error('Failed to restart application'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const redeployMutation = useMutation({
|
||||||
|
mutationFn: () => api.post(`/deployments/applications/${appId}/redeploy`),
|
||||||
|
onSuccess: () => {
|
||||||
|
invalidateAll();
|
||||||
|
toast.success('Redeploy triggered — building new version from latest source');
|
||||||
|
},
|
||||||
|
onError: () => toast.error('Failed to trigger redeploy'),
|
||||||
|
});
|
||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: () => api.delete(`/applications/${appId}`),
|
mutationFn: () => api.delete(`/applications/${appId}`),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -230,6 +239,18 @@ export default function AppDetailPage() {
|
|||||||
{restartMutation.isPending ? '⏳...' : '🔄 Restart'}
|
{restartMutation.isPending ? '⏳...' : '🔄 Restart'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Redeploy: rebuild from latest git/code */}
|
||||||
|
{!isInProgress && (
|
||||||
|
<button
|
||||||
|
onClick={() => redeployMutation.mutate()}
|
||||||
|
disabled={redeployMutation.isPending}
|
||||||
|
className="btn-primary text-sm disabled:opacity-50"
|
||||||
|
title="Rebuild from latest source code and deploy new version"
|
||||||
|
>
|
||||||
|
{redeployMutation.isPending ? '⏳ Rebuilding...' : '🔄 Redeploy'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user