diff --git a/apps/dokploy/components/dashboard/application/general/generic/show.tsx b/apps/dokploy/components/dashboard/application/general/generic/show.tsx index 905fe711..ab02f848 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/show.tsx @@ -16,9 +16,11 @@ import { api } from "@/utils/api"; import { GitBranch, Loader2, UploadCloud } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; +import { toast } from "sonner"; import { SaveBitbucketProvider } from "./save-bitbucket-provider"; import { SaveDragNDrop } from "./save-drag-n-drop"; import { SaveGitlabProvider } from "./save-gitlab-provider"; +import { UnauthorizedGitProvider } from "./unauthorized-git-provider"; type TabState = | "github" @@ -43,12 +45,31 @@ export const ShowProviderForm = ({ applicationId }: Props) => { const { data: giteaProviders, isLoading: isLoadingGitea } = api.gitea.giteaProviders.useQuery(); - const { data: application } = api.application.one.useQuery({ applicationId }); + const { data: application, refetch } = api.application.one.useQuery({ + applicationId, + }); + const { mutateAsync: disconnectGitProvider } = + api.application.disconnectGitProvider.useMutation(); + const [tab, setSab] = useState(application?.sourceType || "github"); const isLoading = isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea; + const handleDisconnect = async () => { + try { + await disconnectGitProvider({ applicationId }); + toast.success("Repository disconnected successfully"); + await refetch(); + } catch (error) { + toast.error( + `Failed to disconnect repository: ${ + error instanceof Error ? error.message : "Unknown error" + }`, + ); + } + }; + if (isLoading) { return ( @@ -77,6 +98,38 @@ export const ShowProviderForm = ({ applicationId }: Props) => { ); } + // Check if user doesn't have access to the current git provider + if ( + application && + !application.hasGitProviderAccess && + application.sourceType !== "docker" && + application.sourceType !== "drop" + ) { + return ( + + + +
+ Provider +

+ Repository connection through unauthorized provider +

+
+
+ +
+
+
+ + + +
+ ); + } + return ( diff --git a/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx new file mode 100644 index 00000000..cf7d41e2 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx @@ -0,0 +1,137 @@ +import { + BitbucketIcon, + GitIcon, + GiteaIcon, + GithubIcon, + GitlabIcon, +} from "@/components/icons/data-tools-icons"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { AlertCircle, GitBranch, Unlink } from "lucide-react"; + +export const UnauthorizedGitProvider = ({ + application, + onDisconnect, +}: { + application: any; + onDisconnect: () => void; +}) => { + const getProviderIcon = (sourceType: string) => { + switch (sourceType) { + case "github": + return ; + case "gitlab": + return ; + case "bitbucket": + return ; + case "gitea": + return ; + case "git": + return ; + default: + return ; + } + }; + + const getRepositoryInfo = () => { + switch (application.sourceType) { + case "github": + return { + repo: application.repository, + branch: application.branch, + owner: application.owner, + }; + case "gitlab": + return { + repo: application.gitlabRepository, + branch: application.gitlabBranch, + owner: application.gitlabOwner, + }; + case "bitbucket": + return { + repo: application.bitbucketRepository, + branch: application.bitbucketBranch, + owner: application.bitbucketOwner, + }; + case "gitea": + return { + repo: application.giteaRepository, + branch: application.giteaBranch, + owner: application.giteaOwner, + }; + case "git": + return { + repo: application.customGitUrl, + branch: application.customGitBranch, + owner: null, + }; + default: + return { repo: null, branch: null, owner: null }; + } + }; + + const { repo, branch, owner } = getRepositoryInfo(); + + return ( +
+ + + + This application is connected to a {application.sourceType} repository + through a git provider that you don't have access to. You can see + basic repository information below, but cannot modify the + configuration. + + + + + + + {getProviderIcon(application.sourceType)} + + {application.sourceType} Repository + + + + + {owner && ( +
+ + Owner: + +

{owner}

+
+ )} + {repo && ( +
+ + Repository: + +

{repo}

+
+ )} + {branch && ( +
+ + Branch: + +

{branch}

+
+ )} + +
+ +

+ Disconnecting will allow you to configure a new repository with + your own git providers. +

+
+
+
+
+ ); +}; diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 6e304636..dfdf71d0 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -31,6 +31,7 @@ import { createApplication, deleteAllMiddlewares, findApplicationById, + findGitProviderById, findProjectById, getApplicationStats, mechanizeDockerContainer, @@ -126,7 +127,45 @@ export const applicationRouter = createTRPCRouter({ message: "You are not authorized to access this application", }); } - return application; + + let hasGitProviderAccess = true; + let unauthorizedProvider: string | null = null; + + const getGitProviderId = () => { + switch (application.sourceType) { + case "github": + return application.github?.gitProviderId; + case "gitlab": + return application.gitlab?.gitProviderId; + case "bitbucket": + return application.bitbucket?.gitProviderId; + case "gitea": + return application.gitea?.gitProviderId; + default: + return null; + } + }; + + const gitProviderId = getGitProviderId(); + + if (gitProviderId) { + try { + const gitProvider = await findGitProviderById(gitProviderId); + if (gitProvider.userId !== ctx.session.userId) { + hasGitProviderAccess = false; + unauthorizedProvider = application.sourceType; + } + } catch { + hasGitProviderAccess = false; + unauthorizedProvider = application.sourceType; + } + } + + return { + ...application, + hasGitProviderAccess, + unauthorizedProvider, + }; }), reload: protectedProcedure @@ -488,6 +527,67 @@ export const applicationRouter = createTRPCRouter({ enableSubmodules: input.enableSubmodules, }); + return true; + }), + disconnectGitProvider: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if ( + application.project.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to disconnect this git provider", + }); + } + + // Reset all git provider related fields + await updateApplication(input.applicationId, { + // GitHub fields + repository: null, + branch: null, + owner: null, + buildPath: "/", + githubId: null, + triggerType: "push", + + // GitLab fields + gitlabRepository: null, + gitlabOwner: null, + gitlabBranch: null, + gitlabBuildPath: null, + gitlabId: null, + gitlabProjectId: null, + gitlabPathNamespace: null, + + // Bitbucket fields + bitbucketRepository: null, + bitbucketOwner: null, + bitbucketBranch: null, + bitbucketBuildPath: null, + bitbucketId: null, + + // Gitea fields + giteaRepository: null, + giteaOwner: null, + giteaBranch: null, + giteaBuildPath: null, + giteaId: null, + + // Custom Git fields + customGitBranch: null, + customGitBuildPath: null, + customGitUrl: null, + customGitSSHKeyId: null, + + // Common fields + sourceType: "github", // Reset to default + applicationStatus: "idle", + watchPaths: null, + enableSubmodules: false, + }); + return true; }), markRunning: protectedProcedure