From a439286e5f7fa242aa4f21318db369d4006d50f0 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Fri, 13 Dec 2024 16:14:52 +0100 Subject: [PATCH 01/13] feat: add UI selection --- .../dashboard/compose/delete-compose.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/dokploy/components/dashboard/compose/delete-compose.tsx b/apps/dokploy/components/dashboard/compose/delete-compose.tsx index 07f42448c..b0b958f21 100644 --- a/apps/dokploy/components/dashboard/compose/delete-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/delete-compose.tsx @@ -1,4 +1,5 @@ import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, @@ -11,6 +12,7 @@ import { import { Form, FormControl, + FormDescription, FormField, FormItem, FormLabel, @@ -30,6 +32,7 @@ const deleteComposeSchema = z.object({ projectName: z.string().min(1, { message: "Compose name is required", }), + deleteVolumes: z.boolean(), }); type DeleteCompose = z.infer; @@ -49,6 +52,7 @@ export const DeleteCompose = ({ composeId }: Props) => { const form = useForm({ defaultValues: { projectName: "", + deleteVolumes: false, }, resolver: zodResolver(deleteComposeSchema), }); @@ -114,6 +118,27 @@ export const DeleteCompose = ({ composeId }: Props) => { )} /> + ( + +
+ + + + + + Delete volumes associated with this compose + +
+ +
+ )} + /> From 4dc7d9e3c81f47d0cac33853b5bd78b8f3875740 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Fri, 13 Dec 2024 16:33:54 +0100 Subject: [PATCH 02/13] feat: add params to mutation --- apps/dokploy/components/dashboard/compose/delete-compose.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/compose/delete-compose.tsx b/apps/dokploy/components/dashboard/compose/delete-compose.tsx index b0b958f21..7d5ab63b9 100644 --- a/apps/dokploy/components/dashboard/compose/delete-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/delete-compose.tsx @@ -60,7 +60,8 @@ export const DeleteCompose = ({ composeId }: Props) => { const onSubmit = async (formData: DeleteCompose) => { const expectedName = `${data?.name}/${data?.appName}`; if (formData.projectName === expectedName) { - await mutateAsync({ composeId }) + const { deleteVolumes } = formData; + await mutateAsync({ composeId, deleteVolumes }) .then((result) => { push(`/dashboard/project/${result?.projectId}`); toast.success("Compose deleted successfully"); From 8779c67b71935ae0426ade6cfb704b51db21ff33 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Fri, 13 Dec 2024 16:34:12 +0100 Subject: [PATCH 03/13] refactor: change import names --- apps/dokploy/server/api/routers/compose.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 6d04e815f..5f53752b5 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -3,6 +3,7 @@ import { db } from "@/server/db"; import { apiCreateCompose, apiCreateComposeByTemplate, + apiDeleteCompose, apiFetchServices, apiFindCompose, apiRandomizeCompose, @@ -117,7 +118,7 @@ export const composeRouter = createTRPCRouter({ return updateCompose(input.composeId, input); }), delete: protectedProcedure - .input(apiFindCompose) + .input(apiDeleteCompose) .mutation(async ({ input, ctx }) => { if (ctx.user.rol === "user") { await checkServiceAccess(ctx.user.authId, input.composeId, "delete"); @@ -138,7 +139,7 @@ export const composeRouter = createTRPCRouter({ .returning(); const cleanupOperations = [ - async () => await removeCompose(composeResult), + async () => await removeCompose(composeResult, input.deleteVolumes), async () => await removeDeploymentsByComposeId(composeResult), async () => await removeComposeDirectory(composeResult.appName), ]; From 3decbd52075fd2629ef774274a936925ba377eb2 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Fri, 13 Dec 2024 16:34:29 +0100 Subject: [PATCH 04/13] feat: add new form validator --- packages/server/src/db/schema/compose.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts index 02bac7813..e0c4863b5 100644 --- a/packages/server/src/db/schema/compose.ts +++ b/packages/server/src/db/schema/compose.ts @@ -155,6 +155,11 @@ export const apiFindCompose = z.object({ composeId: z.string().min(1), }); +export const apiDeleteCompose = z.object({ + composeId: z.string().min(1), + deleteVolumes: z.boolean(), +}); + export const apiFetchServices = z.object({ composeId: z.string().min(1), type: z.enum(["fetch", "cache"]).optional().default("cache"), From 92c2a83d92bd74151ad73b921219c9cfd770aaa1 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Fri, 13 Dec 2024 16:34:46 +0100 Subject: [PATCH 05/13] feat: add filter to delete volumes is wanted --- packages/server/src/services/compose.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 5ae0d7740..c4b88c68f 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -436,13 +436,26 @@ export const rebuildRemoteCompose = async ({ return true; }; -export const removeCompose = async (compose: Compose) => { +export const removeCompose = async ( + compose: Compose, + deleteVolumes: boolean, +) => { try { const { COMPOSE_PATH } = paths(!!compose.serverId); const projectPath = join(COMPOSE_PATH, compose.appName); + console.log("API: DELETE VOLUMES=", deleteVolumes); + if (compose.composeType === "stack") { - const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; + const listVolumesCommand = `docker volume ls --format \"{{.Name}}\" | grep ${compose.appName}`; + const removeVolumesCommand = `${listVolumesCommand} | xargs -r docker volume rm`; + let command: string; + if (deleteVolumes) { + command = `cd ${projectPath} && docker stack rm ${compose.appName} && ${removeVolumesCommand} && rm -rf ${projectPath}`; + } else { + command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; + } + if (compose.serverId) { await execAsyncRemote(compose.serverId, command); } else { @@ -452,7 +465,13 @@ export const removeCompose = async (compose: Compose) => { cwd: projectPath, }); } else { - const command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; + let command: string; + if (deleteVolumes) { + command = `cd ${projectPath} && docker compose -p ${compose.appName} down --volumes && rm -rf ${projectPath}`; + } else { + command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; + } + if (compose.serverId) { await execAsyncRemote(compose.serverId, command); } else { From 8ea453f4440f39226b5e0d42684aee5baf50c7c7 Mon Sep 17 00:00:00 2001 From: DJKnaeckebrot Date: Wed, 18 Dec 2024 13:01:09 +0100 Subject: [PATCH 06/13] feat: add application handling --- .../application/delete-application.tsx | 25 + .../dokploy/server/api/routers/application.ts | 9 +- packages/server/src/db/schema/application.ts | 7 +- packages/server/src/utils/docker/utils.ts | 808 +++++++++--------- 4 files changed, 446 insertions(+), 403 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/delete-application.tsx b/apps/dokploy/components/dashboard/application/delete-application.tsx index f34d29a78..ff63ef5c5 100644 --- a/apps/dokploy/components/dashboard/application/delete-application.tsx +++ b/apps/dokploy/components/dashboard/application/delete-application.tsx @@ -1,5 +1,6 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, @@ -31,6 +32,7 @@ const deleteApplicationSchema = z.object({ projectName: z.string().min(1, { message: "Application name is required", }), + deleteVolumes: z.boolean(), }); type DeleteApplication = z.infer; @@ -50,6 +52,7 @@ export const DeleteApplication = ({ applicationId }: Props) => { const form = useForm({ defaultValues: { projectName: "", + deleteVolumes: false, }, resolver: zodResolver(deleteApplicationSchema), }); @@ -59,6 +62,7 @@ export const DeleteApplication = ({ applicationId }: Props) => { if (formData.projectName === expectedName) { await mutateAsync({ applicationId, + deleteVolumes: formData.deleteVolumes, }) .then((data) => { push(`/dashboard/project/${data?.projectId}`); @@ -134,6 +138,27 @@ export const DeleteApplication = ({ applicationId }: Props) => { )} /> + ( + +
+ + + + + + Delete volumes associated with this compose + +
+ +
+ )} + /> diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 2902c8eda..9b16d5796 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -6,6 +6,7 @@ import { import { db } from "@/server/db"; import { apiCreateApplication, + apiDeleteApplication, apiFindMonitoringStats, apiFindOneApplication, apiReloadApplication, @@ -142,7 +143,7 @@ export const applicationRouter = createTRPCRouter({ }), delete: protectedProcedure - .input(apiFindOneApplication) + .input(apiDeleteApplication) .mutation(async ({ input, ctx }) => { if (ctx.user.rol === "user") { await checkServiceAccess( @@ -178,7 +179,11 @@ export const applicationRouter = createTRPCRouter({ async () => await removeTraefikConfig(application.appName, application.serverId), async () => - await removeService(application?.appName, application.serverId), + await removeService( + application?.appName, + application.serverId, + input.deleteVolumes, + ), ]; for (const operation of cleanupOperations) { diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index d9b1a5df4..923ea130e 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -17,6 +17,7 @@ import { github } from "./github"; import { gitlab } from "./gitlab"; import { mounts } from "./mount"; import { ports } from "./port"; +import { previewDeployments } from "./preview-deployments"; import { projects } from "./project"; import { redirects } from "./redirects"; import { registry } from "./registry"; @@ -25,7 +26,6 @@ import { server } from "./server"; import { applicationStatus, certificateType } from "./shared"; import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; -import { previewDeployments } from "./preview-deployments"; export const sourceType = pgEnum("sourceType", [ "docker", @@ -518,3 +518,8 @@ export const apiUpdateApplication = createSchema applicationId: z.string().min(1), }) .omit({ serverId: true }); + +export const apiDeleteApplication = z.object({ + applicationId: z.string().min(1), + deleteVolumes: z.boolean(), +}); diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 216ee8671..e8c9e6c23 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -15,520 +15,528 @@ import { spawnAsync } from "../process/spawnAsync"; import { getRemoteDocker } from "../servers/remote-docker"; interface RegistryAuth { - username: string; - password: string; - registryUrl: string; + username: string; + password: string; + registryUrl: string; } export const pullImage = async ( - dockerImage: string, - onData?: (data: any) => void, - authConfig?: Partial, + dockerImage: string, + onData?: (data: any) => void, + authConfig?: Partial ): Promise => { - try { - if (!dockerImage) { - throw new Error("Docker image not found"); - } + try { + if (!dockerImage) { + throw new Error("Docker image not found"); + } - if (authConfig?.username && authConfig?.password) { - await spawnAsync( - "docker", - [ - "login", - authConfig.registryUrl || "", - "-u", - authConfig.username, - "-p", - authConfig.password, - ], - onData, - ); - } - await spawnAsync("docker", ["pull", dockerImage], onData); - } catch (error) { - throw error; - } + if (authConfig?.username && authConfig?.password) { + await spawnAsync( + "docker", + [ + "login", + authConfig.registryUrl || "", + "-u", + authConfig.username, + "-p", + authConfig.password, + ], + onData + ); + } + await spawnAsync("docker", ["pull", dockerImage], onData); + } catch (error) { + throw error; + } }; export const pullRemoteImage = async ( - dockerImage: string, - serverId: string, - onData?: (data: any) => void, - authConfig?: Partial, + dockerImage: string, + serverId: string, + onData?: (data: any) => void, + authConfig?: Partial ): Promise => { - try { - if (!dockerImage) { - throw new Error("Docker image not found"); - } + try { + if (!dockerImage) { + throw new Error("Docker image not found"); + } - const remoteDocker = await getRemoteDocker(serverId); + const remoteDocker = await getRemoteDocker(serverId); - await new Promise((resolve, reject) => { - remoteDocker.pull( - dockerImage, - { authconfig: authConfig }, - (err, stream) => { - if (err) { - reject(err); - return; - } + await new Promise((resolve, reject) => { + remoteDocker.pull( + dockerImage, + { authconfig: authConfig }, + (err, stream) => { + if (err) { + reject(err); + return; + } - remoteDocker.modem.followProgress( - stream as Readable, - (err: Error | null, res) => { - if (!err) { - resolve(res); - } - if (err) { - reject(err); - } - }, - (event) => { - onData?.(event); - }, - ); - }, - ); - }); - } catch (error) { - throw error; - } + remoteDocker.modem.followProgress( + stream as Readable, + (err: Error | null, res) => { + if (!err) { + resolve(res); + } + if (err) { + reject(err); + } + }, + (event) => { + onData?.(event); + } + ); + } + ); + }); + } catch (error) { + throw error; + } }; export const containerExists = async (containerName: string) => { - const container = docker.getContainer(containerName); - try { - await container.inspect(); - return true; - } catch (error) { - return false; - } + const container = docker.getContainer(containerName); + try { + await container.inspect(); + return true; + } catch (error) { + return false; + } }; export const stopService = async (appName: string) => { - try { - await execAsync(`docker service scale ${appName}=0 `); - } catch (error) { - console.error(error); - return error; - } + try { + await execAsync(`docker service scale ${appName}=0 `); + } catch (error) { + console.error(error); + return error; + } }; export const stopServiceRemote = async (serverId: string, appName: string) => { - try { - await execAsyncRemote(serverId, `docker service scale ${appName}=0 `); - } catch (error) { - console.error(error); - return error; - } + try { + await execAsyncRemote(serverId, `docker service scale ${appName}=0 `); + } catch (error) { + console.error(error); + return error; + } }; export const getContainerByName = (name: string): Promise => { - const opts = { - limit: 1, - filters: { - name: [name], - }, - }; - return new Promise((resolve, reject) => { - docker.listContainers(opts, (err, containers) => { - if (err) { - reject(err); - } else if (containers?.length === 0) { - reject(new Error(`No container found with name: ${name}`)); - } else if (containers && containers?.length > 0 && containers[0]) { - resolve(containers[0]); - } - }); - }); + const opts = { + limit: 1, + filters: { + name: [name], + }, + }; + return new Promise((resolve, reject) => { + docker.listContainers(opts, (err, containers) => { + if (err) { + reject(err); + } else if (containers?.length === 0) { + reject(new Error(`No container found with name: ${name}`)); + } else if (containers && containers?.length > 0 && containers[0]) { + resolve(containers[0]); + } + }); + }); }; export const cleanUpUnusedImages = async (serverId?: string) => { - try { - if (serverId) { - await execAsyncRemote(serverId, "docker image prune --all --force"); - } else { - await execAsync("docker image prune --all --force"); - } - } catch (error) { - console.error(error); - throw error; - } + try { + if (serverId) { + await execAsyncRemote(serverId, "docker image prune --all --force"); + } else { + await execAsync("docker image prune --all --force"); + } + } catch (error) { + console.error(error); + throw error; + } }; export const cleanStoppedContainers = async (serverId?: string) => { - try { - if (serverId) { - await execAsyncRemote(serverId, "docker container prune --force"); - } else { - await execAsync("docker container prune --force"); - } - } catch (error) { - console.error(error); - throw error; - } + try { + if (serverId) { + await execAsyncRemote(serverId, "docker container prune --force"); + } else { + await execAsync("docker container prune --force"); + } + } catch (error) { + console.error(error); + throw error; + } }; export const cleanUpUnusedVolumes = async (serverId?: string) => { - try { - if (serverId) { - await execAsyncRemote(serverId, "docker volume prune --all --force"); - } else { - await execAsync("docker volume prune --all --force"); - } - } catch (error) { - console.error(error); - throw error; - } + try { + if (serverId) { + await execAsyncRemote(serverId, "docker volume prune --all --force"); + } else { + await execAsync("docker volume prune --all --force"); + } + } catch (error) { + console.error(error); + throw error; + } }; export const cleanUpInactiveContainers = async () => { - try { - const containers = await docker.listContainers({ all: true }); - const inactiveContainers = containers.filter( - (container) => container.State !== "running", - ); + try { + const containers = await docker.listContainers({ all: true }); + const inactiveContainers = containers.filter( + (container) => container.State !== "running" + ); - for (const container of inactiveContainers) { - await docker.getContainer(container.Id).remove({ force: true }); - console.log(`Cleaning up inactive container: ${container.Id}`); - } - } catch (error) { - console.error("Error cleaning up inactive containers:", error); - throw error; - } + for (const container of inactiveContainers) { + await docker.getContainer(container.Id).remove({ force: true }); + console.log(`Cleaning up inactive container: ${container.Id}`); + } + } catch (error) { + console.error("Error cleaning up inactive containers:", error); + throw error; + } }; export const cleanUpDockerBuilder = async (serverId?: string) => { - if (serverId) { - await execAsyncRemote(serverId, "docker builder prune --all --force"); - } else { - await execAsync("docker builder prune --all --force"); - } + if (serverId) { + await execAsyncRemote(serverId, "docker builder prune --all --force"); + } else { + await execAsync("docker builder prune --all --force"); + } }; export const cleanUpSystemPrune = async (serverId?: string) => { - if (serverId) { - await execAsyncRemote( - serverId, - "docker system prune --all --force --volumes", - ); - } else { - await execAsync("docker system prune --all --force --volumes"); - } + if (serverId) { + await execAsyncRemote( + serverId, + "docker system prune --all --force --volumes" + ); + } else { + await execAsync("docker system prune --all --force --volumes"); + } }; export const startService = async (appName: string) => { - try { - await execAsync(`docker service scale ${appName}=1 `); - } catch (error) { - console.error(error); - throw error; - } + try { + await execAsync(`docker service scale ${appName}=1 `); + } catch (error) { + console.error(error); + throw error; + } }; export const startServiceRemote = async (serverId: string, appName: string) => { - try { - await execAsyncRemote(serverId, `docker service scale ${appName}=1 `); - } catch (error) { - console.error(error); - throw error; - } + try { + await execAsyncRemote(serverId, `docker service scale ${appName}=1 `); + } catch (error) { + console.error(error); + throw error; + } }; export const removeService = async ( - appName: string, - serverId?: string | null, + appName: string, + serverId?: string | null, + deleteVolumes = false ) => { - try { - const command = `docker service rm ${appName}`; - if (serverId) { - await execAsyncRemote(serverId, command); - } else { - await execAsync(command); - } - } catch (error) { - return error; - } + try { + let command: string; + + if (deleteVolumes) { + command = `docker service rm --force ${appName}`; + } else { + command = `docker service rm ${appName}`; + } + + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); + } + } catch (error) { + return error; + } }; export const prepareEnvironmentVariables = ( - serviceEnv: string | null, - projectEnv?: string | null, + serviceEnv: string | null, + projectEnv?: string | null ) => { - const projectVars = parse(projectEnv ?? ""); - const serviceVars = parse(serviceEnv ?? ""); + const projectVars = parse(projectEnv ?? ""); + const serviceVars = parse(serviceEnv ?? ""); - const resolvedVars = Object.entries(serviceVars).map(([key, value]) => { - let resolvedValue = value; - if (projectVars) { - resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => { - if (projectVars[ref] !== undefined) { - return projectVars[ref]; - } - throw new Error(`Invalid project environment variable: project.${ref}`); - }); - } - return `${key}=${resolvedValue}`; - }); + const resolvedVars = Object.entries(serviceVars).map(([key, value]) => { + let resolvedValue = value; + if (projectVars) { + resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => { + if (projectVars[ref] !== undefined) { + return projectVars[ref]; + } + throw new Error(`Invalid project environment variable: project.${ref}`); + }); + } + return `${key}=${resolvedValue}`; + }); - return resolvedVars; + return resolvedVars; }; export const prepareBuildArgs = (input: string | null) => { - const pairs = (input ?? "").split("\n"); + const pairs = (input ?? "").split("\n"); - const jsonObject: Record = {}; + const jsonObject: Record = {}; - for (const pair of pairs) { - const [key, value] = pair.split("="); - if (key && value) { - jsonObject[key] = value; - } - } + for (const pair of pairs) { + const [key, value] = pair.split("="); + if (key && value) { + jsonObject[key] = value; + } + } - return jsonObject; + return jsonObject; }; export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => { - if (!mounts || mounts.length === 0) { - return []; - } + if (!mounts || mounts.length === 0) { + return []; + } - return mounts - .filter((mount) => mount.type === "volume") - .map((mount) => ({ - Type: "volume" as const, - Source: mount.volumeName || "", - Target: mount.mountPath, - })); + return mounts + .filter((mount) => mount.type === "volume") + .map((mount) => ({ + Type: "volume" as const, + Source: mount.volumeName || "", + Target: mount.mountPath, + })); }; type Resources = { - memoryLimit: number | null; - memoryReservation: number | null; - cpuLimit: number | null; - cpuReservation: number | null; + memoryLimit: number | null; + memoryReservation: number | null; + cpuLimit: number | null; + cpuReservation: number | null; }; export const calculateResources = ({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, + memoryLimit, + memoryReservation, + cpuLimit, + cpuReservation, }: Resources): ResourceRequirements => { - return { - Limits: { - MemoryBytes: memoryLimit ?? undefined, - NanoCPUs: cpuLimit ?? undefined, - }, - Reservations: { - MemoryBytes: memoryReservation ?? undefined, - NanoCPUs: cpuReservation ?? undefined, - }, - }; + return { + Limits: { + MemoryBytes: memoryLimit ?? undefined, + NanoCPUs: cpuLimit ?? undefined, + }, + Reservations: { + MemoryBytes: memoryReservation ?? undefined, + NanoCPUs: cpuReservation ?? undefined, + }, + }; }; export const generateConfigContainer = (application: ApplicationNested) => { - const { - healthCheckSwarm, - restartPolicySwarm, - placementSwarm, - updateConfigSwarm, - rollbackConfigSwarm, - modeSwarm, - labelsSwarm, - replicas, - mounts, - networkSwarm, - } = application; + const { + healthCheckSwarm, + restartPolicySwarm, + placementSwarm, + updateConfigSwarm, + rollbackConfigSwarm, + modeSwarm, + labelsSwarm, + replicas, + mounts, + networkSwarm, + } = application; - const haveMounts = mounts.length > 0; + const haveMounts = mounts.length > 0; - return { - ...(healthCheckSwarm && { - HealthCheck: healthCheckSwarm, - }), - ...(restartPolicySwarm - ? { - RestartPolicy: restartPolicySwarm, - } - : {}), - ...(placementSwarm - ? { - Placement: placementSwarm, - } - : { - // if app have mounts keep manager as constraint - Placement: { - Constraints: haveMounts ? ["node.role==manager"] : [], - }, - }), - ...(labelsSwarm && { - Labels: labelsSwarm, - }), - ...(modeSwarm - ? { - Mode: modeSwarm, - } - : { - // use replicas value if no modeSwarm provided - Mode: { - Replicated: { - Replicas: replicas, - }, - }, - }), - ...(rollbackConfigSwarm && { - RollbackConfig: rollbackConfigSwarm, - }), - ...(updateConfigSwarm - ? { UpdateConfig: updateConfigSwarm } - : { - // default config if no updateConfigSwarm provided - UpdateConfig: { - Parallelism: 1, - Order: "start-first", - }, - }), - ...(networkSwarm - ? { - Networks: networkSwarm, - } - : { - Networks: [{ Target: "dokploy-network" }], - }), - }; + return { + ...(healthCheckSwarm && { + HealthCheck: healthCheckSwarm, + }), + ...(restartPolicySwarm + ? { + RestartPolicy: restartPolicySwarm, + } + : {}), + ...(placementSwarm + ? { + Placement: placementSwarm, + } + : { + // if app have mounts keep manager as constraint + Placement: { + Constraints: haveMounts ? ["node.role==manager"] : [], + }, + }), + ...(labelsSwarm && { + Labels: labelsSwarm, + }), + ...(modeSwarm + ? { + Mode: modeSwarm, + } + : { + // use replicas value if no modeSwarm provided + Mode: { + Replicated: { + Replicas: replicas, + }, + }, + }), + ...(rollbackConfigSwarm && { + RollbackConfig: rollbackConfigSwarm, + }), + ...(updateConfigSwarm + ? { UpdateConfig: updateConfigSwarm } + : { + // default config if no updateConfigSwarm provided + UpdateConfig: { + Parallelism: 1, + Order: "start-first", + }, + }), + ...(networkSwarm + ? { + Networks: networkSwarm, + } + : { + Networks: [{ Target: "dokploy-network" }], + }), + }; }; export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => { - if (!mounts || mounts.length === 0) { - return []; - } + if (!mounts || mounts.length === 0) { + return []; + } - return mounts - .filter((mount) => mount.type === "bind") - .map((mount) => ({ - Type: "bind" as const, - Source: mount.hostPath || "", - Target: mount.mountPath, - })); + return mounts + .filter((mount) => mount.type === "bind") + .map((mount) => ({ + Type: "bind" as const, + Source: mount.hostPath || "", + Target: mount.mountPath, + })); }; export const generateFileMounts = ( - appName: string, - service: - | ApplicationNested - | MongoNested - | MariadbNested - | MysqlNested - | PostgresNested - | RedisNested, + appName: string, + service: + | ApplicationNested + | MongoNested + | MariadbNested + | MysqlNested + | PostgresNested + | RedisNested ) => { - const { mounts } = service; - const { APPLICATIONS_PATH } = paths(!!service.serverId); - if (!mounts || mounts.length === 0) { - return []; - } + const { mounts } = service; + const { APPLICATIONS_PATH } = paths(!!service.serverId); + if (!mounts || mounts.length === 0) { + return []; + } - return mounts - .filter((mount) => mount.type === "file") - .map((mount) => { - const fileName = mount.filePath; - const absoluteBasePath = path.resolve(APPLICATIONS_PATH); - const directory = path.join(absoluteBasePath, appName, "files"); - const sourcePath = path.join(directory, fileName || ""); - return { - Type: "bind" as const, - Source: sourcePath, - Target: mount.mountPath, - }; - }); + return mounts + .filter((mount) => mount.type === "file") + .map((mount) => { + const fileName = mount.filePath; + const absoluteBasePath = path.resolve(APPLICATIONS_PATH); + const directory = path.join(absoluteBasePath, appName, "files"); + const sourcePath = path.join(directory, fileName || ""); + return { + Type: "bind" as const, + Source: sourcePath, + Target: mount.mountPath, + }; + }); }; export const createFile = async ( - outputPath: string, - filePath: string, - content: string, + outputPath: string, + filePath: string, + content: string ) => { - try { - const fullPath = path.join(outputPath, filePath); - if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { - fs.mkdirSync(fullPath, { recursive: true }); - return; - } + try { + const fullPath = path.join(outputPath, filePath); + if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { + fs.mkdirSync(fullPath, { recursive: true }); + return; + } - const directory = path.dirname(fullPath); - fs.mkdirSync(directory, { recursive: true }); - fs.writeFileSync(fullPath, content || ""); - } catch (error) { - throw error; - } + const directory = path.dirname(fullPath); + fs.mkdirSync(directory, { recursive: true }); + fs.writeFileSync(fullPath, content || ""); + } catch (error) { + throw error; + } }; export const encodeBase64 = (content: string) => - Buffer.from(content, "utf-8").toString("base64"); + Buffer.from(content, "utf-8").toString("base64"); export const getCreateFileCommand = ( - outputPath: string, - filePath: string, - content: string, + outputPath: string, + filePath: string, + content: string ) => { - const fullPath = path.join(outputPath, filePath); - if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { - return `mkdir -p ${fullPath};`; - } + const fullPath = path.join(outputPath, filePath); + if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { + return `mkdir -p ${fullPath};`; + } - const directory = path.dirname(fullPath); - const encodedContent = encodeBase64(content); - return ` + const directory = path.dirname(fullPath); + const encodedContent = encodeBase64(content); + return ` mkdir -p ${directory}; echo "${encodedContent}" | base64 -d > "${fullPath}"; `; }; export const getServiceContainer = async (appName: string) => { - try { - const filter = { - status: ["running"], - label: [`com.docker.swarm.service.name=${appName}`], - }; + try { + const filter = { + status: ["running"], + label: [`com.docker.swarm.service.name=${appName}`], + }; - const containers = await docker.listContainers({ - filters: JSON.stringify(filter), - }); + const containers = await docker.listContainers({ + filters: JSON.stringify(filter), + }); - if (containers.length === 0 || !containers[0]) { - throw new Error(`No container found with name: ${appName}`); - } + if (containers.length === 0 || !containers[0]) { + throw new Error(`No container found with name: ${appName}`); + } - const container = containers[0]; + const container = containers[0]; - return container; - } catch (error) { - throw error; - } + return container; + } catch (error) { + throw error; + } }; export const getRemoteServiceContainer = async ( - serverId: string, - appName: string, + serverId: string, + appName: string ) => { - try { - const filter = { - status: ["running"], - label: [`com.docker.swarm.service.name=${appName}`], - }; - const remoteDocker = await getRemoteDocker(serverId); - const containers = await remoteDocker.listContainers({ - filters: JSON.stringify(filter), - }); + try { + const filter = { + status: ["running"], + label: [`com.docker.swarm.service.name=${appName}`], + }; + const remoteDocker = await getRemoteDocker(serverId); + const containers = await remoteDocker.listContainers({ + filters: JSON.stringify(filter), + }); - if (containers.length === 0 || !containers[0]) { - throw new Error(`No container found with name: ${appName}`); - } + if (containers.length === 0 || !containers[0]) { + throw new Error(`No container found with name: ${appName}`); + } - const container = containers[0]; + const container = containers[0]; - return container; - } catch (error) { - throw error; - } + return container; + } catch (error) { + throw error; + } }; From be2e70a17ebf3540a5b225161870480bf952d63c Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Mon, 23 Dec 2024 08:27:45 +0100 Subject: [PATCH 07/13] chore: only make this apply to compose files --- packages/server/src/services/compose.ts | 853 ++++++++++++------------ 1 file changed, 428 insertions(+), 425 deletions(-) diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 385e553a0..9012cee6c 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -5,42 +5,42 @@ import { type apiCreateCompose, compose } from "@dokploy/server/db/schema"; import { buildAppName, cleanAppName } from "@dokploy/server/db/schema"; import { generatePassword } from "@dokploy/server/templates/utils"; import { - buildCompose, - getBuildComposeCommand, + buildCompose, + getBuildComposeCommand, } from "@dokploy/server/utils/builders/compose"; import { randomizeSpecificationFile } from "@dokploy/server/utils/docker/compose"; import { - cloneCompose, - cloneComposeRemote, - loadDockerCompose, - loadDockerComposeRemote, + cloneCompose, + cloneComposeRemote, + loadDockerCompose, + loadDockerComposeRemote, } from "@dokploy/server/utils/docker/domain"; import type { ComposeSpecification } from "@dokploy/server/utils/docker/types"; import { sendBuildErrorNotifications } from "@dokploy/server/utils/notifications/build-error"; import { sendBuildSuccessNotifications } from "@dokploy/server/utils/notifications/build-success"; import { - execAsync, - execAsyncRemote, + execAsync, + execAsyncRemote, } from "@dokploy/server/utils/process/execAsync"; import { - cloneBitbucketRepository, - getBitbucketCloneCommand, + cloneBitbucketRepository, + getBitbucketCloneCommand, } from "@dokploy/server/utils/providers/bitbucket"; import { - cloneGitRepository, - getCustomGitCloneCommand, + cloneGitRepository, + getCustomGitCloneCommand, } from "@dokploy/server/utils/providers/git"; import { - cloneGithubRepository, - getGithubCloneCommand, + cloneGithubRepository, + getGithubCloneCommand, } from "@dokploy/server/utils/providers/github"; import { - cloneGitlabRepository, - getGitlabCloneCommand, + cloneGitlabRepository, + getGitlabCloneCommand, } from "@dokploy/server/utils/providers/gitlab"; import { - createComposeFile, - getCreateComposeFileCommand, + createComposeFile, + getCreateComposeFileCommand, } from "@dokploy/server/utils/providers/raw"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -52,498 +52,501 @@ import { validUniqueServerAppName } from "./project"; export type Compose = typeof compose.$inferSelect; export const createCompose = async (input: typeof apiCreateCompose._type) => { - const appName = buildAppName("compose", input.appName); + const appName = buildAppName("compose", input.appName); - const valid = await validUniqueServerAppName(appName); - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } + const valid = await validUniqueServerAppName(appName); + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } - const newDestination = await db - .insert(compose) - .values({ - ...input, - composeFile: "", - appName, - }) - .returning() - .then((value) => value[0]); + const newDestination = await db + .insert(compose) + .values({ + ...input, + composeFile: "", + appName, + }) + .returning() + .then((value) => value[0]); - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting compose", - }); - } + if (!newDestination) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error input: Inserting compose", + }); + } - return newDestination; + return newDestination; }; export const createComposeByTemplate = async ( - input: typeof compose.$inferInsert, + input: typeof compose.$inferInsert ) => { - const appName = cleanAppName(input.appName); - if (appName) { - const valid = await validUniqueServerAppName(appName); + const appName = cleanAppName(input.appName); + if (appName) { + const valid = await validUniqueServerAppName(appName); - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - const newDestination = await db - .insert(compose) - .values({ - ...input, - appName, - }) - .returning() - .then((value) => value[0]); + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } + const newDestination = await db + .insert(compose) + .values({ + ...input, + appName, + }) + .returning() + .then((value) => value[0]); - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting compose", - }); - } + if (!newDestination) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error input: Inserting compose", + }); + } - return newDestination; + return newDestination; }; export const findComposeById = async (composeId: string) => { - const result = await db.query.compose.findFirst({ - where: eq(compose.composeId, composeId), - with: { - project: true, - deployments: true, - mounts: true, - domains: true, - github: true, - gitlab: true, - bitbucket: true, - server: true, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Compose not found", - }); - } - return result; + const result = await db.query.compose.findFirst({ + where: eq(compose.composeId, composeId), + with: { + project: true, + deployments: true, + mounts: true, + domains: true, + github: true, + gitlab: true, + bitbucket: true, + server: true, + }, + }); + if (!result) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Compose not found", + }); + } + return result; }; export const loadServices = async ( - composeId: string, - type: "fetch" | "cache" = "fetch", + composeId: string, + type: "fetch" | "cache" = "fetch" ) => { - const compose = await findComposeById(composeId); + const compose = await findComposeById(composeId); - if (type === "fetch") { - if (compose.serverId) { - await cloneComposeRemote(compose); - } else { - await cloneCompose(compose); - } - } + if (type === "fetch") { + if (compose.serverId) { + await cloneComposeRemote(compose); + } else { + await cloneCompose(compose); + } + } - let composeData: ComposeSpecification | null; + let composeData: ComposeSpecification | null; - if (compose.serverId) { - composeData = await loadDockerComposeRemote(compose); - } else { - composeData = await loadDockerCompose(compose); - } + if (compose.serverId) { + composeData = await loadDockerComposeRemote(compose); + } else { + composeData = await loadDockerCompose(compose); + } - if (compose.randomize && composeData) { - const randomizedCompose = randomizeSpecificationFile( - composeData, - compose.suffix, - ); - composeData = randomizedCompose; - } + if (compose.randomize && composeData) { + const randomizedCompose = randomizeSpecificationFile( + composeData, + compose.suffix + ); + composeData = randomizedCompose; + } - if (!composeData?.services) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Services not found", - }); - } + if (!composeData?.services) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Services not found", + }); + } - const services = Object.keys(composeData.services); + const services = Object.keys(composeData.services); - return [...services]; + return [...services]; }; export const updateCompose = async ( - composeId: string, - composeData: Partial, + composeId: string, + composeData: Partial ) => { - const { appName, ...rest } = composeData; - const composeResult = await db - .update(compose) - .set({ - ...rest, - }) - .where(eq(compose.composeId, composeId)) - .returning(); + const { appName, ...rest } = composeData; + const composeResult = await db + .update(compose) + .set({ + ...rest, + }) + .where(eq(compose.composeId, composeId)) + .returning(); - return composeResult[0]; + return composeResult[0]; }; export const deployCompose = async ({ - composeId, - titleLog = "Manual deployment", - descriptionLog = "", + composeId, + titleLog = "Manual deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`; - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); + const compose = await findComposeById(composeId); + const buildLink = `${await getDokployUrl()}/dashboard/project/${ + compose.projectId + }/services/compose/${compose.composeId}?tab=deployments`; + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); - try { - if (compose.sourceType === "github") { - await cloneGithubRepository({ - ...compose, - logPath: deployment.logPath, - type: "compose", - }); - } else if (compose.sourceType === "gitlab") { - await cloneGitlabRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "bitbucket") { - await cloneBitbucketRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "git") { - await cloneGitRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "raw") { - await createComposeFile(compose, deployment.logPath); - } - await buildCompose(compose, deployment.logPath); - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); + try { + if (compose.sourceType === "github") { + await cloneGithubRepository({ + ...compose, + logPath: deployment.logPath, + type: "compose", + }); + } else if (compose.sourceType === "gitlab") { + await cloneGitlabRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "bitbucket") { + await cloneBitbucketRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "git") { + await cloneGitRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "raw") { + await createComposeFile(compose, deployment.logPath); + } + await buildCompose(compose, deployment.logPath); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); - await sendBuildSuccessNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - buildLink, - adminId: compose.project.adminId, - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - await sendBuildErrorNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - adminId: compose.project.adminId, - }); - throw error; - } + await sendBuildSuccessNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + buildLink, + adminId: compose.project.adminId, + }); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + await sendBuildErrorNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + // @ts-ignore + errorMessage: error?.message || "Error to build", + buildLink, + adminId: compose.project.adminId, + }); + throw error; + } }; export const rebuildCompose = async ({ - composeId, - titleLog = "Rebuild deployment", - descriptionLog = "", + composeId, + titleLog = "Rebuild deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); + const compose = await findComposeById(composeId); + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); - try { - if (compose.serverId) { - await getBuildComposeCommand(compose, deployment.logPath); - } else { - await buildCompose(compose, deployment.logPath); - } + try { + if (compose.serverId) { + await getBuildComposeCommand(compose, deployment.logPath); + } else { + await buildCompose(compose, deployment.logPath); + } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + throw error; + } - return true; + return true; }; export const deployRemoteCompose = async ({ - composeId, - titleLog = "Manual deployment", - descriptionLog = "", + composeId, + titleLog = "Manual deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`; - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); - try { - if (compose.serverId) { - let command = "set -e;"; + const compose = await findComposeById(composeId); + const buildLink = `${await getDokployUrl()}/dashboard/project/${ + compose.projectId + }/services/compose/${compose.composeId}?tab=deployments`; + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); + try { + if (compose.serverId) { + let command = "set -e;"; - if (compose.sourceType === "github") { - command += await getGithubCloneCommand({ - ...compose, - logPath: deployment.logPath, - type: "compose", - serverId: compose.serverId, - }); - } else if (compose.sourceType === "gitlab") { - command += await getGitlabCloneCommand( - compose, - deployment.logPath, - true, - ); - } else if (compose.sourceType === "bitbucket") { - command += await getBitbucketCloneCommand( - compose, - deployment.logPath, - true, - ); - } else if (compose.sourceType === "git") { - command += await getCustomGitCloneCommand( - compose, - deployment.logPath, - true, - ); - } else if (compose.sourceType === "raw") { - command += getCreateComposeFileCommand(compose, deployment.logPath); - } + if (compose.sourceType === "github") { + command += await getGithubCloneCommand({ + ...compose, + logPath: deployment.logPath, + type: "compose", + serverId: compose.serverId, + }); + } else if (compose.sourceType === "gitlab") { + command += await getGitlabCloneCommand( + compose, + deployment.logPath, + true + ); + } else if (compose.sourceType === "bitbucket") { + command += await getBitbucketCloneCommand( + compose, + deployment.logPath, + true + ); + } else if (compose.sourceType === "git") { + command += await getCustomGitCloneCommand( + compose, + deployment.logPath, + true + ); + } else if (compose.sourceType === "raw") { + command += getCreateComposeFileCommand(compose, deployment.logPath); + } - await execAsyncRemote(compose.serverId, command); - await getBuildComposeCommand(compose, deployment.logPath); - } + await execAsyncRemote(compose.serverId, command); + await getBuildComposeCommand(compose, deployment.logPath); + } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); - await sendBuildSuccessNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - buildLink, - adminId: compose.project.adminId, - }); - } catch (error) { - // @ts-ignore - const encodedContent = encodeBase64(error?.message); + await sendBuildSuccessNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + buildLink, + adminId: compose.project.adminId, + }); + } catch (error) { + // @ts-ignore + const encodedContent = encodeBase64(error?.message); - await execAsyncRemote( - compose.serverId, - ` + await execAsyncRemote( + compose.serverId, + ` echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath}; echo "Error occurred ❌, check the logs for details." >> ${deployment.logPath}; - echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`, - ); - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - await sendBuildErrorNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - adminId: compose.project.adminId, - }); - throw error; - } + echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";` + ); + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + await sendBuildErrorNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + // @ts-ignore + errorMessage: error?.message || "Error to build", + buildLink, + adminId: compose.project.adminId, + }); + throw error; + } }; export const rebuildRemoteCompose = async ({ - composeId, - titleLog = "Rebuild deployment", - descriptionLog = "", + composeId, + titleLog = "Rebuild deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); + const compose = await findComposeById(composeId); + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); - try { - if (compose.serverId) { - await getBuildComposeCommand(compose, deployment.logPath); - } + try { + if (compose.serverId) { + await getBuildComposeCommand(compose, deployment.logPath); + } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - } catch (error) { - // @ts-ignore - const encodedContent = encodeBase64(error?.message); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + // @ts-ignore + const encodedContent = encodeBase64(error?.message); - await execAsyncRemote( - compose.serverId, - ` + await execAsyncRemote( + compose.serverId, + ` echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath}; echo "Error occurred ❌, check the logs for details." >> ${deployment.logPath}; - echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`, - ); - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } + echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";` + ); + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + throw error; + } - return true; + return true; }; export const removeCompose = async ( - compose: Compose, - deleteVolumes: boolean, + compose: Compose, + deleteVolumes: boolean ) => { - try { - const { COMPOSE_PATH } = paths(!!compose.serverId); - const projectPath = join(COMPOSE_PATH, compose.appName); + try { + const { COMPOSE_PATH } = paths(!!compose.serverId); + const projectPath = join(COMPOSE_PATH, compose.appName); - console.log("API: DELETE VOLUMES=", deleteVolumes); + console.log("API: DELETE VOLUMES=", deleteVolumes); - if (compose.composeType === "stack") { - const listVolumesCommand = `docker volume ls --format \"{{.Name}}\" | grep ${compose.appName}`; - const removeVolumesCommand = `${listVolumesCommand} | xargs -r docker volume rm`; - let command: string; - if (deleteVolumes) { - command = `cd ${projectPath} && docker stack rm ${compose.appName} && ${removeVolumesCommand} && rm -rf ${projectPath}`; - } else { - command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; - } + if (compose.composeType === "stack") { + const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; - if (compose.serverId) { - await execAsyncRemote(compose.serverId, command); - } else { - await execAsync(command); - } - await execAsync(command, { - cwd: projectPath, - }); - } else { - let command: string; - if (deleteVolumes) { - command = `cd ${projectPath} && docker compose -p ${compose.appName} down --volumes && rm -rf ${projectPath}`; - } else { - command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; - } + if (compose.serverId) { + await execAsyncRemote(compose.serverId, command); + } else { + await execAsync(command); + } + await execAsync(command, { + cwd: projectPath, + }); + } else { + let command: string; + if (deleteVolumes) { + command = `cd ${projectPath} && docker compose -p ${compose.appName} down --volumes && rm -rf ${projectPath}`; + } else { + command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; + } - if (compose.serverId) { - await execAsyncRemote(compose.serverId, command); - } else { - await execAsync(command, { - cwd: projectPath, - }); - } - } - } catch (error) { - throw error; - } + if (compose.serverId) { + await execAsyncRemote(compose.serverId, command); + } else { + await execAsync(command, { + cwd: projectPath, + }); + } + } + } catch (error) { + throw error; + } - return true; + 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"), - }); - } - } + 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; - } + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + await updateCompose(composeId, { + composeStatus: "idle", + }); + throw error; + } - return true; + return true; }; export const stopCompose = 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)} && docker compose -p ${compose.appName} stop`, - ); - } else { - await execAsync(`docker compose -p ${compose.appName} stop`, { - cwd: join(COMPOSE_PATH, compose.appName), - }); - } - } + 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)} && docker compose -p ${ + compose.appName + } stop` + ); + } else { + await execAsync(`docker compose -p ${compose.appName} stop`, { + cwd: join(COMPOSE_PATH, compose.appName), + }); + } + } - await updateCompose(composeId, { - composeStatus: "idle", - }); - } catch (error) { - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } + await updateCompose(composeId, { + composeStatus: "idle", + }); + } catch (error) { + await updateCompose(composeId, { + composeStatus: "error", + }); + throw error; + } - return true; + return true; }; From 375decebb29f43042b3a3e8e51e12bcb20dc1a8a Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Mon, 23 Dec 2024 08:36:18 +0100 Subject: [PATCH 08/13] chore: remove application delete volume --- .../application/delete-application.tsx | 291 ++--- .../dokploy/server/api/routers/application.ts | 1139 ++++++++--------- packages/server/src/db/schema/application.ts | 831 ++++++------ 3 files changed, 1113 insertions(+), 1148 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/delete-application.tsx b/apps/dokploy/components/dashboard/application/delete-application.tsx index ff63ef5c5..7202b6f13 100644 --- a/apps/dokploy/components/dashboard/application/delete-application.tsx +++ b/apps/dokploy/components/dashboard/application/delete-application.tsx @@ -1,22 +1,21 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; @@ -29,158 +28,134 @@ import { toast } from "sonner"; import { z } from "zod"; const deleteApplicationSchema = z.object({ - projectName: z.string().min(1, { - message: "Application name is required", - }), - deleteVolumes: z.boolean(), + projectName: z.string().min(1, { + message: "Application name is required", + }), }); type DeleteApplication = z.infer; interface Props { - applicationId: string; + applicationId: string; } export const DeleteApplication = ({ applicationId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading } = api.application.delete.useMutation(); - const { data } = api.application.one.useQuery( - { applicationId }, - { enabled: !!applicationId }, - ); - const { push } = useRouter(); - const form = useForm({ - defaultValues: { - projectName: "", - deleteVolumes: false, - }, - resolver: zodResolver(deleteApplicationSchema), - }); + const [isOpen, setIsOpen] = useState(false); + const { mutateAsync, isLoading } = api.application.delete.useMutation(); + const { data } = api.application.one.useQuery( + { applicationId }, + { enabled: !!applicationId } + ); + const { push } = useRouter(); + const form = useForm({ + defaultValues: { + projectName: "", + }, + resolver: zodResolver(deleteApplicationSchema), + }); - const onSubmit = async (formData: DeleteApplication) => { - const expectedName = `${data?.name}/${data?.appName}`; - if (formData.projectName === expectedName) { - await mutateAsync({ - applicationId, - deleteVolumes: formData.deleteVolumes, - }) - .then((data) => { - push(`/dashboard/project/${data?.projectId}`); - toast.success("Application deleted successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error deleting the application"); - }); - } else { - form.setError("projectName", { - message: "Project name does not match", - }); - } - }; + const onSubmit = async (formData: DeleteApplication) => { + const expectedName = `${data?.name}/${data?.appName}`; + if (formData.projectName === expectedName) { + await mutateAsync({ + applicationId, + }) + .then((data) => { + push(`/dashboard/project/${data?.projectId}`); + toast.success("Application deleted successfully"); + setIsOpen(false); + }) + .catch(() => { + toast.error("Error deleting the application"); + }); + } else { + form.setError("projectName", { + message: "Project name does not match", + }); + } + }; - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - application. If you are sure please enter the application name to - delete this application. - - -
-
- - ( - - - - To confirm, type{" "} - { - if (data?.name && data?.appName) { - navigator.clipboard.writeText( - `${data.name}/${data.appName}`, - ); - toast.success("Copied to clipboard. Be careful!"); - } - }} - > - {data?.name}/{data?.appName}  - - {" "} - in the box below: - - - - - - - - )} - /> - ( - -
- - - - - - Delete volumes associated with this compose - -
- -
- )} - /> - - -
- - - - -
-
- ); + return ( + + + + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete the + application. If you are sure please enter the application name to + delete this application. + + +
+
+ + ( + + + + To confirm, type{" "} + { + if (data?.name && data?.appName) { + navigator.clipboard.writeText( + `${data.name}/${data.appName}` + ); + toast.success("Copied to clipboard. Be careful!"); + } + }} + > + {data?.name}/{data?.appName}  + + {" "} + in the box below: + + + + + + + + )} + /> + + +
+ + + + +
+
+ ); }; diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 9b16d5796..e2fe4cb4e 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -1,55 +1,54 @@ import { - createTRPCRouter, - protectedProcedure, - uploadProcedure, + createTRPCRouter, + protectedProcedure, + uploadProcedure, } from "@/server/api/trpc"; import { db } from "@/server/db"; import { - apiCreateApplication, - apiDeleteApplication, - apiFindMonitoringStats, - apiFindOneApplication, - apiReloadApplication, - apiSaveBitbucketProvider, - apiSaveBuildType, - apiSaveDockerProvider, - apiSaveEnvironmentVariables, - apiSaveGitProvider, - apiSaveGithubProvider, - apiSaveGitlabProvider, - apiUpdateApplication, - applications, + apiCreateApplication, + apiFindMonitoringStats, + apiFindOneApplication, + apiReloadApplication, + apiSaveBitbucketProvider, + apiSaveBuildType, + apiSaveDockerProvider, + apiSaveEnvironmentVariables, + apiSaveGitProvider, + apiSaveGithubProvider, + apiSaveGitlabProvider, + apiUpdateApplication, + applications, } from "@/server/db/schema"; import type { DeploymentJob } from "@/server/queues/queue-types"; import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup"; import { deploy } from "@/server/utils/deploy"; import { uploadFileSchema } from "@/utils/schema"; import { - IS_CLOUD, - addNewService, - checkServiceAccess, - createApplication, - deleteAllMiddlewares, - findApplicationById, - findProjectById, - getApplicationStats, - readConfig, - readRemoteConfig, - removeDeployments, - removeDirectoryCode, - removeMonitoringDirectory, - removeService, - removeTraefikConfig, - startService, - startServiceRemote, - stopService, - stopServiceRemote, - unzipDrop, - updateApplication, - updateApplicationStatus, - writeConfig, - writeConfigRemote, - // uploadFileSchema + IS_CLOUD, + addNewService, + checkServiceAccess, + createApplication, + deleteAllMiddlewares, + findApplicationById, + findProjectById, + getApplicationStats, + readConfig, + readRemoteConfig, + removeDeployments, + removeDirectoryCode, + removeMonitoringDirectory, + removeService, + removeTraefikConfig, + startService, + startServiceRemote, + stopService, + stopServiceRemote, + unzipDrop, + updateApplication, + updateApplicationStatus, + writeConfig, + writeConfigRemote, + // uploadFileSchema } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -57,573 +56,569 @@ import { nanoid } from "nanoid"; import { z } from "zod"; export const applicationRouter = createTRPCRouter({ - create: protectedProcedure - .input(apiCreateApplication) - .mutation(async ({ input, ctx }) => { - try { - if (ctx.user.rol === "user") { - await checkServiceAccess(ctx.user.authId, input.projectId, "create"); - } + create: protectedProcedure + .input(apiCreateApplication) + .mutation(async ({ input, ctx }) => { + try { + if (ctx.user.rol === "user") { + await checkServiceAccess(ctx.user.authId, input.projectId, "create"); + } - if (IS_CLOUD && !input.serverId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You need to use a server to create an application", - }); - } + if (IS_CLOUD && !input.serverId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You need to use a server to create an application", + }); + } - const project = await findProjectById(input.projectId); - if (project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this project", - }); - } - const newApplication = await createApplication(input); + const project = await findProjectById(input.projectId); + if (project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this project", + }); + } + const newApplication = await createApplication(input); - if (ctx.user.rol === "user") { - await addNewService(ctx.user.authId, newApplication.applicationId); - } - return newApplication; - } catch (error: unknown) { - if (error instanceof TRPCError) { - throw error; - } - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the application", - cause: error, - }); - } - }), - one: protectedProcedure - .input(apiFindOneApplication) - .query(async ({ input, ctx }) => { - if (ctx.user.rol === "user") { - await checkServiceAccess( - ctx.user.authId, - input.applicationId, - "access", - ); - } - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this application", - }); - } - return application; - }), + if (ctx.user.rol === "user") { + await addNewService(ctx.user.authId, newApplication.applicationId); + } + return newApplication; + } catch (error: unknown) { + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the application", + cause: error, + }); + } + }), + one: protectedProcedure + .input(apiFindOneApplication) + .query(async ({ input, ctx }) => { + if (ctx.user.rol === "user") { + await checkServiceAccess( + ctx.user.authId, + input.applicationId, + "access" + ); + } + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this application", + }); + } + return application; + }), - reload: protectedProcedure - .input(apiReloadApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to reload this application", - }); - } - if (application.serverId) { - await stopServiceRemote(application.serverId, input.appName); - } else { - await stopService(input.appName); - } - await updateApplicationStatus(input.applicationId, "idle"); + reload: protectedProcedure + .input(apiReloadApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this application", + }); + } + if (application.serverId) { + await stopServiceRemote(application.serverId, input.appName); + } else { + await stopService(input.appName); + } + await updateApplicationStatus(input.applicationId, "idle"); - if (application.serverId) { - await startServiceRemote(application.serverId, input.appName); - } else { - await startService(input.appName); - } - await updateApplicationStatus(input.applicationId, "done"); - return true; - }), + if (application.serverId) { + await startServiceRemote(application.serverId, input.appName); + } else { + await startService(input.appName); + } + await updateApplicationStatus(input.applicationId, "done"); + return true; + }), - delete: protectedProcedure - .input(apiDeleteApplication) - .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "user") { - await checkServiceAccess( - ctx.user.authId, - input.applicationId, - "delete", - ); - } - const application = await findApplicationById(input.applicationId); + delete: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + if (ctx.user.rol === "user") { + await checkServiceAccess( + ctx.user.authId, + input.applicationId, + "delete" + ); + } + const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to delete this application", - }); - } + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this application", + }); + } - const result = await db - .delete(applications) - .where(eq(applications.applicationId, input.applicationId)) - .returning(); + const result = await db + .delete(applications) + .where(eq(applications.applicationId, input.applicationId)) + .returning(); - const cleanupOperations = [ - async () => await deleteAllMiddlewares(application), - async () => await removeDeployments(application), - async () => - await removeDirectoryCode(application.appName, application.serverId), - async () => - await removeMonitoringDirectory( - application.appName, - application.serverId, - ), - async () => - await removeTraefikConfig(application.appName, application.serverId), - async () => - await removeService( - application?.appName, - application.serverId, - input.deleteVolumes, - ), - ]; + const cleanupOperations = [ + async () => await deleteAllMiddlewares(application), + async () => await removeDeployments(application), + async () => + await removeDirectoryCode(application.appName, application.serverId), + async () => + await removeMonitoringDirectory( + application.appName, + application.serverId + ), + async () => + await removeTraefikConfig(application.appName, application.serverId), + async () => + await removeService(application?.appName, application.serverId), + ]; - for (const operation of cleanupOperations) { - try { - await operation(); - } catch (error) {} - } + for (const operation of cleanupOperations) { + try { + await operation(); + } catch (error) {} + } - return result[0]; - }), + return result[0]; + }), - stop: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const service = await findApplicationById(input.applicationId); - if (service.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to stop this application", - }); - } - if (service.serverId) { - await stopServiceRemote(service.serverId, service.appName); - } else { - await stopService(service.appName); - } - await updateApplicationStatus(input.applicationId, "idle"); + stop: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const service = await findApplicationById(input.applicationId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this application", + }); + } + if (service.serverId) { + await stopServiceRemote(service.serverId, service.appName); + } else { + await stopService(service.appName); + } + await updateApplicationStatus(input.applicationId, "idle"); - return service; - }), + return service; + }), - start: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const service = await findApplicationById(input.applicationId); - if (service.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to start this application", - }); - } + start: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const service = await findApplicationById(input.applicationId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this application", + }); + } - if (service.serverId) { - await startServiceRemote(service.serverId, service.appName); - } else { - await startService(service.appName); - } - await updateApplicationStatus(input.applicationId, "done"); + if (service.serverId) { + await startServiceRemote(service.serverId, service.appName); + } else { + await startService(service.appName); + } + await updateApplicationStatus(input.applicationId, "done"); - return service; - }), + return service; + }), - redeploy: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to redeploy this application", - }); - } - const jobData: DeploymentJob = { - applicationId: input.applicationId, - titleLog: "Rebuild deployment", - descriptionLog: "", - type: "redeploy", - applicationType: "application", - server: !!application.serverId, - }; + redeploy: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to redeploy this application", + }); + } + const jobData: DeploymentJob = { + applicationId: input.applicationId, + titleLog: "Rebuild deployment", + descriptionLog: "", + type: "redeploy", + applicationType: "application", + server: !!application.serverId, + }; - if (IS_CLOUD && application.serverId) { - jobData.serverId = application.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - }, - ); - }), - saveEnvironment: protectedProcedure - .input(apiSaveEnvironmentVariables) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this environment", - }); - } - await updateApplication(input.applicationId, { - env: input.env, - buildArgs: input.buildArgs, - }); - return true; - }), - saveBuildType: protectedProcedure - .input(apiSaveBuildType) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this build type", - }); - } - await updateApplication(input.applicationId, { - buildType: input.buildType, - dockerfile: input.dockerfile, - publishDirectory: input.publishDirectory, - dockerContextPath: input.dockerContextPath, - dockerBuildStage: input.dockerBuildStage, - herokuVersion: input.herokuVersion, - }); + if (IS_CLOUD && application.serverId) { + jobData.serverId = application.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + } + ); + }), + saveEnvironment: protectedProcedure + .input(apiSaveEnvironmentVariables) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } + await updateApplication(input.applicationId, { + env: input.env, + buildArgs: input.buildArgs, + }); + return true; + }), + saveBuildType: protectedProcedure + .input(apiSaveBuildType) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this build type", + }); + } + await updateApplication(input.applicationId, { + buildType: input.buildType, + dockerfile: input.dockerfile, + publishDirectory: input.publishDirectory, + dockerContextPath: input.dockerContextPath, + dockerBuildStage: input.dockerBuildStage, + herokuVersion: input.herokuVersion, + }); - return true; - }), - saveGithubProvider: protectedProcedure - .input(apiSaveGithubProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this github provider", - }); - } - await updateApplication(input.applicationId, { - repository: input.repository, - branch: input.branch, - sourceType: "github", - owner: input.owner, - buildPath: input.buildPath, - applicationStatus: "idle", - githubId: input.githubId, - }); + return true; + }), + saveGithubProvider: protectedProcedure + .input(apiSaveGithubProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this github provider", + }); + } + await updateApplication(input.applicationId, { + repository: input.repository, + branch: input.branch, + sourceType: "github", + owner: input.owner, + buildPath: input.buildPath, + applicationStatus: "idle", + githubId: input.githubId, + }); - return true; - }), - saveGitlabProvider: protectedProcedure - .input(apiSaveGitlabProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this gitlab provider", - }); - } - await updateApplication(input.applicationId, { - gitlabRepository: input.gitlabRepository, - gitlabOwner: input.gitlabOwner, - gitlabBranch: input.gitlabBranch, - gitlabBuildPath: input.gitlabBuildPath, - sourceType: "gitlab", - applicationStatus: "idle", - gitlabId: input.gitlabId, - gitlabProjectId: input.gitlabProjectId, - gitlabPathNamespace: input.gitlabPathNamespace, - }); + return true; + }), + saveGitlabProvider: protectedProcedure + .input(apiSaveGitlabProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this gitlab provider", + }); + } + await updateApplication(input.applicationId, { + gitlabRepository: input.gitlabRepository, + gitlabOwner: input.gitlabOwner, + gitlabBranch: input.gitlabBranch, + gitlabBuildPath: input.gitlabBuildPath, + sourceType: "gitlab", + applicationStatus: "idle", + gitlabId: input.gitlabId, + gitlabProjectId: input.gitlabProjectId, + gitlabPathNamespace: input.gitlabPathNamespace, + }); - return true; - }), - saveBitbucketProvider: protectedProcedure - .input(apiSaveBitbucketProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this bitbucket provider", - }); - } - await updateApplication(input.applicationId, { - bitbucketRepository: input.bitbucketRepository, - bitbucketOwner: input.bitbucketOwner, - bitbucketBranch: input.bitbucketBranch, - bitbucketBuildPath: input.bitbucketBuildPath, - sourceType: "bitbucket", - applicationStatus: "idle", - bitbucketId: input.bitbucketId, - }); + return true; + }), + saveBitbucketProvider: protectedProcedure + .input(apiSaveBitbucketProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this bitbucket provider", + }); + } + await updateApplication(input.applicationId, { + bitbucketRepository: input.bitbucketRepository, + bitbucketOwner: input.bitbucketOwner, + bitbucketBranch: input.bitbucketBranch, + bitbucketBuildPath: input.bitbucketBuildPath, + sourceType: "bitbucket", + applicationStatus: "idle", + bitbucketId: input.bitbucketId, + }); - return true; - }), - saveDockerProvider: protectedProcedure - .input(apiSaveDockerProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this docker provider", - }); - } - await updateApplication(input.applicationId, { - dockerImage: input.dockerImage, - username: input.username, - password: input.password, - sourceType: "docker", - applicationStatus: "idle", - registryUrl: input.registryUrl, - }); + return true; + }), + saveDockerProvider: protectedProcedure + .input(apiSaveDockerProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this docker provider", + }); + } + await updateApplication(input.applicationId, { + dockerImage: input.dockerImage, + username: input.username, + password: input.password, + sourceType: "docker", + applicationStatus: "idle", + registryUrl: input.registryUrl, + }); - return true; - }), - saveGitProdiver: protectedProcedure - .input(apiSaveGitProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this git provider", - }); - } - await updateApplication(input.applicationId, { - customGitBranch: input.customGitBranch, - customGitBuildPath: input.customGitBuildPath, - customGitUrl: input.customGitUrl, - customGitSSHKeyId: input.customGitSSHKeyId, - sourceType: "git", - applicationStatus: "idle", - }); + return true; + }), + saveGitProdiver: protectedProcedure + .input(apiSaveGitProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this git provider", + }); + } + await updateApplication(input.applicationId, { + customGitBranch: input.customGitBranch, + customGitBuildPath: input.customGitBuildPath, + customGitUrl: input.customGitUrl, + customGitSSHKeyId: input.customGitSSHKeyId, + sourceType: "git", + applicationStatus: "idle", + }); - return true; - }), - markRunning: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to mark this application as running", - }); - } - await updateApplicationStatus(input.applicationId, "running"); - }), - update: protectedProcedure - .input(apiUpdateApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to update this application", - }); - } - const { applicationId, ...rest } = input; - const updateApp = await updateApplication(applicationId, { - ...rest, - }); + return true; + }), + markRunning: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to mark this application as running", + }); + } + await updateApplicationStatus(input.applicationId, "running"); + }), + update: protectedProcedure + .input(apiUpdateApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this application", + }); + } + const { applicationId, ...rest } = input; + const updateApp = await updateApplication(applicationId, { + ...rest, + }); - if (!updateApp) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Update: Error to update application", - }); - } + if (!updateApp) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Update: Error to update application", + }); + } - return true; - }), - refreshToken: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to refresh this application", - }); - } - await updateApplication(input.applicationId, { - refreshToken: nanoid(), - }); - return true; - }), - deploy: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to deploy this application", - }); - } - const jobData: DeploymentJob = { - applicationId: input.applicationId, - titleLog: "Manual deployment", - descriptionLog: "", - type: "deploy", - applicationType: "application", - server: !!application.serverId, - }; - if (IS_CLOUD && application.serverId) { - jobData.serverId = application.serverId; - await deploy(jobData); + return true; + }), + refreshToken: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to refresh this application", + }); + } + await updateApplication(input.applicationId, { + refreshToken: nanoid(), + }); + return true; + }), + deploy: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this application", + }); + } + const jobData: DeploymentJob = { + applicationId: input.applicationId, + titleLog: "Manual deployment", + descriptionLog: "", + type: "deploy", + applicationType: "application", + server: !!application.serverId, + }; + if (IS_CLOUD && application.serverId) { + jobData.serverId = application.serverId; + await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - }, - ); - }), + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + } + ); + }), - cleanQueues: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to clean this application", - }); - } - await cleanQueuesByApplication(input.applicationId); - }), + cleanQueues: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to clean this application", + }); + } + await cleanQueuesByApplication(input.applicationId); + }), - readTraefikConfig: protectedProcedure - .input(apiFindOneApplication) - .query(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to read this application", - }); - } + readTraefikConfig: protectedProcedure + .input(apiFindOneApplication) + .query(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to read this application", + }); + } - let traefikConfig = null; - if (application.serverId) { - traefikConfig = await readRemoteConfig( - application.serverId, - application.appName, - ); - } else { - traefikConfig = readConfig(application.appName); - } - return traefikConfig; - }), + let traefikConfig = null; + if (application.serverId) { + traefikConfig = await readRemoteConfig( + application.serverId, + application.appName + ); + } else { + traefikConfig = readConfig(application.appName); + } + return traefikConfig; + }), - dropDeployment: protectedProcedure - .meta({ - openapi: { - path: "/drop-deployment", - method: "POST", - override: true, - enabled: false, - }, - }) - .use(uploadProcedure) - .input(uploadFileSchema) - .mutation(async ({ input, ctx }) => { - const zipFile = input.zip; + dropDeployment: protectedProcedure + .meta({ + openapi: { + path: "/drop-deployment", + method: "POST", + override: true, + enabled: false, + }, + }) + .use(uploadProcedure) + .input(uploadFileSchema) + .mutation(async ({ input, ctx }) => { + const zipFile = input.zip; - const app = await findApplicationById(input.applicationId as string); + const app = await findApplicationById(input.applicationId as string); - if (app.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to deploy this application", - }); - } + if (app.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this application", + }); + } - updateApplication(input.applicationId as string, { - sourceType: "drop", - dropBuildPath: input.dropBuildPath, - }); + updateApplication(input.applicationId as string, { + sourceType: "drop", + dropBuildPath: input.dropBuildPath, + }); - await unzipDrop(zipFile, app); - const jobData: DeploymentJob = { - applicationId: app.applicationId, - titleLog: "Manual deployment", - descriptionLog: "", - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } + await unzipDrop(zipFile, app); + const jobData: DeploymentJob = { + applicationId: app.applicationId, + titleLog: "Manual deployment", + descriptionLog: "", + 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, - }, - ); - return true; - }), - updateTraefikConfig: protectedProcedure - .input(z.object({ applicationId: z.string(), traefikConfig: z.string() })) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + } + ); + return true; + }), + updateTraefikConfig: protectedProcedure + .input(z.object({ applicationId: z.string(), traefikConfig: z.string() })) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to update this application", - }); - } + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this application", + }); + } - if (application.serverId) { - await writeConfigRemote( - application.serverId, - application.appName, - input.traefikConfig, - ); - } else { - writeConfig(application.appName, input.traefikConfig); - } - return true; - }), - readAppMonitoring: protectedProcedure - .input(apiFindMonitoringStats) - .query(async ({ input, ctx }) => { - if (IS_CLOUD) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Functionality not available in cloud version", - }); - } - const stats = await getApplicationStats(input.appName); + if (application.serverId) { + await writeConfigRemote( + application.serverId, + application.appName, + input.traefikConfig + ); + } else { + writeConfig(application.appName, input.traefikConfig); + } + return true; + }), + readAppMonitoring: protectedProcedure + .input(apiFindMonitoringStats) + .query(async ({ input, ctx }) => { + if (IS_CLOUD) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Functionality not available in cloud version", + }); + } + const stats = await getApplicationStats(input.appName); - return stats; - }), + return stats; + }), }); diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index 923ea130e..425f8d134 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -1,11 +1,11 @@ import { relations } from "drizzle-orm"; import { - boolean, - integer, - json, - pgEnum, - pgTable, - text, + boolean, + integer, + json, + pgEnum, + pgTable, + text, } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; @@ -28,498 +28,493 @@ import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; export const sourceType = pgEnum("sourceType", [ - "docker", - "git", - "github", - "gitlab", - "bitbucket", - "drop", + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "drop", ]); export const buildType = pgEnum("buildType", [ - "dockerfile", - "heroku_buildpacks", - "paketo_buildpacks", - "nixpacks", - "static", + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", ]); // TODO: refactor this types export interface HealthCheckSwarm { - Test?: string[] | undefined; - Interval?: number | undefined; - Timeout?: number | undefined; - StartPeriod?: number | undefined; - Retries?: number | undefined; + Test?: string[] | undefined; + Interval?: number | undefined; + Timeout?: number | undefined; + StartPeriod?: number | undefined; + Retries?: number | undefined; } export interface RestartPolicySwarm { - Condition?: string | undefined; - Delay?: number | undefined; - MaxAttempts?: number | undefined; - Window?: number | undefined; + Condition?: string | undefined; + Delay?: number | undefined; + MaxAttempts?: number | undefined; + Window?: number | undefined; } export interface PlacementSwarm { - Constraints?: string[] | undefined; - Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined; - MaxReplicas?: number | undefined; - Platforms?: - | Array<{ - Architecture: string; - OS: string; - }> - | undefined; + Constraints?: string[] | undefined; + Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined; + MaxReplicas?: number | undefined; + Platforms?: + | Array<{ + Architecture: string; + OS: string; + }> + | undefined; } export interface UpdateConfigSwarm { - Parallelism: number; - Delay?: number | undefined; - FailureAction?: string | undefined; - Monitor?: number | undefined; - MaxFailureRatio?: number | undefined; - Order: string; + Parallelism: number; + Delay?: number | undefined; + FailureAction?: string | undefined; + Monitor?: number | undefined; + MaxFailureRatio?: number | undefined; + Order: string; } export interface ServiceModeSwarm { - Replicated?: { Replicas?: number | undefined } | undefined; - Global?: {} | undefined; - ReplicatedJob?: - | { - MaxConcurrent?: number | undefined; - TotalCompletions?: number | undefined; - } - | undefined; - GlobalJob?: {} | undefined; + Replicated?: { Replicas?: number | undefined } | undefined; + Global?: {} | undefined; + ReplicatedJob?: + | { + MaxConcurrent?: number | undefined; + TotalCompletions?: number | undefined; + } + | undefined; + GlobalJob?: {} | undefined; } export interface NetworkSwarm { - Target?: string | undefined; - Aliases?: string[] | undefined; - DriverOpts?: { [key: string]: string } | undefined; + Target?: string | undefined; + Aliases?: string[] | undefined; + DriverOpts?: { [key: string]: string } | undefined; } export interface LabelsSwarm { - [name: string]: string; + [name: string]: string; } export const applications = pgTable("application", { - applicationId: text("applicationId") - .notNull() - .primaryKey() - .$defaultFn(() => nanoid()), - name: text("name").notNull(), - appName: text("appName") - .notNull() - .$defaultFn(() => generateAppName("app")) - .unique(), - description: text("description"), - env: text("env"), - previewEnv: text("previewEnv"), - previewBuildArgs: text("previewBuildArgs"), - previewWildcard: text("previewWildcard"), - previewPort: integer("previewPort").default(3000), - previewHttps: boolean("previewHttps").notNull().default(false), - previewPath: text("previewPath").default("/"), - previewCertificateType: certificateType("certificateType") - .notNull() - .default("none"), - previewLimit: integer("previewLimit").default(3), - isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default( - false, - ), - buildArgs: text("buildArgs"), - memoryReservation: integer("memoryReservation"), - memoryLimit: integer("memoryLimit"), - cpuReservation: integer("cpuReservation"), - cpuLimit: integer("cpuLimit"), - title: text("title"), - enabled: boolean("enabled"), - subtitle: text("subtitle"), - command: text("command"), - refreshToken: text("refreshToken").$defaultFn(() => nanoid()), - sourceType: sourceType("sourceType").notNull().default("github"), - // Github - repository: text("repository"), - owner: text("owner"), - branch: text("branch"), - buildPath: text("buildPath").default("/"), - autoDeploy: boolean("autoDeploy").$defaultFn(() => true), - // Gitlab - gitlabProjectId: integer("gitlabProjectId"), - gitlabRepository: text("gitlabRepository"), - gitlabOwner: text("gitlabOwner"), - gitlabBranch: text("gitlabBranch"), - gitlabBuildPath: text("gitlabBuildPath").default("/"), - gitlabPathNamespace: text("gitlabPathNamespace"), - // Bitbucket - bitbucketRepository: text("bitbucketRepository"), - bitbucketOwner: text("bitbucketOwner"), - bitbucketBranch: text("bitbucketBranch"), - bitbucketBuildPath: text("bitbucketBuildPath").default("/"), - // Docker - username: text("username"), - password: text("password"), - dockerImage: text("dockerImage"), - registryUrl: text("registryUrl"), - // Git - customGitUrl: text("customGitUrl"), - customGitBranch: text("customGitBranch"), - customGitBuildPath: text("customGitBuildPath"), - customGitSSHKeyId: text("customGitSSHKeyId").references( - () => sshKeys.sshKeyId, - { - onDelete: "set null", - }, - ), - dockerfile: text("dockerfile"), - dockerContextPath: text("dockerContextPath"), - dockerBuildStage: text("dockerBuildStage"), - // Drop - dropBuildPath: text("dropBuildPath"), - // Docker swarm json - healthCheckSwarm: json("healthCheckSwarm").$type(), - restartPolicySwarm: json("restartPolicySwarm").$type(), - placementSwarm: json("placementSwarm").$type(), - updateConfigSwarm: json("updateConfigSwarm").$type(), - rollbackConfigSwarm: json("rollbackConfigSwarm").$type(), - modeSwarm: json("modeSwarm").$type(), - labelsSwarm: json("labelsSwarm").$type(), - networkSwarm: json("networkSwarm").$type(), - // - replicas: integer("replicas").default(1).notNull(), - applicationStatus: applicationStatus("applicationStatus") - .notNull() - .default("idle"), - buildType: buildType("buildType").notNull().default("nixpacks"), - herokuVersion: text("herokuVersion").default("24"), - publishDirectory: text("publishDirectory"), - createdAt: text("createdAt") - .notNull() - .$defaultFn(() => new Date().toISOString()), - registryId: text("registryId").references(() => registry.registryId, { - onDelete: "set null", - }), - projectId: text("projectId") - .notNull() - .references(() => projects.projectId, { onDelete: "cascade" }), - githubId: text("githubId").references(() => github.githubId, { - onDelete: "set null", - }), - gitlabId: text("gitlabId").references(() => gitlab.gitlabId, { - onDelete: "set null", - }), - bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, { - onDelete: "set null", - }), - serverId: text("serverId").references(() => server.serverId, { - onDelete: "cascade", - }), + applicationId: text("applicationId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + name: text("name").notNull(), + appName: text("appName") + .notNull() + .$defaultFn(() => generateAppName("app")) + .unique(), + description: text("description"), + env: text("env"), + previewEnv: text("previewEnv"), + previewBuildArgs: text("previewBuildArgs"), + previewWildcard: text("previewWildcard"), + previewPort: integer("previewPort").default(3000), + previewHttps: boolean("previewHttps").notNull().default(false), + previewPath: text("previewPath").default("/"), + previewCertificateType: certificateType("certificateType") + .notNull() + .default("none"), + previewLimit: integer("previewLimit").default(3), + isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default( + false + ), + buildArgs: text("buildArgs"), + memoryReservation: integer("memoryReservation"), + memoryLimit: integer("memoryLimit"), + cpuReservation: integer("cpuReservation"), + cpuLimit: integer("cpuLimit"), + title: text("title"), + enabled: boolean("enabled"), + subtitle: text("subtitle"), + command: text("command"), + refreshToken: text("refreshToken").$defaultFn(() => nanoid()), + sourceType: sourceType("sourceType").notNull().default("github"), + // Github + repository: text("repository"), + owner: text("owner"), + branch: text("branch"), + buildPath: text("buildPath").default("/"), + autoDeploy: boolean("autoDeploy").$defaultFn(() => true), + // Gitlab + gitlabProjectId: integer("gitlabProjectId"), + gitlabRepository: text("gitlabRepository"), + gitlabOwner: text("gitlabOwner"), + gitlabBranch: text("gitlabBranch"), + gitlabBuildPath: text("gitlabBuildPath").default("/"), + gitlabPathNamespace: text("gitlabPathNamespace"), + // Bitbucket + bitbucketRepository: text("bitbucketRepository"), + bitbucketOwner: text("bitbucketOwner"), + bitbucketBranch: text("bitbucketBranch"), + bitbucketBuildPath: text("bitbucketBuildPath").default("/"), + // Docker + username: text("username"), + password: text("password"), + dockerImage: text("dockerImage"), + registryUrl: text("registryUrl"), + // Git + customGitUrl: text("customGitUrl"), + customGitBranch: text("customGitBranch"), + customGitBuildPath: text("customGitBuildPath"), + customGitSSHKeyId: text("customGitSSHKeyId").references( + () => sshKeys.sshKeyId, + { + onDelete: "set null", + } + ), + dockerfile: text("dockerfile"), + dockerContextPath: text("dockerContextPath"), + dockerBuildStage: text("dockerBuildStage"), + // Drop + dropBuildPath: text("dropBuildPath"), + // Docker swarm json + healthCheckSwarm: json("healthCheckSwarm").$type(), + restartPolicySwarm: json("restartPolicySwarm").$type(), + placementSwarm: json("placementSwarm").$type(), + updateConfigSwarm: json("updateConfigSwarm").$type(), + rollbackConfigSwarm: json("rollbackConfigSwarm").$type(), + modeSwarm: json("modeSwarm").$type(), + labelsSwarm: json("labelsSwarm").$type(), + networkSwarm: json("networkSwarm").$type(), + // + replicas: integer("replicas").default(1).notNull(), + applicationStatus: applicationStatus("applicationStatus") + .notNull() + .default("idle"), + buildType: buildType("buildType").notNull().default("nixpacks"), + herokuVersion: text("herokuVersion").default("24"), + publishDirectory: text("publishDirectory"), + createdAt: text("createdAt") + .notNull() + .$defaultFn(() => new Date().toISOString()), + registryId: text("registryId").references(() => registry.registryId, { + onDelete: "set null", + }), + projectId: text("projectId") + .notNull() + .references(() => projects.projectId, { onDelete: "cascade" }), + githubId: text("githubId").references(() => github.githubId, { + onDelete: "set null", + }), + gitlabId: text("gitlabId").references(() => gitlab.gitlabId, { + onDelete: "set null", + }), + bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, { + onDelete: "set null", + }), + serverId: text("serverId").references(() => server.serverId, { + onDelete: "cascade", + }), }); export const applicationsRelations = relations( - applications, - ({ one, many }) => ({ - project: one(projects, { - fields: [applications.projectId], - references: [projects.projectId], - }), - deployments: many(deployments), - customGitSSHKey: one(sshKeys, { - fields: [applications.customGitSSHKeyId], - references: [sshKeys.sshKeyId], - }), - domains: many(domains), - mounts: many(mounts), - redirects: many(redirects), - security: many(security), - ports: many(ports), - registry: one(registry, { - fields: [applications.registryId], - references: [registry.registryId], - }), - github: one(github, { - fields: [applications.githubId], - references: [github.githubId], - }), - gitlab: one(gitlab, { - fields: [applications.gitlabId], - references: [gitlab.gitlabId], - }), - bitbucket: one(bitbucket, { - fields: [applications.bitbucketId], - references: [bitbucket.bitbucketId], - }), - server: one(server, { - fields: [applications.serverId], - references: [server.serverId], - }), - previewDeployments: many(previewDeployments), - }), + applications, + ({ one, many }) => ({ + project: one(projects, { + fields: [applications.projectId], + references: [projects.projectId], + }), + deployments: many(deployments), + customGitSSHKey: one(sshKeys, { + fields: [applications.customGitSSHKeyId], + references: [sshKeys.sshKeyId], + }), + domains: many(domains), + mounts: many(mounts), + redirects: many(redirects), + security: many(security), + ports: many(ports), + registry: one(registry, { + fields: [applications.registryId], + references: [registry.registryId], + }), + github: one(github, { + fields: [applications.githubId], + references: [github.githubId], + }), + gitlab: one(gitlab, { + fields: [applications.gitlabId], + references: [gitlab.gitlabId], + }), + bitbucket: one(bitbucket, { + fields: [applications.bitbucketId], + references: [bitbucket.bitbucketId], + }), + server: one(server, { + fields: [applications.serverId], + references: [server.serverId], + }), + previewDeployments: many(previewDeployments), + }) ); const HealthCheckSwarmSchema = z - .object({ - Test: z.array(z.string()).optional(), - Interval: z.number().optional(), - Timeout: z.number().optional(), - StartPeriod: z.number().optional(), - Retries: z.number().optional(), - }) - .strict(); + .object({ + Test: z.array(z.string()).optional(), + Interval: z.number().optional(), + Timeout: z.number().optional(), + StartPeriod: z.number().optional(), + Retries: z.number().optional(), + }) + .strict(); const RestartPolicySwarmSchema = z - .object({ - Condition: z.string().optional(), - Delay: z.number().optional(), - MaxAttempts: z.number().optional(), - Window: z.number().optional(), - }) - .strict(); + .object({ + Condition: z.string().optional(), + Delay: z.number().optional(), + MaxAttempts: z.number().optional(), + Window: z.number().optional(), + }) + .strict(); const PreferenceSchema = z - .object({ - Spread: z.object({ - SpreadDescriptor: z.string(), - }), - }) - .strict(); + .object({ + Spread: z.object({ + SpreadDescriptor: z.string(), + }), + }) + .strict(); const PlatformSchema = z - .object({ - Architecture: z.string(), - OS: z.string(), - }) - .strict(); + .object({ + Architecture: z.string(), + OS: z.string(), + }) + .strict(); const PlacementSwarmSchema = z - .object({ - Constraints: z.array(z.string()).optional(), - Preferences: z.array(PreferenceSchema).optional(), - MaxReplicas: z.number().optional(), - Platforms: z.array(PlatformSchema).optional(), - }) - .strict(); + .object({ + Constraints: z.array(z.string()).optional(), + Preferences: z.array(PreferenceSchema).optional(), + MaxReplicas: z.number().optional(), + Platforms: z.array(PlatformSchema).optional(), + }) + .strict(); const UpdateConfigSwarmSchema = z - .object({ - Parallelism: z.number(), - Delay: z.number().optional(), - FailureAction: z.string().optional(), - Monitor: z.number().optional(), - MaxFailureRatio: z.number().optional(), - Order: z.string(), - }) - .strict(); + .object({ + Parallelism: z.number(), + Delay: z.number().optional(), + FailureAction: z.string().optional(), + Monitor: z.number().optional(), + MaxFailureRatio: z.number().optional(), + Order: z.string(), + }) + .strict(); const ReplicatedSchema = z - .object({ - Replicas: z.number().optional(), - }) - .strict(); + .object({ + Replicas: z.number().optional(), + }) + .strict(); const ReplicatedJobSchema = z - .object({ - MaxConcurrent: z.number().optional(), - TotalCompletions: z.number().optional(), - }) - .strict(); + .object({ + MaxConcurrent: z.number().optional(), + TotalCompletions: z.number().optional(), + }) + .strict(); const ServiceModeSwarmSchema = z - .object({ - Replicated: ReplicatedSchema.optional(), - Global: z.object({}).optional(), - ReplicatedJob: ReplicatedJobSchema.optional(), - GlobalJob: z.object({}).optional(), - }) - .strict(); + .object({ + Replicated: ReplicatedSchema.optional(), + Global: z.object({}).optional(), + ReplicatedJob: ReplicatedJobSchema.optional(), + GlobalJob: z.object({}).optional(), + }) + .strict(); const NetworkSwarmSchema = z.array( - z - .object({ - Target: z.string().optional(), - Aliases: z.array(z.string()).optional(), - DriverOpts: z.object({}).optional(), - }) - .strict(), + z + .object({ + Target: z.string().optional(), + Aliases: z.array(z.string()).optional(), + DriverOpts: z.object({}).optional(), + }) + .strict() ); const LabelsSwarmSchema = z.record(z.string()); const createSchema = createInsertSchema(applications, { - appName: z.string(), - createdAt: z.string(), - applicationId: z.string(), - autoDeploy: z.boolean(), - env: z.string().optional(), - buildArgs: z.string().optional(), - name: z.string().min(1), - description: z.string().optional(), - memoryReservation: z.number().optional(), - memoryLimit: z.number().optional(), - cpuReservation: z.number().optional(), - cpuLimit: z.number().optional(), - title: z.string().optional(), - enabled: z.boolean().optional(), - subtitle: z.string().optional(), - dockerImage: z.string().optional(), - username: z.string().optional(), - isPreviewDeploymentsActive: z.boolean().optional(), - password: z.string().optional(), - registryUrl: z.string().optional(), - customGitSSHKeyId: z.string().optional(), - repository: z.string().optional(), - dockerfile: z.string().optional(), - branch: z.string().optional(), - customGitBranch: z.string().optional(), - customGitBuildPath: z.string().optional(), - customGitUrl: z.string().optional(), - buildPath: z.string().optional(), - projectId: z.string(), - sourceType: z.enum(["github", "docker", "git"]).optional(), - applicationStatus: z.enum(["idle", "running", "done", "error"]), - buildType: z.enum([ - "dockerfile", - "heroku_buildpacks", - "paketo_buildpacks", - "nixpacks", - "static", - ]), - herokuVersion: z.string().optional(), - publishDirectory: z.string().optional(), - owner: z.string(), - healthCheckSwarm: HealthCheckSwarmSchema.nullable(), - restartPolicySwarm: RestartPolicySwarmSchema.nullable(), - placementSwarm: PlacementSwarmSchema.nullable(), - updateConfigSwarm: UpdateConfigSwarmSchema.nullable(), - rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(), - modeSwarm: ServiceModeSwarmSchema.nullable(), - labelsSwarm: LabelsSwarmSchema.nullable(), - networkSwarm: NetworkSwarmSchema.nullable(), - previewPort: z.number().optional(), - previewEnv: z.string().optional(), - previewBuildArgs: z.string().optional(), - previewWildcard: z.string().optional(), - previewLimit: z.number().optional(), - previewHttps: z.boolean().optional(), - previewPath: z.string().optional(), - previewCertificateType: z.enum(["letsencrypt", "none"]).optional(), + appName: z.string(), + createdAt: z.string(), + applicationId: z.string(), + autoDeploy: z.boolean(), + env: z.string().optional(), + buildArgs: z.string().optional(), + name: z.string().min(1), + description: z.string().optional(), + memoryReservation: z.number().optional(), + memoryLimit: z.number().optional(), + cpuReservation: z.number().optional(), + cpuLimit: z.number().optional(), + title: z.string().optional(), + enabled: z.boolean().optional(), + subtitle: z.string().optional(), + dockerImage: z.string().optional(), + username: z.string().optional(), + isPreviewDeploymentsActive: z.boolean().optional(), + password: z.string().optional(), + registryUrl: z.string().optional(), + customGitSSHKeyId: z.string().optional(), + repository: z.string().optional(), + dockerfile: z.string().optional(), + branch: z.string().optional(), + customGitBranch: z.string().optional(), + customGitBuildPath: z.string().optional(), + customGitUrl: z.string().optional(), + buildPath: z.string().optional(), + projectId: z.string(), + sourceType: z.enum(["github", "docker", "git"]).optional(), + applicationStatus: z.enum(["idle", "running", "done", "error"]), + buildType: z.enum([ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + ]), + herokuVersion: z.string().optional(), + publishDirectory: z.string().optional(), + owner: z.string(), + healthCheckSwarm: HealthCheckSwarmSchema.nullable(), + restartPolicySwarm: RestartPolicySwarmSchema.nullable(), + placementSwarm: PlacementSwarmSchema.nullable(), + updateConfigSwarm: UpdateConfigSwarmSchema.nullable(), + rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(), + modeSwarm: ServiceModeSwarmSchema.nullable(), + labelsSwarm: LabelsSwarmSchema.nullable(), + networkSwarm: NetworkSwarmSchema.nullable(), + previewPort: z.number().optional(), + previewEnv: z.string().optional(), + previewBuildArgs: z.string().optional(), + previewWildcard: z.string().optional(), + previewLimit: z.number().optional(), + previewHttps: z.boolean().optional(), + previewPath: z.string().optional(), + previewCertificateType: z.enum(["letsencrypt", "none"]).optional(), }); export const apiCreateApplication = createSchema.pick({ - name: true, - appName: true, - description: true, - projectId: true, - serverId: true, + name: true, + appName: true, + description: true, + projectId: true, + serverId: true, }); export const apiFindOneApplication = createSchema - .pick({ - applicationId: true, - }) - .required(); + .pick({ + applicationId: true, + }) + .required(); export const apiReloadApplication = createSchema - .pick({ - appName: true, - applicationId: true, - }) - .required(); + .pick({ + appName: true, + applicationId: true, + }) + .required(); export const apiSaveBuildType = createSchema - .pick({ - applicationId: true, - buildType: true, - dockerfile: true, - dockerContextPath: true, - dockerBuildStage: true, - herokuVersion: true, - }) - .required() - .merge(createSchema.pick({ publishDirectory: true })); + .pick({ + applicationId: true, + buildType: true, + dockerfile: true, + dockerContextPath: true, + dockerBuildStage: true, + herokuVersion: true, + }) + .required() + .merge(createSchema.pick({ publishDirectory: true })); export const apiSaveGithubProvider = createSchema - .pick({ - applicationId: true, - repository: true, - branch: true, - owner: true, - buildPath: true, - githubId: true, - }) - .required(); + .pick({ + applicationId: true, + repository: true, + branch: true, + owner: true, + buildPath: true, + githubId: true, + }) + .required(); export const apiSaveGitlabProvider = createSchema - .pick({ - applicationId: true, - gitlabBranch: true, - gitlabBuildPath: true, - gitlabOwner: true, - gitlabRepository: true, - gitlabId: true, - gitlabProjectId: true, - gitlabPathNamespace: true, - }) - .required(); + .pick({ + applicationId: true, + gitlabBranch: true, + gitlabBuildPath: true, + gitlabOwner: true, + gitlabRepository: true, + gitlabId: true, + gitlabProjectId: true, + gitlabPathNamespace: true, + }) + .required(); export const apiSaveBitbucketProvider = createSchema - .pick({ - bitbucketBranch: true, - bitbucketBuildPath: true, - bitbucketOwner: true, - bitbucketRepository: true, - bitbucketId: true, - applicationId: true, - }) - .required(); + .pick({ + bitbucketBranch: true, + bitbucketBuildPath: true, + bitbucketOwner: true, + bitbucketRepository: true, + bitbucketId: true, + applicationId: true, + }) + .required(); export const apiSaveDockerProvider = createSchema - .pick({ - dockerImage: true, - applicationId: true, - username: true, - password: true, - registryUrl: true, - }) - .required(); + .pick({ + dockerImage: true, + applicationId: true, + username: true, + password: true, + registryUrl: true, + }) + .required(); export const apiSaveGitProvider = createSchema - .pick({ - customGitBranch: true, - applicationId: true, - customGitBuildPath: true, - customGitUrl: true, - }) - .required() - .merge( - createSchema.pick({ - customGitSSHKeyId: true, - }), - ); + .pick({ + customGitBranch: true, + applicationId: true, + customGitBuildPath: true, + customGitUrl: true, + }) + .required() + .merge( + createSchema.pick({ + customGitSSHKeyId: true, + }) + ); export const apiSaveEnvironmentVariables = createSchema - .pick({ - applicationId: true, - env: true, - buildArgs: true, - }) - .required(); + .pick({ + applicationId: true, + env: true, + buildArgs: true, + }) + .required(); export const apiFindMonitoringStats = createSchema - .pick({ - appName: true, - }) - .required(); + .pick({ + appName: true, + }) + .required(); export const apiUpdateApplication = createSchema - .partial() - .extend({ - applicationId: z.string().min(1), - }) - .omit({ serverId: true }); - -export const apiDeleteApplication = z.object({ - applicationId: z.string().min(1), - deleteVolumes: z.boolean(), -}); + .partial() + .extend({ + applicationId: z.string().min(1), + }) + .omit({ serverId: true }); From fa710d48556826ccf8d6808a5e1af7b5416d10ac Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Mon, 23 Dec 2024 08:39:11 +0100 Subject: [PATCH 09/13] format: fix formatting --- .../application/delete-application.tsx | 266 ++-- .../dokploy/server/api/routers/application.ts | 1134 ++++++++--------- 2 files changed, 700 insertions(+), 700 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/delete-application.tsx b/apps/dokploy/components/dashboard/application/delete-application.tsx index 7202b6f13..f34d29a78 100644 --- a/apps/dokploy/components/dashboard/application/delete-application.tsx +++ b/apps/dokploy/components/dashboard/application/delete-application.tsx @@ -1,21 +1,21 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; @@ -28,134 +28,134 @@ import { toast } from "sonner"; import { z } from "zod"; const deleteApplicationSchema = z.object({ - projectName: z.string().min(1, { - message: "Application name is required", - }), + projectName: z.string().min(1, { + message: "Application name is required", + }), }); type DeleteApplication = z.infer; interface Props { - applicationId: string; + applicationId: string; } export const DeleteApplication = ({ applicationId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading } = api.application.delete.useMutation(); - const { data } = api.application.one.useQuery( - { applicationId }, - { enabled: !!applicationId } - ); - const { push } = useRouter(); - const form = useForm({ - defaultValues: { - projectName: "", - }, - resolver: zodResolver(deleteApplicationSchema), - }); + const [isOpen, setIsOpen] = useState(false); + const { mutateAsync, isLoading } = api.application.delete.useMutation(); + const { data } = api.application.one.useQuery( + { applicationId }, + { enabled: !!applicationId }, + ); + const { push } = useRouter(); + const form = useForm({ + defaultValues: { + projectName: "", + }, + resolver: zodResolver(deleteApplicationSchema), + }); - const onSubmit = async (formData: DeleteApplication) => { - const expectedName = `${data?.name}/${data?.appName}`; - if (formData.projectName === expectedName) { - await mutateAsync({ - applicationId, - }) - .then((data) => { - push(`/dashboard/project/${data?.projectId}`); - toast.success("Application deleted successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error deleting the application"); - }); - } else { - form.setError("projectName", { - message: "Project name does not match", - }); - } - }; + const onSubmit = async (formData: DeleteApplication) => { + const expectedName = `${data?.name}/${data?.appName}`; + if (formData.projectName === expectedName) { + await mutateAsync({ + applicationId, + }) + .then((data) => { + push(`/dashboard/project/${data?.projectId}`); + toast.success("Application deleted successfully"); + setIsOpen(false); + }) + .catch(() => { + toast.error("Error deleting the application"); + }); + } else { + form.setError("projectName", { + message: "Project name does not match", + }); + } + }; - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - application. If you are sure please enter the application name to - delete this application. - - -
-
- - ( - - - - To confirm, type{" "} - { - if (data?.name && data?.appName) { - navigator.clipboard.writeText( - `${data.name}/${data.appName}` - ); - toast.success("Copied to clipboard. Be careful!"); - } - }} - > - {data?.name}/{data?.appName}  - - {" "} - in the box below: - - - - - - - - )} - /> - - -
- - - - -
-
- ); + return ( + + + + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete the + application. If you are sure please enter the application name to + delete this application. + + +
+
+ + ( + + + + To confirm, type{" "} + { + if (data?.name && data?.appName) { + navigator.clipboard.writeText( + `${data.name}/${data.appName}`, + ); + toast.success("Copied to clipboard. Be careful!"); + } + }} + > + {data?.name}/{data?.appName}  + + {" "} + in the box below: + + + + + + + + )} + /> + + +
+ + + + +
+
+ ); }; diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index e2fe4cb4e..2902c8eda 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -1,54 +1,54 @@ import { - createTRPCRouter, - protectedProcedure, - uploadProcedure, + createTRPCRouter, + protectedProcedure, + uploadProcedure, } from "@/server/api/trpc"; import { db } from "@/server/db"; import { - apiCreateApplication, - apiFindMonitoringStats, - apiFindOneApplication, - apiReloadApplication, - apiSaveBitbucketProvider, - apiSaveBuildType, - apiSaveDockerProvider, - apiSaveEnvironmentVariables, - apiSaveGitProvider, - apiSaveGithubProvider, - apiSaveGitlabProvider, - apiUpdateApplication, - applications, + apiCreateApplication, + apiFindMonitoringStats, + apiFindOneApplication, + apiReloadApplication, + apiSaveBitbucketProvider, + apiSaveBuildType, + apiSaveDockerProvider, + apiSaveEnvironmentVariables, + apiSaveGitProvider, + apiSaveGithubProvider, + apiSaveGitlabProvider, + apiUpdateApplication, + applications, } from "@/server/db/schema"; import type { DeploymentJob } from "@/server/queues/queue-types"; import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup"; import { deploy } from "@/server/utils/deploy"; import { uploadFileSchema } from "@/utils/schema"; import { - IS_CLOUD, - addNewService, - checkServiceAccess, - createApplication, - deleteAllMiddlewares, - findApplicationById, - findProjectById, - getApplicationStats, - readConfig, - readRemoteConfig, - removeDeployments, - removeDirectoryCode, - removeMonitoringDirectory, - removeService, - removeTraefikConfig, - startService, - startServiceRemote, - stopService, - stopServiceRemote, - unzipDrop, - updateApplication, - updateApplicationStatus, - writeConfig, - writeConfigRemote, - // uploadFileSchema + IS_CLOUD, + addNewService, + checkServiceAccess, + createApplication, + deleteAllMiddlewares, + findApplicationById, + findProjectById, + getApplicationStats, + readConfig, + readRemoteConfig, + removeDeployments, + removeDirectoryCode, + removeMonitoringDirectory, + removeService, + removeTraefikConfig, + startService, + startServiceRemote, + stopService, + stopServiceRemote, + unzipDrop, + updateApplication, + updateApplicationStatus, + writeConfig, + writeConfigRemote, + // uploadFileSchema } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -56,569 +56,569 @@ import { nanoid } from "nanoid"; import { z } from "zod"; export const applicationRouter = createTRPCRouter({ - create: protectedProcedure - .input(apiCreateApplication) - .mutation(async ({ input, ctx }) => { - try { - if (ctx.user.rol === "user") { - await checkServiceAccess(ctx.user.authId, input.projectId, "create"); - } + create: protectedProcedure + .input(apiCreateApplication) + .mutation(async ({ input, ctx }) => { + try { + if (ctx.user.rol === "user") { + await checkServiceAccess(ctx.user.authId, input.projectId, "create"); + } - if (IS_CLOUD && !input.serverId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You need to use a server to create an application", - }); - } + if (IS_CLOUD && !input.serverId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You need to use a server to create an application", + }); + } - const project = await findProjectById(input.projectId); - if (project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this project", - }); - } - const newApplication = await createApplication(input); + const project = await findProjectById(input.projectId); + if (project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this project", + }); + } + const newApplication = await createApplication(input); - if (ctx.user.rol === "user") { - await addNewService(ctx.user.authId, newApplication.applicationId); - } - return newApplication; - } catch (error: unknown) { - if (error instanceof TRPCError) { - throw error; - } - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the application", - cause: error, - }); - } - }), - one: protectedProcedure - .input(apiFindOneApplication) - .query(async ({ input, ctx }) => { - if (ctx.user.rol === "user") { - await checkServiceAccess( - ctx.user.authId, - input.applicationId, - "access" - ); - } - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this application", - }); - } - return application; - }), + if (ctx.user.rol === "user") { + await addNewService(ctx.user.authId, newApplication.applicationId); + } + return newApplication; + } catch (error: unknown) { + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the application", + cause: error, + }); + } + }), + one: protectedProcedure + .input(apiFindOneApplication) + .query(async ({ input, ctx }) => { + if (ctx.user.rol === "user") { + await checkServiceAccess( + ctx.user.authId, + input.applicationId, + "access", + ); + } + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this application", + }); + } + return application; + }), - reload: protectedProcedure - .input(apiReloadApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to reload this application", - }); - } - if (application.serverId) { - await stopServiceRemote(application.serverId, input.appName); - } else { - await stopService(input.appName); - } - await updateApplicationStatus(input.applicationId, "idle"); + reload: protectedProcedure + .input(apiReloadApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this application", + }); + } + if (application.serverId) { + await stopServiceRemote(application.serverId, input.appName); + } else { + await stopService(input.appName); + } + await updateApplicationStatus(input.applicationId, "idle"); - if (application.serverId) { - await startServiceRemote(application.serverId, input.appName); - } else { - await startService(input.appName); - } - await updateApplicationStatus(input.applicationId, "done"); - return true; - }), + if (application.serverId) { + await startServiceRemote(application.serverId, input.appName); + } else { + await startService(input.appName); + } + await updateApplicationStatus(input.applicationId, "done"); + return true; + }), - delete: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "user") { - await checkServiceAccess( - ctx.user.authId, - input.applicationId, - "delete" - ); - } - const application = await findApplicationById(input.applicationId); + delete: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + if (ctx.user.rol === "user") { + await checkServiceAccess( + ctx.user.authId, + input.applicationId, + "delete", + ); + } + const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to delete this application", - }); - } + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this application", + }); + } - const result = await db - .delete(applications) - .where(eq(applications.applicationId, input.applicationId)) - .returning(); + const result = await db + .delete(applications) + .where(eq(applications.applicationId, input.applicationId)) + .returning(); - const cleanupOperations = [ - async () => await deleteAllMiddlewares(application), - async () => await removeDeployments(application), - async () => - await removeDirectoryCode(application.appName, application.serverId), - async () => - await removeMonitoringDirectory( - application.appName, - application.serverId - ), - async () => - await removeTraefikConfig(application.appName, application.serverId), - async () => - await removeService(application?.appName, application.serverId), - ]; + const cleanupOperations = [ + async () => await deleteAllMiddlewares(application), + async () => await removeDeployments(application), + async () => + await removeDirectoryCode(application.appName, application.serverId), + async () => + await removeMonitoringDirectory( + application.appName, + application.serverId, + ), + async () => + await removeTraefikConfig(application.appName, application.serverId), + async () => + await removeService(application?.appName, application.serverId), + ]; - for (const operation of cleanupOperations) { - try { - await operation(); - } catch (error) {} - } + for (const operation of cleanupOperations) { + try { + await operation(); + } catch (error) {} + } - return result[0]; - }), + return result[0]; + }), - stop: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const service = await findApplicationById(input.applicationId); - if (service.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to stop this application", - }); - } - if (service.serverId) { - await stopServiceRemote(service.serverId, service.appName); - } else { - await stopService(service.appName); - } - await updateApplicationStatus(input.applicationId, "idle"); + stop: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const service = await findApplicationById(input.applicationId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this application", + }); + } + if (service.serverId) { + await stopServiceRemote(service.serverId, service.appName); + } else { + await stopService(service.appName); + } + await updateApplicationStatus(input.applicationId, "idle"); - return service; - }), + return service; + }), - start: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const service = await findApplicationById(input.applicationId); - if (service.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to start this application", - }); - } + start: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const service = await findApplicationById(input.applicationId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this application", + }); + } - if (service.serverId) { - await startServiceRemote(service.serverId, service.appName); - } else { - await startService(service.appName); - } - await updateApplicationStatus(input.applicationId, "done"); + if (service.serverId) { + await startServiceRemote(service.serverId, service.appName); + } else { + await startService(service.appName); + } + await updateApplicationStatus(input.applicationId, "done"); - return service; - }), + return service; + }), - redeploy: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to redeploy this application", - }); - } - const jobData: DeploymentJob = { - applicationId: input.applicationId, - titleLog: "Rebuild deployment", - descriptionLog: "", - type: "redeploy", - applicationType: "application", - server: !!application.serverId, - }; + redeploy: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to redeploy this application", + }); + } + const jobData: DeploymentJob = { + applicationId: input.applicationId, + titleLog: "Rebuild deployment", + descriptionLog: "", + type: "redeploy", + applicationType: "application", + server: !!application.serverId, + }; - if (IS_CLOUD && application.serverId) { - jobData.serverId = application.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - }), - saveEnvironment: protectedProcedure - .input(apiSaveEnvironmentVariables) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this environment", - }); - } - await updateApplication(input.applicationId, { - env: input.env, - buildArgs: input.buildArgs, - }); - return true; - }), - saveBuildType: protectedProcedure - .input(apiSaveBuildType) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this build type", - }); - } - await updateApplication(input.applicationId, { - buildType: input.buildType, - dockerfile: input.dockerfile, - publishDirectory: input.publishDirectory, - dockerContextPath: input.dockerContextPath, - dockerBuildStage: input.dockerBuildStage, - herokuVersion: input.herokuVersion, - }); + if (IS_CLOUD && application.serverId) { + jobData.serverId = application.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + }), + saveEnvironment: protectedProcedure + .input(apiSaveEnvironmentVariables) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } + await updateApplication(input.applicationId, { + env: input.env, + buildArgs: input.buildArgs, + }); + return true; + }), + saveBuildType: protectedProcedure + .input(apiSaveBuildType) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this build type", + }); + } + await updateApplication(input.applicationId, { + buildType: input.buildType, + dockerfile: input.dockerfile, + publishDirectory: input.publishDirectory, + dockerContextPath: input.dockerContextPath, + dockerBuildStage: input.dockerBuildStage, + herokuVersion: input.herokuVersion, + }); - return true; - }), - saveGithubProvider: protectedProcedure - .input(apiSaveGithubProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this github provider", - }); - } - await updateApplication(input.applicationId, { - repository: input.repository, - branch: input.branch, - sourceType: "github", - owner: input.owner, - buildPath: input.buildPath, - applicationStatus: "idle", - githubId: input.githubId, - }); + return true; + }), + saveGithubProvider: protectedProcedure + .input(apiSaveGithubProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this github provider", + }); + } + await updateApplication(input.applicationId, { + repository: input.repository, + branch: input.branch, + sourceType: "github", + owner: input.owner, + buildPath: input.buildPath, + applicationStatus: "idle", + githubId: input.githubId, + }); - return true; - }), - saveGitlabProvider: protectedProcedure - .input(apiSaveGitlabProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this gitlab provider", - }); - } - await updateApplication(input.applicationId, { - gitlabRepository: input.gitlabRepository, - gitlabOwner: input.gitlabOwner, - gitlabBranch: input.gitlabBranch, - gitlabBuildPath: input.gitlabBuildPath, - sourceType: "gitlab", - applicationStatus: "idle", - gitlabId: input.gitlabId, - gitlabProjectId: input.gitlabProjectId, - gitlabPathNamespace: input.gitlabPathNamespace, - }); + return true; + }), + saveGitlabProvider: protectedProcedure + .input(apiSaveGitlabProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this gitlab provider", + }); + } + await updateApplication(input.applicationId, { + gitlabRepository: input.gitlabRepository, + gitlabOwner: input.gitlabOwner, + gitlabBranch: input.gitlabBranch, + gitlabBuildPath: input.gitlabBuildPath, + sourceType: "gitlab", + applicationStatus: "idle", + gitlabId: input.gitlabId, + gitlabProjectId: input.gitlabProjectId, + gitlabPathNamespace: input.gitlabPathNamespace, + }); - return true; - }), - saveBitbucketProvider: protectedProcedure - .input(apiSaveBitbucketProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this bitbucket provider", - }); - } - await updateApplication(input.applicationId, { - bitbucketRepository: input.bitbucketRepository, - bitbucketOwner: input.bitbucketOwner, - bitbucketBranch: input.bitbucketBranch, - bitbucketBuildPath: input.bitbucketBuildPath, - sourceType: "bitbucket", - applicationStatus: "idle", - bitbucketId: input.bitbucketId, - }); + return true; + }), + saveBitbucketProvider: protectedProcedure + .input(apiSaveBitbucketProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this bitbucket provider", + }); + } + await updateApplication(input.applicationId, { + bitbucketRepository: input.bitbucketRepository, + bitbucketOwner: input.bitbucketOwner, + bitbucketBranch: input.bitbucketBranch, + bitbucketBuildPath: input.bitbucketBuildPath, + sourceType: "bitbucket", + applicationStatus: "idle", + bitbucketId: input.bitbucketId, + }); - return true; - }), - saveDockerProvider: protectedProcedure - .input(apiSaveDockerProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this docker provider", - }); - } - await updateApplication(input.applicationId, { - dockerImage: input.dockerImage, - username: input.username, - password: input.password, - sourceType: "docker", - applicationStatus: "idle", - registryUrl: input.registryUrl, - }); + return true; + }), + saveDockerProvider: protectedProcedure + .input(apiSaveDockerProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this docker provider", + }); + } + await updateApplication(input.applicationId, { + dockerImage: input.dockerImage, + username: input.username, + password: input.password, + sourceType: "docker", + applicationStatus: "idle", + registryUrl: input.registryUrl, + }); - return true; - }), - saveGitProdiver: protectedProcedure - .input(apiSaveGitProvider) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to save this git provider", - }); - } - await updateApplication(input.applicationId, { - customGitBranch: input.customGitBranch, - customGitBuildPath: input.customGitBuildPath, - customGitUrl: input.customGitUrl, - customGitSSHKeyId: input.customGitSSHKeyId, - sourceType: "git", - applicationStatus: "idle", - }); + return true; + }), + saveGitProdiver: protectedProcedure + .input(apiSaveGitProvider) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this git provider", + }); + } + await updateApplication(input.applicationId, { + customGitBranch: input.customGitBranch, + customGitBuildPath: input.customGitBuildPath, + customGitUrl: input.customGitUrl, + customGitSSHKeyId: input.customGitSSHKeyId, + sourceType: "git", + applicationStatus: "idle", + }); - return true; - }), - markRunning: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to mark this application as running", - }); - } - await updateApplicationStatus(input.applicationId, "running"); - }), - update: protectedProcedure - .input(apiUpdateApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to update this application", - }); - } - const { applicationId, ...rest } = input; - const updateApp = await updateApplication(applicationId, { - ...rest, - }); + return true; + }), + markRunning: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to mark this application as running", + }); + } + await updateApplicationStatus(input.applicationId, "running"); + }), + update: protectedProcedure + .input(apiUpdateApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this application", + }); + } + const { applicationId, ...rest } = input; + const updateApp = await updateApplication(applicationId, { + ...rest, + }); - if (!updateApp) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Update: Error to update application", - }); - } + if (!updateApp) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Update: Error to update application", + }); + } - return true; - }), - refreshToken: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to refresh this application", - }); - } - await updateApplication(input.applicationId, { - refreshToken: nanoid(), - }); - return true; - }), - deploy: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to deploy this application", - }); - } - const jobData: DeploymentJob = { - applicationId: input.applicationId, - titleLog: "Manual deployment", - descriptionLog: "", - type: "deploy", - applicationType: "application", - server: !!application.serverId, - }; - if (IS_CLOUD && application.serverId) { - jobData.serverId = application.serverId; - await deploy(jobData); + return true; + }), + refreshToken: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to refresh this application", + }); + } + await updateApplication(input.applicationId, { + refreshToken: nanoid(), + }); + return true; + }), + deploy: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this application", + }); + } + const jobData: DeploymentJob = { + applicationId: input.applicationId, + titleLog: "Manual deployment", + descriptionLog: "", + type: "deploy", + applicationType: "application", + server: !!application.serverId, + }; + if (IS_CLOUD && application.serverId) { + jobData.serverId = application.serverId; + await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - }), + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + }), - cleanQueues: protectedProcedure - .input(apiFindOneApplication) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to clean this application", - }); - } - await cleanQueuesByApplication(input.applicationId); - }), + cleanQueues: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to clean this application", + }); + } + await cleanQueuesByApplication(input.applicationId); + }), - readTraefikConfig: protectedProcedure - .input(apiFindOneApplication) - .query(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to read this application", - }); - } + readTraefikConfig: protectedProcedure + .input(apiFindOneApplication) + .query(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to read this application", + }); + } - let traefikConfig = null; - if (application.serverId) { - traefikConfig = await readRemoteConfig( - application.serverId, - application.appName - ); - } else { - traefikConfig = readConfig(application.appName); - } - return traefikConfig; - }), + let traefikConfig = null; + if (application.serverId) { + traefikConfig = await readRemoteConfig( + application.serverId, + application.appName, + ); + } else { + traefikConfig = readConfig(application.appName); + } + return traefikConfig; + }), - dropDeployment: protectedProcedure - .meta({ - openapi: { - path: "/drop-deployment", - method: "POST", - override: true, - enabled: false, - }, - }) - .use(uploadProcedure) - .input(uploadFileSchema) - .mutation(async ({ input, ctx }) => { - const zipFile = input.zip; + dropDeployment: protectedProcedure + .meta({ + openapi: { + path: "/drop-deployment", + method: "POST", + override: true, + enabled: false, + }, + }) + .use(uploadProcedure) + .input(uploadFileSchema) + .mutation(async ({ input, ctx }) => { + const zipFile = input.zip; - const app = await findApplicationById(input.applicationId as string); + const app = await findApplicationById(input.applicationId as string); - if (app.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to deploy this application", - }); - } + if (app.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this application", + }); + } - updateApplication(input.applicationId as string, { - sourceType: "drop", - dropBuildPath: input.dropBuildPath, - }); + updateApplication(input.applicationId as string, { + sourceType: "drop", + dropBuildPath: input.dropBuildPath, + }); - await unzipDrop(zipFile, app); - const jobData: DeploymentJob = { - applicationId: app.applicationId, - titleLog: "Manual deployment", - descriptionLog: "", - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } + await unzipDrop(zipFile, app); + const jobData: DeploymentJob = { + applicationId: app.applicationId, + titleLog: "Manual deployment", + descriptionLog: "", + 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, - } - ); - return true; - }), - updateTraefikConfig: protectedProcedure - .input(z.object({ applicationId: z.string(), traefikConfig: z.string() })) - .mutation(async ({ input, ctx }) => { - const application = await findApplicationById(input.applicationId); + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + return true; + }), + updateTraefikConfig: protectedProcedure + .input(z.object({ applicationId: z.string(), traefikConfig: z.string() })) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); - if (application.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to update this application", - }); - } + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this application", + }); + } - if (application.serverId) { - await writeConfigRemote( - application.serverId, - application.appName, - input.traefikConfig - ); - } else { - writeConfig(application.appName, input.traefikConfig); - } - return true; - }), - readAppMonitoring: protectedProcedure - .input(apiFindMonitoringStats) - .query(async ({ input, ctx }) => { - if (IS_CLOUD) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Functionality not available in cloud version", - }); - } - const stats = await getApplicationStats(input.appName); + if (application.serverId) { + await writeConfigRemote( + application.serverId, + application.appName, + input.traefikConfig, + ); + } else { + writeConfig(application.appName, input.traefikConfig); + } + return true; + }), + readAppMonitoring: protectedProcedure + .input(apiFindMonitoringStats) + .query(async ({ input, ctx }) => { + if (IS_CLOUD) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Functionality not available in cloud version", + }); + } + const stats = await getApplicationStats(input.appName); - return stats; - }), + return stats; + }), }); From c6892ba1882d9484c26ad601dd9493a3f1025e27 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Mon, 23 Dec 2024 08:39:50 +0100 Subject: [PATCH 10/13] format: fix formatting --- packages/server/src/services/compose.ts | 856 ++++++++++++------------ 1 file changed, 428 insertions(+), 428 deletions(-) diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 9012cee6c..d38426fd5 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -5,42 +5,42 @@ import { type apiCreateCompose, compose } from "@dokploy/server/db/schema"; import { buildAppName, cleanAppName } from "@dokploy/server/db/schema"; import { generatePassword } from "@dokploy/server/templates/utils"; import { - buildCompose, - getBuildComposeCommand, + buildCompose, + getBuildComposeCommand, } from "@dokploy/server/utils/builders/compose"; import { randomizeSpecificationFile } from "@dokploy/server/utils/docker/compose"; import { - cloneCompose, - cloneComposeRemote, - loadDockerCompose, - loadDockerComposeRemote, + cloneCompose, + cloneComposeRemote, + loadDockerCompose, + loadDockerComposeRemote, } from "@dokploy/server/utils/docker/domain"; import type { ComposeSpecification } from "@dokploy/server/utils/docker/types"; import { sendBuildErrorNotifications } from "@dokploy/server/utils/notifications/build-error"; import { sendBuildSuccessNotifications } from "@dokploy/server/utils/notifications/build-success"; import { - execAsync, - execAsyncRemote, + execAsync, + execAsyncRemote, } from "@dokploy/server/utils/process/execAsync"; import { - cloneBitbucketRepository, - getBitbucketCloneCommand, + cloneBitbucketRepository, + getBitbucketCloneCommand, } from "@dokploy/server/utils/providers/bitbucket"; import { - cloneGitRepository, - getCustomGitCloneCommand, + cloneGitRepository, + getCustomGitCloneCommand, } from "@dokploy/server/utils/providers/git"; import { - cloneGithubRepository, - getGithubCloneCommand, + cloneGithubRepository, + getGithubCloneCommand, } from "@dokploy/server/utils/providers/github"; import { - cloneGitlabRepository, - getGitlabCloneCommand, + cloneGitlabRepository, + getGitlabCloneCommand, } from "@dokploy/server/utils/providers/gitlab"; import { - createComposeFile, - getCreateComposeFileCommand, + createComposeFile, + getCreateComposeFileCommand, } from "@dokploy/server/utils/providers/raw"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -52,501 +52,501 @@ import { validUniqueServerAppName } from "./project"; export type Compose = typeof compose.$inferSelect; export const createCompose = async (input: typeof apiCreateCompose._type) => { - const appName = buildAppName("compose", input.appName); + const appName = buildAppName("compose", input.appName); - const valid = await validUniqueServerAppName(appName); - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } + const valid = await validUniqueServerAppName(appName); + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } - const newDestination = await db - .insert(compose) - .values({ - ...input, - composeFile: "", - appName, - }) - .returning() - .then((value) => value[0]); + const newDestination = await db + .insert(compose) + .values({ + ...input, + composeFile: "", + appName, + }) + .returning() + .then((value) => value[0]); - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting compose", - }); - } + if (!newDestination) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error input: Inserting compose", + }); + } - return newDestination; + return newDestination; }; export const createComposeByTemplate = async ( - input: typeof compose.$inferInsert + input: typeof compose.$inferInsert, ) => { - const appName = cleanAppName(input.appName); - if (appName) { - const valid = await validUniqueServerAppName(appName); + const appName = cleanAppName(input.appName); + if (appName) { + const valid = await validUniqueServerAppName(appName); - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - const newDestination = await db - .insert(compose) - .values({ - ...input, - appName, - }) - .returning() - .then((value) => value[0]); + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } + const newDestination = await db + .insert(compose) + .values({ + ...input, + appName, + }) + .returning() + .then((value) => value[0]); - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting compose", - }); - } + if (!newDestination) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error input: Inserting compose", + }); + } - return newDestination; + return newDestination; }; export const findComposeById = async (composeId: string) => { - const result = await db.query.compose.findFirst({ - where: eq(compose.composeId, composeId), - with: { - project: true, - deployments: true, - mounts: true, - domains: true, - github: true, - gitlab: true, - bitbucket: true, - server: true, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Compose not found", - }); - } - return result; + const result = await db.query.compose.findFirst({ + where: eq(compose.composeId, composeId), + with: { + project: true, + deployments: true, + mounts: true, + domains: true, + github: true, + gitlab: true, + bitbucket: true, + server: true, + }, + }); + if (!result) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Compose not found", + }); + } + return result; }; export const loadServices = async ( - composeId: string, - type: "fetch" | "cache" = "fetch" + composeId: string, + type: "fetch" | "cache" = "fetch", ) => { - const compose = await findComposeById(composeId); + const compose = await findComposeById(composeId); - if (type === "fetch") { - if (compose.serverId) { - await cloneComposeRemote(compose); - } else { - await cloneCompose(compose); - } - } + if (type === "fetch") { + if (compose.serverId) { + await cloneComposeRemote(compose); + } else { + await cloneCompose(compose); + } + } - let composeData: ComposeSpecification | null; + let composeData: ComposeSpecification | null; - if (compose.serverId) { - composeData = await loadDockerComposeRemote(compose); - } else { - composeData = await loadDockerCompose(compose); - } + if (compose.serverId) { + composeData = await loadDockerComposeRemote(compose); + } else { + composeData = await loadDockerCompose(compose); + } - if (compose.randomize && composeData) { - const randomizedCompose = randomizeSpecificationFile( - composeData, - compose.suffix - ); - composeData = randomizedCompose; - } + if (compose.randomize && composeData) { + const randomizedCompose = randomizeSpecificationFile( + composeData, + compose.suffix, + ); + composeData = randomizedCompose; + } - if (!composeData?.services) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Services not found", - }); - } + if (!composeData?.services) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Services not found", + }); + } - const services = Object.keys(composeData.services); + const services = Object.keys(composeData.services); - return [...services]; + return [...services]; }; export const updateCompose = async ( - composeId: string, - composeData: Partial + composeId: string, + composeData: Partial, ) => { - const { appName, ...rest } = composeData; - const composeResult = await db - .update(compose) - .set({ - ...rest, - }) - .where(eq(compose.composeId, composeId)) - .returning(); + const { appName, ...rest } = composeData; + const composeResult = await db + .update(compose) + .set({ + ...rest, + }) + .where(eq(compose.composeId, composeId)) + .returning(); - return composeResult[0]; + return composeResult[0]; }; export const deployCompose = async ({ - composeId, - titleLog = "Manual deployment", - descriptionLog = "", + composeId, + titleLog = "Manual deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const buildLink = `${await getDokployUrl()}/dashboard/project/${ - compose.projectId - }/services/compose/${compose.composeId}?tab=deployments`; - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); + const compose = await findComposeById(composeId); + const buildLink = `${await getDokployUrl()}/dashboard/project/${ + compose.projectId + }/services/compose/${compose.composeId}?tab=deployments`; + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); - try { - if (compose.sourceType === "github") { - await cloneGithubRepository({ - ...compose, - logPath: deployment.logPath, - type: "compose", - }); - } else if (compose.sourceType === "gitlab") { - await cloneGitlabRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "bitbucket") { - await cloneBitbucketRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "git") { - await cloneGitRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "raw") { - await createComposeFile(compose, deployment.logPath); - } - await buildCompose(compose, deployment.logPath); - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); + try { + if (compose.sourceType === "github") { + await cloneGithubRepository({ + ...compose, + logPath: deployment.logPath, + type: "compose", + }); + } else if (compose.sourceType === "gitlab") { + await cloneGitlabRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "bitbucket") { + await cloneBitbucketRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "git") { + await cloneGitRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "raw") { + await createComposeFile(compose, deployment.logPath); + } + await buildCompose(compose, deployment.logPath); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); - await sendBuildSuccessNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - buildLink, - adminId: compose.project.adminId, - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - await sendBuildErrorNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - adminId: compose.project.adminId, - }); - throw error; - } + await sendBuildSuccessNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + buildLink, + adminId: compose.project.adminId, + }); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + await sendBuildErrorNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + // @ts-ignore + errorMessage: error?.message || "Error to build", + buildLink, + adminId: compose.project.adminId, + }); + throw error; + } }; export const rebuildCompose = async ({ - composeId, - titleLog = "Rebuild deployment", - descriptionLog = "", + composeId, + titleLog = "Rebuild deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); + const compose = await findComposeById(composeId); + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); - try { - if (compose.serverId) { - await getBuildComposeCommand(compose, deployment.logPath); - } else { - await buildCompose(compose, deployment.logPath); - } + try { + if (compose.serverId) { + await getBuildComposeCommand(compose, deployment.logPath); + } else { + await buildCompose(compose, deployment.logPath); + } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + throw error; + } - return true; + return true; }; export const deployRemoteCompose = async ({ - composeId, - titleLog = "Manual deployment", - descriptionLog = "", + composeId, + titleLog = "Manual deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const buildLink = `${await getDokployUrl()}/dashboard/project/${ - compose.projectId - }/services/compose/${compose.composeId}?tab=deployments`; - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); - try { - if (compose.serverId) { - let command = "set -e;"; + const compose = await findComposeById(composeId); + const buildLink = `${await getDokployUrl()}/dashboard/project/${ + compose.projectId + }/services/compose/${compose.composeId}?tab=deployments`; + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); + try { + if (compose.serverId) { + let command = "set -e;"; - if (compose.sourceType === "github") { - command += await getGithubCloneCommand({ - ...compose, - logPath: deployment.logPath, - type: "compose", - serverId: compose.serverId, - }); - } else if (compose.sourceType === "gitlab") { - command += await getGitlabCloneCommand( - compose, - deployment.logPath, - true - ); - } else if (compose.sourceType === "bitbucket") { - command += await getBitbucketCloneCommand( - compose, - deployment.logPath, - true - ); - } else if (compose.sourceType === "git") { - command += await getCustomGitCloneCommand( - compose, - deployment.logPath, - true - ); - } else if (compose.sourceType === "raw") { - command += getCreateComposeFileCommand(compose, deployment.logPath); - } + if (compose.sourceType === "github") { + command += await getGithubCloneCommand({ + ...compose, + logPath: deployment.logPath, + type: "compose", + serverId: compose.serverId, + }); + } else if (compose.sourceType === "gitlab") { + command += await getGitlabCloneCommand( + compose, + deployment.logPath, + true, + ); + } else if (compose.sourceType === "bitbucket") { + command += await getBitbucketCloneCommand( + compose, + deployment.logPath, + true, + ); + } else if (compose.sourceType === "git") { + command += await getCustomGitCloneCommand( + compose, + deployment.logPath, + true, + ); + } else if (compose.sourceType === "raw") { + command += getCreateComposeFileCommand(compose, deployment.logPath); + } - await execAsyncRemote(compose.serverId, command); - await getBuildComposeCommand(compose, deployment.logPath); - } + await execAsyncRemote(compose.serverId, command); + await getBuildComposeCommand(compose, deployment.logPath); + } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); - await sendBuildSuccessNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - buildLink, - adminId: compose.project.adminId, - }); - } catch (error) { - // @ts-ignore - const encodedContent = encodeBase64(error?.message); + await sendBuildSuccessNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + buildLink, + adminId: compose.project.adminId, + }); + } catch (error) { + // @ts-ignore + const encodedContent = encodeBase64(error?.message); - await execAsyncRemote( - compose.serverId, - ` + await execAsyncRemote( + compose.serverId, + ` echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath}; echo "Error occurred ❌, check the logs for details." >> ${deployment.logPath}; - echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";` - ); - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - await sendBuildErrorNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - adminId: compose.project.adminId, - }); - throw error; - } + echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`, + ); + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + await sendBuildErrorNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + // @ts-ignore + errorMessage: error?.message || "Error to build", + buildLink, + adminId: compose.project.adminId, + }); + throw error; + } }; export const rebuildRemoteCompose = async ({ - composeId, - titleLog = "Rebuild deployment", - descriptionLog = "", + composeId, + titleLog = "Rebuild deployment", + descriptionLog = "", }: { - composeId: string; - titleLog: string; - descriptionLog: string; + composeId: string; + titleLog: string; + descriptionLog: string; }) => { - const compose = await findComposeById(composeId); - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); + const compose = await findComposeById(composeId); + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); - try { - if (compose.serverId) { - await getBuildComposeCommand(compose, deployment.logPath); - } + try { + if (compose.serverId) { + await getBuildComposeCommand(compose, deployment.logPath); + } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - } catch (error) { - // @ts-ignore - const encodedContent = encodeBase64(error?.message); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + // @ts-ignore + const encodedContent = encodeBase64(error?.message); - await execAsyncRemote( - compose.serverId, - ` + await execAsyncRemote( + compose.serverId, + ` echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath}; echo "Error occurred ❌, check the logs for details." >> ${deployment.logPath}; - echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";` - ); - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } + echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`, + ); + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + throw error; + } - return true; + return true; }; export const removeCompose = async ( - compose: Compose, - deleteVolumes: boolean + compose: Compose, + deleteVolumes: boolean, ) => { - try { - const { COMPOSE_PATH } = paths(!!compose.serverId); - const projectPath = join(COMPOSE_PATH, compose.appName); + try { + const { COMPOSE_PATH } = paths(!!compose.serverId); + const projectPath = join(COMPOSE_PATH, compose.appName); - console.log("API: DELETE VOLUMES=", deleteVolumes); + console.log("API: DELETE VOLUMES=", deleteVolumes); - if (compose.composeType === "stack") { - const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; + if (compose.composeType === "stack") { + const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; - if (compose.serverId) { - await execAsyncRemote(compose.serverId, command); - } else { - await execAsync(command); - } - await execAsync(command, { - cwd: projectPath, - }); - } else { - let command: string; - if (deleteVolumes) { - command = `cd ${projectPath} && docker compose -p ${compose.appName} down --volumes && rm -rf ${projectPath}`; - } else { - command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; - } + if (compose.serverId) { + await execAsyncRemote(compose.serverId, command); + } else { + await execAsync(command); + } + await execAsync(command, { + cwd: projectPath, + }); + } else { + let command: string; + if (deleteVolumes) { + command = `cd ${projectPath} && docker compose -p ${compose.appName} down --volumes && rm -rf ${projectPath}`; + } else { + command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; + } - if (compose.serverId) { - await execAsyncRemote(compose.serverId, command); - } else { - await execAsync(command, { - cwd: projectPath, - }); - } - } - } catch (error) { - throw error; - } + if (compose.serverId) { + await execAsyncRemote(compose.serverId, command); + } else { + await execAsync(command, { + cwd: projectPath, + }); + } + } + } catch (error) { + throw error; + } - return true; + 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"), - }); - } - } + 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; - } + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + await updateCompose(composeId, { + composeStatus: "idle", + }); + throw error; + } - return true; + return true; }; export const stopCompose = 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)} && docker compose -p ${ - compose.appName - } stop` - ); - } else { - await execAsync(`docker compose -p ${compose.appName} stop`, { - cwd: join(COMPOSE_PATH, compose.appName), - }); - } - } + 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)} && docker compose -p ${ + compose.appName + } stop`, + ); + } else { + await execAsync(`docker compose -p ${compose.appName} stop`, { + cwd: join(COMPOSE_PATH, compose.appName), + }); + } + } - await updateCompose(composeId, { - composeStatus: "idle", - }); - } catch (error) { - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } + await updateCompose(composeId, { + composeStatus: "idle", + }); + } catch (error) { + await updateCompose(composeId, { + composeStatus: "error", + }); + throw error; + } - return true; + return true; }; From ed543e5397bca2af1e10e9e8c7d153251d6bda45 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:28:40 -0600 Subject: [PATCH 11/13] refactor: lint --- .../deployments/show-deployment.tsx | 60 +- .../show-preview-builds.tsx | 4 +- .../show-preview-deployments.tsx | 422 ++++----- .../show-preview-settings.tsx | 14 +- .../deployments/show-deployment-compose.tsx | 53 +- .../compose/general/deploy-compose.tsx | 2 +- .../docker/terminal/docker-terminal-modal.tsx | 2 +- .../components/dashboard/projects/show.tsx | 536 ++++++------ .../components/dashboard/search-command.tsx | 342 ++++---- .../notifications/delete-notification.tsx | 8 +- .../notifications/show-notifications.tsx | 102 +-- .../notifications/update-notification.tsx | 8 +- .../settings/profile/profile-form.tsx | 2 +- .../settings/profile/remove-self-account.tsx | 6 +- .../servers/actions/show-traefik-actions.tsx | 2 +- .../settings/servers/edit-script.tsx | 3 +- .../settings/servers/setup-server.tsx | 2 +- .../settings/servers/show-servers.tsx | 4 +- .../servers/welcome-stripe/create-ssh-key.tsx | 6 +- .../settings/servers/welcome-stripe/setup.tsx | 18 +- .../servers/welcome-stripe/verify.tsx | 16 +- .../welcome-stripe/welcome-suscription.tsx | 14 +- .../web-server/docker-terminal-modal.tsx | 2 +- .../web-server/manage-traefik-ports.tsx | 2 +- apps/dokploy/pages/api/deploy/github.ts | 6 +- apps/dokploy/public/locales/it/common.json | 2 +- apps/dokploy/public/locales/it/settings.json | 88 +- apps/dokploy/templates/onedev/index.ts | 30 +- apps/dokploy/templates/unsend/index.ts | 72 +- packages/server/src/db/schema/application.ts | 826 +++++++++--------- packages/server/src/db/schema/deployment.ts | 2 +- packages/server/src/db/schema/domain.ts | 2 +- packages/server/src/db/schema/index.ts | 2 +- .../src/db/schema/preview-deployments.ts | 10 +- packages/server/src/utils/backups/index.ts | 4 +- packages/server/src/utils/builders/index.ts | 2 +- packages/server/src/utils/docker/utils.ts | 812 ++++++++--------- 37 files changed, 1743 insertions(+), 1745 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx index 380b22d93..c67d1ba60 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx @@ -1,3 +1,4 @@ +import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, @@ -5,11 +6,10 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { Loader2 } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { TerminalLine } from "../../docker/logs/terminal-line"; -import { LogLine, parseLogs } from "../../docker/logs/utils"; -import { Badge } from "@/components/ui/badge"; -import { Loader2 } from "lucide-react"; +import { type LogLine, parseLogs } from "../../docker/logs/utils"; interface Props { logPath: string | null; @@ -24,21 +24,20 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => { const [autoScroll, setAutoScroll] = useState(true); const scrollRef = useRef(null); - const scrollToBottom = () => { if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } - }; - + }; + const handleScroll = () => { if (!scrollRef.current) return; - + const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; setAutoScroll(isAtBottom); - }; - + }; + useEffect(() => { if (!open || !logPath) return; @@ -69,7 +68,6 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => { }; }, [logPath, open]); - useEffect(() => { const logs = parseLogs(data); setFilteredLogs(logs); @@ -77,12 +75,11 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => { useEffect(() => { scrollToBottom(); - - if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }, [filteredLogs, autoScroll]); + if (autoScroll && scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [filteredLogs, autoScroll]); return ( { Deployment - See all the details of this deployment | {filteredLogs.length} lines + See all the details of this deployment |{" "} + + {filteredLogs.length} lines + -
{ - filteredLogs.length > 0 ? filteredLogs.map((log: LogLine, index: number) => ( - - )) : - ( -
- -
- )} + > + {" "} + {filteredLogs.length > 0 ? ( + filteredLogs.map((log: LogLine, index: number) => ( + + )) + ) : ( +
+ +
+ )}
diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-builds.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-builds.tsx index bff6c9290..154510c3b 100644 --- a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-builds.tsx +++ b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-builds.tsx @@ -26,7 +26,9 @@ export const ShowPreviewBuilds = ({ deployments, serverId }: Props) => { return ( - + diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx index 898d412a0..c2d712c3b 100644 --- a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx @@ -1,237 +1,237 @@ -import React from "react"; +import { DateTooltip } from "@/components/shared/date-tooltip"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { StatusTooltip } from "@/components/shared/status-tooltip"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; import { - Clock, - GitBranch, - GitPullRequest, - Pencil, - RocketIcon, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { api } from "@/utils/api"; +import { + Clock, + GitBranch, + GitPullRequest, + Pencil, + RocketIcon, } from "lucide-react"; import Link from "next/link"; -import { ShowModalLogs } from "../../settings/web-server/show-modal-logs"; -import { DialogAction } from "@/components/shared/dialog-action"; -import { api } from "@/utils/api"; -import { ShowPreviewBuilds } from "./show-preview-builds"; -import { DateTooltip } from "@/components/shared/date-tooltip"; +import React from "react"; import { toast } from "sonner"; -import { StatusTooltip } from "@/components/shared/status-tooltip"; +import { ShowModalLogs } from "../../settings/web-server/show-modal-logs"; import { AddPreviewDomain } from "./add-preview-domain"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import { ShowPreviewBuilds } from "./show-preview-builds"; import { ShowPreviewSettings } from "./show-preview-settings"; interface Props { - applicationId: string; + applicationId: string; } export const ShowPreviewDeployments = ({ applicationId }: Props) => { - const { data } = api.application.one.useQuery({ applicationId }); + const { data } = api.application.one.useQuery({ applicationId }); - const { mutateAsync: deletePreviewDeployment, isLoading } = - api.previewDeployment.delete.useMutation(); + const { mutateAsync: deletePreviewDeployment, isLoading } = + api.previewDeployment.delete.useMutation(); - const { data: previewDeployments, refetch: refetchPreviewDeployments } = - api.previewDeployment.all.useQuery( - { applicationId }, - { - enabled: !!applicationId, - } - ); + const { data: previewDeployments, refetch: refetchPreviewDeployments } = + api.previewDeployment.all.useQuery( + { applicationId }, + { + enabled: !!applicationId, + }, + ); - const handleDeletePreviewDeployment = async (previewDeploymentId: string) => { - deletePreviewDeployment({ - previewDeploymentId: previewDeploymentId, - }) - .then(() => { - refetchPreviewDeployments(); - toast.success("Preview deployment deleted"); - }) - .catch((error) => { - toast.error(error.message); - }); - }; + const handleDeletePreviewDeployment = async (previewDeploymentId: string) => { + deletePreviewDeployment({ + previewDeploymentId: previewDeploymentId, + }) + .then(() => { + refetchPreviewDeployments(); + toast.success("Preview deployment deleted"); + }) + .catch((error) => { + toast.error(error.message); + }); + }; - return ( - - -
- Preview Deployments - See all the preview deployments -
- {data?.isPreviewDeploymentsActive && ( - - )} -
- - {data?.isPreviewDeploymentsActive ? ( - <> -
- - Preview deployments are a way to test your application before it - is deployed to production. It will create a new deployment for - each pull request you create. - -
- {!previewDeployments?.length ? ( -
- - - No preview deployments found - -
- ) : ( -
- {previewDeployments.map((previewDeployment) => ( -
-
- - {previewDeployment.pullRequestTitle} - - - - {previewDeployment.previewStatus - ?.replace("running", "Running") - .replace("done", "Done") - .replace("error", "Error") - .replace("idle", "Idle") || "Idle"} - -
+ return ( + + +
+ Preview Deployments + See all the preview deployments +
+ {data?.isPreviewDeploymentsActive && ( + + )} +
+ + {data?.isPreviewDeploymentsActive ? ( + <> +
+ + Preview deployments are a way to test your application before it + is deployed to production. It will create a new deployment for + each pull request you create. + +
+ {!previewDeployments?.length ? ( +
+ + + No preview deployments found + +
+ ) : ( +
+ {previewDeployments.map((previewDeployment) => ( +
+
+ + {previewDeployment.pullRequestTitle} + + + + {previewDeployment.previewStatus + ?.replace("running", "Running") + .replace("done", "Done") + .replace("error", "Error") + .replace("idle", "Idle") || "Idle"} + +
-
-
- - {previewDeployment.domain?.host} - +
+
+ + {previewDeployment.domain?.host} + - - - -
+ + + +
-
-
- - Branch: - - {previewDeployment.branch} - -
-
- - Deployed: - - - -
-
+
+
+ + Branch: + + {previewDeployment.branch} + +
+
+ + Deployed: + + + +
+
- + -
-

- Pull Request -

-
- - - {previewDeployment.pullRequestTitle} - -
-
-
+
+

+ Pull Request +

+
+ + + {previewDeployment.pullRequestTitle} + +
+
+
-
-
- - - +
+
+ + + - + - - handleDeletePreviewDeployment( - previewDeployment.previewDeploymentId - ) - } - > - - -
-
-
- ))} -
- )} - - ) : ( -
- - - Preview deployments are disabled for this application, please - enable it - - -
- )} - - - ); + + handleDeletePreviewDeployment( + previewDeployment.previewDeploymentId, + ) + } + > + + +
+
+
+ ))} +
+ )} + + ) : ( +
+ + + Preview deployments are disabled for this application, please + enable it + + +
+ )} +
+
+ ); }; diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx index 6e56bbdd0..0e4231eb4 100644 --- a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx +++ b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx @@ -1,5 +1,3 @@ -import { api } from "@/utils/api"; -import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -20,12 +18,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input, NumberInput } from "@/components/ui/input"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; import { Secrets } from "@/components/ui/secrets"; -import { toast } from "sonner"; -import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, @@ -33,6 +26,13 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; const schema = z.object({ env: z.string(), diff --git a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx index 4a45fb204..d683d4ab4 100644 --- a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx @@ -1,3 +1,4 @@ +import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, @@ -5,12 +6,10 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { Loader2 } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { TerminalLine } from "../../docker/logs/terminal-line"; -import { LogLine, parseLogs } from "../../docker/logs/utils"; -import { Badge } from "@/components/ui/badge"; -import { Loader2 } from "lucide-react"; - +import { type LogLine, parseLogs } from "../../docker/logs/utils"; interface Props { logPath: string | null; @@ -32,19 +31,18 @@ export const ShowDeploymentCompose = ({ const scrollToBottom = () => { if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } - }; - + }; + const handleScroll = () => { if (!scrollRef.current) return; - + const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; setAutoScroll(isAtBottom); - }; - - + }; + useEffect(() => { if (!open || !logPath) return; @@ -76,7 +74,6 @@ export const ShowDeploymentCompose = ({ }; }, [logPath, open]); - useEffect(() => { const logs = parseLogs(data); setFilteredLogs(logs); @@ -84,11 +81,11 @@ export const ShowDeploymentCompose = ({ useEffect(() => { scrollToBottom(); - + if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } - }, [filteredLogs, autoScroll]); + }, [filteredLogs, autoScroll]); return ( Deployment - See all the details of this deployment | {filteredLogs.length} lines + See all the details of this deployment |{" "} + + {filteredLogs.length} lines +
-
- - - { - filteredLogs.length > 0 ? filteredLogs.map((log: LogLine, index: number) => ( - - )) : - ( + {filteredLogs.length > 0 ? ( + filteredLogs.map((log: LogLine, index: number) => ( + + )) + ) : (
- ) - } + )}
diff --git a/apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx b/apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx index c02a78028..25b59cc73 100644 --- a/apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx @@ -53,7 +53,7 @@ export const DeployCompose = ({ composeId }: Props) => { }) .then(async () => { router.push( - `/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments` + `/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`, ); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx index e7301b0aa..90aa2b406 100644 --- a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx @@ -59,7 +59,7 @@ export const DockerTerminalModal = ({ {children} - event.preventDefault()} > diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index d05bbba2c..d4d9ac553 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -1,35 +1,35 @@ import { DateTooltip } from "@/components/shared/date-tooltip"; import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { api } from "@/utils/api"; import { - AlertTriangle, - BookIcon, - ExternalLink, - ExternalLinkIcon, - FolderInput, - MoreHorizontalIcon, - TrashIcon, + AlertTriangle, + BookIcon, + ExternalLink, + ExternalLinkIcon, + FolderInput, + MoreHorizontalIcon, + TrashIcon, } from "lucide-react"; import Link from "next/link"; import { Fragment } from "react"; @@ -38,257 +38,257 @@ import { ProjectEnviroment } from "./project-enviroment"; import { UpdateProject } from "./update"; export const ShowProjects = () => { - const utils = api.useUtils(); - const { data } = api.project.all.useQuery(); - const { data: auth } = api.auth.get.useQuery(); - const { data: user } = api.user.byAuthId.useQuery( - { - authId: auth?.id || "", - }, - { - enabled: !!auth?.id && auth?.rol === "user", - } - ); - const { mutateAsync } = api.project.remove.useMutation(); + const utils = api.useUtils(); + const { data } = api.project.all.useQuery(); + const { data: auth } = api.auth.get.useQuery(); + const { data: user } = api.user.byAuthId.useQuery( + { + authId: auth?.id || "", + }, + { + enabled: !!auth?.id && auth?.rol === "user", + }, + ); + const { mutateAsync } = api.project.remove.useMutation(); - return ( - <> - {data?.length === 0 && ( -
- - - No projects added yet. Click on Create project. - -
- )} -
- {data?.map((project) => { - const emptyServices = - project?.mariadb.length === 0 && - project?.mongo.length === 0 && - project?.mysql.length === 0 && - project?.postgres.length === 0 && - project?.redis.length === 0 && - project?.applications.length === 0 && - project?.compose.length === 0; + return ( + <> + {data?.length === 0 && ( +
+ + + No projects added yet. Click on Create project. + +
+ )} +
+ {data?.map((project) => { + const emptyServices = + project?.mariadb.length === 0 && + project?.mongo.length === 0 && + project?.mysql.length === 0 && + project?.postgres.length === 0 && + project?.redis.length === 0 && + project?.applications.length === 0 && + project?.compose.length === 0; - const totalServices = - project?.mariadb.length + - project?.mongo.length + - project?.mysql.length + - project?.postgres.length + - project?.redis.length + - project?.applications.length + - project?.compose.length; + const totalServices = + project?.mariadb.length + + project?.mongo.length + + project?.mysql.length + + project?.postgres.length + + project?.redis.length + + project?.applications.length + + project?.compose.length; - const flattedDomains = [ - ...project.applications.flatMap((a) => a.domains), - ...project.compose.flatMap((a) => a.domains), - ]; + const flattedDomains = [ + ...project.applications.flatMap((a) => a.domains), + ...project.compose.flatMap((a) => a.domains), + ]; - const renderDomainsDropdown = ( - item: typeof project.compose | typeof project.applications - ) => - item[0] ? ( - - - {"applicationId" in item[0] ? "Applications" : "Compose"} - - {item.map((a) => ( - - - - - {a.name} - - - {a.domains.map((domain) => ( - - - {domain.host} - - - - ))} - - - ))} - - ) : null; + const renderDomainsDropdown = ( + item: typeof project.compose | typeof project.applications, + ) => + item[0] ? ( + + + {"applicationId" in item[0] ? "Applications" : "Compose"} + + {item.map((a) => ( + + + + + {a.name} + + + {a.domains.map((domain) => ( + + + {domain.host} + + + + ))} + + + ))} + + ) : null; - return ( -
- - - {flattedDomains.length > 1 ? ( - - - - - e.stopPropagation()} - > - {renderDomainsDropdown(project.applications)} - {renderDomainsDropdown(project.compose)} - - - ) : flattedDomains[0] ? ( - - ) : null} + return ( +
+ + + {flattedDomains.length > 1 ? ( + + + + + e.stopPropagation()} + > + {renderDomainsDropdown(project.applications)} + {renderDomainsDropdown(project.compose)} + + + ) : flattedDomains[0] ? ( + + ) : null} - - - -
- - - {project.name} - -
+ + + +
+ + + {project.name} + +
- - {project.description} - -
-
- - - - - - - Actions - -
e.stopPropagation()}> - -
-
e.stopPropagation()}> - -
+ + {project.description} + + +
+ + + + + + + Actions + +
e.stopPropagation()}> + +
+
e.stopPropagation()}> + +
-
e.stopPropagation()}> - {(auth?.rol === "admin" || - user?.canDeleteProjects) && ( - - - e.preventDefault()} - > - - Delete - - - - - - Are you sure to delete this project? - - {!emptyServices ? ( -
- - - You have active services, please - delete them first - -
- ) : ( - - This action cannot be undone - - )} -
- - - Cancel - - { - await mutateAsync({ - projectId: project.projectId, - }) - .then(() => { - toast.success( - "Project delete succesfully" - ); - }) - .catch(() => { - toast.error( - "Error to delete this project" - ); - }) - .finally(() => { - utils.project.all.invalidate(); - }); - }} - > - Delete - - -
-
- )} -
-
-
-
- - - -
- - Created - - - {totalServices}{" "} - {totalServices === 1 ? "service" : "services"} - -
-
- - -
- ); - })} -
- - ); +
e.stopPropagation()}> + {(auth?.rol === "admin" || + user?.canDeleteProjects) && ( + + + e.preventDefault()} + > + + Delete + + + + + + Are you sure to delete this project? + + {!emptyServices ? ( +
+ + + You have active services, please + delete them first + +
+ ) : ( + + This action cannot be undone + + )} +
+ + + Cancel + + { + await mutateAsync({ + projectId: project.projectId, + }) + .then(() => { + toast.success( + "Project delete succesfully", + ); + }) + .catch(() => { + toast.error( + "Error to delete this project", + ); + }) + .finally(() => { + utils.project.all.invalidate(); + }); + }} + > + Delete + + +
+
+ )} +
+ + +
+ + + +
+ + Created + + + {totalServices}{" "} + {totalServices === 1 ? "service" : "services"} + +
+
+ + +
+ ); + })} +
+ + ); }; diff --git a/apps/dokploy/components/dashboard/search-command.tsx b/apps/dokploy/components/dashboard/search-command.tsx index 8afea6720..4d3c75f98 100644 --- a/apps/dokploy/components/dashboard/search-command.tsx +++ b/apps/dokploy/components/dashboard/search-command.tsx @@ -1,189 +1,189 @@ "use client"; -import React from "react"; import { - Command, - CommandEmpty, - CommandList, - CommandGroup, - CommandInput, - CommandItem, - CommandDialog, - CommandSeparator, + MariadbIcon, + MongodbIcon, + MysqlIcon, + PostgresqlIcon, + RedisIcon, +} from "@/components/icons/data-tools-icons"; +import { Badge } from "@/components/ui/badge"; +import { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, } from "@/components/ui/command"; -import { useRouter } from "next/router"; import { - extractServices, - type Services, + type Services, + extractServices, } from "@/pages/dashboard/project/[projectId]"; +import { api } from "@/utils/api"; import type { findProjectById } from "@dokploy/server/services/project"; import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react"; -import { - MariadbIcon, - MongodbIcon, - MysqlIcon, - PostgresqlIcon, - RedisIcon, -} from "@/components/icons/data-tools-icons"; -import { api } from "@/utils/api"; -import { Badge } from "@/components/ui/badge"; +import { useRouter } from "next/router"; +import React from "react"; import { StatusTooltip } from "../shared/status-tooltip"; type Project = Awaited>; export const SearchCommand = () => { - const router = useRouter(); - const [open, setOpen] = React.useState(false); - const [search, setSearch] = React.useState(""); + const router = useRouter(); + const [open, setOpen] = React.useState(false); + const [search, setSearch] = React.useState(""); - const { data } = api.project.all.useQuery(); - const { data: isCloud, isLoading } = api.settings.isCloud.useQuery(); + const { data } = api.project.all.useQuery(); + const { data: isCloud, isLoading } = api.settings.isCloud.useQuery(); - React.useEffect(() => { - const down = (e: KeyboardEvent) => { - if (e.key === "j" && (e.metaKey || e.ctrlKey)) { - e.preventDefault(); - setOpen((open) => !open); - } - }; + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "j" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; - document.addEventListener("keydown", down); - return () => document.removeEventListener("keydown", down); - }, []); + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); - return ( -
- - - - - No projects added yet. Click on Create project. - - - - {data?.map((project) => ( - { - router.push(`/dashboard/project/${project.projectId}`); - setOpen(false); - }} - > - - {project.name} - - ))} - - - - - - {data?.map((project) => { - const applications: Services[] = extractServices(project); - return applications.map((application) => ( - { - router.push( - `/dashboard/project/${project.projectId}/services/${application.type}/${application.id}` - ); - setOpen(false); - }} - > - {application.type === "postgres" && ( - - )} - {application.type === "redis" && ( - - )} - {application.type === "mariadb" && ( - - )} - {application.type === "mongo" && ( - - )} - {application.type === "mysql" && ( - - )} - {application.type === "application" && ( - - )} - {application.type === "compose" && ( - - )} - - {project.name} / {application.name}{" "} -
{application.id}
-
-
- -
-
- )); - })} -
-
- - -
-
-
- ); + return ( +
+ + + + + No projects added yet. Click on Create project. + + + + {data?.map((project) => ( + { + router.push(`/dashboard/project/${project.projectId}`); + setOpen(false); + }} + > + + {project.name} + + ))} + + + + + + {data?.map((project) => { + const applications: Services[] = extractServices(project); + return applications.map((application) => ( + { + router.push( + `/dashboard/project/${project.projectId}/services/${application.type}/${application.id}`, + ); + setOpen(false); + }} + > + {application.type === "postgres" && ( + + )} + {application.type === "redis" && ( + + )} + {application.type === "mariadb" && ( + + )} + {application.type === "mongo" && ( + + )} + {application.type === "mysql" && ( + + )} + {application.type === "application" && ( + + )} + {application.type === "compose" && ( + + )} + + {project.name} / {application.name}{" "} +
{application.id}
+
+
+ +
+
+ )); + })} +
+
+ + +
+
+
+ ); }; diff --git a/apps/dokploy/components/dashboard/settings/notifications/delete-notification.tsx b/apps/dokploy/components/dashboard/settings/notifications/delete-notification.tsx index 4bb197b29..02c95d8b3 100644 --- a/apps/dokploy/components/dashboard/settings/notifications/delete-notification.tsx +++ b/apps/dokploy/components/dashboard/settings/notifications/delete-notification.tsx @@ -24,12 +24,12 @@ export const DeleteNotification = ({ notificationId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx index 2b5d02e0e..8a45bbf31 100644 --- a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx +++ b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx @@ -40,58 +40,60 @@ export const ShowNotifications = () => { ) : (
-
- {data?.map((notification, index) => ( -
-
- {notification.notificationType === "slack" && ( -
- -
- )} - {notification.notificationType === "telegram" && ( -
- -
- )} - {notification.notificationType === "discord" && ( -
- -
- )} - {notification.notificationType === "email" && ( -
- -
- )} -
- - {notification.name} - - - {notification.notificationType?.[0]?.toUpperCase() + notification.notificationType?.slice(1)} notification - -
-
-
- - -
+
+ {data?.map((notification, index) => ( +
+
+ {notification.notificationType === "slack" && ( +
+ +
+ )} + {notification.notificationType === "telegram" && ( +
+ +
+ )} + {notification.notificationType === "discord" && ( +
+ +
+ )} + {notification.notificationType === "email" && ( +
+ +
+ )} +
+ + {notification.name} + + + {notification.notificationType?.[0]?.toUpperCase() + + notification.notificationType?.slice(1)}{" "} + notification + +
+
+
+ + +
+
+ ))} +
+ +
+
- ))}
- -
- -
-
)} diff --git a/apps/dokploy/components/dashboard/settings/notifications/update-notification.tsx b/apps/dokploy/components/dashboard/settings/notifications/update-notification.tsx index 5c594dc49..41306385d 100644 --- a/apps/dokploy/components/dashboard/settings/notifications/update-notification.tsx +++ b/apps/dokploy/components/dashboard/settings/notifications/update-notification.tsx @@ -218,9 +218,11 @@ export const UpdateNotification = ({ notificationId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx index 1141397f9..191c1936d 100644 --- a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx @@ -1,3 +1,4 @@ +import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { Card, @@ -26,7 +27,6 @@ import { toast } from "sonner"; import { z } from "zod"; import { Disable2FA } from "./disable-2fa"; import { Enable2FA } from "./enable-2fa"; -import { AlertBlock } from "@/components/shared/alert-block"; const profileSchema = z.object({ email: z.string(), diff --git a/apps/dokploy/components/dashboard/settings/profile/remove-self-account.tsx b/apps/dokploy/components/dashboard/settings/profile/remove-self-account.tsx index f4f4680bc..3fc554522 100644 --- a/apps/dokploy/components/dashboard/settings/profile/remove-self-account.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/remove-self-account.tsx @@ -1,3 +1,5 @@ +import { AlertBlock } from "@/components/shared/alert-block"; +import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { Card, @@ -18,13 +20,11 @@ import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslation } from "next-i18next"; +import { useRouter } from "next/router"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { DialogAction } from "@/components/shared/dialog-action"; -import { AlertBlock } from "@/components/shared/alert-block"; -import { useRouter } from "next/router"; const profileSchema = z.object({ password: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx index 546069c5b..72854f930 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx @@ -25,8 +25,8 @@ import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { useTranslation } from "next-i18next"; import { EditTraefikEnv } from "../../web-server/edit-traefik-env"; -import { ShowModalLogs } from "../../web-server/show-modal-logs"; import { ManageTraefikPorts } from "../../web-server/manage-traefik-ports"; +import { ShowModalLogs } from "../../web-server/show-modal-logs"; interface Props { serverId?: string; diff --git a/apps/dokploy/components/dashboard/settings/servers/edit-script.tsx b/apps/dokploy/components/dashboard/settings/servers/edit-script.tsx index faaecb1fa..0a22220ed 100644 --- a/apps/dokploy/components/dashboard/settings/servers/edit-script.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/edit-script.tsx @@ -108,7 +108,8 @@ export const EditScript = ({ serverId }: Props) => { - We recommend not modifying this script unless you know what you are doing. + We recommend not modifying this script unless you know what you are + doing.
diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx index 7c1814591..252ca16c5 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx @@ -34,8 +34,8 @@ import { toast } from "sonner"; import { ShowDeployment } from "../../application/deployments/show-deployment"; import { EditScript } from "./edit-script"; import { GPUSupport } from "./gpu-support"; -import { ValidateServer } from "./validate-server"; import { SecurityAudit } from "./security-audit"; +import { ValidateServer } from "./validate-server"; interface Props { serverId: string; diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index d476fb15f..d45a3b77d 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -23,17 +23,17 @@ import { api } from "@/utils/api"; import { format } from "date-fns"; import { KeyIcon, MoreHorizontal, ServerIcon } from "lucide-react"; import Link from "next/link"; +import { useRouter } from "next/router"; import { toast } from "sonner"; import { TerminalModal } from "../web-server/terminal-modal"; import { ShowServerActions } from "./actions/show-server-actions"; import { AddServer } from "./add-server"; import { SetupServer } from "./setup-server"; import { ShowDockerContainersModal } from "./show-docker-containers-modal"; +import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal"; import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; import { UpdateServer } from "./update-server"; -import { useRouter } from "next/router"; import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription"; -import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal"; export const ShowServers = () => { const router = useRouter(); diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx index 740f79607..37f8e0176 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx @@ -1,12 +1,12 @@ +import { CodeEditor } from "@/components/shared/code-editor"; import { Card, CardContent } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { ExternalLinkIcon, Loader2 } from "lucide-react"; import copy from "copy-to-clipboard"; +import { ExternalLinkIcon, Loader2 } from "lucide-react"; import { CopyIcon } from "lucide-react"; +import Link from "next/link"; import { useEffect, useRef } from "react"; import { toast } from "sonner"; -import { CodeEditor } from "@/components/shared/code-editor"; -import Link from "next/link"; export const CreateSSHKey = () => { const { data, refetch } = api.sshKey.all.useQuery(); diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/setup.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/setup.tsx index 39179de83..be7a8e48b 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/setup.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/setup.tsx @@ -5,26 +5,26 @@ import { StatusTooltip } from "@/components/shared/status-tooltip"; import { Button } from "@/components/ui/button"; import { Card, + CardContent, + CardDescription, CardHeader, CardTitle, - CardDescription, - CardContent, } from "@/components/ui/card"; -import { RocketIcon } from "lucide-react"; -import { toast } from "sonner"; -import { EditScript } from "../edit-script"; -import { api } from "@/utils/api"; -import { useState } from "react"; import { Label } from "@/components/ui/label"; import { Select, - SelectTrigger, - SelectValue, SelectContent, SelectGroup, SelectItem, SelectLabel, + SelectTrigger, + SelectValue, } from "@/components/ui/select"; +import { api } from "@/utils/api"; +import { RocketIcon } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { EditScript } from "../edit-script"; export const Setup = () => { const { data: servers } = api.server.all.useQuery(); diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/verify.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/verify.tsx index 3d6cfa7df..fe8c36c2c 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/verify.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/verify.tsx @@ -1,27 +1,27 @@ import { Button } from "@/components/ui/button"; import { Card, + CardContent, + CardDescription, CardHeader, CardTitle, - CardDescription, - CardContent, } from "@/components/ui/card"; -import { Loader2, PcCase, RefreshCw } from "lucide-react"; -import { api } from "@/utils/api"; -import { useState } from "react"; import { Label } from "@/components/ui/label"; +import { api } from "@/utils/api"; +import { Loader2, PcCase, RefreshCw } from "lucide-react"; +import { useState } from "react"; +import { AlertBlock } from "@/components/shared/alert-block"; import { Select, - SelectTrigger, - SelectValue, SelectContent, SelectGroup, SelectItem, SelectLabel, + SelectTrigger, + SelectValue, } from "@/components/ui/select"; import { StatusRow } from "../gpu-support"; -import { AlertBlock } from "@/components/shared/alert-block"; export const Verify = () => { const { data: servers } = api.server.all.useQuery(); diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/welcome-suscription.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/welcome-suscription.tsx index e9de05232..bab930478 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/welcome-suscription.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/welcome-suscription.tsx @@ -1,3 +1,5 @@ +import { GithubIcon } from "@/components/icons/data-tools-icons"; +import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -7,21 +9,19 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { Separator } from "@/components/ui/separator"; +import { defineStepper } from "@stepperize/react"; import { BookIcon, Puzzle } from "lucide-react"; +import { Code2, Database, GitMerge, Globe, Plug, Users } from "lucide-react"; +import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { defineStepper } from "@stepperize/react"; import React from "react"; -import { Separator } from "@/components/ui/separator"; -import { AlertBlock } from "@/components/shared/alert-block"; +import ConfettiExplosion from "react-confetti-explosion"; import { CreateServer } from "./create-server"; import { CreateSSHKey } from "./create-ssh-key"; import { Setup } from "./setup"; import { Verify } from "./verify"; -import { Database, Globe, GitMerge, Users, Code2, Plug } from "lucide-react"; -import ConfettiExplosion from "react-confetti-explosion"; -import Link from "next/link"; -import { GithubIcon } from "@/components/icons/data-tools-icons"; export const { useStepper, steps, Scoped } = defineStepper( { diff --git a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx index f5b6b2a9e..fa9f1a41d 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx @@ -80,7 +80,7 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => { return ( {children} - event.preventDefault()} > diff --git a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx index 965948ca0..180b2fcbb 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx @@ -6,6 +6,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; import { Select, SelectContent, @@ -13,7 +14,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; import { useTranslation } from "next-i18next"; import type React from "react"; diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 0e0a6c829..459237620 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -3,19 +3,19 @@ import { applications, compose, github } from "@/server/db/schema"; import type { DeploymentJob } from "@/server/queues/queue-types"; import { myQueue } from "@/server/queues/queueSetup"; import { deploy } from "@/server/utils/deploy"; +import { generateRandomDomain } from "@/templates/utils"; import { - createPreviewDeployment, type Domain, + IS_CLOUD, + createPreviewDeployment, findPreviewDeploymentByApplicationId, findPreviewDeploymentsByPullRequestId, - IS_CLOUD, removePreviewDeployment, } from "@dokploy/server"; import { Webhooks } from "@octokit/webhooks"; import { and, eq } from "drizzle-orm"; import type { NextApiRequest, NextApiResponse } from "next"; import { extractCommitMessage, extractHash } from "./[refreshToken]"; -import { generateRandomDomain } from "@/templates/utils"; export default async function handler( req: NextApiRequest, diff --git a/apps/dokploy/public/locales/it/common.json b/apps/dokploy/public/locales/it/common.json index 9e26dfeeb..0967ef424 100644 --- a/apps/dokploy/public/locales/it/common.json +++ b/apps/dokploy/public/locales/it/common.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/apps/dokploy/public/locales/it/settings.json b/apps/dokploy/public/locales/it/settings.json index f4c2cc068..6280e44eb 100644 --- a/apps/dokploy/public/locales/it/settings.json +++ b/apps/dokploy/public/locales/it/settings.json @@ -1,44 +1,44 @@ -{ - "settings.common.save": "Salva", - "settings.server.domain.title": "Dominio del server", - "settings.server.domain.description": "Aggiungi un dominio alla tua applicazione server.", - "settings.server.domain.form.domain": "Dominio", - "settings.server.domain.form.letsEncryptEmail": "Email di Let's Encrypt", - "settings.server.domain.form.certificate.label": "Certificato", - "settings.server.domain.form.certificate.placeholder": "Seleziona un certificato", - "settings.server.domain.form.certificateOptions.none": "Nessuno", - "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Predefinito)", - - "settings.server.webServer.title": "Server Web", - "settings.server.webServer.description": "Ricarica o pulisci il server web.", - "settings.server.webServer.actions": "Azioni", - "settings.server.webServer.reload": "Ricarica", - "settings.server.webServer.watchLogs": "Guarda i log", - "settings.server.webServer.updateServerIp": "Aggiorna IP del server", - "settings.server.webServer.server.label": "Server", - "settings.server.webServer.traefik.label": "Traefik", - "settings.server.webServer.traefik.modifyEnv": "Modifica Env", - "settings.server.webServer.storage.label": "Spazio", - "settings.server.webServer.storage.cleanUnusedImages": "Pulisci immagini inutilizzate", - "settings.server.webServer.storage.cleanUnusedVolumes": "Pulisci volumi inutilizzati", - "settings.server.webServer.storage.cleanStoppedContainers": "Pulisci container fermati", - "settings.server.webServer.storage.cleanDockerBuilder": "Pulisci Docker Builder e sistema", - "settings.server.webServer.storage.cleanMonitoring": "Pulisci monitoraggio", - "settings.server.webServer.storage.cleanAll": "Pulisci tutto", - - "settings.profile.title": "Account", - "settings.profile.description": "Modifica i dettagli del tuo profilo qui.", - "settings.profile.email": "Email", - "settings.profile.password": "Password", - "settings.profile.avatar": "Avatar", - - "settings.appearance.title": "Aspetto", - "settings.appearance.description": "Personalizza il tema della tua dashboard.", - "settings.appearance.theme": "Tema", - "settings.appearance.themeDescription": "Seleziona un tema per la tua dashboard", - "settings.appearance.themes.light": "Chiaro", - "settings.appearance.themes.dark": "Scuro", - "settings.appearance.themes.system": "Sistema", - "settings.appearance.language": "Lingua", - "settings.appearance.languageDescription": "Seleziona una lingua per la tua dashboard" -} \ No newline at end of file +{ + "settings.common.save": "Salva", + "settings.server.domain.title": "Dominio del server", + "settings.server.domain.description": "Aggiungi un dominio alla tua applicazione server.", + "settings.server.domain.form.domain": "Dominio", + "settings.server.domain.form.letsEncryptEmail": "Email di Let's Encrypt", + "settings.server.domain.form.certificate.label": "Certificato", + "settings.server.domain.form.certificate.placeholder": "Seleziona un certificato", + "settings.server.domain.form.certificateOptions.none": "Nessuno", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Predefinito)", + + "settings.server.webServer.title": "Server Web", + "settings.server.webServer.description": "Ricarica o pulisci il server web.", + "settings.server.webServer.actions": "Azioni", + "settings.server.webServer.reload": "Ricarica", + "settings.server.webServer.watchLogs": "Guarda i log", + "settings.server.webServer.updateServerIp": "Aggiorna IP del server", + "settings.server.webServer.server.label": "Server", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "Modifica Env", + "settings.server.webServer.storage.label": "Spazio", + "settings.server.webServer.storage.cleanUnusedImages": "Pulisci immagini inutilizzate", + "settings.server.webServer.storage.cleanUnusedVolumes": "Pulisci volumi inutilizzati", + "settings.server.webServer.storage.cleanStoppedContainers": "Pulisci container fermati", + "settings.server.webServer.storage.cleanDockerBuilder": "Pulisci Docker Builder e sistema", + "settings.server.webServer.storage.cleanMonitoring": "Pulisci monitoraggio", + "settings.server.webServer.storage.cleanAll": "Pulisci tutto", + + "settings.profile.title": "Account", + "settings.profile.description": "Modifica i dettagli del tuo profilo qui.", + "settings.profile.email": "Email", + "settings.profile.password": "Password", + "settings.profile.avatar": "Avatar", + + "settings.appearance.title": "Aspetto", + "settings.appearance.description": "Personalizza il tema della tua dashboard.", + "settings.appearance.theme": "Tema", + "settings.appearance.themeDescription": "Seleziona un tema per la tua dashboard", + "settings.appearance.themes.light": "Chiaro", + "settings.appearance.themes.dark": "Scuro", + "settings.appearance.themes.system": "Sistema", + "settings.appearance.language": "Lingua", + "settings.appearance.languageDescription": "Seleziona una lingua per la tua dashboard" +} diff --git a/apps/dokploy/templates/onedev/index.ts b/apps/dokploy/templates/onedev/index.ts index 5dad17282..8017c3514 100644 --- a/apps/dokploy/templates/onedev/index.ts +++ b/apps/dokploy/templates/onedev/index.ts @@ -1,22 +1,22 @@ import { - type DomainSchema, - type Schema, - type Template, - generateRandomDomain, + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, } from "../utils"; export function generate(schema: Schema): Template { - const randomDomain = generateRandomDomain(schema); + const randomDomain = generateRandomDomain(schema); - const domains: DomainSchema[] = [ - { - host: randomDomain, - port: 6610, - serviceName: "onedev", - }, - ]; + const domains: DomainSchema[] = [ + { + host: randomDomain, + port: 6610, + serviceName: "onedev", + }, + ]; - return { - domains, - }; + return { + domains, + }; } diff --git a/apps/dokploy/templates/unsend/index.ts b/apps/dokploy/templates/unsend/index.ts index a383b771f..1c4c9c715 100644 --- a/apps/dokploy/templates/unsend/index.ts +++ b/apps/dokploy/templates/unsend/index.ts @@ -1,44 +1,44 @@ import { - generateHash, - generateRandomDomain, - generateBase64, - type Template, - type Schema, - type DomainSchema, + type DomainSchema, + type Schema, + type Template, + generateBase64, + generateHash, + generateRandomDomain, } from "../utils"; export function generate(schema: Schema): Template { - const mainDomain = generateRandomDomain(schema); - const secretBase = generateBase64(64); + const mainDomain = generateRandomDomain(schema); + const secretBase = generateBase64(64); - const domains: DomainSchema[] = [ - { - host: mainDomain, - port: 3000, - serviceName: "unsend", - }, - ]; + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 3000, + serviceName: "unsend", + }, + ]; - const envs = [ - "REDIS_URL=redis://unsend-redis-prod:6379", - "POSTGRES_USER=postgres", - "POSTGRES_PASSWORD=postgres", - "POSTGRES_DB=unsend", - "DATABASE_URL=postgresql://postgres:postgres@unsend-db-prod:5432/unsend", - "NEXTAUTH_URL=http://localhost:3000", - `NEXTAUTH_SECRET=${secretBase}`, - "GITHUB_ID='Fill'", - "GITHUB_SECRET='Fill'", - "AWS_DEFAULT_REGION=us-east-1", - "AWS_SECRET_KEY='Fill'", - "AWS_ACCESS_KEY='Fill'", - "DOCKER_OUTPUT=1", - "API_RATE_LIMIT=1", - "DISCORD_WEBHOOK_URL=", - ]; + const envs = [ + "REDIS_URL=redis://unsend-redis-prod:6379", + "POSTGRES_USER=postgres", + "POSTGRES_PASSWORD=postgres", + "POSTGRES_DB=unsend", + "DATABASE_URL=postgresql://postgres:postgres@unsend-db-prod:5432/unsend", + "NEXTAUTH_URL=http://localhost:3000", + `NEXTAUTH_SECRET=${secretBase}`, + "GITHUB_ID='Fill'", + "GITHUB_SECRET='Fill'", + "AWS_DEFAULT_REGION=us-east-1", + "AWS_SECRET_KEY='Fill'", + "AWS_ACCESS_KEY='Fill'", + "DOCKER_OUTPUT=1", + "API_RATE_LIMIT=1", + "DISCORD_WEBHOOK_URL=", + ]; - return { - envs, - domains, - }; + return { + envs, + domains, + }; } diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index 425f8d134..0f6aaed31 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -1,11 +1,11 @@ import { relations } from "drizzle-orm"; import { - boolean, - integer, - json, - pgEnum, - pgTable, - text, + boolean, + integer, + json, + pgEnum, + pgTable, + text, } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; @@ -28,493 +28,493 @@ import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; export const sourceType = pgEnum("sourceType", [ - "docker", - "git", - "github", - "gitlab", - "bitbucket", - "drop", + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "drop", ]); export const buildType = pgEnum("buildType", [ - "dockerfile", - "heroku_buildpacks", - "paketo_buildpacks", - "nixpacks", - "static", + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", ]); // TODO: refactor this types export interface HealthCheckSwarm { - Test?: string[] | undefined; - Interval?: number | undefined; - Timeout?: number | undefined; - StartPeriod?: number | undefined; - Retries?: number | undefined; + Test?: string[] | undefined; + Interval?: number | undefined; + Timeout?: number | undefined; + StartPeriod?: number | undefined; + Retries?: number | undefined; } export interface RestartPolicySwarm { - Condition?: string | undefined; - Delay?: number | undefined; - MaxAttempts?: number | undefined; - Window?: number | undefined; + Condition?: string | undefined; + Delay?: number | undefined; + MaxAttempts?: number | undefined; + Window?: number | undefined; } export interface PlacementSwarm { - Constraints?: string[] | undefined; - Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined; - MaxReplicas?: number | undefined; - Platforms?: - | Array<{ - Architecture: string; - OS: string; - }> - | undefined; + Constraints?: string[] | undefined; + Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined; + MaxReplicas?: number | undefined; + Platforms?: + | Array<{ + Architecture: string; + OS: string; + }> + | undefined; } export interface UpdateConfigSwarm { - Parallelism: number; - Delay?: number | undefined; - FailureAction?: string | undefined; - Monitor?: number | undefined; - MaxFailureRatio?: number | undefined; - Order: string; + Parallelism: number; + Delay?: number | undefined; + FailureAction?: string | undefined; + Monitor?: number | undefined; + MaxFailureRatio?: number | undefined; + Order: string; } export interface ServiceModeSwarm { - Replicated?: { Replicas?: number | undefined } | undefined; - Global?: {} | undefined; - ReplicatedJob?: - | { - MaxConcurrent?: number | undefined; - TotalCompletions?: number | undefined; - } - | undefined; - GlobalJob?: {} | undefined; + Replicated?: { Replicas?: number | undefined } | undefined; + Global?: {} | undefined; + ReplicatedJob?: + | { + MaxConcurrent?: number | undefined; + TotalCompletions?: number | undefined; + } + | undefined; + GlobalJob?: {} | undefined; } export interface NetworkSwarm { - Target?: string | undefined; - Aliases?: string[] | undefined; - DriverOpts?: { [key: string]: string } | undefined; + Target?: string | undefined; + Aliases?: string[] | undefined; + DriverOpts?: { [key: string]: string } | undefined; } export interface LabelsSwarm { - [name: string]: string; + [name: string]: string; } export const applications = pgTable("application", { - applicationId: text("applicationId") - .notNull() - .primaryKey() - .$defaultFn(() => nanoid()), - name: text("name").notNull(), - appName: text("appName") - .notNull() - .$defaultFn(() => generateAppName("app")) - .unique(), - description: text("description"), - env: text("env"), - previewEnv: text("previewEnv"), - previewBuildArgs: text("previewBuildArgs"), - previewWildcard: text("previewWildcard"), - previewPort: integer("previewPort").default(3000), - previewHttps: boolean("previewHttps").notNull().default(false), - previewPath: text("previewPath").default("/"), - previewCertificateType: certificateType("certificateType") - .notNull() - .default("none"), - previewLimit: integer("previewLimit").default(3), - isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default( - false - ), - buildArgs: text("buildArgs"), - memoryReservation: integer("memoryReservation"), - memoryLimit: integer("memoryLimit"), - cpuReservation: integer("cpuReservation"), - cpuLimit: integer("cpuLimit"), - title: text("title"), - enabled: boolean("enabled"), - subtitle: text("subtitle"), - command: text("command"), - refreshToken: text("refreshToken").$defaultFn(() => nanoid()), - sourceType: sourceType("sourceType").notNull().default("github"), - // Github - repository: text("repository"), - owner: text("owner"), - branch: text("branch"), - buildPath: text("buildPath").default("/"), - autoDeploy: boolean("autoDeploy").$defaultFn(() => true), - // Gitlab - gitlabProjectId: integer("gitlabProjectId"), - gitlabRepository: text("gitlabRepository"), - gitlabOwner: text("gitlabOwner"), - gitlabBranch: text("gitlabBranch"), - gitlabBuildPath: text("gitlabBuildPath").default("/"), - gitlabPathNamespace: text("gitlabPathNamespace"), - // Bitbucket - bitbucketRepository: text("bitbucketRepository"), - bitbucketOwner: text("bitbucketOwner"), - bitbucketBranch: text("bitbucketBranch"), - bitbucketBuildPath: text("bitbucketBuildPath").default("/"), - // Docker - username: text("username"), - password: text("password"), - dockerImage: text("dockerImage"), - registryUrl: text("registryUrl"), - // Git - customGitUrl: text("customGitUrl"), - customGitBranch: text("customGitBranch"), - customGitBuildPath: text("customGitBuildPath"), - customGitSSHKeyId: text("customGitSSHKeyId").references( - () => sshKeys.sshKeyId, - { - onDelete: "set null", - } - ), - dockerfile: text("dockerfile"), - dockerContextPath: text("dockerContextPath"), - dockerBuildStage: text("dockerBuildStage"), - // Drop - dropBuildPath: text("dropBuildPath"), - // Docker swarm json - healthCheckSwarm: json("healthCheckSwarm").$type(), - restartPolicySwarm: json("restartPolicySwarm").$type(), - placementSwarm: json("placementSwarm").$type(), - updateConfigSwarm: json("updateConfigSwarm").$type(), - rollbackConfigSwarm: json("rollbackConfigSwarm").$type(), - modeSwarm: json("modeSwarm").$type(), - labelsSwarm: json("labelsSwarm").$type(), - networkSwarm: json("networkSwarm").$type(), - // - replicas: integer("replicas").default(1).notNull(), - applicationStatus: applicationStatus("applicationStatus") - .notNull() - .default("idle"), - buildType: buildType("buildType").notNull().default("nixpacks"), - herokuVersion: text("herokuVersion").default("24"), - publishDirectory: text("publishDirectory"), - createdAt: text("createdAt") - .notNull() - .$defaultFn(() => new Date().toISOString()), - registryId: text("registryId").references(() => registry.registryId, { - onDelete: "set null", - }), - projectId: text("projectId") - .notNull() - .references(() => projects.projectId, { onDelete: "cascade" }), - githubId: text("githubId").references(() => github.githubId, { - onDelete: "set null", - }), - gitlabId: text("gitlabId").references(() => gitlab.gitlabId, { - onDelete: "set null", - }), - bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, { - onDelete: "set null", - }), - serverId: text("serverId").references(() => server.serverId, { - onDelete: "cascade", - }), + applicationId: text("applicationId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + name: text("name").notNull(), + appName: text("appName") + .notNull() + .$defaultFn(() => generateAppName("app")) + .unique(), + description: text("description"), + env: text("env"), + previewEnv: text("previewEnv"), + previewBuildArgs: text("previewBuildArgs"), + previewWildcard: text("previewWildcard"), + previewPort: integer("previewPort").default(3000), + previewHttps: boolean("previewHttps").notNull().default(false), + previewPath: text("previewPath").default("/"), + previewCertificateType: certificateType("certificateType") + .notNull() + .default("none"), + previewLimit: integer("previewLimit").default(3), + isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default( + false, + ), + buildArgs: text("buildArgs"), + memoryReservation: integer("memoryReservation"), + memoryLimit: integer("memoryLimit"), + cpuReservation: integer("cpuReservation"), + cpuLimit: integer("cpuLimit"), + title: text("title"), + enabled: boolean("enabled"), + subtitle: text("subtitle"), + command: text("command"), + refreshToken: text("refreshToken").$defaultFn(() => nanoid()), + sourceType: sourceType("sourceType").notNull().default("github"), + // Github + repository: text("repository"), + owner: text("owner"), + branch: text("branch"), + buildPath: text("buildPath").default("/"), + autoDeploy: boolean("autoDeploy").$defaultFn(() => true), + // Gitlab + gitlabProjectId: integer("gitlabProjectId"), + gitlabRepository: text("gitlabRepository"), + gitlabOwner: text("gitlabOwner"), + gitlabBranch: text("gitlabBranch"), + gitlabBuildPath: text("gitlabBuildPath").default("/"), + gitlabPathNamespace: text("gitlabPathNamespace"), + // Bitbucket + bitbucketRepository: text("bitbucketRepository"), + bitbucketOwner: text("bitbucketOwner"), + bitbucketBranch: text("bitbucketBranch"), + bitbucketBuildPath: text("bitbucketBuildPath").default("/"), + // Docker + username: text("username"), + password: text("password"), + dockerImage: text("dockerImage"), + registryUrl: text("registryUrl"), + // Git + customGitUrl: text("customGitUrl"), + customGitBranch: text("customGitBranch"), + customGitBuildPath: text("customGitBuildPath"), + customGitSSHKeyId: text("customGitSSHKeyId").references( + () => sshKeys.sshKeyId, + { + onDelete: "set null", + }, + ), + dockerfile: text("dockerfile"), + dockerContextPath: text("dockerContextPath"), + dockerBuildStage: text("dockerBuildStage"), + // Drop + dropBuildPath: text("dropBuildPath"), + // Docker swarm json + healthCheckSwarm: json("healthCheckSwarm").$type(), + restartPolicySwarm: json("restartPolicySwarm").$type(), + placementSwarm: json("placementSwarm").$type(), + updateConfigSwarm: json("updateConfigSwarm").$type(), + rollbackConfigSwarm: json("rollbackConfigSwarm").$type(), + modeSwarm: json("modeSwarm").$type(), + labelsSwarm: json("labelsSwarm").$type(), + networkSwarm: json("networkSwarm").$type(), + // + replicas: integer("replicas").default(1).notNull(), + applicationStatus: applicationStatus("applicationStatus") + .notNull() + .default("idle"), + buildType: buildType("buildType").notNull().default("nixpacks"), + herokuVersion: text("herokuVersion").default("24"), + publishDirectory: text("publishDirectory"), + createdAt: text("createdAt") + .notNull() + .$defaultFn(() => new Date().toISOString()), + registryId: text("registryId").references(() => registry.registryId, { + onDelete: "set null", + }), + projectId: text("projectId") + .notNull() + .references(() => projects.projectId, { onDelete: "cascade" }), + githubId: text("githubId").references(() => github.githubId, { + onDelete: "set null", + }), + gitlabId: text("gitlabId").references(() => gitlab.gitlabId, { + onDelete: "set null", + }), + bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, { + onDelete: "set null", + }), + serverId: text("serverId").references(() => server.serverId, { + onDelete: "cascade", + }), }); export const applicationsRelations = relations( - applications, - ({ one, many }) => ({ - project: one(projects, { - fields: [applications.projectId], - references: [projects.projectId], - }), - deployments: many(deployments), - customGitSSHKey: one(sshKeys, { - fields: [applications.customGitSSHKeyId], - references: [sshKeys.sshKeyId], - }), - domains: many(domains), - mounts: many(mounts), - redirects: many(redirects), - security: many(security), - ports: many(ports), - registry: one(registry, { - fields: [applications.registryId], - references: [registry.registryId], - }), - github: one(github, { - fields: [applications.githubId], - references: [github.githubId], - }), - gitlab: one(gitlab, { - fields: [applications.gitlabId], - references: [gitlab.gitlabId], - }), - bitbucket: one(bitbucket, { - fields: [applications.bitbucketId], - references: [bitbucket.bitbucketId], - }), - server: one(server, { - fields: [applications.serverId], - references: [server.serverId], - }), - previewDeployments: many(previewDeployments), - }) + applications, + ({ one, many }) => ({ + project: one(projects, { + fields: [applications.projectId], + references: [projects.projectId], + }), + deployments: many(deployments), + customGitSSHKey: one(sshKeys, { + fields: [applications.customGitSSHKeyId], + references: [sshKeys.sshKeyId], + }), + domains: many(domains), + mounts: many(mounts), + redirects: many(redirects), + security: many(security), + ports: many(ports), + registry: one(registry, { + fields: [applications.registryId], + references: [registry.registryId], + }), + github: one(github, { + fields: [applications.githubId], + references: [github.githubId], + }), + gitlab: one(gitlab, { + fields: [applications.gitlabId], + references: [gitlab.gitlabId], + }), + bitbucket: one(bitbucket, { + fields: [applications.bitbucketId], + references: [bitbucket.bitbucketId], + }), + server: one(server, { + fields: [applications.serverId], + references: [server.serverId], + }), + previewDeployments: many(previewDeployments), + }), ); const HealthCheckSwarmSchema = z - .object({ - Test: z.array(z.string()).optional(), - Interval: z.number().optional(), - Timeout: z.number().optional(), - StartPeriod: z.number().optional(), - Retries: z.number().optional(), - }) - .strict(); + .object({ + Test: z.array(z.string()).optional(), + Interval: z.number().optional(), + Timeout: z.number().optional(), + StartPeriod: z.number().optional(), + Retries: z.number().optional(), + }) + .strict(); const RestartPolicySwarmSchema = z - .object({ - Condition: z.string().optional(), - Delay: z.number().optional(), - MaxAttempts: z.number().optional(), - Window: z.number().optional(), - }) - .strict(); + .object({ + Condition: z.string().optional(), + Delay: z.number().optional(), + MaxAttempts: z.number().optional(), + Window: z.number().optional(), + }) + .strict(); const PreferenceSchema = z - .object({ - Spread: z.object({ - SpreadDescriptor: z.string(), - }), - }) - .strict(); + .object({ + Spread: z.object({ + SpreadDescriptor: z.string(), + }), + }) + .strict(); const PlatformSchema = z - .object({ - Architecture: z.string(), - OS: z.string(), - }) - .strict(); + .object({ + Architecture: z.string(), + OS: z.string(), + }) + .strict(); const PlacementSwarmSchema = z - .object({ - Constraints: z.array(z.string()).optional(), - Preferences: z.array(PreferenceSchema).optional(), - MaxReplicas: z.number().optional(), - Platforms: z.array(PlatformSchema).optional(), - }) - .strict(); + .object({ + Constraints: z.array(z.string()).optional(), + Preferences: z.array(PreferenceSchema).optional(), + MaxReplicas: z.number().optional(), + Platforms: z.array(PlatformSchema).optional(), + }) + .strict(); const UpdateConfigSwarmSchema = z - .object({ - Parallelism: z.number(), - Delay: z.number().optional(), - FailureAction: z.string().optional(), - Monitor: z.number().optional(), - MaxFailureRatio: z.number().optional(), - Order: z.string(), - }) - .strict(); + .object({ + Parallelism: z.number(), + Delay: z.number().optional(), + FailureAction: z.string().optional(), + Monitor: z.number().optional(), + MaxFailureRatio: z.number().optional(), + Order: z.string(), + }) + .strict(); const ReplicatedSchema = z - .object({ - Replicas: z.number().optional(), - }) - .strict(); + .object({ + Replicas: z.number().optional(), + }) + .strict(); const ReplicatedJobSchema = z - .object({ - MaxConcurrent: z.number().optional(), - TotalCompletions: z.number().optional(), - }) - .strict(); + .object({ + MaxConcurrent: z.number().optional(), + TotalCompletions: z.number().optional(), + }) + .strict(); const ServiceModeSwarmSchema = z - .object({ - Replicated: ReplicatedSchema.optional(), - Global: z.object({}).optional(), - ReplicatedJob: ReplicatedJobSchema.optional(), - GlobalJob: z.object({}).optional(), - }) - .strict(); + .object({ + Replicated: ReplicatedSchema.optional(), + Global: z.object({}).optional(), + ReplicatedJob: ReplicatedJobSchema.optional(), + GlobalJob: z.object({}).optional(), + }) + .strict(); const NetworkSwarmSchema = z.array( - z - .object({ - Target: z.string().optional(), - Aliases: z.array(z.string()).optional(), - DriverOpts: z.object({}).optional(), - }) - .strict() + z + .object({ + Target: z.string().optional(), + Aliases: z.array(z.string()).optional(), + DriverOpts: z.object({}).optional(), + }) + .strict(), ); const LabelsSwarmSchema = z.record(z.string()); const createSchema = createInsertSchema(applications, { - appName: z.string(), - createdAt: z.string(), - applicationId: z.string(), - autoDeploy: z.boolean(), - env: z.string().optional(), - buildArgs: z.string().optional(), - name: z.string().min(1), - description: z.string().optional(), - memoryReservation: z.number().optional(), - memoryLimit: z.number().optional(), - cpuReservation: z.number().optional(), - cpuLimit: z.number().optional(), - title: z.string().optional(), - enabled: z.boolean().optional(), - subtitle: z.string().optional(), - dockerImage: z.string().optional(), - username: z.string().optional(), - isPreviewDeploymentsActive: z.boolean().optional(), - password: z.string().optional(), - registryUrl: z.string().optional(), - customGitSSHKeyId: z.string().optional(), - repository: z.string().optional(), - dockerfile: z.string().optional(), - branch: z.string().optional(), - customGitBranch: z.string().optional(), - customGitBuildPath: z.string().optional(), - customGitUrl: z.string().optional(), - buildPath: z.string().optional(), - projectId: z.string(), - sourceType: z.enum(["github", "docker", "git"]).optional(), - applicationStatus: z.enum(["idle", "running", "done", "error"]), - buildType: z.enum([ - "dockerfile", - "heroku_buildpacks", - "paketo_buildpacks", - "nixpacks", - "static", - ]), - herokuVersion: z.string().optional(), - publishDirectory: z.string().optional(), - owner: z.string(), - healthCheckSwarm: HealthCheckSwarmSchema.nullable(), - restartPolicySwarm: RestartPolicySwarmSchema.nullable(), - placementSwarm: PlacementSwarmSchema.nullable(), - updateConfigSwarm: UpdateConfigSwarmSchema.nullable(), - rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(), - modeSwarm: ServiceModeSwarmSchema.nullable(), - labelsSwarm: LabelsSwarmSchema.nullable(), - networkSwarm: NetworkSwarmSchema.nullable(), - previewPort: z.number().optional(), - previewEnv: z.string().optional(), - previewBuildArgs: z.string().optional(), - previewWildcard: z.string().optional(), - previewLimit: z.number().optional(), - previewHttps: z.boolean().optional(), - previewPath: z.string().optional(), - previewCertificateType: z.enum(["letsencrypt", "none"]).optional(), + appName: z.string(), + createdAt: z.string(), + applicationId: z.string(), + autoDeploy: z.boolean(), + env: z.string().optional(), + buildArgs: z.string().optional(), + name: z.string().min(1), + description: z.string().optional(), + memoryReservation: z.number().optional(), + memoryLimit: z.number().optional(), + cpuReservation: z.number().optional(), + cpuLimit: z.number().optional(), + title: z.string().optional(), + enabled: z.boolean().optional(), + subtitle: z.string().optional(), + dockerImage: z.string().optional(), + username: z.string().optional(), + isPreviewDeploymentsActive: z.boolean().optional(), + password: z.string().optional(), + registryUrl: z.string().optional(), + customGitSSHKeyId: z.string().optional(), + repository: z.string().optional(), + dockerfile: z.string().optional(), + branch: z.string().optional(), + customGitBranch: z.string().optional(), + customGitBuildPath: z.string().optional(), + customGitUrl: z.string().optional(), + buildPath: z.string().optional(), + projectId: z.string(), + sourceType: z.enum(["github", "docker", "git"]).optional(), + applicationStatus: z.enum(["idle", "running", "done", "error"]), + buildType: z.enum([ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + ]), + herokuVersion: z.string().optional(), + publishDirectory: z.string().optional(), + owner: z.string(), + healthCheckSwarm: HealthCheckSwarmSchema.nullable(), + restartPolicySwarm: RestartPolicySwarmSchema.nullable(), + placementSwarm: PlacementSwarmSchema.nullable(), + updateConfigSwarm: UpdateConfigSwarmSchema.nullable(), + rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(), + modeSwarm: ServiceModeSwarmSchema.nullable(), + labelsSwarm: LabelsSwarmSchema.nullable(), + networkSwarm: NetworkSwarmSchema.nullable(), + previewPort: z.number().optional(), + previewEnv: z.string().optional(), + previewBuildArgs: z.string().optional(), + previewWildcard: z.string().optional(), + previewLimit: z.number().optional(), + previewHttps: z.boolean().optional(), + previewPath: z.string().optional(), + previewCertificateType: z.enum(["letsencrypt", "none"]).optional(), }); export const apiCreateApplication = createSchema.pick({ - name: true, - appName: true, - description: true, - projectId: true, - serverId: true, + name: true, + appName: true, + description: true, + projectId: true, + serverId: true, }); export const apiFindOneApplication = createSchema - .pick({ - applicationId: true, - }) - .required(); + .pick({ + applicationId: true, + }) + .required(); export const apiReloadApplication = createSchema - .pick({ - appName: true, - applicationId: true, - }) - .required(); + .pick({ + appName: true, + applicationId: true, + }) + .required(); export const apiSaveBuildType = createSchema - .pick({ - applicationId: true, - buildType: true, - dockerfile: true, - dockerContextPath: true, - dockerBuildStage: true, - herokuVersion: true, - }) - .required() - .merge(createSchema.pick({ publishDirectory: true })); + .pick({ + applicationId: true, + buildType: true, + dockerfile: true, + dockerContextPath: true, + dockerBuildStage: true, + herokuVersion: true, + }) + .required() + .merge(createSchema.pick({ publishDirectory: true })); export const apiSaveGithubProvider = createSchema - .pick({ - applicationId: true, - repository: true, - branch: true, - owner: true, - buildPath: true, - githubId: true, - }) - .required(); + .pick({ + applicationId: true, + repository: true, + branch: true, + owner: true, + buildPath: true, + githubId: true, + }) + .required(); export const apiSaveGitlabProvider = createSchema - .pick({ - applicationId: true, - gitlabBranch: true, - gitlabBuildPath: true, - gitlabOwner: true, - gitlabRepository: true, - gitlabId: true, - gitlabProjectId: true, - gitlabPathNamespace: true, - }) - .required(); + .pick({ + applicationId: true, + gitlabBranch: true, + gitlabBuildPath: true, + gitlabOwner: true, + gitlabRepository: true, + gitlabId: true, + gitlabProjectId: true, + gitlabPathNamespace: true, + }) + .required(); export const apiSaveBitbucketProvider = createSchema - .pick({ - bitbucketBranch: true, - bitbucketBuildPath: true, - bitbucketOwner: true, - bitbucketRepository: true, - bitbucketId: true, - applicationId: true, - }) - .required(); + .pick({ + bitbucketBranch: true, + bitbucketBuildPath: true, + bitbucketOwner: true, + bitbucketRepository: true, + bitbucketId: true, + applicationId: true, + }) + .required(); export const apiSaveDockerProvider = createSchema - .pick({ - dockerImage: true, - applicationId: true, - username: true, - password: true, - registryUrl: true, - }) - .required(); + .pick({ + dockerImage: true, + applicationId: true, + username: true, + password: true, + registryUrl: true, + }) + .required(); export const apiSaveGitProvider = createSchema - .pick({ - customGitBranch: true, - applicationId: true, - customGitBuildPath: true, - customGitUrl: true, - }) - .required() - .merge( - createSchema.pick({ - customGitSSHKeyId: true, - }) - ); + .pick({ + customGitBranch: true, + applicationId: true, + customGitBuildPath: true, + customGitUrl: true, + }) + .required() + .merge( + createSchema.pick({ + customGitSSHKeyId: true, + }), + ); export const apiSaveEnvironmentVariables = createSchema - .pick({ - applicationId: true, - env: true, - buildArgs: true, - }) - .required(); + .pick({ + applicationId: true, + env: true, + buildArgs: true, + }) + .required(); export const apiFindMonitoringStats = createSchema - .pick({ - appName: true, - }) - .required(); + .pick({ + appName: true, + }) + .required(); export const apiUpdateApplication = createSchema - .partial() - .extend({ - applicationId: z.string().min(1), - }) - .omit({ serverId: true }); + .partial() + .extend({ + applicationId: z.string().min(1), + }) + .omit({ serverId: true }); diff --git a/packages/server/src/db/schema/deployment.ts b/packages/server/src/db/schema/deployment.ts index f79b48ee9..ccaf64661 100644 --- a/packages/server/src/db/schema/deployment.ts +++ b/packages/server/src/db/schema/deployment.ts @@ -11,8 +11,8 @@ import { nanoid } from "nanoid"; import { z } from "zod"; import { applications } from "./application"; import { compose } from "./compose"; -import { server } from "./server"; import { previewDeployments } from "./preview-deployments"; +import { server } from "./server"; export const deploymentStatus = pgEnum("deploymentStatus", [ "running", diff --git a/packages/server/src/db/schema/domain.ts b/packages/server/src/db/schema/domain.ts index f115ce667..5c34d455d 100644 --- a/packages/server/src/db/schema/domain.ts +++ b/packages/server/src/db/schema/domain.ts @@ -14,8 +14,8 @@ import { z } from "zod"; import { domain } from "../validations/domain"; import { applications } from "./application"; import { compose } from "./compose"; -import { certificateType } from "./shared"; import { previewDeployments } from "./preview-deployments"; +import { certificateType } from "./shared"; export const domainType = pgEnum("domainType", [ "compose", diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts index f07a18707..9c7a079c5 100644 --- a/packages/server/src/db/schema/index.ts +++ b/packages/server/src/db/schema/index.ts @@ -29,4 +29,4 @@ export * from "./github"; export * from "./gitlab"; export * from "./server"; export * from "./utils"; -export * from "./preview-deployments"; \ No newline at end of file +export * from "./preview-deployments"; diff --git a/packages/server/src/db/schema/preview-deployments.ts b/packages/server/src/db/schema/preview-deployments.ts index 5d0671e8d..3bdab2c25 100644 --- a/packages/server/src/db/schema/preview-deployments.ts +++ b/packages/server/src/db/schema/preview-deployments.ts @@ -1,13 +1,13 @@ import { relations } from "drizzle-orm"; import { pgTable, text } from "drizzle-orm/pg-core"; -import { nanoid } from "nanoid"; -import { applications } from "./application"; -import { domains } from "./domain"; -import { deployments } from "./deployment"; import { createInsertSchema } from "drizzle-zod"; +import { nanoid } from "nanoid"; import { z } from "zod"; -import { generateAppName } from "./utils"; +import { applications } from "./application"; +import { deployments } from "./deployment"; +import { domains } from "./domain"; import { applicationStatus } from "./shared"; +import { generateAppName } from "./utils"; export const previewDeployments = pgTable("preview_deployments", { previewDeploymentId: text("previewDeploymentId") diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index 797feb383..6fec7a319 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -7,12 +7,12 @@ import { cleanUpSystemPrune, cleanUpUnusedImages, } from "../docker/utils"; +import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; +import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup"; import { runMariadbBackup } from "./mariadb"; import { runMongoBackup } from "./mongo"; import { runMySqlBackup } from "./mysql"; import { runPostgresBackup } from "./postgres"; -import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup"; -import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; export const initCronJobs = async () => { console.log("Setting up cron jobs...."); diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index ce10413ac..df85d98f9 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -2,6 +2,7 @@ import { createWriteStream } from "node:fs"; import { join } from "node:path"; import type { InferResultType } from "@dokploy/server/types/with"; import type { CreateServiceOptions } from "dockerode"; +import { nanoid } from "nanoid"; import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload"; import { calculateResources, @@ -17,7 +18,6 @@ import { buildHeroku, getHerokuCommand } from "./heroku"; import { buildNixpacks, getNixpacksCommand } from "./nixpacks"; import { buildPaketo, getPaketoCommand } from "./paketo"; import { buildStatic, getStaticCommand } from "./static"; -import { nanoid } from "nanoid"; // NIXPACKS codeDirectory = where is the path of the code directory // HEROKU codeDirectory = where is the path of the code directory diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index e8c9e6c23..1c4a38ab4 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -15,528 +15,528 @@ import { spawnAsync } from "../process/spawnAsync"; import { getRemoteDocker } from "../servers/remote-docker"; interface RegistryAuth { - username: string; - password: string; - registryUrl: string; + username: string; + password: string; + registryUrl: string; } export const pullImage = async ( - dockerImage: string, - onData?: (data: any) => void, - authConfig?: Partial + dockerImage: string, + onData?: (data: any) => void, + authConfig?: Partial, ): Promise => { - try { - if (!dockerImage) { - throw new Error("Docker image not found"); - } + try { + if (!dockerImage) { + throw new Error("Docker image not found"); + } - if (authConfig?.username && authConfig?.password) { - await spawnAsync( - "docker", - [ - "login", - authConfig.registryUrl || "", - "-u", - authConfig.username, - "-p", - authConfig.password, - ], - onData - ); - } - await spawnAsync("docker", ["pull", dockerImage], onData); - } catch (error) { - throw error; - } + if (authConfig?.username && authConfig?.password) { + await spawnAsync( + "docker", + [ + "login", + authConfig.registryUrl || "", + "-u", + authConfig.username, + "-p", + authConfig.password, + ], + onData, + ); + } + await spawnAsync("docker", ["pull", dockerImage], onData); + } catch (error) { + throw error; + } }; export const pullRemoteImage = async ( - dockerImage: string, - serverId: string, - onData?: (data: any) => void, - authConfig?: Partial + dockerImage: string, + serverId: string, + onData?: (data: any) => void, + authConfig?: Partial, ): Promise => { - try { - if (!dockerImage) { - throw new Error("Docker image not found"); - } + try { + if (!dockerImage) { + throw new Error("Docker image not found"); + } - const remoteDocker = await getRemoteDocker(serverId); + const remoteDocker = await getRemoteDocker(serverId); - await new Promise((resolve, reject) => { - remoteDocker.pull( - dockerImage, - { authconfig: authConfig }, - (err, stream) => { - if (err) { - reject(err); - return; - } + await new Promise((resolve, reject) => { + remoteDocker.pull( + dockerImage, + { authconfig: authConfig }, + (err, stream) => { + if (err) { + reject(err); + return; + } - remoteDocker.modem.followProgress( - stream as Readable, - (err: Error | null, res) => { - if (!err) { - resolve(res); - } - if (err) { - reject(err); - } - }, - (event) => { - onData?.(event); - } - ); - } - ); - }); - } catch (error) { - throw error; - } + remoteDocker.modem.followProgress( + stream as Readable, + (err: Error | null, res) => { + if (!err) { + resolve(res); + } + if (err) { + reject(err); + } + }, + (event) => { + onData?.(event); + }, + ); + }, + ); + }); + } catch (error) { + throw error; + } }; export const containerExists = async (containerName: string) => { - const container = docker.getContainer(containerName); - try { - await container.inspect(); - return true; - } catch (error) { - return false; - } + const container = docker.getContainer(containerName); + try { + await container.inspect(); + return true; + } catch (error) { + return false; + } }; export const stopService = async (appName: string) => { - try { - await execAsync(`docker service scale ${appName}=0 `); - } catch (error) { - console.error(error); - return error; - } + try { + await execAsync(`docker service scale ${appName}=0 `); + } catch (error) { + console.error(error); + return error; + } }; export const stopServiceRemote = async (serverId: string, appName: string) => { - try { - await execAsyncRemote(serverId, `docker service scale ${appName}=0 `); - } catch (error) { - console.error(error); - return error; - } + try { + await execAsyncRemote(serverId, `docker service scale ${appName}=0 `); + } catch (error) { + console.error(error); + return error; + } }; export const getContainerByName = (name: string): Promise => { - const opts = { - limit: 1, - filters: { - name: [name], - }, - }; - return new Promise((resolve, reject) => { - docker.listContainers(opts, (err, containers) => { - if (err) { - reject(err); - } else if (containers?.length === 0) { - reject(new Error(`No container found with name: ${name}`)); - } else if (containers && containers?.length > 0 && containers[0]) { - resolve(containers[0]); - } - }); - }); + const opts = { + limit: 1, + filters: { + name: [name], + }, + }; + return new Promise((resolve, reject) => { + docker.listContainers(opts, (err, containers) => { + if (err) { + reject(err); + } else if (containers?.length === 0) { + reject(new Error(`No container found with name: ${name}`)); + } else if (containers && containers?.length > 0 && containers[0]) { + resolve(containers[0]); + } + }); + }); }; export const cleanUpUnusedImages = async (serverId?: string) => { - try { - if (serverId) { - await execAsyncRemote(serverId, "docker image prune --all --force"); - } else { - await execAsync("docker image prune --all --force"); - } - } catch (error) { - console.error(error); - throw error; - } + try { + if (serverId) { + await execAsyncRemote(serverId, "docker image prune --all --force"); + } else { + await execAsync("docker image prune --all --force"); + } + } catch (error) { + console.error(error); + throw error; + } }; export const cleanStoppedContainers = async (serverId?: string) => { - try { - if (serverId) { - await execAsyncRemote(serverId, "docker container prune --force"); - } else { - await execAsync("docker container prune --force"); - } - } catch (error) { - console.error(error); - throw error; - } + try { + if (serverId) { + await execAsyncRemote(serverId, "docker container prune --force"); + } else { + await execAsync("docker container prune --force"); + } + } catch (error) { + console.error(error); + throw error; + } }; export const cleanUpUnusedVolumes = async (serverId?: string) => { - try { - if (serverId) { - await execAsyncRemote(serverId, "docker volume prune --all --force"); - } else { - await execAsync("docker volume prune --all --force"); - } - } catch (error) { - console.error(error); - throw error; - } + try { + if (serverId) { + await execAsyncRemote(serverId, "docker volume prune --all --force"); + } else { + await execAsync("docker volume prune --all --force"); + } + } catch (error) { + console.error(error); + throw error; + } }; export const cleanUpInactiveContainers = async () => { - try { - const containers = await docker.listContainers({ all: true }); - const inactiveContainers = containers.filter( - (container) => container.State !== "running" - ); + try { + const containers = await docker.listContainers({ all: true }); + const inactiveContainers = containers.filter( + (container) => container.State !== "running", + ); - for (const container of inactiveContainers) { - await docker.getContainer(container.Id).remove({ force: true }); - console.log(`Cleaning up inactive container: ${container.Id}`); - } - } catch (error) { - console.error("Error cleaning up inactive containers:", error); - throw error; - } + for (const container of inactiveContainers) { + await docker.getContainer(container.Id).remove({ force: true }); + console.log(`Cleaning up inactive container: ${container.Id}`); + } + } catch (error) { + console.error("Error cleaning up inactive containers:", error); + throw error; + } }; export const cleanUpDockerBuilder = async (serverId?: string) => { - if (serverId) { - await execAsyncRemote(serverId, "docker builder prune --all --force"); - } else { - await execAsync("docker builder prune --all --force"); - } + if (serverId) { + await execAsyncRemote(serverId, "docker builder prune --all --force"); + } else { + await execAsync("docker builder prune --all --force"); + } }; export const cleanUpSystemPrune = async (serverId?: string) => { - if (serverId) { - await execAsyncRemote( - serverId, - "docker system prune --all --force --volumes" - ); - } else { - await execAsync("docker system prune --all --force --volumes"); - } + if (serverId) { + await execAsyncRemote( + serverId, + "docker system prune --all --force --volumes", + ); + } else { + await execAsync("docker system prune --all --force --volumes"); + } }; export const startService = async (appName: string) => { - try { - await execAsync(`docker service scale ${appName}=1 `); - } catch (error) { - console.error(error); - throw error; - } + try { + await execAsync(`docker service scale ${appName}=1 `); + } catch (error) { + console.error(error); + throw error; + } }; export const startServiceRemote = async (serverId: string, appName: string) => { - try { - await execAsyncRemote(serverId, `docker service scale ${appName}=1 `); - } catch (error) { - console.error(error); - throw error; - } + try { + await execAsyncRemote(serverId, `docker service scale ${appName}=1 `); + } catch (error) { + console.error(error); + throw error; + } }; export const removeService = async ( - appName: string, - serverId?: string | null, - deleteVolumes = false + appName: string, + serverId?: string | null, + deleteVolumes = false, ) => { - try { - let command: string; + try { + let command: string; - if (deleteVolumes) { - command = `docker service rm --force ${appName}`; - } else { - command = `docker service rm ${appName}`; - } + if (deleteVolumes) { + command = `docker service rm --force ${appName}`; + } else { + command = `docker service rm ${appName}`; + } - if (serverId) { - await execAsyncRemote(serverId, command); - } else { - await execAsync(command); - } - } catch (error) { - return error; - } + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); + } + } catch (error) { + return error; + } }; export const prepareEnvironmentVariables = ( - serviceEnv: string | null, - projectEnv?: string | null + serviceEnv: string | null, + projectEnv?: string | null, ) => { - const projectVars = parse(projectEnv ?? ""); - const serviceVars = parse(serviceEnv ?? ""); + const projectVars = parse(projectEnv ?? ""); + const serviceVars = parse(serviceEnv ?? ""); - const resolvedVars = Object.entries(serviceVars).map(([key, value]) => { - let resolvedValue = value; - if (projectVars) { - resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => { - if (projectVars[ref] !== undefined) { - return projectVars[ref]; - } - throw new Error(`Invalid project environment variable: project.${ref}`); - }); - } - return `${key}=${resolvedValue}`; - }); + const resolvedVars = Object.entries(serviceVars).map(([key, value]) => { + let resolvedValue = value; + if (projectVars) { + resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => { + if (projectVars[ref] !== undefined) { + return projectVars[ref]; + } + throw new Error(`Invalid project environment variable: project.${ref}`); + }); + } + return `${key}=${resolvedValue}`; + }); - return resolvedVars; + return resolvedVars; }; export const prepareBuildArgs = (input: string | null) => { - const pairs = (input ?? "").split("\n"); + const pairs = (input ?? "").split("\n"); - const jsonObject: Record = {}; + const jsonObject: Record = {}; - for (const pair of pairs) { - const [key, value] = pair.split("="); - if (key && value) { - jsonObject[key] = value; - } - } + for (const pair of pairs) { + const [key, value] = pair.split("="); + if (key && value) { + jsonObject[key] = value; + } + } - return jsonObject; + return jsonObject; }; export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => { - if (!mounts || mounts.length === 0) { - return []; - } + if (!mounts || mounts.length === 0) { + return []; + } - return mounts - .filter((mount) => mount.type === "volume") - .map((mount) => ({ - Type: "volume" as const, - Source: mount.volumeName || "", - Target: mount.mountPath, - })); + return mounts + .filter((mount) => mount.type === "volume") + .map((mount) => ({ + Type: "volume" as const, + Source: mount.volumeName || "", + Target: mount.mountPath, + })); }; type Resources = { - memoryLimit: number | null; - memoryReservation: number | null; - cpuLimit: number | null; - cpuReservation: number | null; + memoryLimit: number | null; + memoryReservation: number | null; + cpuLimit: number | null; + cpuReservation: number | null; }; export const calculateResources = ({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, + memoryLimit, + memoryReservation, + cpuLimit, + cpuReservation, }: Resources): ResourceRequirements => { - return { - Limits: { - MemoryBytes: memoryLimit ?? undefined, - NanoCPUs: cpuLimit ?? undefined, - }, - Reservations: { - MemoryBytes: memoryReservation ?? undefined, - NanoCPUs: cpuReservation ?? undefined, - }, - }; + return { + Limits: { + MemoryBytes: memoryLimit ?? undefined, + NanoCPUs: cpuLimit ?? undefined, + }, + Reservations: { + MemoryBytes: memoryReservation ?? undefined, + NanoCPUs: cpuReservation ?? undefined, + }, + }; }; export const generateConfigContainer = (application: ApplicationNested) => { - const { - healthCheckSwarm, - restartPolicySwarm, - placementSwarm, - updateConfigSwarm, - rollbackConfigSwarm, - modeSwarm, - labelsSwarm, - replicas, - mounts, - networkSwarm, - } = application; + const { + healthCheckSwarm, + restartPolicySwarm, + placementSwarm, + updateConfigSwarm, + rollbackConfigSwarm, + modeSwarm, + labelsSwarm, + replicas, + mounts, + networkSwarm, + } = application; - const haveMounts = mounts.length > 0; + const haveMounts = mounts.length > 0; - return { - ...(healthCheckSwarm && { - HealthCheck: healthCheckSwarm, - }), - ...(restartPolicySwarm - ? { - RestartPolicy: restartPolicySwarm, - } - : {}), - ...(placementSwarm - ? { - Placement: placementSwarm, - } - : { - // if app have mounts keep manager as constraint - Placement: { - Constraints: haveMounts ? ["node.role==manager"] : [], - }, - }), - ...(labelsSwarm && { - Labels: labelsSwarm, - }), - ...(modeSwarm - ? { - Mode: modeSwarm, - } - : { - // use replicas value if no modeSwarm provided - Mode: { - Replicated: { - Replicas: replicas, - }, - }, - }), - ...(rollbackConfigSwarm && { - RollbackConfig: rollbackConfigSwarm, - }), - ...(updateConfigSwarm - ? { UpdateConfig: updateConfigSwarm } - : { - // default config if no updateConfigSwarm provided - UpdateConfig: { - Parallelism: 1, - Order: "start-first", - }, - }), - ...(networkSwarm - ? { - Networks: networkSwarm, - } - : { - Networks: [{ Target: "dokploy-network" }], - }), - }; + return { + ...(healthCheckSwarm && { + HealthCheck: healthCheckSwarm, + }), + ...(restartPolicySwarm + ? { + RestartPolicy: restartPolicySwarm, + } + : {}), + ...(placementSwarm + ? { + Placement: placementSwarm, + } + : { + // if app have mounts keep manager as constraint + Placement: { + Constraints: haveMounts ? ["node.role==manager"] : [], + }, + }), + ...(labelsSwarm && { + Labels: labelsSwarm, + }), + ...(modeSwarm + ? { + Mode: modeSwarm, + } + : { + // use replicas value if no modeSwarm provided + Mode: { + Replicated: { + Replicas: replicas, + }, + }, + }), + ...(rollbackConfigSwarm && { + RollbackConfig: rollbackConfigSwarm, + }), + ...(updateConfigSwarm + ? { UpdateConfig: updateConfigSwarm } + : { + // default config if no updateConfigSwarm provided + UpdateConfig: { + Parallelism: 1, + Order: "start-first", + }, + }), + ...(networkSwarm + ? { + Networks: networkSwarm, + } + : { + Networks: [{ Target: "dokploy-network" }], + }), + }; }; export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => { - if (!mounts || mounts.length === 0) { - return []; - } + if (!mounts || mounts.length === 0) { + return []; + } - return mounts - .filter((mount) => mount.type === "bind") - .map((mount) => ({ - Type: "bind" as const, - Source: mount.hostPath || "", - Target: mount.mountPath, - })); + return mounts + .filter((mount) => mount.type === "bind") + .map((mount) => ({ + Type: "bind" as const, + Source: mount.hostPath || "", + Target: mount.mountPath, + })); }; export const generateFileMounts = ( - appName: string, - service: - | ApplicationNested - | MongoNested - | MariadbNested - | MysqlNested - | PostgresNested - | RedisNested + appName: string, + service: + | ApplicationNested + | MongoNested + | MariadbNested + | MysqlNested + | PostgresNested + | RedisNested, ) => { - const { mounts } = service; - const { APPLICATIONS_PATH } = paths(!!service.serverId); - if (!mounts || mounts.length === 0) { - return []; - } + const { mounts } = service; + const { APPLICATIONS_PATH } = paths(!!service.serverId); + if (!mounts || mounts.length === 0) { + return []; + } - return mounts - .filter((mount) => mount.type === "file") - .map((mount) => { - const fileName = mount.filePath; - const absoluteBasePath = path.resolve(APPLICATIONS_PATH); - const directory = path.join(absoluteBasePath, appName, "files"); - const sourcePath = path.join(directory, fileName || ""); - return { - Type: "bind" as const, - Source: sourcePath, - Target: mount.mountPath, - }; - }); + return mounts + .filter((mount) => mount.type === "file") + .map((mount) => { + const fileName = mount.filePath; + const absoluteBasePath = path.resolve(APPLICATIONS_PATH); + const directory = path.join(absoluteBasePath, appName, "files"); + const sourcePath = path.join(directory, fileName || ""); + return { + Type: "bind" as const, + Source: sourcePath, + Target: mount.mountPath, + }; + }); }; export const createFile = async ( - outputPath: string, - filePath: string, - content: string + outputPath: string, + filePath: string, + content: string, ) => { - try { - const fullPath = path.join(outputPath, filePath); - if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { - fs.mkdirSync(fullPath, { recursive: true }); - return; - } + try { + const fullPath = path.join(outputPath, filePath); + if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { + fs.mkdirSync(fullPath, { recursive: true }); + return; + } - const directory = path.dirname(fullPath); - fs.mkdirSync(directory, { recursive: true }); - fs.writeFileSync(fullPath, content || ""); - } catch (error) { - throw error; - } + const directory = path.dirname(fullPath); + fs.mkdirSync(directory, { recursive: true }); + fs.writeFileSync(fullPath, content || ""); + } catch (error) { + throw error; + } }; export const encodeBase64 = (content: string) => - Buffer.from(content, "utf-8").toString("base64"); + Buffer.from(content, "utf-8").toString("base64"); export const getCreateFileCommand = ( - outputPath: string, - filePath: string, - content: string + outputPath: string, + filePath: string, + content: string, ) => { - const fullPath = path.join(outputPath, filePath); - if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { - return `mkdir -p ${fullPath};`; - } + const fullPath = path.join(outputPath, filePath); + if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { + return `mkdir -p ${fullPath};`; + } - const directory = path.dirname(fullPath); - const encodedContent = encodeBase64(content); - return ` + const directory = path.dirname(fullPath); + const encodedContent = encodeBase64(content); + return ` mkdir -p ${directory}; echo "${encodedContent}" | base64 -d > "${fullPath}"; `; }; export const getServiceContainer = async (appName: string) => { - try { - const filter = { - status: ["running"], - label: [`com.docker.swarm.service.name=${appName}`], - }; + try { + const filter = { + status: ["running"], + label: [`com.docker.swarm.service.name=${appName}`], + }; - const containers = await docker.listContainers({ - filters: JSON.stringify(filter), - }); + const containers = await docker.listContainers({ + filters: JSON.stringify(filter), + }); - if (containers.length === 0 || !containers[0]) { - throw new Error(`No container found with name: ${appName}`); - } + if (containers.length === 0 || !containers[0]) { + throw new Error(`No container found with name: ${appName}`); + } - const container = containers[0]; + const container = containers[0]; - return container; - } catch (error) { - throw error; - } + return container; + } catch (error) { + throw error; + } }; export const getRemoteServiceContainer = async ( - serverId: string, - appName: string + serverId: string, + appName: string, ) => { - try { - const filter = { - status: ["running"], - label: [`com.docker.swarm.service.name=${appName}`], - }; - const remoteDocker = await getRemoteDocker(serverId); - const containers = await remoteDocker.listContainers({ - filters: JSON.stringify(filter), - }); + try { + const filter = { + status: ["running"], + label: [`com.docker.swarm.service.name=${appName}`], + }; + const remoteDocker = await getRemoteDocker(serverId); + const containers = await remoteDocker.listContainers({ + filters: JSON.stringify(filter), + }); - if (containers.length === 0 || !containers[0]) { - throw new Error(`No container found with name: ${appName}`); - } + if (containers.length === 0 || !containers[0]) { + throw new Error(`No container found with name: ${appName}`); + } - const container = containers[0]; + const container = containers[0]; - return container; - } catch (error) { - throw error; - } + return container; + } catch (error) { + throw error; + } }; From 8bfe1632fa3cca4ecc5165f1751a1a71594ab3ba Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:32:43 -0600 Subject: [PATCH 12/13] refactor: remove delete volumes --- packages/server/src/utils/docker/utils.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 1c4a38ab4..a14602e93 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -241,13 +241,7 @@ export const removeService = async ( deleteVolumes = false, ) => { try { - let command: string; - - if (deleteVolumes) { - command = `docker service rm --force ${appName}`; - } else { - command = `docker service rm ${appName}`; - } + const command = `docker service rm ${appName}`; if (serverId) { await execAsyncRemote(serverId, command); From 353effd720670a35dea32131c74a61334fdb02ed Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:37:04 -0600 Subject: [PATCH 13/13] refactor: delete log --- packages/server/src/services/compose.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index d38426fd5..ff6603515 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -449,8 +449,6 @@ export const removeCompose = async ( const { COMPOSE_PATH } = paths(!!compose.serverId); const projectPath = join(COMPOSE_PATH, compose.appName); - console.log("API: DELETE VOLUMES=", deleteVolumes); - if (compose.composeType === "stack") { const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`;