From 56b52b3f9c944af9631f78ee82f7e80642eb590f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Wed, 24 Jul 2024 00:04:59 -0600 Subject: [PATCH 01/18] refactor(docker-build): replace docker build with the command --- server/utils/builders/docker-file.ts | 44 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/server/utils/builders/docker-file.ts b/server/utils/builders/docker-file.ts index d8ead600e..46aa44e53 100644 --- a/server/utils/builders/docker-file.ts +++ b/server/utils/builders/docker-file.ts @@ -1,9 +1,8 @@ import type { WriteStream } from "node:fs"; -import { docker } from "@/server/constants"; -import { prepareBuildArgs } from "@/server/utils/docker/utils"; -import * as tar from "tar-fs"; +import { prepareEnvironmentVariables } from "@/server/utils/docker/utils"; import type { ApplicationNested } from "."; import { getBuildAppDirectory } from "../filesystem/directory"; +import { spawnAsync } from "../process/spawnAsync"; import { createEnvFile } from "./utils"; export const buildCustomDocker = async ( @@ -14,29 +13,30 @@ export const buildCustomDocker = async ( const dockerFilePath = getBuildAppDirectory(application); try { const image = `${appName}`; + const contextPath = dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || "."; - const tarStream = tar.pack(contextPath); + const args = prepareEnvironmentVariables(buildArgs); + + const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."]; + + for (const arg of args) { + commandArgs.push("--build-arg", arg); + } createEnvFile(dockerFilePath, env); - - const stream = await docker.buildImage(tarStream, { - t: image, - buildargs: prepareBuildArgs(buildArgs), - dockerfile: dockerFilePath.substring(dockerFilePath.lastIndexOf("/") + 1), - }); - - await new Promise((resolve, reject) => { - docker.modem.followProgress( - stream, - (err, res) => (err ? reject(err) : resolve(res)), - (event) => { - if (event.stream) { - writeStream.write(event.stream); - } - }, - ); - }); + await spawnAsync( + "docker", + commandArgs, + (data) => { + if (writeStream.writable) { + writeStream.write(data); + } + }, + { + cwd: contextPath, + }, + ); } catch (error) { throw error; } From 13c686c2284453cb754d94e1a892211651a5e928 Mon Sep 17 00:00:00 2001 From: Lorenzo Migliorero Date: Wed, 24 Jul 2024 18:47:52 +0200 Subject: [PATCH 02/18] feat: condition certificate --- .../application/domains/add-domain.tsx | 131 +++++---- .../application/domains/show-domains.tsx | 21 +- .../application/domains/update-domain.tsx | 254 ------------------ server/db/schema/domain.ts | 4 +- 4 files changed, 98 insertions(+), 312 deletions(-) delete mode 100644 components/dashboard/application/domains/update-domain.tsx diff --git a/components/dashboard/application/domains/add-domain.tsx b/components/dashboard/application/domains/add-domain.tsx index 17adf2755..59cc05c3b 100644 --- a/components/dashboard/application/domains/add-domain.tsx +++ b/components/dashboard/application/domains/add-domain.tsx @@ -29,38 +29,52 @@ import { import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/; -// const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/; -// .regex(hostnameRegex -const addDomain = z.object({ - host: z.string().min(1, "Hostname is required"), +const domain = z.object({ + host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }), path: z.string().min(1), - port: z.number(), + port: z + .number() + .min(1, { message: "Port must be at least 1" }) + .max(65535, { message: "Port must be 65535 or below" }), https: z.boolean(), certificateType: z.enum(["letsencrypt", "none"]), }); -type AddDomain = z.infer; +type Domain = z.infer; interface Props { applicationId: string; - children?: React.ReactNode; + domainId?: string; + children: React.ReactNode; } export const AddDomain = ({ applicationId, - children = , + domainId = "", + children, }: Props) => { + const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); + const { data } = api.domain.one.useQuery( + { + domainId, + }, + { + enabled: !!domainId, + }, + ); - const { mutateAsync, isError, error } = api.domain.create.useMutation(); + const { mutateAsync, isError, error } = domainId + ? api.domain.update.useMutation() + : api.domain.create.useMutation(); - const form = useForm({ + const form = useForm({ defaultValues: { host: "", https: false, @@ -68,15 +82,29 @@ export const AddDomain = ({ port: 3000, certificateType: "none", }, - resolver: zodResolver(addDomain), + resolver: zodResolver(domain), }); useEffect(() => { - form.reset(); - }, [form, form.reset, form.formState.isSubmitSuccessful]); + if (data) { + form.reset(data); + } + }, [form, form.reset, data]); - const onSubmit = async (data: AddDomain) => { + const dictionary = { + success: domainId ? "Domain Updated" : "Domain Created", + error: domainId + ? "Error to update the domain" + : "Error to create the domain", + submit: domainId ? "Update" : "Create", + dialogDescription: domainId + ? "In this section you can edit a domain" + : "In this section you can add domains", + }; + + const onSubmit = async (data: Domain) => { await mutateAsync({ + domainId, applicationId, host: data.host, https: data.https, @@ -85,27 +113,27 @@ export const AddDomain = ({ certificateType: data.certificateType, }) .then(async () => { - toast.success("Domain Created"); + toast.success(dictionary.success); await utils.domain.byApplicationId.invalidate({ applicationId, }); await utils.application.readTraefikConfig.invalidate({ applicationId }); + setIsOpen(false); }) .catch(() => { - toast.error("Error to create the domain"); + toast.error(dictionary.error); + setIsOpen(false); }); }; return ( - + - + {children} Domain - - In this section you can add custom domains - + {dictionary.dialogDescription} {isError && {error?.message}} @@ -169,33 +197,36 @@ export const AddDomain = ({ ); }} /> - ( - - Certificate - + + + + + + + + None + + Letsencrypt (Default) + + + + + + )} + /> + )} - - None - - Letsencrypt (Default) - - - - - - )} - /> - Create + {dictionary.submit} diff --git a/components/dashboard/application/domains/show-domains.tsx b/components/dashboard/application/domains/show-domains.tsx index 5aed35243..d7724ce72 100644 --- a/components/dashboard/application/domains/show-domains.tsx +++ b/components/dashboard/application/domains/show-domains.tsx @@ -8,13 +8,11 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { ExternalLink, GlobeIcon, RefreshCcw } from "lucide-react"; +import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react"; import Link from "next/link"; -import React from "react"; import { AddDomain } from "./add-domain"; import { DeleteDomain } from "./delete-domain"; import { GenerateDomain } from "./generate-domain"; -import { UpdateDomain } from "./update-domain"; interface Props { applicationId: string; @@ -43,7 +41,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
{data && data?.length > 0 && ( - Add Domain + )} {data && data?.length > 0 && ( @@ -61,7 +61,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
- Add Domain + @@ -90,7 +92,14 @@ export const ShowDomains = ({ applicationId }: Props) => { {item.https ? "HTTPS" : "HTTP"}
- + + +
diff --git a/components/dashboard/application/domains/update-domain.tsx b/components/dashboard/application/domains/update-domain.tsx deleted file mode 100644 index 6614a4803..000000000 --- a/components/dashboard/application/domains/update-domain.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Switch } from "@/components/ui/switch"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon } from "lucide-react"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/; - -const updateDomain = z.object({ - host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }), - path: z.string().min(1), - port: z - .number() - .min(1, { message: "Port must be at least 1" }) - .max(65535, { message: "Port must be 65535 or below" }), - https: z.boolean(), - certificateType: z.enum(["letsencrypt", "none"]), -}); - -type UpdateDomain = z.infer; - -interface Props { - domainId: string; -} - -export const UpdateDomain = ({ domainId }: Props) => { - const utils = api.useUtils(); - const { data, refetch } = api.domain.one.useQuery( - { - domainId, - }, - { - enabled: !!domainId, - }, - ); - const { mutateAsync, isError, error } = api.domain.update.useMutation(); - - const form = useForm({ - defaultValues: { - host: "", - https: true, - path: "/", - port: 3000, - certificateType: "none", - }, - resolver: zodResolver(updateDomain), - }); - - useEffect(() => { - if (data) { - form.reset({ - host: data.host || "", - port: data.port || 3000, - path: data.path || "/", - https: data.https, - certificateType: data.certificateType, - }); - } - }, [form, form.reset, data]); - - const onSubmit = async (data: UpdateDomain) => { - await mutateAsync({ - domainId, - host: data.host, - https: data.https, - path: data.path, - port: data.port, - certificateType: data.certificateType, - }) - .then(async (data) => { - toast.success("Domain Updated"); - await refetch(); - await utils.domain.byApplicationId.invalidate({ - applicationId: data?.applicationId, - }); - await utils.application.readTraefikConfig.invalidate({ - applicationId: data?.applicationId, - }); - }) - .catch(() => { - toast.error("Error to update the domain"); - }); - }; - return ( - - - - - - - Domain - - In this section you can add custom domains - - - {isError && {error?.message}} - -
- -
-
- ( - - Host - - - - - - - )} - /> - - { - return ( - - Path - - - - - - ); - }} - /> - - { - return ( - - Container Port - - { - field.onChange(Number.parseInt(e.target.value)); - }} - /> - - - - ); - }} - /> - ( - - Certificate - - - - )} - /> - ( - -
- HTTPS - - Automatically provision SSL Certificate. - -
- - - -
- )} - /> -
-
-
- - - - - -
-
- ); -}; diff --git a/server/db/schema/domain.ts b/server/db/schema/domain.ts index 3ceca6b52..dad920909 100644 --- a/server/db/schema/domain.ts +++ b/server/db/schema/domain.ts @@ -13,8 +13,8 @@ export const domains = pgTable("domain", { .$defaultFn(() => nanoid()), host: text("host").notNull(), https: boolean("https").notNull().default(false), - port: integer("port").default(80), - path: text("path").default("/"), + port: integer("port").default(80).notNull(), + path: text("path").default("/").notNull(), uniqueConfigKey: serial("uniqueConfigKey"), createdAt: text("createdAt") .notNull() From fa5b75e6fbd4df6147b36391c6136bdde4812a68 Mon Sep 17 00:00:00 2001 From: Lorenzo Migliorero Date: Wed, 24 Jul 2024 19:01:18 +0200 Subject: [PATCH 03/18] fix: type --- __test__/traefik/traefik.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__test__/traefik/traefik.test.ts b/__test__/traefik/traefik.test.ts index f10eefb33..f6af400d1 100644 --- a/__test__/traefik/traefik.test.ts +++ b/__test__/traefik/traefik.test.ts @@ -63,8 +63,8 @@ const baseDomain: Domain = { domainId: "", host: "", https: false, - path: null, - port: null, + path: "", + port: 3000, uniqueConfigKey: 1, }; From 7fd35999b1918d35f0220334e6d81e0e1ad9a236 Mon Sep 17 00:00:00 2001 From: Lorenzo Migliorero Date: Wed, 24 Jul 2024 19:38:25 +0200 Subject: [PATCH 04/18] fix: terminal text selectable --- components/dashboard/docker/logs/docker-logs-id.tsx | 5 ++++- styles/globals.css | 6 ------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/components/dashboard/docker/logs/docker-logs-id.tsx b/components/dashboard/docker/logs/docker-logs-id.tsx index be27aeda3..a269cc0c5 100644 --- a/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/components/dashboard/docker/logs/docker-logs-id.tsx @@ -23,8 +23,11 @@ export const DockerLogsId: React.FC = ({ id, containerId }) => { cursorBlink: true, cols: 80, rows: 30, - lineHeight: 1.4, + lineHeight: 1.25, fontWeight: 400, + fontSize: 14, + fontFamily: + 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', convertEol: true, theme: { diff --git a/styles/globals.css b/styles/globals.css index 3b207a0c6..9d9ffaffb 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -102,12 +102,6 @@ } } -#terminal span { - font-family: "Inter", sans-serif; - font-weight: 500; - letter-spacing: 0px !important; -} - /* Codemirror */ .cm-editor { @apply w-full h-full rounded-md overflow-hidden border border-solid border-border outline-none; From 32f35a6ca0d4f41daa77719c22c276bd3b3ee566 Mon Sep 17 00:00:00 2001 From: Yuki <60097976+binaryYuki@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:25:28 +0800 Subject: [PATCH 05/18] Update traefik-setup.ts --- server/setup/traefik-setup.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts index 4fc383ac3..692be4ce9 100644 --- a/server/setup/traefik-setup.ts +++ b/server/setup/traefik-setup.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, writeFileSync, chmodSync } from "node:fs"; import path from "node:path"; import type { CreateServiceOptions } from "dockerode"; import { dump } from "js-yaml"; @@ -184,6 +184,9 @@ export const createDefaultTraefikConfig = () => { const yamlStr = dump(configObject); mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true }); writeFileSync(mainConfig, yamlStr, "utf8"); + + const acmeJsonPath = "/etc/dokploy/traefik/dynamic/acme.json"; + chmodSync(acmeJsonPath, '600'); }; export const createDefaultMiddlewares = () => { From 087e2c81ccde63c7f374ca39b243b9a2c61a2264 Mon Sep 17 00:00:00 2001 From: Yuki <60097976+binaryYuki@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:34:18 +0800 Subject: [PATCH 06/18] accept suggestion by coderabbit Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- server/setup/traefik-setup.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts index 692be4ce9..3b07d518d 100644 --- a/server/setup/traefik-setup.ts +++ b/server/setup/traefik-setup.ts @@ -186,7 +186,11 @@ export const createDefaultTraefikConfig = () => { writeFileSync(mainConfig, yamlStr, "utf8"); const acmeJsonPath = "/etc/dokploy/traefik/dynamic/acme.json"; - chmodSync(acmeJsonPath, '600'); + if (existsSync(acmeJsonPath)) { + chmodSync(acmeJsonPath, '600'); + } else { + console.error(`File not found: ${acmeJsonPath}`); + } }; export const createDefaultMiddlewares = () => { From 734a6607c8a837caca406271cf5159f690c2a68d Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Wed, 24 Jul 2024 23:31:03 -0600 Subject: [PATCH 07/18] fix(databases): add ip to useeffect deps --- .../mariadb/general/show-external-mariadb-credentials.tsx | 1 + .../dashboard/mongo/general/show-external-mongo-credentials.tsx | 1 + .../dashboard/mysql/general/show-external-mysql-credentials.tsx | 1 + .../postgres/general/show-external-postgres-credentials.tsx | 1 + .../dashboard/redis/general/show-external-redis-credentials.tsx | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx index b4ed7dc49..a908de077 100644 --- a/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx +++ b/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx @@ -90,6 +90,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => { form, data?.databaseName, data?.databaseUser, + ip, ]); return ( <> diff --git a/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/components/dashboard/mongo/general/show-external-mongo-credentials.tsx index 52b584fb1..36d04c9cb 100644 --- a/components/dashboard/mongo/general/show-external-mongo-credentials.tsx +++ b/components/dashboard/mongo/general/show-external-mongo-credentials.tsx @@ -90,6 +90,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => { data?.databasePassword, form, data?.databaseUser, + ip, ]); return ( diff --git a/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/components/dashboard/mysql/general/show-external-mysql-credentials.tsx index caaf85563..18c1adafe 100644 --- a/components/dashboard/mysql/general/show-external-mysql-credentials.tsx +++ b/components/dashboard/mysql/general/show-external-mysql-credentials.tsx @@ -91,6 +91,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => { data?.databaseName, data?.databaseUser, form, + ip, ]); return ( <> diff --git a/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/components/dashboard/postgres/general/show-external-postgres-credentials.tsx index d27022b7b..28a96eb24 100644 --- a/components/dashboard/postgres/general/show-external-postgres-credentials.tsx +++ b/components/dashboard/postgres/general/show-external-postgres-credentials.tsx @@ -92,6 +92,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => { data?.databasePassword, form, data?.databaseName, + ip, ]); return ( diff --git a/components/dashboard/redis/general/show-external-redis-credentials.tsx b/components/dashboard/redis/general/show-external-redis-credentials.tsx index 136f0ef49..b88328417 100644 --- a/components/dashboard/redis/general/show-external-redis-credentials.tsx +++ b/components/dashboard/redis/general/show-external-redis-credentials.tsx @@ -85,7 +85,7 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => { }; setConnectionUrl(buildConnectionUrl()); - }, [data?.appName, data?.externalPort, data?.databasePassword, form]); + }, [data?.appName, data?.externalPort, data?.databasePassword, form, ip]); return ( <>
From 0af532f87ee26f6db649caecb410fe38bcb691bc Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Thu, 25 Jul 2024 00:51:15 -0600 Subject: [PATCH 08/18] refactor(directories): add 600 permissions to SSH_PATH #262 --- server/setup/config-paths.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/setup/config-paths.ts b/server/setup/config-paths.ts index 2b2c262b2..f609bc6c2 100644 --- a/server/setup/config-paths.ts +++ b/server/setup/config-paths.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync } from "node:fs"; +import { chmodSync, existsSync, mkdirSync } from "node:fs"; import { APPLICATIONS_PATH, BASE_PATH, @@ -32,6 +32,9 @@ export const setupDirectories = () => { for (const dir of directories) { try { createDirectoryIfNotExist(dir); + if (dir === SSH_PATH) { + chmodSync(SSH_PATH, "600"); + } } catch (error) { console.log(error, " On path: ", dir); } From 115c8641e74e106b2cdbc460d04660c97e95f3a9 Mon Sep 17 00:00:00 2001 From: Lorenzo Migliorero Date: Thu, 25 Jul 2024 08:59:10 +0200 Subject: [PATCH 09/18] fix: nullable type --- __test__/traefik/traefik.test.ts | 4 ++-- components/dashboard/application/domains/add-domain.tsx | 5 +++-- server/db/schema/domain.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/__test__/traefik/traefik.test.ts b/__test__/traefik/traefik.test.ts index f6af400d1..f10eefb33 100644 --- a/__test__/traefik/traefik.test.ts +++ b/__test__/traefik/traefik.test.ts @@ -63,8 +63,8 @@ const baseDomain: Domain = { domainId: "", host: "", https: false, - path: "", - port: 3000, + path: null, + port: null, uniqueConfigKey: 1, }; diff --git a/components/dashboard/application/domains/add-domain.tsx b/components/dashboard/application/domains/add-domain.tsx index 59cc05c3b..b8ccae1c7 100644 --- a/components/dashboard/application/domains/add-domain.tsx +++ b/components/dashboard/application/domains/add-domain.tsx @@ -37,11 +37,12 @@ const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/; const domain = z.object({ host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }), - path: z.string().min(1), + path: z.string().min(1).nullable(), port: z .number() .min(1, { message: "Port must be at least 1" }) - .max(65535, { message: "Port must be 65535 or below" }), + .max(65535, { message: "Port must be 65535 or below" }) + .nullable(), https: z.boolean(), certificateType: z.enum(["letsencrypt", "none"]), }); diff --git a/server/db/schema/domain.ts b/server/db/schema/domain.ts index dad920909..3ceca6b52 100644 --- a/server/db/schema/domain.ts +++ b/server/db/schema/domain.ts @@ -13,8 +13,8 @@ export const domains = pgTable("domain", { .$defaultFn(() => nanoid()), host: text("host").notNull(), https: boolean("https").notNull().default(false), - port: integer("port").default(80).notNull(), - path: text("path").default("/").notNull(), + port: integer("port").default(80), + path: text("path").default("/"), uniqueConfigKey: serial("uniqueConfigKey"), createdAt: text("createdAt") .notNull() From e72add74c380f851ac0a7565cec3e8db3be064d3 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Thu, 25 Jul 2024 01:02:10 -0600 Subject: [PATCH 10/18] fix(templates): change path of volumes to be in files folder to prevent delete the volumes --- templates/appsmith/docker-compose.yml | 26 +++++++++++++------------- templates/directus/docker-compose.yml | 6 +++--- templates/listmonk/docker-compose.yml | 13 +++++++++---- templates/odoo/docker-compose.yml | 7 +++---- templates/plausible/docker-compose.yml | 8 ++++---- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/templates/appsmith/docker-compose.yml b/templates/appsmith/docker-compose.yml index 4fbdc3418..ad07a709b 100644 --- a/templates/appsmith/docker-compose.yml +++ b/templates/appsmith/docker-compose.yml @@ -1,18 +1,18 @@ version: "3.8" services: - appsmith: - image: index.docker.io/appsmith/appsmith-ee:v1.29 - networks: - - dokploy-network - ports: - - ${APP_SMITH_PORT} - labels: - - "traefik.enable=true" - - "traefik.http.routers.${HASH}.rule=Host(`${APP_SMITH_HOST}`)" - - "traefik.http.services.${HASH}.loadbalancer.server.port=${APP_SMITH_PORT}" - volumes: - - ./stacks:/appsmith-stacks + appsmith: + image: index.docker.io/appsmith/appsmith-ee:v1.29 + networks: + - dokploy-network + ports: + - ${APP_SMITH_PORT} + labels: + - "traefik.enable=true" + - "traefik.http.routers.${HASH}.rule=Host(`${APP_SMITH_HOST}`)" + - "traefik.http.services.${HASH}.loadbalancer.server.port=${APP_SMITH_PORT}" + volumes: + - ../files/stacks:/appsmith-stacks networks: dokploy-network: - external: true \ No newline at end of file + external: true diff --git a/templates/directus/docker-compose.yml b/templates/directus/docker-compose.yml index c022e0b38..08a5db45f 100644 --- a/templates/directus/docker-compose.yml +++ b/templates/directus/docker-compose.yml @@ -23,8 +23,8 @@ services: ports: - 8055 volumes: - - ./uploads:/directus/uploads - - ./extensions:/directus/extensions + - ../files/uploads:/directus/uploads + - ../files/extensions:/directus/extensions depends_on: - cache - database @@ -53,4 +53,4 @@ networks: dokploy-network: external: true volumes: - directus: \ No newline at end of file + directus: diff --git a/templates/listmonk/docker-compose.yml b/templates/listmonk/docker-compose.yml index e17b76577..beabf4474 100644 --- a/templates/listmonk/docker-compose.yml +++ b/templates/listmonk/docker-compose.yml @@ -23,10 +23,15 @@ services: networks: - dokploy-network volumes: - - ./config.toml:/listmonk/config.toml + - ../files/config.toml:/listmonk/config.toml depends_on: - db - command: [sh, -c, "sleep 3 && ./listmonk --install --idempotent --yes --config config.toml"] + command: + [ + sh, + -c, + "sleep 3 && ./listmonk --install --idempotent --yes --config config.toml", + ] app: restart: unless-stopped @@ -41,7 +46,7 @@ services: - db - setup volumes: - - ./config.toml:/listmonk/config.toml + - ../files/config.toml:/listmonk/config.toml labels: - "traefik.enable=true" - "traefik.http.routers.${HASH}.rule=Host(`${LISTMONK_HOST}`)" @@ -50,7 +55,7 @@ services: volumes: listmonk-data: driver: local - + networks: dokploy-network: external: true diff --git a/templates/odoo/docker-compose.yml b/templates/odoo/docker-compose.yml index 8538bc728..e6e2a7242 100644 --- a/templates/odoo/docker-compose.yml +++ b/templates/odoo/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" services: web: image: odoo:16.0 @@ -18,8 +18,8 @@ services: - "traefik.http.services.${HASH}.loadbalancer.server.port=${ODOO_PORT}" volumes: - odoo-web-data:/var/lib/odoo - - ./config:/etc/odoo - - ./addons:/mnt/extra-addons + - ../files/config:/etc/odoo + - ../files/addons:/mnt/extra-addons db: image: postgres:13 @@ -36,7 +36,6 @@ volumes: odoo-web-data: odoo-db-data: - networks: dokploy-network: external: true diff --git a/templates/plausible/docker-compose.yml b/templates/plausible/docker-compose.yml index cc4c41e29..350cd87c5 100644 --- a/templates/plausible/docker-compose.yml +++ b/templates/plausible/docker-compose.yml @@ -18,8 +18,8 @@ services: volumes: - event-data:/var/lib/clickhouse - event-logs:/var/log/clickhouse-server - - ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro - - ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro + - ../files/clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro + - ../files/clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro ulimits: nofile: soft: 262144 @@ -50,7 +50,7 @@ volumes: driver: local event-logs: driver: local - + networks: dokploy-network: - external: true \ No newline at end of file + external: true From 4cacc6b3d12e47fdc8a0f28d07a6394cdc2b41e4 Mon Sep 17 00:00:00 2001 From: Lorenzo Migliorero Date: Thu, 25 Jul 2024 09:15:13 +0200 Subject: [PATCH 11/18] fix: type --- .../application/domains/add-domain.tsx | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/components/dashboard/application/domains/add-domain.tsx b/components/dashboard/application/domains/add-domain.tsx index b8ccae1c7..623779dd0 100644 --- a/components/dashboard/application/domains/add-domain.tsx +++ b/components/dashboard/application/domains/add-domain.tsx @@ -37,12 +37,11 @@ const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/; const domain = z.object({ host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }), - path: z.string().min(1).nullable(), + path: z.string().min(1), port: z .number() .min(1, { message: "Port must be at least 1" }) - .max(65535, { message: "Port must be 65535 or below" }) - .nullable(), + .max(65535, { message: "Port must be 65535 or below" }), https: z.boolean(), certificateType: z.enum(["letsencrypt", "none"]), }); @@ -75,20 +74,26 @@ export const AddDomain = ({ ? api.domain.update.useMutation() : api.domain.create.useMutation(); + const defaultValues: Domain = { + host: "", + https: false, + path: "/", + port: 3000, + certificateType: "none", + }; + const form = useForm({ - defaultValues: { - host: "", - https: false, - path: "/", - port: 3000, - certificateType: "none", - }, + defaultValues, resolver: zodResolver(domain), }); useEffect(() => { if (data) { - form.reset(data); + form.reset({ + ...data, + path: data.path || defaultValues.path, + port: data.port || defaultValues.port, + }); } }, [form, form.reset, data]); @@ -107,11 +112,7 @@ export const AddDomain = ({ await mutateAsync({ domainId, applicationId, - host: data.host, - https: data.https, - path: data.path, - port: data.port, - certificateType: data.certificateType, + ...data, }) .then(async () => { toast.success(dictionary.success); From 54c7572447a46e720c30034567f5958106953c0b Mon Sep 17 00:00:00 2001 From: Yuki <60097976+binaryYuki@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:18:59 +0800 Subject: [PATCH 12/18] check if exist Check if the file already exist, if yes modify its mode --- server/setup/traefik-setup.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts index 3b07d518d..b6f5867a8 100644 --- a/server/setup/traefik-setup.ts +++ b/server/setup/traefik-setup.ts @@ -90,6 +90,13 @@ export const createDefaultServerTraefikConfig = () => { console.log("Default traefik config already exists"); return; } + + const acmeJsonPath = "/etc/dokploy/traefik/dynamic/acme.json"; + if (existsSync(acmeJsonPath)) { + chmodSync(acmeJsonPath, '600'); + } else { + console.error(`File not found: ${acmeJsonPath}`); + } const appName = "dokploy"; const serviceURLDefault = `http://${appName}:${process.env.PORT || 3000}`; @@ -184,13 +191,6 @@ export const createDefaultTraefikConfig = () => { const yamlStr = dump(configObject); mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true }); writeFileSync(mainConfig, yamlStr, "utf8"); - - const acmeJsonPath = "/etc/dokploy/traefik/dynamic/acme.json"; - if (existsSync(acmeJsonPath)) { - chmodSync(acmeJsonPath, '600'); - } else { - console.error(`File not found: ${acmeJsonPath}`); - } }; export const createDefaultMiddlewares = () => { From ef689f06d6309e30f4afbbfd80b51d8ef0d6c709 Mon Sep 17 00:00:00 2001 From: Yuki <60097976+binaryYuki@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:25:04 +0800 Subject: [PATCH 13/18] Update traefik-setup.ts --- server/setup/traefik-setup.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts index b6f5867a8..7a5fee4fa 100644 --- a/server/setup/traefik-setup.ts +++ b/server/setup/traefik-setup.ts @@ -86,17 +86,18 @@ export const initializeTraefik = async () => { export const createDefaultServerTraefikConfig = () => { const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, "dokploy.yml"); - if (existsSync(configFilePath)) { - console.log("Default traefik config already exists"); - return; - } + const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json"); - const acmeJsonPath = "/etc/dokploy/traefik/dynamic/acme.json"; if (existsSync(acmeJsonPath)) { chmodSync(acmeJsonPath, '600'); } else { console.error(`File not found: ${acmeJsonPath}`); } + + if (existsSync(configFilePath)) { + console.log("Default traefik config already exists"); + return; + } const appName = "dokploy"; const serviceURLDefault = `http://${appName}:${process.env.PORT || 3000}`; @@ -191,6 +192,12 @@ export const createDefaultTraefikConfig = () => { const yamlStr = dump(configObject); mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true }); writeFileSync(mainConfig, yamlStr, "utf8"); + const acmeJsonPath = "/etc/dokploy/traefik/dynamic/acme.json"; + if (existsSync(acmeJsonPath)) { + chmodSync(acmeJsonPath, '600'); + } else { + console.error(`File not found: ${acmeJsonPath}, func createDefaultTraefikConfig`); + } }; export const createDefaultMiddlewares = () => { From 9e47103131929e553528e817a2196d0ed69384ba Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Thu, 25 Jul 2024 01:48:52 -0600 Subject: [PATCH 14/18] refactor(script): make ipv4 first and format the url when is ipv6 #258 --- docker/prod.sh | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/docker/prod.sh b/docker/prod.sh index 4dc23da28..2b833340f 100644 --- a/docker/prod.sh +++ b/docker/prod.sh @@ -30,11 +30,6 @@ if ss -tulnp | grep ':443 ' >/dev/null; then exit 1 fi - - - - - command_exists() { command -v "$@" > /dev/null 2>&1 } @@ -46,7 +41,25 @@ else fi docker swarm leave --force 2>/dev/null -docker swarm init; + +get_ip() { + # Try to get IPv4 + local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null) + + if [ -n "$ipv4" ]; then + echo "$ipv4" + else + # Try to get IPv6 + local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null) + if [ -n "$ipv6" ]; then + echo "$ipv6" + fi + fi +} + +advertise_addr=$(get_ip) + +docker swarm init --advertise-addr $advertise_addr echo "Swarm initialized" @@ -71,19 +84,28 @@ docker service create \ --publish published=3000,target=3000,mode=host \ --update-parallelism 1 \ --update-order stop-first \ + --constraint 'node.role == manager' \ dokploy/dokploy:latest - -public_ip=$(hostname -I | awk '{print $1}') - GREEN="\033[0;32m" YELLOW="\033[1;33m" BLUE="\033[0;34m" NC="\033[0m" # No Color +format_ip_for_url() { + local ip="$1" + if echo "$ip" | grep -q ':'; then + # IPv6 + echo "[${ip}]" + else + # IPv4 + echo "${ip}" + fi +} +formatted_addr=$(format_ip_for_url "$advertise_addr") echo "" printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n" printf "${BLUE}Wait 15 seconds for the server to start${NC}\n" -printf "${YELLOW}Please go to http://${public_ip}:3000${NC}\n\n" +printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n" echo "" From 1519a715350062201500b319f05e912acea76b9b Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Thu, 25 Jul 2024 01:49:41 -0600 Subject: [PATCH 15/18] refactor(script): make ipv4 first and format the url when is ipv6 #258 --- docker/prod.sh | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/docker/prod.sh b/docker/prod.sh index 4dc23da28..2b833340f 100644 --- a/docker/prod.sh +++ b/docker/prod.sh @@ -30,11 +30,6 @@ if ss -tulnp | grep ':443 ' >/dev/null; then exit 1 fi - - - - - command_exists() { command -v "$@" > /dev/null 2>&1 } @@ -46,7 +41,25 @@ else fi docker swarm leave --force 2>/dev/null -docker swarm init; + +get_ip() { + # Try to get IPv4 + local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null) + + if [ -n "$ipv4" ]; then + echo "$ipv4" + else + # Try to get IPv6 + local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null) + if [ -n "$ipv6" ]; then + echo "$ipv6" + fi + fi +} + +advertise_addr=$(get_ip) + +docker swarm init --advertise-addr $advertise_addr echo "Swarm initialized" @@ -71,19 +84,28 @@ docker service create \ --publish published=3000,target=3000,mode=host \ --update-parallelism 1 \ --update-order stop-first \ + --constraint 'node.role == manager' \ dokploy/dokploy:latest - -public_ip=$(hostname -I | awk '{print $1}') - GREEN="\033[0;32m" YELLOW="\033[1;33m" BLUE="\033[0;34m" NC="\033[0m" # No Color +format_ip_for_url() { + local ip="$1" + if echo "$ip" | grep -q ':'; then + # IPv6 + echo "[${ip}]" + else + # IPv4 + echo "${ip}" + fi +} +formatted_addr=$(format_ip_for_url "$advertise_addr") echo "" printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n" printf "${BLUE}Wait 15 seconds for the server to start${NC}\n" -printf "${YELLOW}Please go to http://${public_ip}:3000${NC}\n\n" +printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n" echo "" From 9a4b474cdc3aba50c1fd71fc6d18f4e60802a674 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Thu, 25 Jul 2024 02:04:54 -0600 Subject: [PATCH 16/18] refactor(traefik-setup): change order of chmodsync --- server/setup/traefik-setup.ts | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts index 7a5fee4fa..e9f278604 100644 --- a/server/setup/traefik-setup.ts +++ b/server/setup/traefik-setup.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, writeFileSync, chmodSync } from "node:fs"; +import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs"; import path from "node:path"; import type { CreateServiceOptions } from "dockerode"; import { dump } from "js-yaml"; @@ -86,17 +86,10 @@ export const initializeTraefik = async () => { export const createDefaultServerTraefikConfig = () => { const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, "dokploy.yml"); - const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json"); - - if (existsSync(acmeJsonPath)) { - chmodSync(acmeJsonPath, '600'); - } else { - console.error(`File not found: ${acmeJsonPath}`); - } - + if (existsSync(configFilePath)) { - console.log("Default traefik config already exists"); - return; + console.log("Default traefik config already exists"); + return; } const appName = "dokploy"; @@ -133,6 +126,11 @@ export const createDefaultServerTraefikConfig = () => { export const createDefaultTraefikConfig = () => { const mainConfig = path.join(MAIN_TRAEFIK_PATH, "traefik.yml"); + const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json"); + + if (existsSync(acmeJsonPath)) { + chmodSync(acmeJsonPath, "600"); + } if (existsSync(mainConfig)) { console.log("Main config already exists"); return; @@ -192,12 +190,6 @@ export const createDefaultTraefikConfig = () => { const yamlStr = dump(configObject); mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true }); writeFileSync(mainConfig, yamlStr, "utf8"); - const acmeJsonPath = "/etc/dokploy/traefik/dynamic/acme.json"; - if (existsSync(acmeJsonPath)) { - chmodSync(acmeJsonPath, '600'); - } else { - console.error(`File not found: ${acmeJsonPath}, func createDefaultTraefikConfig`); - } }; export const createDefaultMiddlewares = () => { From ee58672d587829db4a7d3973a67e27316aed9d61 Mon Sep 17 00:00:00 2001 From: Lorenzo Migliorero Date: Thu, 25 Jul 2024 11:03:14 +0200 Subject: [PATCH 17/18] refactor: dry validation rules --- .../application/domains/add-domain.tsx | 42 ++++++++---------- server/db/schema/domain.ts | 43 +++++++------------ server/db/validations/index.ts | 25 +++++++++++ 3 files changed, 57 insertions(+), 53 deletions(-) create mode 100644 server/db/validations/index.ts diff --git a/components/dashboard/application/domains/add-domain.tsx b/components/dashboard/application/domains/add-domain.tsx index 623779dd0..76899a7c9 100644 --- a/components/dashboard/application/domains/add-domain.tsx +++ b/components/dashboard/application/domains/add-domain.tsx @@ -28,23 +28,14 @@ import { } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { z } from "zod"; -const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/; -const domain = z.object({ - host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }), - path: z.string().min(1), - port: z - .number() - .min(1, { message: "Port must be at least 1" }) - .max(65535, { message: "Port must be 65535 or below" }), - https: z.boolean(), - certificateType: z.enum(["letsencrypt", "none"]), -}); +import { domain } from "@/server/db/validations"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { flushSync } from "react-dom"; +import type z from "zod"; type Domain = z.infer; @@ -74,16 +65,7 @@ export const AddDomain = ({ ? api.domain.update.useMutation() : api.domain.create.useMutation(); - const defaultValues: Domain = { - host: "", - https: false, - path: "/", - port: 3000, - certificateType: "none", - }; - const form = useForm({ - defaultValues, resolver: zodResolver(domain), }); @@ -91,8 +73,9 @@ export const AddDomain = ({ if (data) { form.reset({ ...data, - path: data.path || defaultValues.path, - port: data.port || defaultValues.port, + /* Convert null to undefined */ + path: data.path || undefined, + port: data.port || undefined, }); } }, [form, form.reset, data]); @@ -120,11 +103,19 @@ export const AddDomain = ({ applicationId, }); await utils.application.readTraefikConfig.invalidate({ applicationId }); + + /* + Reset form if it was a new domain + Flushsync is needed for a bug witht he react-hook-form reset method + https://github.com/orgs/react-hook-form/discussions/7589#discussioncomment-10060621 + */ + if (!domainId) { + flushSync(() => form.reset()); + } setIsOpen(false); }) .catch(() => { toast.error(dictionary.error); - setIsOpen(false); }); }; return ( @@ -239,6 +230,7 @@ export const AddDomain = ({ Automatically provision SSL Certificate. +
({ references: [applications.applicationId], }), })); -const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/; -const createSchema = createInsertSchema(domains, { - domainId: z.string().min(1), - host: z.string().min(1), - path: z.string().min(1), - port: z.number(), - https: z.boolean(), - applicationId: z.string(), - certificateType: z.enum(["letsencrypt", "none"]), -}); -export const apiCreateDomain = createSchema - .pick({ - host: true, - path: true, - port: true, - https: true, - applicationId: true, - certificateType: true, - }) - .required(); +const createSchema = createInsertSchema(domains, domain._def.schema.shape); + +export const apiCreateDomain = createSchema.pick({ + host: true, + path: true, + port: true, + https: true, + applicationId: true, + certificateType: true, +}); export const apiFindDomain = createSchema .pick({ @@ -59,19 +49,16 @@ export const apiFindDomain = createSchema }) .required(); -export const apiFindDomainByApplication = createSchema - .pick({ - applicationId: true, - }) - .required(); +export const apiFindDomainByApplication = createSchema.pick({ + applicationId: true, +}); export const apiUpdateDomain = createSchema .pick({ - domainId: true, host: true, path: true, port: true, https: true, certificateType: true, }) - .required(); + .merge(createSchema.pick({ domainId: true }).required()); diff --git a/server/db/validations/index.ts b/server/db/validations/index.ts new file mode 100644 index 000000000..fcb6117a1 --- /dev/null +++ b/server/db/validations/index.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; + +export const domain = z + .object({ + host: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/, { + message: "Invalid hostname", + }), + path: z.string().min(1).optional(), + port: z + .number() + .min(1, { message: "Port must be at least 1" }) + .max(65535, { message: "Port must be 65535 or below" }) + .optional(), + https: z.boolean().optional(), + certificateType: z.enum(["letsencrypt", "none"]).optional(), + }) + .superRefine((input, ctx) => { + if (input.https && !input.certificateType) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["certificateType"], + message: "Required", + }); + } + }); From fe51dd6b0a98a1bffdcdfa5eab036ff754ccc269 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Fri, 26 Jul 2024 01:04:52 -0600 Subject: [PATCH 18/18] refactor: remove flush sync --- .../application/domains/add-domain.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/components/dashboard/application/domains/add-domain.tsx b/components/dashboard/application/domains/add-domain.tsx index 76899a7c9..71e44f929 100644 --- a/components/dashboard/application/domains/add-domain.tsx +++ b/components/dashboard/application/domains/add-domain.tsx @@ -34,7 +34,6 @@ import { toast } from "sonner"; import { domain } from "@/server/db/validations"; import { zodResolver } from "@hookform/resolvers/zod"; -import { flushSync } from "react-dom"; import type z from "zod"; type Domain = z.infer; @@ -52,7 +51,7 @@ export const AddDomain = ({ }: Props) => { const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); - const { data } = api.domain.one.useQuery( + const { data, refetch } = api.domain.one.useQuery( { domainId, }, @@ -61,7 +60,7 @@ export const AddDomain = ({ }, ); - const { mutateAsync, isError, error } = domainId + const { mutateAsync, isError, error, isLoading } = domainId ? api.domain.update.useMutation() : api.domain.create.useMutation(); @@ -74,11 +73,15 @@ export const AddDomain = ({ form.reset({ ...data, /* Convert null to undefined */ - path: data.path || undefined, - port: data.port || undefined, + path: data?.path || undefined, + port: data?.port || undefined, }); } - }, [form, form.reset, data]); + + if (!domainId) { + form.reset({}); + } + }, [form, form.reset, data, isLoading]); const dictionary = { success: domainId ? "Domain Updated" : "Domain Created", @@ -104,13 +107,8 @@ export const AddDomain = ({ }); await utils.application.readTraefikConfig.invalidate({ applicationId }); - /* - Reset form if it was a new domain - Flushsync is needed for a bug witht he react-hook-form reset method - https://github.com/orgs/react-hook-form/discussions/7589#discussioncomment-10060621 - */ - if (!domainId) { - flushSync(() => form.reset()); + if (domainId) { + refetch(); } setIsOpen(false); })