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;