[#2599] added option to upload a backup file from the Admin UI

This commit is contained in:
Gani Georgiev
2023-08-28 20:06:48 +03:00
parent 2a6b891a9b
commit f7f8f09336
41 changed files with 621 additions and 182 deletions
@@ -6,6 +6,7 @@
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import Field from "@/components/base/Field.svelte";
import CopyIcon from "@/components/base/CopyIcon.svelte";
import { onDestroy } from "svelte";
const formId = "backup_restore_" + CommonHelper.randomString(5);
@@ -13,6 +14,7 @@
let name = "";
let nameConfirm = "";
let isSubmitting = false;
let reloadTimeoutId = null;
$: canSubmit = nameConfirm != "" && name == nameConfirm;
@@ -33,22 +35,30 @@
return;
}
clearTimeout(reloadTimeoutId);
isSubmitting = true;
try {
await ApiClient.backups.restore(name);
// slight delay just in case the application is still restarting
setTimeout(() => {
// optimistic restore page reload
reloadTimeoutId = setTimeout(() => {
window.location.reload();
}, 1000);
}, 2000);
} catch (err) {
clearTimeout(reloadTimeoutId);
if (!err?.isAbort) {
isSubmitting = false;
addErrorToast(err.response?.message || err.message);
}
}
}
onDestroy(() => {
clearTimeout(reloadTimeoutId);
});
</script>
<OverlayPanel
@@ -70,14 +80,16 @@
<i class="ri-alert-line" />
</div>
<div class="content">
<p>Please proceed with caution.</p>
<p>
The restore operation will replace your existing <code>pb_data</code> with the one from the backup
and will restart the application process!
</p>
<p class="txt-bold">
Please proceed with caution.
<br />
Backup restore is still experimental and currently works only on UNIX based systems.
</p>
<p>
The restore operation will attempt to replace your existing <code>pb_data</code> with the one from
the backup and will restart the application process.
</p>
<p>Nothing will happen if the backup file is invalid or incompatible.</p>
</div>
</div>
@@ -0,0 +1,61 @@
<script>
import { createEventDispatcher, onDestroy } from "svelte";
import ApiClient from "@/utils/ApiClient";
import { addSuccessToast, addErrorToast } from "@/stores/toasts";
import tooltip from "@/actions/tooltip";
const dispatch = createEventDispatcher();
const backupRequestKey = "upload_backup";
let classes = "";
export { classes as class };
let fileInput;
let isUploading = false;
async function upload(e) {
if (isUploading || !e?.target?.files?.length) {
return;
}
isUploading = true;
const data = new FormData();
data.set("file", e.target.files[0]);
try {
await ApiClient.backups.upload(data, { requestKey: backupRequestKey });
isUploading = false;
dispatch("success");
addSuccessToast("Successfully uploaded a new backup.");
} catch (err) {
if (!err.isAbort) {
isUploading = false;
if (err.response?.data?.file?.message) {
addErrorToast(err.response.data.file.message);
} else {
ApiClient.error(err);
}
}
}
}
onDestroy(() => {
ApiClient.cancelRequest(backupRequestKey);
});
</script>
<button
type="button"
class="btn btn-circle btn-transparent {classes}"
class:btn-loading={isUploading}
class:btn-disabled={isUploading}
aria-label="Upload backup"
use:tooltip={"Upload backup"}
on:click={() => fileInput?.click()}
>
<i class="ri-upload-cloud-line" />
</button>
<input bind:this={fileInput} type="file" accept="application/zip" class="hidden" on:change={upload} />
@@ -13,6 +13,7 @@
import SettingsSidebar from "@/components/settings/SettingsSidebar.svelte";
import BackupsList from "@/components/settings/BackupsList.svelte";
import S3Fields from "@/components/settings/S3Fields.svelte";
import BackupUploadBtn from "@/components/settings/BackupUploadBtn.svelte";
$pageTitle = "Backups";
@@ -89,7 +90,7 @@
}
async function refreshList() {
await backupsListComponent?.loadBackups();
return backupsListComponent?.loadBackups();
}
</script>
@@ -105,13 +106,10 @@
<div class="wrapper">
<div class="panel" autocomplete="off" on:submit|preventDefault={save}>
<div class="flex m-b-sm flex-gap-5">
<div class="flex m-b-sm flex-gap-10">
<span class="txt-xl">Backup and restore your PocketBase data</span>
<RefreshButton
class="btn-sm"
tooltip={"Reload backups list"}
on:refresh={() => refreshList()}
/>
<RefreshButton class="btn-sm" tooltip={"Refresh"} on:refresh={refreshList} />
<BackupUploadBtn class="btn-sm" on:success={refreshList} />
</div>
<BackupsList bind:this={backupsListComponent} />