diff --git a/db/knex_migrations/2025-07-17-0000-mqtt-websocket-path.js b/db/knex_migrations/2025-07-17-0000-mqtt-websocket-path.js new file mode 100644 index 000000000..85b05f110 --- /dev/null +++ b/db/knex_migrations/2025-07-17-0000-mqtt-websocket-path.js @@ -0,0 +1,15 @@ +exports.up = function (knex) { + // Add new column monitor.mqtt_websocket_path + return knex.schema + .alterTable("monitor", function (table) { + table.string("mqtt_websocket_path", 255).nullable(); + }); +}; + +exports.down = function (knex) { + // Drop column monitor.mqtt_websocket_path + return knex.schema + .alterTable("monitor", function (table) { + table.dropColumn("mqtt_websocket_path"); + }); +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index 0ddfa924c..178d639cd 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -190,6 +190,7 @@ class Monitor extends BeanModel { radiusSecret: this.radiusSecret, mqttUsername: this.mqttUsername, mqttPassword: this.mqttPassword, + mqttWebsocketPath: this.mqttWebsocketPath, authWorkstation: this.authWorkstation, authDomain: this.authDomain, tlsCa: this.tlsCa, diff --git a/server/monitor-types/mqtt.js b/server/monitor-types/mqtt.js index ad734ce8e..1865bbb42 100644 --- a/server/monitor-types/mqtt.js +++ b/server/monitor-types/mqtt.js @@ -15,6 +15,7 @@ class MqttMonitorType extends MonitorType { username: monitor.mqttUsername, password: monitor.mqttPassword, interval: monitor.interval, + websocketPath: monitor.mqttWebsocketPath, }); if (monitor.mqttCheckType == null || monitor.mqttCheckType === "") { @@ -52,12 +53,12 @@ class MqttMonitorType extends MonitorType { * @param {string} hostname Hostname / address of machine to test * @param {string} topic MQTT topic * @param {object} options MQTT options. Contains port, username, - * password and interval (interval defaults to 20) + * password, websocketPath and interval (interval defaults to 20) * @returns {Promise} Received MQTT message */ mqttAsync(hostname, topic, options = {}) { return new Promise((resolve, reject) => { - const { port, username, password, interval = 20 } = options; + const { port, username, password, websocketPath, interval = 20 } = options; // Adds MQTT protocol to the hostname if not already present if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) { @@ -70,7 +71,15 @@ class MqttMonitorType extends MonitorType { reject(new Error("Timeout, Message not received")); }, interval * 1000 * 0.8); - const mqttUrl = `${hostname}:${port}`; + // Construct the URL based on protocol + let mqttUrl = `${hostname}:${port}`; + if (hostname.startsWith("ws://") || hostname.startsWith("wss://")) { + if (websocketPath && !websocketPath.startsWith("/")) { + mqttUrl = `${hostname}:${port}/${websocketPath || ""}`; + } else { + mqttUrl = `${hostname}:${port}${websocketPath || ""}`; + } + } log.debug("mqtt", `MQTT connecting to ${mqttUrl}`); diff --git a/server/server.js b/server/server.js index b7025464b..3fd0585d4 100644 --- a/server/server.js +++ b/server/server.js @@ -837,6 +837,7 @@ let needSetup = false; bean.mqttTopic = monitor.mqttTopic; bean.mqttSuccessMessage = monitor.mqttSuccessMessage; bean.mqttCheckType = monitor.mqttCheckType; + bean.mqttWebsocketPath = monitor.mqttWebsocketPath; bean.databaseConnectionString = monitor.databaseConnectionString; bean.databaseQuery = monitor.databaseQuery; bean.authMethod = monitor.authMethod; diff --git a/src/lang/en.json b/src/lang/en.json index b6449371b..2331e29dc 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -71,6 +71,7 @@ "locally configured mail transfer agent": "locally configured mail transfer agent", "Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Either enter the hostname of the server you want to connect to or {localhost} if you intend to use a {local_mta}", "Port": "Port", + "Path": "Path", "Heartbeat Interval": "Heartbeat Interval", "Request Timeout": "Request Timeout", "timeoutAfter": "Timeout after {0} seconds", @@ -266,6 +267,10 @@ "Current User": "Current User", "topic": "Topic", "topicExplanation": "MQTT topic to monitor", + "mqttWebSocketPath": "MQTT WebSocket Path", + "mqttWebsocketPathExplanation": "WebSocket path for MQTT over WebSocket connections (e.g., /mqtt)", + "mqttWebsocketPathInvalid": "Please use a valid WebSocket Path format", + "mqttHostnameTip": "Please use this format {hostnameFormat}", "successKeyword": "Success Keyword", "successKeywordExplanation": "MQTT Keyword that will be considered as success", "recent": "Recent", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 1b7af4184..56a86270d 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -311,6 +311,13 @@ required data-testid="hostname-input" > +
+ + + +
@@ -483,6 +490,21 @@ +
+ + + +
+ {{ $t("mqttWebsocketPathExplanation") }} +
+
+