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 07ee5b0e4..ee122827f 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 @@ -48,6 +48,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("/"), @@ -74,9 +75,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(); @@ -162,7 +164,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"); @@ -519,13 +521,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..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 @@ -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, { @@ -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"); @@ -149,13 +150,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..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 @@ -16,13 +16,15 @@ 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; } 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(); @@ -53,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"); @@ -126,14 +128,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 99b997d50..35f391fff 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 @@ -36,6 +36,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("/"), @@ -58,7 +59,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(); @@ -102,7 +104,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"); @@ -316,9 +318,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 8da817c80..e7edfe562 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 @@ -48,6 +48,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; @@ -89,8 +90,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(); @@ -177,7 +179,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"); @@ -548,13 +550,17 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { />
- +
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 61b425c1e..5956350bf 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 @@ -74,8 +74,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(); @@ -160,7 +161,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 1711aa63c..c0fac82d2 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 @@ -48,6 +48,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("/"), @@ -75,8 +76,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(); @@ -172,7 +174,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"); @@ -538,13 +540,17 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { />
- +
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 cf04a998f..30d33a735 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 @@ -257,11 +257,12 @@ const Service = ( Deployments )} - {permissions?.deployment.read && ( - - Preview Deployments - - )} + {permissions?.deployment.read && + data?.sourceType === "github" && ( + + Preview Deployments + + )} {permissions?.schedule.read && ( Schedules )} @@ -398,13 +399,19 @@ const Service = ( )} - {permissions?.deployment.read && ( - -
- -
-
- )} + {permissions?.deployment.read && + data?.sourceType === "github" && ( + +
+ +
+
+ )} {permissions?.domain.read && (
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 01a9534d9..bfb383ca2 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -578,6 +578,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, { @@ -605,6 +606,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, { @@ -631,6 +633,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, { @@ -654,6 +657,7 @@ export const applicationRouter = createTRPCRouter({ sourceType: "docker", applicationStatus: "idle", registryUrl: input.registryUrl, + isPreviewDeploymentsActive: false, }); const application = await findApplicationById(input.applicationId); await audit(ctx, { @@ -679,6 +683,7 @@ export const applicationRouter = createTRPCRouter({ applicationStatus: "idle", watchPaths: input.watchPaths, enableSubmodules: input.enableSubmodules, + isPreviewDeploymentsActive: false, }); const application = await findApplicationById(input.applicationId); await audit(ctx, { @@ -934,6 +939,7 @@ export const applicationRouter = createTRPCRouter({ await updateApplication(applicationId, { sourceType: "drop", dropBuildPath: dropBuildPath || "", + isPreviewDeploymentsActive: false, }); await unzipDrop(zipFile, app);