mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: detabase details page in order to support backup only databases.
This commit is contained in:
@@ -21,14 +21,14 @@ interface DatabaseActionsCellProps {
|
||||
backup: BackupWith;
|
||||
activeMember: MemberWithUser;
|
||||
isAlreadyRestore: boolean;
|
||||
isBackupOnly: boolean;
|
||||
}
|
||||
|
||||
export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore}: DatabaseActionsCellProps) {
|
||||
export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore, isBackupOnly}: DatabaseActionsCellProps) {
|
||||
const {openModal} = useBackupModal();
|
||||
|
||||
if (backup.deletedAt || activeMember.role === "member") return null;
|
||||
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center space-x-2")}>
|
||||
<DropdownMenu>
|
||||
@@ -40,18 +40,19 @@ export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore}: Da
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
|
||||
{backup.status == "success" ? (
|
||||
<>
|
||||
<TooltipCustom disabled={isAlreadyRestore}
|
||||
text="Already a restoration waiting">
|
||||
<DropdownMenuItem
|
||||
disabled={isAlreadyRestore}
|
||||
onSelect={() => openModal("restore", backup)}
|
||||
>
|
||||
<ReloadIcon/> Restore
|
||||
</DropdownMenuItem>
|
||||
</TooltipCustom>
|
||||
{!isBackupOnly && (
|
||||
<TooltipCustom disabled={isAlreadyRestore}
|
||||
text="Already a restoration waiting">
|
||||
<DropdownMenuItem
|
||||
disabled={isAlreadyRestore}
|
||||
onSelect={() => openModal("restore", backup)}
|
||||
>
|
||||
<ReloadIcon/> Restore
|
||||
</DropdownMenuItem>
|
||||
</TooltipCustom>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onSelect={() => openModal("download", backup)}
|
||||
>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import {StorageChannel} from "@/db/schema/12_storage-channel";
|
||||
import {
|
||||
EVENT_KIND_BACKUP_ONLY_OPTIONS,
|
||||
EVENT_KIND_OPTIONS,
|
||||
PoliciesSchema,
|
||||
PoliciesType,
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
createAlertPoliciesAction, createStoragePoliciesAction, deleteAlertPoliciesAction, deleteStoragePoliciesAction,
|
||||
updateAlertPoliciesAction, updateStoragePoliciesAction
|
||||
} from "@/components/wrappers/dashboard/database/channels-policy/policy.action";
|
||||
import {backupOnly} from "@/components/wrappers/dashboard/projects/database/database-tabs";
|
||||
|
||||
type ChannelPoliciesFormProps = {
|
||||
onSuccess?: () => void;
|
||||
@@ -53,6 +55,9 @@ export const ChannelPoliciesForm = ({
|
||||
const isMobile = useIsMobile();
|
||||
const channelText = getChannelTextBasedOnKind(kind);
|
||||
|
||||
const isBackupOnly = backupOnly.some((type) => database.dbms === type)
|
||||
|
||||
|
||||
const organizationChannels = channels.map(c => c.id);
|
||||
|
||||
const filterByChannel = <T, K extends keyof T>(
|
||||
@@ -300,7 +305,7 @@ export const ChannelPoliciesForm = ({
|
||||
<FormControl>
|
||||
<div className="max-w-full overflow-hidden">
|
||||
<MultiSelect
|
||||
options={EVENT_KIND_OPTIONS}
|
||||
options={isBackupOnly ? EVENT_KIND_BACKUP_ONLY_OPTIONS : EVENT_KIND_OPTIONS}
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value ?? []}
|
||||
placeholder={isMobile ? "Select events..." : "Select events to trigger notifications..."}
|
||||
|
||||
@@ -23,5 +23,10 @@ export const EVENT_KIND_OPTIONS = [
|
||||
{label: "Error Restore", value: "error_restore"},
|
||||
{label: "Success Restore", value: "success_restore"},
|
||||
{label: "Success Backup", value: "success_backup"},
|
||||
{label: "Weekly Report", value: "weekly_report"},
|
||||
// {label: "Weekly Report", value: "weekly_report"},
|
||||
];
|
||||
|
||||
export const EVENT_KIND_BACKUP_ONLY_OPTIONS = [
|
||||
{label: "Error Backup", value: "error_backup"},
|
||||
{label: "Success Backup", value: "success_backup"},
|
||||
];
|
||||
@@ -18,6 +18,8 @@ export type DatabaseTabsProps = {
|
||||
activeMember: MemberWithUser
|
||||
};
|
||||
|
||||
export const backupOnly = ["redis"];
|
||||
|
||||
export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -26,6 +28,7 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
const newTab = searchParams.get("tab") ?? "backup";
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setTab(newTab);
|
||||
}, [searchParams]);
|
||||
|
||||
@@ -33,13 +36,13 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
||||
router.push(`?tab=${value}`);
|
||||
};
|
||||
|
||||
|
||||
const isBackupOnly = backupOnly.some((type) => props.database.dbms === type)
|
||||
|
||||
|
||||
return (
|
||||
<Tabs className="flex flex-col flex-1" value={tab} onValueChange={handleChangeTab}>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="backup">Backup</TabsTrigger>
|
||||
<TabsTrigger value="restore">Restoration</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent className="h-full justify-between" value="backup">
|
||||
<>
|
||||
{isBackupOnly ?
|
||||
<DatabaseBackupList
|
||||
isAlreadyRestore={props.isAlreadyRestore}
|
||||
settings={props.settings}
|
||||
@@ -47,15 +50,33 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
||||
backups={props.backups}
|
||||
activeMember={props.activeMember}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent className="h-full justify-between" value="restore">
|
||||
<DatabaseRestoreList
|
||||
isAlreadyRestore={props.isAlreadyRestore}
|
||||
restorations={props.restorations}
|
||||
activeMember={props.activeMember}
|
||||
databaseId={props.database.id}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
:
|
||||
<Tabs className="flex flex-col flex-1" value={tab} onValueChange={handleChangeTab}>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="backup">Backup</TabsTrigger>
|
||||
<TabsTrigger value="restore">Restoration</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent className="h-full justify-between" value="backup">
|
||||
<DatabaseBackupList
|
||||
isAlreadyRestore={props.isAlreadyRestore}
|
||||
settings={props.settings}
|
||||
database={props.database}
|
||||
backups={props.backups}
|
||||
activeMember={props.activeMember}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent className="h-full justify-between" value="restore">
|
||||
<DatabaseRestoreList
|
||||
isAlreadyRestore={props.isAlreadyRestore}
|
||||
restorations={props.restorations}
|
||||
activeMember={props.activeMember}
|
||||
databaseId={props.database.id}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
}
|
||||
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import {formatLocalizedDate} from "@/utils/date-formatting";
|
||||
import {formatBytes} from "@/utils/text";
|
||||
import {DatabaseActionsCell} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions-cell";
|
||||
import { Badge as BadgeC } from "@/components/ui/badge";
|
||||
import {backupOnly} from "@/components/wrappers/dashboard/projects/database/database-tabs";
|
||||
|
||||
export function backupColumns(
|
||||
isAlreadyRestore: boolean,
|
||||
@@ -18,6 +19,10 @@ export function backupColumns(
|
||||
database: DatabaseWith,
|
||||
activeMember: MemberWithUser
|
||||
): ColumnDef<Backup>[] {
|
||||
|
||||
const isBackupOnly = backupOnly.some((type) => database.dbms === type)
|
||||
|
||||
|
||||
return [
|
||||
{
|
||||
id: "availability",
|
||||
@@ -90,137 +95,7 @@ export function backupColumns(
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({row}) => <DatabaseActionsCell isAlreadyRestore={isAlreadyRestore} activeMember={activeMember} backup={row.original}/>,
|
||||
cell: ({row}) => <DatabaseActionsCell isAlreadyRestore={isAlreadyRestore} activeMember={activeMember} backup={row.original} isBackupOnly={isBackupOnly}/>,
|
||||
},
|
||||
// {
|
||||
// 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