Merge pull request #2450 from Dokploy/2403-no-delete-volumes-option-when-deleting-in-bulk

feat(ui): add bulk deploy functionality for services in project dashb…
This commit is contained in:
Mauricio Siu
2025-08-23 16:59:03 -06:00
committed by GitHub
@@ -10,6 +10,7 @@ import {
FolderInput, FolderInput,
GlobeIcon, GlobeIcon,
Loader2, Loader2,
Play,
PlusIcon, PlusIcon,
Search, Search,
ServerIcon, ServerIcon,
@@ -315,6 +316,7 @@ const Project = (
stop: api.compose.stop.useMutation(), stop: api.compose.stop.useMutation(),
move: api.compose.move.useMutation(), move: api.compose.move.useMutation(),
delete: api.compose.delete.useMutation(), delete: api.compose.delete.useMutation(),
deploy: api.compose.deploy.useMutation(),
}; };
const applicationActions = { const applicationActions = {
@@ -322,6 +324,7 @@ const Project = (
stop: api.application.stop.useMutation(), stop: api.application.stop.useMutation(),
move: api.application.move.useMutation(), move: api.application.move.useMutation(),
delete: api.application.delete.useMutation(), delete: api.application.delete.useMutation(),
deploy: api.application.deploy.useMutation(),
}; };
const postgresActions = { const postgresActions = {
@@ -329,6 +332,7 @@ const Project = (
stop: api.postgres.stop.useMutation(), stop: api.postgres.stop.useMutation(),
move: api.postgres.move.useMutation(), move: api.postgres.move.useMutation(),
delete: api.postgres.remove.useMutation(), delete: api.postgres.remove.useMutation(),
deploy: api.postgres.deploy.useMutation(),
}; };
const mysqlActions = { const mysqlActions = {
@@ -336,6 +340,7 @@ const Project = (
stop: api.mysql.stop.useMutation(), stop: api.mysql.stop.useMutation(),
move: api.mysql.move.useMutation(), move: api.mysql.move.useMutation(),
delete: api.mysql.remove.useMutation(), delete: api.mysql.remove.useMutation(),
deploy: api.mysql.deploy.useMutation(),
}; };
const mariadbActions = { const mariadbActions = {
@@ -343,6 +348,7 @@ const Project = (
stop: api.mariadb.stop.useMutation(), stop: api.mariadb.stop.useMutation(),
move: api.mariadb.move.useMutation(), move: api.mariadb.move.useMutation(),
delete: api.mariadb.remove.useMutation(), delete: api.mariadb.remove.useMutation(),
deploy: api.mariadb.deploy.useMutation(),
}; };
const redisActions = { const redisActions = {
@@ -350,6 +356,7 @@ const Project = (
stop: api.redis.stop.useMutation(), stop: api.redis.stop.useMutation(),
move: api.redis.move.useMutation(), move: api.redis.move.useMutation(),
delete: api.redis.remove.useMutation(), delete: api.redis.remove.useMutation(),
deploy: api.redis.deploy.useMutation(),
}; };
const mongoActions = { const mongoActions = {
@@ -357,6 +364,7 @@ const Project = (
stop: api.mongo.stop.useMutation(), stop: api.mongo.stop.useMutation(),
move: api.mongo.move.useMutation(), move: api.mongo.move.useMutation(),
delete: api.mongo.remove.useMutation(), delete: api.mongo.remove.useMutation(),
deploy: api.mongo.deploy.useMutation(),
}; };
const handleBulkStart = async () => { const handleBulkStart = async () => {
@@ -589,6 +597,83 @@ const Project = (
setIsBulkActionLoading(false); setIsBulkActionLoading(false);
}; };
const handleBulkDeploy = async () => {
let success = 0;
let failed = 0;
setIsBulkActionLoading(true);
for (const serviceId of selectedServices) {
try {
const service = filteredServices.find((s) => s.id === serviceId);
if (!service) continue;
switch (service.type) {
case "application":
await applicationActions.deploy.mutateAsync({
applicationId: serviceId,
});
break;
case "compose":
await composeActions.deploy.mutateAsync({
composeId: serviceId,
});
break;
case "postgres":
await postgresActions.deploy.mutateAsync({
postgresId: serviceId,
});
break;
case "mysql":
await mysqlActions.deploy.mutateAsync({
mysqlId: serviceId,
});
break;
case "mariadb":
await mariadbActions.deploy.mutateAsync({
mariadbId: serviceId,
});
break;
case "redis":
await redisActions.deploy.mutateAsync({
redisId: serviceId,
});
break;
case "mongo":
await mongoActions.deploy.mutateAsync({
mongoId: serviceId,
});
break;
}
success++;
} catch (error) {
failed++;
toast.error(
`Error deploying service ${serviceId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}
if (success > 0) {
toast.success(
`${success} service${success !== 1 ? "s" : ""} deployed successfully`,
);
}
if (failed > 0) {
toast.error(
`${failed} service${failed !== 1 ? "s" : ""} failed to deploy`,
);
}
setSelectedServices([]);
setIsDropdownOpen(false);
setIsBulkActionLoading(false);
};
const filteredServices = useMemo(() => { const filteredServices = useMemo(() => {
if (!applications) return []; if (!applications) return [];
const filtered = applications.filter( const filtered = applications.filter(
@@ -732,6 +817,24 @@ const Project = (
Start Start
</Button> </Button>
</DialogAction> </DialogAction>
<DialogAction
title="Deploy Services"
description={`Are you sure you want to deploy ${selectedServices.length} service${selectedServices.length !== 1 ? "s" : ""}? This will redeploy/restart the selected services.`}
onClick={handleBulkDeploy}
type="default"
disabled={
selectedServices.length === 0 ||
isBulkActionLoading
}
>
<Button
variant="ghost"
className="w-full justify-start"
>
<Play className="mr-2 h-4 w-4" />
Deploy
</Button>
</DialogAction>
<DialogAction <DialogAction
title="Stop Services" title="Stop Services"
description={`Are you sure you want to stop ${selectedServices.length} services?`} description={`Are you sure you want to stop ${selectedServices.length} services?`}
@@ -904,16 +1007,16 @@ const Project = (
key={serviceId} key={serviceId}
className="flex items-center space-x-2 text-sm" className="flex items-center space-x-2 text-sm"
> >
<Badge variant="secondary"> <span className="px-2 py-1 text-xs bg-secondary rounded">
{service.type} {service.type}
</Badge> </span>
<span>{service.name}</span> <span>{service.name}</span>
</div> </div>
) : null; ) : null;
})} })}
</div> </div>
{/* Volume deletion option for services that support it */} {/* Volume deletion option for compose services */}
{(() => { {(() => {
const servicesWithVolumeSupport = const servicesWithVolumeSupport =
selectedServices.filter((serviceId) => { selectedServices.filter((serviceId) => {
@@ -975,7 +1078,7 @@ const Project = (
setIsBulkDeleteDialogOpen(false); setIsBulkDeleteDialogOpen(false);
setDeleteVolumes(false); // Reset checkbox setDeleteVolumes(false); // Reset checkbox
}} }}
isLoading={isBulkActionLoading} disabled={isBulkActionLoading}
> >
Delete Services Delete Services
</Button> </Button>