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 00000000..95ff9784 --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx @@ -0,0 +1,486 @@ +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, 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 Repository { + name: string; + url: string; + id: number; + owner: { + username: string; + }; +} + +interface Branch { + name: string; +} + +interface GiteaProviderType { + giteaId: string; + gitProvider: { + name: string; + gitProviderId: string; + providerType: "github" | "gitlab" | "bitbucket" | "gitea"; + createdAt: string; + organizationId: string; + }; + name: string; +} + +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"), + id: z.number().nullable(), + giteaPathNamespace: z.string().min(1), + }) + .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: "", + giteaPathNamespace: "", + id: null, + }, + giteaId: "", + branch: "", + watchPaths: [], + }, + resolver: zodResolver(GiteaProviderSchema), + }); + + const repository = form.watch("repository"); + const giteaId = form.watch("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, + id: repository?.id || 0, + giteaId: giteaId, + }, + { + enabled: !!repository?.owner && !!repository?.repo && !!giteaId, + } + ); + + useEffect(() => { + if (data) { + form.reset({ + branch: data.giteaBranch || "", + repository: { + repo: data.giteaRepository || "", + owner: data.giteaOwner || "", + id: null, + giteaPathNamespace: "", + }, + composePath: data.composePath, + 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 Provided 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: Repository) => { + return ( + { + form.setValue("repository", { + owner: repo.owner.username as string, + repo: repo.name, + id: repo.id, + giteaPathNamespace: repo.url, + }); + form.setValue("branch", ""); + }} + > + + {repo.name} + + {repo.owner.username} + + + + + ); + })} + + + + + + {form.formState.errors.repository && ( +

+ Repository is required +

+ )} +
+ )} + /> + ( + + Branch + + + + + + + + + + No branches found. + + + {branches?.map((branch: 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 = ""; + } + } + }} + /> + +
+
+ +
+ )} + /> +
+ +
+ +
+ ); +}; \ No newline at end of file diff --git a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx index 347c134e..38ca5fbe 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx @@ -3,7 +3,8 @@ import { GitIcon, GithubIcon, GitlabIcon, -} from "@/components/icons/data-tools-icons"; + GiteaIcon, + } from "@/components/icons/data-tools-icons"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { api } from "@/utils/api"; @@ -16,153 +17,128 @@ import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose" import { SaveGitProviderCompose } from "./save-git-provider-compose"; import { SaveGithubProviderCompose } from "./save-github-provider-compose"; import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose"; - -type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket"; -interface Props { +import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose"; + + + type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea"; + interface Props { composeId: string; -} - -export const ShowProviderFormCompose = ({ composeId }: Props) => { + } + + export const ShowProviderFormCompose = ({ composeId }: Props) => { const { data: githubProviders } = api.github.githubProviders.useQuery(); const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); - const { data: bitbucketProviders } = - api.bitbucket.bitbucketProviders.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 ( - - - -
- Provider -

- Select the source of your code -

-
-
- - -
-
-
- - { - setSab(e as TabState); - }} + + + +
+ Provider +

+ Select the source of your code +

+
+
+ + +
+
+
+ + { + setSab(e as TabState); + }} + > +
+ + -
- - - - Github - - - - Gitlab - - - - Bitbucket - - - - - Git - - - - Raw - - -
- - {githubProviders && githubProviders?.length > 0 ? ( - - ) : ( -
- - - To deploy using GitHub, you need to configure your account - first. Please, go to{" "} - - Settings - {" "} - to do so. - -
- )} -
- - {gitlabProviders && gitlabProviders?.length > 0 ? ( - - ) : ( -
- - - To deploy using GitLab, you need to configure your account - first. Please, go to{" "} - - Settings - {" "} - to do so. - -
- )} -
- - {bitbucketProviders && bitbucketProviders?.length > 0 ? ( - - ) : ( -
- - - To deploy using Bitbucket, you need to configure your account - first. Please, go to{" "} - - Settings - {" "} - to do so. - -
- )} -
- - - - - - - - - - + + GitHub +
+ + + GitLab + + + + Bitbucket + + + {/* Add Gitea Icon */} + Gitea + + + + Git + + + + Raw + +
+
+ + {githubProviders && githubProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using GitHub, you need to configure your account first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
+ {/* Added Gitea tab */} + {giteaProviders && giteaProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using Gitea, you need to configure your account first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
+ {/* Other tabs remain unchanged */} +
+
+
); -}; + }; \ No newline at end of file diff --git a/packages/server/src/services/gitea.ts b/packages/server/src/services/gitea.ts index 6b6dab7f..fecd8134 100644 --- a/packages/server/src/services/gitea.ts +++ b/packages/server/src/services/gitea.ts @@ -6,23 +6,24 @@ import { } from "@dokploy/server/db/schema"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; + export type Gitea = typeof gitea.$inferSelect; export const createGitea = async ( input: typeof apiCreateGitea._type, organizationId: string, ) => { + // @ts-ignore - Complex transaction type - Added because proper typing in Drizzle in not sufficient return await db.transaction(async (tx) => { - // Insert new Git provider (Gitea) const newGitProvider = await tx .insert(gitProvider) .values({ - providerType: "gitea", // Set providerType to 'gitea' + providerType: "gitea", organizationId: organizationId, name: input.name, }) .returning() - .then((response) => response[0]); + .then((response: typeof gitProvider.$inferSelect[]) => response[0]); if (!newGitProvider) { throw new TRPCError({ @@ -31,7 +32,6 @@ export const createGitea = async ( }); } - // Insert the Gitea data into the `gitea` table await tx .insert(gitea) .values({ @@ -39,7 +39,7 @@ export const createGitea = async ( gitProviderId: newGitProvider?.gitProviderId, }) .returning() - .then((response) => response[0]); + .then((response: typeof gitea.$inferSelect[]) => response[0]); }); }; @@ -84,7 +84,6 @@ export const updateGitea = async (giteaId: string, input: Partial) => { .where(eq(gitea.giteaId, giteaId)) .returning(); - // Explicitly type the result and handle potential undefined const result = updateResult[0] as Gitea | undefined; if (!result) { @@ -97,4 +96,4 @@ export const updateGitea = async (giteaId: string, input: Partial) => { console.error("Error updating Gitea provider:", error); throw error; } -}; +}; \ No newline at end of file diff --git a/packages/server/src/utils/providers/gitea.ts b/packages/server/src/utils/providers/gitea.ts index cd6cb95b..4bedfcb2 100644 --- a/packages/server/src/utils/providers/gitea.ts +++ b/packages/server/src/utils/providers/gitea.ts @@ -1,5 +1,5 @@ import { createWriteStream } from "node:fs"; -import fs from "node:fs/promises"; +import * as fs from "node:fs/promises"; import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import { findGiteaById, updateGitea } from "@dokploy/server/services/gitea";