mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-02-18 16:36:27 +00:00
Update health monitor
This commit is contained in:
@@ -105,6 +105,8 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
|
|||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let newOverallStatus = "OK"
|
||||||
|
|
||||||
// Use the new combined endpoint for fewer round-trips
|
// Use the new combined endpoint for fewer round-trips
|
||||||
const response = await fetch(getApiUrl("/api/health/full"))
|
const response = await fetch(getApiUrl("/api/health/full"))
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -114,14 +116,17 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
|
|||||||
const data = await legacyResponse.json()
|
const data = await legacyResponse.json()
|
||||||
setHealthData(data)
|
setHealthData(data)
|
||||||
setDismissedItems([])
|
setDismissedItems([])
|
||||||
|
newOverallStatus = data?.overall || "OK"
|
||||||
} else {
|
} else {
|
||||||
const fullData: FullHealthData = await response.json()
|
const fullData: FullHealthData = await response.json()
|
||||||
setHealthData(fullData.health)
|
setHealthData(fullData.health)
|
||||||
setDismissedItems(fullData.dismissed || [])
|
setDismissedItems(fullData.dismissed || [])
|
||||||
|
newOverallStatus = fullData.health?.overall || "OK"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit event with the FRESH data from the response, not the stale state
|
||||||
const event = new CustomEvent("healthStatusUpdated", {
|
const event = new CustomEvent("healthStatusUpdated", {
|
||||||
detail: { status: healthData?.overall || "OK" },
|
detail: { status: newOverallStatus },
|
||||||
})
|
})
|
||||||
window.dispatchEvent(event)
|
window.dispatchEvent(event)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -129,7 +134,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [getApiUrl, healthData?.overall])
|
}, [getApiUrl])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
|||||||
@@ -83,6 +83,12 @@ def acknowledge_error():
|
|||||||
health_monitor.last_check_times.pop(cache_key, None)
|
health_monitor.last_check_times.pop(cache_key, None)
|
||||||
health_monitor.cached_results.pop(cache_key, None)
|
health_monitor.cached_results.pop(cache_key, None)
|
||||||
|
|
||||||
|
# Also invalidate overall status caches so header updates immediately
|
||||||
|
health_monitor.last_check_times.pop('_bg_overall', None)
|
||||||
|
health_monitor.cached_results.pop('_bg_overall', None)
|
||||||
|
health_monitor.last_check_times.pop('overall_health', None)
|
||||||
|
health_monitor.cached_results.pop('overall_health', None)
|
||||||
|
|
||||||
# Determine suppression period for the response
|
# Determine suppression period for the response
|
||||||
category = result.get('category', '')
|
category = result.get('category', '')
|
||||||
if category == 'updates':
|
if category == 'updates':
|
||||||
|
|||||||
@@ -569,6 +569,34 @@ def _temperature_collector_loop():
|
|||||||
time.sleep(60)
|
time.sleep(60)
|
||||||
|
|
||||||
|
|
||||||
|
def _health_collector_loop():
|
||||||
|
"""Background thread: run full health checks every 5 minutes.
|
||||||
|
Keeps the health cache always fresh and records events/errors in the DB
|
||||||
|
so the future notification service can consume them."""
|
||||||
|
from health_monitor import health_monitor
|
||||||
|
|
||||||
|
# Wait 30s after startup to let other services initialize
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Run full health check (results get cached internally + recorded in DB)
|
||||||
|
result = health_monitor.get_detailed_status()
|
||||||
|
|
||||||
|
# Update the quick-status cache so the header stays fresh without extra work
|
||||||
|
overall = result.get('overall', 'OK')
|
||||||
|
summary = result.get('summary', 'All systems operational')
|
||||||
|
health_monitor.cached_results['_bg_overall'] = {
|
||||||
|
'status': overall,
|
||||||
|
'summary': summary
|
||||||
|
}
|
||||||
|
health_monitor.last_check_times['_bg_overall'] = time.time()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ProxMenux] Health collector error: {e}")
|
||||||
|
|
||||||
|
time.sleep(300) # Every 5 minutes
|
||||||
|
|
||||||
|
|
||||||
def get_uptime():
|
def get_uptime():
|
||||||
"""Get system uptime in a human-readable format."""
|
"""Get system uptime in a human-readable format."""
|
||||||
try:
|
try:
|
||||||
@@ -7006,6 +7034,15 @@ if __name__ == '__main__':
|
|||||||
else:
|
else:
|
||||||
print("[ProxMenux] Temperature history disabled (DB init failed)")
|
print("[ProxMenux] Temperature history disabled (DB init failed)")
|
||||||
|
|
||||||
|
# ── Background Health Monitor ──
|
||||||
|
# Run full health checks every 5 min, keeping cache fresh and recording events for notifications
|
||||||
|
try:
|
||||||
|
health_thread = threading.Thread(target=_health_collector_loop, daemon=True)
|
||||||
|
health_thread.start()
|
||||||
|
print("[ProxMenux] Background health monitor started (5 min interval)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ProxMenux] Background health monitor failed to start: {e}")
|
||||||
|
|
||||||
# Check for SSL configuration
|
# Check for SSL configuration
|
||||||
ssl_ctx = None
|
ssl_ctx = None
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -201,17 +201,25 @@ class HealthMonitor:
|
|||||||
def get_cached_health_status(self) -> Dict[str, str]:
|
def get_cached_health_status(self) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Get cached health status without running expensive checks.
|
Get cached health status without running expensive checks.
|
||||||
Returns the last calculated status or triggers a check if too old.
|
The background health collector keeps '_bg_overall' always fresh (every 5 min).
|
||||||
|
Falls back to calculating on demand if background data is stale or unavailable.
|
||||||
"""
|
"""
|
||||||
cache_key = 'overall_health'
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
# If cache exists and is less than 60 seconds old, return it
|
# 1. Check background collector cache (updated every 5 min by _health_collector_loop)
|
||||||
|
bg_key = '_bg_overall'
|
||||||
|
if bg_key in self.last_check_times:
|
||||||
|
age = current_time - self.last_check_times[bg_key]
|
||||||
|
if age < 360: # 6 min (5 min interval + 1 min tolerance)
|
||||||
|
return self.cached_results.get(bg_key, {'status': 'OK', 'summary': 'System operational'})
|
||||||
|
|
||||||
|
# 2. Check regular cache (updated by modal fetches or on-demand)
|
||||||
|
cache_key = 'overall_health'
|
||||||
if cache_key in self.last_check_times:
|
if cache_key in self.last_check_times:
|
||||||
if current_time - self.last_check_times[cache_key] < 60:
|
if current_time - self.last_check_times[cache_key] < 60:
|
||||||
return self.cached_results.get(cache_key, {'status': 'OK', 'summary': 'System operational'})
|
return self.cached_results.get(cache_key, {'status': 'OK', 'summary': 'System operational'})
|
||||||
|
|
||||||
# Otherwise, calculate and cache
|
# 3. No fresh cache - calculate on demand (happens only on first load before bg thread runs)
|
||||||
status = self.get_overall_status()
|
status = self.get_overall_status()
|
||||||
self.cached_results[cache_key] = {
|
self.cached_results[cache_key] = {
|
||||||
'status': status['status'],
|
'status': status['status'],
|
||||||
|
|||||||
Reference in New Issue
Block a user