From 68d0a48843d0829802e64afaae134a221b67f3f5 Mon Sep 17 00:00:00 2001 From: vicke4 Date: Fri, 21 Mar 2025 01:36:11 +0530 Subject: [PATCH 01/19] fix(backups): auto deletion of backups --- packages/server/src/utils/backups/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index 4b194efc6..c0359de10 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -80,7 +80,8 @@ export const initCronJobs = async () => { console.log( `PG-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, ); - runPostgresBackup(pg, backup); + await runPostgresBackup(pg, backup); + await keepLatestNBackups(backup, pg.serverId); }); } } @@ -112,6 +113,7 @@ export const initCronJobs = async () => { `MARIADB-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, ); await runMariadbBackup(maria, backup); + await keepLatestNBackups(backup, maria.serverId); }); } } @@ -141,6 +143,7 @@ export const initCronJobs = async () => { `MONGO-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, ); await runMongoBackup(mongo, backup); + await keepLatestNBackups(backup, mongo.serverId); }); } } @@ -170,6 +173,7 @@ export const initCronJobs = async () => { `MYSQL-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, ); await runMySqlBackup(mysql, backup); + await keepLatestNBackups(backup, mysql.serverId); }); } } From 8d227e2a2cf63920d42bbdd8ba0fb962ba40133b Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 23 Mar 2025 04:30:00 -0600 Subject: [PATCH 02/19] feat(dashboard): add informational alert for docker-compose preview requirements --- .../dashboard/compose/general/show-converted-compose.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx index 49606645c..89a9e0753 100644 --- a/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx @@ -62,6 +62,11 @@ export const ShowConvertedCompose = ({ composeId }: Props) => { {isError && {error?.message}} + + Preview your docker-compose file with added domains. Note: At least + one domain must be specified for this conversion to take effect. + +
-
- - - - - - - ); +
+ +
+ + + + + + + ); }; 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 - -