Merge branch 'badrisnarayanan:main' into main

This commit is contained in:
Simon Benedict
2026-01-11 16:15:22 +01:00
committed by GitHub
3 changed files with 74 additions and 8 deletions

View File

@@ -82,10 +82,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 === 'connecting' ? $store.global.t('connecting') : $store.global.t('offline'))"></span>
</div> </div>
<div class="h-4 w-px bg-space-border"></div> <div class="h-4 w-px bg-space-border"></div>

View File

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

View File

@@ -17,6 +17,7 @@ document.addEventListener('alpine:init', () => {
initialLoad: true, // Track first load for skeleton screen initialLoad: true, // Track first load for skeleton screen
connectionStatus: 'connecting', connectionStatus: 'connecting',
lastUpdated: '-', lastUpdated: '-',
healthCheckTimer: null,
// Filters state // Filters state
filters: { filters: {
@@ -31,9 +32,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() {
@@ -67,7 +67,6 @@ document.addEventListener('alpine:init', () => {
this.computeQuotaRows(); this.computeQuotaRows();
this.connectionStatus = 'connected';
this.lastUpdated = new Date().toLocaleTimeString(); this.lastUpdated = new Date().toLocaleTimeString();
// Fetch version from config endpoint if not already loaded // Fetch version from config endpoint if not already loaded
@@ -76,7 +75,6 @@ document.addEventListener('alpine:init', () => {
} }
} 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 {
@@ -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() { computeQuotaRows() {
const models = this.models || []; const models = this.models || [];
const rows = []; const rows = [];
@@ -233,6 +292,13 @@ document.addEventListener('alpine:init', () => {
}); });
return rows; return rows;
},
destroy() {
this.stopHealthCheck();
if (this._visibilityHandler) {
document.removeEventListener('visibilitychange', this._visibilityHandler);
}
} }
}); });
}); });