From 2879b53b3c9e529d6b91cabd0d6ee3d4fbebd85b Mon Sep 17 00:00:00 2001 From: Suven-p Date: Tue, 15 Oct 2024 07:53:31 +0545 Subject: [PATCH 01/19] Add Rabbitmq monitor --- .../2024-10-1315-rabbitmq-monitor.js | 17 +++++ server/model/monitor.js | 3 + server/monitor-types/rabbitmq.js | 74 +++++++++++++++++++ server/server.js | 5 ++ server/uptime-kuma-server.js | 2 + src/pages/EditMonitor.vue | 46 +++++++++++- 6 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 db/knex_migrations/2024-10-1315-rabbitmq-monitor.js create mode 100644 server/monitor-types/rabbitmq.js diff --git a/db/knex_migrations/2024-10-1315-rabbitmq-monitor.js b/db/knex_migrations/2024-10-1315-rabbitmq-monitor.js new file mode 100644 index 0000000000..6a17f3366a --- /dev/null +++ b/db/knex_migrations/2024-10-1315-rabbitmq-monitor.js @@ -0,0 +1,17 @@ +exports.up = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.text("rabbitmq_nodes"); + table.string("rabbitmq_username"); + table.string("rabbitmq_password"); + }); + +}; + +exports.down = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.dropColumn("rabbitmq_nodes"); + table.dropColumn("rabbitmq_username"); + table.dropColumn("rabbitmq_password"); + }); + +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index 5b7e5871a8..3031bf7011 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -153,6 +153,7 @@ class Monitor extends BeanModel { snmpOid: this.snmpOid, jsonPathOperator: this.jsonPathOperator, snmpVersion: this.snmpVersion, + rabbitmqNodes: JSON.parse(this.rabbitmqNodes), conditions: JSON.parse(this.conditions), }; @@ -183,6 +184,8 @@ class Monitor extends BeanModel { tlsCert: this.tlsCert, tlsKey: this.tlsKey, kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions), + rabbitmqUsername: this.rabbitmqUsername, + rabbitmqPassword: this.rabbitmqPassword, }; } diff --git a/server/monitor-types/rabbitmq.js b/server/monitor-types/rabbitmq.js new file mode 100644 index 0000000000..a4a47ce759 --- /dev/null +++ b/server/monitor-types/rabbitmq.js @@ -0,0 +1,74 @@ +const { MonitorType } = require("./monitor-type"); +const { log, UP, DOWN } = require("../../src/util"); +const { axiosAbortSignal } = require("../util-server"); +const axios = require("axios"); + +class RabbitMqMonitorType extends MonitorType { + name = "rabbitmq"; + + /** + * @inheritdoc + */ + async check(monitor, heartbeat, server) { + // HTTP basic auth + let basicAuthHeader = {}; + basicAuthHeader = { + "Authorization": "Basic " + this.encodeBase64(monitor.rabbitmqUsername, monitor.rabbitmqPassword), + }; + + let status = DOWN; + let msg = ""; + + for (const baseUrl of JSON.parse(monitor.rabbitmqNodes)) { + try { + const options = { + url: new URL("/api/health/checks/alarms", baseUrl).href, + method: "get", + timeout: monitor.timeout * 1000, + headers: { + "Accept": "application/json", + ...(basicAuthHeader), + }, + signal: axiosAbortSignal((monitor.timeout + 10) * 1000), + validateStatus: () => true, + }; + log.debug("monitor", `[${monitor.name}] Axios Request: ${JSON.stringify(options)}`); + const res = await axios.request(options); + log.debug("monitor", `[${monitor.name}] Axios Response: status=${res.status} body=${JSON.stringify(res.data)}`); + if (res.status === 200) { + status = UP; + msg = "OK"; + break; + } else { + msg = `${res.status} - ${res.statusText}`; + } + } catch (error) { + if (axios.isCancel(error)) { + msg = "Request timed out"; + log.debug("monitor", `[${monitor.name}] Request timed out`); + } else { + log.debug("monitor", `[${monitor.name}] Axios Error: ${JSON.stringify(error.message)}`); + msg = error.message; + } + } + } + + heartbeat.msg = msg; + heartbeat.status = status; + } + + /** + * Encode user and password to Base64 encoding + * for HTTP "basic" auth, as per RFC-7617 + * @param {string|null} user - The username (nullable if not changed by a user) + * @param {string|null} pass - The password (nullable if not changed by a user) + * @returns {string} Encoded Base64 string + */ + encodeBase64(user, pass) { + return Buffer.from(`${user || ""}:${pass || ""}`).toString("base64"); + } +} + +module.exports = { + RabbitMqMonitorType, +}; diff --git a/server/server.js b/server/server.js index db58ae8291..c88daca88e 100644 --- a/server/server.js +++ b/server/server.js @@ -718,6 +718,8 @@ let needSetup = false; monitor.conditions = JSON.stringify(monitor.conditions); + monitor.rabbitmqNodes = JSON.stringify(monitor.rabbitmqNodes); + bean.import(monitor); bean.user_id = socket.userID; @@ -868,6 +870,9 @@ let needSetup = false; bean.snmpOid = monitor.snmpOid; bean.jsonPathOperator = monitor.jsonPathOperator; bean.timeout = monitor.timeout; + bean.rabbitmqNodes = JSON.stringify(monitor.rabbitmqNodes); + bean.rabbitmqUsername = monitor.rabbitmqUsername; + bean.rabbitmqPassword = monitor.rabbitmqPassword; bean.conditions = JSON.stringify(monitor.conditions); bean.validate(); diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 76bf425657..062f098d7d 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -115,6 +115,7 @@ class UptimeKumaServer { UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType(); UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType(); UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType(); + UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType(); // Allow all CORS origins (polling) in development let cors = undefined; @@ -552,4 +553,5 @@ const { DnsMonitorType } = require("./monitor-types/dns"); const { MqttMonitorType } = require("./monitor-types/mqtt"); const { SNMPMonitorType } = require("./monitor-types/snmp"); const { MongodbMonitorType } = require("./monitor-types/mongodb"); +const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq"); const Monitor = require("./model/monitor"); diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 5d999b597c..c8a05a82b8 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -64,6 +64,9 @@ + @@ -233,6 +236,40 @@ + +
@@ -549,7 +586,7 @@
-
+
@@ -1122,6 +1159,9 @@ const monitorDefaults = { kafkaProducerAllowAutoTopicCreation: false, gamedigGivenPortOnly: true, remote_browser: null, + rabbitmqNodes: [], + rabbitmqUsername: "", + rabbitmqPassword: "", conditions: [] }; @@ -1709,6 +1749,10 @@ message HealthCheckResponse { this.monitor.kafkaProducerBrokers.push(newBroker); }, + addRabbitmqNode(newNode) { + this.monitor.rabbitmqNodes.push(newNode); + }, + /** * Validate form input * @returns {boolean} Is the form input valid? From 4c47d2f12afab916c62bc983853b9b3882559737 Mon Sep 17 00:00:00 2001 From: Suven-p Date: Wed, 16 Oct 2024 06:11:54 +0545 Subject: [PATCH 02/19] Add translation string --- src/lang/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lang/en.json b/src/lang/en.json index c07e06fab4..35e0bdd2d2 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1051,5 +1051,6 @@ "From":"From", "Can be found on:": "Can be found on: {0}", "The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.", - "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies." + "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.", + "RabbitMQ Nodes": "RabbitMQ Nodes" } From 1c020233e4abcb98f54964dcef4cc21835a9897e Mon Sep 17 00:00:00 2001 From: Suven-p Date: Wed, 16 Oct 2024 07:06:22 +0545 Subject: [PATCH 03/19] Add description and validation for nodes input field --- src/lang/en.json | 5 ++++- src/pages/EditMonitor.vue | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/lang/en.json b/src/lang/en.json index 35e0bdd2d2..8562247a93 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1052,5 +1052,8 @@ "Can be found on:": "Can be found on: {0}", "The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.", "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.", - "RabbitMQ Nodes": "RabbitMQ Nodes" + "RabbitMQ Nodes": "RabbitMQ Nodes", + "rabbitmqNodesDescription": "Enter the URL for the RabbitMQ management nodes including protocol and port.", + "rabbitmqNodesRequired": "At least one RabbitMQ node is required.", + "rabbitmqNodesInvalid": "Please enter URL for RabbitMQ nodes." } diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index c8a05a82b8..378e6596ec 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -257,6 +257,9 @@ :preselect-first="false" @tag="addRabbitmqNode" > +
+ {{ $t("rabbitmqNodesDescription") }} +
@@ -1780,6 +1783,17 @@ message HealthCheckResponse { return false; } } + + if (this.monitor.type === "rabbitmq") { + if (this.monitor.rabbitmqNodes.length === 0) { + toast.error(this.$t("rabbitmqNodesRequired")); + return false; + } + if (!this.monitor.rabbitmqNodes.every(node => node.startsWith("http://" || node.startsWith("https://")))) { + toast.error(this.$t("rabbitmqNodesInvalid")); + return false; + } + } return true; }, From 4fa640ba08874ad9b07b73ff29ac758708369ed1 Mon Sep 17 00:00:00 2001 From: Suven-p Date: Wed, 16 Oct 2024 07:18:52 +0545 Subject: [PATCH 04/19] Update messages --- src/lang/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 8562247a93..7e1a47ce3a 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1052,8 +1052,8 @@ "Can be found on:": "Can be found on: {0}", "The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.", "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.", - "RabbitMQ Nodes": "RabbitMQ Nodes", - "rabbitmqNodesDescription": "Enter the URL for the RabbitMQ management nodes including protocol and port.", - "rabbitmqNodesRequired": "At least one RabbitMQ node is required.", - "rabbitmqNodesInvalid": "Please enter URL for RabbitMQ nodes." + "RabbitMQ Nodes": "RabbitMQ Management Nodes", + "rabbitmqNodesDescription": "Enter the URL for the RabbitMQ management nodes including protocol and port. Example: https://node1.rabbitmq.com:15672", + "rabbitmqNodesRequired": "Please set the nodes for this monitor.", + "rabbitmqNodesInvalid": "Please use complete URL for RabbitMQ nodes." } From 44cd67591057a999f741ca52f3d02957e5915f25 Mon Sep 17 00:00:00 2001 From: Suven-p Date: Wed, 16 Oct 2024 07:34:08 +0545 Subject: [PATCH 05/19] Replace input type password with HiddenInput --- src/pages/EditMonitor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 378e6596ec..5ecc8cc756 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -269,7 +269,7 @@
- +
From 009b004756d03b99a97e039f56d7a4405f31bdee Mon Sep 17 00:00:00 2001 From: Suven-p Date: Fri, 18 Oct 2024 07:29:10 +0545 Subject: [PATCH 06/19] Make requested changes --- server/monitor-types/rabbitmq.js | 45 ++++++++++++-------------------- src/lang/en.json | 6 +++-- src/pages/EditMonitor.vue | 6 ++--- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/server/monitor-types/rabbitmq.js b/server/monitor-types/rabbitmq.js index a4a47ce759..90d7730180 100644 --- a/server/monitor-types/rabbitmq.js +++ b/server/monitor-types/rabbitmq.js @@ -10,16 +10,16 @@ class RabbitMqMonitorType extends MonitorType { * @inheritdoc */ async check(monitor, heartbeat, server) { - // HTTP basic auth - let basicAuthHeader = {}; - basicAuthHeader = { - "Authorization": "Basic " + this.encodeBase64(monitor.rabbitmqUsername, monitor.rabbitmqPassword), - }; - - let status = DOWN; - let msg = ""; + let baseUrls = []; + try { + baseUrls = JSON.parse(monitor.rabbitmqNodes); + } + catch (error) { + throw new Error("Invalid RabbitMQ Nodes"); + } - for (const baseUrl of JSON.parse(monitor.rabbitmqNodes)) { + heartbeat.status = DOWN; + for (const baseUrl of baseUrls) { try { const options = { url: new URL("/api/health/checks/alarms", baseUrl).href, @@ -27,8 +27,9 @@ class RabbitMqMonitorType extends MonitorType { timeout: monitor.timeout * 1000, headers: { "Accept": "application/json", - ...(basicAuthHeader), + "Authorization": "Basic " + Buffer.from(`${monitor.rabbitmqUsername || ""}:${monitor.rabbitmqPassword || ""}`).toString("base64"), }, + // Use axios signal to handle connection timeouts https://stackoverflow.com/a/74739938 signal: axiosAbortSignal((monitor.timeout + 10) * 1000), validateStatus: () => true, }; @@ -36,36 +37,22 @@ class RabbitMqMonitorType extends MonitorType { const res = await axios.request(options); log.debug("monitor", `[${monitor.name}] Axios Response: status=${res.status} body=${JSON.stringify(res.data)}`); if (res.status === 200) { - status = UP; - msg = "OK"; + heartbeat.status = UP; + heartbeat.msg = "OK"; break; } else { - msg = `${res.status} - ${res.statusText}`; + heartbeat.msg = `${res.status} - ${res.statusText}`; } } catch (error) { if (axios.isCancel(error)) { - msg = "Request timed out"; + heartbeat.msg = "Request timed out"; log.debug("monitor", `[${monitor.name}] Request timed out`); } else { log.debug("monitor", `[${monitor.name}] Axios Error: ${JSON.stringify(error.message)}`); - msg = error.message; + heartbeat.msg = error.message; } } } - - heartbeat.msg = msg; - heartbeat.status = status; - } - - /** - * Encode user and password to Base64 encoding - * for HTTP "basic" auth, as per RFC-7617 - * @param {string|null} user - The username (nullable if not changed by a user) - * @param {string|null} pass - The password (nullable if not changed by a user) - * @returns {string} Encoded Base64 string - */ - encodeBase64(user, pass) { - return Buffer.from(`${user || ""}:${pass || ""}`).toString("base64"); } } diff --git a/src/lang/en.json b/src/lang/en.json index 7e1a47ce3a..88dba7f10a 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1053,7 +1053,9 @@ "The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.", "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.", "RabbitMQ Nodes": "RabbitMQ Management Nodes", - "rabbitmqNodesDescription": "Enter the URL for the RabbitMQ management nodes including protocol and port. Example: https://node1.rabbitmq.com:15672", + "rabbitmqNodesDescription": "Enter the URL for the RabbitMQ management nodes including protocol and port. Example: {0}", "rabbitmqNodesRequired": "Please set the nodes for this monitor.", - "rabbitmqNodesInvalid": "Please use complete URL for RabbitMQ nodes." + "rabbitmqNodesInvalid": "Please use a complete URL for RabbitMQ nodes.", + "RabbitMQ Username": "RabbitMQ Username", + "RabbitMQ Password": "RabbitMQ Password" } diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 5ecc8cc756..29a5b4d389 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -258,17 +258,17 @@ @tag="addRabbitmqNode" >
- {{ $t("rabbitmqNodesDescription") }} + {{ $t("rabbitmqNodesDescription", ["https://node1.rabbitmq.com:15672"]) }}
- +
- +
From 21a2d3c681402986a9144bbb1315bc38f6c001d1 Mon Sep 17 00:00:00 2001 From: Suven-p Date: Fri, 18 Oct 2024 22:38:35 +0545 Subject: [PATCH 07/19] Change monitor type to specify management plugin --- src/pages/EditMonitor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 29a5b4d389..24b53e28bc 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -65,7 +65,7 @@ MQTT + + + RabbitMQ documentation + . +