feat: add incoming webhook API support with configurable endpoints
- Implemented the incoming webhook API to handle alerts from external applications. - Added configuration options for webhook listening address, port, and endpoints in config.go. - Created WebhookReceiverConfig and WebhookEndpointConfig structures to manage webhook settings. - Developed WebhookHandler to process incoming requests, validate tokens, and deliver alerts to specified channels. - Enhanced existing controller interfaces to support alert delivery. - Updated message rendering to respect Markdown settings for different channels. - Added tests for webhook functionality and ensured proper error handling.
This commit is contained in:
@@ -19,6 +19,9 @@ type Params struct {
|
||||
Message string
|
||||
Command string
|
||||
Result string
|
||||
Subject string // Alert subject (see AlertParams)
|
||||
Source string // Alert source (see AlertParams)
|
||||
Content string // Alert content (see AlertParams)
|
||||
OnlineServers string // Pre-formatted multi-line list
|
||||
OfflineServers string // Pre-formatted multi-line list
|
||||
SoftwareVersion string
|
||||
@@ -30,6 +33,37 @@ type Params struct {
|
||||
SoftwareDescription string
|
||||
}
|
||||
|
||||
// AlertTemplate is the body format of an alert submitted by an external
|
||||
// application through the incoming webhook API.
|
||||
const AlertTemplate = "{{ subject }}\n- Source: {{ source }}\n- Content:\n{{ content }}\n\n- Time: {{ time }}\nSent by Nukumizu Alert System"
|
||||
|
||||
// AlertParams holds the parameters of an alert submitted through the incoming
|
||||
// webhook API.
|
||||
type AlertParams struct {
|
||||
Subject string // Short one-line title of the alert
|
||||
Source string // Name of the webhook endpoint the alert was submitted to
|
||||
Content string // Free-form alert body
|
||||
Time string // Submission time
|
||||
}
|
||||
|
||||
// RenderAlert renders the body of an alert for a channel. The alert source and
|
||||
// content may be wrapped in Markdown — the source in inline code, the content
|
||||
// in a fenced code block — when the target channel has markdown enabled
|
||||
// (markdown); everything else, the timestamp included, stays plain text.
|
||||
func RenderAlert(alert AlertParams, markdown bool) string {
|
||||
params := Params{
|
||||
Time: alert.Time,
|
||||
Subject: alert.Subject,
|
||||
Source: alert.Source,
|
||||
Content: alert.Content,
|
||||
}
|
||||
if markdown {
|
||||
params.Source = "`" + params.Source + "`"
|
||||
params.Content = "```\n" + params.Content + "\n```"
|
||||
}
|
||||
return Render(AlertTemplate, params, false)
|
||||
}
|
||||
|
||||
// BuildBotInitializationMsgParams creates template parameters for the bot initialization message.
|
||||
func BuildBotInitializationMsgParams() Params {
|
||||
return Params{
|
||||
@@ -80,7 +114,13 @@ func BuildParamsFromExecResult(serverName, serverUUID, command, result string) P
|
||||
}
|
||||
}
|
||||
|
||||
// Render substitutes {{ paramName }} placeholders in a template string.
|
||||
// Render substitutes {{ paramName }} placeholders in a template string. The
|
||||
// markdown argument is the target channel's markdown setting: when true the
|
||||
// values that are meant to be read verbatim (UUIDs, messages, commands, command
|
||||
// results) are wrapped in Markdown code spans and blocks, otherwise every value
|
||||
// is inserted as plain text. Whether a channel renders Markdown comes from the
|
||||
// configuration alone — the renderer never infers it from the channel name.
|
||||
//
|
||||
// Supported placeholders:
|
||||
// - {{ time }} — current server time
|
||||
// - {{ serverName }} — server name
|
||||
@@ -90,6 +130,9 @@ func BuildParamsFromExecResult(serverName, serverUUID, command, result string) P
|
||||
// - {{ message }} — event descriptive message
|
||||
// - {{ command }} — executed command
|
||||
// - {{ result }} — command execution result
|
||||
// - {{ subject }} — alert subject
|
||||
// - {{ source }} — alert source
|
||||
// - {{ content }} — alert content
|
||||
// - {{ list.onlineServers }} — multi-line online server list
|
||||
// - {{ list.offlineServers }} — multi-line offline server list
|
||||
// - {{ softwareVersion }} — software version
|
||||
@@ -99,19 +142,20 @@ func BuildParamsFromExecResult(serverName, serverUUID, command, result string) P
|
||||
// - {{ softwareBuildTime }} — software build time
|
||||
// - {{ softwareDeveloper }} — software developer
|
||||
// - {{ softwareDescription }} — software description
|
||||
func Render(tmpl string, params Params, source ...string) string {
|
||||
func Render(tmpl string, params Params, markdown bool) string {
|
||||
result := tmpl
|
||||
|
||||
if len(source) > 0 && source[0] == "telegram" {
|
||||
// Telegram requires special formatting for code blocks and inline code.
|
||||
result = strings.ReplaceAll(result, "{{ time }}", "**" + params.Time + "**")
|
||||
result = strings.ReplaceAll(result, "{{ serverName }}", "**" + params.ServerName + "**")
|
||||
result = strings.ReplaceAll(result, "{{ serverUUID }}", "`" + params.ServerUUID + "`")
|
||||
result = strings.ReplaceAll(result, "{{ upStatus }}", "**" + params.UpStatus + "**")
|
||||
result = strings.ReplaceAll(result, "{{ event }}", "**" + params.Event + "**")
|
||||
result = strings.ReplaceAll(result, "{{ message }}", "`" + params.Message + "`")
|
||||
result = strings.ReplaceAll(result, "{{ command }}", "`" + params.Command + "`")
|
||||
result = strings.ReplaceAll(result, "{{ result }}", "```bash\n" + params.Result + "\n```")
|
||||
if markdown {
|
||||
// Channels that render Markdown get code blocks and inline code for the
|
||||
// values that are read verbatim.
|
||||
result = strings.ReplaceAll(result, "{{ time }}", "**"+params.Time+"**")
|
||||
result = strings.ReplaceAll(result, "{{ serverName }}", "**"+params.ServerName+"**")
|
||||
result = strings.ReplaceAll(result, "{{ serverUUID }}", "`"+params.ServerUUID+"`")
|
||||
result = strings.ReplaceAll(result, "{{ upStatus }}", "**"+params.UpStatus+"**")
|
||||
result = strings.ReplaceAll(result, "{{ event }}", "**"+params.Event+"**")
|
||||
result = strings.ReplaceAll(result, "{{ message }}", "`"+params.Message+"`")
|
||||
result = strings.ReplaceAll(result, "{{ command }}", "`"+params.Command+"`")
|
||||
result = strings.ReplaceAll(result, "{{ result }}", "```bash\n"+params.Result+"\n```")
|
||||
result = strings.ReplaceAll(result, "{{ list.onlineServers }}", params.OnlineServers)
|
||||
result = strings.ReplaceAll(result, "{{ list.offlineServers }}", params.OfflineServers)
|
||||
result = strings.ReplaceAll(result, "{{ softwareVersion }}", params.SoftwareVersion)
|
||||
@@ -140,6 +184,13 @@ func Render(tmpl string, params Params, source ...string) string {
|
||||
result = strings.ReplaceAll(result, "{{ softwareDeveloper }}", params.SoftwareDeveloper)
|
||||
result = strings.ReplaceAll(result, "{{ softwareDescription }}", params.SoftwareDescription)
|
||||
}
|
||||
|
||||
// Alert values carry their own formatting (see RenderAlert), so they are
|
||||
// substituted identically in both branches.
|
||||
result = strings.ReplaceAll(result, "{{ subject }}", params.Subject)
|
||||
result = strings.ReplaceAll(result, "{{ source }}", params.Source)
|
||||
result = strings.ReplaceAll(result, "{{ content }}", params.Content)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user