ntopng/scripts/lua/modules/notifications/endpoints/slack.lua
2025-02-26 16:39:27 +01:00

200 lines
5.5 KiB
Lua

--
-- (C) 2021 - ntop.org
--
require "lua_utils"
local json = require "dkjson"
local alert_consts = require("alert_consts")
local alert_utils = require "alert_utils"
local format_utils = require "format_utils"
local slack = {
name = "Slack",
endpoint_params = {
{ param_name = "slack_sender_username" },
{ param_name = "slack_webhook" },
-- TODO: add severity (Errors, Errors and Warnings, All)
},
endpoint_template = {
script_key = "slack",
template_name = "slack_endpoint.template"
},
recipient_params = {
-- TODO: add channel
},
recipient_template = {
script_key = "slack",
template_name = "slack_recipient.template" -- TODO: add template
},
}
slack.EXPORT_FREQUENCY = 5
slack.prio = 500
local MAX_ALERTS_PER_MESSAGE = 5
local alert_severity_to_emoji = {
["info"] = ":information_source:",
["warning"] = ":warning:",
["error"] = ":exclamation:",
default = ":warning:",
}
-- ##############################################
-- @brief Returns the desided formatted output for recipient params
function slack.format_recipient_params(recipient_params)
return string.format("(%s)", slack.name)
end
-- ##############################################
local function recipient2sendMessageSettings(recipient)
local settings = {
webhook = recipient.endpoint_conf.slack_webhook,
sender_username = recipient.endpoint_conf.slack_sender_username,
}
return settings
end
-- ##############################################
function slack.sendMessage(entity_type, severity, text, settings)
local channel_name = entity_type
if isEmptyString(settings.webhook) or isEmptyString(settings.sender_username) then
return false
end
local message = {
icon_emoji = alert_severity_to_emoji[severity] or alert_severity_to_emoji.default,
username = settings.sender_username .. " [" .. string.upper(severity) .. "]",
text = text,
}
local json_message = json.encode(message)
return ntop.postHTTPJsonData("", "", settings.webhook, json_message)
end
-- ##############################################
-- This will try to send the most recent MAX_ALERTS_PER_MESSAGE messages for every
-- channel and severity (no more than one message for each, budget is ignored).
-- On success, it clears the queue.
-- On error, it leaves the queue unchagned to retry on next round.
function slack.dequeueRecipientAlerts(recipient, budget)
local settings = recipient2sendMessageSettings(recipient)
local more_available = true
local success = true
local error_message = nil
local delivered = 0
local discarded = 0
local failures = 0
-- Separate by severity and channel
local alerts_by_types = {}
local notifications = {}
local i = 0
while i < budget do
local notification = ntop.recipient_dequeue(recipient.recipient_id)
if notification then
if alert_utils.filter_notification(notification, recipient.recipient_id) then
notifications[#notifications + 1] = notification.alert
i = i + 1
else
discarded = discarded + 1
end
else
break
end
end
if #notifications == 0 then
more_available = false
goto done
end
for _, json_message in ipairs(notifications) do
local notif = json.decode(json_message)
notif.severity = map_score_to_severity(notif.score)
if notif.entity_id then
if not alerts_by_types[notif.entity_id] then
alerts_by_types[notif.entity_id] = {}
end
if not alerts_by_types[notif.entity_id][notif.severity] then
alerts_by_types[notif.entity_id][notif.severity] = {}
end
table.insert(alerts_by_types[notif.entity_id][notif.severity], notif)
end
end
for entity_type, by_severity in pairs(alerts_by_types) do
for severity, notifications in pairs(by_severity) do
local messages = {}
entity_type = alert_consts.alertEntityRaw(entity_type)
severity = alert_consts.alertSeverityRaw(severity)
-- Most recent notifications first
for _, notif in pairsByValues(notifications, alert_utils.notification_timestamp_rev) do
local msg = format_utils.formatMessage(notif, {nohtml=true, show_severity=false, show_entity=true})
table.insert(messages, msg)
if #messages >= MAX_ALERTS_PER_MESSAGE then
break
end
end
local missing = #notifications - #messages
if missing > 0 then
table.insert(messages, "NOTE: " .. missing .. " older messages have been suppressed")
end
messages = table.concat(messages, "\n")
if not slack.sendMessage(entity_type, severity, messages, settings) then
success = false
error_message = "Unable to send alerts to slack"
failures = failures + #notifications
goto done
else
delivered = delivered + #notifications
end
end
end
::done::
return {
success = success,
error_message = error_message,
delivered = delivered,
discarded = discarded,
failures = failures,
more_available = more_available,
}
end
-- ##############################################
function slack.runTest(recipient)
local message_info
local settings = recipient2sendMessageSettings(recipient)
local success = slack.sendMessage("interface", "info", "Slack notification is working", settings)
if success then
message_info = i18n("prefs.slack_sent_successfully", {channel="info"})
else
message_info = i18n("prefs.slack_send_error")
end
return success, message_info
end
-- ##############################################
return slack