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 1/3] 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(); } }); }); From c3629d660c8e2eb6a457350c20a78c2284dd79dd Mon Sep 17 00:00:00 2001 From: jgor20 <102353650+jgor20@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:58:11 +0000 Subject: [PATCH 2/3] fix(webui): prevent duplicate visibility change listeners in health check Ensure the visibility change event listener for health checks is set up only once and properly removed on destroy to avoid memory leaks and duplicate handlers. --- public/js/data-store.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/js/data-store.js b/public/js/data-store.js index c1332e7..d1a4e68 100644 --- a/public/js/data-store.js +++ b/public/js/data-store.js @@ -100,10 +100,10 @@ document.addEventListener('alpine:init', () => { clearInterval(this.healthCheckTimer); } - // Setup visibility change listener + // Setup visibility change listener (only once) if (!this._healthVisibilitySetup) { this._healthVisibilitySetup = true; - document.addEventListener('visibilitychange', () => { + this._visibilityHandler = () => { if (document.hidden) { // Tab hidden - stop health checks this.stopHealthCheck(); @@ -111,7 +111,8 @@ document.addEventListener('alpine:init', () => { // Tab visible - restart health checks this.startHealthCheck(); } - }); + }; + document.addEventListener('visibilitychange', this._visibilityHandler); } // Perform immediate health check @@ -271,6 +272,9 @@ document.addEventListener('alpine:init', () => { destroy() { this.stopHealthCheck(); + if (this._visibilityHandler) { + document.removeEventListener('visibilitychange', this._visibilityHandler); + } } }); }); From 5879022dc38cc3ea1e9d59d622770987d6b7e00c Mon Sep 17 00:00:00 2001 From: jgor20 <102353650+jgor20@users.noreply.github.com> Date: Sun, 11 Jan 2026 09:46:41 +0000 Subject: [PATCH 3/3] fix: use lightweight /api/config for health checks and simplify UI status logic --- public/index.html | 2 +- public/js/data-store.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index ba271e7..e7d4c2b 100644 --- a/public/index.html +++ b/public/index.html @@ -132,7 +132,7 @@ :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 === 'connecting' ? $store.global.t('connecting') : $store.global.t('offline'))">
diff --git a/public/js/data-store.js b/public/js/data-store.js index d1a4e68..bb9c0df 100644 --- a/public/js/data-store.js +++ b/public/js/data-store.js @@ -78,8 +78,8 @@ document.addEventListener('alpine:init', () => { // Get password from global store const password = Alpine.store('global').webuiPassword; - // Use lightweight health endpoint - const { response, newPassword } = await window.utils.request('/health', {}, password); + // Use lightweight endpoint (no quota fetching) + const { response, newPassword } = await window.utils.request('/api/config', {}, password); if (newPassword) Alpine.store('global').webuiPassword = newPassword;