From 618538a8548ecd092a5c0263a9a807d1e448f7f7 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Wed, 1 Apr 2026 12:30:19 +0200 Subject: [PATCH] update health_persistence.py --- AppImage/scripts/flask_health_routes.py | 2 +- AppImage/scripts/health_persistence.py | 91 +++++++++++++++++++++---- AppImage/scripts/notification_events.py | 2 +- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/AppImage/scripts/flask_health_routes.py b/AppImage/scripts/flask_health_routes.py index 117319a1..3cc5df28 100644 --- a/AppImage/scripts/flask_health_routes.py +++ b/AppImage/scripts/flask_health_routes.py @@ -458,7 +458,7 @@ def delete_storage_exclusion(storage_name): return jsonify({'error': str(e)}), 500 -# ══════════════════════════════════════════════════════════════════════════ +# ═══════════════════════════════════════════════════════════════════════════ # NETWORK INTERFACE EXCLUSION ROUTES # ═══════════════════════════════════════════════════════════════════════════ diff --git a/AppImage/scripts/health_persistence.py b/AppImage/scripts/health_persistence.py index 8b6b60d0..2fac9f14 100644 --- a/AppImage/scripts/health_persistence.py +++ b/AppImage/scripts/health_persistence.py @@ -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_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 ── # Allows users to permanently exclude remote storages (PBS, NFS, CIFS, etc.) # from health monitoring and notifications @@ -1883,14 +1921,23 @@ class HealthPersistence: conn.close() return - # Upsert observation: if same (disk, type, signature), bump count + update last_occurrence - cursor.execute(''' + # Detect column names for backward compatibility with older schemas + 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 - (disk_registry_id, error_type, error_signature, first_occurrence, - last_occurrence, occurrence_count, raw_message, severity, dismissed) + (disk_registry_id, {type_col}, error_signature, {first_col}, + {last_col}, occurrence_count, raw_message, severity, dismissed) VALUES (?, ?, ?, ?, ?, 1, ?, ?, 0) - ON CONFLICT(disk_registry_id, error_type, error_signature) DO UPDATE SET - last_occurrence = excluded.last_occurrence, + ON CONFLICT(disk_registry_id, {type_col}, error_signature) DO UPDATE SET + {last_col} = excluded.{last_col}, occurrence_count = occurrence_count + 1, severity = CASE WHEN excluded.severity = 'critical' THEN 'critical' ELSE severity END, dismissed = 0 @@ -1915,6 +1962,14 @@ class HealthPersistence: conn = self._get_conn() 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: clean_dev = (device_name or '').replace('/dev/', '') @@ -1940,25 +1995,25 @@ class HealthPersistence: # Query observations for ALL matching registry entries placeholders = ','.join('?' * len(all_ids)) cursor.execute(f''' - SELECT o.id, o.error_type, o.error_signature, - o.first_occurrence, o.last_occurrence, + SELECT o.id, o.{type_col}, o.error_signature, + o.{first_col}, o.{last_col}, o.occurrence_count, o.raw_message, o.severity, o.dismissed, d.device_name, d.serial, d.model FROM disk_observations o JOIN disk_registry d ON o.disk_registry_id = d.id 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) else: - cursor.execute(''' - SELECT o.id, o.error_type, o.error_signature, - o.first_occurrence, o.last_occurrence, + cursor.execute(f''' + SELECT o.id, o.{type_col}, o.error_signature, + o.{first_col}, o.{last_col}, o.occurrence_count, o.raw_message, o.severity, o.dismissed, d.device_name, d.serial, d.model FROM disk_observations o JOIN disk_registry d ON o.disk_registry_id = d.id WHERE o.dismissed = 0 - ORDER BY o.last_occurrence DESC + ORDER BY o.{last_col} DESC ''') rows = cursor.fetchall() @@ -2076,10 +2131,16 @@ class HealthPersistence: cutoff = (datetime.now() - timedelta(days=max_age_days)).isoformat() conn = self._get_conn() 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 SET dismissed = 1 - WHERE dismissed = 0 AND last_occurrence < ? + WHERE dismissed = 0 AND {last_col} < ? ''', (cutoff,)) conn.commit() conn.close() diff --git a/AppImage/scripts/notification_events.py b/AppImage/scripts/notification_events.py index 755d3483..3defe2bf 100644 --- a/AppImage/scripts/notification_events.py +++ b/AppImage/scripts/notification_events.py @@ -197,7 +197,7 @@ def capture_journal_context(keywords: list, lines: int = 30, return "" -# ─── Journal Watcher (Real-time) ──────────────────────────────── +# ─── Journal Watcher (Real-time) ───────────────────────────────── class JournalWatcher: """Watches journald in real-time for critical system events.