diff --git a/apps/dokploy/components/dashboard/application/general/generic/show.tsx b/apps/dokploy/components/dashboard/application/general/generic/show.tsx index ab02f8485..13d3a6d8f 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/show.tsx @@ -122,7 +122,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => { 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 index cad939b07..4dbdf7a69 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx @@ -9,15 +9,17 @@ import { DialogAction } from "@/components/shared/dialog-action"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { RouterOutputs } from "@/utils/api"; import { AlertCircle, GitBranch, Unlink } from "lucide-react"; -export const UnauthorizedGitProvider = ({ - application, - onDisconnect, -}: { - application: any; +interface Props { + service: + | RouterOutputs["application"]["one"] + | RouterOutputs["compose"]["one"]; onDisconnect: () => void; -}) => { +} + +export const UnauthorizedGitProvider = ({ service, onDisconnect }: Props) => { const getProviderIcon = (sourceType: string) => { switch (sourceType) { case "github": @@ -36,35 +38,35 @@ export const UnauthorizedGitProvider = ({ }; const getRepositoryInfo = () => { - switch (application.sourceType) { + switch (service.sourceType) { case "github": return { - repo: application.repository, - branch: application.branch, - owner: application.owner, + repo: service.repository, + branch: service.branch, + owner: service.owner, }; case "gitlab": return { - repo: application.gitlabRepository, - branch: application.gitlabBranch, - owner: application.gitlabOwner, + repo: service.gitlabRepository, + branch: service.gitlabBranch, + owner: service.gitlabOwner, }; case "bitbucket": return { - repo: application.bitbucketRepository, - branch: application.bitbucketBranch, - owner: application.bitbucketOwner, + repo: service.bitbucketRepository, + branch: service.bitbucketBranch, + owner: service.bitbucketOwner, }; case "gitea": return { - repo: application.giteaRepository, - branch: application.giteaBranch, - owner: application.giteaOwner, + repo: service.giteaRepository, + branch: service.giteaBranch, + owner: service.giteaOwner, }; case "git": return { - repo: application.customGitUrl, - branch: application.customGitBranch, + repo: service.customGitUrl, + branch: service.customGitBranch, owner: null, }; default: @@ -79,7 +81,7 @@ export const UnauthorizedGitProvider = ({ - This application is connected to a {application.sourceType} repository + This application is connected to a {service.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. @@ -89,9 +91,9 @@ export const UnauthorizedGitProvider = ({ - {getProviderIcon(application.sourceType)} + {getProviderIcon(service.sourceType)} - {application.sourceType} Repository + {service.sourceType} Repository diff --git a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx index afdfbfba4..cd510ad69 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx @@ -18,6 +18,8 @@ import { SaveGitProviderCompose } from "./save-git-provider-compose"; import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose"; import { SaveGithubProviderCompose } from "./save-github-provider-compose"; import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose"; +import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider"; +import { toast } from "sonner"; type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea"; interface Props { @@ -34,12 +36,29 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { const { data: giteaProviders, isLoading: isLoadingGitea } = api.gitea.giteaProviders.useQuery(); - const { data: compose } = api.compose.one.useQuery({ composeId }); + const { mutateAsync: disconnectGitProvider } = + api.compose.disconnectGitProvider.useMutation(); + + const { data: compose, refetch } = api.compose.one.useQuery({ composeId }); const [tab, setSab] = useState(compose?.sourceType || "github"); const isLoading = isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea; + const handleDisconnect = async () => { + try { + await disconnectGitProvider({ composeId }); + 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 ( @@ -68,6 +87,37 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { ); } + // Check if user doesn't have access to the current git provider + if ( + compose && + !compose.hasGitProviderAccess && + compose.sourceType !== "raw" + ) { + return ( + + + +
+ Provider +

+ Repository connection through unauthorized provider +

+
+
+ +
+
+
+ + + +
+ ); + } + return ( diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 7d5397d7d..1b9ebd6a1 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -28,6 +28,7 @@ import { deleteMount, findComposeById, findDomainsByComposeId, + findGitProviderById, findProjectById, findServerById, findUserById, @@ -119,7 +120,45 @@ export const composeRouter = createTRPCRouter({ message: "You are not authorized to access this compose", }); } - return compose; + + let hasGitProviderAccess = true; + let unauthorizedProvider: string | null = null; + + const getGitProviderId = () => { + switch (compose.sourceType) { + case "github": + return compose.github?.gitProviderId; + case "gitlab": + return compose.gitlab?.gitProviderId; + case "bitbucket": + return compose.bitbucket?.gitProviderId; + case "gitea": + return compose.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 = compose.sourceType; + } + } catch { + hasGitProviderAccess = false; + unauthorizedProvider = compose.sourceType; + } + } + + return { + ...compose, + hasGitProviderAccess, + unauthorizedProvider, + }; }), update: protectedProcedure @@ -526,6 +565,61 @@ export const composeRouter = createTRPCRouter({ const uniqueTags = _.uniq(allTags); return uniqueTags; }), + disconnectGitProvider: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.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 updateCompose(input.composeId, { + // GitHub fields + repository: null, + branch: null, + owner: null, + composePath: undefined, + githubId: null, + triggerType: "push", + + // GitLab fields + gitlabRepository: null, + gitlabOwner: null, + gitlabBranch: null, + gitlabId: null, + gitlabProjectId: null, + gitlabPathNamespace: null, + + // Bitbucket fields + bitbucketRepository: null, + bitbucketOwner: null, + bitbucketBranch: null, + bitbucketId: null, + + // Gitea fields + giteaRepository: null, + giteaOwner: null, + giteaBranch: null, + giteaId: null, + + // Custom Git fields + customGitBranch: null, + customGitUrl: null, + customGitSSHKeyId: null, + + // Common fields + sourceType: "github", // Reset to default + composeStatus: "idle", + watchPaths: null, + enableSubmodules: false, + }); + + return true; + }), move: protectedProcedure .input(