First Commit. Duplicate bunext-sqlite preset
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import formatBytes from "@/src/utils/format-bytes";
|
||||
import type { DbBackup } from "@/src/types";
|
||||
import dayjs from "dayjs";
|
||||
import type { BackupsTableMode } from "./backups-table";
|
||||
import DownloadBackupButton from "./download-backup-button";
|
||||
import RestoreBackupButton from "./restore-backup-button";
|
||||
|
||||
type Props = {
|
||||
backup: DbBackup;
|
||||
mode: BackupsTableMode;
|
||||
};
|
||||
|
||||
export default function BackupTableRow({ backup, mode }: Props) {
|
||||
return (
|
||||
<tr className="border-t border-slate-200 dark:border-white/10">
|
||||
<td className="px-4 py-2.5 max-w-0 w-full">
|
||||
<Span
|
||||
size="small"
|
||||
className="block truncate font-mono"
|
||||
title={backup.name}
|
||||
>
|
||||
{backup.name}
|
||||
</Span>
|
||||
</td>
|
||||
<td className="px-4 py-2.5 whitespace-nowrap">
|
||||
<Span size="small">
|
||||
{dayjs(backup.created_at).format("MMM D, YYYY HH:mm:ss")}
|
||||
</Span>
|
||||
</td>
|
||||
<td className="hidden md:table-cell px-4 py-2.5 whitespace-nowrap">
|
||||
<Span size="small" variant="faded">
|
||||
{formatBytes({ bytes: backup.size })}
|
||||
</Span>
|
||||
</td>
|
||||
<td className="px-4 py-2 text-right whitespace-nowrap">
|
||||
{mode == "restore" ? (
|
||||
<RestoreBackupButton backup={backup} />
|
||||
) : (
|
||||
<DownloadBackupButton backup={backup} />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import type { BackupsTableColumn } from "../(data)/backups-table-columns";
|
||||
|
||||
export default function BackupsTableHeadCell({
|
||||
title,
|
||||
className,
|
||||
hide_title,
|
||||
}: BackupsTableColumn) {
|
||||
return (
|
||||
<th className={twMerge("px-4 py-2.5 text-left font-medium", className)}>
|
||||
<Span
|
||||
size="smaller"
|
||||
variant="faded"
|
||||
className={twMerge(hide_title && "sr-only")}
|
||||
>
|
||||
{title}
|
||||
</Span>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Paper from "@moduletrace/twui/elements/Paper";
|
||||
import type { DbBackup } from "@/src/types";
|
||||
import { BackupsTableColumns } from "../(data)/backups-table-columns";
|
||||
import BackupTableRow from "./backup-table-row";
|
||||
import BackupsTableHeadCell from "./backups-table-head-cell";
|
||||
|
||||
export type BackupsTableMode = "view" | "restore";
|
||||
|
||||
type Props = {
|
||||
backups: DbBackup[];
|
||||
mode: BackupsTableMode;
|
||||
};
|
||||
|
||||
export default function BackupsTable({ backups, mode }: Props) {
|
||||
return (
|
||||
<Paper className="p-0 gap-0 overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-slate-50 dark:bg-white/5">
|
||||
<tr>
|
||||
{BackupsTableColumns.map((column) => (
|
||||
<BackupsTableHeadCell {...column} key={column.title} />
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{backups.map((backup) => (
|
||||
<BackupTableRow
|
||||
backup={backup}
|
||||
mode={mode}
|
||||
key={backup.name}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import Tag from "@moduletrace/twui/elements/Tag";
|
||||
import Row from "@moduletrace/twui/layout/Row";
|
||||
import useStatus from "@moduletrace/twui/hooks/useStatus";
|
||||
import createBackup from "../(functions)/create-backup";
|
||||
|
||||
export default function CreateBackupButton() {
|
||||
const { loading, setLoading, status, setStatus } = useStatus();
|
||||
|
||||
const create_backup = async () => {
|
||||
setStatus(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const res = await createBackup();
|
||||
|
||||
if (res.success) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (res.msg) {
|
||||
setStatus({ error: true, msg: res.msg });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Row className="gap-2! flex-nowrap">
|
||||
{status?.error && <Tag color="error">{status.msg}</Tag>}
|
||||
<Button
|
||||
title="Create Backup"
|
||||
beforeIcon="DatabaseBackup"
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
create_backup();
|
||||
}}
|
||||
>
|
||||
Create backup
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import type { DbBackup } from "@/src/types";
|
||||
import { Download } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
backup: DbBackup;
|
||||
};
|
||||
|
||||
export default function DownloadBackupButton({ backup }: Props) {
|
||||
return (
|
||||
<Button
|
||||
title="Download Backup"
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
size="small"
|
||||
beforeIcon={<Download size={20} />}
|
||||
href={`/api/admin/backups/download?backup_name=${encodeURIComponent(backup.name)}`}
|
||||
linkProps={{
|
||||
download: backup.name,
|
||||
onClick: (e) => {
|
||||
if (!window.confirm(`Download backup ${backup.name}?`)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import Tag from "@moduletrace/twui/elements/Tag";
|
||||
import Row from "@moduletrace/twui/layout/Row";
|
||||
import useStatus from "@moduletrace/twui/hooks/useStatus";
|
||||
import type { DbBackup } from "@/src/types";
|
||||
import restoreBackup from "../(functions)/restore-backup";
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
backup: DbBackup;
|
||||
};
|
||||
|
||||
export default function RestoreBackupButton({ backup }: Props) {
|
||||
const { loading, setLoading, status, setStatus } = useStatus();
|
||||
|
||||
const restore_backup = async () => {
|
||||
setStatus(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const res = await restoreBackup({ backup });
|
||||
|
||||
if (res.success) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (res.msg) {
|
||||
setStatus({ error: true, msg: res.msg });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Row className="gap-2! flex-nowrap justify-end">
|
||||
{status?.error && <Tag color="error">{status.msg}</Tag>}
|
||||
<Button
|
||||
title="Restore Backup"
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
size="small"
|
||||
beforeIcon={<ArchiveRestore size={20} />}
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
restore_backup();
|
||||
}}
|
||||
>
|
||||
Restore
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user