From 80afa789e799fb871423434e748296f619550e05 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 30 Mar 2026 22:26:20 +0200 Subject: [PATCH] Update notification service --- AppImage/scripts/health_monitor.py | 2 +- AppImage/scripts/health_persistence.py | 13 +++++--- AppImage/scripts/notification_events.py | 8 ++--- AppImage/scripts/notification_manager.py | 3 +- AppImage/scripts/notification_templates.py | 36 +++++++++++++--------- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/AppImage/scripts/health_monitor.py b/AppImage/scripts/health_monitor.py index bf48aeee..e90b516e 100644 --- a/AppImage/scripts/health_monitor.py +++ b/AppImage/scripts/health_monitor.py @@ -3148,7 +3148,7 @@ class HealthMonitor: if inode: inode_hint = 'root directory' if inode == '2' else f'inode #{inode}' reason += f'\nAffected: {inode_hint}' - reason += f'\nAction: Run "fsck /dev/{device}" (unmount first)' + # Note: Action/recommendations are provided by AI when AI Suggestions is enabled return reason # Out of memory diff --git a/AppImage/scripts/health_persistence.py b/AppImage/scripts/health_persistence.py index 56a2a1da..4bed6401 100644 --- a/AppImage/scripts/health_persistence.py +++ b/AppImage/scripts/health_persistence.py @@ -2154,10 +2154,12 @@ class HealthPersistence: conn = self._get_conn() cursor = conn.cursor() - # Get all active (non-dismissed) observations + # Get all active (non-dismissed) observations with device info from disk_registry cursor.execute(''' - SELECT id, device_name, serial FROM disk_observations - WHERE dismissed = 0 + SELECT do.id, dr.device_name, dr.serial + FROM disk_observations do + JOIN disk_registry dr ON do.disk_registry_id = dr.id + WHERE do.dismissed = 0 ''') observations = cursor.fetchall() @@ -2171,14 +2173,15 @@ class HealthPersistence: if not os.path.exists(dev_path) and not os.path.exists(base_path): cursor.execute(''' - UPDATE disk_observations SET dismissed = 1 + 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") + if dismissed_count > 0: + print(f"[HealthPersistence] Cleaned up {dismissed_count} orphan observations") return dismissed_count except Exception as e: print(f"[HealthPersistence] Error cleaning orphan observations: {e}") diff --git a/AppImage/scripts/notification_events.py b/AppImage/scripts/notification_events.py index 622589e4..a7929d19 100644 --- a/AppImage/scripts/notification_events.py +++ b/AppImage/scripts/notification_events.py @@ -609,12 +609,10 @@ class JournalWatcher: if inode: inode_hint = 'root directory' if inode == '2' else f'inode #{inode}' parts.append(f'Affected: {inode_hint}') - if smart_health == 'FAILED': - parts.append(f'Action: Disk is failing. Run "fsck /dev/{device}" (unmount first) and plan replacement') - elif smart_health == 'PASSED': + # Note: Specific recommendations are provided by AI when AI Suggestions is enabled + # Only include SMART status note (not an action) + if smart_health == 'PASSED': parts.append(f'Note: SMART reports disk is healthy. This may be a transient error.') - else: - parts.append(f'Action: Run "fsck /dev/{device}" (unmount first) and check "smartctl -a /dev/{base_dev}"') enriched = '\n'.join(parts) else: diff --git a/AppImage/scripts/notification_manager.py b/AppImage/scripts/notification_manager.py index 1b753b05..e89cba42 100644 --- a/AppImage/scripts/notification_manager.py +++ b/AppImage/scripts/notification_manager.py @@ -775,6 +775,7 @@ class NotificationManager: default_detail = 'detailed' if ch_name == 'email' else 'standard' detail_level = self._config.get(detail_level_key, default_detail) + # Rich format (emojis) is a user preference per channel rich_key = f'{ch_name}.rich_format' use_rich_format = self._config.get(rich_key, 'false') == 'true' @@ -1857,7 +1858,7 @@ class NotificationManager: return {'checked': False, 'migrated': False, 'message': str(e)} -# ─── Singleton (for server mode) ───────────────────────────────── +# ─── Singleton (for server mode) ────────────��──────────────────── notification_manager = NotificationManager() diff --git a/AppImage/scripts/notification_templates.py b/AppImage/scripts/notification_templates.py index c78e5719..5119ae91 100644 --- a/AppImage/scripts/notification_templates.py +++ b/AppImage/scripts/notification_templates.py @@ -398,7 +398,7 @@ def _format_system_startup(data: Dict[str, Any]) -> Tuple[str, str]: return title, body -# ─── Severity Icons ────���───────────────────────────────────────── +# ─── Severity Icons ────────────────────────────────────────────── SEVERITY_ICONS = { 'CRITICAL': '\U0001F534', @@ -1165,12 +1165,12 @@ def get_default_enabled_events() -> Dict[str, bool]: # Category-level header icons CATEGORY_EMOJI = { - 'vm_ct': '\U0001F5A5\uFE0F', # desktop computer + 'vm_ct': '\U0001F5A5\uFE0F', # desktop computer 'backup': '\U0001F4BE', # floppy disk (backup) 'resources': '\U0001F4CA', # bar chart 'storage': '\U0001F4BD', # minidisc / hard disk 'network': '\U0001F310', # globe with meridians - 'security': '\U0001F6E1\uFE0F', # shield + 'security': '\U0001F6E1\uFE0F', # shield 'cluster': '\U0001F517', # chain link 'services': '\u2699\uFE0F', # gear 'health': '\U0001FA7A', # stethoscope @@ -1181,9 +1181,9 @@ CATEGORY_EMOJI = { # Event-specific title icons (override category default when present) EVENT_EMOJI = { # VM / CT - 'vm_start': '\u25B6\uFE0F', # play button - 'vm_stop': '\u23F9\uFE0F', # stop button - 'vm_shutdown': '\u23CF\uFE0F', # eject + 'vm_start': '\u25B6\uFE0F', # play button + 'vm_stop': '\u23F9\uFE0F', # stop button + 'vm_shutdown': '\u23CF\uFE0F', # eject 'vm_fail': '\U0001F4A5', # collision (crash) 'vm_restart': '\U0001F504', # cycle 'ct_start': '\u25B6\uFE0F', @@ -1197,7 +1197,7 @@ EVENT_EMOJI = { 'replication_fail': '\u274C', 'replication_complete': '\u2705', # Backups - 'backup_start': '\U0001F4BE\U0001F680', # 💾🚀 floppy + rocket + 'backup_start': '\U0001F4BE\U0001F680', # 💾🚀 floppy + rocket 'backup_complete': '\U0001F4BE\u2705', # 💾✅ floppy + check 'backup_fail': '\U0001F4BE\u274C', # 💾❌ floppy + cross 'snapshot_complete': '\U0001F4F8', # camera with flash @@ -1310,22 +1310,30 @@ def enrich_with_emojis(event_type: str, title: str, body: str, # This helps when everything comes concatenated preprocessed = body - # Patterns that should start on a new line (with emoji prefix) + # First, clean up duplicated device references like "/dev/sda: /dev/sda: /dev/sda [SAT]" + # Convert to just "/dev/sda [SAT]" or "/dev/sda:" + preprocessed = re.sub(r'(/dev/\w+):\s*\1:\s*\1', r'\1', preprocessed) + preprocessed = re.sub(r'(/dev/\w+):\s*\1', r'\1', preprocessed) + + # Patterns that should start on a new line line_break_patterns = [ (r';\s*/dev/', '\n/dev/'), # ;/dev/sdb -> newline + /dev/sdb - (r'Device:', '\nDevice:'), # Device: on new line - (r'Error:', '\nError:'), # Error: on new line - (r'Action:', '\nAction:'), # Action: on new line - (r'Affected:', '\nAffected:'), # Affected: on new line + (r'(?<=[a-z])\s+/dev/', '\n/dev/'), # "sectors /dev/sdb" -> newline before /dev/ + (r'(?<=\))\s*/dev/', '\n/dev/'), # ") /dev/sdb" -> newline before /dev/ + (r'\bDevice:', '\nDevice:'), # Device: on new line + (r'\bError:', '\nError:'), # Error: on new line + (r'\bAction:', '\nAction:'), # Action: on new line + (r'\bAffected:', '\nAffected:'), # Affected: on new line (r'Device not currently', '\nDevice not currently'), # Note about missing device - (r'SMART:', '\nSMART:'), # SMART status + (r'\bSMART:', '\nSMART:'), # SMART status ] for pattern, replacement in line_break_patterns: preprocessed = re.sub(pattern, replacement, preprocessed) - # Clean up multiple newlines + # Clean up multiple newlines and leading newlines preprocessed = re.sub(r'\n{3,}', '\n\n', preprocessed) + preprocessed = re.sub(r'^\n+', '', preprocessed) preprocessed = preprocessed.strip() # ── Extended emoji mappings for health/disk messages ──