Add server user/protocol migrate export-import for connect_domain moves.

This commit is contained in:
orohi
2026-08-09 17:31:08 +03:00
parent 58aad90dee
commit f9b9857460
8 changed files with 768 additions and 2 deletions
+110
View File
@@ -647,6 +647,40 @@
</div>
</div>
<!-- ===== Migrate users / protocol state ===== -->
<div class="modal-backdrop" id="migrateModal">
<div class="modal" style="max-width: 560px;">
<div class="modal-header">
<div>
<h2 class="modal-title">{{ _('migrate_title') }}</h2>
<div class="text-muted text-sm">{{ _('migrate_desc') }}</div>
</div>
<button class="modal-close" onclick="closeModal('migrateModal')">×</button>
</div>
<div class="form-hint" style="margin-bottom: var(--space-md);">{{ _('migrate_hint') }}</div>
<div class="flex" style="flex-direction: column; gap: var(--space-md);">
<div>
<div class="form-label">{{ _('migrate_export_label') }}</div>
<button type="button" class="btn btn-secondary" id="migrateExportBtn" onclick="exportServerMigrate()"
style="width:100%; justify-content:center; gap:var(--space-sm);">
⬇️ {{ _('migrate_export_btn') }}
</button>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: var(--space-md);">
<div class="form-label">{{ _('migrate_import_label') }}</div>
<input type="file" id="migrateFile" accept=".zip,application/zip" style="display:none;"
onchange="importServerMigrate(event)">
<button type="button" class="btn btn-primary" id="migrateImportBtn"
onclick="document.getElementById('migrateFile').click()"
style="width:100%; justify-content:center; gap:var(--space-sm);">
⬆️ {{ _('migrate_import_btn') }}
</button>
<div class="form-hint" style="margin-top: var(--space-xs);">{{ _('migrate_import_hint') }}</div>
</div>
</div>
</div>
</div>
<!-- ===== Install Protocol Modal ===== -->
<div class="modal-backdrop" id="installModal">
<div class="modal">
@@ -1378,6 +1412,9 @@
<button class="btn btn-secondary" onclick="closeModal('managementModal'); checkServer();" style="justify-content: flex-start; padding: 12px 20px;">
<span style="font-size: 1.2rem; margin-right: 8px; display:inline-flex;">${uiIcon('refresh')}</span> ${_('check_server_services')}
</button>
<button class="btn btn-primary" onclick="closeModal('managementModal'); openModal('migrateModal');" style="justify-content: flex-start; padding: 12px 20px;">
<span style="font-size: 1.2rem; margin-right: 8px;">📦</span> ${_('migrate_title')}
</button>
<button class="btn btn-warning" onclick="closeModal('managementModal'); rebootServer();" style="justify-content: flex-start; padding: 12px 20px;">
<span style="font-size: 1.2rem; margin-right: 8px; display:inline-flex;">${uiIcon('refresh')}</span> ${_('reboot_server')}
</button>
@@ -1391,6 +1428,79 @@
`;
}
async function exportServerMigrate() {
const btn = document.getElementById('migrateExportBtn');
if (btn) btn.disabled = true;
showToast(_('migrate_exporting'), 'info');
try {
const res = await fetch(`/api/servers/${SERVER_ID}/migrate/export?include_protocols=true`, {
credentials: 'same-origin',
});
if (!res.ok) {
let err = _('error');
try {
const data = await res.json();
err = data.error || err;
} catch (_) {}
throw new Error(err);
}
const blob = await res.blob();
const cd = res.headers.get('Content-Disposition') || '';
const match = /filename="?([^"]+)"?/i.exec(cd);
const filename = match ? match[1] : `amnezia-migrate-${SERVER_ID}.zip`;
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
showToast(_('migrate_export_done'), 'success');
} catch (err) {
showToast(`${_('error')}: ${err.message}`, 'error');
} finally {
if (btn) btn.disabled = false;
}
}
async function importServerMigrate(e) {
const file = e.target.files && e.target.files[0];
e.target.value = '';
if (!file) return;
if (!confirm(_('migrate_import_confirm'))) return;
const btn = document.getElementById('migrateImportBtn');
if (btn) btn.disabled = true;
showToast(_('migrate_importing'), 'info');
try {
const form = new FormData();
form.append('file', file);
const res = await fetch(`/api/servers/${SERVER_ID}/migrate/import?restore_protocols=true`, {
method: 'POST',
body: form,
credentials: 'same-origin',
});
const data = await res.json().catch(() => ({}));
if (!res.ok || data.status !== 'success') {
throw new Error(data.error || `HTTP ${res.status}`);
}
const added = (data.connections && data.connections.added) || 0;
const protos = (data.protocols_restored || []).join(', ') || '—';
showToast(
`${_('migrate_import_done')} (+${added} ${_('connections') || 'conn'}; ${protos})`,
'success'
);
if (data.connect_domain) {
showToast(`${_('migrate_dns_hint')}: ${data.connect_domain}`, 'info');
}
setTimeout(() => checkServer(), 1500);
} catch (err) {
showToast(`${_('error')}: ${err.message}`, 'error');
} finally {
if (btn) btn.disabled = false;
}
}
function openManagementModal() {
renderManagementModal();
openModal('managementModal');