/** * Orchestrates a full dashboard state refresh cycle. * Fetches state from the background service worker, dispatches it to all page * renderers, pings latency badges, and merges settings defaults. */ // Orchestrates a full state refresh cycle. // Depends on: core/messaging.js (fetchState), core/state.js (settings, SETTINGS_DEFAULTS, currentState), // pages/ssh.js (sshConnections, renderSshGrid), // pages/rdp.js (rdpConnections, renderRdpGrid), // pages/overview.js (updateDashboard), // pages/virtual-hosts.js (updateConnectionsTable), // pages/servers.js (updateSwarmsTable), // pages/proxy-ca.js (updateTabsTable), // pages/service-tunnels.js (updateServiceTunnelsTable), // pages/settings.js (updateSettingsUI), // pages/backups.js (refreshBackups) const _pingInFlight = new Set(); /** * Ping all latency badges visible in the DOM. * Updates badge text/color in-place with a CSS transition so the table never * reflows. Skips badges that already have an in-flight request for the same * host:port key. Respects the `latencyPingEnabled` and `latencyPingIntervalMs` * settings — callers should check `latencyPingEnabled` before calling this. */ function _pingLatencyBadges() { if (settings.latencyPingEnabled === false) return; const badges = document.querySelectorAll('.latency-badge[data-ping-port]'); badges.forEach(badge => { const port = parseInt(badge.dataset.pingPort, 10); const host = badge.dataset.pingHost || '127.0.0.1'; if (!port) return; const key = host + ':' + port; if (_pingInFlight.has(key)) return; _pingInFlight.add(key); // Dim badge while request is in-flight — avoids a hard jump on update badge.classList.add('pinging'); chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'pingTunnel', payload: { host, port } } }, (response) => { _pingInFlight.delete(key); void chrome.runtime.lastError; if (!badge.isConnected) return; badge.classList.remove('pinging'); if (response && response.ok) { const ms = response.latencyMs; const color = ms < 50 ? 'var(--green)' : ms < 200 ? 'var(--amber)' : 'var(--red)'; // Only update if the value actually changed to avoid unnecessary repaints const next = ms + 'ms'; if (badge.textContent !== next) badge.textContent = next; badge.style.color = color; } else { if (badge.textContent !== '—') badge.textContent = '—'; badge.style.color = 'var(--text4)'; } } ); }); } /** * Fetch the latest extension state and update all dashboard page renderers. * Also pings latency badges and syncs settings defaults. * Called on a 2-second interval by `init.js`. * @returns {Promise} The fetched state, or null. */ async function refresh() { const state = await fetchState(); if (state) { // Sync SSH connections from native host state — decode base64 password if present if (Array.isArray(state.sshConnections)) { sshConnections = state.sshConnections.map(c => { const existing = sshConnections.find(e => e.id === c.id); let password = (existing && existing.password) || ''; if (!password && c.passwordB64) { try { password = decodeURIComponent(escape(atob(c.passwordB64))); } catch (_) {} } return { ...c, password }; }); renderSshGrid(); } // Sync RDP connections from native host state — decode base64 password if present if (Array.isArray(state.rdpConnections)) { rdpConnections = state.rdpConnections.map(c => { const existing = rdpConnections.find(e => e.id === c.id); let password = (existing && existing.password) || ''; if (!password && c.passwordB64) { try { password = decodeURIComponent(escape(atob(c.passwordB64))); } catch (_) {} } return { ...c, password }; }); renderRdpGrid(); } // Sync settings from native host state if (state.settings && typeof state.settings === 'object') { settings = { ...SETTINGS_DEFAULTS, ...state.settings }; } updateDashboard(state); updateConnectionsTable(state); updateSwarmsTable(state); updateTabsTable(state); updateServiceTunnelsTable(state); updateSettingsUI(); } const sshCountEl = $('sshCount'); if (sshCountEl) sshCountEl.textContent = sshConnections.length; refreshBackups(); if (typeof updateSyncStatus === 'function') updateSyncStatus(); return state ?? null; }