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 000000000..6a17f3366
--- /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/package-lock.json b/package-lock.json
index a89b1b560..24f63d036 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -93,6 +93,7 @@
"@playwright/test": "~1.39.0",
"@popperjs/core": "~2.10.2",
"@testcontainers/hivemq": "^10.13.1",
+ "@testcontainers/rabbitmq": "^10.13.2",
"@types/bootstrap": "~5.1.9",
"@types/node": "^20.8.6",
"@typescript-eslint/eslint-plugin": "^6.7.5",
@@ -4172,6 +4173,15 @@
"testcontainers": "^10.13.1"
}
},
+ "node_modules/@testcontainers/rabbitmq": {
+ "version": "10.13.2",
+ "resolved": "https://registry.npmjs.org/@testcontainers/rabbitmq/-/rabbitmq-10.13.2.tgz",
+ "integrity": "sha512-npBKBnq3c6hETmxGZ/gVMke9cc1J/pcftNW9S3WidL48hxFBIPjYNM9FdTfWuoNER/8kuf4xJ8yCuJEYGH3ZAg==",
+ "dev": true,
+ "dependencies": {
+ "testcontainers": "^10.13.2"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -15925,11 +15935,10 @@
}
},
"node_modules/testcontainers": {
- "version": "10.13.1",
- "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.13.1.tgz",
- "integrity": "sha512-JBbOhxmygj/ouH/47GnoVNt+c55Telh/45IjVxEbDoswsLchVmJiuKiw/eF6lE5i7LN+/99xsrSCttI3YRtirg==",
+ "version": "10.13.2",
+ "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.13.2.tgz",
+ "integrity": "sha512-LfEll+AG/1Ks3n4+IA5lpyBHLiYh/hSfI4+ERa6urwfQscbDU+M2iW1qPQrHQi+xJXQRYy4whyK1IEHdmxWa3Q==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@balena/dockerignore": "^1.0.2",
"@types/dockerode": "^3.3.29",
diff --git a/package.json b/package.json
index c6bef90eb..5186cafcf 100644
--- a/package.json
+++ b/package.json
@@ -155,6 +155,7 @@
"@playwright/test": "~1.39.0",
"@popperjs/core": "~2.10.2",
"@testcontainers/hivemq": "^10.13.1",
+ "@testcontainers/rabbitmq": "^10.13.2",
"@types/bootstrap": "~5.1.9",
"@types/node": "^20.8.6",
"@typescript-eslint/eslint-plugin": "^6.7.5",
diff --git a/server/model/monitor.js b/server/model/monitor.js
index da0c0d5c8..9a30a6689 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 000000000..165a0ed91
--- /dev/null
+++ b/server/monitor-types/rabbitmq.js
@@ -0,0 +1,67 @@
+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) {
+ let baseUrls = [];
+ try {
+ baseUrls = JSON.parse(monitor.rabbitmqNodes);
+ } catch (error) {
+ throw new Error("Invalid RabbitMQ Nodes");
+ }
+
+ heartbeat.status = DOWN;
+ for (let baseUrl of baseUrls) {
+ try {
+ // Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com
+ if ( !baseUrl.endsWith("/") ) {
+ baseUrl += "/";
+ }
+ const options = {
+ // Do not start with slash, it will strip the trailing slash from baseUrl
+ url: new URL("api/health/checks/alarms/", baseUrl).href,
+ method: "get",
+ timeout: monitor.timeout * 1000,
+ headers: {
+ "Accept": "application/json",
+ "Authorization": "Basic " + Buffer.from(`${monitor.rabbitmqUsername || ""}:${monitor.rabbitmqPassword || ""}`).toString("base64"),
+ },
+ signal: axiosAbortSignal((monitor.timeout + 10) * 1000),
+ // Capture reason for 503 status
+ validateStatus: (status) => status === 200 || status === 503,
+ };
+ 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) {
+ heartbeat.status = UP;
+ heartbeat.msg = "OK";
+ break;
+ } else if (res.status === 503) {
+ heartbeat.msg = res.data.reason;
+ } else {
+ heartbeat.msg = `${res.status} - ${res.statusText}`;
+ }
+ } catch (error) {
+ if (axios.isCancel(error)) {
+ 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)}`);
+ heartbeat.msg = error.message;
+ }
+ }
+ }
+ }
+}
+
+module.exports = {
+ RabbitMqMonitorType,
+};
diff --git a/server/server.js b/server/server.js
index db58ae829..c88daca88 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 76bf42565..062f098d7 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/lang/en.json b/src/lang/en.json
index 5bfc3bd92..d56e61bdf 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1052,6 +1052,13 @@
"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 Management Nodes",
+ "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 a fully qualified (starting with 'http') URL for RabbitMQ nodes.",
+ "RabbitMQ Username": "RabbitMQ Username",
+ "RabbitMQ Password": "RabbitMQ Password",
+ "rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
"SendGrid API Key": "SendGrid API Key",
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
}
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 5d999b597..677210c45 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -64,6 +64,9 @@
+
@@ -90,6 +93,13 @@
+