zabbix_export: version: '7.0' media_types: - name: Mattermost type: WEBHOOK parameters: - name: alert_message value: '{ALERT.MESSAGE}' - name: alert_subject value: '{ALERT.SUBJECT}' - name: bot_token value: '' - name: discovery_host_dns value: '{DISCOVERY.DEVICE.DNS}' - name: discovery_host_ip value: '{DISCOVERY.DEVICE.IPADDRESS}' - name: event_date value: '{EVENT.DATE}' - name: event_id value: '{EVENT.ID}' - name: event_nseverity value: '{EVENT.NSEVERITY}' - name: event_opdata value: '{EVENT.OPDATA}' - name: event_recovery_date value: '{EVENT.RECOVERY.DATE}' - name: event_recovery_time value: '{EVENT.RECOVERY.TIME}' - name: event_severity value: '{EVENT.SEVERITY}' - name: event_source value: '{EVENT.SOURCE}' - name: event_tags value: '{EVENT.TAGS}' - name: event_time value: '{EVENT.TIME}' - name: event_update_date value: '{EVENT.UPDATE.DATE}' - name: event_update_status value: '{EVENT.UPDATE.STATUS}' - name: event_update_time value: '{EVENT.UPDATE.TIME}' - name: event_value value: '{EVENT.VALUE}' - name: host_ip value: '{HOST.IP}' - name: host_name value: '{HOST.HOST}' - name: mattermost_url value: '' - name: send_mode value: alarm - name: send_to value: '{ALERT.SENDTO}' - name: trigger_description value: '{TRIGGER.DESCRIPTION}' - name: trigger_id value: '{TRIGGER.ID}' - name: zabbix_url value: '{$ZABBIX.URL}' attempts: '1' script: | var SEVERITY_COLORS = [ '#97AAB3', '#7499FF', '#FFC859', '#FFA059', '#E97659', '#E45959' ]; var RESOLVE_COLOR = '#009900'; var SEND_MODE_HANDLERS = { alarm: handlerAlarm, event: handlerEvent }; if (!String.prototype.format) { String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return number in args ? args[number] : match ; }); }; } function isEventProblem(params) { return params.event_value == 1 && params.event_update_status == 0 ; } function isEventUpdate(params) { return params.event_value == 1 && params.event_update_status == 1 ; } function isEventResolve(params) { return params.event_value == 0; } function getPermalink(mattermost_url, team_name, postid) { return '{0}/{1}/pl/{2}'.format( mattermost_url.replace(/\/+$/, ''), team_name, postid ); } function getChannel(send_to) { switch (true) { case /.+\/#.+/.test(send_to): return getChannelByName(send_to); case /@.+/.test(send_to): return getDirectChannel(send_to); default: return getChannelByID(send_to); } } function getChannelByName(send_to) { var team_chan = send_to .trim() .split('/#'); var resp = JSON.parse(req.get( Mattermost.channel_byname.format(team_chan[0], team_chan[1]), JSON.stringify(fields) ) ); if (req.getStatus() != 200) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } return resp; } function getDirectChannel(send_to) { Zabbix.log(5, '[ Mattermost Webhook ] Call {0}({1})'.format( arguments.callee.name, JSON.stringify(arguments) )); var teamUser = send_to .trim() .split('/@'), bot = getBotUser(), user = getUserByName(teamUser[1]); var resp = JSON.parse(req.post( Mattermost.direct_channel, JSON.stringify([bot.id, user.id]) ) ); Zabbix.log(5, '[ Mattermost Webhook ] Result {0}: {1}'.format( arguments.callee.name, JSON.stringify(resp) )); if (req.getStatus() != 201) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } resp.team_name = teamUser[0]; return resp; } function getChannelByID(channelID) { Zabbix.log(5, '[ Mattermost Webhook ] Call {0}({1})'.format( arguments.callee.name, JSON.stringify(arguments) )); var resp = JSON.parse(req.get( Mattermost.get_channel.format(channelID), JSON.stringify(fields) ) ); Zabbix.log(5, '[ Mattermost Webhook ] Result {0}: {1}'.format( arguments.callee.name, JSON.stringify(resp) )); if (req.getStatus() != 200) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } return resp; } function getBotUser() { Zabbix.log(5, '[ Mattermost Webhook ] Call {0}({1})'.format( arguments.callee.name, JSON.stringify(arguments) )); var resp = JSON.parse(req.get( Mattermost.bot_user, JSON.stringify(fields) ) ); Zabbix.log(5, '[ Mattermost Webhook ] Result {0}: {1}'.format( arguments.callee.name, JSON.stringify(resp) )); if (req.getStatus() != 200) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } return resp; } function getUserByName(userName) { Zabbix.log(5, '[ Mattermost Webhook ] Call {0}({1})'.format( arguments.callee.name, JSON.stringify(arguments) )); var resp = JSON.parse(req.get( Mattermost.user_byname.format(userName), JSON.stringify(fields) ) ); Zabbix.log(5, '[ Mattermost Webhook ] Result {0}: {1}'.format( arguments.callee.name, JSON.stringify(resp) )); if (req.getStatus() != 200) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } return resp; } function getTeamByID(teamID) { Zabbix.log(5, '[ Mattermost Webhook ] Call {0}({1})'.format( arguments.callee.name, JSON.stringify(arguments) )); var resp = JSON.parse(req.get( Mattermost.get_team.format(teamID), JSON.stringify(fields) ) ); Zabbix.log(5, '[ Mattermost Webhook ] Result {0}: {1}'.format( arguments.callee.name, JSON.stringify(resp) )); if (req.getStatus() != 200) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } return resp; } function createProblemURL(zabbix_url, triggerid, eventid, event_source) { var problem_url = ''; if (event_source === '0') { problem_url = '{0}/tr_events.php?triggerid={1}&eventid={2}' .format( zabbix_url, triggerid, eventid ); } else { problem_url = zabbix_url; } return problem_url; } function getTagValue(event_tags, key) { var pattern = new RegExp('(' + key + ':.+)'); var tagValue = event_tags .split(',') .filter(function (v) { return v.match(pattern); }) .map(function (v) { return v.split(':')[1]; })[0] || 0; return tagValue; } function handlerAlarm(req, params) { var channel = getChannel(params.send_to); var fields = { channel_id: channel.id, props: {} }; if (isEventProblem(params)) { var team_name = channel.team_name ? channel.team_name : getTeamByID(channel.team_id).name; fields.props.attachments = [ createMessage( SEVERITY_COLORS[params.event_nseverity] || 0, params.event_date, params.event_time, createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source) ) ]; var resp = JSON.parse(req.post( Mattermost.post_message, JSON.stringify(fields) ) ); if (req.getStatus() != 201) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } result.tags.__mattermost_post_id = resp.id; result.tags.__mattermost_channel_id = channel.id; result.tags.__mattermost_channel_name = channel.name; result.tags.__mattermost_message_link = getPermalink( params.mattermost_url, team_name, resp.id ); } else if (isEventUpdate(params)) { fields.root_id = getTagValue(params.event_tags, 'mattermost_post_id'); if (params.event_source === '0') {} fields.props.attachments = [ createMessage( SEVERITY_COLORS[params.event_nseverity] || 0, params.event_update_date, params.event_update_time, createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source), true ) ]; resp = JSON.parse(req.post( Mattermost.post_message, JSON.stringify(fields) ) ); if (req.getStatus() != 201) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } } else if (isEventResolve(params)) { fields.channel_id = getTagValue(params.event_tags, 'mattermost_channel_id'); fields.id = getTagValue(params.event_tags, 'mattermost_post_id'); fields.props.attachments = [ createMessage( RESOLVE_COLOR, params.event_date, params.event_time, createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source) ) ]; var post_id = getTagValue(params.event_tags, 'mattermost_post_id'); resp = JSON.parse(req.put( Mattermost.chat_update.format(post_id), JSON.stringify(fields) ) ); if (req.getStatus() != 200) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } } } function handlerEvent(req, params) { var channel = getChannel(params.send_to); var fields = { channel_id: channel.id, props: {} }; if (isEventProblem(params)) { var team_name = channel.team_name ? channel.team_name : getTeamByID(channel.team_id).name; fields.props.attachments = [ createMessage( SEVERITY_COLORS[params.event_nseverity] || 0, params.event_date, params.event_time, createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source) ) ]; var resp = JSON.parse(req.post(Mattermost.post_message, JSON.stringify(fields))); if (req.getStatus() != 201) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } result.tags.__mattermost_channel_name = channel.name; result.tags.__mattermost_message_link = getPermalink( params.mattermost_url, team_name, resp.id ); } else if (isEventUpdate(params)) { fields.props.attachments = [ createMessage( SEVERITY_COLORS[params.event_nseverity] || 0, params.event_update_date, params.event_update_time, createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source), false ) ]; resp = JSON.parse(req.post(Mattermost.post_message, JSON.stringify(fields))); if (req.getStatus() != 201) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } } else if (isEventResolve(params)) { fields.props.attachments = [ createMessage( RESOLVE_COLOR, params.event_recovery_date, params.event_recovery_time, createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source) ) ]; resp = JSON.parse(req.post(Mattermost.post_message, JSON.stringify(fields))); if (req.getStatus() != 201) { throw '[{0}] {1}'.format(resp.status_code, resp.message); } } } function createMessage( event_severity_color, event_date, event_time, problem_url, isShort ) { var message = { fallbac: params.alert_subject, title: params.alert_subject, color: event_severity_color, title_link: problem_url, footer: problem_url, fields: [ { title: 'Host', value: '{0} [{1}]'.format(params.host_name, params.host_ip), short: true }, { title: 'Event time', value: '{0} {1}'.format(event_date, event_time), short: true } ], }; if (params.event_source === '0') { message.fields.push( { title: 'Severity', value: params.event_severity, short: true }, { title: 'Opdata', value: params.event_opdata, short: true } ); } if (!isShort && params.event_source === '0') { message.fields.push( { title: 'Event tags', value: '`{0}`'.format(params.event_tags.replace(/__.+?:(.+?,|.+)/g, '') || 'None'), short: true }, { title: 'Trigger description', value: params.trigger_description, short: true } ); } if (params.event_source !== '0' || params.event_update_status === '1') { message.fields.push( { title: 'Details', value: params.alert_message, short: false } ); } return message; } function validateParams(params) { if (typeof params.bot_token !== 'string' || params.bot_token.trim() === '') { throw 'Field "bot_token" cannot be empty'; } if (isNaN(params.event_id)) { throw 'Field "event_id" is not a number'; } if ([0, 1, 2, 3].indexOf(parseInt(params.event_source)) === -1) { throw 'Incorrect "event_source" parameter given: "' + params.event_source + '".\nMust be 0-3.'; } if (params.event_source !== '0') { params.event_nseverity = '0'; params.event_severity = 'Not classified'; params.event_update_status = '0'; params.send_mode = 'event'; } if (params.event_source === '1' || params.event_source === '2') { params.event_value = '1'; } if (params.event_source === '1') { params.host_name = params.discovery_host_dns; params.host_ip = params.discovery_host_ip; } if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) { throw 'Incorrect "event_nseverity" parameter given: ' + params.event_nseverity + '\nMust be 0-5.'; } if (typeof params.event_severity !== 'string' || params.event_severity.trim() === '') { throw 'Field "event_severity" cannot be empty'; } if (params.event_update_status !== '0' && params.event_update_status !== '1') { throw 'Incorrect "event_update_status" parameter given: ' + params.event_update_status + '\nMust be 0 or 1.'; } if (params.event_value !== '0' && params.event_value !== '1') { throw 'Incorrect "event_value" parameter given: ' + params.event_value + '\nMust be 0 or 1.'; } if (typeof params.host_ip !== 'string' || params.host_ip.trim() === '') { throw 'Field "host_ip" cannot be empty'; } if (typeof params.host_name !== 'string' || params.host_name.trim() === '') { throw 'Field "host_name" cannot be empty'; } if (typeof params.mattermost_url !== 'string' || params.mattermost_url.trim() === '') { throw 'Field "mattermost_url" cannot be empty'; } if (!/^(http|https):\/\/.+/.test(params.mattermost_url)) { throw 'Field "mattermost_url" must contain a schema'; } if (['alarm', 'event'].indexOf(params.send_mode) === -1) { throw 'Incorrect "send_mode" parameter given: ' + params.send_mode + '\nMust be "alarm" or "event".'; } if (typeof params.send_to !== 'string' || params.send_to.trim() === '') { throw 'Field "send_to" cannot be empty'; } if (isNaN(params.trigger_id) && params.event_source === '0') { throw 'field "trigger_id" is not a number'; } if (typeof params.zabbix_url !== 'string' || params.zabbix_url.trim() === '') { throw 'Field "zabbix_url" cannot be empty'; } if (!/^(http|https):\/\/.+/.test(params.zabbix_url)) { throw 'Field "zabbix_url" must contain a schema'; } } try { var params = JSON.parse(value); validateParams(params); var req = new HttpRequest(), fields = {}, result = {tags: {}}; if (typeof params.HTTPProxy === 'string' && params.HTTPProxy.trim() !== '') { req.setProxy(params.HTTPProxy); } req.addHeader('Content-Type: application/json; charset=utf-8'); req.addHeader('Authorization: Bearer ' + params.bot_token); params.mattermost_url = params.mattermost_url.replace(/\/+$/, ''); params.zabbix_url = params.zabbix_url.replace(/\/+$/, ''); var APIEndpoint = params.mattermost_url + '/api/v4/'; var Mattermost = { post_message: APIEndpoint + 'posts', get_channel: APIEndpoint + 'channels/{0}', get_team: APIEndpoint + 'teams/{0}', chat_update: APIEndpoint + 'posts/{0}', direct_channel: APIEndpoint + 'channels/direct', channel_byname: APIEndpoint + 'teams/name/{0}/channels/name/{1}', user_byname: APIEndpoint + 'users/username/{0}', bot_user: APIEndpoint + 'users/me' }; params.send_mode = params.send_mode.toLowerCase(); params.send_mode = params.send_mode in SEND_MODE_HANDLERS ? params.send_mode : 'alarm'; SEND_MODE_HANDLERS[params.send_mode](req, params); if (params.event_source === '0') { return JSON.stringify(result); } else { return 'OK'; } } catch (error) { Zabbix.log(4, '[ Mattermost Webhook ] Mattermost notification failed: ' + error); throw 'Mattermost notification failed: ' + error; } process_tags: 'YES' show_event_menu: 'YES' event_menu_url: '{EVENT.TAGS.__mattermost_message_link}' event_menu_name: 'Open in Mattermost: {EVENT.TAGS.__mattermost_channel_name}' message_templates: - event_source: TRIGGERS operation_mode: PROBLEM subject: 'Problem: {EVENT.NAME}' message: | Problem started at {EVENT.TIME} on {EVENT.DATE} Problem name: {EVENT.NAME} Host: {HOST.NAME} Severity: {EVENT.SEVERITY} Operational data: {EVENT.OPDATA} Original problem ID: {EVENT.ID} {TRIGGER.URL} - event_source: TRIGGERS operation_mode: RECOVERY subject: 'Resolved in {EVENT.DURATION}: {EVENT.NAME}' message: | Problem has been resolved at {EVENT.RECOVERY.TIME} on {EVENT.RECOVERY.DATE} Problem name: {EVENT.NAME} Problem duration: {EVENT.DURATION} Host: {HOST.NAME} Severity: {EVENT.SEVERITY} Original problem ID: {EVENT.ID} {TRIGGER.URL} - event_source: TRIGGERS operation_mode: UPDATE subject: 'Updated problem in {EVENT.AGE}: {EVENT.NAME}' message: | {USER.FULLNAME} {EVENT.UPDATE.ACTION} problem at {EVENT.UPDATE.DATE} {EVENT.UPDATE.TIME}. {EVENT.UPDATE.MESSAGE} Current problem status is {EVENT.STATUS}, age is {EVENT.AGE}, acknowledged: {EVENT.ACK.STATUS}. - event_source: DISCOVERY operation_mode: PROBLEM subject: 'Discovery: {DISCOVERY.DEVICE.STATUS} {DISCOVERY.DEVICE.IPADDRESS}' message: | Discovery rule: {DISCOVERY.RULE.NAME} Device IP: {DISCOVERY.DEVICE.IPADDRESS} Device DNS: {DISCOVERY.DEVICE.DNS} Device status: {DISCOVERY.DEVICE.STATUS} Device uptime: {DISCOVERY.DEVICE.UPTIME} Device service name: {DISCOVERY.SERVICE.NAME} Device service port: {DISCOVERY.SERVICE.PORT} Device service status: {DISCOVERY.SERVICE.STATUS} Device service uptime: {DISCOVERY.SERVICE.UPTIME} - event_source: AUTOREGISTRATION operation_mode: PROBLEM subject: 'Autoregistration: {HOST.HOST}' message: | Host name: {HOST.HOST} Host IP: {HOST.IP} Agent port: {HOST.PORT}