Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Added MQTT Monitor type #875

Merged
merged 34 commits into from
Apr 17, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
670754b
added MQTT monitor type
tarun7singh Nov 4, 2021
6828e8e
Merge branch 'master' of https://github.com/tarun7singh/uptime-kuma
tarun7singh Nov 4, 2021
2d80258
Update review suggestions
tarun7singh Nov 5, 2021
d5d07da
update review suggestions
tarun7singh Nov 5, 2021
2bcbeba
update review suggestions
tarun7singh Nov 5, 2021
1490155
server url changes
tarun7singh Nov 5, 2021
3721d11
changed table column names for more specifity
tarun7singh Nov 9, 2021
3f5133d
Added authentication logic
tarun7singh Nov 17, 2021
3306f4a
removed extra logging
tarun7singh Nov 18, 2021
35da8c7
added connection timeout and refactored code
tarun7singh Nov 22, 2021
5afc6a4
removed https requirement for url
tarun7singh Dec 6, 2021
d3c90df
fixed edit monitor fields empty issues
tarun7singh Dec 18, 2021
4118de6
fix protocol not defined bug
tarun7singh Dec 24, 2021
076d6bd
Merge branch 'master' into mqtt
louislam Jan 13, 2022
bfee634
Merge branch 'master' into mqtt
louislam Jan 13, 2022
6014ed1
Fix conflict
louislam Jan 13, 2022
482b3f9
Update server/util-server.js
louislam Jan 13, 2022
9462646
Fix vulnerabilities
louislam Jan 13, 2022
32ec4be
Merge branch 'master' into mqtt
louislam Jan 13, 2022
1c8407a
[MQTT] Use existing fields instead of creating new ones (UI)
louislam Jan 13, 2022
6272514
[MQTT] Use existing fields instead of creating new ones (Server)
louislam Jan 13, 2022
227bbde
[MQTT] Try to improve error handling
louislam Jan 13, 2022
22256df
added timeout for removing the dead loop state
tarun7singh Jan 20, 2022
0345719
added cleartimeout in case client is already ended
tarun7singh Jan 20, 2022
b1c7915
Merge branch 'master' into mqtt2
louislam Apr 16, 2022
64a0e1a
Update package-lock.json
louislam Apr 16, 2022
083e835
Change debug to log.debug
louislam Apr 16, 2022
30e1137
Add HIDE_LOG and catch error if cannot subscribe topic
louislam Apr 16, 2022
566133e
Domain Name Expiry Notification for https monitor only
louislam Apr 16, 2022
e344203
Remove try-catch and fix username/password/port not working for mqtt
louislam Apr 16, 2022
136fdf3
MQTT password field to password type
louislam Apr 16, 2022
03bcf5c
Add a simple mqtt server for testing
louislam Apr 16, 2022
5fa62a8
Merge branch 'master' into mqtt2
louislam Apr 17, 2022
2a248ad
Change mqtt_topic from VARCHAR to TEXT
louislam Apr 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions db/patch-added-mqtt-monitor.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;

ALTER TABLE monitor
ADD topic VARCHAR(50);
tarun7singh marked this conversation as resolved.
Show resolved Hide resolved

ALTER TABLE monitor
ADD success_message VARCHAR(255);
tarun7singh marked this conversation as resolved.
Show resolved Hide resolved

COMMIT;
256 changes: 165 additions & 91 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"jsonwebtoken": "~8.5.1",
"jwt-decode": "^3.1.2",
"limiter": "^2.1.0",
"mqtt": "^4.2.8",
"nodemailer": "~6.6.5",
"notp": "~2.0.3",
"password-hash": "~1.2.2",
Expand Down
1 change: 1 addition & 0 deletions server/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Database {
"patch-http-monitor-method-body-and-headers.sql": true,
"patch-2fa-invalidate-used-token.sql": true,
"patch-notification_sent_history.sql": true,
"patch-added-mqtt-monitor.sql": true,
}

