diff --git a/AppImage/scripts/flask_notification_routes.py b/AppImage/scripts/flask_notification_routes.py index c0a79c63..64ab97cb 100644 --- a/AppImage/scripts/flask_notification_routes.py +++ b/AppImage/scripts/flask_notification_routes.py @@ -686,6 +686,16 @@ def proxmox_webhook(): else: return _reject(400, 'empty_payload', 400) + # DEBUG: Log full webhook payload to file for analysis + import json as _json + try: + with open('/tmp/proxmenux_webhook_payload.log', 'a') as _f: + _f.write(f"\n{'='*60}\n{time.strftime('%Y-%m-%d %H:%M:%S')}\n") + _f.write(_json.dumps(payload, indent=2, default=str, ensure_ascii=False)) + _f.write('\n') + except Exception: + pass + result = notification_manager.process_webhook(payload) # Always return 200 to PVE -- a non-200 makes PVE report the webhook as broken. # The 'accepted' field in the JSON body indicates actual processing status. diff --git a/AppImage/scripts/notification_events.py b/AppImage/scripts/notification_events.py index 98037d72..04aa22e7 100644 --- a/AppImage/scripts/notification_events.py +++ b/AppImage/scripts/notification_events.py @@ -941,9 +941,6 @@ class ProxmoxHookWatcher: def __init__(self, event_queue: Queue): self._queue = event_queue self._hostname = _hostname() - # Shared dict: TaskWatcher._webhook_delivered points here. - # Records (event_type:entity_id -> timestamp) for cross-source dedup. - self._delivered: Dict[str, float] = {} def process_webhook(self, payload: dict) -> dict: """Process an incoming Proxmox webhook payload. @@ -1026,15 +1023,18 @@ class ProxmoxHookWatcher: if dur_m: data['duration'] = dur_m.group(1).strip() - # Record in the shared delivered dict so TaskWatcher can dedup. - # TaskWatcher._webhook_delivered points at self._delivered (same object). - self._delivered[f"{event_type}:{entity_id}"] = time.time() - # Prune old entries periodically + # Record this event for cross-source dedup. + # TaskWatcher checks this dict before emitting backup/replication + # events so it yields to the richer webhook data. + import time + if not hasattr(self, '_delivered'): + self._delivered = {} + dedup_key = f"{event_type}:{entity_id}" + self._delivered[dedup_key] = time.time() + # Cleanup old entries if len(self._delivered) > 200: cutoff = time.time() - 300 - to_del = [k for k, v in self._delivered.items() if v < cutoff] - for k in to_del: - del self._delivered[k] + self._delivered = {k: v for k, v in self._delivered.items() if v > cutoff} event = NotificationEvent( event_type=event_type, diff --git a/AppImage/scripts/notification_manager.py b/AppImage/scripts/notification_manager.py index 772ce5c5..80696791 100644 --- a/AppImage/scripts/notification_manager.py +++ b/AppImage/scripts/notification_manager.py @@ -419,14 +419,6 @@ class NotificationManager: self._task_watcher = TaskWatcher(self._event_queue) self._polling_collector = PollingCollector(self._event_queue) - # Create hook_watcher eagerly so we can wire the cross-source dedup. - # TaskWatcher._webhook_delivered points at ProxmoxHookWatcher._delivered - # so TaskWatcher can skip backup/replication events the webhook already - # delivered with richer data (full logs, sizes, durations). - if not self._hook_watcher: - self._hook_watcher = ProxmoxHookWatcher(self._event_queue) - self._task_watcher._webhook_delivered = self._hook_watcher._delivered - self._journal_watcher.start() self._task_watcher.start() self._polling_collector.start() @@ -874,6 +866,10 @@ class NotificationManager: """Process incoming Proxmox webhook. Delegates to ProxmoxHookWatcher.""" if not self._hook_watcher: self._hook_watcher = ProxmoxHookWatcher(self._event_queue) + # Share the webhook's delivery record with TaskWatcher + # so tasks can yield to richer webhook data for backup/replication. + if self._task_watcher: + self._task_watcher._webhook_delivered = self._hook_watcher._delivered return self._hook_watcher.process_webhook(payload) def get_webhook_secret(self) -> str: diff --git a/AppImage/scripts/notification_templates.py b/AppImage/scripts/notification_templates.py index 5d1047c4..6b1c238c 100644 --- a/AppImage/scripts/notification_templates.py +++ b/AppImage/scripts/notification_templates.py @@ -302,12 +302,6 @@ TEMPLATES = { 'group': 'system', 'default_enabled': True, }, - 'system_mail': { - 'title': '{hostname}: System mail notification', - 'body': '{reason}', - 'group': 'system', - 'default_enabled': True, - }, 'service_fail': { 'title': '{hostname}: Service failed - {service_name}', 'body': 'Service {service_name} has failed.\n{reason}',