mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-25 08:56:21 +00:00
update health_persistence.py
This commit is contained in:
@@ -458,7 +458,7 @@ def delete_storage_exclusion(storage_name):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# NETWORK INTERFACE EXCLUSION ROUTES
|
# NETWORK INTERFACE EXCLUSION ROUTES
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,44 @@ class HealthPersistence:
|
|||||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_obs_disk ON disk_observations(disk_registry_id)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_obs_disk ON disk_observations(disk_registry_id)')
|
||||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_obs_dismissed ON disk_observations(dismissed)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_obs_dismissed ON disk_observations(dismissed)')
|
||||||
|
|
||||||
|
# Migration: ensure disk_observations has all required columns
|
||||||
|
# Some older DBs may have different column names or missing columns
|
||||||
|
cursor.execute('PRAGMA table_info(disk_observations)')
|
||||||
|
obs_columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
|
||||||
|
# Add missing columns if needed (SQLite doesn't support RENAME COLUMN in older versions)
|
||||||
|
if 'error_type' not in obs_columns and 'observation_type' in obs_columns:
|
||||||
|
# Old schema had observation_type, but we'll work with it as-is
|
||||||
|
pass # The code should handle both column names
|
||||||
|
|
||||||
|
if 'first_occurrence' not in obs_columns and 'first_seen' in obs_columns:
|
||||||
|
# Old schema had first_seen/last_seen instead of first_occurrence/last_occurrence
|
||||||
|
pass # The code should handle both column names
|
||||||
|
|
||||||
|
if 'occurrence_count' not in obs_columns:
|
||||||
|
try:
|
||||||
|
cursor.execute('ALTER TABLE disk_observations ADD COLUMN occurrence_count INTEGER DEFAULT 1')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'raw_message' not in obs_columns:
|
||||||
|
try:
|
||||||
|
cursor.execute('ALTER TABLE disk_observations ADD COLUMN raw_message TEXT')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'severity' not in obs_columns:
|
||||||
|
try:
|
||||||
|
cursor.execute('ALTER TABLE disk_observations ADD COLUMN severity TEXT DEFAULT "warning"')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'dismissed' not in obs_columns:
|
||||||
|
try:
|
||||||
|
cursor.execute('ALTER TABLE disk_observations ADD COLUMN dismissed INTEGER DEFAULT 0')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# ── Remote Storage Exclusions System ──
|
# ── Remote Storage Exclusions System ──
|
||||||
# Allows users to permanently exclude remote storages (PBS, NFS, CIFS, etc.)
|
# Allows users to permanently exclude remote storages (PBS, NFS, CIFS, etc.)
|
||||||
# from health monitoring and notifications
|
# from health monitoring and notifications
|
||||||
@@ -1883,14 +1921,23 @@ class HealthPersistence:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Upsert observation: if same (disk, type, signature), bump count + update last_occurrence
|
# Detect column names for backward compatibility with older schemas
|
||||||
cursor.execute('''
|
cursor.execute('PRAGMA table_info(disk_observations)')
|
||||||
|
columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
|
||||||
|
# Map to actual column names (old vs new schema)
|
||||||
|
type_col = 'error_type' if 'error_type' in columns else 'observation_type'
|
||||||
|
first_col = 'first_occurrence' if 'first_occurrence' in columns else 'first_seen'
|
||||||
|
last_col = 'last_occurrence' if 'last_occurrence' in columns else 'last_seen'
|
||||||
|
|
||||||
|
# Upsert observation: if same (disk, type, signature), bump count + update last timestamp
|
||||||
|
cursor.execute(f'''
|
||||||
INSERT INTO disk_observations
|
INSERT INTO disk_observations
|
||||||
(disk_registry_id, error_type, error_signature, first_occurrence,
|
(disk_registry_id, {type_col}, error_signature, {first_col},
|
||||||
last_occurrence, occurrence_count, raw_message, severity, dismissed)
|
{last_col}, occurrence_count, raw_message, severity, dismissed)
|
||||||
VALUES (?, ?, ?, ?, ?, 1, ?, ?, 0)
|
VALUES (?, ?, ?, ?, ?, 1, ?, ?, 0)
|
||||||
ON CONFLICT(disk_registry_id, error_type, error_signature) DO UPDATE SET
|
ON CONFLICT(disk_registry_id, {type_col}, error_signature) DO UPDATE SET
|
||||||
last_occurrence = excluded.last_occurrence,
|
{last_col} = excluded.{last_col},
|
||||||
occurrence_count = occurrence_count + 1,
|
occurrence_count = occurrence_count + 1,
|
||||||
severity = CASE WHEN excluded.severity = 'critical' THEN 'critical' ELSE severity END,
|
severity = CASE WHEN excluded.severity = 'critical' THEN 'critical' ELSE severity END,
|
||||||
dismissed = 0
|
dismissed = 0
|
||||||
@@ -1915,6 +1962,14 @@ class HealthPersistence:
|
|||||||
conn = self._get_conn()
|
conn = self._get_conn()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Detect column names for backward compatibility with older schemas
|
||||||
|
cursor.execute('PRAGMA table_info(disk_observations)')
|
||||||
|
columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
|
||||||
|
type_col = 'error_type' if 'error_type' in columns else 'observation_type'
|
||||||
|
first_col = 'first_occurrence' if 'first_occurrence' in columns else 'first_seen'
|
||||||
|
last_col = 'last_occurrence' if 'last_occurrence' in columns else 'last_seen'
|
||||||
|
|
||||||
if device_name or serial:
|
if device_name or serial:
|
||||||
clean_dev = (device_name or '').replace('/dev/', '')
|
clean_dev = (device_name or '').replace('/dev/', '')
|
||||||
|
|
||||||
@@ -1940,25 +1995,25 @@ class HealthPersistence:
|
|||||||
# Query observations for ALL matching registry entries
|
# Query observations for ALL matching registry entries
|
||||||
placeholders = ','.join('?' * len(all_ids))
|
placeholders = ','.join('?' * len(all_ids))
|
||||||
cursor.execute(f'''
|
cursor.execute(f'''
|
||||||
SELECT o.id, o.error_type, o.error_signature,
|
SELECT o.id, o.{type_col}, o.error_signature,
|
||||||
o.first_occurrence, o.last_occurrence,
|
o.{first_col}, o.{last_col},
|
||||||
o.occurrence_count, o.raw_message, o.severity, o.dismissed,
|
o.occurrence_count, o.raw_message, o.severity, o.dismissed,
|
||||||
d.device_name, d.serial, d.model
|
d.device_name, d.serial, d.model
|
||||||
FROM disk_observations o
|
FROM disk_observations o
|
||||||
JOIN disk_registry d ON o.disk_registry_id = d.id
|
JOIN disk_registry d ON o.disk_registry_id = d.id
|
||||||
WHERE o.disk_registry_id IN ({placeholders}) AND o.dismissed = 0
|
WHERE o.disk_registry_id IN ({placeholders}) AND o.dismissed = 0
|
||||||
ORDER BY o.last_occurrence DESC
|
ORDER BY o.{last_col} DESC
|
||||||
''', all_ids)
|
''', all_ids)
|
||||||
else:
|
else:
|
||||||
cursor.execute('''
|
cursor.execute(f'''
|
||||||
SELECT o.id, o.error_type, o.error_signature,
|
SELECT o.id, o.{type_col}, o.error_signature,
|
||||||
o.first_occurrence, o.last_occurrence,
|
o.{first_col}, o.{last_col},
|
||||||
o.occurrence_count, o.raw_message, o.severity, o.dismissed,
|
o.occurrence_count, o.raw_message, o.severity, o.dismissed,
|
||||||
d.device_name, d.serial, d.model
|
d.device_name, d.serial, d.model
|
||||||
FROM disk_observations o
|
FROM disk_observations o
|
||||||
JOIN disk_registry d ON o.disk_registry_id = d.id
|
JOIN disk_registry d ON o.disk_registry_id = d.id
|
||||||
WHERE o.dismissed = 0
|
WHERE o.dismissed = 0
|
||||||
ORDER BY o.last_occurrence DESC
|
ORDER BY o.{last_col} DESC
|
||||||
''')
|
''')
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
@@ -2076,10 +2131,16 @@ class HealthPersistence:
|
|||||||
cutoff = (datetime.now() - timedelta(days=max_age_days)).isoformat()
|
cutoff = (datetime.now() - timedelta(days=max_age_days)).isoformat()
|
||||||
conn = self._get_conn()
|
conn = self._get_conn()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
|
||||||
|
# Detect column name for backward compatibility
|
||||||
|
cursor.execute('PRAGMA table_info(disk_observations)')
|
||||||
|
columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
last_col = 'last_occurrence' if 'last_occurrence' in columns else 'last_seen'
|
||||||
|
|
||||||
|
cursor.execute(f'''
|
||||||
UPDATE disk_observations
|
UPDATE disk_observations
|
||||||
SET dismissed = 1
|
SET dismissed = 1
|
||||||
WHERE dismissed = 0 AND last_occurrence < ?
|
WHERE dismissed = 0 AND {last_col} < ?
|
||||||
''', (cutoff,))
|
''', (cutoff,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ def capture_journal_context(keywords: list, lines: int = 30,
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
# ─── Journal Watcher (Real-time) ────────────────────────────────
|
# ─── Journal Watcher (Real-time) ─────────────────────────────────
|
||||||
|
|
||||||
class JournalWatcher:
|
class JournalWatcher:
|
||||||
"""Watches journald in real-time for critical system events.
|
"""Watches journald in real-time for critical system events.
|
||||||
|
|||||||
Reference in New Issue
Block a user