fix: detabase details page in order to support backup only databases.

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