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,67 @@
|
||||
import { Archive, Upload } from "lucide-react";
|
||||
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 createDataBackup from "../(functions)/create-data-backup";
|
||||
import UploadRestoreModal from "../(partials)/upload-restore-modal";
|
||||
import { useState } from "react";
|
||||
|
||||
/**
|
||||
* The data archive list's two ways in: make a fresh archive, or restore
|
||||
* one from a file.
|
||||
*/
|
||||
export default function DataBackupListActions() {
|
||||
const { loading, setLoading, status, setStatus } = useStatus();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
const create_backup = async () => {
|
||||
setStatus(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const res = await createDataBackup();
|
||||
|
||||
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="Restore from an archive file"
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
beforeIcon={<Upload />}
|
||||
onClick={() => {
|
||||
setUploading(true);
|
||||
}}
|
||||
>
|
||||
Restore from file
|
||||
</Button>
|
||||
<Button
|
||||
title="Create Data Archive"
|
||||
beforeIcon={<Archive />}
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
create_backup();
|
||||
}}
|
||||
>
|
||||
Create archive
|
||||
</Button>
|
||||
<UploadRestoreModal
|
||||
open={uploading}
|
||||
onClose={() => setUploading(false)}
|
||||
is_data
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user