CI / Build & Test (push) Failing after 28s
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.
136 lines
5.3 KiB
JavaScript
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');
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|