refactor: dedicated edit page for volumes (#737)
This commit is contained in:
parent
d6e80b71d7
commit
4bf1463406
11 changed files with 428 additions and 253 deletions
|
|
@ -60,6 +60,7 @@ type Props = {
|
||||||
formId?: string;
|
formId?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValuesForType = {
|
const defaultValuesForType = {
|
||||||
|
|
@ -71,7 +72,15 @@ const defaultValuesForType = {
|
||||||
sftp: { backend: "sftp" as const, port: 22, path: "/", skipHostKeyCheck: false },
|
sftp: { backend: "sftp" as const, port: 22, path: "/", skipHostKeyCheck: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, formId, loading, className }: Props) => {
|
export const CreateVolumeForm = ({
|
||||||
|
onSubmit,
|
||||||
|
mode = "create",
|
||||||
|
initialValues,
|
||||||
|
formId,
|
||||||
|
loading,
|
||||||
|
className,
|
||||||
|
readOnly = false,
|
||||||
|
}: Props) => {
|
||||||
const form = useForm<FormValues>({
|
const form = useForm<FormValues>({
|
||||||
resolver: zodResolver(formSchema, undefined, { raw: true }),
|
resolver: zodResolver(formSchema, undefined, { raw: true }),
|
||||||
defaultValues: initialValues || {
|
defaultValues: initialValues || {
|
||||||
|
|
@ -137,123 +146,126 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
||||||
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
|
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
|
||||||
className={cn("space-y-4", className)}
|
className={cn("space-y-4", className)}
|
||||||
>
|
>
|
||||||
<FormField
|
<fieldset disabled={readOnly} className="space-y-4">
|
||||||
control={form.control}
|
<FormField
|
||||||
name="name"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="name"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<FormLabel>Name</FormLabel>
|
<FormItem>
|
||||||
<FormControl>
|
<FormLabel>Name</FormLabel>
|
||||||
<Input {...field} placeholder="Volume name" maxLength={32} minLength={2} />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>Unique identifier for the volume.</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="backend"
|
|
||||||
defaultValue="directory"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Backend</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) => {
|
|
||||||
field.onChange(value);
|
|
||||||
if (mode === "create") {
|
|
||||||
form.reset({
|
|
||||||
name: form.getValues().name,
|
|
||||||
...defaultValuesForType[value as keyof typeof defaultValuesForType],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={field.value}
|
|
||||||
>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<Input {...field} placeholder="Volume name" maxLength={32} minLength={2} />
|
||||||
<SelectValue placeholder="Select a backend" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<FormDescription>Unique identifier for the volume.</FormDescription>
|
||||||
<SelectItem value="directory">Directory</SelectItem>
|
<FormMessage />
|
||||||
<Tooltip>
|
</FormItem>
|
||||||
<TooltipTrigger asChild>
|
)}
|
||||||
<div>
|
/>
|
||||||
<SelectItem disabled={!capabilities.sysAdmin} value="nfs">
|
<FormField
|
||||||
NFS
|
control={form.control}
|
||||||
</SelectItem>
|
name="backend"
|
||||||
</div>
|
defaultValue="directory"
|
||||||
</TooltipTrigger>
|
render={({ field }) => (
|
||||||
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
<FormItem>
|
||||||
<p>Remote mounts require SYS_ADMIN capability</p>
|
<FormLabel>Backend</FormLabel>
|
||||||
</TooltipContent>
|
<Select
|
||||||
</Tooltip>
|
disabled={readOnly}
|
||||||
<Tooltip>
|
onValueChange={(value) => {
|
||||||
<TooltipTrigger asChild>
|
field.onChange(value);
|
||||||
<div>
|
if (mode === "create") {
|
||||||
<SelectItem disabled={!capabilities.sysAdmin} value="smb">
|
form.reset({
|
||||||
SMB
|
name: form.getValues().name,
|
||||||
</SelectItem>
|
...defaultValuesForType[value as keyof typeof defaultValuesForType],
|
||||||
</div>
|
});
|
||||||
</TooltipTrigger>
|
}
|
||||||
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
}}
|
||||||
<p>Remote mounts require SYS_ADMIN capability</p>
|
value={field.value}
|
||||||
</TooltipContent>
|
>
|
||||||
</Tooltip>
|
<FormControl>
|
||||||
<Tooltip>
|
<SelectTrigger>
|
||||||
<TooltipTrigger asChild>
|
<SelectValue placeholder="Select a backend" />
|
||||||
<div>
|
</SelectTrigger>
|
||||||
<SelectItem disabled={!capabilities.sysAdmin} value="webdav">
|
</FormControl>
|
||||||
WebDAV
|
<SelectContent>
|
||||||
</SelectItem>
|
<SelectItem value="directory">Directory</SelectItem>
|
||||||
</div>
|
<Tooltip>
|
||||||
</TooltipTrigger>
|
<TooltipTrigger asChild>
|
||||||
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
<div>
|
||||||
<p>Remote mounts require SYS_ADMIN capability</p>
|
<SelectItem disabled={!capabilities.sysAdmin} value="nfs">
|
||||||
</TooltipContent>
|
NFS
|
||||||
</Tooltip>
|
</SelectItem>
|
||||||
<Tooltip>
|
</div>
|
||||||
<TooltipTrigger asChild>
|
</TooltipTrigger>
|
||||||
<div>
|
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
||||||
<SelectItem disabled={!capabilities.sysAdmin} value="sftp">
|
<p>Remote mounts require SYS_ADMIN capability</p>
|
||||||
SFTP
|
</TooltipContent>
|
||||||
</SelectItem>
|
</Tooltip>
|
||||||
</div>
|
<Tooltip>
|
||||||
</TooltipTrigger>
|
<TooltipTrigger asChild>
|
||||||
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
<div>
|
||||||
<p>Remote mounts require SYS_ADMIN capability</p>
|
<SelectItem disabled={!capabilities.sysAdmin} value="smb">
|
||||||
</TooltipContent>
|
SMB
|
||||||
</Tooltip>
|
</SelectItem>
|
||||||
<Tooltip>
|
</div>
|
||||||
<TooltipTrigger asChild>
|
</TooltipTrigger>
|
||||||
<div>
|
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
||||||
<SelectItem disabled={!capabilities.rclone || !capabilities.sysAdmin} value="rclone">
|
<p>Remote mounts require SYS_ADMIN capability</p>
|
||||||
rclone
|
</TooltipContent>
|
||||||
</SelectItem>
|
</Tooltip>
|
||||||
</div>
|
<Tooltip>
|
||||||
</TooltipTrigger>
|
<TooltipTrigger asChild>
|
||||||
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
<div>
|
||||||
<p>Remote mounts require SYS_ADMIN capability</p>
|
<SelectItem disabled={!capabilities.sysAdmin} value="webdav">
|
||||||
</TooltipContent>
|
WebDAV
|
||||||
<TooltipContent className={cn({ hidden: !capabilities.sysAdmin || capabilities.rclone })}>
|
</SelectItem>
|
||||||
<p>Setup rclone to use this backend</p>
|
</div>
|
||||||
</TooltipContent>
|
</TooltipTrigger>
|
||||||
</Tooltip>
|
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
||||||
</SelectContent>
|
<p>Remote mounts require SYS_ADMIN capability</p>
|
||||||
</Select>
|
</TooltipContent>
|
||||||
<FormDescription>Choose the storage backend for this volume.</FormDescription>
|
</Tooltip>
|
||||||
<FormMessage />
|
<Tooltip>
|
||||||
</FormItem>
|
<TooltipTrigger asChild>
|
||||||
)}
|
<div>
|
||||||
/>
|
<SelectItem disabled={!capabilities.sysAdmin} value="sftp">
|
||||||
{watchedBackend === "directory" && <DirectoryForm form={form} />}
|
SFTP
|
||||||
{watchedBackend === "nfs" && <NFSForm form={form} />}
|
</SelectItem>
|
||||||
{watchedBackend === "webdav" && <WebDAVForm form={form} />}
|
</div>
|
||||||
{watchedBackend === "smb" && <SMBForm form={form} />}
|
</TooltipTrigger>
|
||||||
{watchedBackend === "rclone" && <RcloneForm form={form} />}
|
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
||||||
{watchedBackend === "sftp" && <SFTPForm form={form} />}
|
<p>Remote mounts require SYS_ADMIN capability</p>
|
||||||
{watchedBackend && watchedBackend !== "directory" && watchedBackend !== "rclone" && (
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div>
|
||||||
|
<SelectItem disabled={!capabilities.rclone || !capabilities.sysAdmin} value="rclone">
|
||||||
|
rclone
|
||||||
|
</SelectItem>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
||||||
|
<p>Remote mounts require SYS_ADMIN capability</p>
|
||||||
|
</TooltipContent>
|
||||||
|
<TooltipContent className={cn({ hidden: !capabilities.sysAdmin || capabilities.rclone })}>
|
||||||
|
<p>Setup rclone to use this backend</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>Choose the storage backend for this volume.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{watchedBackend === "directory" && <DirectoryForm form={form} />}
|
||||||
|
{watchedBackend === "nfs" && <NFSForm form={form} readOnly={readOnly} />}
|
||||||
|
{watchedBackend === "webdav" && <WebDAVForm form={form} />}
|
||||||
|
{watchedBackend === "smb" && <SMBForm form={form} readOnly={readOnly} />}
|
||||||
|
{watchedBackend === "rclone" && <RcloneForm form={form} readOnly={readOnly} />}
|
||||||
|
{watchedBackend === "sftp" && <SFTPForm form={form} readOnly={readOnly} />}
|
||||||
|
</fieldset>
|
||||||
|
{!readOnly && watchedBackend && watchedBackend !== "directory" && watchedBackend !== "rclone" && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -292,7 +304,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{mode === "update" && (
|
{!readOnly && mode === "update" && !formId && (
|
||||||
<Button type="submit" className="w-full" loading={loading}>
|
<Button type="submit" className="w-full" loading={loading}>
|
||||||
<Save className="h-4 w-4 mr-2" />
|
<Save className="h-4 w-4 mr-2" />
|
||||||
Save changes
|
Save changes
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
form: UseFormReturn<FormValues>;
|
form: UseFormReturn<FormValues>;
|
||||||
|
readOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NFSForm = ({ form }: Props) => {
|
export const NFSForm = ({ form, readOnly = false }: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
@ -73,7 +74,7 @@ export const NFSForm = ({ form }: Props) => {
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Version</FormLabel>
|
<FormLabel>Version</FormLabel>
|
||||||
<Select onValueChange={field.onChange} value={field.value}>
|
<Select disabled={readOnly} onValueChange={field.onChange} value={field.value}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select NFS version" />
|
<SelectValue placeholder="Select NFS version" />
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
form: UseFormReturn<FormValues>;
|
form: UseFormReturn<FormValues>;
|
||||||
|
readOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RcloneForm = ({ form }: Props) => {
|
export const RcloneForm = ({ form, readOnly = false }: Props) => {
|
||||||
const { capabilities } = useSystemInfo();
|
const { capabilities } = useSystemInfo();
|
||||||
|
|
||||||
const { data: rcloneRemotes, isPending } = useQuery({
|
const { data: rcloneRemotes, isPending } = useQuery({
|
||||||
|
|
@ -28,7 +29,7 @@ export const RcloneForm = ({ form }: Props) => {
|
||||||
enabled: capabilities.rclone,
|
enabled: capabilities.rclone,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isPending && !rcloneRemotes?.length) {
|
if (!readOnly && !isPending && !rcloneRemotes?.length) {
|
||||||
return (
|
return (
|
||||||
<Alert>
|
<Alert>
|
||||||
<AlertDescription className="space-y-2">
|
<AlertDescription className="space-y-2">
|
||||||
|
|
@ -58,7 +59,7 @@ export const RcloneForm = ({ form }: Props) => {
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Remote</FormLabel>
|
<FormLabel>Remote</FormLabel>
|
||||||
<Select onValueChange={(v) => field.onChange(v)} value={field.value ?? ""}>
|
<Select disabled={readOnly} onValueChange={(v) => field.onChange(v)} value={field.value ?? ""}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select an rclone remote" />
|
<SelectValue placeholder="Select an rclone remote" />
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,10 @@ import { Switch } from "../../../../components/ui/switch";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
form: UseFormReturn<FormValues>;
|
form: UseFormReturn<FormValues>;
|
||||||
|
readOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SFTPForm = ({ form }: Props) => {
|
export const SFTPForm = ({ form, readOnly = false }: Props) => {
|
||||||
const skipHostKeyCheck = useWatch({ control: form.control, name: "skipHostKeyCheck" });
|
const skipHostKeyCheck = useWatch({ control: form.control, name: "skipHostKeyCheck" });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -129,7 +130,7 @@ export const SFTPForm = ({ form }: Props) => {
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
<Switch checked={field.value} disabled={readOnly} onCheckedChange={field.onChange} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
form: UseFormReturn<FormValues>;
|
form: UseFormReturn<FormValues>;
|
||||||
|
readOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SMBForm = ({ form }: Props) => {
|
export const SMBForm = ({ form, readOnly = false }: Props) => {
|
||||||
const guest = useWatch({ control: form.control, name: "guest" });
|
const guest = useWatch({ control: form.control, name: "guest" });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -116,7 +117,7 @@ export const SMBForm = ({ form }: Props) => {
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>SMB Version</FormLabel>
|
<FormLabel>SMB Version</FormLabel>
|
||||||
<Select onValueChange={field.onChange} value={field.value}>
|
<Select disabled={readOnly} onValueChange={field.onChange} value={field.value}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select SMB version" />
|
<SelectValue placeholder="Select SMB version" />
|
||||||
|
|
|
||||||
136
app/client/modules/volumes/routes/edit-volume.tsx
Normal file
136
app/client/modules/volumes/routes/edit-volume.tsx
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { HardDrive, Check, Save } from "lucide-react";
|
||||||
|
import { useId, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { getVolumeOptions, updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
import { type UpdateVolumeResponse } from "~/client/api-client/types.gen";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "~/client/components/ui/alert-dialog";
|
||||||
|
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||||
|
import { Button } from "~/client/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
|
import { parseError } from "~/client/lib/errors";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { ManagedBadge } from "~/client/components/managed-badge";
|
||||||
|
import { CreateVolumeForm, formSchema, type FormValues } from "../components/create-volume-form";
|
||||||
|
|
||||||
|
export function EditVolumePage({ volumeId }: { volumeId: string }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const formId = useId();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [pendingValues, setPendingValues] = useState<FormValues | null>(null);
|
||||||
|
|
||||||
|
const { data } = useSuspenseQuery({
|
||||||
|
...getVolumeOptions({ path: { shortId: volumeId } }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { volume } = data;
|
||||||
|
|
||||||
|
const updateVolume = useMutation({
|
||||||
|
...updateVolumeMutation(),
|
||||||
|
onSuccess: (updatedVolume: UpdateVolumeResponse) => {
|
||||||
|
toast.success("Volume updated successfully");
|
||||||
|
setOpen(false);
|
||||||
|
setPendingValues(null);
|
||||||
|
void navigate({ to: `/volumes/${updatedVolume.shortId}` });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error("Failed to update volume", {
|
||||||
|
description: parseError(error)?.message,
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
setPendingValues(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (values: FormValues) => {
|
||||||
|
setPendingValues(values);
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmUpdate = () => {
|
||||||
|
if (!pendingValues) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, ...config } = formSchema.parse(pendingValues);
|
||||||
|
|
||||||
|
updateVolume.mutate({
|
||||||
|
path: { shortId: volume.shortId },
|
||||||
|
body: { name, config },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container mx-auto space-y-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
||||||
|
<HardDrive className="w-5 h-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CardTitle>Edit Volume</CardTitle>
|
||||||
|
{volume.provisioningId && <ManagedBadge />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{updateVolume.isError && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>
|
||||||
|
<strong>Failed to update volume:</strong>
|
||||||
|
<br />
|
||||||
|
{parseError(updateVolume.error)?.message}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<CreateVolumeForm
|
||||||
|
mode="update"
|
||||||
|
formId={formId}
|
||||||
|
initialValues={{ ...volume, ...volume.config }}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
loading={updateVolume.isPending}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end gap-2 pt-4 border-t">
|
||||||
|
<Button type="button" variant="secondary" onClick={() => navigate({ to: `/volumes/${volume.shortId}` })}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" form={formId} loading={updateVolume.isPending}>
|
||||||
|
<Save className="h-4 w-4 mr-2" />
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Update Volume Configuration</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Editing the volume will remount it with the new config immediately. This may temporarily disrupt access to
|
||||||
|
the volume. Continue?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={confirmUpdate} disabled={updateVolume.isPending}>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
Update
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export function VolumeDetails({ volumeId }: { volumeId: string }) {
|
export function VolumeDetails({ volumeId }: { volumeId: string }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const searchParams = useSearch({ from: "/(dashboard)/volumes/$volumeId" });
|
const searchParams = useSearch({ from: "/(dashboard)/volumes/$volumeId/" });
|
||||||
|
|
||||||
const activeTab = searchParams.tab || "info";
|
const activeTab = searchParams.tab || "info";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Check, Plug, Trash2, Unplug } from "lucide-react";
|
import { Pencil, Plug, Trash2, Unplug } from "lucide-react";
|
||||||
import { CreateVolumeForm, formSchema, type FormValues } from "~/client/modules/volumes/components/create-volume-form";
|
import { CreateVolumeForm } from "~/client/modules/volumes/components/create-volume-form";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
|
|
@ -22,9 +22,7 @@ import {
|
||||||
deleteVolumeMutation,
|
deleteVolumeMutation,
|
||||||
mountVolumeMutation,
|
mountVolumeMutation,
|
||||||
unmountVolumeMutation,
|
unmountVolumeMutation,
|
||||||
updateVolumeMutation,
|
|
||||||
} from "~/client/api-client/@tanstack/react-query.gen";
|
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import type { UpdateVolumeResponse } from "~/client/api-client/types.gen";
|
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { parseError } from "~/client/lib/errors";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { ManagedBadge } from "~/client/components/managed-badge";
|
import { ManagedBadge } from "~/client/components/managed-badge";
|
||||||
|
|
@ -37,24 +35,6 @@ type Props = {
|
||||||
export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
|
export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
|
||||||
...updateVolumeMutation(),
|
|
||||||
onSuccess: (data: UpdateVolumeResponse) => {
|
|
||||||
toast.success("Volume updated successfully");
|
|
||||||
setOpen(false);
|
|
||||||
setPendingValues(null);
|
|
||||||
|
|
||||||
if (data.name !== volume.name) {
|
|
||||||
void navigate({ to: `/volumes/${data.shortId}` });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error("Failed to update volume", { description: error.message });
|
|
||||||
setOpen(false);
|
|
||||||
setPendingValues(null);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const mountVol = useMutation({
|
const mountVol = useMutation({
|
||||||
...mountVolumeMutation(),
|
...mountVolumeMutation(),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
@ -92,81 +72,83 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [pendingValues, setPendingValues] = useState<FormValues | null>(null);
|
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = (values: FormValues) => {
|
|
||||||
setPendingValues(values);
|
|
||||||
setOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmUpdate = () => {
|
|
||||||
if (pendingValues) {
|
|
||||||
const { name, ...config } = formSchema.parse(pendingValues);
|
|
||||||
|
|
||||||
updateMutation.mutate({
|
|
||||||
path: { shortId: volume.shortId },
|
|
||||||
body: { name, config },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmDelete = () => {
|
const handleConfirmDelete = () => {
|
||||||
setShowDeleteConfirm(false);
|
setShowDeleteConfirm(false);
|
||||||
deleteVol.mutate({ path: { shortId: volume.shortId } });
|
deleteVol.mutate({ path: { shortId: volume.shortId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasLastError = Boolean(volume.lastError);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid gap-4 xl:grid-cols-[minmax(0,2.3fr)_minmax(320px,1fr)]">
|
<div className="grid gap-4 xl:grid-cols-[minmax(0,2.3fr)_minmax(320px,1fr)]">
|
||||||
<Card className="p-6 @container">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col @xl:flex-row items-start @xl:items-center justify-between gap-4 mb-6">
|
<Card className="p-6 @container">
|
||||||
<div>
|
<div className="flex flex-col @xl:flex-row items-start @xl:items-center justify-between gap-4 mb-6">
|
||||||
<div className="flex items-center gap-2">
|
<div>
|
||||||
<span className="text-lg font-semibold">Volume Configuration</span>
|
<div className="flex items-center gap-2">
|
||||||
{volume.provisioningId && <ManagedBadge />}
|
<span className="text-lg font-semibold">Volume Configuration</span>
|
||||||
|
{volume.provisioningId && <ManagedBadge />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col @xl:flex-row w-full @xl:w-auto gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => navigate({ to: `/volumes/${volume.shortId}/edit` })}
|
||||||
|
>
|
||||||
|
<Pencil className="h-4 w-4 mr-2" />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
{volume.status !== "mounted" ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => mountVol.mutate({ path: { shortId: volume.shortId } })}
|
||||||
|
loading={mountVol.isPending}
|
||||||
|
>
|
||||||
|
<Plug className="h-4 w-4 mr-2" />
|
||||||
|
Mount
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => unmountVol.mutate({ path: { shortId: volume.shortId } })}
|
||||||
|
loading={unmountVol.isPending}
|
||||||
|
>
|
||||||
|
<Unplug className="h-4 w-4 mr-2" />
|
||||||
|
Unmount
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => setShowDeleteConfirm(true)}
|
||||||
|
disabled={deleteVol.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4 mr-2" />
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col @xl:flex-row w-full @xl:w-auto gap-2">
|
<CreateVolumeForm
|
||||||
{volume.status !== "mounted" ? (
|
initialValues={{ ...volume, ...volume.config }}
|
||||||
<Button
|
onSubmit={() => {}}
|
||||||
type="button"
|
mode="update"
|
||||||
onClick={() => mountVol.mutate({ path: { shortId: volume.shortId } })}
|
readOnly
|
||||||
loading={mountVol.isPending}
|
/>
|
||||||
>
|
</Card>
|
||||||
<Plug className="h-4 w-4 mr-2" />
|
{hasLastError && (
|
||||||
Mount
|
<Card className="p-6">
|
||||||
</Button>
|
<div className="space-y-2">
|
||||||
) : (
|
<p className="text-sm font-medium text-destructive">Last Error</p>
|
||||||
<Button
|
<p className="text-sm text-muted-foreground wrap-break-word">{volume.lastError}</p>
|
||||||
type="button"
|
</div>
|
||||||
variant="secondary"
|
</Card>
|
||||||
onClick={() => unmountVol.mutate({ path: { shortId: volume.shortId } })}
|
)}
|
||||||
loading={unmountVol.isPending}
|
</div>
|
||||||
>
|
|
||||||
<Unplug className="h-4 w-4 mr-2" />
|
|
||||||
Unmount
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => setShowDeleteConfirm(true)}
|
|
||||||
disabled={deleteVol.isPending}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4 mr-2" />
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<CreateVolumeForm
|
|
||||||
initialValues={{ ...volume, ...volume.config }}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
mode="update"
|
|
||||||
loading={updateMutation.isPending}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="self-start w-full">
|
<div className="self-start w-full">
|
||||||
<HealthchecksCard volume={volume} />
|
<HealthchecksCard volume={volume} />
|
||||||
|
|
@ -176,24 +158,6 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>Update Volume Configuration</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
Editing the volume will remount it with the new config immediately. This may temporarily disrupt access to
|
|
||||||
the volume. Continue?
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
||||||
<AlertDialogAction onClick={confirmUpdate}>
|
|
||||||
<Check className="h-4 w-4" />
|
|
||||||
Update
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
|
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,15 @@ import { Route as dashboardNotificationsIndexRouteImport } from './routes/(dashb
|
||||||
import { Route as dashboardBackupsIndexRouteImport } from './routes/(dashboard)/backups/index'
|
import { Route as dashboardBackupsIndexRouteImport } from './routes/(dashboard)/backups/index'
|
||||||
import { Route as dashboardAdminIndexRouteImport } from './routes/(dashboard)/admin/index'
|
import { Route as dashboardAdminIndexRouteImport } from './routes/(dashboard)/admin/index'
|
||||||
import { Route as dashboardVolumesCreateRouteImport } from './routes/(dashboard)/volumes/create'
|
import { Route as dashboardVolumesCreateRouteImport } from './routes/(dashboard)/volumes/create'
|
||||||
import { Route as dashboardVolumesVolumeIdRouteImport } from './routes/(dashboard)/volumes/$volumeId'
|
|
||||||
import { Route as dashboardRepositoriesCreateRouteImport } from './routes/(dashboard)/repositories/create'
|
import { Route as dashboardRepositoriesCreateRouteImport } from './routes/(dashboard)/repositories/create'
|
||||||
import { Route as dashboardNotificationsCreateRouteImport } from './routes/(dashboard)/notifications/create'
|
import { Route as dashboardNotificationsCreateRouteImport } from './routes/(dashboard)/notifications/create'
|
||||||
import { Route as dashboardNotificationsNotificationIdRouteImport } from './routes/(dashboard)/notifications/$notificationId'
|
import { Route as dashboardNotificationsNotificationIdRouteImport } from './routes/(dashboard)/notifications/$notificationId'
|
||||||
import { Route as dashboardBackupsCreateRouteImport } from './routes/(dashboard)/backups/create'
|
import { Route as dashboardBackupsCreateRouteImport } from './routes/(dashboard)/backups/create'
|
||||||
import { Route as authLoginErrorRouteImport } from './routes/(auth)/login.error'
|
import { Route as authLoginErrorRouteImport } from './routes/(auth)/login.error'
|
||||||
|
import { Route as dashboardVolumesVolumeIdIndexRouteImport } from './routes/(dashboard)/volumes/$volumeId/index'
|
||||||
import { Route as dashboardRepositoriesRepositoryIdIndexRouteImport } from './routes/(dashboard)/repositories/$repositoryId/index'
|
import { Route as dashboardRepositoriesRepositoryIdIndexRouteImport } from './routes/(dashboard)/repositories/$repositoryId/index'
|
||||||
import { Route as dashboardBackupsBackupIdIndexRouteImport } from './routes/(dashboard)/backups/$backupId/index'
|
import { Route as dashboardBackupsBackupIdIndexRouteImport } from './routes/(dashboard)/backups/$backupId/index'
|
||||||
|
import { Route as dashboardVolumesVolumeIdEditRouteImport } from './routes/(dashboard)/volumes/$volumeId/edit'
|
||||||
import { Route as dashboardSettingsSsoNewRouteImport } from './routes/(dashboard)/settings/sso/new'
|
import { Route as dashboardSettingsSsoNewRouteImport } from './routes/(dashboard)/settings/sso/new'
|
||||||
import { Route as dashboardRepositoriesRepositoryIdEditRouteImport } from './routes/(dashboard)/repositories/$repositoryId/edit'
|
import { Route as dashboardRepositoriesRepositoryIdEditRouteImport } from './routes/(dashboard)/repositories/$repositoryId/edit'
|
||||||
import { Route as dashboardBackupsBackupIdEditRouteImport } from './routes/(dashboard)/backups/$backupId/edit'
|
import { Route as dashboardBackupsBackupIdEditRouteImport } from './routes/(dashboard)/backups/$backupId/edit'
|
||||||
|
|
@ -108,12 +109,6 @@ const dashboardVolumesCreateRoute = dashboardVolumesCreateRouteImport.update({
|
||||||
path: '/volumes/create',
|
path: '/volumes/create',
|
||||||
getParentRoute: () => dashboardRouteRoute,
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const dashboardVolumesVolumeIdRoute =
|
|
||||||
dashboardVolumesVolumeIdRouteImport.update({
|
|
||||||
id: '/volumes/$volumeId',
|
|
||||||
path: '/volumes/$volumeId',
|
|
||||||
getParentRoute: () => dashboardRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const dashboardRepositoriesCreateRoute =
|
const dashboardRepositoriesCreateRoute =
|
||||||
dashboardRepositoriesCreateRouteImport.update({
|
dashboardRepositoriesCreateRouteImport.update({
|
||||||
id: '/repositories/create',
|
id: '/repositories/create',
|
||||||
|
|
@ -142,6 +137,12 @@ const authLoginErrorRoute = authLoginErrorRouteImport.update({
|
||||||
path: '/error',
|
path: '/error',
|
||||||
getParentRoute: () => authLoginRoute,
|
getParentRoute: () => authLoginRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const dashboardVolumesVolumeIdIndexRoute =
|
||||||
|
dashboardVolumesVolumeIdIndexRouteImport.update({
|
||||||
|
id: '/volumes/$volumeId/',
|
||||||
|
path: '/volumes/$volumeId/',
|
||||||
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
|
} as any)
|
||||||
const dashboardRepositoriesRepositoryIdIndexRoute =
|
const dashboardRepositoriesRepositoryIdIndexRoute =
|
||||||
dashboardRepositoriesRepositoryIdIndexRouteImport.update({
|
dashboardRepositoriesRepositoryIdIndexRouteImport.update({
|
||||||
id: '/repositories/$repositoryId/',
|
id: '/repositories/$repositoryId/',
|
||||||
|
|
@ -154,6 +155,12 @@ const dashboardBackupsBackupIdIndexRoute =
|
||||||
path: '/backups/$backupId/',
|
path: '/backups/$backupId/',
|
||||||
getParentRoute: () => dashboardRouteRoute,
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const dashboardVolumesVolumeIdEditRoute =
|
||||||
|
dashboardVolumesVolumeIdEditRouteImport.update({
|
||||||
|
id: '/volumes/$volumeId/edit',
|
||||||
|
path: '/volumes/$volumeId/edit',
|
||||||
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
|
} as any)
|
||||||
const dashboardSettingsSsoNewRoute = dashboardSettingsSsoNewRouteImport.update({
|
const dashboardSettingsSsoNewRoute = dashboardSettingsSsoNewRouteImport.update({
|
||||||
id: '/settings/sso/new',
|
id: '/settings/sso/new',
|
||||||
path: '/settings/sso/new',
|
path: '/settings/sso/new',
|
||||||
|
|
@ -201,7 +208,6 @@ export interface FileRoutesByFullPath {
|
||||||
'/notifications/$notificationId': typeof dashboardNotificationsNotificationIdRoute
|
'/notifications/$notificationId': typeof dashboardNotificationsNotificationIdRoute
|
||||||
'/notifications/create': typeof dashboardNotificationsCreateRoute
|
'/notifications/create': typeof dashboardNotificationsCreateRoute
|
||||||
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
||||||
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
|
||||||
'/volumes/create': typeof dashboardVolumesCreateRoute
|
'/volumes/create': typeof dashboardVolumesCreateRoute
|
||||||
'/admin/': typeof dashboardAdminIndexRoute
|
'/admin/': typeof dashboardAdminIndexRoute
|
||||||
'/backups/': typeof dashboardBackupsIndexRoute
|
'/backups/': typeof dashboardBackupsIndexRoute
|
||||||
|
|
@ -212,8 +218,10 @@ export interface FileRoutesByFullPath {
|
||||||
'/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
|
'/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
|
||||||
'/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
|
'/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
|
||||||
'/settings/sso/new': typeof dashboardSettingsSsoNewRoute
|
'/settings/sso/new': typeof dashboardSettingsSsoNewRoute
|
||||||
|
'/volumes/$volumeId/edit': typeof dashboardVolumesVolumeIdEditRoute
|
||||||
'/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute
|
'/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute
|
||||||
'/repositories/$repositoryId/': typeof dashboardRepositoriesRepositoryIdIndexRoute
|
'/repositories/$repositoryId/': typeof dashboardRepositoriesRepositoryIdIndexRoute
|
||||||
|
'/volumes/$volumeId/': typeof dashboardVolumesVolumeIdIndexRoute
|
||||||
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
'/repositories/$repositoryId/$snapshotId/restore': typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
'/repositories/$repositoryId/$snapshotId/restore': typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
||||||
'/repositories/$repositoryId/$snapshotId/': typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
'/repositories/$repositoryId/$snapshotId/': typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
||||||
|
|
@ -229,7 +237,6 @@ export interface FileRoutesByTo {
|
||||||
'/notifications/$notificationId': typeof dashboardNotificationsNotificationIdRoute
|
'/notifications/$notificationId': typeof dashboardNotificationsNotificationIdRoute
|
||||||
'/notifications/create': typeof dashboardNotificationsCreateRoute
|
'/notifications/create': typeof dashboardNotificationsCreateRoute
|
||||||
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
||||||
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
|
||||||
'/volumes/create': typeof dashboardVolumesCreateRoute
|
'/volumes/create': typeof dashboardVolumesCreateRoute
|
||||||
'/admin': typeof dashboardAdminIndexRoute
|
'/admin': typeof dashboardAdminIndexRoute
|
||||||
'/backups': typeof dashboardBackupsIndexRoute
|
'/backups': typeof dashboardBackupsIndexRoute
|
||||||
|
|
@ -240,8 +247,10 @@ export interface FileRoutesByTo {
|
||||||
'/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
|
'/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
|
||||||
'/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
|
'/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
|
||||||
'/settings/sso/new': typeof dashboardSettingsSsoNewRoute
|
'/settings/sso/new': typeof dashboardSettingsSsoNewRoute
|
||||||
|
'/volumes/$volumeId/edit': typeof dashboardVolumesVolumeIdEditRoute
|
||||||
'/backups/$backupId': typeof dashboardBackupsBackupIdIndexRoute
|
'/backups/$backupId': typeof dashboardBackupsBackupIdIndexRoute
|
||||||
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdIndexRoute
|
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdIndexRoute
|
||||||
|
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdIndexRoute
|
||||||
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
'/repositories/$repositoryId/$snapshotId/restore': typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
'/repositories/$repositoryId/$snapshotId/restore': typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
||||||
'/repositories/$repositoryId/$snapshotId': typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
'/repositories/$repositoryId/$snapshotId': typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
||||||
|
|
@ -260,7 +269,6 @@ export interface FileRoutesById {
|
||||||
'/(dashboard)/notifications/$notificationId': typeof dashboardNotificationsNotificationIdRoute
|
'/(dashboard)/notifications/$notificationId': typeof dashboardNotificationsNotificationIdRoute
|
||||||
'/(dashboard)/notifications/create': typeof dashboardNotificationsCreateRoute
|
'/(dashboard)/notifications/create': typeof dashboardNotificationsCreateRoute
|
||||||
'/(dashboard)/repositories/create': typeof dashboardRepositoriesCreateRoute
|
'/(dashboard)/repositories/create': typeof dashboardRepositoriesCreateRoute
|
||||||
'/(dashboard)/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
|
||||||
'/(dashboard)/volumes/create': typeof dashboardVolumesCreateRoute
|
'/(dashboard)/volumes/create': typeof dashboardVolumesCreateRoute
|
||||||
'/(dashboard)/admin/': typeof dashboardAdminIndexRoute
|
'/(dashboard)/admin/': typeof dashboardAdminIndexRoute
|
||||||
'/(dashboard)/backups/': typeof dashboardBackupsIndexRoute
|
'/(dashboard)/backups/': typeof dashboardBackupsIndexRoute
|
||||||
|
|
@ -271,8 +279,10 @@ export interface FileRoutesById {
|
||||||
'/(dashboard)/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
|
'/(dashboard)/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
|
||||||
'/(dashboard)/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
|
'/(dashboard)/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
|
||||||
'/(dashboard)/settings/sso/new': typeof dashboardSettingsSsoNewRoute
|
'/(dashboard)/settings/sso/new': typeof dashboardSettingsSsoNewRoute
|
||||||
|
'/(dashboard)/volumes/$volumeId/edit': typeof dashboardVolumesVolumeIdEditRoute
|
||||||
'/(dashboard)/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute
|
'/(dashboard)/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute
|
||||||
'/(dashboard)/repositories/$repositoryId/': typeof dashboardRepositoriesRepositoryIdIndexRoute
|
'/(dashboard)/repositories/$repositoryId/': typeof dashboardRepositoriesRepositoryIdIndexRoute
|
||||||
|
'/(dashboard)/volumes/$volumeId/': typeof dashboardVolumesVolumeIdIndexRoute
|
||||||
'/(dashboard)/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
'/(dashboard)/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
'/(dashboard)/repositories/$repositoryId/$snapshotId/restore': typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
'/(dashboard)/repositories/$repositoryId/$snapshotId/restore': typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
||||||
'/(dashboard)/repositories/$repositoryId/$snapshotId/': typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
'/(dashboard)/repositories/$repositoryId/$snapshotId/': typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
||||||
|
|
@ -290,7 +300,6 @@ export interface FileRouteTypes {
|
||||||
| '/notifications/$notificationId'
|
| '/notifications/$notificationId'
|
||||||
| '/notifications/create'
|
| '/notifications/create'
|
||||||
| '/repositories/create'
|
| '/repositories/create'
|
||||||
| '/volumes/$volumeId'
|
|
||||||
| '/volumes/create'
|
| '/volumes/create'
|
||||||
| '/admin/'
|
| '/admin/'
|
||||||
| '/backups/'
|
| '/backups/'
|
||||||
|
|
@ -301,8 +310,10 @@ export interface FileRouteTypes {
|
||||||
| '/backups/$backupId/edit'
|
| '/backups/$backupId/edit'
|
||||||
| '/repositories/$repositoryId/edit'
|
| '/repositories/$repositoryId/edit'
|
||||||
| '/settings/sso/new'
|
| '/settings/sso/new'
|
||||||
|
| '/volumes/$volumeId/edit'
|
||||||
| '/backups/$backupId/'
|
| '/backups/$backupId/'
|
||||||
| '/repositories/$repositoryId/'
|
| '/repositories/$repositoryId/'
|
||||||
|
| '/volumes/$volumeId/'
|
||||||
| '/backups/$backupId/$snapshotId/restore'
|
| '/backups/$backupId/$snapshotId/restore'
|
||||||
| '/repositories/$repositoryId/$snapshotId/restore'
|
| '/repositories/$repositoryId/$snapshotId/restore'
|
||||||
| '/repositories/$repositoryId/$snapshotId/'
|
| '/repositories/$repositoryId/$snapshotId/'
|
||||||
|
|
@ -318,7 +329,6 @@ export interface FileRouteTypes {
|
||||||
| '/notifications/$notificationId'
|
| '/notifications/$notificationId'
|
||||||
| '/notifications/create'
|
| '/notifications/create'
|
||||||
| '/repositories/create'
|
| '/repositories/create'
|
||||||
| '/volumes/$volumeId'
|
|
||||||
| '/volumes/create'
|
| '/volumes/create'
|
||||||
| '/admin'
|
| '/admin'
|
||||||
| '/backups'
|
| '/backups'
|
||||||
|
|
@ -329,8 +339,10 @@ export interface FileRouteTypes {
|
||||||
| '/backups/$backupId/edit'
|
| '/backups/$backupId/edit'
|
||||||
| '/repositories/$repositoryId/edit'
|
| '/repositories/$repositoryId/edit'
|
||||||
| '/settings/sso/new'
|
| '/settings/sso/new'
|
||||||
|
| '/volumes/$volumeId/edit'
|
||||||
| '/backups/$backupId'
|
| '/backups/$backupId'
|
||||||
| '/repositories/$repositoryId'
|
| '/repositories/$repositoryId'
|
||||||
|
| '/volumes/$volumeId'
|
||||||
| '/backups/$backupId/$snapshotId/restore'
|
| '/backups/$backupId/$snapshotId/restore'
|
||||||
| '/repositories/$repositoryId/$snapshotId/restore'
|
| '/repositories/$repositoryId/$snapshotId/restore'
|
||||||
| '/repositories/$repositoryId/$snapshotId'
|
| '/repositories/$repositoryId/$snapshotId'
|
||||||
|
|
@ -348,7 +360,6 @@ export interface FileRouteTypes {
|
||||||
| '/(dashboard)/notifications/$notificationId'
|
| '/(dashboard)/notifications/$notificationId'
|
||||||
| '/(dashboard)/notifications/create'
|
| '/(dashboard)/notifications/create'
|
||||||
| '/(dashboard)/repositories/create'
|
| '/(dashboard)/repositories/create'
|
||||||
| '/(dashboard)/volumes/$volumeId'
|
|
||||||
| '/(dashboard)/volumes/create'
|
| '/(dashboard)/volumes/create'
|
||||||
| '/(dashboard)/admin/'
|
| '/(dashboard)/admin/'
|
||||||
| '/(dashboard)/backups/'
|
| '/(dashboard)/backups/'
|
||||||
|
|
@ -359,8 +370,10 @@ export interface FileRouteTypes {
|
||||||
| '/(dashboard)/backups/$backupId/edit'
|
| '/(dashboard)/backups/$backupId/edit'
|
||||||
| '/(dashboard)/repositories/$repositoryId/edit'
|
| '/(dashboard)/repositories/$repositoryId/edit'
|
||||||
| '/(dashboard)/settings/sso/new'
|
| '/(dashboard)/settings/sso/new'
|
||||||
|
| '/(dashboard)/volumes/$volumeId/edit'
|
||||||
| '/(dashboard)/backups/$backupId/'
|
| '/(dashboard)/backups/$backupId/'
|
||||||
| '/(dashboard)/repositories/$repositoryId/'
|
| '/(dashboard)/repositories/$repositoryId/'
|
||||||
|
| '/(dashboard)/volumes/$volumeId/'
|
||||||
| '/(dashboard)/backups/$backupId/$snapshotId/restore'
|
| '/(dashboard)/backups/$backupId/$snapshotId/restore'
|
||||||
| '/(dashboard)/repositories/$repositoryId/$snapshotId/restore'
|
| '/(dashboard)/repositories/$repositoryId/$snapshotId/restore'
|
||||||
| '/(dashboard)/repositories/$repositoryId/$snapshotId/'
|
| '/(dashboard)/repositories/$repositoryId/$snapshotId/'
|
||||||
|
|
@ -473,13 +486,6 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof dashboardVolumesCreateRouteImport
|
preLoaderRoute: typeof dashboardVolumesCreateRouteImport
|
||||||
parentRoute: typeof dashboardRouteRoute
|
parentRoute: typeof dashboardRouteRoute
|
||||||
}
|
}
|
||||||
'/(dashboard)/volumes/$volumeId': {
|
|
||||||
id: '/(dashboard)/volumes/$volumeId'
|
|
||||||
path: '/volumes/$volumeId'
|
|
||||||
fullPath: '/volumes/$volumeId'
|
|
||||||
preLoaderRoute: typeof dashboardVolumesVolumeIdRouteImport
|
|
||||||
parentRoute: typeof dashboardRouteRoute
|
|
||||||
}
|
|
||||||
'/(dashboard)/repositories/create': {
|
'/(dashboard)/repositories/create': {
|
||||||
id: '/(dashboard)/repositories/create'
|
id: '/(dashboard)/repositories/create'
|
||||||
path: '/repositories/create'
|
path: '/repositories/create'
|
||||||
|
|
@ -515,6 +521,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof authLoginErrorRouteImport
|
preLoaderRoute: typeof authLoginErrorRouteImport
|
||||||
parentRoute: typeof authLoginRoute
|
parentRoute: typeof authLoginRoute
|
||||||
}
|
}
|
||||||
|
'/(dashboard)/volumes/$volumeId/': {
|
||||||
|
id: '/(dashboard)/volumes/$volumeId/'
|
||||||
|
path: '/volumes/$volumeId'
|
||||||
|
fullPath: '/volumes/$volumeId/'
|
||||||
|
preLoaderRoute: typeof dashboardVolumesVolumeIdIndexRouteImport
|
||||||
|
parentRoute: typeof dashboardRouteRoute
|
||||||
|
}
|
||||||
'/(dashboard)/repositories/$repositoryId/': {
|
'/(dashboard)/repositories/$repositoryId/': {
|
||||||
id: '/(dashboard)/repositories/$repositoryId/'
|
id: '/(dashboard)/repositories/$repositoryId/'
|
||||||
path: '/repositories/$repositoryId'
|
path: '/repositories/$repositoryId'
|
||||||
|
|
@ -529,6 +542,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof dashboardBackupsBackupIdIndexRouteImport
|
preLoaderRoute: typeof dashboardBackupsBackupIdIndexRouteImport
|
||||||
parentRoute: typeof dashboardRouteRoute
|
parentRoute: typeof dashboardRouteRoute
|
||||||
}
|
}
|
||||||
|
'/(dashboard)/volumes/$volumeId/edit': {
|
||||||
|
id: '/(dashboard)/volumes/$volumeId/edit'
|
||||||
|
path: '/volumes/$volumeId/edit'
|
||||||
|
fullPath: '/volumes/$volumeId/edit'
|
||||||
|
preLoaderRoute: typeof dashboardVolumesVolumeIdEditRouteImport
|
||||||
|
parentRoute: typeof dashboardRouteRoute
|
||||||
|
}
|
||||||
'/(dashboard)/settings/sso/new': {
|
'/(dashboard)/settings/sso/new': {
|
||||||
id: '/(dashboard)/settings/sso/new'
|
id: '/(dashboard)/settings/sso/new'
|
||||||
path: '/settings/sso/new'
|
path: '/settings/sso/new'
|
||||||
|
|
@ -607,7 +627,6 @@ interface dashboardRouteRouteChildren {
|
||||||
dashboardNotificationsNotificationIdRoute: typeof dashboardNotificationsNotificationIdRoute
|
dashboardNotificationsNotificationIdRoute: typeof dashboardNotificationsNotificationIdRoute
|
||||||
dashboardNotificationsCreateRoute: typeof dashboardNotificationsCreateRoute
|
dashboardNotificationsCreateRoute: typeof dashboardNotificationsCreateRoute
|
||||||
dashboardRepositoriesCreateRoute: typeof dashboardRepositoriesCreateRoute
|
dashboardRepositoriesCreateRoute: typeof dashboardRepositoriesCreateRoute
|
||||||
dashboardVolumesVolumeIdRoute: typeof dashboardVolumesVolumeIdRoute
|
|
||||||
dashboardVolumesCreateRoute: typeof dashboardVolumesCreateRoute
|
dashboardVolumesCreateRoute: typeof dashboardVolumesCreateRoute
|
||||||
dashboardAdminIndexRoute: typeof dashboardAdminIndexRoute
|
dashboardAdminIndexRoute: typeof dashboardAdminIndexRoute
|
||||||
dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute
|
dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute
|
||||||
|
|
@ -618,8 +637,10 @@ interface dashboardRouteRouteChildren {
|
||||||
dashboardBackupsBackupIdEditRoute: typeof dashboardBackupsBackupIdEditRoute
|
dashboardBackupsBackupIdEditRoute: typeof dashboardBackupsBackupIdEditRoute
|
||||||
dashboardRepositoriesRepositoryIdEditRoute: typeof dashboardRepositoriesRepositoryIdEditRoute
|
dashboardRepositoriesRepositoryIdEditRoute: typeof dashboardRepositoriesRepositoryIdEditRoute
|
||||||
dashboardSettingsSsoNewRoute: typeof dashboardSettingsSsoNewRoute
|
dashboardSettingsSsoNewRoute: typeof dashboardSettingsSsoNewRoute
|
||||||
|
dashboardVolumesVolumeIdEditRoute: typeof dashboardVolumesVolumeIdEditRoute
|
||||||
dashboardBackupsBackupIdIndexRoute: typeof dashboardBackupsBackupIdIndexRoute
|
dashboardBackupsBackupIdIndexRoute: typeof dashboardBackupsBackupIdIndexRoute
|
||||||
dashboardRepositoriesRepositoryIdIndexRoute: typeof dashboardRepositoriesRepositoryIdIndexRoute
|
dashboardRepositoriesRepositoryIdIndexRoute: typeof dashboardRepositoriesRepositoryIdIndexRoute
|
||||||
|
dashboardVolumesVolumeIdIndexRoute: typeof dashboardVolumesVolumeIdIndexRoute
|
||||||
dashboardBackupsBackupIdSnapshotIdRestoreRoute: typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
dashboardBackupsBackupIdSnapshotIdRestoreRoute: typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute: typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute: typeof dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute
|
||||||
dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute: typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute: typeof dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute
|
||||||
|
|
@ -631,7 +652,6 @@ const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
|
||||||
dashboardNotificationsNotificationIdRoute,
|
dashboardNotificationsNotificationIdRoute,
|
||||||
dashboardNotificationsCreateRoute: dashboardNotificationsCreateRoute,
|
dashboardNotificationsCreateRoute: dashboardNotificationsCreateRoute,
|
||||||
dashboardRepositoriesCreateRoute: dashboardRepositoriesCreateRoute,
|
dashboardRepositoriesCreateRoute: dashboardRepositoriesCreateRoute,
|
||||||
dashboardVolumesVolumeIdRoute: dashboardVolumesVolumeIdRoute,
|
|
||||||
dashboardVolumesCreateRoute: dashboardVolumesCreateRoute,
|
dashboardVolumesCreateRoute: dashboardVolumesCreateRoute,
|
||||||
dashboardAdminIndexRoute: dashboardAdminIndexRoute,
|
dashboardAdminIndexRoute: dashboardAdminIndexRoute,
|
||||||
dashboardBackupsIndexRoute: dashboardBackupsIndexRoute,
|
dashboardBackupsIndexRoute: dashboardBackupsIndexRoute,
|
||||||
|
|
@ -643,9 +663,11 @@ const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
|
||||||
dashboardRepositoriesRepositoryIdEditRoute:
|
dashboardRepositoriesRepositoryIdEditRoute:
|
||||||
dashboardRepositoriesRepositoryIdEditRoute,
|
dashboardRepositoriesRepositoryIdEditRoute,
|
||||||
dashboardSettingsSsoNewRoute: dashboardSettingsSsoNewRoute,
|
dashboardSettingsSsoNewRoute: dashboardSettingsSsoNewRoute,
|
||||||
|
dashboardVolumesVolumeIdEditRoute: dashboardVolumesVolumeIdEditRoute,
|
||||||
dashboardBackupsBackupIdIndexRoute: dashboardBackupsBackupIdIndexRoute,
|
dashboardBackupsBackupIdIndexRoute: dashboardBackupsBackupIdIndexRoute,
|
||||||
dashboardRepositoriesRepositoryIdIndexRoute:
|
dashboardRepositoriesRepositoryIdIndexRoute:
|
||||||
dashboardRepositoriesRepositoryIdIndexRoute,
|
dashboardRepositoriesRepositoryIdIndexRoute,
|
||||||
|
dashboardVolumesVolumeIdIndexRoute: dashboardVolumesVolumeIdIndexRoute,
|
||||||
dashboardBackupsBackupIdSnapshotIdRestoreRoute:
|
dashboardBackupsBackupIdSnapshotIdRestoreRoute:
|
||||||
dashboardBackupsBackupIdSnapshotIdRestoreRoute,
|
dashboardBackupsBackupIdSnapshotIdRestoreRoute,
|
||||||
dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute:
|
dashboardRepositoriesRepositoryIdSnapshotIdRestoreRoute:
|
||||||
|
|
|
||||||
37
app/routes/(dashboard)/volumes/$volumeId/edit.tsx
Normal file
37
app/routes/(dashboard)/volumes/$volumeId/edit.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { getVolumeOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
import { EditVolumePage } from "~/client/modules/volumes/routes/edit-volume";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/(dashboard)/volumes/$volumeId/edit")({
|
||||||
|
component: RouteComponent,
|
||||||
|
errorComponent: (e) => <div>{e.error.message}</div>,
|
||||||
|
loader: async ({ params, context }) => {
|
||||||
|
const volume = await context.queryClient.ensureQueryData({
|
||||||
|
...getVolumeOptions({ path: { shortId: params.volumeId } }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return volume;
|
||||||
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Volumes", href: "/volumes" },
|
||||||
|
{ label: match.loaderData?.volume.name || "Volume", href: `/volumes/${match.params.volumeId}` },
|
||||||
|
{ label: "Edit" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
head: ({ loaderData }) => ({
|
||||||
|
meta: [
|
||||||
|
{ title: `Zerobyte - Edit ${loaderData?.volume.name ?? "Volume"}` },
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
content: "Edit volume configuration.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const { volumeId } = Route.useParams();
|
||||||
|
|
||||||
|
return <EditVolumePage volumeId={volumeId} />;
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import { z } from "zod";
|
||||||
import { getVolumeOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
import { getVolumeOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { VolumeDetails } from "~/client/modules/volumes/routes/volume-details";
|
import { VolumeDetails } from "~/client/modules/volumes/routes/volume-details";
|
||||||
|
|
||||||
export const Route = createFileRoute("/(dashboard)/volumes/$volumeId")({
|
export const Route = createFileRoute("/(dashboard)/volumes/$volumeId/")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
errorComponent: (e) => <div>{e.error.message}</div>,
|
errorComponent: (e) => <div>{e.error.message}</div>,
|
||||||
loader: async ({ params, context }) => {
|
loader: async ({ params, context }) => {
|
||||||
Loading…
Reference in a new issue