From 8001304e987e3dd1e88cdb172757dbcebdc34e0b Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 4 Apr 2026 00:18:19 -0600 Subject: [PATCH 1/6] feat(database-credentials): add password update functionality for MariaDB, MongoDB, MySQL, Postgres, and Redis - Introduced a new `UpdateDatabasePassword` component to facilitate password updates for database credentials. - Implemented password change mutations in the respective API routers for MariaDB, MongoDB, MySQL, Postgres, and Redis. - Enhanced user experience by providing success notifications upon successful password updates. - Updated UI components to include the new password update functionality, ensuring consistency across different database types. --- .../show-internal-mariadb-credentials.tsx | 32 ++++- .../show-internal-mongo-credentials.tsx | 17 ++- .../show-internal-mysql-credentials.tsx | 32 ++++- .../show-internal-postgres-credentials.tsx | 17 ++- .../show-internal-redis-credentials.tsx | 17 ++- .../shared/update-database-password.tsx | 126 ++++++++++++++++++ apps/dokploy/server/api/routers/mariadb.ts | 53 ++++++++ apps/dokploy/server/api/routers/mongo.ts | 46 +++++++ apps/dokploy/server/api/routers/mysql.ts | 55 ++++++++ apps/dokploy/server/api/routers/postgres.ts | 46 +++++++ apps/dokploy/server/api/routers/redis.ts | 46 +++++++ 11 files changed, 480 insertions(+), 7 deletions(-) create mode 100644 apps/dokploy/components/shared/update-database-password.tsx diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx index 170269269..8a99300ad 100644 --- a/apps/dokploy/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx +++ b/apps/dokploy/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx @@ -1,14 +1,19 @@ import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { UpdateDatabasePassword } from "@/components/shared/update-database-password"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { toast } from "sonner"; interface Props { mariadbId: string; } export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => { const { data } = api.mariadb.one.useQuery({ mariadbId }); + const utils = api.useUtils(); + const { mutateAsync: changePassword } = + api.mariadb.changePassword.useMutation(); return ( <>
@@ -28,20 +33,43 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
-
+
+ { + await changePassword({ + mariadbId, + password: newPassword, + type: "user", + }); + toast.success("Password updated successfully"); + utils.mariadb.one.invalidate({ mariadbId }); + }} + />
-
+
+ { + await changePassword({ + mariadbId, + password: newPassword, + type: "root", + }); + toast.success("Root password updated successfully"); + utils.mariadb.one.invalidate({ mariadbId }); + }} + />
diff --git a/apps/dokploy/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx b/apps/dokploy/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx index e66ea8c36..e8c73e349 100644 --- a/apps/dokploy/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx +++ b/apps/dokploy/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx @@ -1,14 +1,19 @@ import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { UpdateDatabasePassword } from "@/components/shared/update-database-password"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { toast } from "sonner"; interface Props { mongoId: string; } export const ShowInternalMongoCredentials = ({ mongoId }: Props) => { const { data } = api.mongo.one.useQuery({ mongoId }); + const utils = api.useUtils(); + const { mutateAsync: changePassword } = + api.mongo.changePassword.useMutation(); return ( <>
@@ -25,11 +30,21 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
-
+
+ { + await changePassword({ + mongoId, + password: newPassword, + }); + toast.success("Password updated successfully"); + utils.mongo.one.invalidate({ mongoId }); + }} + />
diff --git a/apps/dokploy/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx index 3f1872371..4f91c7efc 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx @@ -1,14 +1,19 @@ import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { UpdateDatabasePassword } from "@/components/shared/update-database-password"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { toast } from "sonner"; interface Props { mysqlId: string; } export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => { const { data } = api.mysql.one.useQuery({ mysqlId }); + const utils = api.useUtils(); + const { mutateAsync: changePassword } = + api.mysql.changePassword.useMutation(); return ( <>
@@ -28,20 +33,43 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
-
+
+ { + await changePassword({ + mysqlId, + password: newPassword, + type: "user", + }); + toast.success("Password updated successfully"); + utils.mysql.one.invalidate({ mysqlId }); + }} + />
-
+
+ { + await changePassword({ + mysqlId, + password: newPassword, + type: "root", + }); + toast.success("Root password updated successfully"); + utils.mysql.one.invalidate({ mysqlId }); + }} + />
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 545150f87..30e265577 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 @@ -1,14 +1,19 @@ import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { UpdateDatabasePassword } from "@/components/shared/update-database-password"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { toast } from "sonner"; interface Props { postgresId: string; } export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => { const { data } = api.postgres.one.useQuery({ postgresId }); + const utils = api.useUtils(); + const { mutateAsync: changePassword } = + api.postgres.changePassword.useMutation(); return ( <>
@@ -28,11 +33,21 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
-
+
+ { + await changePassword({ + postgresId, + password: newPassword, + }); + toast.success("Password updated successfully"); + utils.postgres.one.invalidate({ postgresId }); + }} + />
diff --git a/apps/dokploy/components/dashboard/redis/general/show-internal-redis-credentials.tsx b/apps/dokploy/components/dashboard/redis/general/show-internal-redis-credentials.tsx index 47ad0df0b..2245e724e 100644 --- a/apps/dokploy/components/dashboard/redis/general/show-internal-redis-credentials.tsx +++ b/apps/dokploy/components/dashboard/redis/general/show-internal-redis-credentials.tsx @@ -1,14 +1,19 @@ import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { UpdateDatabasePassword } from "@/components/shared/update-database-password"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { toast } from "sonner"; interface Props { redisId: string; } export const ShowInternalRedisCredentials = ({ redisId }: Props) => { const { data } = api.redis.one.useQuery({ redisId }); + const utils = api.useUtils(); + const { mutateAsync: changePassword } = + api.redis.changePassword.useMutation(); return ( <>
@@ -24,11 +29,21 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
-
+
+ { + await changePassword({ + redisId, + password: newPassword, + }); + toast.success("Password updated successfully"); + utils.redis.one.invalidate({ redisId }); + }} + />
diff --git a/apps/dokploy/components/shared/update-database-password.tsx b/apps/dokploy/components/shared/update-database-password.tsx new file mode 100644 index 000000000..bc58c2f43 --- /dev/null +++ b/apps/dokploy/components/shared/update-database-password.tsx @@ -0,0 +1,126 @@ +import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema"; +import { PenBox } from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +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, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; + +const updatePasswordSchema = z.object({ + password: z.string().min(1, "Password is required"), +}); + +type UpdatePassword = z.infer; + +interface Props { + label?: string; + onUpdatePassword: (newPassword: string) => Promise; +} + +export const UpdateDatabasePassword = ({ + label = "Password", + onUpdatePassword, +}: Props) => { + const [isOpen, setIsOpen] = useState(false); + const [error, setError] = useState(null); + const [isPending, setIsPending] = useState(false); + + const form = useForm({ + defaultValues: { password: "" }, + resolver: zodResolver(updatePasswordSchema), + }); + + const onSubmit = async (formData: UpdatePassword) => { + setIsPending(true); + setError(null); + try { + await onUpdatePassword(formData.password); + form.reset(); + setIsOpen(false); + } catch (e) { + setError(e instanceof Error ? e.message : "Error updating password"); + } finally { + setIsPending(false); + } + }; + + return ( + { + setIsOpen(open); + if (!open) { + form.reset(); + setError(null); + } + }} + > + + + + + + Update {label} + + Enter the new {label.toLowerCase()} for the database + + + {error && {error}} + + This will change the {label.toLowerCase()} both in the running + database container and in Dokploy. The container must be running + for this operation to succeed. + +
+ + ( + + New {label} + + + + + + )} + /> + + + + + +
+
+ ); +}; diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index bb739a43b..07dcb8c36 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -3,10 +3,13 @@ import { createMariadb, createMount, deployMariadb, + execAsync, + execAsyncRemote, findBackupsByDbId, findEnvironmentById, findMariadbById, findProjectById, + getServiceContainerCommand, IS_CLOUD, rebuildDatabase, removeMariadbById, @@ -366,6 +369,56 @@ export const mariadbRouter = createTRPCRouter({ resourceId: mariadbId, resourceName: service.appName, }); + return true; + }), + changePassword: protectedProcedure + .input( + z.object({ + mariadbId: z.string().min(1), + password: z.string().min(1), + type: z.enum(["user", "root"]).default("user"), + }), + ) + .mutation(async ({ input, ctx }) => { + const { mariadbId, password, type } = input; + await checkServicePermissionAndAccess(ctx, mariadbId, { + service: ["create"], + }); + + const maria = await findMariadbById(mariadbId); + const { appName, serverId, databaseUser, databaseRootPassword } = maria; + + const containerCmd = getServiceContainerCommand(appName); + const targetUser = type === "root" ? "root" : databaseUser; + + const command = ` + CONTAINER_ID=$(${containerCmd}) + if [ -z "$CONTAINER_ID" ]; then + echo "No running container found for ${appName}" >&2 + exit 1 + fi + docker exec "$CONTAINER_ID" mariadb -u root -p'${databaseRootPassword}' -e "ALTER USER '${targetUser}'@'%' IDENTIFIED BY '${password}'; FLUSH PRIVILEGES;" + `; + + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command, { shell: "/bin/bash" }); + } + + if (type === "root") { + await updateMariadbById(mariadbId, { databaseRootPassword: password }); + } else { + await updateMariadbById(mariadbId, { databasePassword: password }); + } + + await audit(ctx, { + action: "update", + resourceType: "service", + resourceId: mariadbId, + resourceName: appName, + }); + return true; }), move: protectedProcedure diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index de9e1f36f..abf1c2edf 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -3,10 +3,13 @@ import { createMongo, createMount, deployMongo, + execAsync, + execAsyncRemote, findBackupsByDbId, findEnvironmentById, findMongoById, findProjectById, + getServiceContainerCommand, IS_CLOUD, rebuildDatabase, removeMongoById, @@ -388,6 +391,49 @@ export const mongoRouter = createTRPCRouter({ resourceId: mongoId, resourceName: service.appName, }); + return true; + }), + changePassword: protectedProcedure + .input( + z.object({ + mongoId: z.string().min(1), + password: z.string().min(1), + }), + ) + .mutation(async ({ input, ctx }) => { + const { mongoId, password } = input; + await checkServicePermissionAndAccess(ctx, mongoId, { + service: ["create"], + }); + + const mongo = await findMongoById(mongoId); + const { appName, serverId, databaseUser, databasePassword } = mongo; + + const containerCmd = getServiceContainerCommand(appName); + const command = ` + CONTAINER_ID=$(${containerCmd}) + if [ -z "$CONTAINER_ID" ]; then + echo "No running container found for ${appName}" >&2 + exit 1 + fi + docker exec "$CONTAINER_ID" mongosh -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase admin --eval "db.getSiblingDB('admin').changeUserPassword('${databaseUser}', '${password}')" + `; + + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command, { shell: "/bin/bash" }); + } + + await updateMongoById(mongoId, { databasePassword: password }); + + await audit(ctx, { + action: "update", + resourceType: "service", + resourceId: mongoId, + resourceName: appName, + }); + return true; }), move: protectedProcedure diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index b834f52c2..028ca6d6e 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -3,10 +3,13 @@ import { createMount, createMysql, deployMySql, + execAsync, + execAsyncRemote, findBackupsByDbId, findEnvironmentById, findMySqlById, findProjectById, + getServiceContainerCommand, IS_CLOUD, rebuildDatabase, removeMySqlById, @@ -385,6 +388,58 @@ export const mysqlRouter = createTRPCRouter({ resourceId: mysqlId, resourceName: service.appName, }); + return true; + }), + changePassword: protectedProcedure + .input( + z.object({ + mysqlId: z.string().min(1), + password: z.string().min(1), + type: z.enum(["user", "root"]).default("user"), + }), + ) + .mutation(async ({ input, ctx }) => { + const { mysqlId, password, type } = input; + await checkServicePermissionAndAccess(ctx, mysqlId, { + service: ["create"], + }); + + const my = await findMySqlById(mysqlId); + const { appName, serverId, databaseUser, databaseRootPassword } = my; + + const containerCmd = getServiceContainerCommand(appName); + const authPassword = + type === "root" ? databaseRootPassword : databaseRootPassword; + const targetUser = type === "root" ? "root" : databaseUser; + + const command = ` + CONTAINER_ID=$(${containerCmd}) + if [ -z "$CONTAINER_ID" ]; then + echo "No running container found for ${appName}" >&2 + exit 1 + fi + docker exec "$CONTAINER_ID" mysql -u root -p'${authPassword}' -e "ALTER USER '${targetUser}'@'%' IDENTIFIED BY '${password}'; FLUSH PRIVILEGES;" + `; + + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command, { shell: "/bin/bash" }); + } + + if (type === "root") { + await updateMySqlById(mysqlId, { databaseRootPassword: password }); + } else { + await updateMySqlById(mysqlId, { databasePassword: password }); + } + + await audit(ctx, { + action: "update", + resourceType: "service", + resourceId: mysqlId, + resourceName: appName, + }); + return true; }), move: protectedProcedure diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 78e0e1284..72485c882 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -3,11 +3,14 @@ import { createMount, createPostgres, deployPostgres, + execAsync, + execAsyncRemote, findBackupsByDbId, findEnvironmentById, findPostgresById, findProjectById, getMountPath, + getServiceContainerCommand, IS_CLOUD, rebuildDatabase, removePostgresById, @@ -394,6 +397,49 @@ export const postgresRouter = createTRPCRouter({ resourceId: postgresId, resourceName: service.appName, }); + return true; + }), + changePassword: protectedProcedure + .input( + z.object({ + postgresId: z.string().min(1), + password: z.string().min(1), + }), + ) + .mutation(async ({ input, ctx }) => { + const { postgresId, password } = input; + await checkServicePermissionAndAccess(ctx, postgresId, { + service: ["create"], + }); + + const pg = await findPostgresById(postgresId); + const { appName, serverId, databaseUser } = pg; + + const containerCmd = getServiceContainerCommand(appName); + const command = ` + CONTAINER_ID=$(${containerCmd}) + if [ -z "$CONTAINER_ID" ]; then + echo "No running container found for ${appName}" >&2 + exit 1 + fi + docker exec "$CONTAINER_ID" psql -U ${databaseUser} -c "ALTER USER \\"${databaseUser}\\" WITH PASSWORD '${password}';" + `; + + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command, { shell: "/bin/bash" }); + } + + await updatePostgresById(postgresId, { databasePassword: password }); + + await audit(ctx, { + action: "update", + resourceType: "service", + resourceId: postgresId, + resourceName: appName, + }); + return true; }), move: protectedProcedure diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index efc98bf77..75afe209a 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -3,9 +3,12 @@ import { createMount, createRedis, deployRedis, + execAsync, + execAsyncRemote, findEnvironmentById, findProjectById, findRedisById, + getServiceContainerCommand, IS_CLOUD, rebuildDatabase, removeRedisById, @@ -375,6 +378,49 @@ export const redisRouter = createTRPCRouter({ resourceId: redisId, resourceName: redis.appName, }); + return true; + }), + changePassword: protectedProcedure + .input( + z.object({ + redisId: z.string().min(1), + password: z.string().min(1), + }), + ) + .mutation(async ({ input, ctx }) => { + const { redisId, password } = input; + await checkServicePermissionAndAccess(ctx, redisId, { + service: ["create"], + }); + + const rd = await findRedisById(redisId); + const { appName, serverId, databasePassword } = rd; + + const containerCmd = getServiceContainerCommand(appName); + const command = ` + CONTAINER_ID=$(${containerCmd}) + if [ -z "$CONTAINER_ID" ]; then + echo "No running container found for ${appName}" >&2 + exit 1 + fi + docker exec "$CONTAINER_ID" redis-cli -a '${databasePassword}' CONFIG SET requirepass '${password}' + `; + + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command, { shell: "/bin/bash" }); + } + + await updateRedisById(redisId, { databasePassword: password }); + + await audit(ctx, { + action: "update", + resourceType: "service", + resourceId: redisId, + resourceName: appName, + }); + return true; }), move: protectedProcedure From e1e175b1e09a60a0c55c77e6b61842e9e80c1ee6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 06:19:12 +0000 Subject: [PATCH 2/6] [autofix.ci] apply automated fixes --- apps/dokploy/components/shared/update-database-password.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/shared/update-database-password.tsx b/apps/dokploy/components/shared/update-database-password.tsx index bc58c2f43..a48e78bb9 100644 --- a/apps/dokploy/components/shared/update-database-password.tsx +++ b/apps/dokploy/components/shared/update-database-password.tsx @@ -88,8 +88,8 @@ export const UpdateDatabasePassword = ({ {error && {error}} This will change the {label.toLowerCase()} both in the running - database container and in Dokploy. The container must be running - for this operation to succeed. + database container and in Dokploy. The container must be running for + this operation to succeed.
Date: Sat, 4 Apr 2026 00:22:19 -0600 Subject: [PATCH 3/6] fix(update-database-password): enhance error handling for password update failures - Improved error messages when updating the database password to provide clearer guidance based on the error type. - Added specific feedback for cases where the database container is not running, prompting users to start the service before attempting to change the password. --- .../components/shared/update-database-password.tsx | 13 +++++++++++-- apps/dokploy/server/api/routers/postgres.ts | 1 - 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/components/shared/update-database-password.tsx b/apps/dokploy/components/shared/update-database-password.tsx index a48e78bb9..fa5c42b6c 100644 --- a/apps/dokploy/components/shared/update-database-password.tsx +++ b/apps/dokploy/components/shared/update-database-password.tsx @@ -56,12 +56,21 @@ export const UpdateDatabasePassword = ({ form.reset(); setIsOpen(false); } catch (e) { - setError(e instanceof Error ? e.message : "Error updating password"); + const raw = e instanceof Error ? e.message : "Error updating password"; + const noContainer = raw.match(/No running container found for \S+/); + if (noContainer) { + setError( + "The database container is not running. Please start the service before changing the password.", + ); + } else { + setError( + "Error updating password. Please check that the container is running and try again.", + ); + } } finally { setIsPending(false); } }; - return ( Date: Sat, 4 Apr 2026 09:19:29 -0600 Subject: [PATCH 4/6] feat(password-update): enhance password update functionality across database routers - Added confirmation password field and validation to the `UpdateDatabasePassword` component. - Refactored password update logic in MariaDB, MongoDB, MySQL, Postgres, and Redis routers to utilize database transactions for improved reliability. - Ensured consistent handling of password updates across all database types, enhancing user experience and security. --- .../shared/update-database-password.tsx | 31 ++++++++++++++++--- apps/dokploy/server/api/routers/mariadb.ts | 25 +++++++++------ apps/dokploy/server/api/routers/mongo.ts | 17 ++++++---- apps/dokploy/server/api/routers/mysql.ts | 29 +++++++++-------- apps/dokploy/server/api/routers/postgres.ts | 18 +++++++---- apps/dokploy/server/api/routers/redis.ts | 17 ++++++---- 6 files changed, 92 insertions(+), 45 deletions(-) diff --git a/apps/dokploy/components/shared/update-database-password.tsx b/apps/dokploy/components/shared/update-database-password.tsx index fa5c42b6c..978375fd0 100644 --- a/apps/dokploy/components/shared/update-database-password.tsx +++ b/apps/dokploy/components/shared/update-database-password.tsx @@ -24,9 +24,15 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -const updatePasswordSchema = z.object({ - password: z.string().min(1, "Password is required"), -}); +const updatePasswordSchema = z + .object({ + password: z.string().min(1, "Password is required"), + confirmPassword: z.string().min(1, "Please confirm the password"), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], + }); type UpdatePassword = z.infer; @@ -44,7 +50,7 @@ export const UpdateDatabasePassword = ({ const [isPending, setIsPending] = useState(false); const form = useForm({ - defaultValues: { password: "" }, + defaultValues: { password: "", confirmPassword: "" }, resolver: zodResolver(updatePasswordSchema), }); @@ -122,6 +128,23 @@ export const UpdateDatabasePassword = ({ )} /> + ( + + Confirm {label} + + + + + + )} + />