mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-11-01 00:12:16 +00:00
a89c08a59e
A second review found the moved-alert text was honest but the visuals still said recovered: Discord kept its green embed, both Teams cards kept recovery green, Mattermost led with a check emoji, and a grouped ntfy batch holding a moved alert kept the green check tag. A check mark on a phone is read before the sentence under it. Resolved webhook data now carries NotRecovered when any close in the delivery was not a recovery. The built-in templates switch to neutral colours and an arrow when it is set, and ntfy uses the arrow tag for a single alert or a batch. Titles still say Resolved, which stays true for the closed alert, and ordinary recoveries keep their green. The render tests now use an isolated data directory, stop their managers, and hand the captured request over a channel, so they no longer touch a shared queue or race on the captured body.
588 lines
29 KiB
Go
588 lines
29 KiB
Go
package notifications
|
|
|
|
// WebhookTemplate represents a webhook template for popular services
|
|
type WebhookTemplate struct {
|
|
Service string `json:"service"`
|
|
Label string `json:"label"`
|
|
MentionPlaceholder string `json:"mentionPlaceholder"`
|
|
MentionHelp string `json:"mentionHelp"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
URLPattern string `json:"urlPattern"`
|
|
Method string `json:"method"`
|
|
Headers map[string]string `json:"headers"`
|
|
PayloadTemplate string `json:"payloadTemplate"`
|
|
ResolvedPayloadTemplate string `json:"resolvedPayloadTemplate"`
|
|
Instructions string `json:"instructions"`
|
|
}
|
|
|
|
// GetWebhookTemplates returns templates for popular webhook services
|
|
func GetWebhookTemplates() []WebhookTemplate {
|
|
return []WebhookTemplate{
|
|
{
|
|
Service: "discord",
|
|
Label: "Discord",
|
|
MentionPlaceholder: "@everyone or <@USER_ID> or <@&ROLE_ID>",
|
|
MentionHelp: "Discord: Use @everyone, @here, <@USER_ID>, or <@&ROLE_ID>",
|
|
Name: "Discord Webhook",
|
|
Description: "Discord server webhook",
|
|
URLPattern: "https://discord.com/api/webhooks/{webhook_id}/{webhook_token}",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"username": "Pulse Monitoring",
|
|
{{if .Mention}}"content": "{{.Mention | jsonString}}",{{end}}
|
|
"embeds": [{
|
|
"title": "Pulse Alert: {{.Level | title | jsonString}}",
|
|
"description": "{{.Message | jsonString}}",
|
|
"color": {{if eq .Level "critical"}}15158332{{else if eq .Level "warning"}}15105570{{else}}3447003{{end}},
|
|
"fields": [
|
|
{"name": "Resource", "value": "{{.ResourceName | jsonString}}", "inline": true},
|
|
{"name": "Node", "value": "{{.Node | jsonString}}", "inline": true},
|
|
{"name": "Type", "value": "{{.Type | title | jsonString}}", "inline": true},
|
|
{"name": "Value", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}", "inline": true},
|
|
{"name": "Threshold", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}", "inline": true},
|
|
{"name": "Duration", "value": "{{.Duration | jsonString}}", "inline": true}
|
|
],
|
|
"timestamp": "{{.Timestamp | jsonString}}",
|
|
"footer": {
|
|
"text": "Pulse Monitoring"
|
|
}
|
|
}]
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"username": "Pulse Monitoring",
|
|
{{if .Mention}}"content": "{{.Mention | jsonString}}",{{end}}
|
|
"embeds": [{
|
|
"title": "Resolved: {{.ResourceName | jsonString}}",
|
|
"description": "{{.Message | jsonString}}",
|
|
"color": {{if .NotRecovered}}3447003{{else}}3066993{{end}},
|
|
"fields": [
|
|
{"name": "Resource", "value": "{{.ResourceName | jsonString}}", "inline": true},
|
|
{"name": "Node", "value": "{{.Node | jsonString}}", "inline": true},
|
|
{"name": "Type", "value": "{{.Type | title | jsonString}}", "inline": true},
|
|
{"name": "Duration", "value": "{{.Duration | jsonString}}", "inline": true},
|
|
{"name": "Resolved At", "value": "{{.ResolvedAt | jsonString}}", "inline": true}
|
|
],
|
|
"timestamp": "{{.Timestamp | jsonString}}",
|
|
"footer": {
|
|
"text": "Pulse Monitoring"
|
|
}
|
|
}]
|
|
}`,
|
|
Instructions: "1. In Discord, go to Server Settings > Integrations > Webhooks\n2. Create a new webhook and copy the URL\n3. Paste the URL here (format: https://discord.com/api/webhooks/...)\n4. Optional: Add a mention in the Mention field (e.g., @everyone, <@USER_ID>, <@&ROLE_ID>)",
|
|
},
|
|
{
|
|
Service: "telegram",
|
|
Label: "Telegram",
|
|
Name: "Telegram Bot",
|
|
Description: "Telegram bot notifications",
|
|
URLPattern: "https://api.telegram.org/bot{bot_token}/sendMessage",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"chat_id": "{{.ChatID | jsonString}}",
|
|
"text": "*Pulse Alert: {{.Level | title | jsonString}}*\n\n{{.Message | jsonString}}\n\n*Details:*\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Value: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}\n• Threshold: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}\n• Duration: {{.Duration | jsonString}}\n\n[View in Pulse]({{.Instance | jsonString}})",
|
|
"parse_mode": "Markdown",
|
|
"disable_web_page_preview": true
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"chat_id": "{{.ChatID | jsonString}}",
|
|
"text": "*Resolved: {{.ResourceName | jsonString}}*\n\n{{.Message | jsonString}}\n\n*Details:*\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Duration: {{.Duration | jsonString}}\n• Resolved At: {{.ResolvedAt | jsonString}}",
|
|
"parse_mode": "Markdown",
|
|
"disable_web_page_preview": true
|
|
}`,
|
|
Instructions: "1. Create a bot with @BotFather on Telegram\n2. Get your bot token\n3. Get your chat ID by messaging the bot and visiting: https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates\n4. URL format: https://api.telegram.org/bot<BOT_TOKEN>/sendMessage?chat_id=<CHAT_ID>\n5. IMPORTANT: You MUST include ?chat_id=YOUR_CHAT_ID in the URL",
|
|
},
|
|
{
|
|
Service: "slack",
|
|
Label: "Slack",
|
|
MentionPlaceholder: "@channel, @here, or <@USER_ID>",
|
|
MentionHelp: "Slack: Use @channel, @here, <@USER_ID>, or <!subteam^ID>",
|
|
Name: "Slack Incoming Webhook",
|
|
Description: "Slack incoming webhook",
|
|
URLPattern: "https://hooks.slack.com/services/{webhook_path}",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"text": "{{if .Mention}}{{.Mention | jsonString}} {{end}}Pulse Alert: {{.Level | title | jsonString}} - {{.ResourceName | jsonString}}",
|
|
"blocks": [
|
|
{{if .Mention}}{
|
|
"type": "section",
|
|
"text": {
|
|
"type": "mrkdwn",
|
|
"text": "{{.Mention | jsonString}}"
|
|
}
|
|
},{{end}}
|
|
{
|
|
"type": "header",
|
|
"text": {
|
|
"type": "plain_text",
|
|
"text": "Pulse Alert: {{.Level | title | jsonString}}",
|
|
"emoji": true
|
|
}
|
|
},
|
|
{
|
|
"type": "section",
|
|
"text": {
|
|
"type": "mrkdwn",
|
|
"text": "{{.Message | jsonString}}"
|
|
}
|
|
},
|
|
{
|
|
"type": "section",
|
|
"fields": [
|
|
{"type": "mrkdwn", "text": "*Resource:*\n{{.ResourceName | jsonString}}"},
|
|
{"type": "mrkdwn", "text": "*Node:*\n{{.Node | jsonString}}"},
|
|
{"type": "mrkdwn", "text": "*Type:*\n{{.Type | title | jsonString}}"},
|
|
{"type": "mrkdwn", "text": "*Value:*\n{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}"},
|
|
{"type": "mrkdwn", "text": "*Threshold:*\n{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}"},
|
|
{"type": "mrkdwn", "text": "*Duration:*\n{{.Duration | jsonString}}"}
|
|
]
|
|
},
|
|
{
|
|
"type": "context",
|
|
"elements": [
|
|
{
|
|
"type": "mrkdwn",
|
|
"text": "View in <{{.Instance | jsonString}}|Proxmox> | Alert Identifier: {{.ID | jsonString}}"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"text": "{{if .Mention}}{{.Mention | jsonString}} {{end}}Resolved: {{.ResourceName | jsonString}}",
|
|
"blocks": [
|
|
{{if .Mention}}{
|
|
"type": "section",
|
|
"text": {
|
|
"type": "mrkdwn",
|
|
"text": "{{.Mention | jsonString}}"
|
|
}
|
|
},{{end}}
|
|
{
|
|
"type": "header",
|
|
"text": {
|
|
"type": "plain_text",
|
|
"text": "Resolved: {{.ResourceName | jsonString}}",
|
|
"emoji": true
|
|
}
|
|
},
|
|
{
|
|
"type": "section",
|
|
"text": {
|
|
"type": "mrkdwn",
|
|
"text": "{{.Message | jsonString}}"
|
|
}
|
|
},
|
|
{
|
|
"type": "section",
|
|
"fields": [
|
|
{"type": "mrkdwn", "text": "*Resource:*\n{{.ResourceName | jsonString}}"},
|
|
{"type": "mrkdwn", "text": "*Node:*\n{{.Node | jsonString}}"},
|
|
{"type": "mrkdwn", "text": "*Type:*\n{{.Type | title | jsonString}}"},
|
|
{"type": "mrkdwn", "text": "*Duration:*\n{{.Duration | jsonString}}"},
|
|
{"type": "mrkdwn", "text": "*Resolved At:*\n{{.ResolvedAt | jsonString}}"}
|
|
]
|
|
},
|
|
{
|
|
"type": "context",
|
|
"elements": [
|
|
{
|
|
"type": "mrkdwn",
|
|
"text": "Alert Identifier: {{.ID | jsonString}}"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`,
|
|
Instructions: "1. In Slack, go to Apps > Incoming Webhooks\n2. Add to Slack and choose a channel\n3. Copy the webhook URL and paste it here (format: https://hooks.slack.com/services/...)\n4. Optional: Add a mention in the Mention field (e.g., @channel, @here, <@USER_ID>, <!subteam^ID>)",
|
|
},
|
|
{
|
|
Service: "teams",
|
|
Label: "Microsoft Teams",
|
|
MentionPlaceholder: "@General or user email",
|
|
MentionHelp: "Teams: Use channel names like @General",
|
|
Name: "Microsoft Teams",
|
|
Description: "Microsoft Teams webhook",
|
|
URLPattern: "https://{tenant}.webhook.office.com/webhookb2/{webhook_path}",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"@type": "MessageCard",
|
|
"@context": "http://schema.org/extensions",
|
|
"themeColor": "{{if eq .Level "critical"}}FF0000{{else if eq .Level "warning"}}FFA500{{else}}00FF00{{end}}",
|
|
"summary": "Pulse Alert: {{.Level | title | jsonString}} - {{.ResourceName | jsonString}}",
|
|
{{if .Mention}}"text": "{{.Mention | jsonString}}",{{end}}
|
|
"sections": [{
|
|
"activityTitle": "Pulse Alert: {{.Level | title | jsonString}}",
|
|
"activitySubtitle": "{{.Message | jsonString}}",
|
|
"facts": [
|
|
{"name": "Resource", "value": "{{.ResourceName | jsonString}}"},
|
|
{"name": "Node", "value": "{{.Node | jsonString}}"},
|
|
{"name": "Type", "value": "{{.Type | title | jsonString}}"},
|
|
{"name": "Value", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}"},
|
|
{"name": "Threshold", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}"},
|
|
{"name": "Duration", "value": "{{.Duration | jsonString}}"},
|
|
{"name": "Instance", "value": "{{.Instance | jsonString}}"}
|
|
],
|
|
"markdown": true
|
|
}],
|
|
"potentialAction": [{
|
|
"@type": "OpenUri",
|
|
"name": "View in Proxmox",
|
|
"targets": [{
|
|
"os": "default",
|
|
"uri": "{{.Instance | jsonString}}"
|
|
}]
|
|
}]
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"@type": "MessageCard",
|
|
"@context": "http://schema.org/extensions",
|
|
"themeColor": "{{if .NotRecovered}}0076D7{{else}}2DC72D{{end}}",
|
|
"summary": "Resolved: {{.ResourceName | jsonString}}",
|
|
{{if .Mention}}"text": "{{.Mention | jsonString}}",{{end}}
|
|
"sections": [{
|
|
"activityTitle": "Resolved: {{.ResourceName | jsonString}}",
|
|
"activitySubtitle": "{{.Message | jsonString}}",
|
|
"facts": [
|
|
{"name": "Resource", "value": "{{.ResourceName | jsonString}}"},
|
|
{"name": "Node", "value": "{{.Node | jsonString}}"},
|
|
{"name": "Type", "value": "{{.Type | title | jsonString}}"},
|
|
{"name": "Duration", "value": "{{.Duration | jsonString}}"},
|
|
{"name": "Resolved At", "value": "{{.ResolvedAt | jsonString}}"}
|
|
],
|
|
"markdown": true
|
|
}]
|
|
}`,
|
|
Instructions: "1. In Teams channel, click ... > Connectors\n2. Configure Incoming Webhook\n3. Copy the URL and paste it here\n4. Optional: Add a mention in the Mention field (e.g., @General)\n\nNote: MessageCard format is supported until December 2025. For new implementations, consider using Adaptive Cards.",
|
|
},
|
|
{
|
|
Service: "pagerduty",
|
|
Label: "PagerDuty",
|
|
Name: "PagerDuty Events API v2",
|
|
Description: "PagerDuty Events API v2",
|
|
URLPattern: "https://events.pagerduty.com/v2/enqueue",
|
|
Method: "POST",
|
|
Headers: map[string]string{
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/vnd.pagerduty+json;version=2",
|
|
},
|
|
PayloadTemplate: `{
|
|
"routing_key": "{{.CustomFields.routing_key | jsonString}}",
|
|
"event_action": "trigger",
|
|
"dedup_key": "{{.ID | jsonString}}",
|
|
"payload": {
|
|
"summary": "{{.Message | jsonString}}",
|
|
"timestamp": "{{.Timestamp | jsonString}}",
|
|
"severity": "{{if eq .Level "critical"}}critical{{else if eq .Level "warning"}}warning{{else}}info{{end}}",
|
|
"source": "{{.Node | jsonString}}",
|
|
"component": "{{.ResourceName | jsonString}}",
|
|
"group": "{{.Type | jsonString}}",
|
|
"class": "{{.Type | jsonString}}",
|
|
"custom_details": {
|
|
"alert_identifier": "{{.ID | jsonString}}",
|
|
"resource_type": "{{.Type | jsonString}}",
|
|
"current_value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}",
|
|
"threshold": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}",
|
|
"duration": "{{.Duration | jsonString}}",
|
|
"instance": "{{.Instance | jsonString}}"
|
|
}
|
|
},
|
|
"client": "Pulse Monitoring",
|
|
"client_url": "{{.Instance | jsonString}}",
|
|
"links": [{
|
|
"href": "{{.Instance | jsonString}}",
|
|
"text": "View in Proxmox"
|
|
}]
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"routing_key": "{{.CustomFields.routing_key | jsonString}}",
|
|
"event_action": "resolve",
|
|
"dedup_key": "{{.ID | jsonString}}"
|
|
}`,
|
|
Instructions: "1. In PagerDuty, go to Configuration > Services\n2. Add an integration > Events API V2\n3. Copy the Integration Key\n4. Add the key as a custom field named 'routing_key'\n\nNote: PagerDuty recommends using Events API v2 for new integrations.",
|
|
},
|
|
{
|
|
Service: "teams-adaptive",
|
|
Label: "Teams (Adaptive)",
|
|
MentionPlaceholder: "@General or user email",
|
|
MentionHelp: "Teams: Use channel names like @General",
|
|
Name: "Microsoft Teams (Adaptive Card)",
|
|
Description: "Teams with Adaptive Cards",
|
|
URLPattern: "https://{tenant}.webhook.office.com/webhookb2/{webhook_path}",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"type": "message",
|
|
"attachments": [{
|
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
|
"content": {
|
|
"type": "AdaptiveCard",
|
|
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
"version": "1.4",
|
|
"body": [
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "Pulse Alert: {{.Level | title}}",
|
|
"weight": "Bolder",
|
|
"size": "Large",
|
|
"color": "{{if eq .Level "critical"}}Attention{{else if eq .Level "warning"}}Warning{{else}}Good{{end}}"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "{{.Message | jsonString}}",
|
|
"wrap": true,
|
|
"spacing": "Small"
|
|
},
|
|
{
|
|
"type": "FactSet",
|
|
"facts": [
|
|
{"title": "Resource", "value": "{{.ResourceName | jsonString}}"},
|
|
{"title": "Node", "value": "{{.Node | jsonString}}"},
|
|
{"title": "Type", "value": "{{.Type | title | jsonString}}"},
|
|
{"title": "Current Value", "value": "{{printf "%.1f" .Value}}%"},
|
|
{"title": "Threshold", "value": "{{printf "%.0f" .Threshold}}%"},
|
|
{"title": "Duration", "value": "{{.Duration | jsonString}}"},
|
|
{"title": "Alert Identifier", "value": "{{.ID | jsonString}}"}
|
|
]
|
|
}
|
|
],
|
|
"actions": [{
|
|
"type": "Action.OpenUrl",
|
|
"title": "View in Proxmox",
|
|
"url": "{{.Instance | jsonString}}"
|
|
}]
|
|
}
|
|
}]
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"type": "message",
|
|
"attachments": [{
|
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
|
"content": {
|
|
"type": "AdaptiveCard",
|
|
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
"version": "1.4",
|
|
"body": [
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "Resolved: {{.ResourceName | jsonString}}",
|
|
"weight": "Bolder",
|
|
"size": "Large",
|
|
"color": "{{if .NotRecovered}}Accent{{else}}Good{{end}}"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "{{.Message | jsonString}}",
|
|
"wrap": true,
|
|
"spacing": "Small"
|
|
},
|
|
{
|
|
"type": "FactSet",
|
|
"facts": [
|
|
{"title": "Resource", "value": "{{.ResourceName | jsonString}}"},
|
|
{"title": "Node", "value": "{{.Node | jsonString}}"},
|
|
{"title": "Type", "value": "{{.Type | title | jsonString}}"},
|
|
{"title": "Duration", "value": "{{.Duration | jsonString}}"},
|
|
{"title": "Resolved At", "value": "{{.ResolvedAt | jsonString}}"},
|
|
{"title": "Alert Identifier", "value": "{{.ID | jsonString}}"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}]
|
|
}`,
|
|
Instructions: "1. In Teams channel, click ... > Connectors\n2. Configure Incoming Webhook\n3. Copy the URL and paste it here\n\nThis uses the modern Adaptive Card format recommended for new implementations.",
|
|
},
|
|
{
|
|
Service: "pushover",
|
|
Label: "Pushover",
|
|
Name: "Pushover",
|
|
Description: "Mobile push notifications",
|
|
URLPattern: "https://api.pushover.net/1/messages.json",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"token": "{{.CustomFields.token | jsonString}}",
|
|
"user": "{{.CustomFields.user | jsonString}}",
|
|
"title": "Pulse Alert: {{.Level | title | jsonString}} - {{.ResourceName | jsonString}}",
|
|
"message": "{{.Message | jsonString}}\n\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Value: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}\n• Threshold: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}\n• Duration: {{.Duration | jsonString}}",
|
|
"priority": {{if .CustomFields.priority}}{{.CustomFields.priority}}{{else}}{{if eq .Level "critical"}}1{{else if eq .Level "warning"}}0{{else}}-1{{end}}{{end}},
|
|
"sound": "{{if .CustomFields.sound}}{{.CustomFields.sound | jsonString}}{{else}}{{if eq .Level "critical"}}siren{{else if eq .Level "warning"}}tugboat{{else}}pushover{{end}}{{end}}",
|
|
"device": "{{if .CustomFields.device}}{{.CustomFields.device | jsonString}}{{else}}{{.ResourceName | jsonString}}{{end}}",
|
|
"timestamp": "{{.Timestamp | jsonString}}"
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"token": "{{.CustomFields.token | jsonString}}",
|
|
"user": "{{.CustomFields.user | jsonString}}",
|
|
"title": "Resolved: {{.ResourceName | jsonString}}",
|
|
"message": "{{.Message | jsonString}}\n\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Duration: {{.Duration | jsonString}}\n• Resolved At: {{.ResolvedAt | jsonString}}",
|
|
"priority": -1,
|
|
"sound": "pushover",
|
|
"timestamp": "{{.Timestamp | jsonString}}"
|
|
}`,
|
|
Instructions: "1. Create an application at https://pushover.net/apps\n2. Copy your Application Token\n3. Get your User Key from your Pushover dashboard\n4. URL: https://api.pushover.net/1/messages.json\n5. Add custom fields:\n • token: YOUR_APP_TOKEN (required)\n • user: YOUR_USER_KEY (required)\n • sound: notification sound (optional, e.g., spacealarm, siren, tugboat)\n • priority: -2 to 2 (optional, overrides level-based default)\n • device: specific device name (optional, overrides ResourceName)",
|
|
},
|
|
{
|
|
Service: "gotify",
|
|
Label: "Gotify",
|
|
Name: "Gotify",
|
|
Description: "Self-hosted push notifications",
|
|
URLPattern: "https://{your-gotify-server}/message?token={your-app-token}",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"message": "**{{if eq .Level "critical"}}CRITICAL{{else if eq .Level "warning"}}WARNING{{else}}INFO{{end}}**: **{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n**Alert Details:**\n- **Resource:** {{.ResourceName | jsonString}}\n- **Node:** {{.Node | jsonString}}\n- **Type:** {{.Type | title | jsonString}}\n- **Current:** {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}\n- **Threshold:** {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}\n- **Duration:** {{.Duration | jsonString}}\n- **Alert Identifier:** {{.ID | jsonString}}\n\n[View in Pulse]({{.Instance | jsonString}})",
|
|
"title": "{{.ResourceName | jsonString}} - {{.Type | title | jsonString}} {{.Level | upper | jsonString}} Alert",
|
|
"priority": {{if eq .Level "critical"}}10{{else if eq .Level "warning"}}5{{else}}2{{end}},
|
|
"extras": {
|
|
"client::display": {
|
|
"contentType": "text/markdown"
|
|
},
|
|
"pulse::alert": {
|
|
"id": "{{.ID | jsonString}}",
|
|
"level": "{{.Level | jsonString}}",
|
|
"type": "{{.Type | jsonString}}",
|
|
"resource_name": "{{.ResourceName | jsonString}}",
|
|
"node": "{{.Node | jsonString}}",
|
|
"value": {{.Value}},
|
|
"threshold": {{.Threshold}},
|
|
"duration": "{{.Duration | jsonString}}",
|
|
"instance": "{{.Instance | jsonString}}"
|
|
}
|
|
}
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"message": "**RESOLVED**: **{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n**Details:**\n- **Resource:** {{.ResourceName | jsonString}}\n- **Node:** {{.Node | jsonString}}\n- **Type:** {{.Type | title | jsonString}}\n- **Duration:** {{.Duration | jsonString}}\n- **Resolved At:** {{.ResolvedAt | jsonString}}\n- **Alert Identifier:** {{.ID | jsonString}}",
|
|
"title": "Resolved: {{.ResourceName | jsonString}} - {{.Type | title | jsonString}}",
|
|
"priority": 2,
|
|
"extras": {
|
|
"client::display": {
|
|
"contentType": "text/markdown"
|
|
},
|
|
"pulse::alert": {
|
|
"id": "{{.ID | jsonString}}",
|
|
"event": "resolved",
|
|
"resource_name": "{{.ResourceName | jsonString}}",
|
|
"node": "{{.Node | jsonString}}",
|
|
"duration": "{{.Duration | jsonString}}",
|
|
"resolved_at": "{{.ResolvedAt | jsonString}}"
|
|
}
|
|
}
|
|
}`,
|
|
Instructions: "1. In Gotify, create a new application\n2. Copy the application token\n3. URL format: https://your-gotify-server/message?token=YOUR_APP_TOKEN\n4. The token must be included in the URL as a parameter",
|
|
},
|
|
{
|
|
Service: "ntfy",
|
|
Label: "ntfy",
|
|
Name: "ntfy.sh",
|
|
Description: "Push notifications via ntfy.sh",
|
|
URLPattern: "https://ntfy.sh/{topic}",
|
|
Method: "POST",
|
|
Headers: map[string]string{
|
|
"Content-Type": "text/plain",
|
|
// Note: Title, Priority, and Tags headers should be added dynamically based on alert level
|
|
// For now, we'll use static reasonable defaults that won't break
|
|
},
|
|
PayloadTemplate: `{{if eq .Level "critical"}}CRITICAL{{else if eq .Level "warning"}}WARNING{{else}}INFO{{end}}: {{.ResourceName}} on {{.Node}}
|
|
|
|
{{.Message}}
|
|
|
|
Alert Details:
|
|
- Resource: {{.ResourceName}}
|
|
- Node: {{.Node}}
|
|
- Type: {{.Type | title}}
|
|
- Current: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}
|
|
- Threshold: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}
|
|
- Duration: {{.Duration}}
|
|
- Alert Identifier: {{.ID}}
|
|
|
|
View in Pulse: {{.Instance}}`,
|
|
Instructions: "1. Choose a topic name (e.g., 'my-pulse-alerts')\n2. URL format: https://ntfy.sh/YOUR_TOPIC\n Or for self-hosted: https://your-ntfy-server/YOUR_TOPIC\n3. For authentication, add a custom header:\n • Header Name: Authorization\n • Header Value: Bearer YOUR_TOKEN (or Basic base64_encoded_credentials)\n4. Subscribe to the topic in your ntfy app using the same topic name",
|
|
},
|
|
{
|
|
Service: "mattermost",
|
|
Label: "Mattermost",
|
|
MentionPlaceholder: "@channel, @all, or @username",
|
|
MentionHelp: "Mattermost: Use @channel, @all, or @username",
|
|
Name: "Mattermost Incoming Webhook",
|
|
Description: "Mattermost incoming webhook",
|
|
URLPattern: "https://{your-mattermost-server}/hooks/{webhook_id}",
|
|
Method: "POST",
|
|
Headers: map[string]string{"Content-Type": "application/json"},
|
|
PayloadTemplate: `{
|
|
"username": "Pulse Monitoring",
|
|
"icon_url": "https://raw.githubusercontent.com/rcourtman/Pulse/main/frontend-modern/public/android-chrome-192x192.png",
|
|
"text": "{{if eq .Level "critical"}}:rotating_light: **CRITICAL ALERT**{{else if eq .Level "warning"}}:warning: **WARNING ALERT**{{else}}:information_source: **INFO**{{end}}\n\n**{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n| Detail | Value |\n|:-------|:------|\n| Resource | {{.ResourceName | jsonString}} |\n| Node | {{.Node | jsonString}} |\n| Type | {{.Type | title | jsonString}} |\n| Current | {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}} |\n| Threshold | {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}} |\n| Duration | {{.Duration | jsonString}} |\n| Alert Identifier | {{.ID | jsonString}} |\n\n[View in Pulse]({{.Instance | jsonString}})"
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"username": "Pulse Monitoring",
|
|
"icon_url": "https://raw.githubusercontent.com/rcourtman/Pulse/main/frontend-modern/public/android-chrome-192x192.png",
|
|
"text": "{{if .Mention}}{{.Mention | jsonString}}\n\n{{end}}{{if .NotRecovered}}:arrow_right:{{else}}:white_check_mark:{{end}} **RESOLVED**\n\n**{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n| Detail | Value |\n|:-------|:------|\n| Resource | {{.ResourceName | jsonString}} |\n| Node | {{.Node | jsonString}} |\n| Type | {{.Type | title | jsonString}} |\n| Duration | {{.Duration | jsonString}} |\n| Resolved At | {{.ResolvedAt | jsonString}} |\n| Alert Identifier | {{.ID | jsonString}} |"
|
|
}`,
|
|
Instructions: "1. In Mattermost, go to Integrations > Incoming Webhooks\n2. Create a new webhook and select the channel\n3. Copy the webhook URL and paste it here\n\nNote: This template uses Markdown formatting which is fully supported by Mattermost.",
|
|
},
|
|
{
|
|
Service: "generic",
|
|
Label: "Generic",
|
|
Name: "Generic JSON Webhook",
|
|
Description: "Custom webhook endpoint",
|
|
URLPattern: "",
|
|
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}}
|
|
"tenant": {
|
|
"id": "{{.TenantID | jsonString}}",
|
|
"name": "{{.TenantName | jsonString}}"
|
|
},{{end}}
|
|
"timestamp": "{{.Timestamp | jsonString}}",
|
|
"source": "pulse-monitoring"
|
|
}`,
|
|
ResolvedPayloadTemplate: `{
|
|
"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}}"
|
|
},{{if .TenantID}}
|
|
"tenant": {
|
|
"id": "{{.TenantID | jsonString}}",
|
|
"name": "{{.TenantName | jsonString}}"
|
|
},{{end}}
|
|
"resolved_at": "{{.ResolvedAt | jsonString}}",
|
|
"timestamp": "{{.Timestamp | jsonString}}",
|
|
"source": "pulse-monitoring"
|
|
}`,
|
|
Instructions: "Configure with your custom webhook endpoint",
|
|
},
|
|
}
|
|
}
|