mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
feat: backup delete/restore actions.
This commit is contained in:
@@ -1,48 +1,3 @@
|
|||||||
// "use client";
|
|
||||||
//
|
|
||||||
// import { Button } from "@/components/ui/button";
|
|
||||||
// import { useState } from "react";
|
|
||||||
// import { Loader2 } from "lucide-react";
|
|
||||||
//
|
|
||||||
// export type VariantButton = {
|
|
||||||
// secondary: string;
|
|
||||||
// default: string;
|
|
||||||
// outline: string;
|
|
||||||
// ghost: string;
|
|
||||||
// link: string;
|
|
||||||
// destructive: string;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// export type ButtonWithConfirmProps = {
|
|
||||||
// icon?: any;
|
|
||||||
// text: string;
|
|
||||||
// variant?: keyof VariantButton;
|
|
||||||
// className?: string;
|
|
||||||
// onClick?: () => void;
|
|
||||||
// isPending?: boolean;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
|
|
||||||
// const [isConfirming, setIsConfirming] = useState(false);
|
|
||||||
//
|
|
||||||
// return (
|
|
||||||
// <Button
|
|
||||||
// onClick={() => {
|
|
||||||
// if (isConfirming && props.onClick) {
|
|
||||||
// props.onClick();
|
|
||||||
// } else {
|
|
||||||
// setIsConfirming(true);
|
|
||||||
// }
|
|
||||||
// }}
|
|
||||||
// variant={props.variant ? props.variant : "default"}
|
|
||||||
// className={props.className}
|
|
||||||
// >
|
|
||||||
// {props.isPending && <Loader2 className="animate-spin mr-4" size={16} />}
|
|
||||||
// {isConfirming ? "Are you sure ?" : `${props.text}`}
|
|
||||||
// <>{props.icon ? props.icon : null}</>
|
|
||||||
// </Button>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
"use client"
|
"use client"
|
||||||
import {Button, ButtonVariantsProps} from "@/components/ui/button";
|
import {Button, ButtonVariantsProps} from "@/components/ui/button";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
@@ -57,6 +12,7 @@ export type ButtonWithConfirmProps = {
|
|||||||
button: {
|
button: {
|
||||||
main: {
|
main: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
type?: "button" | "submit" | "reset" | undefined;
|
||||||
text?: string;
|
text?: string;
|
||||||
icon?: any;
|
icon?: any;
|
||||||
variant?: ButtonVariantsProps["variant"];
|
variant?: ButtonVariantsProps["variant"];
|
||||||
@@ -100,6 +56,7 @@ export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
|
|||||||
)}
|
)}
|
||||||
role="button">
|
role="button">
|
||||||
<Button
|
<Button
|
||||||
|
type={props.button.main.type}
|
||||||
disabled={!!props.button.main.disabled}
|
disabled={!!props.button.main.disabled}
|
||||||
variant={props.button.main.variant ?? "default"}
|
variant={props.button.main.variant ?? "default"}
|
||||||
size={props.button.main.size ?? "default"}
|
size={props.button.main.size ?? "default"}
|
||||||
@@ -108,7 +65,8 @@ export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
|
|||||||
if (!props.button.main.disabled) setIsConfirming(true);
|
if (!props.button.main.disabled) setIsConfirming(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.button.main.icon}
|
{props.isPending && <Loader2 className="animate-spin mr-4" size={16}/>}
|
||||||
|
{props.button.main.icon}
|
||||||
{props.button.main.text && <span>{props.button.main.text}</span>}
|
{props.button.main.text && <span>{props.button.main.text}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -15,19 +15,17 @@ import {MoreHorizontal, Trash2, Download} from "lucide-react";
|
|||||||
import {ReloadIcon} from "@radix-ui/react-icons";
|
import {ReloadIcon} from "@radix-ui/react-icons";
|
||||||
import {cn} from "@/lib/utils";
|
import {cn} from "@/lib/utils";
|
||||||
import {MemberWithUser} from "@/db/schema/03_organization";
|
import {MemberWithUser} from "@/db/schema/03_organization";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {TooltipCustom} from "@/components/wrappers/common/tooltip-custom";
|
||||||
import {deleteBackupAction} from "@/features/dashboard/restore/restore.action";
|
|
||||||
import {toast} from "sonner";
|
|
||||||
|
|
||||||
interface DatabaseActionsCellProps {
|
interface DatabaseActionsCellProps {
|
||||||
backup: BackupWith;
|
backup: BackupWith;
|
||||||
activeMember: MemberWithUser;
|
activeMember: MemberWithUser;
|
||||||
|
isAlreadyRestore: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DatabaseActionsCell({backup, activeMember}: DatabaseActionsCellProps) {
|
export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore}: DatabaseActionsCellProps) {
|
||||||
const {openModal} = useBackupModal();
|
const {openModal} = useBackupModal();
|
||||||
|
|
||||||
|
|
||||||
if (backup.deletedAt || activeMember.role === "member") return null;
|
if (backup.deletedAt || activeMember.role === "member") return null;
|
||||||
|
|
||||||
|
|
||||||
@@ -42,12 +40,26 @@ export function DatabaseActionsCell({backup, activeMember}: DatabaseActionsCellP
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
<DropdownMenuItem onSelect={() => openModal("restore", backup)}>
|
|
||||||
<ReloadIcon/> Restore
|
{backup.status == "success" ? (
|
||||||
</DropdownMenuItem>
|
<>
|
||||||
<DropdownMenuItem onSelect={() => openModal("download", backup)}>
|
<TooltipCustom disabled={isAlreadyRestore}
|
||||||
<Download/> Download
|
text="Already a restoration waiting">
|
||||||
</DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
|
disabled={isAlreadyRestore}
|
||||||
|
onSelect={() => openModal("restore", backup)}
|
||||||
|
>
|
||||||
|
<ReloadIcon/> Restore
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</TooltipCustom>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={() => openModal("download", backup)}
|
||||||
|
>
|
||||||
|
<Download/> Download
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<DropdownMenuSeparator/>
|
<DropdownMenuSeparator/>
|
||||||
<DropdownMenuItem onSelect={() => openModal("delete", backup)} className="text-red-600">
|
<DropdownMenuItem onSelect={() => openModal("delete", backup)} className="text-red-600">
|
||||||
<Trash2/> Delete
|
<Trash2/> Delete
|
||||||
|
|||||||
+136
-82
@@ -28,6 +28,9 @@ import {toast} from "sonner";
|
|||||||
import {SafeActionResult} from "next-safe-action";
|
import {SafeActionResult} from "next-safe-action";
|
||||||
import {ServerActionResult} from "@/types/action-type";
|
import {ServerActionResult} from "@/types/action-type";
|
||||||
import {ZodString} from "zod";
|
import {ZodString} from "zod";
|
||||||
|
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||||
|
import {AlertCircleIcon, Trash2} from "lucide-react";
|
||||||
|
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
|
||||||
|
|
||||||
type BackupActionsFormProps = {
|
type BackupActionsFormProps = {
|
||||||
backup: BackupWith;
|
backup: BackupWith;
|
||||||
@@ -36,6 +39,7 @@ type BackupActionsFormProps = {
|
|||||||
|
|
||||||
export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
||||||
|
|
||||||
|
const filteredBackupStorages = backup.storages?.filter((storage) => storage.deletedAt === null) ?? []
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const {closeModal} = useBackupModal();
|
const {closeModal} = useBackupModal();
|
||||||
|
|
||||||
@@ -98,8 +102,6 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
const mutationDeleteEntireBackup = useMutation({
|
const mutationDeleteEntireBackup = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
|
|
||||||
console.log("mutation deleteEntireBackup");
|
|
||||||
|
|
||||||
const result = await deleteBackupAction({
|
const result = await deleteBackupAction({
|
||||||
databaseId: backup.databaseId,
|
databaseId: backup.databaseId,
|
||||||
backupId: backup.id,
|
backupId: backup.id,
|
||||||
@@ -118,6 +120,8 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
|
||||||
|
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
className="flex flex-col gap-4 mb-1"
|
className="flex flex-col gap-4 mb-1"
|
||||||
@@ -125,102 +129,152 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
await mutation.mutateAsync(values);
|
await mutation.mutateAsync(values);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="backupStorageId"
|
{filteredBackupStorages.length > 0 ?
|
||||||
render={({field}) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Choose a storage backup</FormLabel>
|
<FormField
|
||||||
<FormControl>
|
control={form.control}
|
||||||
<div style={{height: "250px"}}>
|
name="backupStorageId"
|
||||||
<Swiper
|
render={({field}) => (
|
||||||
direction="vertical"
|
<FormItem>
|
||||||
slidesPerView={3.5}
|
<FormLabel>Choose a storage backup</FormLabel>
|
||||||
spaceBetween={10}
|
<FormControl>
|
||||||
// pagination={{ clickable: true }}
|
|
||||||
mousewheel={{releaseOnEdges: true, forceToAxis: true}}
|
<div style={{height: "250px"}}>
|
||||||
modules={[Pagination, Mousewheel]}
|
<Swiper
|
||||||
className="mySwiper"
|
direction="vertical"
|
||||||
style={{height: "100%"}}
|
slidesPerView={3.5}
|
||||||
>
|
spaceBetween={10}
|
||||||
{backup.storages?.filter((storage) => storage.deletedAt === null).map((storage: BackupStorageWith) => (
|
// pagination={{ clickable: true }}
|
||||||
<SwiperSlide key={storage.id}>
|
mousewheel={{releaseOnEdges: true, forceToAxis: true}}
|
||||||
<button
|
modules={[Pagination, Mousewheel]}
|
||||||
disabled={action !== "delete" && storage.status.toLowerCase() !== "success"}
|
className="mySwiper"
|
||||||
type="button"
|
style={{height: "100%"}}
|
||||||
onClick={() => field.onChange(storage.id)}
|
>
|
||||||
className={`w-full h-full flex items-start gap-3 p-4 rounded-lg border text-left transition-colors
|
{filteredBackupStorages.map((storage: BackupStorageWith) => (
|
||||||
|
<SwiperSlide key={storage.id}>
|
||||||
|
<button
|
||||||
|
disabled={action !== "delete" && storage.status.toLowerCase() !== "success"}
|
||||||
|
type="button"
|
||||||
|
onClick={() => field.onChange(storage.id)}
|
||||||
|
className={`w-full h-full flex items-start gap-3 p-4 rounded-lg border text-left transition-colors
|
||||||
${field.value === storage.id
|
${field.value === storage.id
|
||||||
? "border-foreground bg-background"
|
? "border-foreground bg-background"
|
||||||
: "border-border bg-background" + ((storage.status.toLowerCase() === "success" || action === "delete") ? " hover:border-muted-foreground" : "")}
|
: "border-border bg-background" + ((storage.status.toLowerCase() === "success" || action === "delete") ? " hover:border-muted-foreground" : "")}
|
||||||
${storage.status.toLowerCase() !== "success" && action !== "delete" ? "opacity-50 cursor-not-allowed" : ""}`}
|
${storage.status.toLowerCase() !== "success" && action !== "delete" ? "opacity-50 cursor-not-allowed" : ""}`}
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`mt-0.5 h-4 w-4 shrink-0 rounded-full border ${
|
|
||||||
field.value === storage.id ? "border-foreground" : "border-muted-foreground"
|
|
||||||
} flex items-center justify-center`}
|
|
||||||
>
|
>
|
||||||
{field.value === storage.id &&
|
<div
|
||||||
<div className="h-2 w-2 rounded-full bg-foreground"/>}
|
className={`mt-0.5 h-4 w-4 shrink-0 rounded-full border ${
|
||||||
</div>
|
field.value === storage.id ? "border-foreground" : "border-muted-foreground"
|
||||||
<div className="flex-1 min-w-0">
|
} flex items-center justify-center`}
|
||||||
<div className="flex items-center gap-2">
|
>
|
||||||
<div className="flex-1 min-w-0 flex flex-col gap-1">
|
{field.value === storage.id &&
|
||||||
<div className="flex items-center justify-between">
|
<div className="h-2 w-2 rounded-full bg-foreground"/>}
|
||||||
<div className="flex items-center gap-2">
|
</div>
|
||||||
{getChannelIcon(storage.storageChannel?.provider || "")}
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-medium text-foreground">
|
<div className="flex items-center gap-2">
|
||||||
{isMobile ? truncateWords(storage?.storageChannel?.name ?? "", 2) : storage.storageChannel?.name}
|
<div className="flex-1 min-w-0 flex flex-col gap-1">
|
||||||
</h3>
|
<div className="flex items-center justify-between">
|
||||||
<Badge variant="secondary"
|
<div className="flex items-center gap-2">
|
||||||
className="text-xs font-mono">
|
{getChannelIcon(storage.storageChannel?.provider || "")}
|
||||||
{storage.storageChannel?.provider}
|
<h3 className="font-medium text-foreground">
|
||||||
|
{isMobile ? truncateWords(storage?.storageChannel?.name ?? "", 2) : storage.storageChannel?.name}
|
||||||
|
</h3>
|
||||||
|
<Badge variant="secondary"
|
||||||
|
className="text-xs font-mono">
|
||||||
|
{storage.storageChannel?.provider}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline"
|
||||||
|
className={`gap-1.5 ${getStatusColor(storage.status)}`}>
|
||||||
|
{getStatusIcon(storage.status === "success")}
|
||||||
|
<span
|
||||||
|
className="capitalize">{storage.status.toUpperCase()}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline"
|
|
||||||
className={`gap-1.5 ${getStatusColor(storage.status)}`}>
|
|
||||||
{getStatusIcon(storage.status === "success")}
|
|
||||||
<span
|
|
||||||
className="capitalize">{storage.status.toUpperCase()}</span>
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</button>
|
</SwiperSlide>
|
||||||
</SwiperSlide>
|
)) ?? <p>No storages available</p>}
|
||||||
)) ?? <p>No storages available</p>}
|
</Swiper>
|
||||||
</Swiper>
|
</div>
|
||||||
</div>
|
</FormControl>
|
||||||
</FormControl>
|
<FormMessage/>
|
||||||
<FormMessage/>
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
|
||||||
|
:
|
||||||
|
<Alert>
|
||||||
|
<AlertCircleIcon/>
|
||||||
|
<AlertTitle>Backup does not have files</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
<p>You can safely delete the entire backup; no files seem to be related. Maybe an error
|
||||||
|
occurred.</p>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
|
||||||
<div className="flex flex-row items-center gap-x-4 w-full">
|
<div className="flex flex-row items-center gap-x-4 w-full">
|
||||||
{action === "delete" && (
|
{action === "delete" && (
|
||||||
<ButtonWithLoading
|
// <ButtonWithLoading
|
||||||
type="button"
|
// type="button"
|
||||||
variant="destructive"
|
// variant="destructive"
|
||||||
onClick={() => mutationDeleteEntireBackup.mutateAsync()}
|
// onClick={() => mutationDeleteEntireBackup.mutateAsync()}
|
||||||
|
// isPending={mutationDeleteEntireBackup.isPending}
|
||||||
|
// disabled={mutationDeleteEntireBackup.isPending}
|
||||||
|
// >
|
||||||
|
// Delete entire backup
|
||||||
|
// </ButtonWithLoading>
|
||||||
|
|
||||||
|
|
||||||
|
<ButtonWithConfirm
|
||||||
|
title={"Delete entire backup"}
|
||||||
|
description={"Are you sure you want to delete this entire backup?"}
|
||||||
|
button={{
|
||||||
|
main: {
|
||||||
|
type: "button",
|
||||||
|
variant: "destructive",
|
||||||
|
text: "Delete entire backup",
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
className: "w-full",
|
||||||
|
text: "Delete",
|
||||||
|
icon: <Trash2/>,
|
||||||
|
variant: "destructive",
|
||||||
|
onClick: async () => {
|
||||||
|
mutationDeleteEntireBackup.mutateAsync()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
className: "w-full",
|
||||||
|
text: "Cancel",
|
||||||
|
icon: <Trash2/>,
|
||||||
|
variant: "outline",
|
||||||
|
},
|
||||||
|
}}
|
||||||
isPending={mutationDeleteEntireBackup.isPending}
|
isPending={mutationDeleteEntireBackup.isPending}
|
||||||
disabled={mutationDeleteEntireBackup.isPending}
|
/>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
{filteredBackupStorages.length > 0 && (
|
||||||
|
<ButtonWithLoading
|
||||||
|
type="submit"
|
||||||
|
isPending={mutation.isPending}
|
||||||
|
disabled={mutation.isPending}
|
||||||
|
className="ml-auto"
|
||||||
>
|
>
|
||||||
Delete All
|
Confirm
|
||||||
</ButtonWithLoading>
|
</ButtonWithLoading>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
<ButtonWithLoading
|
|
||||||
type="submit"
|
|
||||||
isPending={mutation.isPending}
|
|
||||||
disabled={mutation.isPending}
|
|
||||||
className="ml-auto"
|
|
||||||
>
|
|
||||||
Confirm
|
|
||||||
</ButtonWithLoading>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,139 +93,139 @@ export function backupColumns(
|
|||||||
return <StatusBadge status={row.getValue("status")}/>;
|
return <StatusBadge status={row.getValue("status")}/>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "actions2",
|
|
||||||
cell: ({row}) => <DatabaseActionsCell activeMember={activeMember} backup={row.original}/>,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
cell: ({row, table}) => {
|
cell: ({row}) => <DatabaseActionsCell isAlreadyRestore={isAlreadyRestore} activeMember={activeMember} backup={row.original}/>,
|
||||||
const status = row.getValue("status");
|
|
||||||
const rowData: Backup = row.original;
|
|
||||||
const fileName = rowData.file;
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const mutationRestore = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const restoration = await createRestorationAction({
|
|
||||||
backupId: rowData.id,
|
|
||||||
databaseId: rowData.databaseId,
|
|
||||||
});
|
|
||||||
// @ts-ignore
|
|
||||||
if (restoration.data.success) {
|
|
||||||
// @ts-ignore
|
|
||||||
toast.success(restoration.data.actionSuccess?.message || "Restoration created successfully!");
|
|
||||||
router.refresh();
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
toast.error(restoration.serverError || "Failed to create restoration.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const mutationDeleteBackup = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const deletion = await deleteBackupAction({
|
|
||||||
backupId: rowData.id,
|
|
||||||
databaseId: rowData.databaseId,
|
|
||||||
status: rowData.status,
|
|
||||||
file: rowData.file ?? "",
|
|
||||||
projectSlug: database.project?.slug!
|
|
||||||
});
|
|
||||||
// @ts-ignore
|
|
||||||
if (deletion.data.success) {
|
|
||||||
// @ts-ignore
|
|
||||||
toast.success(deletion.data.actionSuccess.message);
|
|
||||||
router.refresh();
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
toast.error(deletion.data.actionError.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleRestore = async () => {
|
|
||||||
await mutationRestore.mutateAsync();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
|
||||||
await mutationDeleteBackup.mutateAsync();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownload = async (fileName: string) => {
|
|
||||||
|
|
||||||
let url: string = "";
|
|
||||||
let data: SafeActionResult<string, ZodString, readonly [], {
|
|
||||||
_errors?: string[] | undefined;
|
|
||||||
}, readonly [], ServerActionResult<string>, object> | undefined
|
|
||||||
|
|
||||||
if (settings.storage == "local") {
|
|
||||||
data = await getFileUrlPresignedLocal({fileName: fileName!})
|
|
||||||
} else if (settings.storage == "s3") {
|
|
||||||
data = await getFileUrlPreSignedS3Action(`backups/${database.project?.slug}/${fileName}`);
|
|
||||||
}
|
|
||||||
if (data?.data?.success) {
|
|
||||||
url = data.data.value ?? "";
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
const errorMessage = data?.data?.actionError?.message || "Failed to get file!";
|
|
||||||
toast.error(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(url, "_self");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{(rowData.deletedAt == null && activeMember.role != "member") && (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
<MoreHorizontal className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
||||||
{status == "success" ? (
|
|
||||||
<>
|
|
||||||
<TooltipCustom disabled={isAlreadyRestore}
|
|
||||||
text="Already a restoration waiting">
|
|
||||||
<DropdownMenuItem
|
|
||||||
disabled={mutationRestore.isPending || isAlreadyRestore}
|
|
||||||
onClick={async () => {
|
|
||||||
await handleRestore();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ReloadIcon/> Restore
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</TooltipCustom>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={async () => {
|
|
||||||
await handleDownload(fileName ?? "");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Download/> Download
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<DropdownMenuSeparator/>
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="text-red-600"
|
|
||||||
onClick={async () => {
|
|
||||||
await handleDelete();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trash2/> Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// id: "actions",
|
||||||
|
// cell: ({row, table}) => {
|
||||||
|
// const status = row.getValue("status");
|
||||||
|
// const rowData: Backup = row.original;
|
||||||
|
// const fileName = rowData.file;
|
||||||
|
//
|
||||||
|
// const router = useRouter();
|
||||||
|
//
|
||||||
|
// const mutationRestore = useMutation({
|
||||||
|
// mutationFn: async () => {
|
||||||
|
// const restoration = await createRestorationAction({
|
||||||
|
// backupId: rowData.id,
|
||||||
|
// databaseId: rowData.databaseId,
|
||||||
|
// });
|
||||||
|
// // @ts-ignore
|
||||||
|
// if (restoration.data.success) {
|
||||||
|
// // @ts-ignore
|
||||||
|
// toast.success(restoration.data.actionSuccess?.message || "Restoration created successfully!");
|
||||||
|
// router.refresh();
|
||||||
|
// } else {
|
||||||
|
// // @ts-ignore
|
||||||
|
// toast.error(restoration.serverError || "Failed to create restoration.");
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const mutationDeleteBackup = useMutation({
|
||||||
|
// mutationFn: async () => {
|
||||||
|
// const deletion = await deleteBackupAction({
|
||||||
|
// backupId: rowData.id,
|
||||||
|
// databaseId: rowData.databaseId,
|
||||||
|
// status: rowData.status,
|
||||||
|
// file: rowData.file ?? "",
|
||||||
|
// projectSlug: database.project?.slug!
|
||||||
|
// });
|
||||||
|
// // @ts-ignore
|
||||||
|
// if (deletion.data.success) {
|
||||||
|
// // @ts-ignore
|
||||||
|
// toast.success(deletion.data.actionSuccess.message);
|
||||||
|
// router.refresh();
|
||||||
|
// } else {
|
||||||
|
// // @ts-ignore
|
||||||
|
// toast.error(deletion.data.actionError.message);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const handleRestore = async () => {
|
||||||
|
// await mutationRestore.mutateAsync();
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// const handleDelete = async () => {
|
||||||
|
// await mutationDeleteBackup.mutateAsync();
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// const handleDownload = async (fileName: string) => {
|
||||||
|
//
|
||||||
|
// let url: string = "";
|
||||||
|
// let data: SafeActionResult<string, ZodString, readonly [], {
|
||||||
|
// _errors?: string[] | undefined;
|
||||||
|
// }, readonly [], ServerActionResult<string>, object> | undefined
|
||||||
|
//
|
||||||
|
// if (settings.storage == "local") {
|
||||||
|
// data = await getFileUrlPresignedLocal({fileName: fileName!})
|
||||||
|
// } else if (settings.storage == "s3") {
|
||||||
|
// data = await getFileUrlPreSignedS3Action(`backups/${database.project?.slug}/${fileName}`);
|
||||||
|
// }
|
||||||
|
// if (data?.data?.success) {
|
||||||
|
// url = data.data.value ?? "";
|
||||||
|
// } else {
|
||||||
|
// // @ts-ignore
|
||||||
|
// const errorMessage = data?.data?.actionError?.message || "Failed to get file!";
|
||||||
|
// toast.error(errorMessage);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// window.open(url, "_self");
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// return (
|
||||||
|
// <>
|
||||||
|
// {(rowData.deletedAt == null && activeMember.role != "member") && (
|
||||||
|
// <DropdownMenu>
|
||||||
|
// <DropdownMenuTrigger asChild>
|
||||||
|
// <Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
|
// <span className="sr-only">Open menu</span>
|
||||||
|
// <MoreHorizontal className="h-4 w-4"/>
|
||||||
|
// </Button>
|
||||||
|
// </DropdownMenuTrigger>
|
||||||
|
// <DropdownMenuContent align="end">
|
||||||
|
// <DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
|
// {status == "success" ? (
|
||||||
|
// <>
|
||||||
|
// <TooltipCustom disabled={isAlreadyRestore}
|
||||||
|
// text="Already a restoration waiting">
|
||||||
|
// <DropdownMenuItem
|
||||||
|
// disabled={mutationRestore.isPending || isAlreadyRestore}
|
||||||
|
// onClick={async () => {
|
||||||
|
// await handleRestore();
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <ReloadIcon/> Restore
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// </TooltipCustom>
|
||||||
|
// <DropdownMenuItem
|
||||||
|
// onClick={async () => {
|
||||||
|
// await handleDownload(fileName ?? "");
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <Download/> Download
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// </>
|
||||||
|
// ) : null}
|
||||||
|
// <DropdownMenuSeparator/>
|
||||||
|
// <DropdownMenuItem
|
||||||
|
// className="text-red-600"
|
||||||
|
// onClick={async () => {
|
||||||
|
// await handleDelete();
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <Trash2/> Delete
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// </DropdownMenuContent>
|
||||||
|
// </DropdownMenu>
|
||||||
|
// )}
|
||||||
|
// </>
|
||||||
|
//
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user