Implement WebDAV backup feature: add configuration options, API endpoints, and UI for managing database backups
This commit is contained in:
@@ -7,6 +7,8 @@ const elements = Object.fromEntries([
|
||||
'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'audio-level', 'audio-level-fill', 'capture-message', 'audio-file-input',
|
||||
'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty',
|
||||
'network-state', 'storage-status', 'voice-mode', 'write-mode', 'voice-panel', 'write-panel', 'written-note', 'draft-status', 'save-written-note',
|
||||
'settings-button', 'settings-dialog', 'backup-form', 'backup-fields', 'backup-url', 'backup-username', 'backup-password',
|
||||
'clear-backup-password', 'close-settings', 'test-backup', 'backup-status', 'retry-backup',
|
||||
].map((id) => [id, document.getElementById(id)]));
|
||||
|
||||
let databasePromise;
|
||||
@@ -154,6 +156,94 @@ function updateNetwork() {
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshBackupStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/backup');
|
||||
if (!response.ok) throw new Error('Backup status unavailable');
|
||||
const backup = await response.json();
|
||||
elements['backup-fields'].disabled = backup.managed_by_environment;
|
||||
const managedNotice = backup.managed_by_environment ? 'Settings are managed by environment variables. ' : '';
|
||||
if (backup.last_error) {
|
||||
elements['backup-status'].textContent = `${managedNotice}Backup issue: ${backup.last_error}`;
|
||||
} else if (backup.pending && backup.enabled) {
|
||||
elements['backup-status'].textContent = `${managedNotice}A database snapshot is waiting to upload.`;
|
||||
} else if (backup.pending) {
|
||||
elements['backup-status'].textContent = 'Backup is waiting; configure a WebDAV folder to upload it.';
|
||||
} else if (backup.managed_by_environment) {
|
||||
elements['backup-status'].textContent = `${managedNotice}Latest database snapshot is current.`;
|
||||
} else if (backup.enabled) {
|
||||
elements['backup-status'].textContent = 'WebDAV backup is enabled. Latest database snapshot is current.';
|
||||
} else {
|
||||
elements['backup-status'].textContent = 'WebDAV backup is not configured.';
|
||||
}
|
||||
elements['retry-backup'].hidden = !backup.enabled || !backup.pending;
|
||||
if (!backup.managed_by_environment) {
|
||||
elements['backup-url'].value = backup.url;
|
||||
elements['backup-username'].value = backup.username;
|
||||
}
|
||||
return backup;
|
||||
} catch {
|
||||
elements['backup-status'].textContent = 'Could not load backup status.';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveBackupConfiguration() {
|
||||
const response = await fetch('/api/backup/config', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: elements['backup-url'].value.trim(),
|
||||
username: elements['backup-username'].value.trim(),
|
||||
password: elements['backup-password'].value,
|
||||
clear_password: String(elements['clear-backup-password'].checked),
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || 'Could not save backup settings.');
|
||||
elements['backup-password'].value = '';
|
||||
elements['clear-backup-password'].checked = false;
|
||||
await refreshBackupStatus();
|
||||
}
|
||||
|
||||
elements['settings-button'].addEventListener('click', () => {
|
||||
elements['settings-dialog'].showModal();
|
||||
refreshBackupStatus();
|
||||
});
|
||||
elements['close-settings'].addEventListener('click', () => elements['settings-dialog'].close());
|
||||
elements['backup-form'].addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
elements['backup-status'].textContent = 'Saving settings…';
|
||||
try {
|
||||
await saveBackupConfiguration();
|
||||
} catch (error) {
|
||||
elements['backup-status'].textContent = error.message;
|
||||
}
|
||||
});
|
||||
elements['test-backup'].addEventListener('click', async () => {
|
||||
elements['backup-status'].textContent = 'Testing WebDAV…';
|
||||
try {
|
||||
await saveBackupConfiguration();
|
||||
const response = await fetch('/api/backup/test', { method: 'POST' });
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || 'WebDAV test failed.');
|
||||
elements['backup-status'].textContent = result.message;
|
||||
} catch (error) {
|
||||
elements['backup-status'].textContent = error.message;
|
||||
}
|
||||
});
|
||||
elements['retry-backup'].addEventListener('click', async () => {
|
||||
elements['backup-status'].textContent = 'Queuing backup retry…';
|
||||
try {
|
||||
const response = await fetch('/api/backup/retry', { method: 'POST' });
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || 'Could not retry backup.');
|
||||
await refreshBackupStatus();
|
||||
} catch (error) {
|
||||
elements['backup-status'].textContent = error.message;
|
||||
}
|
||||
});
|
||||
|
||||
function formatDuration(milliseconds) {
|
||||
const seconds = Math.floor(milliseconds / 1000);
|
||||
return `${String(Math.floor(seconds / 60)).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
|
||||
Reference in New Issue
Block a user