Failed to load schedule: ' + escapeHtml(e.message) + '
';
}
}
// Render the provider-specific form fields and load saved credentials (masked)
async function renderDestinationForm(type) {
var formEl = document.getElementById('backup-dest-form');
if (!formEl) return;
if (type === 'local') {
formEl.innerHTML = '
Backups are stored on the host filesystem. No additional configuration required.
';
return;
}
var html = '';
if (type === 'dropbox') {
html += '';
html += '';
html += '';
html += '';
html += '
Generate a token at Dropbox App Console with files.content.write + files.content.read scopes.
';
} else if (type === 'webdav') {
html += '';
html += '';
html += '
';
html += '
';
html += '
';
html += '
';
html += '
';
html += '
';
html += '';
html += '';
} else if (type === 'sftp') {
html += '
';
html += '
';
html += '
';
html += '
';
html += '
';
html += '
';
html += '';
html += '';
html += '';
html += '';
html += '
';
html += '
';
html += '
';
html += '
';
html += '';
html += '';
}
html += '
';
html += ' ';
html += ' ';
html += ' ';
html += '
';
formEl.innerHTML = html;
// SFTP auth type toggle
if (type === 'sftp') {
var authSel = document.getElementById('dest-sftp-authtype');
var pwRow = document.getElementById('dest-sftp-password-row');
var keyRow = document.getElementById('dest-sftp-key-row');
authSel?.addEventListener('change', function() {
if (authSel.value === 'key') { pwRow.style.display = 'none'; keyRow.style.display = ''; }
else { pwRow.style.display = ''; keyRow.style.display = 'none'; }
});
}
document.getElementById('dest-save-creds')?.addEventListener('click', function() { saveCredentials(type); });
document.getElementById('dest-test-conn')?.addEventListener('click', function() { testDestination(type); });
document.getElementById('dest-clear-creds')?.addEventListener('click', function() { clearCredentials(type); });
// Pull existing (masked) credentials
await loadCredentials(type);
}
function destResult(msg, ok) {
var el = document.getElementById('backup-dest-result');
if (!el) return;
el.innerHTML = msg;
el.style.display = 'block';
el.style.background = ok ? 'color-mix(in srgb, var(--ok-fg) 15%, transparent)' : 'color-mix(in srgb, var(--bad-fg) 15%, transparent)';
el.style.border = ok ? '1px solid var(--ok-fg)' : '1px solid var(--bad-fg)';
}
async function loadCredentials(provider) {
try {
var res = await fetch('/api/v1/backups/credentials/' + provider);
var data = await res.json();
if (!data.success || !data.credentials) return;
var c = data.credentials;
if (provider === 'dropbox') {
var t = document.getElementById('dest-dropbox-token'); if (t && c.token) t.value = c.token;
} else if (provider === 'webdav') {
var u = document.getElementById('dest-webdav-url'); if (u && c.url) u.value = c.url;
var n = document.getElementById('dest-webdav-username'); if (n && c.username) n.value = c.username;
var p = document.getElementById('dest-webdav-password'); if (p && c.password) p.value = c.password;
} else if (provider === 'sftp') {
var h = document.getElementById('dest-sftp-host'); if (h && c.host) h.value = c.host;
var po = document.getElementById('dest-sftp-port'); if (po && c.port) po.value = c.port;
var un = document.getElementById('dest-sftp-username'); if (un && c.username) un.value = c.username;
var pw = document.getElementById('dest-sftp-password'); if (pw && c.password) pw.value = c.password;
var pk = document.getElementById('dest-sftp-privatekey'); if (pk && c.privateKey) pk.value = c.privateKey;
if (c.privateKey) {
var sel = document.getElementById('dest-sftp-authtype');
if (sel) { sel.value = 'key'; sel.dispatchEvent(new Event('change')); }
}
}
} catch (e) { /* no creds yet — silent */ }
}
function collectCredentials(provider) {
if (provider === 'dropbox') {
return { token: document.getElementById('dest-dropbox-token')?.value };
}
if (provider === 'webdav') {
return {
url: document.getElementById('dest-webdav-url')?.value,
username: document.getElementById('dest-webdav-username')?.value,
password: document.getElementById('dest-webdav-password')?.value
};
}
if (provider === 'sftp') {
var auth = document.getElementById('dest-sftp-authtype')?.value;
var creds = {
host: document.getElementById('dest-sftp-host')?.value,
port: parseInt(document.getElementById('dest-sftp-port')?.value) || 22,
username: document.getElementById('dest-sftp-username')?.value
};
if (auth === 'key') creds.privateKey = document.getElementById('dest-sftp-privatekey')?.value;
else creds.password = document.getElementById('dest-sftp-password')?.value;
return creds;
}
return {};
}
async function saveCredentials(provider) {
try {
var creds = collectCredentials(provider);
var res = await secureFetch('/api/v1/backups/credentials/' + provider, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(creds)
});
var data = await res.json();
destResult(data.success ? '✅ Credentials saved' : '⚠️ ' + escapeHtml(data.error || 'Failed'), data.success);
} catch (e) {
destResult('❌ ' + escapeHtml(e.message), false);
}
}
async function clearCredentials(provider) {
if (!confirm('Delete saved ' + provider + ' credentials?')) return;
try {
var res = await secureFetch('/api/v1/backups/credentials/' + provider, { method: 'DELETE' });
var data = await res.json();
if (data.success) {
destResult('✅ Credentials cleared', true);
renderDestinationForm(provider);
} else {
destResult('⚠️ ' + escapeHtml(data.error || 'Failed'), false);
}
} catch (e) { destResult('❌ ' + escapeHtml(e.message), false); }
}
function buildDestination(type) {
var dest = { type: type };
if (type === 'local') return dest;
if (type === 'dropbox') dest.path = document.getElementById('dest-dropbox-path')?.value || '/dashcaddy-backups';
else if (type === 'webdav') dest.path = document.getElementById('dest-webdav-path')?.value || '/dashcaddy-backups';
else if (type === 'sftp') dest.path = document.getElementById('dest-sftp-path')?.value || '/dashcaddy-backups';
return dest;
}
async function testDestination(type) {
destResult(' Testing connection...', true);
try {
var dest = buildDestination(type);
var res = await secureFetch('/api/v1/backups/test-destination', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dest)
});
var data = await res.json();
if (data.success) {
var ms = data.elapsedMs ? ' (' + data.elapsedMs + 'ms)' : '';
destResult('✅ Connection OK' + ms + ' — write/read/delete probe succeeded', true);
} else {
destResult('❌ ' + escapeHtml(data.error || 'Connection failed'), false);
}
} catch (e) { destResult('❌ ' + escapeHtml(e.message), false); }
}
async function saveSchedule() {
var schedule = document.getElementById('backup-schedule-select')?.value;
var retention = parseInt(document.getElementById('backup-retention-select')?.value) || 5;
var encrypt = document.getElementById('backup-encrypt-toggle')?.checked ?? true;
var destType = document.getElementById('backup-dest-type')?.value || 'local';
var resultEl = document.getElementById('backup-schedule-result');
try {
var res = await secureFetch('/api/v1/backups/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
backups: {
auto: {
enabled: schedule !== 'disabled',
schedule: schedule === 'disabled' ? 'daily' : schedule,
include: ['all'],
encrypt: encrypt,
verify: true,
retention: { keep: retention },
destinations: [buildDestination(destType)]
}
}
})
});
var data = await res.json();
if (resultEl) {
resultEl.innerHTML = data.success ? '✅ Schedule saved' : '⚠️ ' + escapeHtml(data.error);
resultEl.style.display = 'block';
resultEl.style.background = data.success ? 'color-mix(in srgb, var(--ok-fg) 15%, transparent)' : 'color-mix(in srgb, var(--bad-fg) 15%, transparent)';
resultEl.style.border = data.success ? '1px solid var(--ok-fg)' : '1px solid var(--bad-fg)';
setTimeout(function() { if (resultEl) resultEl.style.display = 'none'; }, 3000);
}
} catch (e) {
if (resultEl) {
resultEl.innerHTML = '❌ ' + escapeHtml(e.message);
resultEl.style.display = 'block';
resultEl.style.background = 'color-mix(in srgb, var(--bad-fg) 15%, transparent)';
resultEl.style.border = '1px solid var(--bad-fg)';
}
}
}
async function runBackupNow() {
var btn = document.getElementById('backup-run-now');
var resultEl = document.getElementById('backup-schedule-result');
var destType = document.getElementById('backup-dest-type')?.value || 'local';
if (btn) { btn.disabled = true; btn.innerHTML = ' Running...'; }
try {
var res = await secureFetch('/api/v1/backups/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ include: ['all'], destinations: [buildDestination(destType)] })
});
var data = await res.json();
if (resultEl) {
if (data.success) {
var sizeMB = data.backup?.size ? (data.backup.size / 1024 / 1024).toFixed(2) : '?';
resultEl.innerHTML = '✅ Backup complete (' + sizeMB + ' MB)';
resultEl.style.background = 'color-mix(in srgb, var(--ok-fg) 15%, transparent)';
resultEl.style.border = '1px solid var(--ok-fg)';
} else {
resultEl.innerHTML = '⚠️ ' + escapeHtml(data.error);
resultEl.style.background = 'color-mix(in srgb, var(--bad-fg) 15%, transparent)';
resultEl.style.border = '1px solid var(--bad-fg)';
}
resultEl.style.display = 'block';
}
loadBackupHistory();
} catch (e) {
if (resultEl) {
resultEl.innerHTML = '❌ ' + escapeHtml(e.message);
resultEl.style.display = 'block';
resultEl.style.background = 'color-mix(in srgb, var(--bad-fg) 15%, transparent)';
resultEl.style.border = '1px solid var(--bad-fg)';
}
}
if (btn) { btn.disabled = false; btn.innerHTML = '▶️ Run Backup Now'; }
}
// === Backup History Tab ===
async function loadBackupHistory() {
if (!historyContainer) return;
historyContainer.innerHTML = '
Loading...
';
try {
var res = await fetch('/api/v1/backups/history?limit=50');
var data = await res.json();
if (!data.success || !data.history?.length) {
historyContainer.innerHTML = '
📋 No backup history yet
';
return;
}
var html = '
';
for (var i = 0; i < data.history.length; i++) {
var bk = data.history[i];
var sizeMB = bk.size ? (bk.size / 1024 / 1024).toFixed(2) : '?';
html += '
';
html += '
';
html += ' ' + escapeHtml(bk.name || 'backup') + '';
html += '
';
html += ' ' + escapeHtml(bk.status) + '';
if (bk.status === 'success') html += ' ';
html += '
';
html += '
';
html += '
';
html += ' ' + new Date(bk.timestamp).toLocaleString() + ' | ' + sizeMB + ' MB | ' + (bk.duration ? (bk.duration / 1000).toFixed(1) + 's' : '--');
if (bk.encrypted) html += ' | 🔒';
html += '