// Depends on: core/utils.js ($, escapeHtml, truncate), ui/state-tag.js (stateTag), // ui/toast.js (showToast, copyToClipboard), ui/modal.js (openModal), // data/hostname-validator.js (isValidVhostHostname) function updateConnectionsTable(state) { const tbody = $('connectionsTable'); if (!tbody) return; const virtualHosts = state.virtualHosts || []; if (virtualHosts.length === 0) { tbody.innerHTML = `
No virtual hosts
Click "Add Host" to assign a hostname to an hs:// tunnel
`; return; } tbody.innerHTML = virtualHosts.map(v => { const hostname = v.hostname || v.id || ''; const hsUrl = v.hsUrl || ''; const backend = (v.localHost && v.localPort != null) ? v.localHost + ':' + v.localPort : '—'; const openUrl = `https://${hostname}`; const safeHostname = hostname.replace(/"/g, '"'); const needsReconnect = v.state === 'error' || v.state === 'closed'; return ` ${escapeHtml(hostname)}
${truncate(hsUrl, 28)}
${escapeHtml(backend)} ${stateTag(v.state)}
Open ${needsReconnect ? `` : ''}
`; }).join(''); tbody.querySelectorAll('[data-copy]').forEach(btn => { btn.addEventListener('click', () => copyToClipboard(btn.dataset.copy, btn)); }); tbody.querySelectorAll('[data-reconnect-vhost]').forEach(btn => { btn.addEventListener('click', () => { const hostname = btn.dataset.reconnectVhost; const hsUrl = btn.dataset.hsUrl; btn.disabled = true; btn.textContent = 'Reconnecting…'; chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'setVirtualHost', payload: { hostname, hsUrl } } }, (response) => { if (response?.ok) { showToast('Tunnel reconnecting…', 'success'); } else { showToast(response?.error || 'Reconnect failed', 'error'); } refresh(); } ); }); }); tbody.querySelectorAll('[data-remove-vhost]').forEach(btn => { btn.addEventListener('click', () => { const hostname = btn.dataset.removeVhost; const nameEl = $('removeVhostName'); if (nameEl) nameEl.textContent = hostname; $('removeVhostConfirm').dataset.hostname = hostname; openModal('modal-removeVhost'); }); }); } function setupVirtualHostEvents() { $('addVhostBtn')?.addEventListener('click', () => openModal('modal-addVhost')); $('addVhostSubmit')?.addEventListener('click', () => { const hostnameEl = $('addVhostHostname'); const hsUrlEl = $('addVhostHsUrl'); let hostname = (hostnameEl?.value || '').trim(); hostname = hostname.replace(/^https?:\/\//i, '').replace(/[/:?#].*$/, '').toLowerCase().trim(); const hsUrl = (hsUrlEl?.value || '').trim(); const hostnameValidation = isValidVhostHostname(hostname); if (!hostnameValidation.ok) { showModalError('modal-addVhost', 'addVhostError', hostnameValidation.error); return; } if (!hsUrl || !hsUrl.startsWith('hs://')) { showModalError('modal-addVhost', 'addVhostError', 'Enter a valid hs:// URL'); return; } const btn = $('addVhostSubmit'); if (btn) { btn.disabled = true; btn.textContent = 'Adding…'; } chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'setVirtualHost', payload: { hostname, hsUrl } } }, (response) => { if (btn) { btn.disabled = false; btn.textContent = 'Add Host'; } if (response?.ok) { if (hostnameEl) hostnameEl.value = ''; if (hsUrlEl) hsUrlEl.value = ''; closeModal('modal-addVhost'); showToast('Virtual host added', 'success'); refresh(); } else { showModalError('modal-addVhost', 'addVhostError', response?.error || 'Failed to add'); } } ); }); $('removeVhostConfirm')?.addEventListener('click', () => { const hostname = $('removeVhostConfirm').dataset.hostname; if (!hostname) return; const btn = $('removeVhostConfirm'); btn.disabled = true; btn.textContent = 'Removing…'; chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'removeVirtualHost', payload: { hostname } } }, (response) => { btn.disabled = false; btn.textContent = 'Remove'; closeModal('modal-removeVhost'); if (response?.ok) { showToast('Virtual host removed', 'success'); } else { showToast(response?.error || 'Failed to remove', 'error'); } refresh(); } ); }); }