Add explicit database import UI and harden SQL restore.

This commit is contained in:
orohi
2026-08-09 16:22:38 +03:00
parent bf1c6761fd
commit 58aad90dee
9 changed files with 107 additions and 45 deletions
+35 -20
View File
@@ -600,29 +600,34 @@
</div>
</div>
<!-- BLOCK: Simple Backup -->
<!-- BLOCK: Backup / Import -->
<div class="card" style="margin-top: var(--space-lg);">
<h3 class="card-title" style="margin-bottom: var(--space-lg);">📤 {{ _('backup_title') }}</h3>
<div style="display: flex; flex-direction: column; gap: var(--space-md);">
<div style="display: flex; gap: var(--space-sm); flex-wrap: wrap;">
<a href="/api/settings/backup/download" class="btn btn-secondary"
style="flex:1; min-width:200px; text-decoration:none; display:flex; align-items:center; justify-content:center; gap:var(--space-sm);">
<span>⬇️</span> {{ _('download_backup') }}
</a>
<a href="/api/settings/backup/download/json" class="btn btn-secondary"
style="flex:1; min-width:200px; text-decoration:none; display:flex; align-items:center; justify-content:center; gap:var(--space-sm);">
<span>📄</span> {{ _('download_backup_json') }}
</a>
<div>
<div class="form-label" style="margin-bottom: var(--space-sm);">{{ _('backup_export_label') }}</div>
<div style="display: flex; gap: var(--space-sm); flex-wrap: wrap;">
<a href="/api/settings/backup/download" class="btn btn-secondary"
style="flex:1; min-width:200px; text-decoration:none; display:flex; align-items:center; justify-content:center; gap:var(--space-sm);">
<span>⬇️</span> {{ _('download_backup') }}
</a>
<a href="/api/settings/backup/download/json" class="btn btn-secondary"
style="flex:1; min-width:200px; text-decoration:none; display:flex; align-items:center; justify-content:center; gap:var(--space-sm);">
<span>📄</span> {{ _('download_backup_json') }}
</a>
</div>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: var(--space-md);">
<div class="form-label" style="margin-bottom: var(--space-sm);">{{ _('backup_import_label') }}</div>
<div style="display: flex; flex-direction: column; gap: var(--space-sm);">
<input type="file" id="backupFile" accept=".sql,.json" style="display: none;"
onchange="handleRestore(event)">
<button type="button" class="btn btn-secondary"
<input type="file" id="backupFile" accept=".sql,.sql.gz,.json,application/sql,application/gzip,application/json"
style="display: none;" onchange="handleRestore(event)">
<button type="button" class="btn btn-primary"
onclick="document.getElementById('backupFile').click()" id="restoreBtn"
style="gap:var(--space-sm);">
<span>⬆️</span> {{ _('restore_backup') }}
<span>⬆️</span> {{ _('import_backup') }}
</button>
<div class="form-hint" id="backupFileName"></div>
</div>
</div>
</div>
@@ -1667,30 +1672,40 @@
const file = e.target.files[0];
if (!file) return;
const nameEl = document.getElementById('backupFileName');
if (nameEl) nameEl.textContent = file.name;
if (!confirm(_('restore_confirm'))) {
e.target.value = '';
if (nameEl) nameEl.textContent = '';
return;
}
const btn = document.getElementById('restoreBtn');
btn.disabled = true;
const originalText = btn.innerHTML;
btn.innerHTML = `<div class="spinner" style="width:14px; height:14px;"></div> ${_('loading')}`;
btn.innerHTML = `<div class="spinner" style="width:14px; height:14px;"></div> ${_('importing_backup')}`;
const formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/settings/backup/restore', {
const res = await fetch('/api/settings/backup/import', {
method: 'POST',
body: formData
body: formData,
credentials: 'same-origin',
});
const data = await res.json();
if (data.status === 'success') {
let data = {};
try {
data = await res.json();
} catch (_) {
data = {};
}
if (res.ok && data.status === 'success') {
showToast(_('restore_success'), 'success');
setTimeout(() => window.location.reload(), 2000);
} else {
showToast(data.error || _('invalid_backup_file'), 'error');
showToast(data.error || _('invalid_backup_file') + ` (HTTP ${res.status})`, 'error');
}
} catch (err) {
showToast(`${_('error')}: ` + err.message, 'error');