mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-25 08:56:21 +00:00
Update notification service
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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 + '%'))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user