/** * Backups page — lists backup archives with size/date, and wires up * create/restore/delete operations via native host messages. * Depends on: core/utils.js ($, escapeHtml, timeAgo), * ui/toast.js (showToast), ui/modal.js (openModal, closeModal, showModalError) */ let pendingRestoreFilename = null; let pendingDeleteFilename = null; function formatBytes(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; } /** * Re-render the backups table with the provided list of backup entries. * @param {Array<{filename: string, size: number, createdAt: number}>} backups */ function updateBackupsTable(backups) { const tbody = $('backupsTable'); if (!tbody) return; const countEl = $('backupCount'); if (countEl) countEl.textContent = backups ? backups.length : 0; if (!backups || backups.length === 0) { tbody.innerHTML = `
No backups yet
Click "Take Backup" to create your first backup
`; return; } tbody.innerHTML = backups.map((b) => { const name = escapeHtml(b.filename); const created = b.createdAt ? timeAgo(b.createdAt) : '—'; const size = b.size ? formatBytes(b.size) : '—'; return ` ${name} ${created} ${size}
`; }).join(''); } /** * Fetch the current backup list from the native host and re-render the table. */ function refreshBackups() { chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'listBackups' } }, (response) => { if (chrome.runtime.lastError) return; if (response && response.ok) { updateBackupsTable(response.backups || []); } } ); } /** * Attach all event listeners for the Backups page. * Called once during dashboard initialisation. */ function setupBackupEvents() { $('btnTakeBackup')?.addEventListener('click', () => { const btn = $('btnTakeBackup'); if (btn) { btn.disabled = true; btn.textContent = 'Creating…'; } chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'createBackup' } }, (response) => { if (btn) { btn.disabled = false; btn.innerHTML = ` Take Backup`; } if (response && response.ok) { showToast('Backup created: ' + response.filename, 'success'); refreshBackups(); } else { showToast(response?.error || 'Backup failed', 'error'); } } ); }); $('backupsTable')?.addEventListener('click', (e) => { const restoreBtn = e.target.closest('[data-backup-restore]'); if (restoreBtn) { pendingRestoreFilename = restoreBtn.dataset.backupRestore; const nameEl = $('restoreBackupName'); if (nameEl) nameEl.textContent = pendingRestoreFilename; openModal('modal-restoreBackup'); return; } const deleteBtn = e.target.closest('[data-backup-delete]'); if (deleteBtn) { pendingDeleteFilename = deleteBtn.dataset.backupDelete; const nameEl = $('deleteBackupName'); if (nameEl) nameEl.textContent = pendingDeleteFilename; openModal('modal-deleteBackup'); } }); $('restoreBackupConfirm')?.addEventListener('click', () => { if (!pendingRestoreFilename) return; const btn = $('restoreBackupConfirm'); if (btn) btn.disabled = true; chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'restoreBackup', payload: { filename: pendingRestoreFilename } } }, (response) => { if (btn) btn.disabled = false; if (response && response.ok) { closeModal('modal-restoreBackup'); const certsNote = response.restoredCerts ? ' Certificates restored.' : ''; showToast('Backup restored.' + certsNote + ' Restart tunnels to apply changes.', 'success'); pendingRestoreFilename = null; refresh(); } else { showModalError('modal-restoreBackup', 'restoreBackupError', response?.error || 'Restore failed'); } } ); }); $('deleteBackupConfirm')?.addEventListener('click', () => { if (!pendingDeleteFilename) return; const btn = $('deleteBackupConfirm'); if (btn) btn.disabled = true; chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'deleteBackup', payload: { filename: pendingDeleteFilename } } }, (response) => { if (btn) btn.disabled = false; if (response && response.ok) { closeModal('modal-deleteBackup'); showToast('Backup deleted', 'success'); pendingDeleteFilename = null; refreshBackups(); } else { showModalError('modal-deleteBackup', 'deleteBackupError', response?.error || 'Delete failed'); } } ); }); }