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.
This commit is contained in:
@@ -129,10 +129,10 @@
|
|||||||
? 'bg-neon-green/10 border-neon-green/20 text-neon-green'
|
? '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')">
|
: (connectionStatus === 'connecting' ? 'bg-yellow-500/10 border-yellow-500/20 text-yellow-500' : 'bg-red-500/10 border-red-500/20 text-red-500')">
|
||||||
<div class="w-1.5 h-1.5 rounded-full"
|
<div class="w-1.5 h-1.5 rounded-full"
|
||||||
:class="connectionStatus === 'connected' ? 'bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)]' : (connectionStatus === 'connecting' ? 'bg-yellow-500 animate-pulse' : 'bg-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')">
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
x-text="$store.global.connectionStatus === 'connected' ? $store.global.t('online') : ($store.global.connectionStatus === 'disconnected' ? $store.global.t('offline') : $store.global.t('connecting'))"></span>
|
x-text="$store.data.connectionStatus === 'connected' ? $store.global.t('online') : ($store.data.connectionStatus === 'disconnected' ? $store.global.t('offline') : $store.global.t('connecting'))"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="h-4 w-px bg-space-border"></div>
|
<div class="h-4 w-px bg-space-border"></div>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.startAutoRefresh();
|
this.startAutoRefresh();
|
||||||
document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh());
|
document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh());
|
||||||
|
|
||||||
// Initial Fetch
|
// Initial Data Fetch (separate from health check)
|
||||||
Alpine.store('data').fetchData();
|
Alpine.store('data').fetchData();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
loading: false,
|
loading: false,
|
||||||
connectionStatus: 'connecting',
|
connectionStatus: 'connecting',
|
||||||
lastUpdated: '-',
|
lastUpdated: '-',
|
||||||
|
healthCheckTimer: null,
|
||||||
|
|
||||||
// Filters state
|
// Filters state
|
||||||
filters: {
|
filters: {
|
||||||
@@ -30,9 +31,8 @@ document.addEventListener('alpine:init', () => {
|
|||||||
// For simplicity, let's keep relevant filters here.
|
// For simplicity, let's keep relevant filters here.
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Watch filters to recompute
|
// Start health check monitoring
|
||||||
// Alpine stores don't have $watch automatically unless inside a component?
|
this.startHealthCheck();
|
||||||
// We can manually call compute when filters change.
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
@@ -63,11 +63,9 @@ document.addEventListener('alpine:init', () => {
|
|||||||
|
|
||||||
this.computeQuotaRows();
|
this.computeQuotaRows();
|
||||||
|
|
||||||
this.connectionStatus = 'connected';
|
|
||||||
this.lastUpdated = new Date().toLocaleTimeString();
|
this.lastUpdated = new Date().toLocaleTimeString();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fetch error:', error);
|
console.error('Fetch error:', error);
|
||||||
this.connectionStatus = 'disconnected';
|
|
||||||
const store = Alpine.store('global');
|
const store = Alpine.store('global');
|
||||||
store.showToast(store.t('connectionLost'), 'error');
|
store.showToast(store.t('connectionLost'), 'error');
|
||||||
} finally {
|
} 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() {
|
computeQuotaRows() {
|
||||||
const models = this.models || [];
|
const models = this.models || [];
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -209,6 +267,10 @@ document.addEventListener('alpine:init', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.stopHealthCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user