Update notification service

This commit is contained in:
MacRimi
2026-03-12 18:12:04 +01:00
parent 1a88dd801d
commit 4aaba7619e
2 changed files with 71 additions and 5 deletions

View File

@@ -85,6 +85,57 @@ export function ProxmoxDashboard() {
const [showHealthModal, setShowHealthModal] = useState(false)
const { showReleaseNotes, setShowReleaseNotes } = useVersionCheck()
// Category keys for health info count calculation
const HEALTH_CATEGORY_KEYS = [
{ key: "cpu", category: "temperature" },
{ key: "memory", category: "memory" },
{ key: "storage", category: "storage" },
{ key: "disks", category: "disks" },
{ key: "network", category: "network" },
{ key: "vms", category: "vms" },
{ key: "services", category: "pve_services" },
{ key: "logs", category: "logs" },
{ key: "updates", category: "updates" },
{ key: "security", category: "security" },
]
// Fetch health info count independently (for initial load and refresh)
const fetchHealthInfoCount = useCallback(async () => {
try {
const response = await fetchApi("/api/health/full")
let calculatedInfoCount = 0
if (response && response.health?.details) {
// Get categories that have dismissed items (these become INFO)
const customCats = new Set((response.custom_suppressions || []).map((cs: { category: string }) => cs.category))
const filteredDismissed = (response.dismissed || []).filter((item: { category: string }) => !customCats.has(item.category))
const categoriesWithDismissed = new Set<string>()
filteredDismissed.forEach((item: { category: string }) => {
const catMeta = HEALTH_CATEGORY_KEYS.find(c => c.category === item.category || c.key === item.category)
if (catMeta) {
categoriesWithDismissed.add(catMeta.key)
}
})
// Count effective INFO categories (original INFO + OK categories with dismissed)
HEALTH_CATEGORY_KEYS.forEach(({ key }) => {
const cat = response.health.details[key as keyof typeof response.health.details]
if (cat) {
const originalStatus = cat.status?.toUpperCase()
// Count as INFO if: originally INFO OR (originally OK and has dismissed items)
if (originalStatus === "INFO" || (originalStatus === "OK" && categoriesWithDismissed.has(key))) {
calculatedInfoCount++
}
}
})
}
setInfoCount(calculatedInfoCount)
} catch (error) {
// Silently fail - infoCount will remain at 0
}
}, [])
const fetchSystemData = useCallback(async () => {
try {
const data: FlaskSystemInfo = await fetchApi("/api/system-info")
@@ -129,20 +180,25 @@ export function ProxmoxDashboard() {
useEffect(() => {
// Siempre fetch inicial
fetchSystemData()
fetchHealthInfoCount() // Fetch info count on initial load
// En overview: cada 30 segundos para actualización frecuente del estado de salud
// En otras tabs: cada 60 segundos para reducir carga
let interval: ReturnType<typeof setInterval> | null = null
let healthInterval: ReturnType<typeof setInterval> | null = null
if (activeTab === "overview") {
interval = setInterval(fetchSystemData, 30000) // 30 segundos
healthInterval = setInterval(fetchHealthInfoCount, 30000) // Also refresh info count
} else {
interval = setInterval(fetchSystemData, 60000) // 60 segundos
healthInterval = setInterval(fetchHealthInfoCount, 60000) // Also refresh info count
}
return () => {
if (interval) clearInterval(interval)
if (healthInterval) clearInterval(healthInterval)
}
}, [fetchSystemData, activeTab])
}, [fetchSystemData, fetchHealthInfoCount, activeTab])
useEffect(() => {
const handleChangeTab = (event: CustomEvent) => {

View File

@@ -141,6 +141,10 @@ class HealthPersistence:
if 'suppression_hours' not in columns:
cursor.execute('ALTER TABLE errors ADD COLUMN suppression_hours INTEGER DEFAULT 24')
# Migration: add acknowledged_at column to errors if not present
if 'acknowledged_at' not in columns:
cursor.execute('ALTER TABLE errors ADD COLUMN acknowledged_at TEXT')
# Indexes for performance
cursor.execute('CREATE INDEX IF NOT EXISTS idx_error_key ON errors(error_key)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_category ON errors(category)')
@@ -346,9 +350,10 @@ class HealthPersistence:
configured_hours = int(stored)
if configured_hours != self.DEFAULT_SUPPRESSION_HOURS:
# Non-default setting found: auto-acknowledge
# Mark as acknowledged but DO NOT set resolved_at - error remains active
cursor.execute('''
UPDATE errors
SET acknowledged = 1, resolved_at = ?, suppression_hours = ?
SET acknowledged = 1, acknowledged_at = ?, suppression_hours = ?
WHERE error_key = ? AND acknowledged = 0
''', (now, configured_hours, error_key))
@@ -514,9 +519,10 @@ class HealthPersistence:
except (ValueError, TypeError):
pass
# Insert as acknowledged but NOT resolved - error remains active
cursor.execute('''
INSERT INTO errors (error_key, category, severity, reason, first_seen, last_seen,
occurrence_count, acknowledged, resolved_at, suppression_hours)
occurrence_count, acknowledged, acknowledged_at, suppression_hours)
VALUES (?, ?, 'WARNING', 'Dismissed by user', ?, ?, 1, 1, ?, ?)
''', (error_key, category, now, now, now, sup_hours))
@@ -554,9 +560,12 @@ class HealthPersistence:
except (ValueError, TypeError):
pass
# Mark as acknowledged but DO NOT set resolved_at
# The error remains active until it actually disappears from the system
# resolved_at should only be set when the error is truly resolved
cursor.execute('''
UPDATE errors
SET acknowledged = 1, resolved_at = ?, suppression_hours = ?
SET acknowledged = 1, acknowledged_at = ?, suppression_hours = ?
WHERE error_key = ?
''', (now, sup_hours, error_key))
@@ -578,9 +587,10 @@ class HealthPersistence:
if child_prefix:
# Only cascade to active (unresolved) child errors.
# Already-resolved/expired entries must NOT be re-surfaced.
# Mark as acknowledged but DO NOT set resolved_at
cursor.execute('''
UPDATE errors
SET acknowledged = 1, resolved_at = ?, suppression_hours = ?
SET acknowledged = 1, acknowledged_at = ?, suppression_hours = ?
WHERE error_key LIKE ? AND acknowledged = 0 AND resolved_at IS NULL
''', (now, sup_hours, child_prefix + '%'))