// Depends on: core/utils.js ($, escapeHtml, truncate), ui/toast.js (showToast, copyToClipboard), // ui/modal.js (openModal, closeModal, showModalError) function _updateServerBulkBar() { const checked = document.querySelectorAll('#swarmsTable input[type="checkbox"]:checked'); const bar = $('serverBulkBar'); const countEl = $('serverBulkCount'); if (!bar) return; if (checked.length > 0) { bar.style.display = 'flex'; if (countEl) countEl.textContent = checked.length + ' selected'; } else { bar.style.display = 'none'; } } function updateSwarmsTable(state) { const tbody = $('swarmsTable'); if (!tbody) return; const servers = state.servers || []; if (servers.length === 0) { tbody.innerHTML = `
No server tunnels
Click "New Server" to expose a local port as an hs:// tunnel
`; _updateServerBulkBar(); return; } tbody.innerHTML = servers.map(s => { const id = s.id || s.serverId || ''; const url = s.url || s.hsUrl || ''; const safeId = id.replace(/"/g, '"'); const label = s.label || ''; return ` ${label ? `
${escapeHtml(label)}
` : ''}
${escapeHtml(truncate(id, 20))}
${s.port ?? '—'}
${truncate(url, 30)} ${url ? `` : ''}
${s.udp ? 'UDP' : 'TCP'}${s.secure ? `secure` : `plain`}
`; }).join(''); tbody.querySelectorAll('.server-row-cb').forEach(cb => { cb.addEventListener('change', _updateServerBulkBar); }); 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'); }); }); _updateServerBulkBar(); } function setupServerEvents() { $('serverSelectAll')?.addEventListener('change', (e) => { document.querySelectorAll('#swarmsTable .server-row-cb').forEach(cb => { cb.checked = e.target.checked; }); _updateServerBulkBar(); }); $('btnServerBulkStop')?.addEventListener('click', () => { const checked = Array.from(document.querySelectorAll('#swarmsTable .server-row-cb:checked')); if (!checked.length) return; const ids = checked.map(cb => cb.dataset.serverId); if (!confirm('Stop ' + ids.length + ' server(s)?')) return; let done = 0; for (const serverId of ids) { chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'stopServer', payload: { serverId } } }, () => { done++; if (done === ids.length) { showToast(ids.length + ' server(s) stopped', 'success'); refresh(); } } ); } }); $('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 1–65535'); 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(); } ); }); }