From eeafc5b820979eb1d16a8e4cad5c9659b08a1446 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 22 Apr 2026 20:50:55 +0100 Subject: [PATCH 1/4] feat: show modal when switching to incompatible providers This commit shows a modal after validating the form data with Zod to confirm that the user intends to switch providers after being told that the preview deployments feature will disabled, as it's only compatible with one of the available application source types. --- .../generic/confirmable-provider-save.tsx | 93 +++++++++++++++++++ .../generic/save-bitbucket-provider.tsx | 13 ++- .../general/generic/save-docker-provider.tsx | 16 ++-- .../general/generic/save-drag-n-drop.tsx | 15 ++- .../general/generic/save-git-provider.tsx | 13 ++- .../general/generic/save-gitea-provider.tsx | 13 ++- .../general/generic/save-gitlab-provider.tsx | 13 ++- .../dokploy/server/api/routers/application.ts | 6 ++ packages/server/src/lib/auth.ts | 2 +- 9 files changed, 158 insertions(+), 26 deletions(-) create mode 100644 apps/dokploy/components/dashboard/application/general/generic/confirmable-provider-save.tsx diff --git a/apps/dokploy/components/dashboard/application/general/generic/confirmable-provider-save.tsx b/apps/dokploy/components/dashboard/application/general/generic/confirmable-provider-save.tsx new file mode 100644 index 000000000..ab048af7f --- /dev/null +++ b/apps/dokploy/components/dashboard/application/general/generic/confirmable-provider-save.tsx @@ -0,0 +1,93 @@ +import { type ReactNode, useState } from "react"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; + +interface Props { + onConfirm: () => void; + needsConfirmation: boolean; + onValidate?: () => Promise | boolean; + isLoading?: boolean; + disabled?: boolean; + children?: ReactNode; +} + +export const ConfirmableProviderSave = ({ + onConfirm, + needsConfirmation, + onValidate, + isLoading, + disabled, + children = "Save", +}: Props) => { + const [open, setOpen] = useState(false); + + if (!needsConfirmation) { + return ( + + ); + } + + const handleClick = async () => { + if (onValidate) { + const valid = await onValidate(); + if (!valid) return; + } + setOpen(true); + }; + + return ( + <> + + + + + + Switch provider and disable preview deployments? + + + Preview deployments only run on the GitHub provider. Switching + will disable preview deployments for this application; existing + previews will stop receiving updates. + + + + Cancel + { + setOpen(false); + onConfirm(); + }} + > + Confirm + + + + + + ); +}; diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx index a4fab46d9..365da5549 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx @@ -47,6 +47,7 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; +import { ConfirmableProviderSave } from "./confirmable-provider-save"; const BitbucketProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), @@ -500,13 +501,17 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { />
- +
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx index 078271bca..f9a627ecc 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx @@ -3,7 +3,6 @@ import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Button } from "@/components/ui/button"; import { Form, FormControl, @@ -14,6 +13,7 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; +import { ConfirmableProviderSave } from "./confirmable-provider-save"; const DockerProviderSchema = z.object({ dockerImage: z.string().min(1, { @@ -149,13 +149,17 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
- + Save +
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx index 583b865c5..204367e6e 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx @@ -16,6 +16,7 @@ import { import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { type UploadFile, uploadFileSchema } from "@/utils/schema"; +import { ConfirmableProviderSave } from "./confirmable-provider-save"; interface Props { applicationId: string; @@ -126,14 +127,18 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
- + Deploy +
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx index cb3190e0b..c807cf0ce 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx @@ -35,6 +35,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; +import { ConfirmableProviderSave } from "./confirmable-provider-save"; const GitProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), @@ -315,9 +316,17 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
- +
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx index 02cae2c4a..120b8dc96 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx @@ -47,6 +47,7 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; +import { ConfirmableProviderSave } from "./confirmable-provider-save"; interface GiteaRepository { name: string; @@ -527,13 +528,17 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { />
- +
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx index b49a1658f..05057cde9 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx @@ -47,6 +47,7 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; +import { ConfirmableProviderSave } from "./confirmable-provider-save"; const GitlabProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), @@ -520,13 +521,17 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { />
- +
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 228c98656..13e2e29d8 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -457,6 +457,7 @@ export const applicationRouter = createTRPCRouter({ gitlabPathNamespace: input.gitlabPathNamespace, watchPaths: input.watchPaths, enableSubmodules: input.enableSubmodules, + isPreviewDeploymentsActive: false, }); const application = await findApplicationById(input.applicationId); await audit(ctx, { @@ -484,6 +485,7 @@ export const applicationRouter = createTRPCRouter({ bitbucketId: input.bitbucketId, watchPaths: input.watchPaths, enableSubmodules: input.enableSubmodules, + isPreviewDeploymentsActive: false, }); const application = await findApplicationById(input.applicationId); await audit(ctx, { @@ -510,6 +512,7 @@ export const applicationRouter = createTRPCRouter({ giteaId: input.giteaId, watchPaths: input.watchPaths, enableSubmodules: input.enableSubmodules, + isPreviewDeploymentsActive: false, }); const application = await findApplicationById(input.applicationId); await audit(ctx, { @@ -533,6 +536,7 @@ export const applicationRouter = createTRPCRouter({ sourceType: "docker", applicationStatus: "idle", registryUrl: input.registryUrl, + isPreviewDeploymentsActive: false, }); const application = await findApplicationById(input.applicationId); await audit(ctx, { @@ -558,6 +562,7 @@ export const applicationRouter = createTRPCRouter({ applicationStatus: "idle", watchPaths: input.watchPaths, enableSubmodules: input.enableSubmodules, + isPreviewDeploymentsActive: false, }); const application = await findApplicationById(input.applicationId); await audit(ctx, { @@ -813,6 +818,7 @@ export const applicationRouter = createTRPCRouter({ await updateApplication(applicationId, { sourceType: "drop", dropBuildPath: dropBuildPath || "", + isPreviewDeploymentsActive: false, }); await unzipDrop(zipFile, app); diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index 65dd1b01d..5d981cb5f 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -92,7 +92,7 @@ const { handler, api } = betterAuth({ process.env.NODE_ENV === "development" ? [ "http://localhost:3000", - "https://absolutely-handy-falcon.ngrok-free.app", + "https://select-stuffed-andrea-timeline.trycloudflare.com", ] : []; return [ From 6e36bedcbd7c1229c5ff89c43664f0dd68784b52 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 22 Apr 2026 20:51:24 +0100 Subject: [PATCH 2/4] feat: hide preview deployments tab on non-github providers Hides the "Preview Deployments" tab when the feature is not available to prevent confusion. --- .../services/application/[applicationId].tsx | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx index 7cb888171..763be7e95 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx @@ -245,11 +245,12 @@ const Service = ( Deployments )} - {permissions?.deployment.read && ( - - Preview Deployments - - )} + {permissions?.deployment.read && + data?.sourceType === "github" && ( + + Preview Deployments + + )} {permissions?.schedule.read && ( Schedules )} @@ -385,13 +386,19 @@ const Service = ( )} - {permissions?.deployment.read && ( - -
- -
-
- )} + {permissions?.deployment.read && + data?.sourceType === "github" && ( + +
+ +
+
+ )} {permissions?.domain.read && (
From a5ab5074e37d4810461b52c6fa406d653af0c179 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 22 Apr 2026 21:00:14 +0100 Subject: [PATCH 3/4] rollback: remove my trycloudflare url was NOT meant to commit that, my fault --- packages/server/src/lib/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index 5d981cb5f..65dd1b01d 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -92,7 +92,7 @@ const { handler, api } = betterAuth({ process.env.NODE_ENV === "development" ? [ "http://localhost:3000", - "https://select-stuffed-andrea-timeline.trycloudflare.com", + "https://absolutely-handy-falcon.ngrok-free.app", ] : []; return [ From d9039c822c5648f90a45119a8b08488d6dc72abf Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 23 Apr 2026 12:10:18 +0100 Subject: [PATCH 4/4] fix: use application document invalidate instead --- .../application/general/generic/save-bitbucket-provider.tsx | 5 +++-- .../application/general/generic/save-docker-provider.tsx | 5 +++-- .../application/general/generic/save-drag-n-drop.tsx | 5 +++-- .../application/general/generic/save-git-provider.tsx | 5 +++-- .../application/general/generic/save-gitea-provider.tsx | 5 +++-- .../application/general/generic/save-github-provider.tsx | 5 +++-- .../application/general/generic/save-gitlab-provider.tsx | 5 +++-- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx index 365da5549..be8f272ed 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx @@ -71,9 +71,10 @@ interface Props { } export const SaveBitbucketProvider = ({ applicationId }: Props) => { + const utils = api.useUtils(); const { data: bitbucketProviders } = api.bitbucket.bitbucketProviders.useQuery(); - const { data, refetch } = api.application.one.useQuery({ applicationId }); + const { data } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isPending: isSavingBitbucketProvider } = api.application.saveBitbucketProvider.useMutation(); @@ -159,7 +160,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { }) .then(async () => { toast.success("Service Provider Saved"); - await refetch(); + await utils.application.one.invalidate({ applicationId }); }) .catch(() => { toast.error("Error saving the Bitbucket provider"); diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx index f9a627ecc..446b62fe2 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx @@ -31,7 +31,8 @@ interface Props { } export const SaveDockerProvider = ({ applicationId }: Props) => { - const { data, refetch } = api.application.one.useQuery({ applicationId }); + const utils = api.useUtils(); + const { data } = api.application.one.useQuery({ applicationId }); const { mutateAsync } = api.application.saveDockerProvider.useMutation(); const form = useForm({ @@ -65,7 +66,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => { }) .then(async () => { toast.success("Docker Provider Saved"); - await refetch(); + await utils.application.one.invalidate({ applicationId }); }) .catch(() => { toast.error("Error saving the Docker provider"); diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx index 204367e6e..6eccf2342 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx @@ -23,7 +23,8 @@ interface Props { } export const SaveDragNDrop = ({ applicationId }: Props) => { - const { data, refetch } = api.application.one.useQuery({ applicationId }); + const utils = api.useUtils(); + const { data } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isPending } = api.application.dropDeployment.useMutation(); @@ -54,7 +55,7 @@ export const SaveDragNDrop = ({ applicationId }: Props) => { await mutateAsync(formData) .then(async () => { toast.success("Deployment saved"); - await refetch(); + await utils.application.one.invalidate({ applicationId }); }) .catch(() => { toast.error("Error saving the deployment"); diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx index c807cf0ce..8c5c74dc3 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx @@ -55,7 +55,8 @@ interface Props { } export const SaveGitProvider = ({ applicationId }: Props) => { - const { data, refetch } = api.application.one.useQuery({ applicationId }); + const utils = api.useUtils(); + const { data } = api.application.one.useQuery({ applicationId }); const { data: sshKeys } = api.sshKey.allForApps.useQuery(); const router = useRouter(); @@ -99,7 +100,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => { }) .then(async () => { toast.success("Git Provider Saved"); - await refetch(); + await utils.application.one.invalidate({ applicationId }); }) .catch(() => { toast.error("Error saving the Git provider"); diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx index 120b8dc96..8b71edb86 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx @@ -86,8 +86,9 @@ interface Props { } export const SaveGiteaProvider = ({ applicationId }: Props) => { + const utils = api.useUtils(); const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); - const { data, refetch } = api.application.one.useQuery({ applicationId }); + const { data } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isPending: isSavingGiteaProvider } = api.application.saveGiteaProvider.useMutation(); @@ -174,7 +175,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { }) .then(async () => { toast.success("Service Provider Saved"); - await refetch(); + await utils.application.one.invalidate({ applicationId }); }) .catch(() => { toast.error("Error saving the Gitea provider"); diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx index 6bce2d243..44aadbe3a 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx @@ -69,8 +69,9 @@ interface Props { } export const SaveGithubProvider = ({ applicationId }: Props) => { + const utils = api.useUtils(); const { data: githubProviders } = api.github.githubProviders.useQuery(); - const { data, refetch } = api.application.one.useQuery({ applicationId }); + const { data } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isPending: isSavingGithubProvider } = api.application.saveGithubProvider.useMutation(); @@ -150,7 +151,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { }) .then(async () => { toast.success("Service Provider Saved"); - await refetch(); + await utils.application.one.invalidate({ applicationId }); }) .catch(() => { toast.error("Error saving the github provider"); diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx index 05057cde9..b00bcffe3 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx @@ -72,8 +72,9 @@ interface Props { } export const SaveGitlabProvider = ({ applicationId }: Props) => { + const utils = api.useUtils(); const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); - const { data, refetch } = api.application.one.useQuery({ applicationId }); + const { data } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isPending: isSavingGitlabProvider } = api.application.saveGitlabProvider.useMutation(); @@ -169,7 +170,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { }) .then(async () => { toast.success("Service Provider Saved"); - await refetch(); + await utils.application.one.invalidate({ applicationId }); }) .catch(() => { toast.error("Error saving the gitlab provider");