Move backups into the settings pages
Admin settings gains a super-admin Backups page at /admin/settings/backups, with restore nested at .../backups/restore. Both use SettingsShell and SettingsGroup like the other settings sub pages instead of the retired admin hero.
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
import type { BunextPageModuleMetaFn } from "@moduletrace/bunext/types";
|
||||
import { SiteData } from "@/src/data/site-data";
|
||||
|
||||
type Params = {
|
||||
label: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export default function backupsPageMeta({
|
||||
label,
|
||||
description,
|
||||
}: Params): BunextPageModuleMetaFn {
|
||||
return async () => {
|
||||
return {
|
||||
title: `${label} | ${SiteData["SiteName"]}`,
|
||||
description,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import grabSuperAdminUser from "@/src/functions/backend/backups/grab-super-admin-user";
|
||||
import grabDbBackups from "@/src/functions/backend/backups/grab-db-backups";
|
||||
import type { PagePropsType } from "@/src/types";
|
||||
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
|
||||
|
||||
type Params = Parameters<BunextPageServerFn<PagePropsType>>[0];
|
||||
|
||||
/**
|
||||
* Shared server props for the `/admin/backups` sub-pages. Super admin only.
|
||||
*/
|
||||
export default async function grabBackupsServerProps({ req }: Params) {
|
||||
const user = await grabSuperAdminUser({ req });
|
||||
|
||||
if (!user?.id) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/admin",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
backups: grabDbBackups(),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import EmptyContent from "@moduletrace/twui/elements/EmptyContent";
|
||||
import Section from "@moduletrace/twui/layout/Section";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import { useContext } from "react";
|
||||
import BackupsTable, { type BackupsTableMode } from "../(partials)/backups-table";
|
||||
|
||||
type Props = {
|
||||
mode: BackupsTableMode;
|
||||
};
|
||||
|
||||
export default function BackupsListSection({ mode }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
|
||||
const backups = pageProps?.backups || [];
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full items-stretch">
|
||||
<Span size="small" variant="faded">
|
||||
{`${backups.length} backup${backups.length == 1 ? "" : "s"}`}
|
||||
</Span>
|
||||
{backups[0] ? (
|
||||
<BackupsTable backups={backups} mode={mode} />
|
||||
) : (
|
||||
<EmptyContent title="No backups yet" />
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import AdminHero from "@/src/components/general/admin-hero";
|
||||
import CreateBackupButton from "../(partials)/create-backup-button";
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
<AdminHero
|
||||
title="Backups"
|
||||
description="Snapshots of the database. Older backups are pruned automatically."
|
||||
buttons={<CreateBackupButton />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import Hero from "./(sections)/hero";
|
||||
import BackupsListSection from "./(sections)/backups-list-section";
|
||||
import backupsPageMeta from "./(functions)/backups-page-meta";
|
||||
|
||||
export default function AdminBackupsPage() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<BackupsListSection mode="view" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const meta = backupsPageMeta({
|
||||
label: "Backups",
|
||||
description: "Create and download database backups",
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import AdminHero from "@/src/components/general/admin-hero";
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
<AdminHero
|
||||
title="Restore"
|
||||
description="Replace all current data with the contents of a saved or uploaded backup. This cannot be undone."
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import Paper from "@moduletrace/twui/elements/Paper";
|
||||
import H3 from "@moduletrace/twui/layout/H3";
|
||||
import Section from "@moduletrace/twui/layout/Section";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import UploadRestoreForm from "../(partials)/upload-restore-form";
|
||||
|
||||
export default function UploadRestoreSection() {
|
||||
return (
|
||||
<Section className="pb-0!">
|
||||
<Paper>
|
||||
<Stack className="w-full items-stretch gap-4">
|
||||
<Stack className="gap-1!">
|
||||
<H3>Restore from a file</H3>
|
||||
<Span size="small" variant="faded">
|
||||
Upload a SQLite database file. It is saved to the
|
||||
backups list, then restored.
|
||||
</Span>
|
||||
</Stack>
|
||||
<UploadRestoreForm />
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import Hero from "./(sections)/hero";
|
||||
import UploadRestoreSection from "./(sections)/upload-restore-section";
|
||||
import BackupsListSection from "../(sections)/backups-list-section";
|
||||
import backupsPageMeta from "../(functions)/backups-page-meta";
|
||||
|
||||
export default function AdminBackupsRestorePage() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<UploadRestoreSection />
|
||||
<BackupsListSection mode="restore" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const meta = backupsPageMeta({
|
||||
label: "Restore Backup",
|
||||
description: "Restore the database from a backup",
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import grabSuperAdminUser from "@/src/functions/backend/backups/grab-super-admin-user";
|
||||
import grabDbBackups from "@/src/functions/backend/backups/grab-db-backups";
|
||||
import { SettingsDefaultPath } from "@/src/dict/settings-pages-dict";
|
||||
import type { PagePropsType } from "@/src/types";
|
||||
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
|
||||
import grabSettingsServerProps from "../../(functions)/grab-settings-server-props";
|
||||
|
||||
type Params = Parameters<BunextPageServerFn<PagePropsType>>[0];
|
||||
|
||||
/**
|
||||
* Shared server props for the `/admin/settings/backups` sub-pages: the
|
||||
* saved snapshots. Super admins only; everyone else lands on the general
|
||||
* settings.
|
||||
*/
|
||||
export default async function grabBackupsServerProps(params: Params) {
|
||||
const settings = await grabSettingsServerProps(params);
|
||||
|
||||
if (!settings.props) return settings;
|
||||
|
||||
if (!(await grabSuperAdminUser({ req: params.req }))) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: SettingsDefaultPath,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
...settings.props,
|
||||
backups: grabDbBackups(),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useContext } from "react";
|
||||
import Row from "@moduletrace/twui/layout/Row";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import SettingsGroup from "../../(partials)/settings-group";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import CreateBackupButton from "../(partials)/create-backup-button";
|
||||
import BackupsTable, { type BackupsTableMode } from "../(partials)/backups-table";
|
||||
|
||||
type Props = {
|
||||
mode: BackupsTableMode;
|
||||
};
|
||||
|
||||
/**
|
||||
* The database snapshots, newest first. On the restore page each row
|
||||
* restores; otherwise each one downloads, and a new backup can be made.
|
||||
*/
|
||||
export default function BackupsListSection({ mode }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const backups = pageProps?.backups || [];
|
||||
const is_restore = mode == "restore";
|
||||
|
||||
return (
|
||||
<SettingsGroup
|
||||
title={is_restore ? "Saved snapshots" : "Snapshots"}
|
||||
description={
|
||||
is_restore
|
||||
? "Restore any of these, or upload a file below. Create a fresh backup first if you might need the current data."
|
||||
: "Older snapshots are pruned automatically. Download one to keep it."
|
||||
}
|
||||
>
|
||||
<Row className="w-full items-center justify-between gap-2! flex-wrap">
|
||||
<Span size="small" variant="faded">
|
||||
{`${backups.length} backup${backups.length == 1 ? "" : "s"}`}
|
||||
</Span>
|
||||
{is_restore ? null : <CreateBackupButton />}
|
||||
</Row>
|
||||
{backups[0] ? (
|
||||
<BackupsTable backups={backups} mode={mode} />
|
||||
) : (
|
||||
<Span size="small" variant="faded">
|
||||
No backups yet.
|
||||
</Span>
|
||||
)}
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
import SettingsGroup from "../../(partials)/settings-group";
|
||||
import { SettingsBackupsRestorePath } from "@/src/dict/settings-pages-dict";
|
||||
|
||||
/**
|
||||
* The way into the restore page from the backups list.
|
||||
*/
|
||||
export default function RestoreBackupsSection() {
|
||||
return (
|
||||
<SettingsGroup
|
||||
title="Restore"
|
||||
description="Replace all current data with the contents of a saved or uploaded backup. This cannot be undone."
|
||||
>
|
||||
<Button
|
||||
title="Restore backup"
|
||||
href={SettingsBackupsRestorePath}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
size="small"
|
||||
beforeIcon={<ArchiveRestore size={20} />}
|
||||
className="self-start"
|
||||
>
|
||||
Restore a backup
|
||||
</Button>
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import BackupsListSection from "./(sections)/backups-list-section";
|
||||
import RestoreBackupsSection from "./(sections)/restore-backups-section";
|
||||
import SettingsShell from "../(partials)/settings-shell";
|
||||
import settingsPageMeta from "../(functions)/settings-page-meta";
|
||||
|
||||
export default function AdminSettingsBackupsPage() {
|
||||
return (
|
||||
<SettingsShell
|
||||
title="Backups"
|
||||
description="Snapshots of the database, and restoring one."
|
||||
wide
|
||||
>
|
||||
<BackupsListSection mode="view" />
|
||||
<RestoreBackupsSection />
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
|
||||
export const meta = settingsPageMeta({ label: "Backups" });
|
||||
@@ -0,0 +1,16 @@
|
||||
import SettingsGroup from "../../../(partials)/settings-group";
|
||||
import UploadRestoreForm from "../(partials)/upload-restore-form";
|
||||
|
||||
/**
|
||||
* Restoring from a file on this machine, for a backup kept elsewhere.
|
||||
*/
|
||||
export default function UploadRestoreSection() {
|
||||
return (
|
||||
<SettingsGroup
|
||||
title="From a file"
|
||||
description="Upload a SQLite database file. It is saved to the backups list, then restored."
|
||||
>
|
||||
<UploadRestoreForm />
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import UploadRestoreSection from "./(sections)/upload-restore-section";
|
||||
import BackupsListSection from "../(sections)/backups-list-section";
|
||||
import SettingsShell from "../../(partials)/settings-shell";
|
||||
import type { BunextPageModuleMetaFn } from "@moduletrace/bunext/types";
|
||||
import { SiteData } from "@/src/data/site-data";
|
||||
|
||||
export default function AdminSettingsRestoreBackupPage() {
|
||||
return (
|
||||
<SettingsShell
|
||||
title="Restore backup"
|
||||
description="Replace all current data with a saved or uploaded backup."
|
||||
breadcrumb_title="Restore backup"
|
||||
wide
|
||||
>
|
||||
<UploadRestoreSection />
|
||||
<BackupsListSection mode="restore" />
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
|
||||
export const meta: BunextPageModuleMetaFn = async () => {
|
||||
return {
|
||||
title: `Restore backup | ${SiteData["SiteName"]}`,
|
||||
description: `Restore a database backup on ${SiteData["SiteName"]}.`,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user