diff --git a/q1-deploy-monitor/dashboard/script.js b/q1-deploy-monitor/dashboard/script.js index c660dcc..de9bb66 100644 --- a/q1-deploy-monitor/dashboard/script.js +++ b/q1-deploy-monitor/dashboard/script.js @@ -14,7 +14,7 @@ ========================================================================= */ const CONFIG = { - primaryBaseUrl: 'https://gheyma-app-2b88268529f9.hosted.ghaymah.systems', + primaryBaseUrl: 'https://ghaymah-app-19cc5d65231a.hosted.ghaymah.systems', fallbackBaseUrl: 'http://localhost:8080', healthPath: '/health', metricsPath: '/metrics', @@ -32,6 +32,10 @@ const CONFIG = { const CACHE_KEYS = { CUMULATIVE_TOTAL: 'ghaymah_cumulative_total_requests', LAST_RAW_COUNT: 'ghaymah_last_raw_server_requests', + HISTORY: 'ghaymah_history', + UPTIME_BUCKETS: 'ghaymah_uptime_buckets', + EVENTS: 'ghaymah_events', + TOTALS: 'ghaymah_totals', }; function getStoredTotalRequests() { @@ -67,6 +71,82 @@ function updateStoredTotalRequests(serverRequests) { } } +/* ========================================================================= + Persistent State Helpers (history, uptime, events, totals) + ========================================================================= */ + +function saveState() { + try { + // Serialize history: convert Date timestamps to ISO strings + const historyToStore = state.history.map(h => ({ + ...h, + timestamp: h.timestamp instanceof Date ? h.timestamp.toISOString() : h.timestamp, + })); + localStorage.setItem(CACHE_KEYS.HISTORY, JSON.stringify(historyToStore)); + + localStorage.setItem(CACHE_KEYS.UPTIME_BUCKETS, JSON.stringify(state.uptimeBuckets)); + + // Serialize events: convert Date times to ISO strings + const eventsToStore = state.events.map(ev => ({ + ...ev, + time: ev.time instanceof Date ? ev.time.toISOString() : ev.time, + })); + localStorage.setItem(CACHE_KEYS.EVENTS, JSON.stringify(eventsToStore)); + + localStorage.setItem(CACHE_KEYS.TOTALS, JSON.stringify({ + success: state.totals.success, + failed: state.totals.failed, + })); + } catch (_) {} +} + +function loadState() { + try { + const historyRaw = localStorage.getItem(CACHE_KEYS.HISTORY); + if (historyRaw) { + const parsed = JSON.parse(historyRaw); + state.history = parsed.map(h => ({ + ...h, + timestamp: new Date(h.timestamp), + })); + // Trim to max allowed + if (state.history.length > CONFIG.maxHistoryPoints) { + state.history = state.history.slice(-CONFIG.maxHistoryPoints); + } + } + } catch (_) { state.history = []; } + + try { + const bucketsRaw = localStorage.getItem(CACHE_KEYS.UPTIME_BUCKETS); + if (bucketsRaw) { + state.uptimeBuckets = JSON.parse(bucketsRaw); + if (state.uptimeBuckets.length > CONFIG.uptimeBuckets) { + state.uptimeBuckets = state.uptimeBuckets.slice(-CONFIG.uptimeBuckets); + } + } + } catch (_) { state.uptimeBuckets = []; } + + try { + const eventsRaw = localStorage.getItem(CACHE_KEYS.EVENTS); + if (eventsRaw) { + const parsed = JSON.parse(eventsRaw); + state.events = parsed.map(ev => ({ + ...ev, + time: new Date(ev.time), + })); + } + } catch (_) { state.events = []; } + + try { + const totalsRaw = localStorage.getItem(CACHE_KEYS.TOTALS); + if (totalsRaw) { + const t = JSON.parse(totalsRaw); + state.totals.success = typeof t.success === 'number' ? t.success : 0; + state.totals.failed = typeof t.failed === 'number' ? t.failed : 0; + } + } catch (_) {} +} + /* ========================================================================= State ========================================================================= */ @@ -252,6 +332,9 @@ async function refreshFromSources() { } state.metricsError = metricsRes.error || 'unavailable'; } + + // Persist all state to localStorage after every refresh + saveState(); } /* ========================================================================= @@ -670,7 +753,8 @@ async function doRefresh() { ========================================================================= */ function init() { - renderAll(); // Initial empty state paint + loadState(); // Restore persisted state before first paint + renderAll(); // Paint with restored data document.getElementById('refreshBtn').addEventListener('click', doRefresh); window.addEventListener('resize', debounce(renderAll, 150));