dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx
Mauricio Siu 8f9d21c0f8
Docker compose support (#111)
* feat(WIP): compose implementation

* feat: add volumes, networks, services name hash generate

* feat: add compose config test unique

* feat: add tests for each unique config

* feat: implement lodash for docker compose parsing

* feat: add tests for generating compose file

* refactor: implement logs docker compose

* refactor: composeFile set not empty

* feat: implement providers for compose deployments

* feat: add Files volumes to compose

* feat: add stop compose button

* refactor: change strategie of building compose

* feat: create .env file in composepath

* refactor: simplify git and github function

* chore: update deps

* refactor: update migrations and add badge to recognize compose type

* chore: update lock yaml

* refactor: use code editor

* feat: add monitoring for app types

* refactor: reset stats on change appName

* refactor: add option to clean monitoring folder

* feat: show current command that will run

* feat: add prefix

* fix: add missing types

* refactor: add docker provider and expose by default as false

* refactor: customize error page

* refactor: unified deployments to be a single one

* feat: add vitest to ci/cd

* revert: back to initial version

* refactor: add maxconcurrency vitest

* refactor: add pool forks to vitest

* feat: add pocketbase template

* fix: update path resolution compose

* removed

* feat: add template pocketbase

* feat: add pocketbase template

* feat: add support button

* feat: add plausible template

* feat: add calcom template

* feat: add version to each template

* feat: add code editor to enviroment variables and swarm settings json

* refactor: add loader when download the image

* fix: use base64 to generate keys plausible

* feat: add recognized domain names by enviroment compose

* refactor: show alert to redeploy in each card advanced tab

* refactor: add validation to prevent create compose if not have permissions

* chore: add templates section to contributing

* chore: add example contributing
2024-06-02 15:26:28 -06:00

264 lines
5.9 KiB
TypeScript

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";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { Pencil } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Textarea } from "@/components/ui/textarea";
const mountSchema = z.object({
mountPath: z.string().min(1, "Mount path required"),
});
const mySchema = z.discriminatedUnion("type", [
z
.object({
type: z.literal("bind"),
hostPath: z.string().min(1, "Host path required"),
})
.merge(mountSchema),
z
.object({
type: z.literal("volume"),
volumeName: z.string().min(1, "Volume name required"),
})
.merge(mountSchema),
z
.object({
type: z.literal("file"),
content: z.string().optional(),
})
.merge(mountSchema),
]);
type UpdateMount = z.infer<typeof mySchema>;
interface Props {
mountId: string;
type: "bind" | "volume" | "file";
refetch: () => void;
}
export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
const utils = api.useUtils();
const { data } = api.mounts.one.useQuery(
{
mountId,
},
{
enabled: !!mountId,
},
);
const { mutateAsync, isLoading, error, isError } =
api.mounts.update.useMutation();
const form = useForm<UpdateMount>({
defaultValues: {
type,
hostPath: "",
mountPath: "",
},
resolver: zodResolver(mySchema),
});
const typeForm = form.watch("type");
useEffect(() => {
if (data) {
if (typeForm === "bind") {
form.reset({
hostPath: data.hostPath || "",
mountPath: data.mountPath,
type: "bind",
});
} else if (typeForm === "volume") {
form.reset({
volumeName: data.volumeName || "",
mountPath: data.mountPath,
type: "volume",
});
} else if (typeForm === "file") {
form.reset({
content: data.content || "",
mountPath: data.mountPath,
type: "file",
});
}
}
}, [form, form.reset, data]);
const onSubmit = async (data: UpdateMount) => {
if (data.type === "bind") {
await mutateAsync({
hostPath: data.hostPath,
mountPath: data.mountPath,
type: data.type,
mountId,
})
.then(() => {
toast.success("Mount Update");
})
.catch(() => {
toast.error("Error to update the Bind mount");
});
} else if (data.type === "volume") {
await mutateAsync({
volumeName: data.volumeName,
mountPath: data.mountPath,
type: data.type,
mountId,
})
.then(() => {
toast.success("Mount Update");
})
.catch(() => {
toast.error("Error to update the Volume mount");
});
} else if (data.type === "file") {
await mutateAsync({
content: data.content,
mountPath: data.mountPath,
type: data.type,
mountId,
})
.then(() => {
toast.success("Mount Update");
})
.catch(() => {
toast.error("Error to update the File mount");
});
}
refetch();
};
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<Pencil className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the mount</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-update-volume"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<div className="flex flex-col gap-4">
{type === "bind" && (
<FormField
control={form.control}
name="hostPath"
render={({ field }) => (
<FormItem>
<FormLabel>Host Path</FormLabel>
<FormControl>
<Input placeholder="Host Path" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{type === "volume" && (
<FormField
control={form.control}
name="volumeName"
render={({ field }) => (
<FormItem>
<FormLabel>Volume Name</FormLabel>
<FormControl>
<Input
placeholder="Volume Name"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{type === "file" && (
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormLabel>Content</FormLabel>
<FormControl>
<FormControl>
<Textarea
placeholder="Any content"
className="h-64"
{...field}
/>
</FormControl>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormField
control={form.control}
name="mountPath"
render={({ field }) => (
<FormItem>
<FormLabel>Mount Path</FormLabel>
<FormControl>
<Input placeholder="Mount Path" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-update-volume"
type="submit"
>
Update
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};