Update notification service

This commit is contained in:
MacRimi
2026-03-27 20:42:03 +01:00
parent 976f23a90e
commit 4cc1147579
4 changed files with 56 additions and 54 deletions

View File

@@ -55,9 +55,9 @@ class HealthPersistence:
def _get_conn(self) -> sqlite3.Connection: def _get_conn(self) -> sqlite3.Connection:
"""Get a SQLite connection with timeout and WAL mode for safe concurrency.""" """Get a SQLite connection with timeout and WAL mode for safe concurrency."""
conn = sqlite3.connect(str(self.db_path), timeout=10) conn = sqlite3.connect(str(self.db_path), timeout=30)
conn.execute('PRAGMA journal_mode=WAL') conn.execute('PRAGMA journal_mode=WAL')
conn.execute('PRAGMA busy_timeout=5000') conn.execute('PRAGMA busy_timeout=10000')
return conn return conn
@contextmanager @contextmanager
@@ -1327,7 +1327,7 @@ class HealthPersistence:
# ──────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────
# Disk Observations API # Disk Observations API
# ──────────────────────<EFBFBD><EFBFBD><EFBFBD>───────────────────────────────────────── # ───────────────────────────────────────────────────────────────
def register_disk(self, device_name: str, serial: Optional[str] = None, def register_disk(self, device_name: str, serial: Optional[str] = None,
model: Optional[str] = None, size_bytes: Optional[int] = None): model: Optional[str] = None, size_bytes: Optional[int] = None):
@@ -1340,56 +1340,57 @@ class HealthPersistence:
under 'ata8' and we now know the real block device is 'sdh' with under 'ata8' and we now know the real block device is 'sdh' with
serial 'WX72...', update the old entry so observations are linked. serial 'WX72...', update the old entry so observations are linked.
""" """
now = datetime.now().isoformat() with self._db_lock:
try: now = datetime.now().isoformat()
conn = self._get_conn() try:
cursor = conn.cursor() conn = self._get_conn()
cursor = conn.cursor()
# Consolidate: if serial is known and an old entry exists with
# a different device_name (e.g. 'ata8' instead of 'sdh'), # Consolidate: if serial is known and an old entry exists with
# update that entry's device_name so observations carry over. # a different device_name (e.g. 'ata8' instead of 'sdh'),
if serial: # update that entry's device_name so observations carry over.
if serial:
cursor.execute('''
SELECT id, device_name FROM disk_registry
WHERE serial = ? AND serial != '' AND device_name != ?
''', (serial, device_name))
old_rows = cursor.fetchall()
for old_id, old_dev in old_rows:
# Only consolidate ATA names -> block device names
if old_dev.startswith('ata') and not device_name.startswith('ata'):
# Check if target (device_name, serial) already exists
cursor.execute(
'SELECT id FROM disk_registry WHERE device_name = ? AND serial = ?',
(device_name, serial))
existing = cursor.fetchone()
if existing:
# Merge: move observations from old -> existing, then delete old
cursor.execute(
'UPDATE disk_observations SET disk_registry_id = ? WHERE disk_registry_id = ?',
(existing[0], old_id))
cursor.execute('DELETE FROM disk_registry WHERE id = ?', (old_id,))
else:
# Rename the old entry to the real block device name
cursor.execute(
'UPDATE disk_registry SET device_name = ?, model = COALESCE(?, model), '
'size_bytes = COALESCE(?, size_bytes), last_seen = ?, removed = 0 '
'WHERE id = ?',
(device_name, model, size_bytes, now, old_id))
cursor.execute(''' cursor.execute('''
SELECT id, device_name FROM disk_registry INSERT INTO disk_registry (device_name, serial, model, size_bytes, first_seen, last_seen, removed)
WHERE serial = ? AND serial != '' AND device_name != ? VALUES (?, ?, ?, ?, ?, ?, 0)
''', (serial, device_name)) ON CONFLICT(device_name, serial) DO UPDATE SET
old_rows = cursor.fetchall() model = COALESCE(excluded.model, model),
for old_id, old_dev in old_rows: size_bytes = COALESCE(excluded.size_bytes, size_bytes),
# Only consolidate ATA names -> block device names last_seen = excluded.last_seen,
if old_dev.startswith('ata') and not device_name.startswith('ata'): removed = 0
# Check if target (device_name, serial) already exists ''', (device_name, serial or '', model, size_bytes, now, now))
cursor.execute(
'SELECT id FROM disk_registry WHERE device_name = ? AND serial = ?', conn.commit()
(device_name, serial)) conn.close()
existing = cursor.fetchone() except Exception as e:
if existing: print(f"[HealthPersistence] Error registering disk {device_name}: {e}")
# Merge: move observations from old -> existing, then delete old
cursor.execute(
'UPDATE disk_observations SET disk_registry_id = ? WHERE disk_registry_id = ?',
(existing[0], old_id))
cursor.execute('DELETE FROM disk_registry WHERE id = ?', (old_id,))
else:
# Rename the old entry to the real block device name
cursor.execute(
'UPDATE disk_registry SET device_name = ?, model = COALESCE(?, model), '
'size_bytes = COALESCE(?, size_bytes), last_seen = ?, removed = 0 '
'WHERE id = ?',
(device_name, model, size_bytes, now, old_id))
cursor.execute('''
INSERT INTO disk_registry (device_name, serial, model, size_bytes, first_seen, last_seen, removed)
VALUES (?, ?, ?, ?, ?, ?, 0)
ON CONFLICT(device_name, serial) DO UPDATE SET
model = COALESCE(excluded.model, model),
size_bytes = COALESCE(excluded.size_bytes, size_bytes),
last_seen = excluded.last_seen,
removed = 0
''', (device_name, serial or '', model, size_bytes, now, now))
conn.commit()
conn.close()
except Exception as e:
print(f"[HealthPersistence] Error registering disk {device_name}: {e}")
def _get_disk_registry_id(self, cursor, device_name: str, def _get_disk_registry_id(self, cursor, device_name: str,
serial: Optional[str] = None, serial: Optional[str] = None,

View File

@@ -251,7 +251,7 @@ class TelegramChannel(NotificationChannel):
.replace('>', '&gt;')) .replace('>', '&gt;'))
# ─── Gotify ───────────────────────────────────────────────────── # ─── Gotify ───────────────────────────────────<EFBFBD><EFBFBD>──────────────────
class GotifyChannel(NotificationChannel): class GotifyChannel(NotificationChannel):
"""Gotify push notification channel with priority mapping.""" """Gotify push notification channel with priority mapping."""

