mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-03-04 16:35:57 +00:00
Merge branch 'feature-slow-response-notification-cards' into feature-slow-response-notification
This commit is contained in:
commit
e4b79139ff
5 changed files with 168 additions and 29 deletions
|
@ -1458,17 +1458,38 @@ class Monitor extends BeanModel {
|
||||||
* Send a slow response notification about a monitor
|
* Send a slow response notification about a monitor
|
||||||
* @param {Monitor} monitor The monitor to send a notificaton about
|
* @param {Monitor} monitor The monitor to send a notificaton about
|
||||||
* @param {Bean} bean Status information about monitor
|
* @param {Bean} bean Status information about monitor
|
||||||
* @param {string} msg Notification text to be sent
|
* @param {object} slowStats Slow response information
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static async sendSlowResponseNotification(monitor, bean, msg) {
|
static async sendSlowResponseNotification(monitor, bean, slowStats) {
|
||||||
// Send notification
|
// Send notification
|
||||||
const notificationList = await Monitor.getNotificationList(monitor);
|
const notificationList = await Monitor.getNotificationList(monitor);
|
||||||
|
|
||||||
|
let text;
|
||||||
|
if (bean.pingStatus === NOMINAL) {
|
||||||
|
text = "🚀 Nominal";
|
||||||
|
} else {
|
||||||
|
text = "🐌 Slow";
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = `[${monitor.name}] [${text}] ${bean.pingMsg}`;
|
||||||
|
|
||||||
for (let notification of notificationList) {
|
for (let notification of notificationList) {
|
||||||
try {
|
try {
|
||||||
log.debug("monitor", `[${this.name}] Sending to ${notification.name}`);
|
const heartbeatJSON = bean.toJSON();
|
||||||
await Notification.send(JSON.parse(notification.config), msg);
|
|
||||||
|
// Override status with SLOW/NOMINAL, add slowStats
|
||||||
|
heartbeatJSON["status"] = bean.pingStatus;
|
||||||
|
heartbeatJSON["calculatedResponse"] = slowStats.calculatedResponse;
|
||||||
|
heartbeatJSON["calculatedThreshold"] = slowStats.calculatedThreshold;
|
||||||
|
heartbeatJSON["slowFor"] = slowStats.slowFor;
|
||||||
|
|
||||||
|
// Also provide the time in server timezone
|
||||||
|
heartbeatJSON["timezone"] = await UptimeKumaServer.getInstance().getTimezone();
|
||||||
|
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||||
|
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
|
||||||
|
|
||||||
|
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("monitor", `[${this.name}] Cannot send slow response notification to ${notification.name}`);
|
log.error("monitor", `[${this.name}] Cannot send slow response notification to ${notification.name}`);
|
||||||
log.error("monitor", e);
|
log.error("monitor", e);
|
||||||
|
@ -1564,11 +1585,6 @@ class Monitor extends BeanModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create stats to append to messages/logs
|
|
||||||
const methodDescription = [ "average", "max" ].includes(method) ? `${method} of last ${windowDuration}s` : method;
|
|
||||||
let msgStats = `Response: ${actualResponseTime}ms (${methodDescription}) | Threshold: ${threshold}ms (${thresholdDescription})`;
|
|
||||||
let pingMsg = `${actualResponseTime}ms resp. (${methodDescription})`;
|
|
||||||
|
|
||||||
// Verify valid response time was calculated
|
// Verify valid response time was calculated
|
||||||
if (actualResponseTime === 0 || !Number.isInteger(actualResponseTime)) {
|
if (actualResponseTime === 0 || !Number.isInteger(actualResponseTime)) {
|
||||||
log.debug("monitor", `[${this.name}] Failed to calculate valid response time`);
|
log.debug("monitor", `[${this.name}] Failed to calculate valid response time`);
|
||||||
|
@ -1581,6 +1597,15 @@ class Monitor extends BeanModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create stats to append to messages/logs
|
||||||
|
const methodDescription = [ "average", "max" ].includes(method) ? `${method} of last ${windowDuration}s` : method;
|
||||||
|
let msgStats = `Response: ${actualResponseTime}ms (${methodDescription}) | Threshold: ${threshold}ms (${thresholdDescription})`;
|
||||||
|
const slowStats = {
|
||||||
|
calculatedResponse: `${actualResponseTime}ms (${methodDescription})`,
|
||||||
|
calculatedThreshold: `${threshold}ms (${thresholdDescription})`,
|
||||||
|
slowFor: `${bean.slowResponseCount * monitor.interval}s`,
|
||||||
|
};
|
||||||
|
|
||||||
bean.pingThreshold = threshold;
|
bean.pingThreshold = threshold;
|
||||||
|
|
||||||
// Responding normally
|
// Responding normally
|
||||||
|
@ -1591,13 +1616,12 @@ class Monitor extends BeanModel {
|
||||||
} else {
|
} else {
|
||||||
msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`;
|
msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`;
|
||||||
log.debug("monitor", `[${this.name}] Returned to normal response time | ${msgStats}`);
|
log.debug("monitor", `[${this.name}] Returned to normal response time | ${msgStats}`);
|
||||||
let msg = `[${this.name}] Returned to Normal Response Time \n${msgStats}`;
|
|
||||||
Monitor.sendSlowResponseNotification(monitor, bean, msg);
|
|
||||||
|
|
||||||
// Mark important (SLOW -> NOMINAL)
|
// Mark important (SLOW -> NOMINAL)
|
||||||
pingMsg += ` < ${threshold}ms`;
|
|
||||||
bean.pingImportant = true;
|
bean.pingImportant = true;
|
||||||
bean.pingMsg = pingMsg;
|
bean.pingMsg = `Returned to Normal Response Time \n${msgStats}`;
|
||||||
|
|
||||||
|
Monitor.sendSlowResponseNotification(monitor, bean, slowStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset slow response count
|
// Reset slow response count
|
||||||
|
@ -1610,28 +1634,29 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
// Always send first notification
|
// Always send first notification
|
||||||
if (bean.slowResponseCount === 1) {
|
if (bean.slowResponseCount === 1) {
|
||||||
log.debug("monitor", `[${this.name}] Responded slowly, sending notification | ${msgStats}`);
|
log.debug("monitor", `[${this.name}] Responded slow, sending notification | ${msgStats}`);
|
||||||
let msg = `[${this.name}] Responded Slowly \n${msgStats}`;
|
|
||||||
Monitor.sendSlowResponseNotification(monitor, bean, msg);
|
|
||||||
|
|
||||||
// Mark important (NOMINAL -> SLOW)
|
// Mark important (NOMINAL -> SLOW)
|
||||||
pingMsg += ` > ${threshold}ms`;
|
|
||||||
bean.pingImportant = true;
|
bean.pingImportant = true;
|
||||||
bean.pingMsg = pingMsg;
|
bean.pingMsg = `Responded Slow \n${msgStats}`;
|
||||||
|
|
||||||
|
Monitor.sendSlowResponseNotification(monitor, bean, slowStats);
|
||||||
|
|
||||||
// Send notification every x times
|
// Send notification every x times
|
||||||
} else if (this.slowResponseNotificationResendInterval > 0) {
|
} else if (this.slowResponseNotificationResendInterval > 0) {
|
||||||
if (((bean.slowResponseCount) % this.slowResponseNotificationResendInterval) === 0) {
|
if (((bean.slowResponseCount) % this.slowResponseNotificationResendInterval) === 0) {
|
||||||
// Send notification again, because we are still responding slow
|
// Send notification again, because we are still responding slow
|
||||||
msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`;
|
msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`;
|
||||||
log.debug("monitor", `[${this.name}] Still responding slowly, sendSlowResponseNotification again | ${msgStats}`);
|
log.debug("monitor", `[${this.name}] Still responding slow, sendSlowResponseNotification again | ${msgStats}`);
|
||||||
let msg = `[${this.name}] Still Responding Slowly \n${msgStats}`;
|
|
||||||
Monitor.sendSlowResponseNotification(monitor, bean, msg);
|
bean.pingMsg = `Still Responding Slow \n${msgStats}`;
|
||||||
|
|
||||||
|
Monitor.sendSlowResponseNotification(monitor, bean, slowStats);
|
||||||
} else {
|
} else {
|
||||||
log.debug("monitor", `[${this.name}] Still responding slowly, waiting for resend interal | ${msgStats}`);
|
log.debug("monitor", `[${this.name}] Still responding slow, waiting for resend interal | ${msgStats}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug("monitor", `[${this.name}] Still responding slowly, but resend is disabled | ${msgStats}`);
|
log.debug("monitor", `[${this.name}] Still responding slow, but resend is disabled | ${msgStats}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { DOWN, UP } = require("../../src/util");
|
const { DOWN, UP, SLOW, NOMINAL } = require("../../src/util");
|
||||||
|
|
||||||
class Discord extends NotificationProvider {
|
class Discord extends NotificationProvider {
|
||||||
|
|
||||||
name = "discord";
|
name = "discord";
|
||||||
|
supportSlowNotifications = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
|
@ -115,12 +116,93 @@ class Discord extends NotificationProvider {
|
||||||
|
|
||||||
await axios.post(notification.discordWebhookUrl, discordupdata);
|
await axios.post(notification.discordWebhookUrl, discordupdata);
|
||||||
return okMsg;
|
return okMsg;
|
||||||
|
} else if (heartbeatJSON["status"] === SLOW) {
|
||||||
|
let discordslowdata = {
|
||||||
|
username: discordDisplayName,
|
||||||
|
embeds: [{
|
||||||
|
title: "🐌 Your service " + monitorJSON["name"] + " responded slow. 🐌",
|
||||||
|
color: 16761095,
|
||||||
|
timestamp: heartbeatJSON["time"],
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Service Name",
|
||||||
|
value: monitorJSON["name"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
|
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
|
value: heartbeatJSON["localDateTime"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ping",
|
||||||
|
value: heartbeatJSON["calculatedResponse"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Threshold",
|
||||||
|
value: heartbeatJSON["calculatedThreshold"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.discordPrefixMessage) {
|
||||||
|
discordslowdata.content = notification.discordPrefixMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios.post(notification.discordWebhookUrl, discordslowdata);
|
||||||
|
return okMsg;
|
||||||
|
} else if (heartbeatJSON["status"] === NOMINAL) {
|
||||||
|
let discordnominaldata = {
|
||||||
|
username: discordDisplayName,
|
||||||
|
embeds: [{
|
||||||
|
title: "🚀 Your service " + monitorJSON["name"] + " is responding normally! 🚀",
|
||||||
|
color: 65280,
|
||||||
|
timestamp: heartbeatJSON["time"],
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Service Name",
|
||||||
|
value: monitorJSON["name"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
|
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
|
value: heartbeatJSON["localDateTime"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ping",
|
||||||
|
value: heartbeatJSON["calculatedResponse"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Threshold",
|
||||||
|
value: heartbeatJSON["calculatedThreshold"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Slow For",
|
||||||
|
value: heartbeatJSON["slowFor"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.discordPrefixMessage) {
|
||||||
|
discordnominaldata.content = notification.discordPrefixMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios.post(notification.discordWebhookUrl, discordnominaldata);
|
||||||
|
return okMsg;
|
||||||
|
} else {
|
||||||
|
this.throwGeneralAxiosError("Not sure why we're here");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.throwGeneralAxiosError(error);
|
this.throwGeneralAxiosError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Discord;
|
module.exports = Discord;
|
||||||
|
|
|
@ -6,6 +6,12 @@ class NotificationProvider {
|
||||||
*/
|
*/
|
||||||
name = undefined;
|
name = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the notification provider support slow response notifications?
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
supportSlowNotifications = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a notification
|
* Send a notification
|
||||||
* @param {BeanModel} notification Notification to send
|
* @param {BeanModel} notification Notification to send
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { setSettings, setting } = require("../util-server");
|
const { setSettings, setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
const { getMonitorRelativeURL, UP, DOWN, NOMINAL, SLOW } = require("../../src/util");
|
||||||
|
|
||||||
class Slack extends NotificationProvider {
|
class Slack extends NotificationProvider {
|
||||||
|
|
||||||
name = "slack";
|
name = "slack";
|
||||||
|
supportSlowNotifications = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deprecated property notification.slackbutton
|
* Deprecated property notification.slackbutton
|
||||||
|
@ -50,6 +51,23 @@ class Slack extends NotificationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
const textMsg = "Uptime Kuma Alert";
|
const textMsg = "Uptime Kuma Alert";
|
||||||
|
|
||||||
|
let color;
|
||||||
|
switch (heartbeatJSON["status"]) {
|
||||||
|
case UP:
|
||||||
|
case NOMINAL:
|
||||||
|
color = "#2eb886";
|
||||||
|
break;
|
||||||
|
case SLOW:
|
||||||
|
color = "#ffc107";
|
||||||
|
break;
|
||||||
|
case DOWN:
|
||||||
|
color = "#e01e5a";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = "#0dcaf0";
|
||||||
|
}
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
"text": `${textMsg}\n${msg}`,
|
"text": `${textMsg}\n${msg}`,
|
||||||
"channel": notification.slackchannel,
|
"channel": notification.slackchannel,
|
||||||
|
@ -57,7 +75,7 @@ class Slack extends NotificationProvider {
|
||||||
"icon_emoji": notification.slackiconemo,
|
"icon_emoji": notification.slackiconemo,
|
||||||
"attachments": [
|
"attachments": [
|
||||||
{
|
{
|
||||||
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
|
"color": color,
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"type": "header",
|
"type": "header",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { log } = require("../src/util");
|
const { log, SLOW, NOMINAL } = require("../src/util");
|
||||||
const Alerta = require("./notification-providers/alerta");
|
const Alerta = require("./notification-providers/alerta");
|
||||||
const AlertNow = require("./notification-providers/alertnow");
|
const AlertNow = require("./notification-providers/alertnow");
|
||||||
const AliyunSms = require("./notification-providers/aliyun-sms");
|
const AliyunSms = require("./notification-providers/aliyun-sms");
|
||||||
|
@ -149,7 +149,15 @@ class Notification {
|
||||||
*/
|
*/
|
||||||
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
if (this.providerList[notification.type]) {
|
if (this.providerList[notification.type]) {
|
||||||
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
|
if ((heartbeatJSON?.status === SLOW || heartbeatJSON?.status === NOMINAL) && !this.providerList[notification.type].supportSlowNotifications) {
|
||||||
|
// This is a SLOW/NOMINAL notification where the provider does NOT support card notificatons yet
|
||||||
|
// TODO Ideally, this goes away once all the notification providers support slow response notification cards
|
||||||
|
log.debug("notification", `${notification.type} does not support card notifications for SLOW/NOMINAL events yet. Sending plain text message.`);
|
||||||
|
return this.providerList[notification.type].send(notification, msg);
|
||||||
|
} else {
|
||||||
|
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Notification type is not supported");
|
throw new Error("Notification type is not supported");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue