diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts index 74c8036557..7dc9e560cf 100644 --- a/apps/dokploy/__test__/drop/drop.test.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.test.ts @@ -27,6 +27,11 @@ if (typeof window === "undefined") { const baseApp: ApplicationNested = { applicationId: "", herokuVersion: "", + giteaBranch: "", + giteaBuildPath: "", + giteaId: "", + giteaOwner: "", + giteaRepository: "", cleanCache: false, watchPaths: [], applicationStatus: "done", diff --git a/apps/dokploy/__test__/templates/config.template.test.ts b/apps/dokploy/__test__/templates/config.template.test.ts index 902e3163b2..9516e9d1f8 100644 --- a/apps/dokploy/__test__/templates/config.template.test.ts +++ b/apps/dokploy/__test__/templates/config.template.test.ts @@ -1,7 +1,7 @@ -import { describe, expect, it } from "vitest"; +import type { Schema } from "@dokploy/server/templates"; import type { CompleteTemplate } from "@dokploy/server/templates/processors"; import { processTemplate } from "@dokploy/server/templates/processors"; -import type { Schema } from "@dokploy/server/templates"; +import { describe, expect, it } from "vitest"; describe("processTemplate", () => { // Mock schema for testing diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 74b0e265b9..d8a14ab425 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -7,6 +7,11 @@ import { expect, test } from "vitest"; const baseApp: ApplicationNested = { applicationId: "", herokuVersion: "", + giteaRepository: "", + giteaOwner: "", + giteaBranch: "", + giteaBuildPath: "", + giteaId: "", cleanCache: false, applicationStatus: "done", appName: "", diff --git a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx index 2a3f2f43a5..0e848fece4 100644 --- a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx @@ -1,3 +1,4 @@ +import { AlertBlock } from "@/components/shared/alert-block"; import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; import { @@ -32,7 +33,6 @@ import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { AlertBlock } from "@/components/shared/alert-block"; const ImportSchema = z.object({ base64: z.string(), diff --git a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx b/apps/dokploy/components/dashboard/application/domains/add-domain.tsx index 8b1fa7e1ce..8da85a8791 100644 --- a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/add-domain.tsx @@ -41,8 +41,8 @@ import { toast } from "sonner"; import { domain } from "@/server/db/validations/domain"; import { zodResolver } from "@hookform/resolvers/zod"; import { Dices } from "lucide-react"; -import type z from "zod"; import Link from "next/link"; +import type z from "zod"; type Domain = z.infer; diff --git a/apps/dokploy/components/dashboard/application/environment/show.tsx b/apps/dokploy/components/dashboard/application/environment/show.tsx index b574ce092f..6f504959c1 100644 --- a/apps/dokploy/components/dashboard/application/environment/show.tsx +++ b/apps/dokploy/components/dashboard/application/environment/show.tsx @@ -4,10 +4,10 @@ import { Form } from "@/components/ui/form"; import { Secrets } from "@/components/ui/secrets"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { useEffect } from "react"; const addEnvironmentSchema = z.object({ env: z.string(), 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 ca1bf823f3..b506fbac5c 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 @@ -1,4 +1,6 @@ +import { BitbucketIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -39,13 +41,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { BitbucketIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const BitbucketProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), 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 3d6f6a3889..a7020c59d5 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 @@ -26,15 +26,15 @@ import { import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { KeyRoundIcon, LockIcon, X } from "lucide-react"; -import { useRouter } from "next/router"; import Link from "next/link"; +import { useRouter } from "next/router"; +import { GitIcon } from "@/components/icons/data-tools-icons"; +import { Badge } from "@/components/ui/badge"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { GitIcon } from "@/components/icons/data-tools-icons"; const GitProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), 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 new file mode 100644 index 0000000000..0ad8894523 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx @@ -0,0 +1,515 @@ +import { GiteaIcon } from "@/components/icons/data-tools-icons"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +interface GiteaRepository { + name: string; + url: string; + id: number; + owner: { + username: string; + }; +} + +interface GiteaBranch { + name: string; + commit: { + id: string; + }; +} + +const GiteaProviderSchema = z.object({ + buildPath: z.string().min(1, "Path is required").default("/"), + repository: z + .object({ + repo: z.string().min(1, "Repo is required"), + owner: z.string().min(1, "Owner is required"), + }) + .required(), + branch: z.string().min(1, "Branch is required"), + giteaId: z.string().min(1, "Gitea Provider is required"), + watchPaths: z.array(z.string()).default([]), +}); + +type GiteaProvider = z.infer; + +interface Props { + applicationId: string; +} + +export const SaveGiteaProvider = ({ applicationId }: Props) => { + const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); + const { data, refetch } = api.application.one.useQuery({ applicationId }); + + const { mutateAsync, isLoading: isSavingGiteaProvider } = + api.application.saveGiteaProvider.useMutation(); + + const form = useForm({ + defaultValues: { + buildPath: "/", + repository: { + owner: "", + repo: "", + }, + giteaId: "", + branch: "", + watchPaths: [], + }, + resolver: zodResolver(GiteaProviderSchema), + }); + + const repository = form.watch("repository"); + const giteaId = form.watch("giteaId"); + + const { data: giteaUrl } = api.gitea.getGiteaUrl.useQuery( + { giteaId }, + { + enabled: !!giteaId, + }, + ); + + const { + data: repositories, + isLoading: isLoadingRepositories, + error, + } = api.gitea.getGiteaRepositories.useQuery( + { + giteaId, + }, + { + enabled: !!giteaId, + }, + ); + + const { + data: branches, + fetchStatus, + status, + } = api.gitea.getGiteaBranches.useQuery( + { + owner: repository?.owner, + repositoryName: repository?.repo, + giteaId: giteaId, + }, + { + enabled: !!repository?.owner && !!repository?.repo && !!giteaId, + }, + ); + + useEffect(() => { + if (data) { + form.reset({ + branch: data.giteaBranch || "", + repository: { + repo: data.giteaRepository || "", + owner: data.giteaOwner || "", + }, + buildPath: data.giteaBuildPath || "/", + giteaId: data.giteaId || "", + watchPaths: data.watchPaths || [], + }); + } + }, [form.reset, data, form]); + + const onSubmit = async (data: GiteaProvider) => { + await mutateAsync({ + giteaBranch: data.branch, + giteaRepository: data.repository.repo, + giteaOwner: data.repository.owner, + giteaBuildPath: data.buildPath, + giteaId: data.giteaId, + applicationId, + watchPaths: data.watchPaths, + }) + .then(async () => { + toast.success("Service Provider Saved"); + await refetch(); + }) + .catch(() => { + toast.error("Error saving the Gitea provider"); + }); + }; + + return ( +
+
+ + {error && {error?.message}} +
+ ( + + Gitea Account + + + + )} + /> + + ( + +
+ Repository + {field.value.owner && field.value.repo && ( + + + View Repository + + )} +
+ + + + + + + + + + + {isLoadingRepositories && ( + + Loading Repositories.... + + )} + No repositories found. + + + {repositories && repositories.length === 0 && ( + + No repositories found. + + )} + {repositories?.map((repo: GiteaRepository) => { + return ( + { + form.setValue("repository", { + owner: repo.owner.username as string, + repo: repo.name, + }); + form.setValue("branch", ""); + }} + > + + {repo.name} + + {repo.owner.username} + + + + + ); + })} + + + + + + {form.formState.errors.repository && ( +

+ Repository is required +

+ )} +
+ )} + /> + ( + + Branch + + + + + + + + + + {status === "loading" && fetchStatus === "fetching" && ( + + Loading Branches.... + + )} + {!repository?.owner && ( + + Select a repository + + )} + + No branch found. + + + {branches?.map((branch: GiteaBranch) => ( + { + form.setValue("branch", branch.name); + }} + > + {branch.name} + + + ))} + + + + + + + + + )} + /> + ( + + Build Path + + + + + + + )} + /> + ( + +
+ Watch Paths + + + + + + +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. +

+
+
+
+
+
+ {field.value?.map((path: string, index: number) => ( + + {path} + { + const newPaths = [...field.value]; + newPaths.splice(index, 1); + field.onChange(newPaths); + }} + /> + + ))} +
+
+ + { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const path = input.value.trim(); + if (path) { + field.onChange([...field.value, path]); + input.value = ""; + } + } + }} + /> + + +
+ +
+ )} + /> +
+
+ +
+
+ +
+ ); +}; 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 202c7f880a..2a267a1853 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 @@ -1,3 +1,5 @@ +import { GithubIcon } from "@/components/icons/data-tools-icons"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -34,17 +36,15 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import Link from "next/link"; -import { GithubIcon } from "@/components/icons/data-tools-icons"; const GithubProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), 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 c0c90a0163..0f8bb849e7 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 @@ -1,4 +1,6 @@ +import { GitlabIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -35,17 +37,15 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import Link from "next/link"; -import { GitlabIcon } from "@/components/icons/data-tools-icons"; const GitlabProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/show.tsx b/apps/dokploy/components/dashboard/application/general/generic/show.tsx index b00a349533..3f8854888d 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/show.tsx @@ -1,10 +1,12 @@ import { SaveDockerProvider } from "@/components/dashboard/application/general/generic/save-docker-provider"; import { SaveGitProvider } from "@/components/dashboard/application/general/generic/save-git-provider"; +import { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider"; import { SaveGithubProvider } from "@/components/dashboard/application/general/generic/save-github-provider"; import { BitbucketIcon, DockerIcon, GitIcon, + GiteaIcon, GithubIcon, GitlabIcon, } from "@/components/icons/data-tools-icons"; @@ -18,7 +20,14 @@ import { SaveBitbucketProvider } from "./save-bitbucket-provider"; import { SaveDragNDrop } from "./save-drag-n-drop"; import { SaveGitlabProvider } from "./save-gitlab-provider"; -type TabState = "github" | "docker" | "git" | "drop" | "gitlab" | "bitbucket"; +type TabState = + | "github" + | "docker" + | "git" + | "drop" + | "gitlab" + | "bitbucket" + | "gitea"; interface Props { applicationId: string; @@ -29,6 +38,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => { const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); const { data: bitbucketProviders } = api.bitbucket.bitbucketProviders.useQuery(); + const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); const { data: application } = api.application.one.useQuery({ applicationId }); const [tab, setSab] = useState(application?.sourceType || "github"); @@ -78,6 +88,13 @@ export const ShowProviderForm = ({ applicationId }: Props) => { Bitbucket + + + Gitea + { )} + + {giteaProviders && giteaProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using Gitea, you need to configure your account + first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
diff --git a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx b/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx index 975ce1ffef..6089c99fff 100644 --- a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx +++ b/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx @@ -41,8 +41,8 @@ import { import { domainCompose } from "@/server/db/validations/domain"; import { zodResolver } from "@hookform/resolvers/zod"; import { DatabaseZap, Dices, RefreshCw } from "lucide-react"; -import type z from "zod"; import Link from "next/link"; +import type z from "zod"; type Domain = z.infer; diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx index 6dc99b2673..ff329a0afe 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx @@ -1,4 +1,6 @@ +import { BitbucketIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -39,13 +41,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { BitbucketIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const BitbucketProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx index ebe998923a..68891e456b 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx @@ -1,3 +1,4 @@ +import { GitIcon } from "@/components/icons/data-tools-icons"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -27,13 +28,12 @@ import { import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { KeyRoundIcon, LockIcon, X } from "lucide-react"; +import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { GitIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const GitProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx new file mode 100644 index 0000000000..201f9da2ec --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx @@ -0,0 +1,483 @@ +import { GiteaIcon } from "@/components/icons/data-tools-icons"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { api } from "@/utils/api"; +import type { Repository } from "@/utils/gitea-utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { CheckIcon, ChevronsUpDown, Plus, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +const GiteaProviderSchema = z.object({ + composePath: z.string().min(1), + repository: z + .object({ + repo: z.string().min(1, "Repo is required"), + owner: z.string().min(1, "Owner is required"), + }) + .required(), + branch: z.string().min(1, "Branch is required"), + giteaId: z.string().min(1, "Gitea Provider is required"), + watchPaths: z.array(z.string()).optional(), +}); + +type GiteaProvider = z.infer; + +interface Props { + composeId: string; +} + +export const SaveGiteaProviderCompose = ({ composeId }: Props) => { + const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); + const { data, refetch } = api.compose.one.useQuery({ composeId }); + const { mutateAsync, isLoading: isSavingGiteaProvider } = + api.compose.update.useMutation(); + + const form = useForm({ + defaultValues: { + composePath: "./docker-compose.yml", + repository: { + owner: "", + repo: "", + }, + giteaId: "", + branch: "", + watchPaths: [], + }, + resolver: zodResolver(GiteaProviderSchema), + }); + + const repository = form.watch("repository"); + const giteaId = form.watch("giteaId"); + + const { data: giteaUrl } = api.gitea.getGiteaUrl.useQuery( + { giteaId }, + { + enabled: !!giteaId, + }, + ); + + const { + data: repositories, + isLoading: isLoadingRepositories, + error, + } = api.gitea.getGiteaRepositories.useQuery( + { + giteaId, + }, + { + enabled: !!giteaId, + }, + ); + + const { + data: branches, + fetchStatus, + status, + } = api.gitea.getGiteaBranches.useQuery( + { + owner: repository?.owner, + repositoryName: repository?.repo, + giteaId: giteaId, + }, + { + enabled: !!repository?.owner && !!repository?.repo && !!giteaId, + }, + ); + + useEffect(() => { + if (data) { + form.reset({ + branch: data.giteaBranch || "", + repository: { + repo: data.giteaRepository || "", + owner: data.giteaOwner || "", + }, + composePath: data.composePath || "./docker-compose.yml", + giteaId: data.giteaId || "", + watchPaths: data.watchPaths || [], + }); + } + }, [form.reset, data, form]); + + const onSubmit = async (data: GiteaProvider) => { + await mutateAsync({ + giteaBranch: data.branch, + giteaRepository: data.repository.repo, + giteaOwner: data.repository.owner, + composePath: data.composePath, + giteaId: data.giteaId, + composeId, + sourceType: "gitea", + composeStatus: "idle", + watchPaths: data.watchPaths, + } as any) + .then(async () => { + toast.success("Service Provider Saved"); + await refetch(); + }) + .catch(() => { + toast.error("Error saving the Gitea provider"); + }); + }; + + return ( +
+
+ + {error && {error?.message}} + +
+ ( + + Gitea Account + + + + )} + /> + + ( + +
+ Repository + {field.value.owner && field.value.repo && ( + + + View Repository + + )} +
+ + + + + + + + + + {isLoadingRepositories && ( + + Loading Repositories.... + + )} + No repositories found. + + + {repositories?.map((repo) => ( + { + form.setValue("repository", { + owner: repo.owner.username, + repo: repo.name, + }); + form.setValue("branch", ""); + }} + > + + {repo.name} + + {repo.owner.username} + + + + + ))} + + + + + + {form.formState.errors.repository && ( +

+ Repository is required +

+ )} +
+ )} + /> + + ( + + Branch + + + + + + + + + + No branches found. + + + {branches?.map((branch) => ( + + form.setValue("branch", branch.name) + } + > + + {branch.name} + + + + ))} + + + + + + {form.formState.errors.branch && ( +

+ Branch is required +

+ )} +
+ )} + /> + + ( + + Compose Path + + + + + + )} + /> + + ( + +
+ Watch Paths + + + +
+ ? +
+
+ +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + form.setValue("watchPaths", newPaths); + }} + /> + + ))} +
+ +
+ { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const value = input.value.trim(); + if (value) { + const newPaths = [...(field.value || []), value]; + form.setValue("watchPaths", newPaths); + input.value = ""; + } + } + }} + /> + +
+
+ +
+ )} + /> +
+ +
+ +
+
+ +
+ ); +}; diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx index b58347dc37..4f4c1d5ad2 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx @@ -1,3 +1,4 @@ +import { GithubIcon } from "@/components/icons/data-tools-icons"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -39,12 +40,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { GithubIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const GithubProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx index 693fea7189..c191248eac 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx @@ -1,4 +1,6 @@ +import { GitlabIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -39,13 +41,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { GitlabIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const GitlabProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx index 347c134e3f..2ac879e870 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx @@ -1,6 +1,7 @@ import { BitbucketIcon, GitIcon, + GiteaIcon, GithubIcon, GitlabIcon, } from "@/components/icons/data-tools-icons"; @@ -14,10 +15,11 @@ import { ComposeFileEditor } from "../compose-file-editor"; import { ShowConvertedCompose } from "../show-converted-compose"; import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose"; 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"; -type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket"; +type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea"; interface Props { composeId: string; } @@ -27,9 +29,11 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); const { data: bitbucketProviders } = api.bitbucket.bitbucketProviders.useQuery(); + const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); const { data: compose } = api.compose.one.useQuery({ composeId }); const [tab, setSab] = useState(compose?.sourceType || "github"); + return ( @@ -54,21 +58,21 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { setSab(e as TabState); }} > -
- +
+ - Github + GitHub - Gitlab + GitLab { Bitbucket - + + Gitea + { value="raw" className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border" > - + Raw
+ {githubProviders && githubProviders?.length > 0 ? ( @@ -154,6 +164,26 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
)} + + {giteaProviders && giteaProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using Gitea, you need to configure your account + first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
diff --git a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx index 5dcd77327a..b585d5ee6a 100644 --- a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx @@ -1,3 +1,5 @@ +import { DrawerLogs } from "@/components/shared/drawer-logs"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -23,6 +25,7 @@ import { FormLabel, FormMessage, } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, @@ -32,18 +35,15 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; +import copy from "copy-to-clipboard"; +import { debounce } from "lodash"; import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; +import { toast } from "sonner"; import { z } from "zod"; import type { ServiceType } from "../../application/advanced/show-resources"; -import { debounce } from "lodash"; -import { Input } from "@/components/ui/input"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; -import { DrawerLogs } from "@/components/shared/drawer-logs"; -import { Badge } from "@/components/ui/badge"; -import copy from "copy-to-clipboard"; -import { toast } from "sonner"; interface Props { databaseId: string; diff --git a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx index cdfe614c87..3de79c2f34 100644 --- a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx +++ b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx @@ -16,12 +16,12 @@ import { import { api } from "@/utils/api"; import { DatabaseBackup, Play, Trash2 } from "lucide-react"; import Link from "next/link"; +import { useState } from "react"; import { toast } from "sonner"; import type { ServiceType } from "../../application/advanced/show-resources"; import { AddBackup } from "./add-backup"; -import { UpdateBackup } from "./update-backup"; import { RestoreBackup } from "./restore-backup"; -import { useState } from "react"; +import { UpdateBackup } from "./update-backup"; interface Props { id: string; diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index 8abc8b40a0..5dbbcd1da8 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -67,7 +67,7 @@ import { SearchIcon, } from "lucide-react"; import Link from "next/link"; -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import { toast } from "sonner"; const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url"; diff --git a/apps/dokploy/components/dashboard/project/ai/step-one.tsx b/apps/dokploy/components/dashboard/project/ai/step-one.tsx index 109ad44135..e2a6795fe4 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-one.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-one.tsx @@ -13,7 +13,6 @@ import { } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { useState } from "react"; const examples = [ "Make a personal blog", @@ -23,7 +22,7 @@ const examples = [ "Sendgrid service opensource analogue", ]; -export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => { +export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { // Get servers from the API const { data: servers } = api.server.withSSHKey.useQuery(); diff --git a/apps/dokploy/components/dashboard/requests/request-distribution-chart.tsx b/apps/dokploy/components/dashboard/requests/request-distribution-chart.tsx index dd23488016..cf4b4ded46 100644 --- a/apps/dokploy/components/dashboard/requests/request-distribution-chart.tsx +++ b/apps/dokploy/components/dashboard/requests/request-distribution-chart.tsx @@ -1,10 +1,10 @@ -import { api } from "@/utils/api"; import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; +import { api } from "@/utils/api"; import { Area, AreaChart, diff --git a/apps/dokploy/components/dashboard/requests/show-requests.tsx b/apps/dokploy/components/dashboard/requests/show-requests.tsx index 134110872e..aad4f011f3 100644 --- a/apps/dokploy/components/dashboard/requests/show-requests.tsx +++ b/apps/dokploy/components/dashboard/requests/show-requests.tsx @@ -25,13 +25,13 @@ import { import { type RouterOutputs, api } from "@/utils/api"; import { format } from "date-fns"; import { - ArrowDownUp, AlertCircle, - InfoIcon, + ArrowDownUp, Calendar as CalendarIcon, + InfoIcon, } from "lucide-react"; import Link from "next/link"; -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import { toast } from "sonner"; import { RequestDistributionChart } from "./request-distribution-chart"; import { RequestsTable } from "./requests-table"; diff --git a/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx b/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx index 131d7ddfbf..568b86e942 100644 --- a/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx +++ b/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx @@ -1,14 +1,22 @@ +import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { toast } from "sonner"; import { Dialog, DialogContent, + DialogDescription, DialogHeader, DialogTitle, DialogTrigger, - DialogDescription, } from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, @@ -17,22 +25,14 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import copy from "copy-to-clipboard"; import { useState } from "react"; import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; +import { toast } from "sonner"; import { z } from "zod"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - FormDescription, -} from "@/components/ui/form"; -import { Switch } from "@/components/ui/switch"; -import copy from "copy-to-clipboard"; -import { CodeEditor } from "@/components/shared/code-editor"; const formSchema = z.object({ name: z.string().min(1, "Name is required"), diff --git a/apps/dokploy/components/dashboard/settings/api/show-api-keys.tsx b/apps/dokploy/components/dashboard/settings/api/show-api-keys.tsx index 6744f1dea7..743542ad05 100644 --- a/apps/dokploy/components/dashboard/settings/api/show-api-keys.tsx +++ b/apps/dokploy/components/dashboard/settings/api/show-api-keys.tsx @@ -1,3 +1,5 @@ +import { DialogAction } from "@/components/shared/dialog-action"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, @@ -7,13 +9,11 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { ExternalLinkIcon, KeyIcon, Trash2, Clock, Tag } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; +import { Clock, ExternalLinkIcon, KeyIcon, Tag, Trash2 } from "lucide-react"; import Link from "next/link"; import { toast } from "sonner"; -import { formatDistanceToNow } from "date-fns"; -import { DialogAction } from "@/components/shared/dialog-action"; import { AddApiKey } from "./add-api-key"; -import { Badge } from "@/components/ui/badge"; export const ShowApiKeys = () => { const { data, refetch } = api.user.get.useQuery(); diff --git a/apps/dokploy/components/dashboard/settings/cluster/nodes/add-node.tsx b/apps/dokploy/components/dashboard/settings/cluster/nodes/add-node.tsx index 966055b4f5..63fb17ddad 100644 --- a/apps/dokploy/components/dashboard/settings/cluster/nodes/add-node.tsx +++ b/apps/dokploy/components/dashboard/settings/cluster/nodes/add-node.tsx @@ -1,3 +1,4 @@ +import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -12,7 +13,6 @@ import { ExternalLink, PlusIcon } from "lucide-react"; import Link from "next/link"; import { AddManager } from "./manager/add-manager"; import { AddWorker } from "./workers/add-worker"; -import { AlertBlock } from "@/components/shared/alert-block"; interface Props { serverId?: string; diff --git a/apps/dokploy/components/dashboard/settings/cluster/nodes/show-nodes.tsx b/apps/dokploy/components/dashboard/settings/cluster/nodes/show-nodes.tsx index 827fc520e1..4354a8bcab 100644 --- a/apps/dokploy/components/dashboard/settings/cluster/nodes/show-nodes.tsx +++ b/apps/dokploy/components/dashboard/settings/cluster/nodes/show-nodes.tsx @@ -35,9 +35,9 @@ import { api } from "@/utils/api"; import { Boxes, HelpCircle, + Loader2, LockIcon, MoreHorizontal, - Loader2, } from "lucide-react"; import { toast } from "sonner"; import { AddNode } from "./add-node"; diff --git a/apps/dokploy/components/dashboard/settings/git/gitea/add-gitea-provider.tsx b/apps/dokploy/components/dashboard/settings/git/gitea/add-gitea-provider.tsx new file mode 100644 index 0000000000..13c65bdf39 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/git/gitea/add-gitea-provider.tsx @@ -0,0 +1,286 @@ +import { GiteaIcon } from "@/components/icons/data-tools-icons"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { api } from "@/utils/api"; +import { + type GiteaProviderResponse, + getGiteaOAuthUrl, +} from "@/utils/gitea-utils"; +import { useUrl } from "@/utils/hooks/use-url"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { ExternalLink } from "lucide-react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +const Schema = z.object({ + name: z.string().min(1, { + message: "Name is required", + }), + giteaUrl: z.string().min(1, { + message: "Gitea URL is required", + }), + clientId: z.string().min(1, { + message: "Client ID is required", + }), + clientSecret: z.string().min(1, { + message: "Client Secret is required", + }), + redirectUri: z.string().min(1, { + message: "Redirect URI is required", + }), + organizationName: z.string().optional(), +}); + +type Schema = z.infer; + +export const AddGiteaProvider = () => { + const [isOpen, setIsOpen] = useState(false); + + const urlObj = useUrl(); + const baseUrl = + typeof urlObj === "string" ? urlObj : (urlObj as any)?.url || ""; + + const { mutateAsync, error, isError } = api.gitea.create.useMutation(); + const webhookUrl = `${baseUrl}/api/providers/gitea/callback`; + + const form = useForm({ + defaultValues: { + clientId: "", + clientSecret: "", + redirectUri: webhookUrl, + name: "", + giteaUrl: "https://gitea.com", + }, + resolver: zodResolver(Schema), + }); + + const giteaUrl = form.watch("giteaUrl"); + + useEffect(() => { + form.reset({ + clientId: "", + clientSecret: "", + redirectUri: webhookUrl, + name: "", + giteaUrl: "https://gitea.com", + }); + }, [form, webhookUrl, isOpen]); + + const onSubmit = async (data: Schema) => { + try { + // Send the form data to create the Gitea provider + const result = (await mutateAsync({ + clientId: data.clientId, + clientSecret: data.clientSecret, + name: data.name, + redirectUri: data.redirectUri, + giteaUrl: data.giteaUrl, + organizationName: data.organizationName, + })) as unknown as GiteaProviderResponse; + + // Check if we have a giteaId from the response + if (!result || !result.giteaId) { + toast.error("Failed to get Gitea ID from response"); + return; + } + + // Generate OAuth URL using the shared utility + const authUrl = getGiteaOAuthUrl( + result.giteaId, + data.clientId, + data.giteaUrl, + baseUrl, + ); + + // Open the Gitea OAuth URL + if (authUrl !== "#") { + window.open(authUrl, "_blank"); + } else { + toast.error("Configuration Incomplete", { + description: "Please fill in Client ID and Gitea URL first.", + }); + } + + toast.success("Gitea provider created successfully"); + setIsOpen(false); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(`Error configuring Gitea: ${error.message}`); + } else { + toast.error("An unknown error occurred."); + } + } + }; + + return ( + + + + + + + + Gitea Provider + + + + {isError && {error?.message}} +
+ + +
+

+ To integrate your Gitea account, you need to create a new + application in your Gitea settings. Follow these steps: +

+
    +
  1. + Go to your Gitea settings{" "} + + + +
  2. +
  3. + Navigate to Applications {"->"} Create new OAuth2 + Application +
  4. +
  5. + Create a new application with the following details: +
      +
    • Name: Dokploy
    • +
    • + Redirect URI:{" "} + {webhookUrl}{" "} +
    • +
    +
  6. +
  7. + After creating, you'll receive an ID and Secret, copy them + and paste them below. +
  8. +
+ ( + + Name + + + + + + )} + /> + + ( + + Gitea URL + + + + + + )} + /> + + ( + + Redirect URI + + + + + + )} + /> + + ( + + Client ID + + + + + + )} + /> + + ( + + Client Secret + + + + + + )} + /> + + +
+
+
+ +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/git/gitea/edit-gitea-provider.tsx b/apps/dokploy/components/dashboard/settings/git/gitea/edit-gitea-provider.tsx new file mode 100644 index 0000000000..13e43fbf95 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/git/gitea/edit-gitea-provider.tsx @@ -0,0 +1,296 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { api } from "@/utils/api"; +import { getGiteaOAuthUrl } from "@/utils/gitea-utils"; +import { useUrl } from "@/utils/hooks/use-url"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +const formSchema = z.object({ + name: z.string().min(1, "Name is required"), + giteaUrl: z.string().min(1, "Gitea URL is required"), + clientId: z.string().min(1, "Client ID is required"), + clientSecret: z.string().min(1, "Client Secret is required"), +}); + +interface Props { + giteaId: string; +} + +export const EditGiteaProvider = ({ giteaId }: Props) => { + const router = useRouter(); + const [open, setOpen] = useState(false); + const { + data: gitea, + isLoading, + refetch, + } = api.gitea.one.useQuery({ giteaId }); + const { mutateAsync, isLoading: isUpdating } = api.gitea.update.useMutation(); + const { mutateAsync: testConnection, isLoading: isTesting } = + api.gitea.testConnection.useMutation(); + const url = useUrl(); + const utils = api.useUtils(); + + useEffect(() => { + const { connected, error } = router.query; + + if (!router.isReady) return; + + if (connected) { + toast.success("Successfully connected to Gitea", { + description: "Your Gitea provider has been authorized.", + id: "gitea-connection-success", + }); + refetch(); + router.replace( + { + pathname: router.pathname, + query: {}, + }, + undefined, + { shallow: true }, + ); + } + + if (error) { + toast.error("Gitea Connection Failed", { + description: decodeURIComponent(error as string), + id: "gitea-connection-error", + }); + router.replace( + { + pathname: router.pathname, + query: {}, + }, + undefined, + { shallow: true }, + ); + } + }, [router.query, router.isReady, refetch]); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + giteaUrl: "https://gitea.com", + clientId: "", + clientSecret: "", + }, + }); + + useEffect(() => { + if (gitea) { + form.reset({ + name: gitea.gitProvider?.name || "", + giteaUrl: gitea.giteaUrl || "https://gitea.com", + clientId: gitea.clientId || "", + clientSecret: gitea.clientSecret || "", + }); + } + }, [gitea, form]); + + const onSubmit = async (values: z.infer) => { + await mutateAsync({ + giteaId: giteaId, + gitProviderId: gitea?.gitProvider?.gitProviderId || "", + name: values.name, + giteaUrl: values.giteaUrl, + clientId: values.clientId, + clientSecret: values.clientSecret, + }) + .then(async () => { + await utils.gitProvider.getAll.invalidate(); + toast.success("Gitea provider updated successfully"); + await refetch(); + setOpen(false); + }) + .catch(() => { + toast.error("Error updating Gitea provider"); + }); + }; + + const handleTestConnection = async () => { + try { + const result = await testConnection({ giteaId }); + toast.success("Gitea Connection Verified", { + description: result, + }); + } catch (error: any) { + const formValues = form.getValues(); + const authUrl = + error.authorizationUrl || + getGiteaOAuthUrl( + giteaId, + formValues.clientId, + formValues.giteaUrl, + typeof url === "string" ? url : (url as any).url || "", + ); + + toast.error("Gitea Not Connected", { + description: + error.message || "Please complete the OAuth authorization process.", + action: + authUrl && authUrl !== "#" + ? { + label: "Authorize Now", + onClick: () => window.open(authUrl, "_blank"), + } + : undefined, + }); + } + }; + + if (isLoading) { + return ( + + ); + } + + // Function to handle dialog open state + const handleOpenChange = (newOpen: boolean) => { + setOpen(newOpen); + }; + + return ( + + + + + + + Edit Gitea Provider + + Update your Gitea provider details. + + +
+ + ( + + Name + + + + + + )} + /> + ( + + Gitea URL + + + + + + )} + /> + ( + + Client ID + + + + + + )} + /> + ( + + Client Secret + + + + + + )} + /> + +
+ + + + + +
+ + +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx index 451ea5d173..35d9ef0d70 100644 --- a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx +++ b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx @@ -1,5 +1,6 @@ import { BitbucketIcon, + GiteaIcon, GithubIcon, GitlabIcon, } from "@/components/icons/data-tools-icons"; @@ -26,6 +27,8 @@ import Link from "next/link"; import { toast } from "sonner"; import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider"; import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider"; +import { AddGiteaProvider } from "./gitea/add-gitea-provider"; +import { EditGiteaProvider } from "./gitea/edit-gitea-provider"; import { AddGithubProvider } from "./github/add-github-provider"; import { EditGithubProvider } from "./github/edit-github-provider"; import { AddGitlabProvider } from "./gitlab/add-gitlab-provider"; @@ -36,19 +39,18 @@ export const ShowGitProviders = () => { const { mutateAsync, isLoading: isRemoving } = api.gitProvider.remove.useMutation(); const url = useUrl(); + const getGitlabUrl = ( clientId: string, gitlabId: string, gitlabUrl: string, ) => { const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`; - const scope = "api read_user read_repository"; - const authUrl = `${gitlabUrl}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`; - return authUrl; }; + return (
@@ -82,6 +84,7 @@ export const ShowGitProviders = () => { +
@@ -97,6 +100,7 @@ export const ShowGitProviders = () => { + @@ -107,13 +111,16 @@ export const ShowGitProviders = () => { const isGitlab = gitProvider.providerType === "gitlab"; const isBitbucket = gitProvider.providerType === "bitbucket"; + const isGitea = gitProvider.providerType === "gitea"; + const haveGithubRequirements = - gitProvider.providerType === "github" && + isGithub && gitProvider.github?.githubPrivateKey && gitProvider.github?.githubAppId && gitProvider.github?.githubInstallationId; const haveGitlabRequirements = + isGitlab && gitProvider.gitlab?.accessToken && gitProvider.gitlab?.refreshToken; @@ -122,18 +129,19 @@ export const ShowGitProviders = () => { key={gitProvider.gitProviderId} className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg" > -
+
- {gitProvider.providerType === "github" && ( + {isGithub && ( )} - {gitProvider.providerType === "gitlab" && ( + {isGitlab && ( )} - {gitProvider.providerType === "bitbucket" && ( + {isBitbucket && ( )} + {isGitea && }
{gitProvider.name} @@ -194,26 +202,33 @@ export const ShowGitProviders = () => {
)} + {isGithub && haveGithubRequirements && ( )} {isGitlab && ( )} {isBitbucket && ( )} + {isGitea && ( + + )} + {