diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 000000000..827ccc709 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,22 @@ +name: autofix.ci + +on: + push: + branches: [canary] + pull_request: + branches: [canary] + +jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup biomeJs + uses: biomejs/setup-biome@v2 + + - name: Run Biome formatter + run: biome format . --write + + - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c64d0672e..015095aa6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,9 +61,9 @@ pnpm install cp apps/dokploy/.env.example apps/dokploy/.env ``` -## Development +## Requirements -Is required to have **Docker** installed on your machine. +- [Docker](/GUIDES.md#docker) ### Setup diff --git a/GUIDES.md b/GUIDES.md new file mode 100644 index 000000000..cfb7cd812 --- /dev/null +++ b/GUIDES.md @@ -0,0 +1,49 @@ +# Docker + +Here's how to install docker on different operating systems: + +## macOS + +1. Visit [Docker Desktop for Mac](https://www.docker.com/products/docker-desktop) +2. Download the Docker Desktop installer +3. Double-click the downloaded `.dmg` file +4. Drag Docker to your Applications folder +5. Open Docker Desktop from Applications +6. Follow the onboarding tutorial if desired + +## Linux + +### Ubuntu + +```bash +# Update package index +sudo apt-get update + +# Install prerequisites +sudo apt-get install \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + +# Add Docker's official GPG key +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + +# Set up stable repository +echo \ + "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +# Install Docker Engine +sudo apt-get update +sudo apt-get install docker-ce docker-ce-cli containerd.io +``` + +## Windows + +1. Enable WSL2 if not already enabled +2. Visit [Docker Desktop for Windows](https://www.docker.com/products/docker-desktop) +3. Download the installer +4. Run the installer and follow the prompts +5. Start Docker Desktop from the Start menu \ No newline at end of file diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts index 74c803655..7dc9e560c 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 902e3163b..9516e9d1f 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 74b0e265b..d8a14ab42 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 2a3f2f43a..0e848fece 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 8b1fa7e1c..8da85a879 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 b574ce092..6f504959c 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 ca1bf823f..b506fbac5 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-docker-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx index 6f51af3d6..a1f3367dd 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 @@ -115,7 +115,11 @@ export const SaveDockerProvider = ({ applicationId }: Props) => { Username - + @@ -130,7 +134,12 @@ export const SaveDockerProvider = ({ applicationId }: Props) => { Password - + 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 2613174b7..00d283956 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 000000000..0ad889452 --- /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 30df1812c..7637f596d 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 a32694541..996b1dca1 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 b00a34953..3f8854888 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 975ce1ffe..6089c99ff 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 6dc99b267..ff329a0af 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 ebe998923..68891e456 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 000000000..201f9da2e --- /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 b58347dc3..4f4c1d5ad 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 693fea718..c191248ea 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 347c134e3..2ac879e87 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/compose/general/isolated-deployment.tsx b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx index 1eb13cadf..8ee9c786b 100644 --- a/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx +++ b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx @@ -147,7 +147,9 @@ export const IsolatedDeployment = ({ composeId }: Props) => { render={({ field }) => (
- Enable Isolated Deployment ({data?.appName}) + + Enable Isolated Deployment ({data?.appName}) + Enable isolated deployment to the compose file. diff --git a/apps/dokploy/components/dashboard/database/backups/add-backup.tsx b/apps/dokploy/components/dashboard/database/backups/add-backup.tsx index 219e42184..5b963a4c6 100644 --- a/apps/dokploy/components/dashboard/database/backups/add-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/add-backup.tsx @@ -286,16 +286,21 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => { Keep the latest - + - Optional. If provided, only keeps the latest N backups in the cloud. + Optional. If provided, only keeps the latest N backups + in the cloud. ); }} - /> + /> { enabled: backup.enabled || false, prefix: backup.prefix, schedule: backup.schedule, - keepLatestCount: backup.keepLatestCount ? Number(backup.keepLatestCount) : undefined, + keepLatestCount: backup.keepLatestCount + ? Number(backup.keepLatestCount) + : undefined, }); } }, [form, form.reset, backup]); @@ -274,10 +276,15 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => { Keep the latest - + - Optional. If provided, only keeps the latest N backups in the cloud. + Optional. If provided, only keeps the latest N backups + in the cloud. diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx index 9f5b63c31..c00af42be 100644 --- a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx +++ b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx @@ -27,145 +27,149 @@ import { toast } from "sonner"; import { z } from "zod"; const DockerProviderSchema = z.object({ - externalPort: z.preprocess((a) => { - if (a !== null) { - const parsed = Number.parseInt(z.string().parse(a), 10); - return Number.isNaN(parsed) ? null : parsed; - } - return null; - }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()), + externalPort: z.preprocess((a) => { + if (a !== null) { + const parsed = Number.parseInt(z.string().parse(a), 10); + return Number.isNaN(parsed) ? null : parsed; + } + return null; + }, z + .number() + .gte(0, "Range must be 0 - 65535") + .lte(65535, "Range must be 0 - 65535") + .nullable()), }); type DockerProvider = z.infer; interface Props { - mariadbId: string; + mariadbId: string; } export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); - const { data, refetch } = api.mariadb.one.useQuery({ mariadbId }); - const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation(); - const [connectionUrl, setConnectionUrl] = useState(""); - const getIp = data?.server?.ipAddress || ip; - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(DockerProviderSchema), - }); + const { data: ip } = api.settings.getIp.useQuery(); + const { data, refetch } = api.mariadb.one.useQuery({ mariadbId }); + const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation(); + const [connectionUrl, setConnectionUrl] = useState(""); + const getIp = data?.server?.ipAddress || ip; + const form = useForm({ + defaultValues: {}, + resolver: zodResolver(DockerProviderSchema), + }); - useEffect(() => { - if (data?.externalPort) { - form.reset({ - externalPort: data.externalPort, - }); - } - }, [form.reset, data, form]); + useEffect(() => { + if (data?.externalPort) { + form.reset({ + externalPort: data.externalPort, + }); + } + }, [form.reset, data, form]); - const onSubmit = async (values: DockerProvider) => { - await mutateAsync({ - externalPort: values.externalPort, - mariadbId, - }) - .then(async () => { - toast.success("External Port updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error saving the external port"); - }); - }; + const onSubmit = async (values: DockerProvider) => { + await mutateAsync({ + externalPort: values.externalPort, + mariadbId, + }) + .then(async () => { + toast.success("External Port updated"); + await refetch(); + }) + .catch(() => { + toast.error("Error saving the external port"); + }); + }; - useEffect(() => { - const buildConnectionUrl = () => { - const port = form.watch("externalPort") || data?.externalPort; + useEffect(() => { + const buildConnectionUrl = () => { + const port = form.watch("externalPort") || data?.externalPort; - return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`; - }; + return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`; + }; - setConnectionUrl(buildConnectionUrl()); - }, [ - data?.appName, - data?.externalPort, - data?.databasePassword, - form, - data?.databaseName, - data?.databaseUser, - getIp, - ]); - return ( - <> -
- - - External Credentials - - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database - - - - {!getIp && ( - - You need to set an IP address in your{" "} - - {data?.serverId - ? "Remote Servers -> Server -> Edit Server -> Update IP Address" - : "Web Server -> Server -> Update Server IP"} - {" "} - to fix the database url connection. - - )} -
- -
-
- { - return ( - - External Port (Internet) - - - - - - ); - }} - /> -
-
- {!!data?.externalPort && ( -
-
- {/* jdbc:mariadb://5.161.59.207:3306/pixel-calculate?user=mariadb&password=HdVXfq6hM7W7F1 */} - - -
-
- )} + setConnectionUrl(buildConnectionUrl()); + }, [ + data?.appName, + data?.externalPort, + data?.databasePassword, + form, + data?.databaseName, + data?.databaseUser, + getIp, + ]); + return ( + <> +
+ + + External Credentials + + In order to make the database reachable trought internet is + required to set a port, make sure the port is not used by another + application or database + + + + {!getIp && ( + + You need to set an IP address in your{" "} + + {data?.serverId + ? "Remote Servers -> Server -> Edit Server -> Update IP Address" + : "Web Server -> Server -> Update Server IP"} + {" "} + to fix the database url connection. + + )} + + +
+
+ { + return ( + + External Port (Internet) + + + + + + ); + }} + /> +
+
+ {!!data?.externalPort && ( +
+
+ {/* jdbc:mariadb://5.161.59.207:3306/pixel-calculate?user=mariadb&password=HdVXfq6hM7W7F1 */} + + +
+
+ )} -
- -
- - -
-
-
- - ); +
+ +
+ + +
+
+
+ + ); }; diff --git a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx index b5ed9f863..75772bfdf 100644 --- a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx +++ b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx @@ -27,144 +27,148 @@ import { toast } from "sonner"; import { z } from "zod"; const DockerProviderSchema = z.object({ - externalPort: z.preprocess((a) => { - if (a !== null) { - const parsed = Number.parseInt(z.string().parse(a), 10); - return Number.isNaN(parsed) ? null : parsed; - } - return null; - }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()), + externalPort: z.preprocess((a) => { + if (a !== null) { + const parsed = Number.parseInt(z.string().parse(a), 10); + return Number.isNaN(parsed) ? null : parsed; + } + return null; + }, z + .number() + .gte(0, "Range must be 0 - 65535") + .lte(65535, "Range must be 0 - 65535") + .nullable()), }); type DockerProvider = z.infer; interface Props { - mongoId: string; + mongoId: string; } export const ShowExternalMongoCredentials = ({ mongoId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); - const { data, refetch } = api.mongo.one.useQuery({ mongoId }); - const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation(); - const [connectionUrl, setConnectionUrl] = useState(""); - const getIp = data?.server?.ipAddress || ip; - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(DockerProviderSchema), - }); + const { data: ip } = api.settings.getIp.useQuery(); + const { data, refetch } = api.mongo.one.useQuery({ mongoId }); + const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation(); + const [connectionUrl, setConnectionUrl] = useState(""); + const getIp = data?.server?.ipAddress || ip; + const form = useForm({ + defaultValues: {}, + resolver: zodResolver(DockerProviderSchema), + }); - useEffect(() => { - if (data?.externalPort) { - form.reset({ - externalPort: data.externalPort, - }); - } - }, [form.reset, data, form]); + useEffect(() => { + if (data?.externalPort) { + form.reset({ + externalPort: data.externalPort, + }); + } + }, [form.reset, data, form]); - const onSubmit = async (values: DockerProvider) => { - await mutateAsync({ - externalPort: values.externalPort, - mongoId, - }) - .then(async () => { - toast.success("External Port updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error saving the external port"); - }); - }; + const onSubmit = async (values: DockerProvider) => { + await mutateAsync({ + externalPort: values.externalPort, + mongoId, + }) + .then(async () => { + toast.success("External Port updated"); + await refetch(); + }) + .catch(() => { + toast.error("Error saving the external port"); + }); + }; - useEffect(() => { - const buildConnectionUrl = () => { - const port = form.watch("externalPort") || data?.externalPort; + useEffect(() => { + const buildConnectionUrl = () => { + const port = form.watch("externalPort") || data?.externalPort; - return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`; - }; + return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`; + }; - setConnectionUrl(buildConnectionUrl()); - }, [ - data?.appName, - data?.externalPort, - data?.databasePassword, - form, - data?.databaseUser, - getIp, - ]); + setConnectionUrl(buildConnectionUrl()); + }, [ + data?.appName, + data?.externalPort, + data?.databasePassword, + form, + data?.databaseUser, + getIp, + ]); - return ( - <> -
- - - External Credentials - - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database - - - - {!getIp && ( - - You need to set an IP address in your{" "} - - {data?.serverId - ? "Remote Servers -> Server -> Edit Server -> Update IP Address" - : "Web Server -> Server -> Update Server IP"} - {" "} - to fix the database url connection. - - )} -
- -
-
- { - return ( - - External Port (Internet) - - - - - - ); - }} - /> -
-
- {!!data?.externalPort && ( -
-
- - -
-
- )} + return ( + <> +
+ + + External Credentials + + In order to make the database reachable trought internet is + required to set a port, make sure the port is not used by another + application or database + + + + {!getIp && ( + + You need to set an IP address in your{" "} + + {data?.serverId + ? "Remote Servers -> Server -> Edit Server -> Update IP Address" + : "Web Server -> Server -> Update Server IP"} + {" "} + to fix the database url connection. + + )} + + +
+
+ { + return ( + + External Port (Internet) + + + + + + ); + }} + /> +
+
+ {!!data?.externalPort && ( +
+
+ + +
+
+ )} -
- -
- - -
-
-
- - ); +
+ +
+ + +
+
+
+ + ); }; diff --git a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx index 2c8ed5f5b..73f99b7d0 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx @@ -27,144 +27,148 @@ import { toast } from "sonner"; import { z } from "zod"; const DockerProviderSchema = z.object({ - externalPort: z.preprocess((a) => { - if (a !== null) { - const parsed = Number.parseInt(z.string().parse(a), 10); - return Number.isNaN(parsed) ? null : parsed; - } - return null; - }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()), + externalPort: z.preprocess((a) => { + if (a !== null) { + const parsed = Number.parseInt(z.string().parse(a), 10); + return Number.isNaN(parsed) ? null : parsed; + } + return null; + }, z + .number() + .gte(0, "Range must be 0 - 65535") + .lte(65535, "Range must be 0 - 65535") + .nullable()), }); type DockerProvider = z.infer; interface Props { - mysqlId: string; + mysqlId: string; } export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); - const { data, refetch } = api.mysql.one.useQuery({ mysqlId }); - const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation(); - const [connectionUrl, setConnectionUrl] = useState(""); - const getIp = data?.server?.ipAddress || ip; - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(DockerProviderSchema), - }); + const { data: ip } = api.settings.getIp.useQuery(); + const { data, refetch } = api.mysql.one.useQuery({ mysqlId }); + const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation(); + const [connectionUrl, setConnectionUrl] = useState(""); + const getIp = data?.server?.ipAddress || ip; + const form = useForm({ + defaultValues: {}, + resolver: zodResolver(DockerProviderSchema), + }); - useEffect(() => { - if (data?.externalPort) { - form.reset({ - externalPort: data.externalPort, - }); - } - }, [form.reset, data, form]); + useEffect(() => { + if (data?.externalPort) { + form.reset({ + externalPort: data.externalPort, + }); + } + }, [form.reset, data, form]); - const onSubmit = async (values: DockerProvider) => { - await mutateAsync({ - externalPort: values.externalPort, - mysqlId, - }) - .then(async () => { - toast.success("External Port updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error saving the external port"); - }); - }; + const onSubmit = async (values: DockerProvider) => { + await mutateAsync({ + externalPort: values.externalPort, + mysqlId, + }) + .then(async () => { + toast.success("External Port updated"); + await refetch(); + }) + .catch(() => { + toast.error("Error saving the external port"); + }); + }; - useEffect(() => { - const buildConnectionUrl = () => { - const port = form.watch("externalPort") || data?.externalPort; + useEffect(() => { + const buildConnectionUrl = () => { + const port = form.watch("externalPort") || data?.externalPort; - return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`; - }; + return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`; + }; - setConnectionUrl(buildConnectionUrl()); - }, [ - data?.appName, - data?.externalPort, - data?.databasePassword, - data?.databaseName, - data?.databaseUser, - form, - getIp, - ]); - return ( - <> -
- - - External Credentials - - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database - - - - {!getIp && ( - - You need to set an IP address in your{" "} - - {data?.serverId - ? "Remote Servers -> Server -> Edit Server -> Update IP Address" - : "Web Server -> Server -> Update Server IP"} - {" "} - to fix the database url connection. - - )} -
- -
-
- { - return ( - - External Port (Internet) - - - - - - ); - }} - /> -
-
- {!!data?.externalPort && ( -
-
- - -
-
- )} + setConnectionUrl(buildConnectionUrl()); + }, [ + data?.appName, + data?.externalPort, + data?.databasePassword, + data?.databaseName, + data?.databaseUser, + form, + getIp, + ]); + return ( + <> +
+ + + External Credentials + + In order to make the database reachable trought internet is + required to set a port, make sure the port is not used by another + application or database + + + + {!getIp && ( + + You need to set an IP address in your{" "} + + {data?.serverId + ? "Remote Servers -> Server -> Edit Server -> Update IP Address" + : "Web Server -> Server -> Update Server IP"} + {" "} + to fix the database url connection. + + )} + + +
+
+ { + return ( + + External Port (Internet) + + + + + + ); + }} + /> +
+
+ {!!data?.externalPort && ( +
+
+ + +
+
+ )} -
- -
- - -
-
-
- - ); +
+ +
+ + +
+
+
+ + ); }; diff --git a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx index 0c87a7bcd..444fa0cee 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx @@ -27,146 +27,150 @@ import { toast } from "sonner"; import { z } from "zod"; const DockerProviderSchema = z.object({ - externalPort: z.preprocess((a) => { - if (a !== null) { - const parsed = Number.parseInt(z.string().parse(a), 10); - return Number.isNaN(parsed) ? null : parsed; - } - return null; - }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()), + externalPort: z.preprocess((a) => { + if (a !== null) { + const parsed = Number.parseInt(z.string().parse(a), 10); + return Number.isNaN(parsed) ? null : parsed; + } + return null; + }, z + .number() + .gte(0, "Range must be 0 - 65535") + .lte(65535, "Range must be 0 - 65535") + .nullable()), }); type DockerProvider = z.infer; interface Props { - postgresId: string; + postgresId: string; } export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); - const { data, refetch } = api.postgres.one.useQuery({ postgresId }); - const { mutateAsync, isLoading } = - api.postgres.saveExternalPort.useMutation(); - const getIp = data?.server?.ipAddress || ip; - const [connectionUrl, setConnectionUrl] = useState(""); + const { data: ip } = api.settings.getIp.useQuery(); + const { data, refetch } = api.postgres.one.useQuery({ postgresId }); + const { mutateAsync, isLoading } = + api.postgres.saveExternalPort.useMutation(); + const getIp = data?.server?.ipAddress || ip; + const [connectionUrl, setConnectionUrl] = useState(""); - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(DockerProviderSchema), - }); + const form = useForm({ + defaultValues: {}, + resolver: zodResolver(DockerProviderSchema), + }); - useEffect(() => { - if (data?.externalPort) { - form.reset({ - externalPort: data.externalPort, - }); - } - }, [form.reset, data, form]); + useEffect(() => { + if (data?.externalPort) { + form.reset({ + externalPort: data.externalPort, + }); + } + }, [form.reset, data, form]); - const onSubmit = async (values: DockerProvider) => { - await mutateAsync({ - externalPort: values.externalPort, - postgresId, - }) - .then(async () => { - toast.success("External Port updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error saving the external port"); - }); - }; + const onSubmit = async (values: DockerProvider) => { + await mutateAsync({ + externalPort: values.externalPort, + postgresId, + }) + .then(async () => { + toast.success("External Port updated"); + await refetch(); + }) + .catch(() => { + toast.error("Error saving the external port"); + }); + }; - useEffect(() => { - const buildConnectionUrl = () => { - const port = form.watch("externalPort") || data?.externalPort; + useEffect(() => { + const buildConnectionUrl = () => { + const port = form.watch("externalPort") || data?.externalPort; - return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`; - }; + return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`; + }; - setConnectionUrl(buildConnectionUrl()); - }, [ - data?.appName, - data?.externalPort, - data?.databasePassword, - form, - data?.databaseName, - getIp, - ]); + setConnectionUrl(buildConnectionUrl()); + }, [ + data?.appName, + data?.externalPort, + data?.databasePassword, + form, + data?.databaseName, + getIp, + ]); - return ( - <> -
- - - External Credentials - - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database - - - - {!getIp && ( - - You need to set an IP address in your{" "} - - {data?.serverId - ? "Remote Servers -> Server -> Edit Server -> Update IP Address" - : "Web Server -> Server -> Update Server IP"} - {" "} - to fix the database url connection. - - )} -
- -
-
- { - return ( - - External Port (Internet) - - - - - - ); - }} - /> -
-
- {!!data?.externalPort && ( -
-
- - -
-
- )} + return ( + <> +
+ + + External Credentials + + In order to make the database reachable trought internet is + required to set a port, make sure the port is not used by another + application or database + + + + {!getIp && ( + + You need to set an IP address in your{" "} + + {data?.serverId + ? "Remote Servers -> Server -> Edit Server -> Update IP Address" + : "Web Server -> Server -> Update Server IP"} + {" "} + to fix the database url connection. + + )} + + +
+
+ { + return ( + + External Port (Internet) + + + + + + ); + }} + /> +
+
+ {!!data?.externalPort && ( +
+
+ + +
+
+ )} -
- -
- - -
-
-
- - ); +
+ +
+ + +
+
+
+ + ); }; diff --git a/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx index cff00a998..545150f87 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx @@ -5,58 +5,58 @@ import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; interface Props { - postgresId: string; + postgresId: string; } export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => { - const { data } = api.postgres.one.useQuery({ postgresId }); - return ( - <> -
- - - Internal Credentials - - -
-
- - -
-
- - -
-
- -
- -
-
-
- - -
+ const { data } = api.postgres.one.useQuery({ postgresId }); + return ( + <> +
+ + + Internal Credentials + + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ + +
-
- - -
+
+ + +
-
- - -
-
-
-
-
- - ); +
+ + +
+
+
+
+
+ + ); }; // ReplyError: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-w diff --git a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx index 33ed7a60e..f70cd8c90 100644 --- a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx +++ b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx @@ -28,139 +28,139 @@ import { toast } from "sonner"; import { z } from "zod"; const updatePostgresSchema = z.object({ - name: z.string().min(1, { - message: "Name is required", - }), - description: z.string().optional(), + name: z.string().min(1, { + message: "Name is required", + }), + description: z.string().optional(), }); type UpdatePostgres = z.infer; interface Props { - postgresId: string; + postgresId: string; } export const UpdatePostgres = ({ postgresId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const utils = api.useUtils(); - const { mutateAsync, error, isError, isLoading } = - api.postgres.update.useMutation(); - const { data } = api.postgres.one.useQuery( - { - postgresId, - }, - { - enabled: !!postgresId, - } - ); - const form = useForm({ - defaultValues: { - description: data?.description ?? "", - name: data?.name ?? "", - }, - resolver: zodResolver(updatePostgresSchema), - }); - useEffect(() => { - if (data) { - form.reset({ - description: data.description ?? "", - name: data.name, - }); - } - }, [data, form, form.reset]); + const [isOpen, setIsOpen] = useState(false); + const utils = api.useUtils(); + const { mutateAsync, error, isError, isLoading } = + api.postgres.update.useMutation(); + const { data } = api.postgres.one.useQuery( + { + postgresId, + }, + { + enabled: !!postgresId, + }, + ); + const form = useForm({ + defaultValues: { + description: data?.description ?? "", + name: data?.name ?? "", + }, + resolver: zodResolver(updatePostgresSchema), + }); + useEffect(() => { + if (data) { + form.reset({ + description: data.description ?? "", + name: data.name, + }); + } + }, [data, form, form.reset]); - const onSubmit = async (formData: UpdatePostgres) => { - await mutateAsync({ - name: formData.name, - postgresId: postgresId, - description: formData.description || "", - }) - .then(() => { - toast.success("Postgres updated successfully"); - utils.postgres.one.invalidate({ - postgresId: postgresId, - }); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error updating Postgres"); - }) - .finally(() => {}); - }; + const onSubmit = async (formData: UpdatePostgres) => { + await mutateAsync({ + name: formData.name, + postgresId: postgresId, + description: formData.description || "", + }) + .then(() => { + toast.success("Postgres updated successfully"); + utils.postgres.one.invalidate({ + postgresId: postgresId, + }); + setIsOpen(false); + }) + .catch(() => { + toast.error("Error updating Postgres"); + }) + .finally(() => {}); + }; - return ( - - - - - - - Modify Postgres - Update the Postgres data - - {isError && {error?.message}} + return ( + + + + + + + Modify Postgres + Update the Postgres data + + {isError && {error?.message}} -
-
-
- - ( - - Name - - - +
+
+ + + ( + + Name + + + - - - )} - /> - ( - - Description - -