From fd0a472468cce82b235a9b869927a5b62c910d25 Mon Sep 17 00:00:00 2001 From: 190km Date: Tue, 3 Dec 2024 02:20:20 +0100 Subject: [PATCH 1/2] feat/fix: fixed stop button & added start button --- .../dashboard/compose/general/actions.tsx | 6 +- .../dashboard/compose/start-compose.tsx | 65 +++++ .../dashboard/compose/stop-compose.tsx | 65 +++++ apps/dokploy/pages/api/deploy/github.ts | 266 +++++++++--------- apps/dokploy/server/api/routers/compose.ts | 15 + packages/server/src/services/compose.ts | 30 ++ 6 files changed, 313 insertions(+), 134 deletions(-) create mode 100644 apps/dokploy/components/dashboard/compose/start-compose.tsx create mode 100644 apps/dokploy/components/dashboard/compose/stop-compose.tsx diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index 365e37f51..140b8d96b 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"; @@ -71,7 +72,10 @@ export const ComposeActions = ({ composeId }: Props) => { Autodeploy {data?.autoDeploy && } - {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 000000000..20f990bb3 --- /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 000000000..3080e755a --- /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/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 1d8c094af..08589fae1 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -10,138 +10,138 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { extractCommitMessage, extractHash } from "./[refreshToken]"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse, ) { - const signature = req.headers["x-hub-signature-256"]; - const githubBody = req.body; - - if (!githubBody?.installation?.id) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } - - const githubResult = await db.query.github.findFirst({ - where: eq(github.githubInstallationId, githubBody.installation.id), - }); - - if (!githubResult) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } - - if (!githubResult.githubWebhookSecret) { - res.status(400).json({ message: "Github Webhook Secret not set" }); - return; - } - const webhooks = new Webhooks({ - secret: githubResult.githubWebhookSecret, - }); - - const verified = await webhooks.verify( - JSON.stringify(githubBody), - signature as string - ); - - if (!verified) { - res.status(401).json({ message: "Unauthorized" }); - return; - } - - if (req.headers["x-github-event"] === "ping") { - res.status(200).json({ message: "Ping received, webhook is active" }); - return; - } - - if (req.headers["x-github-event"] !== "push") { - res.status(400).json({ message: "We only accept push events" }); - return; - } - - try { - const branchName = githubBody?.ref?.replace("refs/heads/", ""); - const repository = githubBody?.repository?.name; - const deploymentTitle = extractCommitMessage(req.headers, req.body); - const deploymentHash = extractHash(req.headers, req.body); - - const apps = await db.query.applications.findMany({ - where: and( - eq(applications.sourceType, "github"), - eq(applications.autoDeploy, true), - eq(applications.branch, branchName), - eq(applications.repository, repository) - ), - }); - - for (const app of apps) { - const jobData: DeploymentJob = { - applicationId: app.applicationId as string, - titleLog: deploymentTitle, - descriptionLog: `Hash: ${deploymentHash}`, - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; - - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } - - const composeApps = await db.query.compose.findMany({ - where: and( - eq(compose.sourceType, "github"), - eq(compose.autoDeploy, true), - eq(compose.branch, branchName), - eq(compose.repository, repository) - ), - }); - - for (const composeApp of composeApps) { - const jobData: DeploymentJob = { - composeId: composeApp.composeId as string, - titleLog: deploymentTitle, - type: "deploy", - applicationType: "compose", - descriptionLog: `Hash: ${deploymentHash}`, - server: !!composeApp.serverId, - }; - - if (IS_CLOUD && composeApp.serverId) { - jobData.serverId = composeApp.serverId; - await deploy(jobData); - return true; - } - - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } - - const totalApps = apps.length + composeApps.length; - const emptyApps = totalApps === 0; - - if (emptyApps) { - res.status(200).json({ message: "No apps to deploy" }); - return; - } - res.status(200).json({ message: `Deployed ${totalApps} apps` }); - } catch (error) { - res.status(400).json({ message: "Error To Deploy Application", error }); - } + const signature = req.headers["x-hub-signature-256"]; + const githubBody = req.body; + + if (!githubBody?.installation?.id) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } + + const githubResult = await db.query.github.findFirst({ + where: eq(github.githubInstallationId, githubBody.installation.id), + }); + + if (!githubResult) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } + + if (!githubResult.githubWebhookSecret) { + res.status(400).json({ message: "Github Webhook Secret not set" }); + return; + } + const webhooks = new Webhooks({ + secret: githubResult.githubWebhookSecret, + }); + + const verified = await webhooks.verify( + JSON.stringify(githubBody), + signature as string, + ); + + if (!verified) { + res.status(401).json({ message: "Unauthorized" }); + return; + } + + if (req.headers["x-github-event"] === "ping") { + res.status(200).json({ message: "Ping received, webhook is active" }); + return; + } + + if (req.headers["x-github-event"] !== "push") { + res.status(400).json({ message: "We only accept push events" }); + return; + } + + try { + const branchName = githubBody?.ref?.replace("refs/heads/", ""); + const repository = githubBody?.repository?.name; + const deploymentTitle = extractCommitMessage(req.headers, req.body); + const deploymentHash = extractHash(req.headers, req.body); + + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.branch, branchName), + eq(applications.repository, repository), + ), + }); + + for (const app of apps) { + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: deploymentTitle, + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application", + server: !!app.serverId, + }; + + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } + + const composeApps = await db.query.compose.findMany({ + where: and( + eq(compose.sourceType, "github"), + eq(compose.autoDeploy, true), + eq(compose.branch, branchName), + eq(compose.repository, repository), + ), + }); + + for (const composeApp of composeApps) { + const jobData: DeploymentJob = { + composeId: composeApp.composeId as string, + titleLog: deploymentTitle, + type: "deploy", + applicationType: "compose", + descriptionLog: `Hash: ${deploymentHash}`, + server: !!composeApp.serverId, + }; + + if (IS_CLOUD && composeApp.serverId) { + jobData.serverId = composeApp.serverId; + await deploy(jobData); + return true; + } + + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } + + const totalApps = apps.length + composeApps.length; + const emptyApps = totalApps === 0; + + if (emptyApps) { + res.status(200).json({ message: "No apps to deploy" }); + return; + } + res.status(200).json({ message: `Deployed ${totalApps} apps` }); + } catch (error) { + res.status(400).json({ message: "Error To Deploy Application", error }); + } } diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 257f374f2..6d04e815f 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 63d29539d..604c2150e 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 { From 40c97b8e9cf4433e3fa9b5beed992a26ea22d6fe Mon Sep 17 00:00:00 2001 From: usopp Date: Tue, 3 Dec 2024 02:42:19 +0100 Subject: [PATCH 2/2] Update github.ts --- apps/dokploy/pages/api/deploy/github.ts | 266 ++++++++++++------------ 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 08589fae1..1d8c094af 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -10,138 +10,138 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { extractCommitMessage, extractHash } from "./[refreshToken]"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse, + req: NextApiRequest, + res: NextApiResponse ) { - const signature = req.headers["x-hub-signature-256"]; - const githubBody = req.body; - - if (!githubBody?.installation?.id) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } - - const githubResult = await db.query.github.findFirst({ - where: eq(github.githubInstallationId, githubBody.installation.id), - }); - - if (!githubResult) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } - - if (!githubResult.githubWebhookSecret) { - res.status(400).json({ message: "Github Webhook Secret not set" }); - return; - } - const webhooks = new Webhooks({ - secret: githubResult.githubWebhookSecret, - }); - - const verified = await webhooks.verify( - JSON.stringify(githubBody), - signature as string, - ); - - if (!verified) { - res.status(401).json({ message: "Unauthorized" }); - return; - } - - if (req.headers["x-github-event"] === "ping") { - res.status(200).json({ message: "Ping received, webhook is active" }); - return; - } - - if (req.headers["x-github-event"] !== "push") { - res.status(400).json({ message: "We only accept push events" }); - return; - } - - try { - const branchName = githubBody?.ref?.replace("refs/heads/", ""); - const repository = githubBody?.repository?.name; - const deploymentTitle = extractCommitMessage(req.headers, req.body); - const deploymentHash = extractHash(req.headers, req.body); - - const apps = await db.query.applications.findMany({ - where: and( - eq(applications.sourceType, "github"), - eq(applications.autoDeploy, true), - eq(applications.branch, branchName), - eq(applications.repository, repository), - ), - }); - - for (const app of apps) { - const jobData: DeploymentJob = { - applicationId: app.applicationId as string, - titleLog: deploymentTitle, - descriptionLog: `Hash: ${deploymentHash}`, - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; - - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - }, - ); - } - - const composeApps = await db.query.compose.findMany({ - where: and( - eq(compose.sourceType, "github"), - eq(compose.autoDeploy, true), - eq(compose.branch, branchName), - eq(compose.repository, repository), - ), - }); - - for (const composeApp of composeApps) { - const jobData: DeploymentJob = { - composeId: composeApp.composeId as string, - titleLog: deploymentTitle, - type: "deploy", - applicationType: "compose", - descriptionLog: `Hash: ${deploymentHash}`, - server: !!composeApp.serverId, - }; - - if (IS_CLOUD && composeApp.serverId) { - jobData.serverId = composeApp.serverId; - await deploy(jobData); - return true; - } - - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - }, - ); - } - - const totalApps = apps.length + composeApps.length; - const emptyApps = totalApps === 0; - - if (emptyApps) { - res.status(200).json({ message: "No apps to deploy" }); - return; - } - res.status(200).json({ message: `Deployed ${totalApps} apps` }); - } catch (error) { - res.status(400).json({ message: "Error To Deploy Application", error }); - } + const signature = req.headers["x-hub-signature-256"]; + const githubBody = req.body; + + if (!githubBody?.installation?.id) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } + + const githubResult = await db.query.github.findFirst({ + where: eq(github.githubInstallationId, githubBody.installation.id), + }); + + if (!githubResult) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } + + if (!githubResult.githubWebhookSecret) { + res.status(400).json({ message: "Github Webhook Secret not set" }); + return; + } + const webhooks = new Webhooks({ + secret: githubResult.githubWebhookSecret, + }); + + const verified = await webhooks.verify( + JSON.stringify(githubBody), + signature as string + ); + + if (!verified) { + res.status(401).json({ message: "Unauthorized" }); + return; + } + + if (req.headers["x-github-event"] === "ping") { + res.status(200).json({ message: "Ping received, webhook is active" }); + return; + } + + if (req.headers["x-github-event"] !== "push") { + res.status(400).json({ message: "We only accept push events" }); + return; + } + + try { + const branchName = githubBody?.ref?.replace("refs/heads/", ""); + const repository = githubBody?.repository?.name; + const deploymentTitle = extractCommitMessage(req.headers, req.body); + const deploymentHash = extractHash(req.headers, req.body); + + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.branch, branchName), + eq(applications.repository, repository) + ), + }); + + for (const app of apps) { + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: deploymentTitle, + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application", + server: !!app.serverId, + }; + + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + } + ); + } + + const composeApps = await db.query.compose.findMany({ + where: and( + eq(compose.sourceType, "github"), + eq(compose.autoDeploy, true), + eq(compose.branch, branchName), + eq(compose.repository, repository) + ), + }); + + for (const composeApp of composeApps) { + const jobData: DeploymentJob = { + composeId: composeApp.composeId as string, + titleLog: deploymentTitle, + type: "deploy", + applicationType: "compose", + descriptionLog: `Hash: ${deploymentHash}`, + server: !!composeApp.serverId, + }; + + if (IS_CLOUD && composeApp.serverId) { + jobData.serverId = composeApp.serverId; + await deploy(jobData); + return true; + } + + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + } + ); + } + + const totalApps = apps.length + composeApps.length; + const emptyApps = totalApps === 0; + + if (emptyApps) { + res.status(200).json({ message: "No apps to deploy" }); + return; + } + res.status(200).json({ message: `Deployed ${totalApps} apps` }); + } catch (error) { + res.status(400).json({ message: "Error To Deploy Application", error }); + } }