mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
Expose stable webhook message keys
Add a language-neutral condition key and explicit resource context to generic alert payloads so receivers can translate notifications without parsing English text. Refs #1779 Change-source: pulse-maintainer
This commit is contained in:
+6
-2
@@ -28,8 +28,8 @@ Pulse includes built-in templates for popular services and a generic JSON templa
|
||||
For generic webhooks, use Go templates to format the JSON payload.
|
||||
|
||||
**Variables (common):**
|
||||
- `{{.ID}}`, `{{.Level}}`, `{{.Type}}`
|
||||
- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}`
|
||||
- `{{.ID}}`, `{{.Event}}`, `{{.MessageKey}}`, `{{.Level}}`, `{{.Type}}`
|
||||
- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}`, `{{.NodeDisplayName}}`
|
||||
- `{{.Message}}`, `{{.Value}}`, `{{.Threshold}}`, `{{.Duration}}`, `{{.Timestamp}}`
|
||||
- `{{.Instance}}` (Pulse public URL if configured)
|
||||
- `{{.TenantID}}`, `{{.TenantName}}` (tenant identity in multi-tenant orgs and MSP client runtimes; empty on plain single-tenant installs)
|
||||
@@ -71,6 +71,8 @@ These fields and behaviors are stable; ticket-routing integrations can rely on t
|
||||
|
||||
**Alert type.** `{{.Type}}` is the metric or condition that fired: `cpu`, `memory`, `disk`, `diskRead`, `diskWrite`, `networkIn`, `networkOut`, `connectivity`, and similar. The alert ID (`{{.ID}}`) is stable for the lifetime of an alert occurrence, so the `resolved` event carries the same ID as the `alert` event it closes.
|
||||
|
||||
**Message key.** `{{.MessageKey}}` is the stable, language-neutral condition key for rebuilding or translating a notification. Canonical alerts use `<kind>.<type>` (for example, `metric-threshold.disk`); older alert paths fall back to `{{.Type}}`. Combine it with `{{.Event}}` and `{{.ResourceType}}` when the receiving system needs separate wording for firing/recovery events or different resource classes. Unlike a numeric message index, the symbolic key does not change when another alert type is added.
|
||||
|
||||
**Tenant identity.** In multi-tenant organizations and MSP client runtimes, `{{.TenantID}}` and `{{.TenantName}}` identify which tenant fired the alert. Client runtimes get identity from the `PULSE_TENANT_ID` / `PULSE_TENANT_NAME` environment; shared-process organizations stamp the org ID and display name automatically.
|
||||
|
||||
**Resource tag routing.** Email and each alert webhook can be limited to resources with selected tags in **Alerts → Notifications**. An empty filter receives every alert. With multiple tags, choose **Match all tags** or **Match any tag**. Matching ignores case. Proxmox tags are matched as shown; Docker container and service labels are exposed as `key:value` tags (or `key` when the label value is empty). Recovery notifications follow the destinations that received the firing alert, even if a resource's tags change before recovery.
|
||||
@@ -192,11 +194,13 @@ stable severity/type fields from the [delivery contract](#-delivery-contract):
|
||||
{
|
||||
"event": "{{.Event}}",
|
||||
"alertId": "{{.ID | jsonString}}",
|
||||
"messageKey": "{{.MessageKey | jsonString}}",
|
||||
"severity": "{{.Level | jsonString}}",
|
||||
"alertType": "{{.Type | jsonString}}",
|
||||
"tenantId": "{{.TenantID | jsonString}}",
|
||||
"tenantName": "{{.TenantName | jsonString}}",
|
||||
"resource": "{{.ResourceName | jsonString}}",
|
||||
"resourceType": "{{.ResourceType | jsonString}}",
|
||||
"node": "{{.Node | jsonString}}",
|
||||
"summary": "{{.Message | jsonString}}",
|
||||
"value": {{.Value}},
|
||||
|
||||
@@ -434,6 +434,12 @@ inspectability, or convert missing/stale evidence into health.
|
||||
|
||||
## Current State
|
||||
|
||||
The alert webhook editor exposes the delivery contract's language-neutral
|
||||
`MessageKey`, event, resource type and node display name alongside the existing
|
||||
type, severity and metric fields. Presentation must advertise the backend-owned
|
||||
template data rather than asking operators to parse the English alert message
|
||||
or reach into metadata for canonical alert identity.
|
||||
|
||||
### Agent custom sensors use canonical health-assessment alerts
|
||||
|
||||
Typed `HostSensorSummary.Custom` numeric, boolean, or timestamp readings with
|
||||
|
||||
@@ -25,6 +25,9 @@ in the existing customer-facing section and have exact packet proof in
|
||||
`render_release_body_test.py`; a note about repository-scoped Proxmox backup
|
||||
review must name both PBS server and datastore rather than implying that a
|
||||
workload or node filter provides the same boundary.
|
||||
The next-candidate release notes and changelog must also describe newly stable
|
||||
integration fields when external receivers need them to consume the release;
|
||||
packet proof keeps the customer-facing summary and detailed changelog aligned.
|
||||
|
||||
Provider-hosted MSP deploy artifacts must package the provider control plane as
|
||||
a least-privilege Docker provisioner. The packaged compose/setup path must avoid
|
||||
|
||||
@@ -198,6 +198,13 @@ its tenant block only when an identity is present so single-tenant payloads
|
||||
keep their existing shape. PSA/ticket-bridge receivers must get tenant routing
|
||||
identity from this payload boundary, not by inferring it from webhook endpoint
|
||||
configuration.
|
||||
That payload boundary also owns the language-neutral `MessageKey` exposed to
|
||||
custom templates. Canonical alerts derive it from `canonicalAlertKind` and the
|
||||
alert type (for example `metric-threshold.disk`); legacy alert paths use the
|
||||
alert type directly. The generic JSON templates must emit the message key,
|
||||
event, resource type, node display name and formatted metric values explicitly
|
||||
so external receivers can translate or reconstruct notifications without
|
||||
parsing English message text or reaching into the metadata map.
|
||||
That same transport boundary also owns outbound delivery integrity. A webhook
|
||||
config may carry an optional signing secret; when present, every JSON delivery
|
||||
through the canonical webhook transport must send `X-Pulse-Timestamp` and
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- **Faster large-estate navigation** — Storage and platform cold loads fell from roughly 15.6 seconds to 1.1 seconds in the measured large-estate case, with windowed tables keeping scrolling responsive.
|
||||
- **Lighter live updates** — The `/api/state` response fell from 4.75 MB to 4.09 MB, while changed-item catch-up and faster patch merging reduce work after reconnects and open mobile Alerts reliably.
|
||||
- **Visible notification delivery** — A recent-delivery log shows redacted outcomes, and test sends now say when delivery is paused instead of implying that a message was sent.
|
||||
- **Translation-ready webhooks** — Generic alert payloads include a stable message key and explicit resource context, so external systems can rebuild notifications in another language without parsing Pulse's English message.
|
||||
- **Safer agent operation** — A least-privilege install profile limits the agent account and sudo access, while Docker and Kubernetes recovery controls allow deliberate re-enrolment after removal.
|
||||
- **Clearer plan choices** — MSP evaluation mode supports provider trials, and the Business tier is now available for teams that need the annual business plan.
|
||||
- **Simpler saved filtering** — Saved views have been removed; bookmarkable filtered URLs remain available for returning to useful table filters.
|
||||
|
||||
@@ -7,6 +7,9 @@ next minor release._
|
||||
|
||||
## Added
|
||||
|
||||
- Generic alert webhooks expose a stable language-neutral message key plus
|
||||
explicit event, resource type, display name and formatted metric fields for
|
||||
external notification translation and reconstruction.
|
||||
- Notification delivery history exposes recent redacted delivery outcomes,
|
||||
while test sends report when delivery is paused instead of claiming success.
|
||||
- A least-privilege Unified Agent install profile creates a non-login service
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "e27585b0a25bfcfc5b99d72f8dd3dcf7c668811e",
|
||||
"verified_at": "2026-08-26T10:17:12Z",
|
||||
"base_sha": "4d457159fc7684e7e63c9fd68b85db313300f2ed",
|
||||
"verified_at": "2026-08-26T11:03:57Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Workloads/guestDrawerModel.ts",
|
||||
"frontend-modern/src/hooks/useWorkloads.ts",
|
||||
"frontend-modern/src/types/workloads.ts"
|
||||
"frontend-modern/src/utils/alertWebhookPresentation.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Workloads/guestDrawerModel.ts": "8cf63f54a06cd0d2d0a02fbec871c553dc61d31d82724efa4dfa737ddbd5745e",
|
||||
"frontend-modern/src/hooks/useWorkloads.ts": "87d048e258d71d04087d76d3fb5a3fcf109d454beaed915d1992f50ab4996c94",
|
||||
"frontend-modern/src/types/workloads.ts": "b10648f1bcc689e1cd078ae1ab8f9f0651a6fab6d0106c30f0eddc797d13c249"
|
||||
"frontend-modern/src/utils/alertWebhookPresentation.ts": "aaf1bc2327b6b232470569f9110159a88222e7c8cd7b0bf4e473ff0c34eb5147"
|
||||
},
|
||||
"routes": ["/proxmox/overview"],
|
||||
"routes": ["/alerts/notifications"],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1280,
|
||||
@@ -25,17 +21,14 @@
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"checkout-web-265 VM row filtered and expanded from the Proxmox guest table",
|
||||
"24-hour History tab with utilization, network I/O, and disk I/O series rendered",
|
||||
"1-hour History range loaded with the same three drawable chart groups",
|
||||
"narrow History layout stacked vertically without page-level horizontal overflow",
|
||||
"drawer collapsed with focus returned to the workload row toggle"
|
||||
"new Generic webhook editor with its default custom-payload placeholder visible",
|
||||
"translation-ready variable list showing Event, MessageKey, ResourceType and NodeDisplayName",
|
||||
"narrow webhook editor with no page-level horizontal overflow"
|
||||
],
|
||||
"interactions": [
|
||||
"signed into the current local build and filtered the Proxmox guest table to checkout-web-265",
|
||||
"expanded checkout-web-265 and selected History at 1280 by 800; confirmed three chart groups, seven SVG paths, and zero Collecting history placeholders",
|
||||
"switched the history selector from 24 hours to 1 hour and back; both ranges retained seven drawable SVG paths with no collection placeholder",
|
||||
"rechecked the open History drawer at 390 by 844; confirmed the three charts stack vertically and the 390-pixel page has no horizontal overflow",
|
||||
"restored the viewport override, collapsed checkout-web-265, and confirmed focus returned to the row expansion control"
|
||||
"signed into the current local mock build and opened Alerts, Notifications, then Add Webhook",
|
||||
"confirmed the Generic payload placeholder includes event, message_key and resource_type at desktop width",
|
||||
"confirmed the available-variable help exposes MessageKey, ResourceType and NodeDisplayName",
|
||||
"rechecked the open editor at 390 by 844 and measured document scroll width equal to the 390-pixel viewport"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ Pulse includes built-in templates for popular services and a generic JSON templa
|
||||
For generic webhooks, use Go templates to format the JSON payload.
|
||||
|
||||
**Variables (common):**
|
||||
- `{{.ID}}`, `{{.Level}}`, `{{.Type}}`
|
||||
- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}`
|
||||
- `{{.ID}}`, `{{.Event}}`, `{{.MessageKey}}`, `{{.Level}}`, `{{.Type}}`
|
||||
- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}`, `{{.NodeDisplayName}}`
|
||||
- `{{.Message}}`, `{{.Value}}`, `{{.Threshold}}`, `{{.Duration}}`, `{{.Timestamp}}`
|
||||
- `{{.Instance}}` (Pulse public URL if configured)
|
||||
- `{{.TenantID}}`, `{{.TenantName}}` (tenant identity in multi-tenant orgs and MSP client runtimes; empty on plain single-tenant installs)
|
||||
@@ -71,6 +71,8 @@ These fields and behaviors are stable; ticket-routing integrations can rely on t
|
||||
|
||||
**Alert type.** `{{.Type}}` is the metric or condition that fired: `cpu`, `memory`, `disk`, `diskRead`, `diskWrite`, `networkIn`, `networkOut`, `connectivity`, and similar. The alert ID (`{{.ID}}`) is stable for the lifetime of an alert occurrence, so the `resolved` event carries the same ID as the `alert` event it closes.
|
||||
|
||||
**Message key.** `{{.MessageKey}}` is the stable, language-neutral condition key for rebuilding or translating a notification. Canonical alerts use `<kind>.<type>` (for example, `metric-threshold.disk`); older alert paths fall back to `{{.Type}}`. Combine it with `{{.Event}}` and `{{.ResourceType}}` when the receiving system needs separate wording for firing/recovery events or different resource classes. Unlike a numeric message index, the symbolic key does not change when another alert type is added.
|
||||
|
||||
**Tenant identity.** In multi-tenant organizations and MSP client runtimes, `{{.TenantID}}` and `{{.TenantName}}` identify which tenant fired the alert. Client runtimes get identity from the `PULSE_TENANT_ID` / `PULSE_TENANT_NAME` environment; shared-process organizations stamp the org ID and display name automatically.
|
||||
|
||||
**Resource tag routing.** Email and each alert webhook can be limited to resources with selected tags in **Alerts → Notifications**. An empty filter receives every alert. With multiple tags, choose **Match all tags** or **Match any tag**. Matching ignores case. Proxmox tags are matched as shown; Docker container and service labels are exposed as `key:value` tags (or `key` when the label value is empty). Recovery notifications follow the destinations that received the firing alert, even if a resource's tags change before recovery.
|
||||
@@ -192,11 +194,13 @@ stable severity/type fields from the [delivery contract](#-delivery-contract):
|
||||
{
|
||||
"event": "{{.Event}}",
|
||||
"alertId": "{{.ID | jsonString}}",
|
||||
"messageKey": "{{.MessageKey | jsonString}}",
|
||||
"severity": "{{.Level | jsonString}}",
|
||||
"alertType": "{{.Type | jsonString}}",
|
||||
"tenantId": "{{.TenantID | jsonString}}",
|
||||
"tenantName": "{{.TenantName | jsonString}}",
|
||||
"resource": "{{.ResourceName | jsonString}}",
|
||||
"resourceType": "{{.ResourceType | jsonString}}",
|
||||
"node": "{{.Node | jsonString}}",
|
||||
"summary": "{{.Message | jsonString}}",
|
||||
"value": {{.Value}},
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { extractChangelog, extractHighlights, isReleaseVersion } from '../whatsNewModel';
|
||||
import whatsNewCardSource from '../WhatsNewCard.tsx?raw';
|
||||
|
||||
@@ -261,3 +263,19 @@ describe('post-update telemetry disclosure', () => {
|
||||
expect(whatsNewCardSource).not.toContain('setDialogVisible(true);\n markVersionSeen');
|
||||
});
|
||||
});
|
||||
|
||||
describe('current candidate notification packet', () => {
|
||||
it('keeps translation-ready webhook support in both release summaries', () => {
|
||||
const releaseNotes = readFileSync(
|
||||
path.resolve(process.cwd(), '../docs/releases/RELEASE_NOTES_v6.4.0-rc.3.md'),
|
||||
'utf8',
|
||||
);
|
||||
const changelog = readFileSync(
|
||||
path.resolve(process.cwd(), '../docs/releases/V6_CHANGELOG_v6.4.0-rc.3.md'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
expect(releaseNotes).toContain('Translation-ready webhooks');
|
||||
expect(changelog).toContain('stable language-neutral message key');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER,
|
||||
ALERT_WEBHOOK_PAYLOAD_VARIABLES,
|
||||
ALERT_WEBHOOK_TEST_FAILURE,
|
||||
ALERT_WEBHOOK_TEST_SUCCESS,
|
||||
getAlertWebhookServices,
|
||||
@@ -15,6 +17,18 @@ import {
|
||||
} from '@/utils/alertWebhookPresentation';
|
||||
|
||||
describe('alertWebhookPresentation', () => {
|
||||
it('advertises translation-ready generic webhook variables', () => {
|
||||
expect(ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER).toContain(
|
||||
'"message_key": "{{.MessageKey}}"',
|
||||
);
|
||||
expect(ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER).toContain(
|
||||
'"resource_type": "{{.ResourceType}}"',
|
||||
);
|
||||
expect(ALERT_WEBHOOK_PAYLOAD_VARIABLES).toContain('{{.MessageKey}}');
|
||||
expect(ALERT_WEBHOOK_PAYLOAD_VARIABLES).toContain('{{.ResourceType}}');
|
||||
expect(ALERT_WEBHOOK_PAYLOAD_VARIABLES).toContain('{{.NodeDisplayName}}');
|
||||
});
|
||||
|
||||
it('returns canonical webhook test-result copy', () => {
|
||||
expect(ALERT_WEBHOOK_TEST_SUCCESS).toBe('Test webhook sent successfully!');
|
||||
expect(ALERT_WEBHOOK_TEST_FAILURE).toBe('Failed to send test webhook');
|
||||
|
||||
@@ -112,13 +112,16 @@ export const ALERT_WEBHOOK_MENTION_HELP_LABEL = 'Optional — tag users or group
|
||||
export const ALERT_WEBHOOK_MENTION_FALLBACK_PLACEHOLDER = '@everyone';
|
||||
export const ALERT_WEBHOOK_PAYLOAD_HELP_LABEL = 'Optional — leave empty to use default';
|
||||
export const ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER = `{
|
||||
"event": "{{.Event}}",
|
||||
"message_key": "{{.MessageKey}}",
|
||||
"text": "Alert: {{.Level}} - {{.Message}}",
|
||||
"resource_type": "{{.ResourceType}}",
|
||||
"resource": "{{.ResourceName}}",
|
||||
"value": {{.Value}},
|
||||
"threshold": {{.Threshold}}
|
||||
}`;
|
||||
export const ALERT_WEBHOOK_PAYLOAD_VARIABLES =
|
||||
'{{.ID}}, {{.Level}}, {{.Type}}, {{.ResourceName}}, {{.Node}}, {{.Message}}, {{.Value}}, {{.Threshold}}, {{.Duration}}, {{.Timestamp}}';
|
||||
'{{.ID}}, {{.Event}}, {{.MessageKey}}, {{.Level}}, {{.Type}}, {{.ResourceType}}, {{.ResourceName}}, {{.Node}}, {{.NodeDisplayName}}, {{.Message}}, {{.Value}}, {{.Threshold}}, {{.Duration}}, {{.Timestamp}}';
|
||||
export const ALERT_WEBHOOK_CUSTOM_FIELDS_HELP = 'Available as';
|
||||
export const ALERT_WEBHOOK_CUSTOM_FIELDS_REFERENCE = '{{.CustomFields.<name>}}';
|
||||
export const ALERT_WEBHOOK_CUSTOM_FIELD_KEY_PLACEHOLDER = 'Field name';
|
||||
|
||||
@@ -3052,10 +3052,17 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel
|
||||
}
|
||||
|
||||
resourceType := ""
|
||||
messageKey := strings.TrimSpace(alert.Type)
|
||||
if alert.Metadata != nil {
|
||||
if rt, ok := alert.Metadata["resourceType"].(string); ok {
|
||||
resourceType = rt
|
||||
}
|
||||
if kind, ok := alert.Metadata["canonicalAlertKind"].(string); ok && strings.TrimSpace(kind) != "" {
|
||||
messageKey = strings.TrimSpace(kind)
|
||||
if alertType := strings.TrimSpace(alert.Type); alertType != "" {
|
||||
messageKey += "." + alertType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var metadataCopy map[string]interface{}
|
||||
@@ -3079,6 +3086,7 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel
|
||||
|
||||
return WebhookPayloadData{
|
||||
ID: alert.ID,
|
||||
MessageKey: messageKey,
|
||||
Level: string(alert.Level),
|
||||
Type: alert.Type,
|
||||
ResourceName: alert.ResourceName,
|
||||
|
||||
@@ -2293,6 +2293,35 @@ func TestPrepareWebhookData(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("builds a language-neutral key from canonical alert identity", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
alert := &alerts.Alert{
|
||||
ID: "test-1",
|
||||
Type: "disk",
|
||||
StartTime: time.Now(),
|
||||
Metadata: map[string]interface{}{
|
||||
"canonicalAlertKind": "metric-threshold",
|
||||
},
|
||||
}
|
||||
|
||||
result := nm.prepareWebhookData(alert, nil)
|
||||
|
||||
if result.MessageKey != "metric-threshold.disk" {
|
||||
t.Fatalf("expected canonical message key, got %q", result.MessageKey)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("uses alert type as the message key for legacy alert paths", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
alert := &alerts.Alert{ID: "test-1", Type: "connectivity", StartTime: time.Now()}
|
||||
|
||||
result := nm.prepareWebhookData(alert, nil)
|
||||
|
||||
if result.MessageKey != "connectivity" {
|
||||
t.Fatalf("expected legacy message key, got %q", result.MessageKey)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("resourceType empty when not in metadata", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
alert := &alerts.Alert{
|
||||
|
||||
@@ -438,6 +438,21 @@ func TestGetWebhookTemplates_GenericSettings(t *testing.T) {
|
||||
if generic.Method != "POST" {
|
||||
t.Errorf("Generic Method = %q, want POST", generic.Method)
|
||||
}
|
||||
for _, field := range []string{
|
||||
`"event": "{{.Event | jsonString}}"`,
|
||||
`"message_key": "{{.MessageKey | jsonString}}"`,
|
||||
`"resource_type": "{{.ResourceType | jsonString}}"`,
|
||||
`"node_display_name": "{{.NodeDisplayName | jsonString}}"`,
|
||||
`"value_formatted": "{{.ValueFormatted | jsonString}}"`,
|
||||
`"threshold_formatted": "{{.ThresholdFormatted | jsonString}}"`,
|
||||
} {
|
||||
if !strings.Contains(generic.PayloadTemplate, field) {
|
||||
t.Errorf("Generic PayloadTemplate missing %s", field)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(generic.ResolvedPayloadTemplate, `"message_key": "{{.MessageKey | jsonString}}"`) {
|
||||
t.Error("Generic ResolvedPayloadTemplate missing message_key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWebhookTemplates_PayloadTemplatesHaveRequiredFields(t *testing.T) {
|
||||
|
||||
@@ -40,6 +40,7 @@ type WebhookFilterRules struct {
|
||||
type WebhookPayloadData struct {
|
||||
// Alert fields
|
||||
ID string
|
||||
MessageKey string
|
||||
Level string
|
||||
Type string
|
||||
ResourceName string
|
||||
|
||||
@@ -534,15 +534,21 @@ View in Pulse: {{.Instance}}`,
|
||||
Method: "POST",
|
||||
Headers: map[string]string{"Content-Type": "application/json"},
|
||||
PayloadTemplate: `{
|
||||
"event": "{{.Event | jsonString}}",
|
||||
"alert": {
|
||||
"id": "{{.ID | jsonString}}",
|
||||
"message_key": "{{.MessageKey | jsonString}}",
|
||||
"level": "{{.Level | jsonString}}",
|
||||
"type": "{{.Type | jsonString}}",
|
||||
"resource_type": "{{.ResourceType | jsonString}}",
|
||||
"resource_name": "{{.ResourceName | jsonString}}",
|
||||
"node": "{{.Node | jsonString}}",
|
||||
"node_display_name": "{{.NodeDisplayName | jsonString}}",
|
||||
"message": "{{.Message | jsonString}}",
|
||||
"value": {{.Value}},
|
||||
"value_formatted": "{{.ValueFormatted | jsonString}}",
|
||||
"threshold": {{.Threshold}},
|
||||
"threshold_formatted": "{{.ThresholdFormatted | jsonString}}",
|
||||
"start_time": "{{.StartTime | jsonString}}",
|
||||
"duration": "{{.Duration | jsonString}}"
|
||||
},{{if .TenantID}}
|
||||
@@ -557,9 +563,12 @@ View in Pulse: {{.Instance}}`,
|
||||
"event": "resolved",
|
||||
"alert": {
|
||||
"id": "{{.ID | jsonString}}",
|
||||
"message_key": "{{.MessageKey | jsonString}}",
|
||||
"type": "{{.Type | jsonString}}",
|
||||
"resource_type": "{{.ResourceType | jsonString}}",
|
||||
"resource_name": "{{.ResourceName | jsonString}}",
|
||||
"node": "{{.Node | jsonString}}",
|
||||
"node_display_name": "{{.NodeDisplayName | jsonString}}",
|
||||
"message": "{{.Message | jsonString}}",
|
||||
"start_time": "{{.StartTime | jsonString}}",
|
||||
"duration": "{{.Duration | jsonString}}"
|
||||
|
||||
Reference in New Issue
Block a user