Files
holesail-browser/extension/dashboard/pages/backups.js
T
Raven Scott 5c4f0b4aba
CI / Build & Test (push) Failing after 28s
efactor(extension): modularize dashboard, background, and docs
Split monolithic dashboard.js (2753 lines) into 18 focused modules
under dashboard/{core,data,ui,pages}/ with refresh.js and events.js
as orchestrators. Extracted ~900-line inline <style> into dashboard.css
and moved dashboard.html to dashboard/dashboard.html.

Split background.js (609 lines) into background/{logs,state,proxy,
native-messaging,tab-lifecycle,message-router}.js with a thin entry
point using importScripts().

Deleted dead files: wrong-domain.js (duplicate of inline script).
Updated manifest.json web_accessible_resources for new paths.
Updated docs/ARCHITECTURE.md to reflect the new file structure.

No functionality changed. No build step introduced.
2026-02-28 23:02:52 -05:00

136 lines
5.3 KiB
JavaScript

// Depends on: core/utils.js ($, escapeHtml), core/utils.js (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';
}
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 = `<tr><td colspan="4"><div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:32px;height:32px;color:var(--text4)"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17,8 12,3 7,8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
<div class="empty-state-title">No backups yet</div>
<div class="empty-state-desc">Click "Take Backup" to create your first backup</div>
</div></td></tr>`;
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 `<tr>
<td><span class="mono" style="font-size:12px;">${name}</span></td>
<td>${created}</td>
<td>${size}</td>
<td>
<div style="display:flex;gap:6px;">
<button class="btn btn-secondary btn-sm" data-backup-restore="${escapeHtml(b.filename)}">Restore</button>
<button class="btn btn-danger btn-sm" data-backup-delete="${escapeHtml(b.filename)}">Delete</button>
</div>
</td>
</tr>`;
}).join('');
}
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 || []);
}
}
);
}
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 = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17,8 12,3 7,8"/><line x1="12" y1="3" x2="12" y2="15"/></svg> 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');
}
}
);
});
}