added MQTT monitor type
This commit is contained in:
		
							parent
							
								
									d4c9431142
								
							
						
					
					
						commit
						670754b697
					
				
					 8 changed files with 261 additions and 14937 deletions
				
			
		
							
								
								
									
										10
									
								
								db/patch-added-mqtt-monitor.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								db/patch-added-mqtt-monitor.sql
									
									
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| 
 | ||||
| ALTER TABLE monitor | ||||
| 	ADD success_message VARCHAR(255); | ||||
| 
 | ||||
| COMMIT; | ||||
							
								
								
									
										15091
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15091
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -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", | ||||
|  |  | |||
|  | @ -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, | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| 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); | ||||
|  | @ -7,7 +8,7 @@ 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"); | ||||
|  | @ -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, | ||||
|                 ]); | ||||
|  | @ -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; | ||||
|  | @ -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) => { | ||||
|  | @ -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) => { | ||||
|  | @ -319,6 +320,14 @@ class Monitor extends BeanModel { | |||
|                         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; | ||||
|  | @ -385,7 +394,7 @@ class Monitor extends BeanModel { | |||
| 
 | ||||
|             previousBeat = bean; | ||||
| 
 | ||||
|             if (! this.isStop) { | ||||
|             if (!this.isStop) { | ||||
| 
 | ||||
|                 if (demoMode) { | ||||
|                     if (beatInterval < 20) { | ||||
|  | @ -407,7 +416,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); | ||||
|                 } | ||||
|  | @ -590,7 +599,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; | ||||
|  |  | |||
|  | @ -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"); | ||||
| 
 | ||||
| 
 | ||||
| // From ping-lite
 | ||||
| exports.WIN = /^win/.test(process.platform); | ||||
|  | @ -26,7 +28,7 @@ exports.initJWTSecret = async () => { | |||
|         "jwtSecret", | ||||
|     ]); | ||||
| 
 | ||||
|     if (! jwtSecretBean) { | ||||
|     if (!jwtSecretBean) { | ||||
|         jwtSecretBean = R.dispense("setting"); | ||||
|         jwtSecretBean.key = "jwtSecret"; | ||||
|     } | ||||
|  | @ -88,6 +90,30 @@ 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]); | ||||
|  | @ -269,13 +295,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; | ||||
|     } | ||||
| 
 | ||||
|  | @ -300,7 +326,7 @@ exports.allowAllOrigin = (res) => { | |||
| }; | ||||
| 
 | ||||
| exports.checkLogin = (socket) => { | ||||
|     if (! socket.userID) { | ||||
|     if (!socket.userID) { | ||||
|         throw new Error("You are not logged in."); | ||||
|     } | ||||
| }; | ||||
|  |  | |||
|  | @ -306,4 +306,9 @@ 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", | ||||
|     url: "Server URL", | ||||
| }; | ||||
|  |  | |||
|  | @ -32,6 +32,9 @@ | |||
|                                     <option value="steam"> | ||||
|                                         Steam Game Server | ||||
|                                     </option> | ||||
|                                     <option value="mqtt"> | ||||
|                                         MQTT | ||||
|                                     </option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
| 
 | ||||
|  | @ -115,6 +118,32 @@ | |||
|                                 </div> | ||||
|                             </template> | ||||
| 
 | ||||
| 
 | ||||
|                             <!-- MQTT --> | ||||
|                             <!-- For MQTT Type --> | ||||
|                             <template v-if="monitor.type === 'mqtt'"> | ||||
|                                 <div class="my-3"> | ||||
|                                     <label for="url" class="form-label">{{ $t("url") }}</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> | ||||
|  | @ -426,6 +455,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++) { | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue