diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index 1531942f..0fa5ae38 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -14,6 +14,7 @@ import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react"; import Link from "next/link"; import { toast } from "sonner"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; +import { StartCompose } from "../start-compose"; import { DeployCompose } from "./deploy-compose"; import { RedbuildCompose } from "./rebuild-compose"; import { StopCompose } from "./stop-compose"; @@ -51,7 +52,10 @@ export const ComposeActions = ({ composeId }: Props) => {
- {data?.composeType === "docker-compose" && ( + {data?.composeType === "docker-compose" && + data?.composeStatus === "idle" ? ( + + ) : ( )} diff --git a/apps/dokploy/components/dashboard/compose/start-compose.tsx b/apps/dokploy/components/dashboard/compose/start-compose.tsx new file mode 100644 index 00000000..20f990bb --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/start-compose.tsx @@ -0,0 +1,65 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { api } from "@/utils/api"; +import { CheckCircle2 } from "lucide-react"; +import { toast } from "sonner"; + +interface Props { + composeId: string; +} + +export const StartCompose = ({ composeId }: Props) => { + const { mutateAsync, isLoading } = api.compose.start.useMutation(); + const utils = api.useUtils(); + return ( + + + + + + + + Are you sure to start the compose? + + + This will start the compose + + + + Cancel + { + await mutateAsync({ + composeId, + }) + .then(async () => { + await utils.compose.one.invalidate({ + composeId, + }); + toast.success("Compose started succesfully"); + }) + .catch(() => { + toast.error("Error to start the Compose"); + }); + }} + > + Confirm + + + + + ); +}; diff --git a/apps/dokploy/components/dashboard/compose/stop-compose.tsx b/apps/dokploy/components/dashboard/compose/stop-compose.tsx new file mode 100644 index 00000000..3080e755 --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/stop-compose.tsx @@ -0,0 +1,65 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { api } from "@/utils/api"; +import { Ban } from "lucide-react"; +import { toast } from "sonner"; + +interface Props { + composeId: string; +} + +export const StopCompose = ({ composeId }: Props) => { + const { mutateAsync, isLoading } = api.compose.stop.useMutation(); + const utils = api.useUtils(); + return ( + + + + + + + + Are you absolutely sure to stop the compose? + + + This will stop the compose + + + + Cancel + { + await mutateAsync({ + composeId, + }) + .then(async () => { + await utils.compose.one.invalidate({ + composeId, + }); + toast.success("Compose stopped succesfully"); + }) + .catch(() => { + toast.error("Error to stop the Compose"); + }); + }} + > + Confirm + + + + + ); +}; diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 257f374f..6d04e815 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -48,6 +48,7 @@ import { removeCompose, removeComposeDirectory, removeDeploymentsByComposeId, + startCompose, stopCompose, updateCompose, } from "@dokploy/server"; @@ -309,6 +310,20 @@ export const composeRouter = createTRPCRouter({ } await stopCompose(input.composeId); + return true; + }), + start: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this compose", + }); + } + await startCompose(input.composeId); + return true; }), getDefaultCommand: protectedProcedure diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 63d29539..604c2150 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -463,6 +463,36 @@ export const removeCompose = async (compose: Compose) => { return true; }; +export const startCompose = async (composeId: string) => { + const compose = await findComposeById(composeId); + try { + const { COMPOSE_PATH } = paths(!!compose.serverId); + if (compose.composeType === "docker-compose") { + if (compose.serverId) { + await execAsyncRemote( + compose.serverId, + `cd ${join(COMPOSE_PATH, compose.appName, "code")} && docker compose -p ${compose.appName} up -d`, + ); + } else { + await execAsync(`docker compose -p ${compose.appName} up -d`, { + cwd: join(COMPOSE_PATH, compose.appName, "code"), + }); + } + } + + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + await updateCompose(composeId, { + composeStatus: "idle", + }); + throw error; + } + + return true; +}; + export const stopCompose = async (composeId: string) => { const compose = await findComposeById(composeId); try {