mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on some bugs.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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";
|
||||
|
||||
export const AdvancedCronSelect = ({
|
||||
id,
|
||||
label,
|
||||
options,
|
||||
type,
|
||||
value,
|
||||
defaultValue,
|
||||
onValueChange,
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
options: string[];
|
||||
type: string;
|
||||
value: string;
|
||||
defaultValue: string;
|
||||
onValueChange: (value: string) => void;
|
||||
}) => {
|
||||
const [isAdvanced, setIsAdvanced] = useState(false);
|
||||
const [customValue, setCustomValue] = useState(defaultValue || value);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleBlur = () => {
|
||||
if (customValue.trim() === "") {
|
||||
setIsAdvanced(false);
|
||||
} else if (!isValidCronPart(type, customValue)) {
|
||||
setError("Invalid cron part value.");
|
||||
} else {
|
||||
setError(null);
|
||||
onValueChange(customValue);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 items-center gap-2">
|
||||
<Label htmlFor={id} className="text-left">
|
||||
{label}
|
||||
</Label>
|
||||
{!isAdvanced ? (
|
||||
<Select
|
||||
id={id}
|
||||
className="col-span-4"
|
||||
value={defaultValue}
|
||||
onValueChange={(value: string) => {
|
||||
if (value === "advanced") {
|
||||
setIsAdvanced(true);
|
||||
} else {
|
||||
setCustomValue(value);
|
||||
onValueChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue>
|
||||
{value}
|
||||
{/*{options.includes(value) ? value : "Custom value"}*/}
|
||||
</SelectValue>
|
||||
{/*<SelectValue placeholder="Select value" />*/}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map((opt: string) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectItem value="advanced">Advanced</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<Input
|
||||
id={id}
|
||||
className="col-span-4"
|
||||
type="text"
|
||||
value={customValue}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setCustomValue(newValue);
|
||||
if (isValidCronPart(type, newValue)) {
|
||||
setError(null);
|
||||
} else {
|
||||
setError("Invalid cron part value.");
|
||||
}
|
||||
}}
|
||||
onBlur={handleBlur}
|
||||
placeholder="e.g., *, 1-5, */5"
|
||||
/>
|
||||
)}
|
||||
{error && <p className="text-sm text-red-500 col-span-4">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
"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/cron-button/cron-input";
|
||||
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/cron-button/cron.action";
|
||||
import {Database} from "@/db/schema/06_database";
|
||||
|
||||
export type CronButtonProps = {
|
||||
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 }),
|
||||
onSuccess: () => {
|
||||
toast.success(`Method updated successfully.`);
|
||||
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("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<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>
|
||||
</DialogHeader>
|
||||
<Separator />
|
||||
|
||||
<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);
|
||||
}}
|
||||
id="type-mode"
|
||||
/>
|
||||
</div>
|
||||
{isSwitched ? <CronInput database={props.database} /> : null}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import { AdvancedCronSelect } from "./advanced-cron-select";
|
||||
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/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/06_database";
|
||||
|
||||
export type CronInputProps = {
|
||||
database: Database;
|
||||
};
|
||||
|
||||
export const CronInput = ({ database }: CronInputProps) => {
|
||||
const [cron, setCron] = useState<string>(database.backupPolicy ?? "* * * * *");
|
||||
const router = useRouter();
|
||||
|
||||
const updateBackupPolicy = useMutation({
|
||||
mutationFn: (value: string) => updateDatabaseBackupPolicyAction({ databaseId: database.id, backupPolicy: value }),
|
||||
onSuccess: () => {
|
||||
toast.success(`Cron updated successfully.`);
|
||||
router.refresh();
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(`An error occurred while updating cron value.`);
|
||||
},
|
||||
});
|
||||
|
||||
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;
|
||||
setCron(cronParts.join(" "));
|
||||
};
|
||||
|
||||
const handleUpdateCron = async (cron: string) => {
|
||||
await updateBackupPolicy.mutateAsync(cron);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Configure your cron schedule</h1>
|
||||
<AdvancedCronSelect
|
||||
id="minute"
|
||||
label="Minute"
|
||||
options={Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"))}
|
||||
type="minute"
|
||||
value={cron.split(" ")[0]}
|
||||
defaultValue={cron.split(" ")[0]}
|
||||
onValueChange={(value) => handleChangeCron("minute", value)}
|
||||
/>
|
||||
<AdvancedCronSelect
|
||||
id="hour"
|
||||
label="Hour"
|
||||
options={Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"))}
|
||||
type="hour"
|
||||
value={cron.split(" ")[1]}
|
||||
defaultValue={cron.split(" ")[1]}
|
||||
onValueChange={(value) => handleChangeCron("hour", value)}
|
||||
/>
|
||||
<AdvancedCronSelect
|
||||
id="day-of-month"
|
||||
label="Day of Month"
|
||||
options={Array.from({ length: 31 }, (_, i) => String(i + 1).padStart(2, "0"))}
|
||||
type="day-of-month"
|
||||
value={cron.split(" ")[2]}
|
||||
defaultValue={cron.split(" ")[2]}
|
||||
onValueChange={(value) => handleChangeCron("day-of-month", value)}
|
||||
/>
|
||||
<AdvancedCronSelect
|
||||
id="month"
|
||||
label="Month"
|
||||
options={["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]}
|
||||
type="month"
|
||||
value={cron.split(" ")[3]}
|
||||
defaultValue={cron.split(" ")[3]}
|
||||
onValueChange={(value) => handleChangeCron("month", value)}
|
||||
/>
|
||||
<AdvancedCronSelect
|
||||
id="day-of-week"
|
||||
label="Day of Week"
|
||||
options={["0", "1", "2", "3", "4", "5", "6"]}
|
||||
type="day-of-week"
|
||||
value={cron.split(" ")[4]}
|
||||
defaultValue={cron.split(" ")[4]}
|
||||
onValueChange={(value) => handleChangeCron("day-of-week", value)}
|
||||
/>
|
||||
<Separator />
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<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>
|
||||
<div className="flex justify-between gap-2">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setCron("* * * * *");
|
||||
await handleUpdateCron("* * * * *");
|
||||
}}
|
||||
variant="destructive"
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await handleUpdateCron(cron);
|
||||
}}
|
||||
>
|
||||
Save cron
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
"use server";
|
||||
|
||||
import { userAction } from "@/safe-actions";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import * as drizzleDb from "@/db";
|
||||
|
||||
export const updateDatabaseBackupPolicyAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
databaseId: z.string(),
|
||||
backupPolicy: z.string(),
|
||||
})
|
||||
)
|
||||
.action(async ({ parsedInput }) => {
|
||||
const cronPolicy = parsedInput.backupPolicy === "" ? null : parsedInput.backupPolicy;
|
||||
|
||||
const [updated] = await db
|
||||
.update(drizzleDb.schemas.database)
|
||||
.set({
|
||||
backupPolicy: cronPolicy,
|
||||
})
|
||||
.where(eq(drizzleDb.schemas.database.id, parsedInput.databaseId))
|
||||
.returning()
|
||||
.execute();
|
||||
|
||||
return {
|
||||
data: updated,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user