diff --git a/static/js/core/alerts.js b/static/js/core/alerts.js index 4c3f8a1..a0ebbb3 100644 --- a/static/js/core/alerts.js +++ b/static/js/core/alerts.js @@ -7,6 +7,7 @@ const AlertCenter = (function() { let rules = []; let eventSource = null; let reconnectTimer = null; + let lastConnectionWarningAt = 0; function init() { loadRules(); @@ -31,7 +32,14 @@ const AlertCenter = (function() { }; eventSource.onerror = function() { - console.warn('[Alerts] SSE connection error'); + const now = Date.now(); + const offline = (typeof window.isOffline === 'function' && window.isOffline()) || + (typeof navigator !== 'undefined' && navigator.onLine === false); + const shouldLog = !offline && !document.hidden && (now - lastConnectionWarningAt) > 15000; + if (shouldLog) { + lastConnectionWarningAt = now; + console.warn('[Alerts] SSE connection error; retrying'); + } if (reconnectTimer) clearTimeout(reconnectTimer); reconnectTimer = setTimeout(connect, 2500); }; diff --git a/static/js/core/run-state.js b/static/js/core/run-state.js index b9ea250..c481934 100644 --- a/static/js/core/run-state.js +++ b/static/js/core/run-state.js @@ -92,8 +92,9 @@ const RunState = (function() { renderHealth(data); } catch (err) { renderHealth(null, err); + const transient = isTransientFailure(err); const now = Date.now(); - if (typeof reportActionableError === 'function' && (now - lastErrorToastAt) > 30000) { + if (!transient && typeof reportActionableError === 'function' && (now - lastErrorToastAt) > 30000) { lastErrorToastAt = now; reportActionableError('Run State', err, { persistent: false }); } @@ -214,6 +215,17 @@ const RunState = (function() { return String(err); } + function isTransientFailure(err) { + if (typeof window.isTransientOrOffline === 'function' && window.isTransientOrOffline(err)) { + return true; + } + if (typeof navigator !== 'undefined' && navigator.onLine === false) { + return true; + } + const text = extractMessage(err).toLowerCase(); + return text.includes('failed to fetch') || text.includes('network') || text.includes('timeout'); + } + function getLastHealth() { return lastHealth; } diff --git a/static/js/core/ui-feedback.js b/static/js/core/ui-feedback.js index c39d53f..7b8c12f 100644 --- a/static/js/core/ui-feedback.js +++ b/static/js/core/ui-feedback.js @@ -208,9 +208,31 @@ const AppFeedback = (function() { return state; } + function isOffline() { + return typeof navigator !== 'undefined' && navigator.onLine === false; + } + + function isTransientNetworkError(error) { + const text = String(extractMessage(error) || '').toLowerCase(); + if (!text) return false; + + return text.includes('networkerror') || + text.includes('failed to fetch') || + text.includes('network request failed') || + text.includes('load failed') || + text.includes('err_network_io_suspended') || + text.includes('network io suspended') || + text.includes('the network connection was lost') || + text.includes('connection reset') || + text.includes('timeout'); + } + + function isTransientOrOffline(error) { + return isOffline() || isTransientNetworkError(error); + } + function isNetworkError(message) { - const text = String(message || '').toLowerCase(); - return text.includes('networkerror') || text.includes('failed to fetch') || text.includes('timeout'); + return isTransientNetworkError(message); } function isSettingsError(message) { @@ -224,6 +246,9 @@ const AppFeedback = (function() { reportError, removeToast, renderCollectionState, + isOffline, + isTransientNetworkError, + isTransientOrOffline, }; })(); @@ -243,6 +268,18 @@ window.renderCollectionState = function(container, options) { return AppFeedback.renderCollectionState(container, options); }; +window.isOffline = function() { + return AppFeedback.isOffline(); +}; + +window.isTransientNetworkError = function(error) { + return AppFeedback.isTransientNetworkError(error); +}; + +window.isTransientOrOffline = function(error) { + return AppFeedback.isTransientOrOffline(error); +}; + document.addEventListener('DOMContentLoaded', () => { AppFeedback.init(); }); diff --git a/templates/index.html b/templates/index.html index dd8937a..2fc06fa 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5655,10 +5655,19 @@ renderSdrStatus(devices); }) .catch(err => { - console.error('Failed to fetch SDR status:', err); + const transient = (typeof window.isTransientOrOffline === 'function' && window.isTransientOrOffline(err)) || + (typeof navigator !== 'undefined' && navigator.onLine === false) || + /failed to fetch|network io suspended|networkerror|timeout/i.test(String((err && err.message) || err || '')); + if (!transient) { + console.error('Failed to fetch SDR status:', err); + } const container = document.getElementById('sdrStatusList'); if (container) { - container.innerHTML = '