From 785d58cb598d305883e1e9267a7c553cb192fcd9 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 15 Mar 2026 18:12:42 +0100 Subject: [PATCH] Update health monitor --- AppImage/scripts/flask_health_routes.py | 60 +++++++++++++++++++++++++ AppImage/scripts/health_monitor.py | 16 ++++++- AppImage/scripts/health_persistence.py | 41 +++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/AppImage/scripts/flask_health_routes.py b/AppImage/scripts/flask_health_routes.py index 67c6ab16..a5117d06 100644 --- a/AppImage/scripts/flask_health_routes.py +++ b/AppImage/scripts/flask_health_routes.py @@ -179,6 +179,66 @@ def get_full_health(): except Exception as e: return jsonify({'error': str(e)}), 500 +@health_bp.route('/api/health/cleanup-orphans', methods=['POST']) +def cleanup_orphan_errors(): + """ + Clean up errors for devices that no longer exist in the system. + Useful when USB drives or temporary devices are disconnected. + """ + import os + import re + try: + cleaned = [] + # Get all active disk errors + disk_errors = health_persistence.get_active_errors(category='disks') + + for err in disk_errors: + err_key = err.get('error_key', '') + details = err.get('details', {}) + if isinstance(details, str): + try: + import json as _json + details = _json.loads(details) + except Exception: + details = {} + + device = details.get('device', '') + base_disk = details.get('disk', '') + + # Try to determine the device path + dev_path = None + if base_disk: + dev_path = f'/dev/{base_disk}' + elif device: + dev_path = device if device.startswith('/dev/') else f'/dev/{device}' + elif err_key.startswith('disk_'): + # Extract device from error_key + dev_name = err_key.replace('disk_fs_', '').replace('disk_', '') + dev_name = re.sub(r'_.*$', '', dev_name) # Remove suffix + if dev_name: + dev_path = f'/dev/{dev_name}' + + if dev_path: + # Also check base disk (remove partition number) + base_path = re.sub(r'\d+$', '', dev_path) + if not os.path.exists(dev_path) and not os.path.exists(base_path): + health_persistence.resolve_error(err_key, 'Device no longer present (manual cleanup)') + cleaned.append({'error_key': err_key, 'device': dev_path}) + + # Also cleanup disk_observations for non-existent devices + try: + health_persistence.cleanup_orphan_observations() + except Exception: + pass + + return jsonify({ + 'success': True, + 'cleaned_count': len(cleaned), + 'cleaned_errors': cleaned + }) + except Exception as e: + return jsonify({'error': str(e)}), 500 + @health_bp.route('/api/health/pending-notifications', methods=['GET']) def get_pending_notifications(): """ diff --git a/AppImage/scripts/health_monitor.py b/AppImage/scripts/health_monitor.py index 5ec041ac..3f7e6937 100644 --- a/AppImage/scripts/health_monitor.py +++ b/AppImage/scripts/health_monitor.py @@ -2093,7 +2093,21 @@ class HealthMonitor: # Check if the device still exists. If not, auto-resolve # the error -- it was likely a disconnected USB/temp device. dev_path = f'/dev/{base_disk}' if base_disk else device - if not os.path.exists(dev_path): + + # Also extract base disk from partition (e.g., sdb1 -> sdb) + if not base_disk and device: + # Remove /dev/ prefix and partition number + dev_name = device.replace('/dev/', '') + base_disk = re.sub(r'\d+$', '', dev_name) # sdb1 -> sdb + if base_disk: + dev_path = f'/dev/{base_disk}' + + # Check both the specific device and the base disk + device_exists = os.path.exists(dev_path) + if not device_exists and device and device != dev_path: + device_exists = os.path.exists(device) + + if not device_exists: health_persistence.resolve_error( err_key, 'Device no longer present in system') continue diff --git a/AppImage/scripts/health_persistence.py b/AppImage/scripts/health_persistence.py index aa352371..f0d23c77 100644 --- a/AppImage/scripts/health_persistence.py +++ b/AppImage/scripts/health_persistence.py @@ -1765,6 +1765,47 @@ class HealthPersistence: except Exception as e: print(f"[HealthPersistence] Error marking removed disks: {e}") + def cleanup_orphan_observations(self): + """ + Dismiss observations for devices that no longer exist in /dev/. + Useful for cleaning up after USB drives or temporary devices are disconnected. + """ + import os + import re + try: + conn = self._get_conn() + cursor = conn.cursor() + + # Get all active (non-dismissed) observations + cursor.execute(''' + SELECT id, device_name, serial FROM disk_observations + WHERE dismissed = 0 + ''') + observations = cursor.fetchall() + + dismissed_count = 0 + for obs_id, device_name, serial in observations: + # Check if device exists + dev_path = f'/dev/{device_name}' + # Also check base device (remove partition number) + base_dev = re.sub(r'\d+$', '', device_name) + base_path = f'/dev/{base_dev}' + + if not os.path.exists(dev_path) and not os.path.exists(base_path): + cursor.execute(''' + UPDATE disk_observations SET dismissed = 1 + WHERE id = ? + ''', (obs_id,)) + dismissed_count += 1 + + conn.commit() + conn.close() + print(f"[HealthPersistence] Cleaned up {dismissed_count} orphan observations") + return dismissed_count + except Exception as e: + print(f"[HealthPersistence] Error cleaning orphan observations: {e}") + return 0 + # Global instance health_persistence = HealthPersistence()