* feat: add Apprise as a fourth notification channel
Support keyed and stateless Apprise endpoints with secret-safe public DTOs, fail-closed malformed config, and mode-specific Settings UI. Docs and screenshots updated for four-channel Channels and routing.
* fix: harden Apprise secrets at rest and preserve-on-write saves
Encrypt Apprise endpoint and config with CryptoService so a downgrade cannot leak via SELECT *. Align channel and routing saves so blank destination fields omit config on same-mode URL edits, enforce keyed notify IDs, and keep secrets_redacted truthful.
* fix: harden Apprise route type changes and mixed-version config UI
Require a raw channel_url when switching notification-route types so ciphertext cannot strand under Discord/Slack/webhook. Default missing remote apprise status, replace Channels state on node switch, and exercise the production config-column migrator.
* fix: tolerate stub fleet configuration payloads without agents
Normalize remote Apprise agent status only when notifications.agents is present so successful Pilot/stub fetches stay online instead of throwing into the offline catch path.
* fix: correct TypeScript in configuration normalize tests
* fix: ignore stale Channels agent bodies after node switch
Compare the active node after response JSON parsing so a slow body
cannot overwrite the newly selected node's channel state.
* fix: isolate corrupt Apprise crypto and keep keyed Tags visible
Decrypt failures on one Apprise row no longer 500 agent/route lists or
suppress sibling channel dispatch. Treat public /notify/<redacted> as keyed
so Tags remain editable after reload.
* fix(notifications): harden alert dispatch crash-safety and redact webhook secrets in logs
Make NotificationService.dispatchAlert never reject so the many
fire-and-forget callers (monitors, event streams, policy and image-update
paths) cannot trigger an unhandledRejection on an unhealthy database. The
whole dispatch body now sits inside a guard covering node resolution, the
history insert, channel-table reads, and the WebSocket broadcast; a
failure logs and drops the notification instead of crashing the process.
An inner guard still splits the write-success and write-failure metrics.
Also:
- Redact webhook URLs in diagnostic logs via a new maskWebhookUrl helper;
Discord/Slack/custom webhook URLs embed their token in the path, so only
the origin is safe to emit.
- Add error logging to four notification-history route handlers that
previously swallowed database errors silently before returning 500.
- Snapshot the subscriber set before broadcasting so a close/error handler
firing mid-send cannot mutate the set during iteration.
- Sanitize the admin-supplied route name in dispatch log lines.
Adds tests for dispatch crash-safety (write failure, post-write routing
failure, broadcast send failure), the success-path write metric, webhook
URL masking, and history-route error logging.
* fix(notifications): sanitize route name and patterns in create logs
Apply sanitizeForLog to the admin-supplied route name and stack patterns
in the route-creation log lines, matching the dispatch-site sanitization
and closing the remaining log-injection path on this feature.
Also extend tests: userinfo-stripping in maskWebhookUrl, subscriber-set
snapshot behavior under an unsubscribe-during-send, and error logging on
the mark-read, delete-one, and clear-all notification-history handlers.