Files
holesail-browser/extension/dashboard/pages/servers.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

180 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Depends on: core/utils.js ($, escapeHtml, truncate), ui/toast.js (showToast, copyToClipboard),
// ui/modal.js (openModal, closeModal, showModalError)
function updateSwarmsTable(state) {
const tbody = $('swarmsTable');
if (!tbody) return;
const servers = state.servers || [];
if (servers.length === 0) {
tbody.innerHTML = `
<tr><td colspan="5">
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="6" height="6" rx="1"/><rect x="16" y="3" width="6" height="6" rx="1"/><rect x="9" y="15" width="6" height="6" rx="1"/><line x1="5" y1="9" x2="12" y2="15"/><line x1="19" y1="9" x2="12" y2="15"/></svg>
<div class="empty-state-title">No server tunnels</div>
<div class="empty-state-desc">Click "New Server" to expose a local port as an hs:// tunnel</div>
</div>
</td></tr>`;
return;
}
tbody.innerHTML = servers.map(s => {
const id = s.id || s.serverId || '';
const url = s.url || s.hsUrl || '';
const safeId = id.replace(/"/g, '&quot;');
const label = s.label || '';
return `
<tr>
<td>
${label ? `<div style="font-weight:600;color:var(--text);margin-bottom:2px;">${escapeHtml(label)}</div>` : ''}
<div class="mono" style="font-size:11px;color:var(--text3);">${escapeHtml(truncate(id, 20))}</div>
</td>
<td style="font-weight:600;color:var(--text);">${s.port ?? '—'}</td>
<td>
<div style="display:flex;align-items:center;gap:6px;">
<span class="mono" title="${escapeHtml(url)}" style="font-size:11px;color:var(--cyan);">${truncate(url, 30)}</span>
${url ? `<button class="btn-icon copy-btn" data-copy="${escapeHtml(url)}" title="Copy hs:// URL">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
<span class="copy-tooltip">Copied!</span>
</button>` : ''}
</div>
</td>
<td>
<span class="badge badge-neutral" style="margin-right:4px;">${s.udp ? 'UDP' : 'TCP'}</span>${s.secure ? `<span class="badge badge-green">secure</span>` : `<span class="badge badge-neutral">plain</span>`}
</td>
<td>
<div style="display:flex;align-items:center;gap:6px;">
<button class="btn btn-ghost btn-sm" data-edit-server="${safeId}" title="Edit server">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
Edit
</button>
<button class="btn btn-danger btn-sm" data-stop-server="${safeId}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
Stop
</button>
</div>
</td>
</tr>`;
}).join('');
tbody.querySelectorAll('[data-copy]').forEach(btn => {
btn.addEventListener('click', () => copyToClipboard(btn.dataset.copy, btn));
});
tbody.querySelectorAll('[data-edit-server]').forEach(btn => {
btn.addEventListener('click', () => {
const serverId = btn.dataset.editServer;
const server = (state.servers || []).find(s => (s.id || s.serverId) === serverId);
if (!server) return;
$('serverEditId').value = serverId;
const labelEl = $('startServerLabel');
const portEl = $('startServerPort');
const hostEl = $('startServerHost');
const secureEl = $('startServerSecure');
if (labelEl) labelEl.value = server.label || '';
if (portEl) portEl.value = server.port ?? 3000;
if (hostEl) hostEl.value = server.host ?? '127.0.0.1';
if (secureEl) secureEl.checked = server.secure !== false;
const udpEl = document.querySelector('input[name="startServerProtocol"][value="' + (server.udp ? 'udp' : 'tcp') + '"]');
if (udpEl) udpEl.checked = true;
const titleEl = $('modal-startServer-title');
if (titleEl) titleEl.textContent = 'Edit Server Tunnel';
const submitEl = $('startServerSubmit');
if (submitEl) submitEl.textContent = 'Save Changes';
openModal('modal-startServer');
});
});
tbody.querySelectorAll('[data-stop-server]').forEach(btn => {
btn.addEventListener('click', () => {
const serverId = btn.dataset.stopServer;
const nameEl = $('stopServerName');
if (nameEl) nameEl.textContent = serverId;
$('stopServerConfirm').dataset.serverId = serverId;
openModal('modal-stopServer');
});
});
}
function setupServerEvents() {
$('startServerBtn')?.addEventListener('click', () => {
const editIdEl = $('serverEditId');
if (editIdEl) editIdEl.value = '';
const labelEl = $('startServerLabel');
if (labelEl) labelEl.value = '';
const portEl = $('startServerPort');
if (portEl) portEl.value = '3000';
const hostEl = $('startServerHost');
if (hostEl) hostEl.value = '127.0.0.1';
const secureEl = $('startServerSecure');
if (secureEl) secureEl.checked = true;
const tcpEl = $('startServerProtocolTcp');
if (tcpEl) tcpEl.checked = true;
const titleEl = $('modal-startServer-title');
if (titleEl) titleEl.textContent = 'Start Server Tunnel';
const submitEl = $('startServerSubmit');
if (submitEl) submitEl.textContent = 'Start Server';
openModal('modal-startServer');
});
$('startServerSubmit')?.addEventListener('click', () => {
const portEl = $('startServerPort');
const hostEl = $('startServerHost');
const secureEl = $('startServerSecure');
const labelEl = $('startServerLabel');
const editIdEl = $('serverEditId');
const port = parseInt(portEl?.value, 10) || 3000;
const host = (hostEl?.value || '127.0.0.1').trim();
const secure = secureEl?.checked !== false;
const udp = document.querySelector('input[name="startServerProtocol"]:checked')?.value === 'udp';
const label = (labelEl?.value || '').trim();
const editId = (editIdEl?.value || '').trim();
if (!port || port < 1 || port > 65535) { showModalError('modal-startServer', 'startServerError', 'Port must be 165535'); return; }
const btn = $('startServerSubmit');
if (btn) { btn.disabled = true; btn.textContent = editId ? 'Saving…' : 'Starting…'; }
const doStart = () => {
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'startServer', payload: { port, host, secure, udp, label } } },
(response) => {
if (btn) { btn.disabled = false; btn.textContent = editId ? 'Save Changes' : 'Start Server'; }
if (response?.ok) {
if (editIdEl) editIdEl.value = '';
closeModal('modal-startServer');
showToast(editId ? 'Server updated' : 'Server started', 'success');
refresh();
} else {
showModalError('modal-startServer', 'startServerError', response?.error || 'Failed to start');
}
}
);
};
if (editId) {
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'stopServer', payload: { serverId: editId } } },
() => doStart()
);
} else {
doStart();
}
});
$('stopServerConfirm')?.addEventListener('click', () => {
const serverId = $('stopServerConfirm').dataset.serverId;
if (!serverId) return;
const btn = $('stopServerConfirm');
btn.disabled = true; btn.textContent = 'Stopping…';
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'stopServer', payload: { serverId } } },
(response) => {
btn.disabled = false; btn.textContent = 'Stop Server';
closeModal('modal-stopServer');
if (response?.ok) showToast('Server stopped', 'success');
else showToast(response?.error || 'Failed to stop', 'error');
refresh();
}
);
});
}