Template
Add explicit database import UI and harden SQL restore.
This commit is contained in:
+35
-20
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user