CI / Build & Test (push) Successful in 3m19s
- Add unit tests (hostname-validator, TLDs, payload-schemas) and integration tests for message handler registry - Refactor native host message router into handler registry (handlers/state, tunnels, ssh, rdp, backup, ca, connections) - Add ESLint config and npm test + lint steps in CI - Dashboard: visibility-based refresh pause, configurable refresh interval (2s/5s/10s/paused) - Accessibility: ARIA on nav and modals, focus trap and restore, prefers-reduced-motion - Empty states: primary action buttons for virtual hosts, servers, service tunnels - Native host rate limiting for backup and CA operations; update SECURITY.md - CONTRIBUTING: "Adding a new dashboard page", dev workflow; add npm run dev script
85 lines
5.2 KiB
JavaScript
85 lines
5.2 KiB
JavaScript
/**
|
|
* Settings page — syncs the settings form with the persisted settings object
|
|
* and saves changes to the native host.
|
|
* Depends on: core/utils.js ($), core/state.js (settings, SETTINGS_DEFAULTS), ui/toast.js (showToast)
|
|
*/
|
|
|
|
/**
|
|
* True while the user has made unsaved changes to settings controls.
|
|
* Prevents the 2-second refresh cycle from overwriting in-progress edits.
|
|
*/
|
|
let _settingsDirty = false;
|
|
|
|
/** Mark settings as having unsaved changes. */
|
|
function _markSettingsDirty() { _settingsDirty = true; }
|
|
|
|
/** Clear the dirty flag (called after a successful save or reset). */
|
|
function _clearSettingsDirty() { _settingsDirty = false; }
|
|
|
|
/**
|
|
* Sync all settings form controls with the current `settings` object.
|
|
* Called on every refresh cycle to keep the UI in sync with persisted state.
|
|
* Skipped while the user has unsaved changes to avoid reverting their edits.
|
|
*/
|
|
function updateSettingsUI() {
|
|
if (_settingsDirty) return;
|
|
$('toggleNotify')?.classList.toggle('active', settings.notifyOnDisconnect === true);
|
|
$('toggleNotifyTunnelError')?.classList.toggle('active', settings.notifyOnTunnelError !== false);
|
|
$('toggleTunnelAutoReconnect')?.classList.toggle('active', settings.tunnelAutoReconnect === true);
|
|
$('toggleDebug')?.classList.toggle('active', settings.debug === true);
|
|
$('toggleDisableFileUrls')?.classList.toggle('active', settings.disableOnFileUrls === true);
|
|
$('toggleTheme')?.classList.toggle('active', document.documentElement.getAttribute('data-theme') === 'light');
|
|
$('toggleLatencyPing')?.classList.toggle('active', settings.latencyPingEnabled === true);
|
|
const proxyPortEl = $('proxyPort');
|
|
const readyTimeoutMsEl = $('readyTimeoutMs');
|
|
const backupRetentionEl = $('backupRetention');
|
|
const backupIntervalEl = $('backupIntervalHours');
|
|
const latencyPingIntervalEl = $('latencyPingIntervalMs');
|
|
const dashboardRefreshEl = $('dashboardRefreshIntervalMs');
|
|
if (proxyPortEl) proxyPortEl.value = settings.proxyPort ?? SETTINGS_DEFAULTS.proxyPort;
|
|
if (readyTimeoutMsEl) readyTimeoutMsEl.value = settings.readyTimeoutMs ?? SETTINGS_DEFAULTS.readyTimeoutMs;
|
|
if (backupRetentionEl) backupRetentionEl.value = settings.backupRetention ?? SETTINGS_DEFAULTS.backupRetention;
|
|
if (backupIntervalEl) backupIntervalEl.value = settings.backupIntervalHours ?? SETTINGS_DEFAULTS.backupIntervalHours;
|
|
if (latencyPingIntervalEl) latencyPingIntervalEl.value = (settings.latencyPingIntervalMs ?? SETTINGS_DEFAULTS.latencyPingIntervalMs) / 1000;
|
|
if (dashboardRefreshEl) dashboardRefreshEl.value = String(settings.dashboardRefreshIntervalMs ?? SETTINGS_DEFAULTS.dashboardRefreshIntervalMs ?? 2000);
|
|
}
|
|
|
|
function saveSettings() {
|
|
settings.notifyOnDisconnect = $('toggleNotify')?.classList.contains('active') ?? SETTINGS_DEFAULTS.notifyOnDisconnect;
|
|
settings.notifyOnTunnelError = $('toggleNotifyTunnelError')?.classList.contains('active') ?? SETTINGS_DEFAULTS.notifyOnTunnelError;
|
|
settings.tunnelAutoReconnect = $('toggleTunnelAutoReconnect')?.classList.contains('active') ?? SETTINGS_DEFAULTS.tunnelAutoReconnect;
|
|
settings.debug = $('toggleDebug')?.classList.contains('active') ?? SETTINGS_DEFAULTS.debug;
|
|
settings.disableOnFileUrls = $('toggleDisableFileUrls')?.classList.contains('active') ?? SETTINGS_DEFAULTS.disableOnFileUrls;
|
|
settings.latencyPingEnabled = $('toggleLatencyPing')?.classList.contains('active') ?? SETTINGS_DEFAULTS.latencyPingEnabled;
|
|
settings.proxyPort = parseInt($('proxyPort')?.value, 10) || SETTINGS_DEFAULTS.proxyPort;
|
|
settings.readyTimeoutMs = parseInt($('readyTimeoutMs')?.value, 10) || SETTINGS_DEFAULTS.readyTimeoutMs;
|
|
settings.backupRetention = Math.max(1, parseInt($('backupRetention')?.value, 10) || SETTINGS_DEFAULTS.backupRetention);
|
|
settings.backupIntervalHours = Math.max(0, parseInt($('backupIntervalHours')?.value, 10) || 0);
|
|
const pingSecs = parseFloat($('latencyPingIntervalMs')?.value);
|
|
settings.latencyPingIntervalMs = isNaN(pingSecs) || pingSecs <= 0
|
|
? SETTINGS_DEFAULTS.latencyPingIntervalMs
|
|
: Math.round(pingSecs * 1000);
|
|
const refreshVal = parseInt($('dashboardRefreshIntervalMs')?.value, 10);
|
|
settings.dashboardRefreshIntervalMs = ([0, 2000, 5000, 10000].includes(refreshVal) ? refreshVal : SETTINGS_DEFAULTS.dashboardRefreshIntervalMs);
|
|
chrome.runtime.sendMessage(
|
|
{ target: 'holesail-native', action: 'send', payload: { type: 'updateSettings', payload: { ...settings } } },
|
|
(response) => {
|
|
if (chrome.runtime.lastError) { showToast('Settings save failed: ' + chrome.runtime.lastError.message, 'error'); return; }
|
|
if (response && response.ok) {
|
|
if (response.settings) settings = { ...SETTINGS_DEFAULTS, ...response.settings };
|
|
_clearSettingsDirty();
|
|
// Restart the ping interval so the new interval / enabled state takes effect immediately
|
|
if (typeof window._restartPingInterval === 'function') window._restartPingInterval();
|
|
if (typeof window._restartRefreshInterval === 'function') window._restartRefreshInterval();
|
|
if (response.requiresRestart) {
|
|
showToast('Settings saved — restart the native host for proxy port changes to take effect', 'warning');
|
|
} else {
|
|
showToast('Settings saved', 'success');
|
|
}
|
|
} else {
|
|
showToast(response?.error || 'Failed to save settings', 'error');
|
|
}
|
|
}
|
|
);
|
|
}
|