mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-25 00:46:21 +00:00
update health_persistence.py
This commit is contained in:
@@ -1194,7 +1194,7 @@ return (
|
||||
<span className="text-xs text-muted-foreground">{engineName}</span>
|
||||
<span className="text-xs font-medium">{utilizationNum.toFixed(1)}%</span>
|
||||
</div>
|
||||
<Progress value={utilizationNum} className="h-1.5 [&>div]:bg-blue-500" />
|
||||
<Progress value={utilizationNum} className="h-2 [&>div]:bg-blue-500" />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1450,12 +1450,11 @@ export function StorageOverview() {
|
||||
{lifeRemaining !== null && (
|
||||
<div className="flex flex-col items-center gap-1 flex-shrink-0">
|
||||
<svg width="88" height="88" viewBox="0 0 88 88">
|
||||
<circle cx="44" cy="44" r="35" fill="none" stroke="currentColor" strokeWidth="6" className="text-muted/10" />
|
||||
<circle cx="44" cy="44" r="35" fill="none" stroke={lifeColor} strokeWidth="6"
|
||||
strokeDasharray={`${lifeRemaining * 2.199} 219.9`}
|
||||
strokeLinecap="round" transform="rotate(-90 44 44)" />
|
||||
<text x="44" y="40" textAnchor="middle" fill={lifeColor} fontSize="20" fontWeight="700">{lifeRemaining}%</text>
|
||||
<text x="44" y="55" textAnchor="middle" fill="currentColor" fontSize="9" className="text-muted-foreground">life</text>
|
||||
<text x="44" y="56" textAnchor="middle" fill="currentColor" fontSize="12" className="text-muted-foreground">life</text>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
@@ -1466,7 +1465,7 @@ export function StorageOverview() {
|
||||
<p className="text-xs text-muted-foreground">Wear</p>
|
||||
<p className="text-sm font-medium text-blue-400">{wearUsed}%</p>
|
||||
</div>
|
||||
<Progress value={wearUsed} className="h-1.5 [&>div]:bg-blue-500" />
|
||||
<Progress value={wearUsed} className="h-2 [&>div]:bg-blue-500" />
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
@@ -2737,10 +2736,10 @@ ${isNvmeDisk ? `
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
|
||||
<span style="font-size:12px;color:#475569;">Percentage Used</span>
|
||||
<span style="font-size:14px;font-weight:600;color:${getWearColorHex(nvmePercentUsed)};">${nvmePercentUsed}%</span>
|
||||
<span style="font-size:14px;font-weight:600;color:#3b82f6;">${nvmePercentUsed}%</span>
|
||||
</div>
|
||||
<div style="background:#e2e8f0;border-radius:4px;height:8px;overflow:hidden;">
|
||||
<div style="background:${getWearColorHex(nvmePercentUsed)};height:100%;width:${Math.min(nvmePercentUsed, 100)}%;border-radius:4px;"></div>
|
||||
<div style="background:#3b82f6;height:100%;width:${Math.min(nvmePercentUsed, 100)}%;border-radius:4px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2885,10 +2884,10 @@ ${!isNvmeDisk && diskType === 'SSD' ? (() => {
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
|
||||
<span style="font-size:12px;color:#475569;">Wear Level</span>
|
||||
<span style="font-size:14px;font-weight:600;color:${getWearColorHex(lifeUsed)};">${lifeUsed}%</span>
|
||||
<span style="font-size:14px;font-weight:600;color:#3b82f6;">${lifeUsed}%</span>
|
||||
</div>
|
||||
<div style="background:#e2e8f0;border-radius:4px;height:8px;overflow:hidden;">
|
||||
<div style="background:${getWearColorHex(lifeUsed)};height:100%;width:${Math.min(lifeUsed, 100)}%;border-radius:4px;"></div>
|
||||
<div style="background:#3b82f6;height:100%;width:${Math.min(lifeUsed, 100)}%;border-radius:4px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3602,12 +3601,10 @@ function HistoryTab({ disk }: { disk: DiskInfo }) {
|
||||
|
||||
if (history.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 gap-3 text-center">
|
||||
<Archive className="h-10 w-10 text-muted-foreground/30" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">No test history</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">Run a SMART test to start building history for this disk.</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
|
||||
<Archive className="h-12 w-12 mb-3 opacity-30" />
|
||||
<span className="text-sm">No test history</span>
|
||||
<span className="text-xs mt-1">Run a SMART test to start building history for this disk.</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1462,38 +1462,15 @@ class HealthMonitor:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check disk_observations for active (non-dismissed) warnings
|
||||
# This ensures disks with persistent observations appear in Health Monitor
|
||||
# even if the error is not currently in the logs
|
||||
try:
|
||||
all_observations = health_persistence.get_disk_observations()
|
||||
for obs in all_observations:
|
||||
device_name = obs.get('device_name', '').replace('/dev/', '')
|
||||
if not device_name:
|
||||
continue
|
||||
severity = (obs.get('severity') or 'warning').upper()
|
||||
# Only include if WARNING/CRITICAL and not already dismissed
|
||||
if severity in ('WARNING', 'CRITICAL') and not obs.get('dismissed'):
|
||||
# Check if there's a corresponding acknowledged error in the errors table
|
||||
# If so, skip this observation (it was dismissed via Health Monitor)
|
||||
error_key = f"disk_smart_{device_name}"
|
||||
error_record = health_persistence.get_error_by_key(error_key)
|
||||
if error_record and error_record.get('acknowledged'):
|
||||
continue # Skip - this was dismissed
|
||||
|
||||
# Add to disk_errors_by_device if not already present
|
||||
if device_name not in disk_errors_by_device:
|
||||
obs_reason = obs.get('raw_message', f'{device_name}: Disk observation recorded')
|
||||
disk_errors_by_device[device_name] = {
|
||||
'status': severity,
|
||||
'reason': obs_reason,
|
||||
'error_type': obs.get('error_type', 'disk_observation'),
|
||||
'serial': obs.get('serial', ''),
|
||||
'model': obs.get('model', ''),
|
||||
'dismissable': True,
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
# NOTE: disk_observations is the PERMANENT historical record of disk events
|
||||
# and must NOT be used as a source for Health Monitor warnings.
|
||||
# Only the `errors` table (active alerts) drives the Health Monitor view.
|
||||
# Observations are visible separately in the disk detail UI, where users
|
||||
# can review the full history and dismiss individual entries if desired.
|
||||
#
|
||||
# Previous behavior read disk_observations here and created phantom warnings
|
||||
# that persisted even after the underlying error was gone — conflating the
|
||||
# permanent history with the current health state.
|
||||
|
||||
# Add consolidated disk entries (only for disks with errors)
|
||||
for device_name, error_info in disk_errors_by_device.items():
|
||||
|
||||
@@ -343,6 +343,12 @@ class HealthPersistence:
|
||||
# re-processed every cycle, causing infinite notification loops.
|
||||
# On upgrade, clean up any stale errors that are stuck in the
|
||||
# active state from the old buggy behavior.
|
||||
#
|
||||
# IMPORTANT: Only cleans the `errors` table (health monitor state).
|
||||
# The `disk_observations` table is a PERMANENT historical record
|
||||
# and must NEVER be auto-modified on startup. Users dismiss
|
||||
# observations manually from the disk detail UI.
|
||||
#
|
||||
# Covers: disk I/O (smart_*, disk_*), VM/CT (vm_*, ct_*, vmct_*),
|
||||
# and log errors (log_*) — all journal-sourced categories.
|
||||
try:
|
||||
@@ -363,26 +369,9 @@ class HealthPersistence:
|
||||
''', (cutoff,))
|
||||
cleaned_errors = cursor.rowcount
|
||||
|
||||
# Also dismiss stale disk observations that are still active
|
||||
# but haven't been updated recently — leftovers from the
|
||||
# feedback loop bug where occurrence_count kept incrementing.
|
||||
# Detect column names for backward compatibility
|
||||
cursor.execute('PRAGMA table_info(disk_observations)')
|
||||
obs_cols = [col[1] for col in cursor.fetchall()]
|
||||
last_col = 'last_occurrence' if 'last_occurrence' in obs_cols else 'last_seen'
|
||||
|
||||
cursor.execute(f'''
|
||||
UPDATE disk_observations
|
||||
SET dismissed = 1
|
||||
WHERE dismissed = 0
|
||||
AND {last_col} < ?
|
||||
''', (cutoff,))
|
||||
cleaned_obs = cursor.rowcount
|
||||
|
||||
total_cleaned = cleaned_errors + cleaned_obs
|
||||
if total_cleaned > 0:
|
||||
if cleaned_errors > 0:
|
||||
conn.commit()
|
||||
print(f"[HealthPersistence] Startup cleanup: removed {cleaned_errors} stale error(s), dismissed {cleaned_obs} stale observation(s)")
|
||||
print(f"[HealthPersistence] Startup cleanup: removed {cleaned_errors} stale error(s) from health monitor")
|
||||
except Exception as e:
|
||||
print(f"[HealthPersistence] Startup cleanup warning: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user