Archive the whole data volume in Backups, not just the database
Settings > Backups gains a whole-data archive beside the database snapshots, and both lists now fully work from the page. - Data archives: `write-data-backup-archive.ts` tars all of DATA_DIR into BACKUP_ARCHIVE_DIR as cobble-data-<epoch-ms>.tar.gz, every entry under one root folder named after the archive, writing a .part name and renaming on success so a cut-off run is never listed. The newest AppData.DataBackupsKept are kept; db/.backups is left as it is. - Restore: `restore-data-backup.ts` extracts to a temp dir, requires one root folder holding a db folder, takes a safety archive first, then replaces DATA_DIR (the database snapshots included) and `restart-app.ts` restarts the process through the docker socket, since the open SQLite handle points at the replaced file. - Upload and restore: a modal over each list (database file or data archive), replacing the old /admin/settings/backups/restore page. - Delete for both kinds, icon-only row actions shared through RowIconButton, and restore on every row. - One backup or restore of either kind at a time (with-backup-run-lock). - BACKUP_ARCHIVE_DIR is a start-up path (ProcessOnlyEnvKeys); the installer mounts $COBBLE_HOME/backups-archives at /backups-archives and recreates a container that lacks it. The image installs tar and gzip.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import FileUpload from "@moduletrace/twui/form/FileUpload";
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import Tag from "@moduletrace/twui/elements/Tag";
|
||||
import Row from "@moduletrace/twui/layout/Row";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import useStatus from "@moduletrace/twui/hooks/useStatus";
|
||||
import { useState } from "react";
|
||||
import { Upload } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* The file picker's hint, e.g. "Click or drop a SQLite database file".
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* What the picker accepts, e.g. `.db,.sqlite,.sqlite3`.
|
||||
*/
|
||||
accept?: string;
|
||||
/**
|
||||
* The button's label.
|
||||
*/
|
||||
action_label: string;
|
||||
/**
|
||||
* Sends the file and reports the outcome.
|
||||
*/
|
||||
upload: (params: { file: File }) => Promise<{ success: boolean; msg?: string }>;
|
||||
/**
|
||||
* Reload the page once it worked (the app restarts after an archive
|
||||
* restore, so the caller waits for it first).
|
||||
*/
|
||||
onDone: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Picks one file and sends it for a restore: shared by the database upload
|
||||
* and the data archive upload, which differ only in what they accept.
|
||||
*/
|
||||
export default function UploadRestoreFields({
|
||||
label,
|
||||
accept,
|
||||
action_label,
|
||||
upload,
|
||||
onDone,
|
||||
}: Props) {
|
||||
const { loading, setLoading, status, setStatus } = useStatus();
|
||||
const [file, setFile] = useState<File>();
|
||||
|
||||
const send = async () => {
|
||||
if (!file) return;
|
||||
|
||||
setStatus(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const res = await upload({ file });
|
||||
|
||||
if (res.success) {
|
||||
onDone();
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (res.msg) {
|
||||
setStatus({ error: true, msg: res.msg });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack className="w-full items-stretch gap-4">
|
||||
<FileUpload
|
||||
label={label}
|
||||
className="h-[220px]"
|
||||
fileInputProps={accept ? { accept } : undefined}
|
||||
changeHandler={(file_data) => {
|
||||
setStatus(undefined);
|
||||
setFile(file_data?.file);
|
||||
}}
|
||||
/>
|
||||
<Row className="w-full justify-end gap-2! flex-nowrap">
|
||||
{status?.error && <Tag color="error">{status.msg}</Tag>}
|
||||
<Button
|
||||
title={action_label}
|
||||
color="warning"
|
||||
beforeIcon={<Upload />}
|
||||
loading={loading}
|
||||
disabled={!file || loading}
|
||||
onClick={() => {
|
||||
send();
|
||||
}}
|
||||
>
|
||||
{action_label}
|
||||
</Button>
|
||||
</Row>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user