From 1fe12ba93e2eff1e93c99a18ec0e3005407a5511 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:38:10 -0600 Subject: [PATCH] feat(isolation): add preview functionality for isolated deployment with loading state and dialog --- .../compose/advanced/add-isolation.tsx | 101 +++++++++++++----- .../general/show-converted-compose.tsx | 2 +- packages/server/src/utils/docker/collision.ts | 40 +++++-- 3 files changed, 107 insertions(+), 36 deletions(-) diff --git a/apps/dokploy/components/dashboard/compose/advanced/add-isolation.tsx b/apps/dokploy/components/dashboard/compose/advanced/add-isolation.tsx index 55cb0d906..cea716858 100644 --- a/apps/dokploy/components/dashboard/compose/advanced/add-isolation.tsx +++ b/apps/dokploy/components/dashboard/compose/advanced/add-isolation.tsx @@ -1,5 +1,5 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import { AlertTriangle } from "lucide-react"; +import { AlertTriangle, Loader2 } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -14,6 +14,13 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { Form, FormControl, @@ -22,7 +29,7 @@ import { FormItem, FormLabel, } from "@/components/ui/form"; -import { Label } from "@/components/ui/label"; + import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; @@ -40,9 +47,12 @@ type IsolatedSchema = z.infer; export const IsolatedDeploymentTab = ({ composeId }: Props) => { const utils = api.useUtils(); const [compose, setCompose] = useState(""); + const [isPreviewLoading, setIsPreviewLoading] = useState(false); const { mutateAsync, error, isError } = api.compose.isolatedDeployment.useMutation(); + const [isOpenPreview, setIsOpenPreview] = useState(false); + const { mutateAsync: updateCompose } = api.compose.update.useMutation(); const { data, refetch } = api.compose.one.useQuery( @@ -58,7 +68,6 @@ export const IsolatedDeploymentTab = ({ composeId }: Props) => { }); useEffect(() => { - randomizeCompose(); if (data) { form.reset({ isolatedDeployment: data?.isolatedDeployment || false, @@ -72,7 +81,6 @@ export const IsolatedDeploymentTab = ({ composeId }: Props) => { isolatedDeployment: formData?.isolatedDeployment || false, }) .then(async (_data) => { - await randomizeCompose(); await refetch(); toast.success("Compose updated"); }) @@ -81,26 +89,31 @@ export const IsolatedDeploymentTab = ({ composeId }: Props) => { }); }; - const randomizeCompose = async () => { - await mutateAsync({ - composeId, - suffix: data?.appName || "", - }).then(async (data) => { - await utils.project.all.invalidate(); - setCompose(data); - }); + const generatePreview = async () => { + setIsOpenPreview(true); + setIsPreviewLoading(true); + try { + await mutateAsync({ + composeId, + suffix: data?.appName || "", + }).then(async (data) => { + await utils.project.all.invalidate(); + setCompose(data); + }); + } catch { + toast.error("Error generating preview"); + setIsOpenPreview(false); + } finally { + setIsPreviewLoading(false); + } }; return ( - Enable Isolated Deployment + Enable Isolated Deployment Configure isolated deployment to the compose file. - - - -
This feature creates an isolated environment for your deployment @@ -122,6 +135,10 @@ export const IsolatedDeploymentTab = ({ composeId }: Props) => {
+ + + +
{isError && {error?.message}}
{
-
- -
-									
-								
+ +
+ + + + + Isolated Deployment Preview + + Preview of the compose file with isolated deployment + configuration + + +
+ {isPreviewLoading ? ( +
+ +

+ Generating compose preview... +

+
+ ) : ( +
+													
+												
+ )} +
+
+
diff --git a/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx index 253a5fde3..fac6c2a34 100644 --- a/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx @@ -62,7 +62,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => { {isError && {error?.message}} - + Preview your docker-compose file with added domains. Note: At least one domain must be specified for this conversion to take effect. diff --git a/packages/server/src/utils/docker/collision.ts b/packages/server/src/utils/docker/collision.ts index 9d399dc0d..de6d9bbb3 100644 --- a/packages/server/src/utils/docker/collision.ts +++ b/packages/server/src/utils/docker/collision.ts @@ -1,8 +1,14 @@ import { findComposeById } from "@dokploy/server/services/compose"; -import { dump, load } from "js-yaml"; +import { dump } from "js-yaml"; import { addAppNameToAllServiceNames } from "./collision/root-network"; import { generateRandomHash } from "./compose"; import { addSuffixToAllVolumes } from "./compose/volume"; +import { + cloneCompose, + cloneComposeRemote, + loadDockerCompose, + loadDockerComposeRemote, +} from "./domain"; import type { ComposeSpecification } from "./types"; export const addAppNameToPreventCollision = ( @@ -24,16 +30,34 @@ export const randomizeIsolatedDeploymentComposeFile = async ( suffix?: string, ) => { const compose = await findComposeById(composeId); - const composeFile = compose.composeFile; - const composeData = load(composeFile) as ComposeSpecification; + + if (compose.serverId) { + await cloneComposeRemote(compose); + } else { + await cloneCompose(compose); + } + + let composeData: ComposeSpecification | null; + + if (compose.serverId) { + composeData = await loadDockerComposeRemote(compose); + } else { + composeData = await loadDockerCompose(compose); + } + + if (!composeData) { + throw new Error("Compose data not found"); + } const randomSuffix = suffix || compose.appName || generateRandomHash(); - const newComposeFile = addAppNameToPreventCollision( - composeData, - randomSuffix, - compose.isolatedDeploymentsVolume, - ); + const newComposeFile = compose.isolatedDeployment + ? addAppNameToPreventCollision( + composeData, + randomSuffix, + compose.isolatedDeploymentsVolume, + ) + : composeData; return dump(newComposeFile); };