View File

@@ -197,7 +197,7 @@ def capture_journal_context(keywords: list, lines: int = 30,
return "" return ""
# ─── Journal Watcher (Real-time) ──────────────────────────────── # ─── Journal Watcher (Real-time) ───────────────<EFBFBD><EFBFBD>─────────────────
class JournalWatcher: class JournalWatcher:
"""Watches journald in real-time for critical system events. """Watches journald in real-time for critical system events.

View File

@@ -1615,6 +1615,7 @@ class NotificationManager:
'ai_openai_base_url': self._config.get('ai_openai_base_url', ''), 'ai_openai_base_url': self._config.get('ai_openai_base_url', ''),
'ai_prompt_mode': self._config.get('ai_prompt_mode', 'default'), 'ai_prompt_mode': self._config.get('ai_prompt_mode', 'default'),
'ai_custom_prompt': self._config.get('ai_custom_prompt', ''), 'ai_custom_prompt': self._config.get('ai_custom_prompt', ''),
'ai_allow_suggestions': self._config.get('ai_allow_suggestions', 'false') == 'true',
'ai_detail_levels': ai_detail_levels, 'ai_detail_levels': ai_detail_levels,
'hostname': self._config.get('hostname', ''), 'hostname': self._config.get('hostname', ''),
'webhook_secret': self._config.get('webhook_secret', ''), 'webhook_secret': self._config.get('webhook_secret', ''),