mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
migration
This commit is contained in:
@@ -2,17 +2,17 @@ import { useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {isValidCronPart} from "@/utils/cron";
|
||||
import { isValidCronPart } from "@/utils/cron";
|
||||
|
||||
export const AdvancedCronSelect = ({
|
||||
id,
|
||||
label,
|
||||
options,
|
||||
type,
|
||||
value,
|
||||
defaultValue,
|
||||
onValueChange,
|
||||
}: {
|
||||
id,
|
||||
label,
|
||||
options,
|
||||
type,
|
||||
value,
|
||||
defaultValue,
|
||||
onValueChange,
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
options: string[];
|
||||
@@ -38,7 +38,9 @@ export const AdvancedCronSelect = ({
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 items-center gap-2">
|
||||
<Label htmlFor={id} className="text-left">{label}</Label>
|
||||
<Label htmlFor={id} className="text-left">
|
||||
{label}
|
||||
</Label>
|
||||
{!isAdvanced ? (
|
||||
<Select
|
||||
id={id}
|
||||
|
||||
@@ -1,95 +1,71 @@
|
||||
"use client"
|
||||
import {Clock9} from "lucide-react";
|
||||
import {Button} from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription, DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from "@/components/ui/dialog";
|
||||
import {CronInput} from "@/components/wrappers/dashboard/database/CronButton/CronInput";
|
||||
import {Switch} from "@/components/ui/switch";
|
||||
import {Label} from "@/components/ui/label"
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {useState} from "react";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {
|
||||
updateStorageSettingsAction
|
||||
} from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.action";
|
||||
import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Database} from "@prisma/client";
|
||||
import {
|
||||
updateBackupPolicyAction,
|
||||
updateDatabaseBackupPolicyAction
|
||||
} from "@/components/wrappers/dashboard/database/CronButton/cron.action";
|
||||
|
||||
"use client";
|
||||
import { Clock9 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { CronInput } from "@/components/wrappers/dashboard/database/CronButton/CronInput";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { useState } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/CronButton/cron.action";
|
||||
import { Database } from "@/db/schema";
|
||||
|
||||
export type CronButtonProps = {
|
||||
database: Database
|
||||
}
|
||||
database: Database;
|
||||
};
|
||||
|
||||
export const CronButton = (props: CronButtonProps) => {
|
||||
const router = useRouter();
|
||||
const [isSwitched, setIsSwitched] = useState(props.database.backupPolicy !== null);
|
||||
|
||||
const updateDatabaseBackupPolicy = useMutation({
|
||||
mutationFn: (value: string) => updateDatabaseBackupPolicyAction({databaseId: props.database.id, backupPolicy:value}),
|
||||
mutationFn: (value: string) => updateDatabaseBackupPolicyAction({ databaseId: props.database.id, backupPolicy: value }),
|
||||
onSuccess: () => {
|
||||
toast.success(`Method updated successfully.`);
|
||||
router.refresh()
|
||||
router.refresh();
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(`An error occurred while updating backup method.`);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const handleTypeChange = async (state: boolean) => {
|
||||
setIsSwitched(state);
|
||||
if(state == false) {
|
||||
await updateDatabaseBackupPolicy.mutateAsync("")
|
||||
if (state == false) {
|
||||
await updateDatabaseBackupPolicy.mutateAsync("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
{...props}
|
||||
|
||||
>
|
||||
<Clock9/>
|
||||
<Button variant="outline" {...props}>
|
||||
<Clock9 />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Backup method</DialogTitle>
|
||||
<DialogDescription>
|
||||
Your settings for the backup method
|
||||
</DialogDescription>
|
||||
<DialogDescription>Your settings for the backup method</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Separator/>
|
||||
<Separator />
|
||||
|
||||
<h1>
|
||||
Select your backup method
|
||||
</h1>
|
||||
<h1>Select your backup method</h1>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label>Manual / Automatic </Label>
|
||||
<Switch
|
||||
checked={isSwitched}
|
||||
onCheckedChange={async () => {
|
||||
await handleTypeChange(!isSwitched)
|
||||
await handleTypeChange(!isSwitched);
|
||||
}}
|
||||
id="type-mode"/>
|
||||
id="type-mode"
|
||||
/>
|
||||
</div>
|
||||
{isSwitched ?
|
||||
<CronInput database={props.database}/>
|
||||
:null}
|
||||
{isSwitched ? <CronInput database={props.database} /> : null}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { AdvancedCronSelect } from "./AdvancedCronSelect";
|
||||
import {
|
||||
updateBackupPolicyAction,
|
||||
updateDatabaseBackupPolicyAction
|
||||
} from "@/components/wrappers/dashboard/database/CronButton/cron.action";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {useState} from "react";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {toast} from "sonner";
|
||||
import {Database} from "@prisma/client";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/CronButton/cron.action";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Database } from "@/db/schema";
|
||||
|
||||
export type CronInputProps = {
|
||||
database : Database
|
||||
}
|
||||
|
||||
database: Database;
|
||||
};
|
||||
|
||||
export const CronInput = ({ database }: CronInputProps) => {
|
||||
const [cron, setCron] = useState<string>(database.backupPolicy ?? "* * * * *");
|
||||
@@ -31,7 +27,7 @@ export const CronInput = ({ database }: CronInputProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const handleChangeCron = (type: string, value: string) => {
|
||||
const handleChangeCron = (type: "minute" | "hour" | "day-of-month" | "month" | "day-of-week", value: string) => {
|
||||
const cronParts = cron.split(" ");
|
||||
const indexMap = { minute: 0, hour: 1, "day-of-month": 2, month: 3, "day-of-week": 4 };
|
||||
cronParts[indexMap[type]] = value;
|
||||
@@ -96,9 +92,7 @@ export const CronInput = ({ database }: CronInputProps) => {
|
||||
<div className="font-semibold">Cron Expression</div>
|
||||
<div className="font-mono text-muted-foreground">{cron}</div>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
This cron expression determines when the job will run.
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">This cron expression determines when the job will run.</div>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2">
|
||||
<Button
|
||||
@@ -120,4 +114,4 @@ export const CronInput = ({ database }: CronInputProps) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
"use server"
|
||||
import {userAction} from "@/safe-actions";
|
||||
import {z} from "zod";
|
||||
import {prisma} from "@/prisma";
|
||||
"use server";
|
||||
|
||||
import { userAction } from "@/safe-actions";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/db";
|
||||
import { database } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const updateDatabaseBackupPolicyAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
databaseId: z.string(),
|
||||
backupPolicy: z.string(),
|
||||
}
|
||||
)
|
||||
)
|
||||
.action(async ({parsedInput, ctx}) => {
|
||||
const cronPolicy = parsedInput.backupPolicy == "" ? null : parsedInput.backupPolicy
|
||||
|
||||
const updatedDatabase = await prisma.database.update({
|
||||
where: {
|
||||
id: parsedInput.databaseId,
|
||||
},
|
||||
data: {
|
||||
backupPolicy: cronPolicy,
|
||||
}
|
||||
databaseId: z.string(),
|
||||
backupPolicy: z.string(),
|
||||
})
|
||||
)
|
||||
.action(async ({ parsedInput }) => {
|
||||
const cronPolicy = parsedInput.backupPolicy === "" ? null : parsedInput.backupPolicy;
|
||||
|
||||
const [updated] = await db
|
||||
.update(database)
|
||||
.set({
|
||||
backupPolicy: cronPolicy,
|
||||
})
|
||||
.where(eq(database.id, parsedInput.databaseId))
|
||||
.returning()
|
||||
.execute();
|
||||
|
||||
return {
|
||||
data: updatedDatabase,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
data: updated,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,119 +1,113 @@
|
||||
"use client";
|
||||
|
||||
import {Card, CardContent} from "@/components/ui/card";
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
useZodForm
|
||||
} from "@/components/ui/form";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Form} from "@/components/ui/form"
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {TooltipProvider} from "@/components/ui/tooltip";
|
||||
import {DatabaseSchema, DatabaseType} from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
|
||||
import {updateDatabaseAction} from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.action";
|
||||
import {toast} from "sonner";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { DatabaseSchema, DatabaseType } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
|
||||
import { updateDatabaseAction } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.action";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export type DatabaseFormProps = {
|
||||
defaultValues?: DatabaseType;
|
||||
databaseId?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export const DatabaseForm = (props: DatabaseFormProps) => {
|
||||
const { defaultValues, databaseId } = props;
|
||||
|
||||
const {defaultValues, databaseId} = props;
|
||||
|
||||
const isCreate = !Boolean(defaultValues)
|
||||
const isCreate = !Boolean(defaultValues);
|
||||
|
||||
const form = useZodForm({
|
||||
schema: DatabaseSchema,
|
||||
defaultValues: {...defaultValues},
|
||||
defaultValues: { ...defaultValues },
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: DatabaseType) => {
|
||||
if (!databaseId) {
|
||||
throw new Error("Database ID is required");
|
||||
}
|
||||
const database = await updateDatabaseAction({ id: databaseId, data: values });
|
||||
|
||||
const database = await updateDatabaseAction({id: databaseId, data: values});
|
||||
|
||||
if (!database) {
|
||||
toast.error("Failed to update database");
|
||||
return;
|
||||
}
|
||||
if (database.serverError) {
|
||||
console.error(database?.serverError);
|
||||
toast.error(database?.serverError);
|
||||
toast.error(database.serverError);
|
||||
return;
|
||||
}
|
||||
console.log(database)
|
||||
toast.success(`Database settings successfully updated!`);
|
||||
|
||||
router.back()
|
||||
}
|
||||
})
|
||||
router.back();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Form form={form}
|
||||
className="flex flex-col gap-4 mt-3"
|
||||
onSubmit={async (values) => {
|
||||
console.log("sssssss")
|
||||
await mutation.mutateAsync(values);
|
||||
}}
|
||||
<Form
|
||||
form={form}
|
||||
className="flex flex-col gap-4 mt-3"
|
||||
onSubmit={async (values) => {
|
||||
console.log("sssssss");
|
||||
await mutation.mutateAsync(values);
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input disabled placeholder="Database 1" {...field}/>
|
||||
<Input disabled placeholder="Database 1" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Your database project name setup in agent</FormDescription>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dbms"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Database type</FormLabel>
|
||||
<FormControl>
|
||||
<Input disabled placeholder="PostgreSQL" {...field}/>
|
||||
<Input disabled placeholder="PostgreSQL" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Your database project name setup in agent</FormDescription>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Prod database for project 1" {...field}/>
|
||||
<Input placeholder="Prod database for project 1" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Add a short description about this database</FormDescription>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button>
|
||||
{isCreate ? `Create database` : `Save database`}
|
||||
</Button>
|
||||
<Button>{isCreate ? `Create database` : `Save database`}</Button>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
"use server"
|
||||
|
||||
import {z} from "zod";
|
||||
|
||||
import {userAction} from "@/safe-actions";
|
||||
import {prisma} from "@/prisma";
|
||||
import {DatabaseSchema} from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { userAction } from "@/safe-actions";
|
||||
import { db } from "@/db";
|
||||
import { database } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { DatabaseSchema } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
|
||||
|
||||
export const updateDatabaseAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
data: DatabaseSchema,
|
||||
}
|
||||
)
|
||||
id: z.string(),
|
||||
data: DatabaseSchema,
|
||||
})
|
||||
)
|
||||
.action(async ({parsedInput, ctx}) => {
|
||||
return prisma.database.update({
|
||||
where: {
|
||||
id: parsedInput.id,
|
||||
},
|
||||
data: parsedInput.data,
|
||||
});
|
||||
})
|
||||
.action(async ({ parsedInput }) => {
|
||||
const [updated] = await db.update(database).set(parsedInput.data).where(eq(database.id, parsedInput.id)).returning().execute();
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import {z} from "zod";
|
||||
|
||||
const cronRegex = /^(\d{1,2}|\*|(\d{1,2}-\d{1,2})|(\d{1,2}\/\d{1,2}))\s+(\d{1,2}|\*|(\d{1,2}-\d{1,2})|(\d{1,2}\/\d{1,2}))\s+(\d{1,2}|\*|(\d{1,2}-\d{1,2})|(\d{1,2}\/\d{1,2}))\s+(\d{1,7}|\*|(\d{1,7}-\d{1,7})|(\d{1,7}\/\d{1,7}))$/;
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export const DatabaseSchema = z.object({
|
||||
name: z.string().readonly(),
|
||||
description: z.string().optional(),
|
||||
dbms: z.string().readonly(),
|
||||
dbms: z.enum(["active", "inactive"]).readonly(),
|
||||
});
|
||||
|
||||
export type DatabaseType = z.infer<typeof DatabaseSchema>;
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
"use client"
|
||||
import {buttonVariants} from "@/components/ui/button";
|
||||
import {GearIcon} from "@radix-ui/react-icons";
|
||||
"use client";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { GearIcon } from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
import {usePathname} from "next/navigation";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export type EditButtonProps = {}
|
||||
export type EditButtonProps = {};
|
||||
|
||||
export const EditButton = (props: EditButtonProps) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
return(
|
||||
<Link
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
href={`${pathname}/edit`}
|
||||
>
|
||||
return (
|
||||
<Link className={buttonVariants({ variant: "outline" })} href={`${pathname}/edit`}>
|
||||
<GearIcon className="w-7 h-7" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,38 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import {useState} from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import {DateTimePicker} from "@/components/wrappers/common/daytime-picker";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
useZodForm
|
||||
} from "@/components/ui/form";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {RestoreSchema} from "@/components/wrappers/dashboard/database/restore-form.schema";
|
||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {ComboBox, ComboBoxFormItem} from "@/components/wrappers/common/combobox";
|
||||
import {Label} from "@/components/ui/label";
|
||||
import { DateTimePicker } from "@/components/wrappers/common/day-time-picker";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { RestoreSchema } from "@/components/wrappers/dashboard/database/restore-form.schema";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { ComboBox, ComboBoxFormItem } from "@/components/wrappers/common/combobox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
export type restoreFormProps = {
|
||||
databaseToRestore: any
|
||||
databases: any[]
|
||||
backups: any[]
|
||||
}
|
||||
|
||||
databaseToRestore: any;
|
||||
databases: any[];
|
||||
backups: any[];
|
||||
};
|
||||
|
||||
export const RestoreForm = (props: restoreFormProps) => {
|
||||
|
||||
const {databaseToRestore, databases, backups} = props
|
||||
|
||||
console.log("bacups", backups)
|
||||
console.log("bacups", databaseToRestore)
|
||||
const { databaseToRestore, databases, backups } = props;
|
||||
|
||||
const backupLocations = [
|
||||
{
|
||||
@@ -43,7 +29,7 @@ export const RestoreForm = (props: restoreFormProps) => {
|
||||
value: "desktop-file",
|
||||
label: "Desktop File",
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const executionModes = [
|
||||
{
|
||||
@@ -54,29 +40,23 @@ export const RestoreForm = (props: restoreFormProps) => {
|
||||
value: "scheduled",
|
||||
label: "Scheduled",
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const [selectedDatabaseId, setSelectedDatabaseId] = useState(databaseToRestore.id)
|
||||
const [selectedDatabaseId, setSelectedDatabaseId] = useState(databaseToRestore.id);
|
||||
|
||||
|
||||
const filteredBackups = backups.filter(backup => backup.databaseId == selectedDatabaseId)
|
||||
const filteredBackups = backups.filter((backup) => backup.databaseId == selectedDatabaseId);
|
||||
|
||||
const defaultValues = {
|
||||
executionMode: "immediate",
|
||||
backupLocation: "remote-file",
|
||||
}
|
||||
executionMode: "immediate" as const,
|
||||
backupLocation: "remote-file" as const,
|
||||
};
|
||||
|
||||
const form = useZodForm({
|
||||
schema: RestoreSchema,
|
||||
defaultValues: defaultValues
|
||||
defaultValues: defaultValues,
|
||||
});
|
||||
|
||||
const values = form.getValues()
|
||||
console.log("values", values)
|
||||
|
||||
|
||||
const mutation = useMutation({})
|
||||
|
||||
const values = form.getValues();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -89,36 +69,34 @@ export const RestoreForm = (props: restoreFormProps) => {
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="backupLocation"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Backup location</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{backupLocations.map((backupLocation, key) =>
|
||||
{backupLocations.map((backupLocation, key) => (
|
||||
<SelectItem key={key} value={backupLocation.value}>
|
||||
{backupLocation.label}
|
||||
</SelectItem>
|
||||
)}
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{values.backupLocation == "remote-file" ?
|
||||
{values.backupLocation == "remote-file" ? (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<Label>Database</Label>
|
||||
<ComboBox
|
||||
values={databases.map(database =>
|
||||
({"value": database.id, "label": database.name})
|
||||
)}
|
||||
values={databases.map((database) => ({ value: database.id, label: database.name }))}
|
||||
onValueChange={setSelectedDatabaseId}
|
||||
defaultValue={selectedDatabaseId}
|
||||
searchField
|
||||
@@ -127,81 +105,82 @@ export const RestoreForm = (props: restoreFormProps) => {
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="remoteBackup"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Remote backup</FormLabel>
|
||||
<ComboBoxFormItem
|
||||
values={filteredBackups.map(backup => ({
|
||||
"value": backup.id,
|
||||
"label": backup.createdAt.toString()
|
||||
})
|
||||
)}
|
||||
values={filteredBackups.map((backup) => ({
|
||||
value: backup.id,
|
||||
label: backup.createdAt.toString(),
|
||||
}))}
|
||||
{...field}
|
||||
/>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
: null}
|
||||
) : null}
|
||||
|
||||
{values.backupLocation == "desktop-file" ?
|
||||
{values.backupLocation == "desktop-file" ? (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="uploadedBackupFile"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>File</FormLabel>
|
||||
<FormControl>
|
||||
<Input id="uploadedBackupFile" type="file" {...field}/>
|
||||
<Input id="uploadedBackupFile" type="file" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/> : null}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="executionMode"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Execution mode</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{executionModes.map((executionMode, key) =>
|
||||
{executionModes.map((executionMode, key) => (
|
||||
<SelectItem key={key} value={executionMode.value}>
|
||||
{executionMode.label}
|
||||
</SelectItem>
|
||||
)}
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{values.executionMode == "scheduled" ?
|
||||
{values.executionMode == "scheduled" ? (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scheduledDatetime"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Scheduled date</FormLabel>
|
||||
<FormControl>
|
||||
<DateTimePicker value={field.value} onChange={field.onChange}/>
|
||||
<DateTimePicker value={field.value} onSelect={field.onChange} />
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/> : null}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Button type="submit">Launch restore</Button>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
import {z} from "zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const ImmediateExecutionSchema = z.object({
|
||||
executionMode: z.literal('immediate'),
|
||||
executionMode: z.literal("immediate"),
|
||||
});
|
||||
|
||||
const ScheduledExecutionSchema = z.object({
|
||||
executionMode: z.literal('scheduled'),
|
||||
executionMode: z.literal("scheduled"),
|
||||
scheduledDatetime: z.date(),
|
||||
});
|
||||
|
||||
const CommonBackupSchema = z.union([ImmediateExecutionSchema, ScheduledExecutionSchema]);
|
||||
const RemoteImmediateSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("remote-file"),
|
||||
})
|
||||
.merge(ImmediateExecutionSchema);
|
||||
|
||||
const RemoteBackupSchema = z.object({
|
||||
backupLocation: z.literal('remote-file'),
|
||||
}).merge(CommonBackupSchema);
|
||||
const RemoteScheduledSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("remote-file"),
|
||||
})
|
||||
.merge(ScheduledExecutionSchema);
|
||||
|
||||
const DesktopBackupSchema = z.object({
|
||||
backupLocation: z.literal('desktop-file'),
|
||||
uploadedBackupFile: z.instanceof(File),
|
||||
}).merge(CommonBackupSchema);
|
||||
const DesktopImmediateSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("desktop-file"),
|
||||
uploadedBackupFile: z.instanceof(File),
|
||||
})
|
||||
.merge(ImmediateExecutionSchema);
|
||||
|
||||
export const RestoreSchema = z.union([RemoteBackupSchema, DesktopBackupSchema]);
|
||||
const DesktopScheduledSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("desktop-file"),
|
||||
uploadedBackupFile: z.instanceof(File),
|
||||
})
|
||||
.merge(ScheduledExecutionSchema);
|
||||
|
||||
export const RestoreSchema = z.union([RemoteImmediateSchema, RemoteScheduledSchema, DesktopImmediateSchema, DesktopScheduledSchema]);
|
||||
export type RestoreType = z.infer<typeof RestoreSchema>;
|
||||
|
||||
Reference in New Issue
Block a user