diff --git a/public/index.html b/public/index.html
index 1533120..dd4467b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -82,10 +82,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 === 'connecting' ? $store.global.t('connecting') : $store.global.t('offline'))">
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 4b5262c..dbc65ec 100644
--- a/public/js/data-store.js
+++ b/public/js/data-store.js
@@ -17,6 +17,7 @@ document.addEventListener('alpine:init', () => {
initialLoad: true, // Track first load for skeleton screen
connectionStatus: 'connecting',
lastUpdated: '-',
+ healthCheckTimer: null,
// Filters state
filters: {
@@ -31,9 +32,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() {
@@ -67,7 +67,6 @@ document.addEventListener('alpine:init', () => {
this.computeQuotaRows();
- this.connectionStatus = 'connected';
this.lastUpdated = new Date().toLocaleTimeString();
// Fetch version from config endpoint if not already loaded
@@ -76,7 +75,6 @@ document.addEventListener('alpine:init', () => {
}
} catch (error) {
console.error('Fetch error:', error);
- this.connectionStatus = 'disconnected';
const store = Alpine.store('global');
store.showToast(store.t('connectionLost'), 'error');
} finally {
@@ -99,6 +97,67 @@ document.addEventListener('alpine:init', () => {
}
},
+ async performHealthCheck() {
+ try {
+ // Get password from global store
+ const password = Alpine.store('global').webuiPassword;
+
+ // Use lightweight endpoint (no quota fetching)
+ const { response, newPassword } = await window.utils.request('/api/config', {}, 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 (only once)
+ if (!this._healthVisibilitySetup) {
+ this._healthVisibilitySetup = true;
+ this._visibilityHandler = () => {
+ if (document.hidden) {
+ // Tab hidden - stop health checks
+ this.stopHealthCheck();
+ } else {
+ // Tab visible - restart health checks
+ this.startHealthCheck();
+ }
+ };
+ document.addEventListener('visibilitychange', this._visibilityHandler);
+ }
+
+ // 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 = [];
@@ -233,6 +292,13 @@ document.addEventListener('alpine:init', () => {
});
return rows;
+ },
+
+ destroy() {
+ this.stopHealthCheck();
+ if (this._visibilityHandler) {
+ document.removeEventListener('visibilitychange', this._visibilityHandler);
+ }
}
});
});