mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-05-01 03:46:22 +00:00
Update notification service
This commit is contained in:
@@ -441,7 +441,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
|
|||||||
disabled={dismissingKey === checkKey}
|
disabled={dismissingKey === checkKey}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
handleAcknowledge(checkKey, e)
|
handleAcknowledge(checkData.error_key || checkKey, e)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{dismissingKey === checkKey ? (
|
{dismissingKey === checkKey ? (
|
||||||
|
|||||||
@@ -1817,7 +1817,8 @@ class HealthMonitor:
|
|||||||
checks[vm_label] = {
|
checks[vm_label] = {
|
||||||
'status': val.get('status', 'WARNING'),
|
'status': val.get('status', 'WARNING'),
|
||||||
'detail': val.get('reason', 'Error'),
|
'detail': val.get('reason', 'Error'),
|
||||||
'dismissable': True
|
'dismissable': True,
|
||||||
|
'error_key': vm_label
|
||||||
}
|
}
|
||||||
|
|
||||||
if not issues:
|
if not issues:
|
||||||
@@ -1878,16 +1879,20 @@ class HealthMonitor:
|
|||||||
# Build checks dict with status per service
|
# Build checks dict with status per service
|
||||||
checks = {}
|
checks = {}
|
||||||
for svc in services_to_check:
|
for svc in services_to_check:
|
||||||
|
error_key = f'pve_service_{svc}'
|
||||||
if svc in failed_services:
|
if svc in failed_services:
|
||||||
state = service_details.get(svc, 'inactive')
|
state = service_details.get(svc, 'inactive')
|
||||||
checks[svc] = {
|
checks[svc] = {
|
||||||
'status': 'CRITICAL',
|
'status': 'CRITICAL',
|
||||||
'detail': f'Service is {state}',
|
'detail': f'Service is {state}',
|
||||||
|
'error_key': error_key,
|
||||||
|
'dismissable': True,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
checks[svc] = {
|
checks[svc] = {
|
||||||
'status': 'OK',
|
'status': 'OK',
|
||||||
'detail': 'Active',
|
'detail': 'Active',
|
||||||
|
'error_key': error_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_cluster:
|
if is_cluster:
|
||||||
@@ -2445,20 +2450,24 @@ class HealthMonitor:
|
|||||||
'status': sec_status,
|
'status': sec_status,
|
||||||
'detail': f'{len(security_updates_packages)} security update(s) pending' if security_updates_packages else 'No security updates pending',
|
'detail': f'{len(security_updates_packages)} security update(s) pending' if security_updates_packages else 'No security updates pending',
|
||||||
'dismissable': True if security_updates_packages and not sec_dismissed else False,
|
'dismissable': True if security_updates_packages and not sec_dismissed else False,
|
||||||
'dismissed': bool(sec_dismissed)
|
'dismissed': bool(sec_dismissed),
|
||||||
|
'error_key': 'security_updates'
|
||||||
},
|
},
|
||||||
'system_age': {
|
'system_age': {
|
||||||
'status': update_age_status,
|
'status': update_age_status,
|
||||||
'detail': f'Last updated {last_update_days} day(s) ago' if last_update_days is not None else 'Unknown',
|
'detail': f'Last updated {last_update_days} day(s) ago' if last_update_days is not None else 'Unknown',
|
||||||
'dismissable': False if update_age_status == 'CRITICAL' else True if update_age_status == 'WARNING' else False
|
'dismissable': False if update_age_status == 'CRITICAL' else True if update_age_status == 'WARNING' else False,
|
||||||
|
'error_key': 'system_age'
|
||||||
},
|
},
|
||||||
'pending_updates': {
|
'pending_updates': {
|
||||||
'status': 'INFO' if update_count > 50 else 'OK',
|
'status': 'INFO' if update_count > 50 else 'OK',
|
||||||
'detail': f'{update_count} package(s) pending',
|
'detail': f'{update_count} package(s) pending',
|
||||||
|
'error_key': 'pending_updates'
|
||||||
},
|
},
|
||||||
'kernel_pve': {
|
'kernel_pve': {
|
||||||
'status': kernel_status,
|
'status': kernel_status,
|
||||||
'detail': f'{len(kernel_pve_updates_packages)} kernel/PVE update(s)' if kernel_pve_updates_packages else 'Kernel/PVE up to date',
|
'detail': f'{len(kernel_pve_updates_packages)} kernel/PVE update(s)' if kernel_pve_updates_packages else 'Kernel/PVE up to date',
|
||||||
|
'error_key': 'kernel_pve'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2695,13 +2704,19 @@ class HealthMonitor:
|
|||||||
# Persist errors and respect dismiss for each sub-check
|
# Persist errors and respect dismiss for each sub-check
|
||||||
dismissed_keys = set()
|
dismissed_keys = set()
|
||||||
security_sub_checks = {
|
security_sub_checks = {
|
||||||
'security_login_attempts': checks.get('login_attempts', {}),
|
'security_login_attempts': 'login_attempts',
|
||||||
'security_certificates': checks.get('certificates', {}),
|
'security_certificates': 'certificates',
|
||||||
'security_uptime': checks.get('uptime', {}),
|
'security_uptime': 'uptime',
|
||||||
'security_fail2ban': checks.get('fail2ban', {}),
|
'security_fail2ban': 'fail2ban',
|
||||||
}
|
}
|
||||||
|
|
||||||
for err_key, check_info in security_sub_checks.items():
|
# Inject error_key into each check so the frontend knows which DB key to use
|
||||||
|
for err_key, check_name in security_sub_checks.items():
|
||||||
|
if check_name in checks:
|
||||||
|
checks[check_name]['error_key'] = err_key
|
||||||
|
|
||||||
|
for err_key, check_name in security_sub_checks.items():
|
||||||
|
check_info = checks.get(check_name, {})
|
||||||
check_status = check_info.get('status', 'OK')
|
check_status = check_info.get('status', 'OK')
|
||||||
if check_status not in ('OK', 'INFO'):
|
if check_status not in ('OK', 'INFO'):
|
||||||
is_dismissable = check_info.get('dismissable', True)
|
is_dismissable = check_info.get('dismissable', True)
|
||||||
|
|||||||
@@ -410,6 +410,54 @@ class HealthPersistence:
|
|||||||
|
|
||||||
result = {'success': False, 'error_key': error_key}
|
result = {'success': False, 'error_key': error_key}
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
# Error not in DB yet -- create a minimal record so the dismiss persists.
|
||||||
|
# Try to infer category from the error_key prefix.
|
||||||
|
category = ''
|
||||||
|
for cat, prefix in [('security', 'security_'), ('updates', 'security_updates'),
|
||||||
|
('updates', 'update_'), ('updates', 'kernel_'),
|
||||||
|
('updates', 'pending_'), ('updates', 'system_age'),
|
||||||
|
('pve_services', 'pve_service_'), ('vms', 'vm_'), ('vms', 'ct_'),
|
||||||
|
('disks', 'disk_'), ('logs', 'log_'), ('network', 'net_'),
|
||||||
|
('temperature', 'temp_')]:
|
||||||
|
if error_key.startswith(prefix) or error_key == prefix:
|
||||||
|
category = cat
|
||||||
|
break
|
||||||
|
|
||||||
|
setting_key = self.CATEGORY_SETTING_MAP.get(category, '')
|
||||||
|
sup_hours = self.DEFAULT_SUPPRESSION_HOURS
|
||||||
|
if setting_key:
|
||||||
|
stored = self.get_setting(setting_key)
|
||||||
|
if stored is not None:
|
||||||
|
try:
|
||||||
|
sup_hours = int(stored)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO errors (error_key, category, severity, reason, first_seen, last_seen,
|
||||||
|
occurrence_count, acknowledged, resolved_at, suppression_hours)
|
||||||
|
VALUES (?, ?, 'WARNING', 'Dismissed by user', ?, ?, 1, 1, ?, ?)
|
||||||
|
''', (error_key, category, now, now, now, sup_hours))
|
||||||
|
|
||||||
|
self._record_event(cursor, 'acknowledged', error_key, {
|
||||||
|
'original_severity': 'WARNING',
|
||||||
|
'category': category,
|
||||||
|
'suppression_hours': sup_hours
|
||||||
|
})
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'success': True,
|
||||||
|
'error_key': error_key,
|
||||||
|
'original_severity': 'WARNING',
|
||||||
|
'category': category,
|
||||||
|
'suppression_hours': sup_hours,
|
||||||
|
'acknowledged_at': now
|
||||||
|
}
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return result
|
||||||
|
|
||||||
if row:
|
if row:
|
||||||
error_dict = dict(row)
|
error_dict = dict(row)
|
||||||
original_severity = error_dict.get('severity', 'WARNING')
|
original_severity = error_dict.get('severity', 'WARNING')
|
||||||
|
|||||||
Reference in New Issue
Block a user