mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-20 06:35:22 +02:00
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.
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
@@ -28,20 +33,43 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databasePassword}
|
||||
/>
|
||||
<UpdateDatabasePassword
|
||||
onUpdatePassword={async (newPassword) => {
|
||||
await changePassword({
|
||||
mariadbId,
|
||||
password: newPassword,
|
||||
type: "user",
|
||||
});
|
||||
toast.success("Password updated successfully");
|
||||
utils.mariadb.one.invalidate({ mariadbId });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Root Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databaseRootPassword}
|
||||
/>
|
||||
<UpdateDatabasePassword
|
||||
label="Root Password"
|
||||
onUpdatePassword={async (newPassword) => {
|
||||
await changePassword({
|
||||
mariadbId,
|
||||
password: newPassword,
|
||||
type: "root",
|
||||
});
|
||||
toast.success("Root password updated successfully");
|
||||
utils.mariadb.one.invalidate({ mariadbId });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
@@ -25,11 +30,21 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databasePassword}
|
||||
/>
|
||||
<UpdateDatabasePassword
|
||||
onUpdatePassword={async (newPassword) => {
|
||||
await changePassword({
|
||||
mongoId,
|
||||
password: newPassword,
|
||||
});
|
||||
toast.success("Password updated successfully");
|
||||
utils.mongo.one.invalidate({ mongoId });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
@@ -28,20 +33,43 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databasePassword}
|
||||
/>
|
||||
<UpdateDatabasePassword
|
||||
onUpdatePassword={async (newPassword) => {
|
||||
await changePassword({
|
||||
mysqlId,
|
||||
password: newPassword,
|
||||
type: "user",
|
||||
});
|
||||
toast.success("Password updated successfully");
|
||||
utils.mysql.one.invalidate({ mysqlId });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Root Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databaseRootPassword}
|
||||
/>
|
||||
<UpdateDatabasePassword
|
||||
label="Root Password"
|
||||
onUpdatePassword={async (newPassword) => {
|
||||
await changePassword({
|
||||
mysqlId,
|
||||
password: newPassword,
|
||||
type: "root",
|
||||
});
|
||||
toast.success("Root password updated successfully");
|
||||
utils.mysql.one.invalidate({ mysqlId });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
@@ -28,11 +33,21 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<ToggleVisibilityInput
|
||||
value={data?.databasePassword}
|
||||
disabled
|
||||
/>
|
||||
<UpdateDatabasePassword
|
||||
onUpdatePassword={async (newPassword) => {
|
||||
await changePassword({
|
||||
postgresId,
|
||||
password: newPassword,
|
||||
});
|
||||
toast.success("Password updated successfully");
|
||||
utils.postgres.one.invalidate({ postgresId });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
@@ -24,11 +29,21 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<ToggleVisibilityInput
|
||||
value={data?.databasePassword}
|
||||
disabled
|
||||
/>
|
||||
<UpdateDatabasePassword
|
||||
onUpdatePassword={async (newPassword) => {
|
||||
await changePassword({
|
||||
redisId,
|
||||
password: newPassword,
|
||||
});
|
||||
toast.success("Password updated successfully");
|
||||
utils.redis.one.invalidate({ redisId });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
126
apps/dokploy/components/shared/update-database-password.tsx
Normal file
126
apps/dokploy/components/shared/update-database-password.tsx
Normal file
@@ -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<typeof updatePasswordSchema>;
|
||||
|
||||
interface Props {
|
||||
label?: string;
|
||||
onUpdatePassword: (newPassword: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const UpdateDatabasePassword = ({
|
||||
label = "Password",
|
||||
onUpdatePassword,
|
||||
}: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
const form = useForm<UpdatePassword>({
|
||||
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 (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
setIsOpen(open);
|
||||
if (!open) {
|
||||
form.reset();
|
||||
setError(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<PenBox className="size-3.5 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update {label}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter the new {label.toLowerCase()} for the database
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{error && <AlertBlock type="error">{error}</AlertBlock>}
|
||||
<AlertBlock type="warning">
|
||||
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.
|
||||
</AlertBlock>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>New {label}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={`Enter new ${label.toLowerCase()}`}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button isLoading={isPending} type="submit">
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user