feat(notifications): customizable per-channel JSON payload templates (#1805)

Add an optional Edit Payload editor to every notification channel
(Discord, Slack, Webhook, Apprise, ntfy). A saved template replaces the
built-in payload for that channel, with {{level}}, {{message}},
{{category}}, {{timestamp}}, {{stack_name}}, and {{actor}} substituted as
JSON-escaped values (variables may stand alone or be mixed into strings).
Templates are validated on save: known variables only, no unterminated
tokens, valid JSON after substitution, 8000 characters max. Apprise keeps
urls/tag managed by the channel fields and merges them server-side at
dispatch. ntfy publishes JSON instead of plain text when templated. Test
dispatch uses the editor template.
This commit is contained in:
Anso
2026-08-11 10:25:29 -04:00
committed by GitHub
parent 578ce7684d
commit 866d784316
16 changed files with 1508 additions and 33 deletions
+7 -4
View File
@@ -30,6 +30,8 @@ export interface Agent {
url: string;
enabled: boolean;
config?: string | null;
/** Optional user-authored JSON payload template; null = built-in payload. */
payload_template?: string | null;
}
export interface GlobalSetting {
@@ -2436,6 +2438,7 @@ export class DatabaseService {
private migrateNotificationChannelConfig(): void {
this.tryAddColumn('agents', 'config', 'TEXT NULL');
this.tryAddColumn('agents', 'payload_template', 'TEXT NULL');
this.tryAddColumn('notification_routes', 'config', 'TEXT NULL');
}
@@ -2995,11 +2998,11 @@ export class DatabaseService {
const stored = this.storeAppriseFields(agent.type === 'apprise', agent.url, agent.config);
const existing = this.db.prepare('SELECT id FROM agents WHERE node_id = ? AND type = ?').get(nodeId, agent.type) as any;
if (existing) {
const stmt = this.db.prepare('UPDATE agents SET url = ?, enabled = ?, config = ? WHERE node_id = ? AND type = ?');
stmt.run(stored.url, agent.enabled ? 1 : 0, stored.config, nodeId, agent.type);
const stmt = this.db.prepare('UPDATE agents SET url = ?, enabled = ?, config = ?, payload_template = ? WHERE node_id = ? AND type = ?');
stmt.run(stored.url, agent.enabled ? 1 : 0, stored.config, agent.payload_template ?? null, nodeId, agent.type);
} else {
const stmt = this.db.prepare('INSERT INTO agents (node_id, type, url, enabled, config) VALUES (?, ?, ?, ?, ?)');
stmt.run(nodeId, agent.type, stored.url, agent.enabled ? 1 : 0, stored.config);
const stmt = this.db.prepare('INSERT INTO agents (node_id, type, url, enabled, config, payload_template) VALUES (?, ?, ?, ?, ?, ?)');
stmt.run(nodeId, agent.type, stored.url, agent.enabled ? 1 : 0, stored.config, agent.payload_template ?? null);
}
}