Files
sencho/backend/src/helpers/configurationStatus.ts
T
Anso 83b3d932e5 feat: add Apprise as a fourth notification channel (#1644)
* 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.
2026-07-18 16:32:58 -04:00

48 lines
1.4 KiB
TypeScript

/** Agent slot on dashboard/fleet configuration responses. */
export interface AgentStatus {
configured: boolean;
enabled: boolean;
}
export type ConfigurationAgents = {
discord: AgentStatus;
slack: AgentStatus;
webhook: AgentStatus;
apprise: AgentStatus;
};
/**
* Older remotes omit `apprise`. Default the slot so mixed-version hubs do not
* treat the response as malformed or crash UI consumers.
*/
export function normalizeConfigurationAgents(agents: {
discord: AgentStatus;
slack: AgentStatus;
webhook: AgentStatus;
apprise?: AgentStatus;
}): ConfigurationAgents {
return {
discord: agents.discord,
slack: agents.slack,
webhook: agents.webhook,
apprise: agents.apprise ?? { configured: false, enabled: false },
};
}
/**
* Normalize a remote `/dashboard/configuration` body when it carries an agents
* block. Stub or partial JSON without `notifications.agents` is returned as-is
* so a successful fetch still marks the node online.
*/
export function normalizeRemoteConfigurationStatus<T extends object>(raw: T): T {
const notifications = (raw as { notifications?: { agents?: Parameters<typeof normalizeConfigurationAgents>[0] } }).notifications;
if (!notifications?.agents) return raw;
return {
...raw,
notifications: {
...notifications,
agents: normalizeConfigurationAgents(notifications.agents),
},
};
}