From 7f88aacbe735c3632a2d3664888e7a96f2c8e245 Mon Sep 17 00:00:00 2001 From: Thomas Spalinger Date: Thu, 23 Feb 2023 16:16:49 +0000 Subject: [PATCH 01/11] make monitor start() and stop() async --- server/model/monitor.js | 4 ++-- server/server.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 4bb859e9..c7388607 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -199,7 +199,7 @@ class Monitor extends BeanModel { * Start monitor * @param {Server} io Socket server instance */ - start(io) { + async start(io) { let previousBeat = null; let retries = 0; @@ -836,7 +836,7 @@ class Monitor extends BeanModel { } /** Stop monitor */ - stop() { + async stop() { clearTimeout(this.heartbeatInterval); this.isStop = true; diff --git a/server/server.js b/server/server.js index 18598171..1dd012af 100644 --- a/server/server.js +++ b/server/server.js @@ -872,7 +872,7 @@ let needSetup = false; log.info("manage", `Delete Monitor: ${monitorID} User ID: ${socket.userID}`); if (monitorID in server.monitorList) { - server.monitorList[monitorID].stop(); + await server.monitorList[monitorID].stop(); delete server.monitorList[monitorID]; } @@ -1694,11 +1694,11 @@ async function startMonitor(userID, monitorID) { ]); if (monitor.id in server.monitorList) { - server.monitorList[monitor.id].stop(); + await server.monitorList[monitor.id].stop(); } server.monitorList[monitor.id] = monitor; - monitor.start(io); + await monitor.start(io); } /** @@ -1728,7 +1728,7 @@ async function pauseMonitor(userID, monitorID) { ]); if (monitorID in server.monitorList) { - server.monitorList[monitorID].stop(); + await server.monitorList[monitorID].stop(); } } @@ -1741,7 +1741,7 @@ async function startMonitors() { } for (let monitor of list) { - monitor.start(io); + await monitor.start(io); // Give some delays, so all monitors won't make request at the same moment when just start the server. await sleep(getRandomInt(300, 1000)); } @@ -1762,7 +1762,7 @@ async function shutdownFunction(signal) { log.info("server", "Stopping all monitors"); for (let id in server.monitorList) { let monitor = server.monitorList[id]; - monitor.stop(); + await monitor.stop(); } await sleep(2000); await Database.close(); From dc3abc68f0c90681727c7ea574a35f8588174108 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 30 Jan 2024 17:54:04 +0100 Subject: [PATCH 02/11] Fixed type anotation --- server/model/monitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 30c631a7..0c322b92 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -324,7 +324,7 @@ class Monitor extends BeanModel { /** * Start monitor * @param {Server} io Socket server instance - * @returns {void} + * @returns {Promise} */ async start(io) { let previousBeat = null; From 1b293f2754413e391b8339540e46132796a886ba Mon Sep 17 00:00:00 2001 From: Stefan Heine Date: Mon, 19 Feb 2024 07:42:37 +0100 Subject: [PATCH 03/11] MQTT monitor, set the MQTT clientId to make it easier in the MQTT broker to identify where the connections are coming from --- server/monitor-types/mqtt.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/monitor-types/mqtt.js b/server/monitor-types/mqtt.js index cff5c93d..d9aaf964 100644 --- a/server/monitor-types/mqtt.js +++ b/server/monitor-types/mqtt.js @@ -81,7 +81,8 @@ class MqttMonitorType extends MonitorType { let client = mqtt.connect(mqttUrl, { username, - password + password, + clientId: 'uptime-kuma_' + Math.random().toString(16).substr(2, 8) }); client.on("connect", () => { From 6bfc58674a2304ec8994005da87854635ed01734 Mon Sep 17 00:00:00 2001 From: Stefan Heine Date: Mon, 19 Feb 2024 07:57:34 +0100 Subject: [PATCH 04/11] MQTT monitor, set the MQTT clientId to make it easier in the MQTT broker to identify where the connections are coming from --- server/monitor-types/mqtt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/monitor-types/mqtt.js b/server/monitor-types/mqtt.js index d9aaf964..9b2db43d 100644 --- a/server/monitor-types/mqtt.js +++ b/server/monitor-types/mqtt.js @@ -82,7 +82,7 @@ class MqttMonitorType extends MonitorType { let client = mqtt.connect(mqttUrl, { username, password, - clientId: 'uptime-kuma_' + Math.random().toString(16).substr(2, 8) + clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8) }); client.on("connect", () => { From 99dccd9e902a9b49abaffb90f3dacd95f9c02ab3 Mon Sep 17 00:00:00 2001 From: Huzaifa Azim Date: Fri, 23 Feb 2024 18:10:09 +0500 Subject: [PATCH 05/11] reset-password-issue-4518 --- extra/reset-password.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extra/reset-password.js b/extra/reset-password.js index 3b40e93e..b87d90f1 100644 --- a/extra/reset-password.js +++ b/extra/reset-password.js @@ -8,6 +8,7 @@ const User = require("../server/model/user"); const { io } = require("socket.io-client"); const { localWebSocketURL } = require("../server/config"); const args = require("args-parser")(process.argv); + const rl = readline.createInterface({ input: process.stdin, output: process.stdout @@ -19,10 +20,10 @@ const main = async () => { } console.log("Connecting the database"); - Database.initDataDir(args); - await Database.connect(false, false, true); try { + Database.initDataDir(args); + await Database.connect(false, false, true); // No need to actually reset the password for testing, just make sure no connection problem. It is ok for now. if (!process.env.TEST_BACKEND) { const user = await R.findOne("user"); From 7756070c57d081cadc1a17661d54f4c8bb45632f Mon Sep 17 00:00:00 2001 From: taschenuhr Date: Wed, 28 Feb 2024 09:47:14 +0100 Subject: [PATCH 06/11] refactor MS-Teams notification to use adaptive cards --- server/notification-providers/teams.js | 119 +++++++++++++++++++------ 1 file changed, 92 insertions(+), 27 deletions(-) diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index 30976cf5..3568acad 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -21,18 +21,32 @@ class Teams extends NotificationProvider { }; /** - * Select theme color to use based on status + * Select the style to use based on status * @param {const} status The status constant - * @returns {string} Selected color in hex RGB format + * @returns {string} Selected style for adaptive cards */ - _getThemeColor = (status) => { + _getStyle = (status) => { if (status === DOWN) { - return "ff0000"; + return "attention"; } if (status === UP) { - return "00e804"; + return "good"; + } + return "emphasis"; + }; + + /** + * Format an URL in the markdown format + * @param {string} url An absolute URL + * @param {string} linkName Optional name of the link + * @returns {string} The URL formatted as markdown link + */ + _formatAsMarkdownLink = (url, linkName) => { + if (linkName) { + return `[${linkName}](${url})`; + } else { + return `[${url}](${url})`; } - return "008cff"; }; /** @@ -57,40 +71,91 @@ class Teams extends NotificationProvider { const facts = []; + if (monitorMessage) { + facts.push({ + title: "Description", + value: monitorMessage, + }); + } + if (monitorName) { facts.push({ - name: "Monitor", + title: "Monitor", value: monitorName, }); } if (monitorUrl && monitorUrl !== "https://") { facts.push({ - name: "URL", - value: monitorUrl, + title: "URL", + value: this._formatAsMarkdownLink(monitorUrl), }); } return { - "@context": "https://schema.org/extensions", - "@type": "MessageCard", - themeColor: this._getThemeColor(status), - summary: notificationMessage, - sections: [ + "type": "message", + "attachments": [ { - activityImage: - "https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.png", - activityTitle: "**Uptime Kuma**", - }, - { - activityTitle: notificationMessage, - }, - { - activityTitle: "**Description**", - text: monitorMessage, - facts, - }, - ], + "contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": "", + "content": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Container", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "ColumnSet", + "style": this._getStyle(status), + "columns": [ + { + "type": "Column", + "width": "auto", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "Image", + "width": "32px", + "style": "Person", + "url": "https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.png", + "altText": "Uptime Kuma Logo" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "size": "Large", + "weight": "Bolder", + "text": "**Uptime Kuma Alert**" + } + ] + } + ] + } + ] + }, + { + "type": "TextBlock", + "weight": "Bolder", + "text": notificationMessage, + "separator": true, + "wrap": true + }, + { + "type": "FactSet", + "facts": facts + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5" + } + } + ] }; }; From 5b87da94b3834383d4c83c04cc0c20b4e8f77e7e Mon Sep 17 00:00:00 2001 From: taschenuhr Date: Wed, 28 Feb 2024 14:19:32 +0100 Subject: [PATCH 07/11] push notificationMessage to card header and add action button to dashboard --- server/notification-providers/teams.js | 88 ++++++++++++++++---------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index 3568acad..16ce6435 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -1,6 +1,7 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); -const { DOWN, UP } = require("../../src/util"); +const { setting } = require("../util-server"); +const { DOWN, UP, getMonitorRelativeURL } = require("../../src/util"); class Teams extends NotificationProvider { name = "teams"; @@ -13,9 +14,9 @@ class Teams extends NotificationProvider { */ _statusMessageFactory = (status, monitorName) => { if (status === DOWN) { - return `🔴 Application [${monitorName}] went down`; + return `🔴 [${monitorName}] went down`; } else if (status === UP) { - return `✅ Application [${monitorName}] is back online`; + return `✅ [${monitorName}] is back online`; } return "Notification"; }; @@ -35,27 +36,14 @@ class Teams extends NotificationProvider { return "emphasis"; }; - /** - * Format an URL in the markdown format - * @param {string} url An absolute URL - * @param {string} linkName Optional name of the link - * @returns {string} The URL formatted as markdown link - */ - _formatAsMarkdownLink = (url, linkName) => { - if (linkName) { - return `[${linkName}](${url})`; - } else { - return `[${url}](${url})`; - } - }; - /** * Generate payload for notification * @param {object} args Method arguments * @param {const} args.status The status of the monitor * @param {string} args.monitorMessage Message to send - * @param {string} args.monitorName Name of monitor affected - * @param {string} args.monitorUrl URL of monitor affected + * @param {string} args.monitorName Name of the monitor affected + * @param {string} args.monitorUrl URL of the monitor affected + * @param {string} args.dashboardUrl URL of the dashboard affected * @returns {object} Notification payload */ _notificationPayloadFactory = ({ @@ -63,6 +51,7 @@ class Teams extends NotificationProvider { monitorMessage, monitorName, monitorUrl, + dashboardUrl, }) => { const notificationMessage = this._statusMessageFactory( status, @@ -88,11 +77,14 @@ class Teams extends NotificationProvider { if (monitorUrl && monitorUrl !== "https://") { facts.push({ title: "URL", - value: this._formatAsMarkdownLink(monitorUrl), + // format URL as markdown syntax, to be clickable + value: `[${monitorUrl}](${monitorUrl})`, }); } - return { + const headerMessage = `**${notificationMessage}**`; + + const payload = { "type": "message", "attachments": [ { @@ -129,9 +121,17 @@ class Teams extends NotificationProvider { "items": [ { "type": "TextBlock", - "size": "Large", + "size": "Medium", "weight": "Bolder", - "text": "**Uptime Kuma Alert**" + "text": headerMessage, + }, + { + "type": "TextBlock", + "size": "Small", + "weight": "Default", + "text": "Uptime Kuma Alert", + "isSubtle": true, + "spacing": "None" } ] } @@ -139,15 +139,9 @@ class Teams extends NotificationProvider { } ] }, - { - "type": "TextBlock", - "weight": "Bolder", - "text": notificationMessage, - "separator": true, - "wrap": true - }, { "type": "FactSet", + "separator": true, "facts": facts } ], @@ -157,6 +151,23 @@ class Teams extends NotificationProvider { } ] }; + + if (dashboardUrl) { + payload.attachments.forEach(element => { + element.content.push({ + "type": "ActionSet", + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Visit Uptime Kuma", + "url": dashboardUrl + } + ] + }); + }); + } + + return payload; }; /** @@ -195,26 +206,33 @@ class Teams extends NotificationProvider { return okMsg; } - let url; + let monitorUrl; switch (monitorJSON["type"]) { case "http": case "keywork": - url = monitorJSON["url"]; + monitorUrl = monitorJSON["url"]; break; case "docker": - url = monitorJSON["docker_host"]; + monitorUrl = monitorJSON["docker_host"]; break; default: - url = monitorJSON["hostname"]; + monitorUrl = monitorJSON["hostname"]; break; } + const baseURL = await setting("primaryBaseURL"); + let dashboardUrl; + if (baseURL) { + dashboardUrl = baseURL + getMonitorRelativeURL(monitorJSON.id); + } + const payload = this._notificationPayloadFactory({ monitorMessage: heartbeatJSON.msg, monitorName: monitorJSON.name, - monitorUrl: url, + monitorUrl: monitorUrl, status: heartbeatJSON.status, + dashboardUrl: dashboardUrl, }); await this._sendNotification(notification.webhookUrl, payload); From b8941403d16cbadadcef0f3f21e7a0af1600089e Mon Sep 17 00:00:00 2001 From: taschenuhr Date: Wed, 28 Feb 2024 16:30:48 +0100 Subject: [PATCH 08/11] code cleanup --- server/notification-providers/teams.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index 16ce6435..4435d2dd 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -14,9 +14,9 @@ class Teams extends NotificationProvider { */ _statusMessageFactory = (status, monitorName) => { if (status === DOWN) { - return `🔴 [${monitorName}] went down`; + return `[${monitorName}] went down`; } else if (status === UP) { - return `✅ [${monitorName}] is back online`; + return `[${monitorName}] is back online`; } return "Notification"; }; @@ -82,8 +82,6 @@ class Teams extends NotificationProvider { }); } - const headerMessage = `**${notificationMessage}**`; - const payload = { "type": "message", "attachments": [ @@ -123,7 +121,7 @@ class Teams extends NotificationProvider { "type": "TextBlock", "size": "Medium", "weight": "Bolder", - "text": headerMessage, + "text": `**${notificationMessage}**`, }, { "type": "TextBlock", @@ -141,7 +139,7 @@ class Teams extends NotificationProvider { }, { "type": "FactSet", - "separator": true, + "separator": false, "facts": facts } ], From cc09147b4bbd831fd614ed5c770dc7496d7638f3 Mon Sep 17 00:00:00 2001 From: taschenuhr Date: Wed, 28 Feb 2024 16:31:47 +0100 Subject: [PATCH 09/11] fix adding optional ActionSet --- server/notification-providers/teams.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index 4435d2dd..e46bc9e3 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -151,17 +151,15 @@ class Teams extends NotificationProvider { }; if (dashboardUrl) { - payload.attachments.forEach(element => { - element.content.push({ - "type": "ActionSet", - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Visit Uptime Kuma", - "url": dashboardUrl - } - ] - }); + payload.attachments[0].content.body.push({ + "type": "ActionSet", + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Visit Uptime Kuma", + "url": dashboardUrl + } + ] }); } From 8bd69d78a84937849748371053b3676513b6ff8b Mon Sep 17 00:00:00 2001 From: taschenuhr Date: Wed, 28 Feb 2024 17:41:00 +0100 Subject: [PATCH 10/11] some more tweaking of the card content and notification summary --- server/notification-providers/teams.js | 61 +++++++++++++++----------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index e46bc9e3..e8aab1a4 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -10,13 +10,14 @@ class Teams extends NotificationProvider { * Generate the message to send * @param {const} status The status constant * @param {string} monitorName Name of monitor + * @param {boolean} withStatusSymbol If the status should be prepended as symbol * @returns {string} Status message */ - _statusMessageFactory = (status, monitorName) => { + _statusMessageFactory = (status, monitorName, withStatusSymbol) => { if (status === DOWN) { - return `[${monitorName}] went down`; + return (withStatusSymbol ? "🔴 " : "") + `[${monitorName}] went down`; } else if (status === UP) { - return `[${monitorName}] is back online`; + return (withStatusSymbol ? "✅ " : "") + `[${monitorName}] is back online`; } return "Notification"; }; @@ -39,31 +40,34 @@ class Teams extends NotificationProvider { /** * Generate payload for notification * @param {object} args Method arguments - * @param {const} args.status The status of the monitor - * @param {string} args.monitorMessage Message to send + * @param {object} args.heartbeatJSON Heartbeat details * @param {string} args.monitorName Name of the monitor affected * @param {string} args.monitorUrl URL of the monitor affected * @param {string} args.dashboardUrl URL of the dashboard affected * @returns {object} Notification payload */ _notificationPayloadFactory = ({ - status, - monitorMessage, + heartbeatJSON, monitorName, monitorUrl, dashboardUrl, }) => { - const notificationMessage = this._statusMessageFactory( - status, - monitorName - ); - + const status = heartbeatJSON.status; const facts = []; + const actions = []; - if (monitorMessage) { + if (dashboardUrl) { + actions.push({ + "type": "Action.OpenUrl", + "title": "Visit Uptime Kuma", + "url": dashboardUrl + }); + } + + if (heartbeatJSON?.msg) { facts.push({ title: "Description", - value: monitorMessage, + value: heartbeatJSON.msg, }); } @@ -80,10 +84,24 @@ class Teams extends NotificationProvider { // format URL as markdown syntax, to be clickable value: `[${monitorUrl}](${monitorUrl})`, }); + actions.push({ + "type": "Action.OpenUrl", + "title": "Visit Monitor URL", + "url": monitorUrl + }); + } + + if (heartbeatJSON?.localDateTime) { + facts.push({ + title: "Time", + value: heartbeatJSON.localDateTime + (heartbeatJSON.timezone ? ` (${heartbeatJSON.timezone})` : ""), + }); } const payload = { "type": "message", + // message with status prefix as notification text + "summary": this._statusMessageFactory(status, monitorName, true), "attachments": [ { "contentType": "application/vnd.microsoft.card.adaptive", @@ -121,7 +139,7 @@ class Teams extends NotificationProvider { "type": "TextBlock", "size": "Medium", "weight": "Bolder", - "text": `**${notificationMessage}**`, + "text": `**${this._statusMessageFactory(status, monitorName, false)}**`, }, { "type": "TextBlock", @@ -150,16 +168,10 @@ class Teams extends NotificationProvider { ] }; - if (dashboardUrl) { + if (actions) { payload.attachments[0].content.body.push({ "type": "ActionSet", - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Visit Uptime Kuma", - "url": dashboardUrl - } - ] + "actions": actions, }); } @@ -224,10 +236,9 @@ class Teams extends NotificationProvider { } const payload = this._notificationPayloadFactory({ - monitorMessage: heartbeatJSON.msg, + heartbeatJSON: heartbeatJSON, monitorName: monitorJSON.name, monitorUrl: monitorUrl, - status: heartbeatJSON.status, dashboardUrl: dashboardUrl, }); From 4aaa0b92fb96ff6c47d50e42451c085dfb493f16 Mon Sep 17 00:00:00 2001 From: taschenuhr Date: Thu, 7 Mar 2024 15:11:19 +0100 Subject: [PATCH 11/11] fix test notification after method refactoring --- server/notification-providers/teams.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index e8aab1a4..bf2cd764 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -52,7 +52,7 @@ class Teams extends NotificationProvider { monitorUrl, dashboardUrl, }) => { - const status = heartbeatJSON.status; + const status = heartbeatJSON?.status; const facts = []; const actions = []; @@ -196,7 +196,9 @@ class Teams extends NotificationProvider { */ _handleGeneralNotification = (webhookUrl, msg) => { const payload = this._notificationPayloadFactory({ - monitorMessage: msg + heartbeatJSON: { + msg: msg + } }); return this._sendNotification(webhookUrl, payload);