/**
Expand Down
26 changes: 17 additions & 9 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const https = require("https");
const dayjs = require("dayjs");
const mqtt = require("mqtt");
const utc = require("dayjs/plugin/utc");
let timezone = require("dayjs/plugin/timezone");
dayjs.extend(utc);
dayjs.extend(timezone);
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog, mqttAsync } = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
Expand Down Expand Up @@ -112,7 +113,7 @@ class Monitor extends BeanModel {
// undefined if not https
let tlsInfo = undefined;

if (! previousBeat) {
if (!previousBeat) {
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
this.id,
]);
Expand All @@ -130,7 +131,7 @@ class Monitor extends BeanModel {
}

// Duration
if (! isFirstBeat) {
if (!isFirstBeat) {
bean.duration = dayjs(bean.time).diff(dayjs(previousBeat.time), "second");
} else {
bean.duration = 0;
Expand All @@ -153,7 +154,7 @@ class Monitor extends BeanModel {
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: ! this.getIgnoreTls(),
rejectUnauthorized: !this.getIgnoreTls(),
}),
maxRedirects: this.maxredirects,
validateStatus: (status) => {
Expand Down Expand Up @@ -296,7 +297,7 @@ class Monitor extends BeanModel {
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: ! this.getIgnoreTls(),
rejectUnauthorized: !this.getIgnoreTls(),
}),
maxRedirects: this.maxredirects,
validateStatus: (status) => {
Expand All @@ -318,7 +319,14 @@ class Monitor extends BeanModel {
} else {
throw new Error("Server not found on Steam");
}

} else if (this.type === "mqtt") {
try {
bean.msg = await mqttAsync(this.url, this.topic, this.successMessage);
bean.status = UP;
} catch (error) {
bean.status = DOWN;
bean.msg = error.message;
}
} else {
bean.msg = "Unknown Monitor Type";
bean.status = PENDING;
Expand Down Expand Up @@ -385,7 +393,7 @@ class Monitor extends BeanModel {

previousBeat = bean;

if (! this.isStop) {
if (!this.isStop) {

if (demoMode) {
if (beatInterval < 20) {
Expand All @@ -407,7 +415,7 @@ class Monitor extends BeanModel {
errorLog(e, false);
console.error("Please report to https://github.com/louislam/uptime-kuma/issues");

if (! this.isStop) {
if (!this.isStop) {
console.log("Try to restart the monitor");
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
}
Expand Down Expand Up @@ -590,7 +598,7 @@ class Monitor extends BeanModel {

} else {
// Handle new monitor with only one beat, because the beat's duration = 0
let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ]));
let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [monitorID]));

if (status === UP) {
uptime = 1;
Expand Down
33 changes: 29 additions & 4 deletions server/util-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const iconv = require("iconv-lite");
const chardet = require("chardet");
const fs = require("fs");
const nodeJsUtil = require("util");
const mqtt = require("mqtt");

tarun7singh marked this conversation as resolved.
Show resolved Hide resolved

// From ping-lite
exports.WIN = /^win/.test(process.platform);
Expand All @@ -26,7 +28,7 @@ exports.initJWTSecret = async () => {
"jwtSecret",
]);

if (! jwtSecretBean) {
if (!jwtSecretBean) {
jwtSecretBean = R.dispense("setting");
jwtSecretBean.key = "jwtSecret";
}
Expand Down Expand Up @@ -88,6 +90,29 @@ exports.pingAsync = function (hostname, ipv6 = false) {
});
};

exports.mqttAsync = function (hostname, topic, okMessage) {
return new Promise((resolve, reject) => {
try {
let client = mqtt.connect(hostname);
client.on("connect", () => {
client.subscribe(topic);
});
client.on("message", (messageTopic, message) => {
console.log(messageTopic);
if (messageTopic == topic && message.toString() !== okMessage) {
client.end();
reject(new Error(`Error; Topic: ${messageTopic}; Message: ${message.toString()}`));
} else {
client.end();
resolve(`Topic: ${messageTopic}; Message: ${message.toString()}`);
}
});
} catch (error) {
reject(new Error(error));
}
});
};

exports.dnsResolve = function (hostname, resolver_server, rrtype) {
const resolver = new Resolver();
resolver.setServers([resolver_server]);
Expand Down Expand Up @@ -269,13 +294,13 @@ exports.getTotalClientInRoom = (io, roomName) => {

const sockets = io.sockets;

if (! sockets) {
if (!sockets) {
return 0;
}

const adapter = sockets.adapter;

if (! adapter) {
if (!adapter) {
return 0;
}

Expand All @@ -300,7 +325,7 @@ exports.allowAllOrigin = (res) => {
};

exports.checkLogin = (socket) => {
if (! socket.userID) {
if (!socket.userID) {
throw new Error("You are not logged in.");
}
};
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,10 @@ export default {
"One record": "One record",
steamApiKeyDescription: "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key here: ",
"Current User": "Current User",
topic: "Topic",
topicExplanation: "MQTT topic to monitor",
successMessage: "Success Message",
successMessageExplanation: "MQTT message that will be considered as success",
serverUrl: "Server URL",
recent: "Recent",
};
30 changes: 30 additions & 0 deletions src/pages/EditMonitor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
<option value="steam">
Steam Game Server
</option>
<option value="mqtt">
MQTT
</option>
</select>
</div>

Expand Down Expand Up @@ -115,6 +118,31 @@
</div>
</template>

<!-- MQTT -->
<!-- For MQTT Type -->
<template v-if="monitor.type === 'mqtt'">
<div class="my-3">
<label for="url" class="form-label">{{ $t("serverUrl") }}</label>
<input id="url" v-model="monitor.url" type="text" class="form-control" pattern="https?://.+" required>
</div>

<div class="my-3">
<label for="topic" class="form-label">{{ $t("topic") }}</label>
<input id="topic" v-model="monitor.topic" type="text" class="form-control" required>
<div class="form-text">
{{ $t("topicExplanation") }}
</div>
</div>

<div class="my-3">
<label for="successMessage" class="form-label">{{ $t("successMessage") }}</label>
<input id="successMessage" v-model="monitor.successMessage" type="text" class="form-control" required>
<div class="form-text">
{{ $t("successMessageExplanation") }}
</div>
</div>
</template>

<!-- Interval -->
<div class="my-3">
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
Expand Down Expand Up @@ -426,6 +454,8 @@ export default {
accepted_statuscodes: ["200-299"],
dns_resolve_type: "A",
dns_resolve_server: "1.1.1.1",
topic: "",
successMessage: "",
};

for (let i = 0; i < this.$root.notificationList.length; i++) {
Expand Down