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();
}
});
});