From 69440584fdce8c94c9180871f1bb78f2323acf4c Mon Sep 17 00:00:00 2001 From: jgor20 <102353650+jgor20@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:41:15 +0000 Subject: [PATCH] feat(webui): add health check monitoring for connection status Implement periodic health checks every 15 seconds to monitor connection status, pausing when the tab is hidden and resuming on visibility. Update UI bindings to use data store for connection status instead of global store. Add destroy method to clean up timers on component teardown. --- public/index.html | 4 +-- public/js/app-init.js | 2 +- public/js/data-store.js | 72 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/public/index.html b/public/index.html index c948359..ba271e7 100644 --- a/public/index.html +++ b/public/index.html @@ -129,10 +129,10 @@ ? 'bg-neon-green/10 border-neon-green/20 text-neon-green' : (connectionStatus === 'connecting' ? 'bg-yellow-500/10 border-yellow-500/20 text-yellow-500' : 'bg-red-500/10 border-red-500/20 text-red-500')">
+ :class="$store.data.connectionStatus === 'connected' ? 'bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)]' : ($store.data.connectionStatus === 'connecting' ? 'bg-yellow-500 animate-pulse' : 'bg-red-500')">
+ x-text="$store.data.connectionStatus === 'connected' ? $store.global.t('online') : ($store.data.connectionStatus === 'disconnected' ? $store.global.t('offline') : $store.global.t('connecting'))">
diff --git a/public/js/app-init.js b/public/js/app-init.js index c2b4593..407d48d 100644 --- a/public/js/app-init.js +++ b/public/js/app-init.js @@ -34,7 +34,7 @@ document.addEventListener('alpine:init', () => { this.startAutoRefresh(); document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh()); - // Initial Fetch + // Initial Data Fetch (separate from health check) Alpine.store('data').fetchData(); }, diff --git a/public/js/data-store.js b/public/js/data-store.js index 4bcb24d..c1332e7 100644 --- a/public/js/data-store.js +++ b/public/js/data-store.js @@ -16,6 +16,7 @@ document.addEventListener('alpine:init', () => { loading: false, connectionStatus: 'connecting', lastUpdated: '-', + healthCheckTimer: null, // Filters state filters: { @@ -30,9 +31,8 @@ document.addEventListener('alpine:init', () => { // For simplicity, let's keep relevant filters here. init() { - // Watch filters to recompute - // Alpine stores don't have $watch automatically unless inside a component? - // We can manually call compute when filters change. + // Start health check monitoring + this.startHealthCheck(); }, async fetchData() { @@ -63,11 +63,9 @@ document.addEventListener('alpine:init', () => { this.computeQuotaRows(); - this.connectionStatus = 'connected'; this.lastUpdated = new Date().toLocaleTimeString(); } catch (error) { console.error('Fetch error:', error); - this.connectionStatus = 'disconnected'; const store = Alpine.store('global'); store.showToast(store.t('connectionLost'), 'error'); } finally { @@ -75,6 +73,66 @@ document.addEventListener('alpine:init', () => { } }, + async performHealthCheck() { + try { + // Get password from global store + const password = Alpine.store('global').webuiPassword; + + // Use lightweight health endpoint + const { response, newPassword } = await window.utils.request('/health', {}, password); + + if (newPassword) Alpine.store('global').webuiPassword = newPassword; + + if (response.ok) { + this.connectionStatus = 'connected'; + } else { + this.connectionStatus = 'disconnected'; + } + } catch (error) { + console.error('Health check error:', error); + this.connectionStatus = 'disconnected'; + } + }, + + startHealthCheck() { + // Clear existing timer + if (this.healthCheckTimer) { + clearInterval(this.healthCheckTimer); + } + + // Setup visibility change listener + if (!this._healthVisibilitySetup) { + this._healthVisibilitySetup = true; + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + // Tab hidden - stop health checks + this.stopHealthCheck(); + } else { + // Tab visible - restart health checks + this.startHealthCheck(); + } + }); + } + + // Perform immediate health check + this.performHealthCheck(); + + // Schedule regular health checks every 15 seconds + this.healthCheckTimer = setInterval(() => { + // Only perform health check if tab is visible + if (!document.hidden) { + this.performHealthCheck(); + } + }, 15000); + }, + + stopHealthCheck() { + if (this.healthCheckTimer) { + clearInterval(this.healthCheckTimer); + this.healthCheckTimer = null; + } + }, + computeQuotaRows() { const models = this.models || []; const rows = []; @@ -209,6 +267,10 @@ document.addEventListener('alpine:init', () => { }); return rows; + }, + + destroy() { + this.stopHealthCheck(); } }); });