Files
holesail-browser/extension/dashboard/refresh.js
T
Raven Scott 4ed688a315
CI / Build & Test (push) Successful in 2m52s
fix(dashboard): smooth latency badge updates, add ping settings
- Snapshot and restore latency badge values across innerHTML re-renders
  so badges never flash back to "…ms" on the 2-second state refresh cycle
- Add CSS transition (color 0.4s ease) and in-flight opacity dim (.pinging)
  to .latency-badge for smooth color changes
- Decouple latency pinging from the state refresh cycle — pings now run on
  their own independent interval instead of being triggered by refresh()
- Add two new settings under a "Latency Ping" section in Settings:
  - Enable Latency Ping toggle (latencyPingEnabled, default: true)
  - Ping Interval in seconds (latencyPingIntervalMs, default: 5s)
- Restart ping interval immediately on settings save so changes take effect
  without a page reload
2026-03-01 00:46:37 -05:00

110 lines
4.5 KiB
JavaScript

/**
* 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<void>}
*/
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();
}