diff --git a/db/knex_migrations/2023-11-09-slow-response-notification.js b/db/knex_migrations/2023-11-09-slow-response-notification.js new file mode 100644 index 00000000..88ea4a94 --- /dev/null +++ b/db/knex_migrations/2023-11-09-slow-response-notification.js @@ -0,0 +1,33 @@ +exports.up = function (knex) { + // add various slow_response_notification parameters + return knex.schema + .alterTable("monitor", function (table) { + table.boolean("slow_response_notification").notNullable().defaultTo(false); + table.string("slow_response_notification_method").notNullable().defaultTo("average"); + table.integer("slow_response_notification_range").notNullable().defaultTo(300); + table.string("slow_response_notification_threshold_method").notNullable().defaultTo("threshold-relative-24-hour"); + table.integer("slow_response_notification_threshold").notNullable().defaultTo(2500); + table.float("slow_response_notification_threshold_multiplier").notNullable().defaultTo(5.0); + table.integer("slow_response_notification_resend_interval").notNullable().defaultTo(0); + }) + .alterTable("heartbeat", function (table) { + table.integer("slow_response_count").notNullable().defaultTo(0); + }); +}; + +exports.down = function (knex) { + // remove various slow_response_notification parameters + return knex.schema + .alterTable("monitor", function (table) { + table.dropColumn("slow_response_notification"); + table.dropColumn("slow_response_notification_method"); + table.dropColumn("slow_response_notification_range"); + table.dropColumn("slow_response_notification_threshold_method"); + table.dropColumn("slow_response_notification_threshold"); + table.dropColumn("slow_response_notification_threshold_multiplier"); + table.dropColumn("slow_response_notification_resend_interval"); + }) + .alterTable("heartbeat", function (table) { + table.dropColumn("slow_response_count"); + }); +}; diff --git a/db/knex_migrations/2023-11-10-slow-response-visual-improvements.js b/db/knex_migrations/2023-11-10-slow-response-visual-improvements.js new file mode 100644 index 00000000..d85e1cfd --- /dev/null +++ b/db/knex_migrations/2023-11-10-slow-response-visual-improvements.js @@ -0,0 +1,21 @@ +exports.up = function (knex) { + // add various slow response parameters + return knex.schema + .alterTable("heartbeat", function (table) { + table.integer("ping_status").nullable().defaultTo(null); + table.integer("ping_threshold").nullable().defaultTo(null); + table.boolean("ping_important").notNullable().defaultTo(0); + table.string("ping_msg").nullable().defaultTo(null); + }); +}; + +exports.down = function (knex) { + // remove various slow response parameters + return knex.schema + .alterTable("heartbeat", function (table) { + table.dropColumn("ping_status"); + table.dropColumn("ping_threshold"); + table.dropColumn("ping_important"); + table.dropColumn("ping_msg"); + }); +}; diff --git a/package-lock.json b/package-lock.json index a6ef4f03..2e39463c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,6 +100,7 @@ "bootstrap": "5.1.3", "chart.js": "~4.2.1", "chartjs-adapter-dayjs-4": "~1.0.4", + "chartjs-plugin-annotation": "~3.0.1", "concurrently": "^7.1.0", "core-js": "~3.26.1", "cronstrue": "~2.24.0", @@ -5735,6 +5736,15 @@ "dayjs": "^1.9.7" } }, + "node_modules/chartjs-plugin-annotation": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.0.1.tgz", + "integrity": "sha512-hlIrXXKqSDgb+ZjVYHefmlZUXK8KbkCPiynSVrTb/HjTMkT62cOInaT1NTQCKtxKKOm9oHp958DY3RTAFKtkHg==", + "dev": true, + "peerDependencies": { + "chart.js": ">=4.0.0" + } + }, "node_modules/check-password-strength": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/check-password-strength/-/check-password-strength-2.0.10.tgz", diff --git a/package.json b/package.json index 567efa1b..80347232 100644 --- a/package.json +++ b/package.json @@ -165,6 +165,7 @@ "bootstrap": "5.1.3", "chart.js": "~4.2.1", "chartjs-adapter-dayjs-4": "~1.0.4", + "chartjs-plugin-annotation": "~3.0.1", "concurrently": "^7.1.0", "core-js": "~3.26.1", "cronstrue": "~2.24.0", diff --git a/server/client.js b/server/client.js index 58ed8f95..32a91c0a 100644 --- a/server/client.js +++ b/server/client.js @@ -55,7 +55,7 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = monitorID, ]); - let result = list.reverse(); + let result = R.convertToBeans("heartbeat", list.reverse()); if (toUser) { io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite); diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js index 9e972a37..64892120 100644 --- a/server/model/heartbeat.js +++ b/server/model/heartbeat.js @@ -6,6 +6,9 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); * 1 = UP * 2 = PENDING * 3 = MAINTENANCE + * pingStatus: + * 4 = SLOW + * 5 = NOMINAL */ class Heartbeat extends BeanModel { @@ -37,6 +40,10 @@ class Heartbeat extends BeanModel { important: this._important, duration: this._duration, retries: this._retries, + pingThreshold: this._pingThreshold, + pingStatus: this._pingStatus, + pingImportant: this._pingImportant, + pingMsg: this._pingMsg, }; } diff --git a/server/model/monitor.js b/server/model/monitor.js index 1667b83a..928044af 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1,8 +1,7 @@ const dayjs = require("dayjs"); const axios = require("axios"); const { Prometheus } = require("../prometheus"); -const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, - SQL_DATETIME_FORMAT +const { log, UP, DOWN, PENDING, MAINTENANCE, NOMINAL, SLOW, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, SQL_DATETIME_FORMAT } = require("../../src/util"); const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery, redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal @@ -34,6 +33,9 @@ const rootCertificates = rootCertificatesFingerprints(); * 1 = UP * 2 = PENDING * 3 = MAINTENANCE + * pingStatus: + * 4 = SLOW + * 5 = NOMINAL */ class Monitor extends BeanModel { @@ -142,6 +144,13 @@ class Monitor extends BeanModel { mqttCheckType: this.mqttCheckType, databaseQuery: this.databaseQuery, authMethod: this.authMethod, + slowResponseNotification: this.isEnabledSlowResponseNotification(), + slowResponseNotificationMethod: this.slowResponseNotificationMethod, + slowResponseNotificationRange: this.slowResponseNotificationRange, + slowResponseNotificationThresholdMethod: this.slowResponseNotificationThresholdMethod, + slowResponseNotificationThreshold: this.slowResponseNotificationThreshold, + slowResponseNotificationThresholdMultiplier: this.slowResponseNotificationThresholdMultiplier, + slowResponseNotificationResendInterval: this.slowResponseNotificationResendInterval, grpcUrl: this.grpcUrl, grpcProtobuf: this.grpcProtobuf, grpcMethod: this.grpcMethod, @@ -309,6 +318,14 @@ class Monitor extends BeanModel { return Boolean(this.gamedigGivenPortOnly); } + /** + * Is the slow response notification enabled? + * @returns {boolean} Slow response notification is enabled? + */ + isEnabledSlowResponseNotification() { + return Boolean(this.slowResponseNotification); + } + /** * Parse to boolean * @returns {boolean} Kafka Producer Ssl enabled? @@ -371,6 +388,7 @@ class Monitor extends BeanModel { bean.time = R.isoDateTimeMillis(dayjs.utc()); bean.status = DOWN; bean.downCount = previousBeat?.downCount || 0; + bean.slowResponseCount = previousBeat?.slowResponseCount || 0; if (this.isUpsideDown()) { bean.status = flipStatus(bean.status); @@ -983,6 +1001,12 @@ class Monitor extends BeanModel { let endTimeDayjs = await uptimeCalculator.update(bean.status, parseFloat(bean.ping)); bean.end_time = R.isoDateTimeMillis(endTimeDayjs); + // Check if response time is slow + if (this.isEnabledSlowResponseNotification() && !isFirstBeat) { + log.debug("monitor", `[${this.name}] Check if response is slow`); + await this.checkSlowResponseNotification(this, bean); + } + // Send to frontend log.debug("monitor", `[${this.name}] Send to socket`); io.to(this.user_id).emit("heartbeat", bean.toJSON()); @@ -1449,6 +1473,213 @@ class Monitor extends BeanModel { } } + /** + * Send a slow response notification about a monitor + * @param {Monitor} monitor The monitor to send a notificaton about + * @param {Bean} bean Status information about monitor + * @param {object} slowStats Slow response information + * @returns {void} + */ + static async sendSlowResponseNotification(monitor, bean, slowStats) { + // Send notification + 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) { + try { + const heartbeatJSON = bean.toJSON(); + + // 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) { + log.error("monitor", `[${this.name}] Cannot send slow response notification to ${notification.name}`); + log.error("monitor", e); + } + } + } + + /** + * Check if heartbeat response time is slower than threshold. + * @param {Monitor} monitor The monitor to send a notification about + * @param {Bean} bean Status information about monitor + * @returns {Promise} + */ + async checkSlowResponseNotification(monitor, bean) { + + if (bean.status !== UP) { + log.debug("monitor", `[${this.name}] Monitor status is not UP, skipping slow response check`); + return; + } + + const method = monitor.slowResponseNotificationMethod; + const thresholdMethod = monitor.slowResponseNotificationThresholdMethod; + const thresholdMultipler = monitor.slowResponseNotificationThresholdMultiplier; + const windowDuration = monitor.slowResponseNotificationRange; + let actualResponseTime = 0; + + let previousBeats; + if (method !== "last") { + //Get recent heartbeat list with range of time + const afterThisDate = new Date(Date.now() - (1000 * (monitor.slowResponseNotificationRange + 1))); // add 1 second otherwise we grab 0 previous beats when Time Range == Heartbeat Interval + previousBeats = await R.getAll(` + SELECT * FROM heartbeat + WHERE monitor_id = ? AND time > datetime(?) AND status = ?`, + [ + monitor.id, + afterThisDate.toISOString(), + UP, + ]); + } + + switch (method) { + case "average": + previousBeats.forEach(beat => { + actualResponseTime = actualResponseTime + beat.ping; + }); + actualResponseTime = Math.round(actualResponseTime / previousBeats.length); + break; + + case "max": + previousBeats.forEach(beat => { + actualResponseTime = Math.max(actualResponseTime, beat.ping); + }); + break; + + case "last": + actualResponseTime = bean.ping; + break; + + default: + log.error("monitor", `[${this.name}] Unknown response time calculation method for slow response notification: ${method}`); + return; + } + + let threshold; + let thresholdDescription; + let afterThisDate; + let avgPing; + switch (thresholdMethod) { + case "threshold-static": + threshold = monitor.slowResponseNotificationThreshold; + thresholdDescription = "static"; + break; + + case "threshold-relative-24-hour": + //Get average response time over last 24 hours + afterThisDate = new Date(Date.now() - (1000 * (24 * 60 * 60))); // 24 hours in milliseconds + avgPing = parseInt(await R.getCell(` + SELECT AVG(ping) FROM heartbeat + WHERE time > datetime(?) + AND ping IS NOT NULL + AND monitor_id = ? + AND status = ? + `, + [ afterThisDate.toISOString(), monitor.id, UP ] + )); + //calculate threshold + threshold = Math.round(avgPing * thresholdMultipler); + thresholdDescription = `${thresholdMultipler}x 24H Avg`; + break; + + default: + log.error("monitor", `[${this.name}] Unknown threshold calculation method for slow response notification: ${thresholdMethod}`); + return; + } + + // Verify valid response time was calculated + if (actualResponseTime === 0 || !Number.isInteger(actualResponseTime)) { + log.debug("monitor", `[${this.name}] Failed to calculate valid response time`); + return; + } + + // Verify valid threshold was calculated + if (!Number.isInteger(threshold)) { + log.debug("monitor", `[${this.name}] Failed to calculate valid threshold`); + 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; + + // Responding normally + if (actualResponseTime < threshold) { + bean.pingStatus = NOMINAL; + if (bean.slowResponseCount === 0) { + log.debug("monitor", `[${this.name}] Responding normally. No need to send slow response notification | ${msgStats}`); + } else { + msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`; + log.debug("monitor", `[${this.name}] Returned to normal response time | ${msgStats}`); + + // Mark important (SLOW -> NOMINAL) + bean.pingImportant = true; + bean.pingMsg = `Returned to Normal Response Time \n${msgStats}`; + + Monitor.sendSlowResponseNotification(monitor, bean, slowStats); + } + + // Reset slow response count + bean.slowResponseCount = 0; + + // Responding slowly + } else { + bean.pingStatus = SLOW; + ++bean.slowResponseCount; + + // Always send first notification + if (bean.slowResponseCount === 1) { + log.debug("monitor", `[${this.name}] Responded slow, sending notification | ${msgStats}`); + + // Mark important (NOMINAL -> SLOW) + bean.pingImportant = true; + bean.pingMsg = `Responded Slow \n${msgStats}`; + + Monitor.sendSlowResponseNotification(monitor, bean, slowStats); + + // Send notification every x times + } else if (this.slowResponseNotificationResendInterval > 0) { + if (((bean.slowResponseCount) % this.slowResponseNotificationResendInterval) === 0) { + // Send notification again, because we are still responding slow + msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`; + log.debug("monitor", `[${this.name}] Still responding slow, sendSlowResponseNotification again | ${msgStats}`); + + bean.pingMsg = `Still Responding Slow \n${msgStats}`; + + Monitor.sendSlowResponseNotification(monitor, bean, slowStats); + } else { + log.debug("monitor", `[${this.name}] Still responding slow, waiting for resend interal | ${msgStats}`); + } + } else { + log.debug("monitor", `[${this.name}] Still responding slow, but resend is disabled | ${msgStats}`); + } + } + } + /** * Get the status of the previous heartbeat * @param {number} monitorID ID of monitor to check diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js index f24d4725..034a23ce 100644 --- a/server/notification-providers/discord.js +++ b/server/notification-providers/discord.js @@ -1,9 +1,10 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); -const { DOWN, UP } = require("../../src/util"); +const { DOWN, UP, SLOW, NOMINAL } = require("../../src/util"); class Discord extends NotificationProvider { name = "discord"; + supportSlowNotifications = true; /** * @inheritdoc @@ -114,12 +115,93 @@ class Discord extends NotificationProvider { await axios.post(notification.discordWebhookUrl, discordupdata); 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) { this.throwGeneralAxiosError(error); } } - } module.exports = Discord; diff --git a/server/notification-providers/notification-provider.js b/server/notification-providers/notification-provider.js index 9b4f0bb0..59ad3c8a 100644 --- a/server/notification-providers/notification-provider.js +++ b/server/notification-providers/notification-provider.js @@ -6,6 +6,12 @@ class NotificationProvider { */ name = undefined; + /** + * Does the notification provider support slow response notifications? + * @type {boolean} + */ + supportSlowNotifications = false; + /** * Send a notification * @param {BeanModel} notification Notification to send diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js index 9347b07c..f9e83ecc 100644 --- a/server/notification-providers/slack.js +++ b/server/notification-providers/slack.js @@ -1,10 +1,11 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); 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 { name = "slack"; + supportSlowNotifications = true; /** * Deprecated property notification.slackbutton @@ -49,6 +50,23 @@ class Slack extends NotificationProvider { } 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 = { "text": `${textMsg}\n${msg}`, "channel": notification.slackchannel, @@ -56,7 +74,7 @@ class Slack extends NotificationProvider { "icon_emoji": notification.slackiconemo, "attachments": [ { - "color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a", + "color": color, "blocks": [ { "type": "header", diff --git a/server/notification.js b/server/notification.js index 1d847104..f0f77838 100644 --- a/server/notification.js +++ b/server/notification.js @@ -1,5 +1,5 @@ const { R } = require("redbean-node"); -const { log } = require("../src/util"); +const { log, SLOW, NOMINAL } = require("../src/util"); const Alerta = require("./notification-providers/alerta"); const AlertNow = require("./notification-providers/alertnow"); const AliyunSms = require("./notification-providers/aliyun-sms"); @@ -155,7 +155,15 @@ class Notification { */ static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { 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 { throw new Error("Notification type is not supported"); } diff --git a/server/server.js b/server/server.js index 753f88b8..450c1412 100644 --- a/server/server.js +++ b/server/server.js @@ -805,6 +805,13 @@ let needSetup = false; bean.authMethod = monitor.authMethod; bean.authWorkstation = monitor.authWorkstation; bean.authDomain = monitor.authDomain; + bean.slowResponseNotification = monitor.slowResponseNotification; + bean.slowResponseNotificationMethod = monitor.slowResponseNotificationMethod; + bean.slowResponseNotificationRange = monitor.slowResponseNotificationRange; + bean.slowResponseNotificationThresholdMethod = monitor.slowResponseNotificationThresholdMethod; + bean.slowResponseNotificationThreshold = monitor.slowResponseNotificationThreshold; + bean.slowResponseNotificationThresholdMultiplier = monitor.slowResponseNotificationThresholdMultiplier; + bean.slowResponseNotificationResendInterval = monitor.slowResponseNotificationResendInterval; bean.grpcUrl = monitor.grpcUrl; bean.grpcProtobuf = monitor.grpcProtobuf; bean.grpcServiceName = monitor.grpcServiceName; @@ -1191,9 +1198,9 @@ let needSetup = false; let count; if (monitorID == null) { - count = await R.count("heartbeat", "important = 1"); + count = await R.count("heartbeat", "important = 1 OR ping_important = 1"); } else { - count = await R.count("heartbeat", "monitor_id = ? AND important = 1", [ + count = await R.count("heartbeat", "monitor_id = ? AND (important = 1 OR ping_important = 1)", [ monitorID, ]); } @@ -1217,7 +1224,7 @@ let needSetup = false; let list; if (monitorID == null) { list = await R.find("heartbeat", ` - important = 1 + important = 1 OR ping_important = 1 ORDER BY time DESC LIMIT ? OFFSET ? @@ -1228,7 +1235,7 @@ let needSetup = false; } else { list = await R.find("heartbeat", ` monitor_id = ? - AND important = 1 + AND (important = 1 OR ping_important = 1) ORDER BY time DESC LIMIT ? OFFSET ? @@ -1442,7 +1449,9 @@ let needSetup = false; log.info("manage", `Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`); - await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [ + await R.exec("UPDATE heartbeat SET msg = ?, important = ?, ping_msg = ?, ping_important = ? WHERE monitor_id = ? ", [ + "", + "0", "", "0", monitorID, diff --git a/src/components/PingChart.vue b/src/components/PingChart.vue index c550b4b4..dd9db4e3 100644 --- a/src/components/PingChart.vue +++ b/src/components/PingChart.vue @@ -19,11 +19,12 @@