Merge branch 'master' into feature/add-channel-notification-for-slack
This commit is contained in:
		
						commit
						376d84c742
					
				
					 29 changed files with 1174 additions and 550 deletions
				
			
		
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							|  | @ -148,17 +148,18 @@ Telegram Notification Sample: | ||||||
| 
 | 
 | ||||||
| If you love this project, please consider giving me a ⭐. | If you love this project, please consider giving me a ⭐. | ||||||
| 
 | 
 | ||||||
| ## 🗣️ Discussion | ## 🗣️ Discussion / Ask for Help | ||||||
| 
 | 
 | ||||||
| ### Issues Page | ⚠️ For any general or technical questions, please don't send me an email, as I am unable to provide support in that manner. I will not response if you asked such questions. | ||||||
| 
 | 
 | ||||||
| You can discuss or ask for help in [issues](https://github.com/louislam/uptime-kuma/issues). | I recommend using Google, GitHub Issues, or Uptime Kuma's Subreddit for finding answers to your question. If you cannot find the information you need, feel free to ask: | ||||||
| 
 | 
 | ||||||
| ### Subreddit | - [GitHub Issues](https://github.com/louislam/uptime-kuma/issues) | ||||||
|  | - [Subreddit r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/) | ||||||
| 
 | 
 | ||||||
| My Reddit account: [u/louislamlam](https://reddit.com/u/louislamlam).   | My Reddit account: [u/louislamlam](https://reddit.com/u/louislamlam).   | ||||||
| You can mention me if you ask a question on Reddit. | You can mention me if you ask a question on Reddit. | ||||||
| [r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/) | 
 | ||||||
| 
 | 
 | ||||||
| ## Contribute | ## Contribute | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8" /> |     <meta charset="UTF-8" /> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |     <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> | ||||||
|     <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> |     <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> | ||||||
|     <link rel="icon" type="image/svg+xml" href="/icon.svg" /> |     <link rel="icon" type="image/svg+xml" href="/icon.svg" /> | ||||||
|     <link rel="manifest" href="/manifest.json" /> |     <link rel="manifest" href="/manifest.json" /> | ||||||
|  |  | ||||||
							
								
								
									
										936
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										936
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -185,6 +185,7 @@ | ||||||
|         "vue-router": "~4.0.14", |         "vue-router": "~4.0.14", | ||||||
|         "vue-toastification": "~2.0.0-rc.5", |         "vue-toastification": "~2.0.0-rc.5", | ||||||
|         "vuedraggable": "~4.1.0", |         "vuedraggable": "~4.1.0", | ||||||
|         "wait-on": "^6.0.1" |         "wait-on": "^6.0.1", | ||||||
|  |         "whatwg-url": "~12.0.1" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -365,8 +365,8 @@ class Monitor extends BeanModel { | ||||||
|                             tlsInfo = await this.updateTlsInfo(tlsInfoObject); |                             tlsInfo = await this.updateTlsInfo(tlsInfoObject); | ||||||
| 
 | 
 | ||||||
|                             if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) { |                             if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) { | ||||||
|                                 log.debug("monitor", `[${this.name}] call sendCertNotification`); |                                 log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`); | ||||||
|                                 await this.sendCertNotification(tlsInfoObject); |                                 await this.checkCertExpiryNotifications(tlsInfoObject); | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
|                         } catch (e) { |                         } catch (e) { | ||||||
|  | @ -1212,13 +1212,19 @@ class Monitor extends BeanModel { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Send notification about a certificate |      * checks certificate chain for expiring certificates | ||||||
|      * @param {Object} tlsInfoObject Information about certificate |      * @param {Object} tlsInfoObject Information about certificate | ||||||
|      */ |      */ | ||||||
|     async sendCertNotification(tlsInfoObject) { |     async checkCertExpiryNotifications(tlsInfoObject) { | ||||||
|         if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { |         if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { | ||||||
|             const notificationList = await Monitor.getNotificationList(this); |             const notificationList = await Monitor.getNotificationList(this); | ||||||
| 
 | 
 | ||||||
|  |             if (! notificationList.length > 0) { | ||||||
|  |                 // fail fast. If no notification is set, all the following checks can be skipped.
 | ||||||
|  |                 log.debug("monitor", "No notification, no need to send cert notification"); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             let notifyDays = await setting("tlsExpiryNotifyDays"); |             let notifyDays = await setting("tlsExpiryNotifyDays"); | ||||||
|             if (notifyDays == null || !Array.isArray(notifyDays)) { |             if (notifyDays == null || !Array.isArray(notifyDays)) { | ||||||
|                 // Reset Default
 |                 // Reset Default
 | ||||||
|  | @ -1226,10 +1232,19 @@ class Monitor extends BeanModel { | ||||||
|                 notifyDays = [ 7, 14, 21 ]; |                 notifyDays = [ 7, 14, 21 ]; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (notifyDays != null && Array.isArray(notifyDays)) { |             if (Array.isArray(notifyDays)) { | ||||||
|                 for (const day of notifyDays) { |                 for (const targetDays of notifyDays) { | ||||||
|                     log.debug("monitor", "call sendCertNotificationByTargetDays", day); |                     let certInfo = tlsInfoObject.certInfo; | ||||||
|                     await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, day, notificationList); |                     while (certInfo) { | ||||||
|  |                         let subjectCN = certInfo.subject["CN"]; | ||||||
|  |                         if (certInfo.daysRemaining > targetDays) { | ||||||
|  |                             log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`); | ||||||
|  |                         } else { | ||||||
|  |                             log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`); | ||||||
|  |                             await this.sendCertNotificationByTargetDays(subjectCN, certInfo.certType, certInfo.daysRemaining, targetDays, notificationList); | ||||||
|  |                         } | ||||||
|  |                         certInfo = certInfo.issuerCertificate; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -1238,55 +1253,47 @@ class Monitor extends BeanModel { | ||||||
|     /** |     /** | ||||||
|      * Send a certificate notification when certificate expires in less |      * Send a certificate notification when certificate expires in less | ||||||
|      * than target days |      * than target days | ||||||
|      * @param {number} daysRemaining Number of days remaining on certifcate |      * @param {string} certCN  Common Name attribute from the certificate subject | ||||||
|  |      * @param {string} certType  certificate type | ||||||
|  |      * @param {number} daysRemaining Number of days remaining on certificate | ||||||
|      * @param {number} targetDays Number of days to alert after |      * @param {number} targetDays Number of days to alert after | ||||||
|      * @param {LooseObject<any>[]} notificationList List of notification providers |      * @param {LooseObject<any>[]} notificationList List of notification providers | ||||||
|      * @returns {Promise<void>} |      * @returns {Promise<void>} | ||||||
|      */ |      */ | ||||||
|     async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) { |     async sendCertNotificationByTargetDays(certCN, certType, daysRemaining, targetDays, notificationList) { | ||||||
| 
 | 
 | ||||||
|         if (daysRemaining > targetDays) { |         let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days <= ?", [ | ||||||
|             log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`); |             "certificate", | ||||||
|  |             this.id, | ||||||
|  |             targetDays, | ||||||
|  |         ]); | ||||||
|  | 
 | ||||||
|  |         // Sent already, no need to send again
 | ||||||
|  |         if (row) { | ||||||
|  |             log.debug("monitor", "Sent already, no need to send again"); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (notificationList.length > 0) { |         let sent = false; | ||||||
|  |         log.debug("monitor", "Send certificate notification"); | ||||||
| 
 | 
 | ||||||
|             let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days <= ?", [ |         for (let notification of notificationList) { | ||||||
|  |             try { | ||||||
|  |                 log.debug("monitor", "Sending to " + notification.name); | ||||||
|  |                 await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will be expired in ${daysRemaining} days`); | ||||||
|  |                 sent = true; | ||||||
|  |             } catch (e) { | ||||||
|  |                 log.error("monitor", "Cannot send cert notification to " + notification.name); | ||||||
|  |                 log.error("monitor", e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (sent) { | ||||||
|  |             await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [ | ||||||
|                 "certificate", |                 "certificate", | ||||||
|                 this.id, |                 this.id, | ||||||
|                 targetDays, |                 targetDays, | ||||||
|             ]); |             ]); | ||||||
| 
 |  | ||||||
|             // Sent already, no need to send again
 |  | ||||||
|             if (row) { |  | ||||||
|                 log.debug("monitor", "Sent already, no need to send again"); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             let sent = false; |  | ||||||
|             log.debug("monitor", "Send certificate notification"); |  | ||||||
| 
 |  | ||||||
|             for (let notification of notificationList) { |  | ||||||
|                 try { |  | ||||||
|                     log.debug("monitor", "Sending to " + notification.name); |  | ||||||
|                     await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will expire in ${daysRemaining} days`); |  | ||||||
|                     sent = true; |  | ||||||
|                 } catch (e) { |  | ||||||
|                     log.error("monitor", "Cannot send cert notification to " + notification.name); |  | ||||||
|                     log.error("monitor", e); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (sent) { |  | ||||||
|                 await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [ |  | ||||||
|                     "certificate", |  | ||||||
|                     this.id, |  | ||||||
|                     targetDays, |  | ||||||
|                 ]); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             log.debug("monitor", "No notification, no need to send cert notification"); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -59,8 +59,8 @@ class Discord extends NotificationProvider { | ||||||
|                                 value: monitorJSON["type"] === "push" ? "Heartbeat" : address, |                                 value: monitorJSON["type"] === "push" ? "Heartbeat" : address, | ||||||
|                             }, |                             }, | ||||||
|                             { |                             { | ||||||
|                                 name: "Time (UTC)", |                                 name: `Time (${heartbeatJSON["timezone"]})`, | ||||||
|                                 value: heartbeatJSON["time"], |                                 value: heartbeatJSON["localDateTime"], | ||||||
|                             }, |                             }, | ||||||
|                             { |                             { | ||||||
|                                 name: "Error", |                                 name: "Error", | ||||||
|  | @ -94,8 +94,8 @@ class Discord extends NotificationProvider { | ||||||
|                                 value: monitorJSON["type"] === "push" ? "Heartbeat" : address, |                                 value: monitorJSON["type"] === "push" ? "Heartbeat" : address, | ||||||
|                             }, |                             }, | ||||||
|                             { |                             { | ||||||
|                                 name: "Time (UTC)", |                                 name: `Time (${heartbeatJSON["timezone"]})`, | ||||||
|                                 value: heartbeatJSON["time"], |                                 value: heartbeatJSON["localDateTime"], | ||||||
|                             }, |                             }, | ||||||
|                             { |                             { | ||||||
|                                 name: "Ping", |                                 name: "Ping", | ||||||
|  |  | ||||||
|  | @ -35,8 +35,7 @@ class Feishu extends NotificationProvider { | ||||||
|                                             text: |                                             text: | ||||||
|                                                 "[Down] " + |                                                 "[Down] " + | ||||||
|                                                 heartbeatJSON["msg"] + |                                                 heartbeatJSON["msg"] + | ||||||
|                                                 "\nTime (UTC): " + |                                                 `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` | ||||||
|                                                 heartbeatJSON["time"], |  | ||||||
|                                         }, |                                         }, | ||||||
|                                     ], |                                     ], | ||||||
|                                 ], |                                 ], | ||||||
|  | @ -62,8 +61,7 @@ class Feishu extends NotificationProvider { | ||||||
|                                             text: |                                             text: | ||||||
|                                                 "[Up] " + |                                                 "[Up] " + | ||||||
|                                                 heartbeatJSON["msg"] + |                                                 heartbeatJSON["msg"] + | ||||||
|                                                 "\nTime (UTC): " + |                                                 `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`, | ||||||
|                                                 heartbeatJSON["time"], |  | ||||||
|                                         }, |                                         }, | ||||||
|                                     ], |                                     ], | ||||||
|                                 ], |                                 ], | ||||||
|  |  | ||||||
|  | @ -33,7 +33,10 @@ class Line extends NotificationProvider { | ||||||
|                     "messages": [ |                     "messages": [ | ||||||
|                         { |                         { | ||||||
|                             "type": "text", |                             "type": "text", | ||||||
|                             "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] |                             "text": "UptimeKuma Alert: [🔴 Down]\n" + | ||||||
|  |                                 "Name: " + monitorJSON["name"] + " \n" + | ||||||
|  |                                 heartbeatJSON["msg"] + | ||||||
|  |                                 `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` | ||||||
|                         } |                         } | ||||||
|                     ] |                     ] | ||||||
|                 }; |                 }; | ||||||
|  | @ -44,7 +47,10 @@ class Line extends NotificationProvider { | ||||||
|                     "messages": [ |                     "messages": [ | ||||||
|                         { |                         { | ||||||
|                             "type": "text", |                             "type": "text", | ||||||
|                             "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] |                             "text": "UptimeKuma Alert: [✅ Up]\n" + | ||||||
|  |                                 "Name: " + monitorJSON["name"] + " \n" + | ||||||
|  |                                 heartbeatJSON["msg"] + | ||||||
|  |                                 `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` | ||||||
|                         } |                         } | ||||||
|                     ] |                     ] | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
|  | @ -24,12 +24,18 @@ class LineNotify extends NotificationProvider { | ||||||
|                 await axios.post(lineAPIUrl, qs.stringify(testMessage), config); |                 await axios.post(lineAPIUrl, qs.stringify(testMessage), config); | ||||||
|             } else if (heartbeatJSON["status"] === DOWN) { |             } else if (heartbeatJSON["status"] === DOWN) { | ||||||
|                 let downMessage = { |                 let downMessage = { | ||||||
|                     "message": "\n[🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] |                     "message": "\n[🔴 Down]\n" + | ||||||
|  |                         "Name: " + monitorJSON["name"] + " \n" + | ||||||
|  |                         heartbeatJSON["msg"] + "\n" + | ||||||
|  |                         `Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` | ||||||
|                 }; |                 }; | ||||||
|                 await axios.post(lineAPIUrl, qs.stringify(downMessage), config); |                 await axios.post(lineAPIUrl, qs.stringify(downMessage), config); | ||||||
|             } else if (heartbeatJSON["status"] === UP) { |             } else if (heartbeatJSON["status"] === UP) { | ||||||
|                 let upMessage = { |                 let upMessage = { | ||||||
|                     "message": "\n[✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] |                     "message": "\n[✅ Up]\n" + | ||||||
|  |                         "Name: " + monitorJSON["name"] + " \n" + | ||||||
|  |                         heartbeatJSON["msg"] + "\n" + | ||||||
|  |                         `Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` | ||||||
|                 }; |                 }; | ||||||
|                 await axios.post(lineAPIUrl, qs.stringify(upMessage), config); |                 await axios.post(lineAPIUrl, qs.stringify(upMessage), config); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -28,7 +28,9 @@ class LunaSea extends NotificationProvider { | ||||||
|             if (heartbeatJSON["status"] === DOWN) { |             if (heartbeatJSON["status"] === DOWN) { | ||||||
|                 let downdata = { |                 let downdata = { | ||||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], |                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||||
|                     "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |                     "body": "[🔴 Down] " + | ||||||
|  |                         heartbeatJSON["msg"] + | ||||||
|  |                         `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` | ||||||
|                 }; |                 }; | ||||||
|                 await axios.post(lunaseaurl, downdata); |                 await axios.post(lunaseaurl, downdata); | ||||||
|                 return okMsg; |                 return okMsg; | ||||||
|  | @ -37,7 +39,9 @@ class LunaSea extends NotificationProvider { | ||||||
|             if (heartbeatJSON["status"] === UP) { |             if (heartbeatJSON["status"] === UP) { | ||||||
|                 let updata = { |                 let updata = { | ||||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], |                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||||
|                     "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |                     "body": "[✅ Up] " + | ||||||
|  |                         heartbeatJSON["msg"] + | ||||||
|  |                         `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` | ||||||
|                 }; |                 }; | ||||||
|                 await axios.post(lunaseaurl, updata); |                 await axios.post(lunaseaurl, updata); | ||||||
|                 return okMsg; |                 return okMsg; | ||||||
|  |  | ||||||
|  | @ -88,8 +88,8 @@ class Mattermost extends NotificationProvider { | ||||||
|                             statusField, |                             statusField, | ||||||
|                             { |                             { | ||||||
|                                 short: true, |                                 short: true, | ||||||
|                                 title: "Time (UTC)", |                                 title: `Time (${heartbeatJSON["timezone"]})`, | ||||||
|                                 value: heartbeatJSON.time, |                                 value: heartbeatJSON.localDateTime, | ||||||
|                             }, |                             }, | ||||||
|                         ], |                         ], | ||||||
|                     }, |                     }, | ||||||
|  |  | ||||||
|  | @ -29,14 +29,18 @@ class Pushbullet extends NotificationProvider { | ||||||
|                 let downData = { |                 let downData = { | ||||||
|                     "type": "note", |                     "type": "note", | ||||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], |                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||||
|                     "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |                     "body": "[🔴 Down] " + | ||||||
|  |                         heartbeatJSON["msg"] + | ||||||
|  |                         `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`, | ||||||
|                 }; |                 }; | ||||||
|                 await axios.post(pushbulletUrl, downData, config); |                 await axios.post(pushbulletUrl, downData, config); | ||||||
|             } else if (heartbeatJSON["status"] === UP) { |             } else if (heartbeatJSON["status"] === UP) { | ||||||
|                 let upData = { |                 let upData = { | ||||||
|                     "type": "note", |                     "type": "note", | ||||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], |                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||||
|                     "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |                     "body": "[✅ Up] " + | ||||||
|  |                         heartbeatJSON["msg"] + | ||||||
|  |                         `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`, | ||||||
|                 }; |                 }; | ||||||
|                 await axios.post(pushbulletUrl, upData, config); |                 await axios.post(pushbulletUrl, upData, config); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -24,13 +24,16 @@ class Pushover extends NotificationProvider { | ||||||
|         if (notification.pushoverdevice) { |         if (notification.pushoverdevice) { | ||||||
|             data.device = notification.pushoverdevice; |             data.device = notification.pushoverdevice; | ||||||
|         } |         } | ||||||
|  |         if (notification.pushoverttl) { | ||||||
|  |             data.ttl = notification.pushoverttl; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             if (heartbeatJSON == null) { |             if (heartbeatJSON == null) { | ||||||
|                 await axios.post(pushoverlink, data); |                 await axios.post(pushoverlink, data); | ||||||
|                 return okMsg; |                 return okMsg; | ||||||
|             } else { |             } else { | ||||||
|                 data.message += "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"]; |                 data.message += `\n<b>Time (${heartbeatJSON["timezone"]})</b>:${heartbeatJSON["localDateTime"]}`; | ||||||
|                 await axios.post(pushoverlink, data); |                 await axios.post(pushoverlink, data); | ||||||
|                 return okMsg; |                 return okMsg; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -22,8 +22,6 @@ class RocketChat extends NotificationProvider { | ||||||
|                 return okMsg; |                 return okMsg; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const time = heartbeatJSON["time"]; |  | ||||||
| 
 |  | ||||||
|             let data = { |             let data = { | ||||||
|                 "text": "Uptime Kuma Alert", |                 "text": "Uptime Kuma Alert", | ||||||
|                 "channel": notification.rocketchannel, |                 "channel": notification.rocketchannel, | ||||||
|  | @ -31,7 +29,7 @@ class RocketChat extends NotificationProvider { | ||||||
|                 "icon_emoji": notification.rocketiconemo, |                 "icon_emoji": notification.rocketiconemo, | ||||||
|                 "attachments": [ |                 "attachments": [ | ||||||
|                     { |                     { | ||||||
|                         "title": "Uptime Kuma Alert *Time (UTC)*\n" + time, |                         "title": `Uptime Kuma Alert *Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`, | ||||||
|                         "text": "*Message*\n" + msg, |                         "text": "*Message*\n" + msg, | ||||||
|                     } |                     } | ||||||
|                 ] |                 ] | ||||||
|  |  | ||||||
|  | @ -44,7 +44,6 @@ class Slack extends NotificationProvider { | ||||||
|                 return okMsg; |                 return okMsg; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const time = heartbeatJSON["time"]; |  | ||||||
|             const textMsg = "Uptime Kuma Alert"; |             const textMsg = "Uptime Kuma Alert"; | ||||||
|             let data = { |             let data = { | ||||||
|                 "text": `${textMsg}\n${msg}`, |                 "text": `${textMsg}\n${msg}`, | ||||||
|  | @ -70,7 +69,7 @@ class Slack extends NotificationProvider { | ||||||
|                                 }, |                                 }, | ||||||
|                                 { |                                 { | ||||||
|                                     "type": "mrkdwn", |                                     "type": "mrkdwn", | ||||||
|                                     "text": "*Time (UTC)*\n" + time, |                                     "text": `*Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`, | ||||||
|                                 }], |                                 }], | ||||||
|                             } |                             } | ||||||
|                         ], |                         ], | ||||||
|  |  | ||||||
|  | @ -91,7 +91,7 @@ class SMTP extends NotificationProvider { | ||||||
| 
 | 
 | ||||||
|         let bodyTextContent = msg; |         let bodyTextContent = msg; | ||||||
|         if (heartbeatJSON) { |         if (heartbeatJSON) { | ||||||
|             bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`; |             bodyTextContent = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // send mail with defined transport object
 |         // send mail with defined transport object
 | ||||||
|  |  | ||||||
|  | @ -19,6 +19,11 @@ const nodeVersion = parseInt(process.versions.node.split(".")[0]); | ||||||
| const requiredVersion = 14; | const requiredVersion = 14; | ||||||
| console.log(`Your Node.js version: ${nodeVersion}`); | console.log(`Your Node.js version: ${nodeVersion}`); | ||||||
| 
 | 
 | ||||||
|  | // See more: https://github.com/louislam/uptime-kuma/issues/3138
 | ||||||
|  | if (nodeVersion >= 20) { | ||||||
|  |     console.warn("\x1b[31m%s\x1b[0m", "Warning: Uptime Kuma is currently not stable on Node.js >= 20, please use Node.js 18."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| if (nodeVersion < requiredVersion) { | if (nodeVersion < requiredVersion) { | ||||||
|     console.error(`Error: Your Node.js version is not supported, please upgrade to Node.js >= ${requiredVersion}.`); |     console.error(`Error: Your Node.js version is not supported, please upgrade to Node.js >= ${requiredVersion}.`); | ||||||
|     process.exit(-1); |     process.exit(-1); | ||||||
|  |  | ||||||
|  | @ -519,12 +519,16 @@ const parseCertificateInfo = function (info) { | ||||||
| 
 | 
 | ||||||
|         // Move up the chain until loop is encountered
 |         // Move up the chain until loop is encountered
 | ||||||
|         if (link.issuerCertificate == null) { |         if (link.issuerCertificate == null) { | ||||||
|  |             link.certType = (i === 0) ? "self-signed" : "root CA"; | ||||||
|             break; |             break; | ||||||
|         } else if (link.issuerCertificate.fingerprint in existingList) { |         } else if (link.issuerCertificate.fingerprint in existingList) { | ||||||
|  |             // a root CA certificate is typically "signed by itself"  (=> "self signed certificate") and thus the "issuerCertificate" is a reference to itself.
 | ||||||
|             log.debug("cert", `[Last] ${link.issuerCertificate.fingerprint}`); |             log.debug("cert", `[Last] ${link.issuerCertificate.fingerprint}`); | ||||||
|  |             link.certType = (i === 0) ? "self-signed" : "root CA"; | ||||||
|             link.issuerCertificate = null; |             link.issuerCertificate = null; | ||||||
|             break; |             break; | ||||||
|         } else { |         } else { | ||||||
|  |             link.certType = (i === 0) ? "server" : "intermediate CA"; | ||||||
|             link = link.issuerCertificate; |             link = link.issuerCertificate; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -48,15 +48,14 @@ | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
| 
 |                     </div> | ||||||
|                         <div class="modal-footer"> |                     <div class="modal-footer"> | ||||||
|                             <button |                         <button | ||||||
|                                 id="monitor-submit-btn" class="btn btn-primary" type="submit" |                             id="monitor-submit-btn" class="btn btn-primary" type="submit" | ||||||
|                                 :disabled="processing" |                             :disabled="processing" | ||||||
|                             > |                         > | ||||||
|                                 {{ $t("Generate") }} |                             {{ $t("Generate") }} | ||||||
|                             </button> |                         </button> | ||||||
|                         </div> |  | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
							
								
								
									
										299
									
								
								src/components/BadgeGeneratorDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								src/components/BadgeGeneratorDialog.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,299 @@ | ||||||
|  | <template> | ||||||
|  |     <div ref="BadgeGeneratorModal" class="modal fade" tabindex="-1" data-bs-backdrop="static"> | ||||||
|  |         <div class="modal-dialog"> | ||||||
|  |             <div class="modal-content"> | ||||||
|  |                 <div class="modal-header"> | ||||||
|  |                     <h5 class="modal-title"> | ||||||
|  |                         {{ $t("Badge Generator", [monitor.name]) }} | ||||||
|  |                     </h5> | ||||||
|  |                     <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="modal-body"> | ||||||
|  |                     <div class="mb-3"> | ||||||
|  |                         <label for="type" class="form-label">{{ $t("Badge Type") }}</label> | ||||||
|  |                         <select id="type" v-model="badge.type" class="form-select"> | ||||||
|  |                             <option value="status">status</option> | ||||||
|  |                             <option value="uptime">uptime</option> | ||||||
|  |                             <option value="ping">ping</option> | ||||||
|  |                             <option value="avg-response">avg-response</option> | ||||||
|  |                             <option value="cert-exp">cert-exp</option> | ||||||
|  |                             <option value="response">response</option> | ||||||
|  |                         </select> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('duration') " class="mb-3"> | ||||||
|  |                         <label for="duration" class="form-label">{{ $t("Badge Duration") }}</label> | ||||||
|  |                         <input id="duration" v-model="badge.duration" type="number" min="0" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('label') " class="mb-3"> | ||||||
|  |                         <label for="label" class="form-label">{{ $t("Badge Label") }}</label> | ||||||
|  |                         <input id="label" v-model="badge.label" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('prefix') " class="mb-3"> | ||||||
|  |                         <label for="prefix" class="form-label">{{ $t("Badge Prefix") }}</label> | ||||||
|  |                         <input id="prefix" v-model="badge.prefix" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('suffix') " class="mb-3"> | ||||||
|  |                         <label for="suffix" class="form-label">{{ $t("Badge Suffix") }}</label> | ||||||
|  |                         <input id="suffix" v-model="badge.suffix" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelColor') " class="mb-3"> | ||||||
|  |                         <label for="labelColor" class="form-label">{{ $t("Badge Label Color") }}</label> | ||||||
|  |                         <input id="labelColor" v-model="badge.labelColor" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('color') " class="mb-3"> | ||||||
|  |                         <label for="color" class="form-label">{{ $t("Badge Color") }}</label> | ||||||
|  |                         <input id="color" v-model="badge.color" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelPrefix') " class="mb-3"> | ||||||
|  |                         <label for="labelPrefix" class="form-label">{{ $t("Badge Label Prefix") }}</label> | ||||||
|  |                         <input id="labelPrefix" v-model="badge.labelPrefix" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelSuffix') " class="mb-3"> | ||||||
|  |                         <label for="labelSuffix" class="form-label">{{ $t("Badge Label Suffix") }}</label> | ||||||
|  |                         <input id="labelSuffix" v-model="badge.labelSuffix" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('upColor') " class="mb-3"> | ||||||
|  |                         <label for="upColor" class="form-label">{{ $t("Badge Up Color") }}</label> | ||||||
|  |                         <input id="upColor" v-model="badge.upColor" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('downColor') " class="mb-3"> | ||||||
|  |                         <label for="downColor" class="form-label">{{ $t("Badge Down Color") }}</label> | ||||||
|  |                         <input id="downColor" v-model="badge.downColor" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('pendingColor') " class="mb-3"> | ||||||
|  |                         <label for="pendingColor" class="form-label">{{ $t("Badge Pending Color") }}</label> | ||||||
|  |                         <input id="pendingColor" v-model="badge.pendingColor" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('maintenanceColor') " class="mb-3"> | ||||||
|  |                         <label for="maintenanceColor" class="form-label">{{ $t("Badge Maintenance Color") }}</label> | ||||||
|  |                         <input id="maintenanceColor" v-model="badge.maintenanceColor" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('warnColor') " class="mb-3"> | ||||||
|  |                         <label for="warnColor" class="form-label">{{ $t("Badge Warn Color") }}</label> | ||||||
|  |                         <input id="warnColor" v-model="badge.warnColor" type="number" min="0" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('warnDays') " class="mb-3"> | ||||||
|  |                         <label for="warnDays" class="form-label">{{ $t("Badge Warn Days") }}</label> | ||||||
|  |                         <input id="warnDays" v-model="badge.warnDays" type="number" min="0" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if=" (parameters[badge.type || 'null'] || [] ).includes('downDays') " class="mb-3"> | ||||||
|  |                         <label for="downDays" class="form-label">{{ $t("Badge Down Days") }}</label> | ||||||
|  |                         <input id="downDays" v-model="badge.downDays" type="number" min="0" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div class="mb-3"> | ||||||
|  |                         <label for="style" class="form-label">{{ $t("Badge Style") }}</label> | ||||||
|  |                         <select id="style" v-model="badge.style" class="form-select"> | ||||||
|  |                             <option value="plastic">plastic</option> | ||||||
|  |                             <option value="flat">flat</option> | ||||||
|  |                             <option value="flat-square">flat-square</option> | ||||||
|  |                             <option value="for-the-badge">for-the-badge</option> | ||||||
|  |                             <option value="social">social</option> | ||||||
|  |                         </select> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div class="mb-3"> | ||||||
|  |                         <label for="value" class="form-label">{{ $t("Badge value (For Testing only.)") }}</label> | ||||||
|  |                         <input id="value" v-model="badge.value" type="text" class="form-control" required> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div class="my-3"> | ||||||
|  |                         <label for="push-url" class="form-label">{{ $t("Badge URL") }}</label> | ||||||
|  |                         <CopyableInput id="push-url" v-model="badgeURL" type="url" disabled="disabled" /> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |                 <div class="modal-footer"> | ||||||
|  |                     <button type="submit" class="btn btn-danger" data-bs-dismiss="modal"> | ||||||
|  |                         {{ $t("Close") }} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import { Modal } from "bootstrap"; | ||||||
|  | import CopyableInput from "./CopyableInput.vue"; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     components: { | ||||||
|  |         CopyableInput | ||||||
|  |     }, | ||||||
|  |     props: {}, | ||||||
|  |     emits: [], | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             model: null, | ||||||
|  |             processing: false, | ||||||
|  |             monitor: { | ||||||
|  |                 id: null, | ||||||
|  |                 name: null, | ||||||
|  |             }, | ||||||
|  |             badge: { | ||||||
|  |                 type: "status", | ||||||
|  |                 duration: null, | ||||||
|  |                 label: null, | ||||||
|  |                 prefix: null, | ||||||
|  |                 suffix: null, | ||||||
|  |                 labelColor: null, | ||||||
|  |                 color: null, | ||||||
|  |                 labelPrefix: null, | ||||||
|  |                 labelSuffix: null, | ||||||
|  |                 upColor: null, | ||||||
|  |                 downColor: null, | ||||||
|  |                 pendingColor: null, | ||||||
|  |                 maintenanceColor: null, | ||||||
|  |                 warnColor: null, | ||||||
|  |                 warnDays: null, | ||||||
|  |                 downDays: null, | ||||||
|  |                 style: "flat", | ||||||
|  |                 value: null, | ||||||
|  |             }, | ||||||
|  |             parameters: { | ||||||
|  |                 status: [ | ||||||
|  |                     "upLabel", | ||||||
|  |                     "downLabel", | ||||||
|  |                     "pendingLabel", | ||||||
|  |                     "maintenanceLabel", | ||||||
|  |                     "upColor", | ||||||
|  |                     "downColor", | ||||||
|  |                     "pendingColor", | ||||||
|  |                     "maintenanceColor", | ||||||
|  |                 ], | ||||||
|  |                 uptime: [ | ||||||
|  |                     "duration", | ||||||
|  |                     "labelPrefix", | ||||||
|  |                     "labelSuffix", | ||||||
|  |                     "prefix", | ||||||
|  |                     "suffix", | ||||||
|  |                     "color", | ||||||
|  |                     "labelColor", | ||||||
|  |                 ], | ||||||
|  |                 ping: [ | ||||||
|  |                     "duration", | ||||||
|  |                     "labelPrefix", | ||||||
|  |                     "labelSuffix", | ||||||
|  |                     "prefix", | ||||||
|  |                     "suffix", | ||||||
|  |                     "color", | ||||||
|  |                     "labelColor", | ||||||
|  |                 ], | ||||||
|  |                 "avg-response": [ | ||||||
|  |                     "duration", | ||||||
|  |                     "labelPrefix", | ||||||
|  |                     "labelSuffix", | ||||||
|  |                     "prefix", | ||||||
|  |                     "suffix", | ||||||
|  |                     "color", | ||||||
|  |                     "labelColor", | ||||||
|  |                 ], | ||||||
|  |                 "cert-exp": [ | ||||||
|  |                     "labelPrefix", | ||||||
|  |                     "labelSuffix", | ||||||
|  |                     "prefix", | ||||||
|  |                     "suffix", | ||||||
|  |                     "upColor", | ||||||
|  |                     "warnColor", | ||||||
|  |                     "downColor", | ||||||
|  |                     "warnDays", | ||||||
|  |                     "downDays", | ||||||
|  |                     "labelColor", | ||||||
|  |                 ], | ||||||
|  |                 response: [ | ||||||
|  |                     "labelPrefix", | ||||||
|  |                     "labelSuffix", | ||||||
|  |                     "prefix", | ||||||
|  |                     "suffix", | ||||||
|  |                     "color", | ||||||
|  |                     "labelColor", | ||||||
|  |                 ], | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     computed: { | ||||||
|  |         badgeURL() { | ||||||
|  |             if (!this.monitor.id || !this.badge.type) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             let badgeURL = this.$root.baseURL + "/api/badge/" + this.monitor.id + "/" + this.badge.type; | ||||||
|  | 
 | ||||||
|  |             let parameterList = {}; | ||||||
|  | 
 | ||||||
|  |             for (let parameter of this.parameters[this.badge.type] || []) { | ||||||
|  |                 if (parameter === "duration" && this.badge.duration) { | ||||||
|  |                     badgeURL += "/" + this.badge.duration; | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (this.badge[parameter]) { | ||||||
|  |                     parameterList[parameter] = this.badge[parameter]; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for (let parameter of [ "label", "style", "value" ]) { | ||||||
|  |                 if (parameter === "style" && this.badge.style === "flat") { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (this.badge[parameter]) { | ||||||
|  |                     parameterList[parameter] = this.badge[parameter]; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (Object.keys(parameterList).length > 0) { | ||||||
|  |                 return badgeURL + "?" + new URLSearchParams(parameterList); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return badgeURL; | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     mounted() { | ||||||
|  |         this.BadgeGeneratorModal = new Modal(this.$refs.BadgeGeneratorModal); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     methods: { | ||||||
|  |         /** | ||||||
|  |          * Setting monitor | ||||||
|  |          * @param {number} monitorId    ID of monitor | ||||||
|  |          * @param {string} monitorName  Name of monitor | ||||||
|  |          */ | ||||||
|  |         show(monitorId, monitorName) { | ||||||
|  |             this.monitor = { | ||||||
|  |                 id: monitorId, | ||||||
|  |                 name: monitorName, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             this.BadgeGeneratorModal.show(); | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import "../assets/vars.scss"; | ||||||
|  | 
 | ||||||
|  | .dark { | ||||||
|  |     .modal-dialog .form-text, .modal-dialog p { | ||||||
|  |         color: $dark-font-color; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										123
									
								
								src/components/MonitorSettingDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/components/MonitorSettingDialog.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,123 @@ | ||||||
|  | <template> | ||||||
|  |     <div ref="MonitorSettingDialog" class="modal fade" tabindex="-1"> | ||||||
|  |         <div class="modal-dialog"> | ||||||
|  |             <div class="modal-content"> | ||||||
|  |                 <div class="modal-header"> | ||||||
|  |                     <h5 class="modal-title"> | ||||||
|  |                         {{ $t("Monitor Setting", [monitor.name]) }} | ||||||
|  |                     </h5> | ||||||
|  |                     <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="modal-body"> | ||||||
|  |                     <div class="my-3 form-check"> | ||||||
|  |                         <input id="show-clickable-link" v-model="monitor.isClickAble" class="form-check-input" type="checkbox" @click="toggleLink(monitor.group_index, monitor.monitor_index)" /> | ||||||
|  |                         <label class="form-check-label" for="show-clickable-link"> | ||||||
|  |                             {{ $t("Show Clickable Link") }} | ||||||
|  |                         </label> | ||||||
|  |                         <div class="form-text"> | ||||||
|  |                             {{ $t("Show Clickable Link Description") }} | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <button | ||||||
|  |                         class="btn btn-primary btn-add-group me-2" | ||||||
|  |                         @click="$refs.badgeGeneratorDialog.show(monitor.id, monitor.name)" | ||||||
|  |                     > | ||||||
|  |                         <font-awesome-icon icon="certificate" /> | ||||||
|  |                         {{ $t("Open Badge Generator") }} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |                 <div class="modal-footer"> | ||||||
|  |                     <button type="submit" class="btn btn-danger" data-bs-dismiss="modal"> | ||||||
|  |                         {{ $t("Close") }} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     <BadgeGeneratorDialog ref="badgeGeneratorDialog" /> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import { Modal } from "bootstrap"; | ||||||
|  | import BadgeGeneratorDialog from "./BadgeGeneratorDialog.vue"; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     components: { | ||||||
|  |         BadgeGeneratorDialog | ||||||
|  |     }, | ||||||
|  |     props: {}, | ||||||
|  |     emits: [], | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             monitor: { | ||||||
|  |                 id: null, | ||||||
|  |                 name: null, | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     computed: {}, | ||||||
|  | 
 | ||||||
|  |     mounted() { | ||||||
|  |         this.MonitorSettingDialog = new Modal(this.$refs.MonitorSettingDialog); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     methods: { | ||||||
|  |         /** | ||||||
|  |          * Setting monitor | ||||||
|  |          * @param {Object} group Data of monitor | ||||||
|  |          * @param {Object} monitor Data of monitor | ||||||
|  |          */ | ||||||
|  |         show(group, monitor) { | ||||||
|  |             this.monitor = { | ||||||
|  |                 id: monitor.element.id, | ||||||
|  |                 name: monitor.element.name, | ||||||
|  |                 monitor_index: monitor.index, | ||||||
|  |                 group_index: group.index, | ||||||
|  |                 isClickAble: this.showLink(monitor), | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             this.MonitorSettingDialog.show(); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Toggle the value of sendUrl | ||||||
|  |          * @param {number} groupIndex Index of group monitor is member of | ||||||
|  |          * @param {number} index Index of monitor within group | ||||||
|  |          */ | ||||||
|  |         toggleLink(groupIndex, index) { | ||||||
|  |             this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl = !this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl; | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Should a link to the monitor be shown? | ||||||
|  |          * Attempts to guess if a link should be shown based upon if | ||||||
|  |          * sendUrl is set and if the URL is default or not. | ||||||
|  |          * @param {Object} monitor Monitor to check | ||||||
|  |          * @param {boolean} [ignoreSendUrl=false] Should the presence of the sendUrl | ||||||
|  |          * property be ignored. This will only work in edit mode. | ||||||
|  |          * @returns {boolean} | ||||||
|  |          */ | ||||||
|  |         showLink(monitor, ignoreSendUrl = false) { | ||||||
|  |             // We must check if there are any elements in monitorList to | ||||||
|  |             // prevent undefined errors if it hasn't been loaded yet | ||||||
|  |             if (this.$parent.editMode && ignoreSendUrl && Object.keys(this.$root.monitorList).length) { | ||||||
|  |                 return this.$root.monitorList[monitor.element.id].type === "http" || this.$root.monitorList[monitor.element.id].type === "keyword"; | ||||||
|  |             } | ||||||
|  |             return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode; | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import "../assets/vars.scss"; | ||||||
|  | 
 | ||||||
|  | .dark { | ||||||
|  |     .modal-dialog .form-text, .modal-dialog p { | ||||||
|  |         color: $dark-font-color; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -49,16 +49,15 @@ | ||||||
|                                                 {{ monitor.element.name }} |                                                 {{ monitor.element.name }} | ||||||
|                                             </a> |                                             </a> | ||||||
|                                             <p v-else class="item-name"> {{ monitor.element.name }} </p> |                                             <p v-else class="item-name"> {{ monitor.element.name }} </p> | ||||||
|  | 
 | ||||||
|                                             <span |                                             <span | ||||||
|                                                 v-if="showLink(monitor, true)" |                                                 title="Setting" | ||||||
|                                                 title="Toggle Clickable Link" |  | ||||||
|                                             > |                                             > | ||||||
|                                                 <font-awesome-icon |                                                 <font-awesome-icon | ||||||
|                                                     v-if="editMode" |                                                     v-if="editMode" | ||||||
|                                                     :class="{'link-active': monitor.element.sendUrl, 'btn-link': true}" |                                                     :class="{'link-active': true, 'btn-link': true}" | ||||||
|                                                     icon="link" class="action me-3" |                                                     icon="cog" class="action me-3" | ||||||
| 
 |                                                     @click="$refs.monitorSettingDialog.show(group, monitor)" | ||||||
|                                                     @click="toggleLink(group.index, monitor.index)" |  | ||||||
|                                                 /> |                                                 /> | ||||||
|                                             </span> |                                             </span> | ||||||
|                                         </div> |                                         </div> | ||||||
|  | @ -77,9 +76,11 @@ | ||||||
|             </div> |             </div> | ||||||
|         </template> |         </template> | ||||||
|     </Draggable> |     </Draggable> | ||||||
|  |     <MonitorSettingDialog ref="monitorSettingDialog" /> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|  | import MonitorSettingDialog from "./MonitorSettingDialog.vue"; | ||||||
| import Draggable from "vuedraggable"; | import Draggable from "vuedraggable"; | ||||||
| import HeartbeatBar from "./HeartbeatBar.vue"; | import HeartbeatBar from "./HeartbeatBar.vue"; | ||||||
| import Uptime from "./Uptime.vue"; | import Uptime from "./Uptime.vue"; | ||||||
|  | @ -87,6 +88,7 @@ import Tag from "./Tag.vue"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|     components: { |     components: { | ||||||
|  |         MonitorSettingDialog, | ||||||
|         Draggable, |         Draggable, | ||||||
|         HeartbeatBar, |         HeartbeatBar, | ||||||
|         Uptime, |         Uptime, | ||||||
|  | @ -135,15 +137,6 @@ export default { | ||||||
|             this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1); |             this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1); | ||||||
|         }, |         }, | ||||||
| 
 | 
 | ||||||
|         /** |  | ||||||
|          * Toggle the value of sendUrl |  | ||||||
|          * @param {number} groupIndex Index of group monitor is member of |  | ||||||
|          * @param {number} index Index of monitor within group |  | ||||||
|          */ |  | ||||||
|         toggleLink(groupIndex, index) { |  | ||||||
|             this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl = !this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl; |  | ||||||
|         }, |  | ||||||
| 
 |  | ||||||
|         /** |         /** | ||||||
|          * Should a link to the monitor be shown? |          * Should a link to the monitor be shown? | ||||||
|          * Attempts to guess if a link should be shown based upon if |          * Attempts to guess if a link should be shown based upon if | ||||||
|  |  | ||||||
|  | @ -42,6 +42,8 @@ | ||||||
|             <option value="vibrate">{{ $t("pushoversounds vibrate") }}</option> |             <option value="vibrate">{{ $t("pushoversounds vibrate") }}</option> | ||||||
|             <option value="none">{{ $t("pushoversounds none") }}</option> |             <option value="none">{{ $t("pushoversounds none") }}</option> | ||||||
|         </select> |         </select> | ||||||
|  |         <label for="pushover-ttl" class="form-label">{{ $t("pushoverMessageTtl") }}</label> | ||||||
|  |         <input id="pushover-ttl" v-model="$parent.notification.pushoverttl" type="number" min="0" step="1" class="form-control"> | ||||||
|         <div class="form-text"> |         <div class="form-text"> | ||||||
|             <span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} |             <span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} | ||||||
|             <i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> |             <i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> | ||||||
|  |  | ||||||
|  | @ -49,6 +49,7 @@ import { | ||||||
|     faFilter, |     faFilter, | ||||||
|     faInfoCircle, |     faInfoCircle, | ||||||
|     faClone, |     faClone, | ||||||
|  |     faCertificate, | ||||||
| } from "@fortawesome/free-solid-svg-icons"; | } from "@fortawesome/free-solid-svg-icons"; | ||||||
| 
 | 
 | ||||||
| library.add( | library.add( | ||||||
|  | @ -95,6 +96,7 @@ library.add( | ||||||
|     faFilter, |     faFilter, | ||||||
|     faInfoCircle, |     faInfoCircle, | ||||||
|     faClone, |     faClone, | ||||||
|  |     faCertificate, | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| export { FontAwesomeIcon }; | export { FontAwesomeIcon }; | ||||||
|  |  | ||||||
|  | @ -259,6 +259,7 @@ | ||||||
|     "More info on:": "Mehr Infos auf: {0}", |     "More info on:": "Mehr Infos auf: {0}", | ||||||
|     "pushoverDesc1": "Notfallpriorität (2) hat standardmässig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.", |     "pushoverDesc1": "Notfallpriorität (2) hat standardmässig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.", | ||||||
|     "pushoverDesc2": "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.", |     "pushoverDesc2": "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.", | ||||||
|  |     "pushoverMessageTtl": "Message TTL (Sekunden)", | ||||||
|     "SMS Type": "SMS Typ", |     "SMS Type": "SMS Typ", | ||||||
|     "octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)", |     "octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)", | ||||||
|     "octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)", |     "octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)", | ||||||
|  |  | ||||||
|  | @ -259,6 +259,7 @@ | ||||||
|     "More info on:": "Mehr Infos auf: {0}", |     "More info on:": "Mehr Infos auf: {0}", | ||||||
|     "pushoverDesc1": "Notfallpriorität (2) hat standardmäßig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.", |     "pushoverDesc1": "Notfallpriorität (2) hat standardmäßig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.", | ||||||
|     "pushoverDesc2": "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.", |     "pushoverDesc2": "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.", | ||||||
|  |     "pushoverMessageTtl": "Message TTL (Sekunden)", | ||||||
|     "SMS Type": "SMS Typ", |     "SMS Type": "SMS Typ", | ||||||
|     "octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)", |     "octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)", | ||||||
|     "octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)", |     "octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)", | ||||||
|  |  | ||||||
|  | @ -556,6 +556,7 @@ | ||||||
|     "More info on:": "More info on: {0}", |     "More info on:": "More info on: {0}", | ||||||
|     "pushoverDesc1": "Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.", |     "pushoverDesc1": "Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.", | ||||||
|     "pushoverDesc2": "If you want to send notifications to different devices, fill out Device field.", |     "pushoverDesc2": "If you want to send notifications to different devices, fill out Device field.", | ||||||
|  |     "pushoverMessageTtl": "Message TTL (Seconds)", | ||||||
|     "SMS Type": "SMS Type", |     "SMS Type": "SMS Type", | ||||||
|     "octopushTypePremium": "Premium (Fast - recommended for alerting)", |     "octopushTypePremium": "Premium (Fast - recommended for alerting)", | ||||||
|     "octopushTypeLowCost": "Low Cost (Slow - sometimes blocked by operator)", |     "octopushTypeLowCost": "Low Cost (Slow - sometimes blocked by operator)", | ||||||
|  | @ -720,5 +721,29 @@ | ||||||
|     "twilioAccountSID": "Account SID", |     "twilioAccountSID": "Account SID", | ||||||
|     "twilioAuthToken": "Auth Token", |     "twilioAuthToken": "Auth Token", | ||||||
|     "twilioFromNumber": "From Number", |     "twilioFromNumber": "From Number", | ||||||
|     "twilioToNumber": "To Number" |     "twilioToNumber": "To Number", | ||||||
|  |     "Monitor Setting": "{0}'s Monitor Setting", | ||||||
|  |     "Show Clickable Link": "Show Clickable Link", | ||||||
|  |     "Show Clickable Link Description": "If checked everyone who have access to this status page can have access to monitor URL.", | ||||||
|  |     "Open Badge Generator": "Open Badge Generator", | ||||||
|  |     "Badge Generator": "{0}'s Badge Generator", | ||||||
|  |     "Badge Type": "Badge Type", | ||||||
|  |     "Badge Duration": "Badge Duration", | ||||||
|  |     "Badge Label": "Badge Label", | ||||||
|  |     "Badge Prefix": "Badge Prefix", | ||||||
|  |     "Badge Suffix": "Badge Suffix", | ||||||
|  |     "Badge Label Color": "Badge Label Color", | ||||||
|  |     "Badge Color": "Badge Color", | ||||||
|  |     "Badge Label Prefix": "Badge Label Prefix", | ||||||
|  |     "Badge Label Suffix": "Badge Label Suffix", | ||||||
|  |     "Badge Up Color": "Badge Up Color", | ||||||
|  |     "Badge Down Color": "Badge Down Color", | ||||||
|  |     "Badge Pending Color": "Badge Pending Color", | ||||||
|  |     "Badge Maintenance Color": "Badge Maintenance Color", | ||||||
|  |     "Badge Warn Color": "Badge Warn Color", | ||||||
|  |     "Badge Warn Days": "Badge Warn Days", | ||||||
|  |     "Badge Down Days": "Badge Down Days", | ||||||
|  |     "Badge Style": "Badge Style", | ||||||
|  |     "Badge value (For Testing only.)": "Badge value (For Testing only.)", | ||||||
|  |     "Badge URL": "Badge URL" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -95,7 +95,7 @@ | ||||||
|         </main> |         </main> | ||||||
| 
 | 
 | ||||||
|         <!-- Mobile Only --> |         <!-- Mobile Only --> | ||||||
|         <div v-if="$root.isMobile" style="width: 100%; height: 60px;" /> |         <div v-if="$root.isMobile" style="width: 100%; height: calc(60px + env(safe-area-inset-bottom));" /> | ||||||
|         <nav v-if="$root.isMobile && $root.loggedIn" class="bottom-nav"> |         <nav v-if="$root.isMobile && $root.loggedIn" class="bottom-nav"> | ||||||
|             <router-link to="/dashboard" class="nav-link"> |             <router-link to="/dashboard" class="nav-link"> | ||||||
|                 <div><font-awesome-icon icon="tachometer-alt" /></div> |                 <div><font-awesome-icon icon="tachometer-alt" /></div> | ||||||
|  | @ -182,14 +182,14 @@ export default { | ||||||
|     z-index: 1000; |     z-index: 1000; | ||||||
|     position: fixed; |     position: fixed; | ||||||
|     bottom: 0; |     bottom: 0; | ||||||
|     height: 60px; |     height: calc(60px + env(safe-area-inset-bottom)); | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     left: 0; |     left: 0; | ||||||
|     background-color: #fff; |     background-color: #fff; | ||||||
|     box-shadow: 0 15px 47px 0 rgba(0, 0, 0, 0.05), 0 5px 14px 0 rgba(0, 0, 0, 0.05); |     box-shadow: 0 15px 47px 0 rgba(0, 0, 0, 0.05), 0 5px 14px 0 rgba(0, 0, 0, 0.05); | ||||||
|     text-align: center; |     text-align: center; | ||||||
|     white-space: nowrap; |     white-space: nowrap; | ||||||
|     padding: 0 10px; |     padding: 0 10px env(safe-area-inset-bottom); | ||||||
| 
 | 
 | ||||||
|     a { |     a { | ||||||
|         text-align: center; |         text-align: center; | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
|                 <Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" /> |                 <Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" /> | ||||||
|             </div> |             </div> | ||||||
|             <p class="url"> |             <p class="url"> | ||||||
|                 <a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank" rel="noopener noreferrer">{{ monitor.url }}</a> |                 <a v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'mp-health' " :href="monitor.url" target="_blank" rel="noopener noreferrer">{{ filterPassword(monitor.url) }}</a> | ||||||
|                 <span v-if="monitor.type === 'port'">TCP Port {{ monitor.hostname }}:{{ monitor.port }}</span> |                 <span v-if="monitor.type === 'port'">TCP Port {{ monitor.hostname }}:{{ monitor.port }}</span> | ||||||
|                 <span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span> |                 <span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span> | ||||||
|                 <span v-if="monitor.type === 'keyword'"> |                 <span v-if="monitor.type === 'keyword'"> | ||||||
|  | @ -18,6 +18,21 @@ | ||||||
|                     <br> |                     <br> | ||||||
|                     <span>{{ $t("Last Result") }}:</span> <span class="keyword">{{ monitor.dns_last_result }}</span> |                     <span>{{ $t("Last Result") }}:</span> <span class="keyword">{{ monitor.dns_last_result }}</span> | ||||||
|                 </span> |                 </span> | ||||||
|  |                 <span v-if="monitor.type === 'docker'">Docker container: {{ monitor.docker_container }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'gamedig'">Gamedig - {{ monitor.game }}: {{ monitor.hostname }}:{{ monitor.port }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'grpc-keyword'">gRPC - {{ filterPassword(monitor.grpcUrl) }} | ||||||
|  |                     <br> | ||||||
|  |                     <span>{{ $t("Keyword") }}:</span> <span class="keyword">{{ monitor.keyword }}</span> | ||||||
|  |                 </span> | ||||||
|  |                 <span v-if="monitor.type === 'mongodb'">{{ filterPassword(monitor.databaseConnectionString) }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'mqtt'">MQTT: {{ monitor.hostname }}:{{ monitor.port }}/{{ monitor.mqttTopic }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'mysql'">{{ filterPassword(monitor.databaseConnectionString) }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'postgres'">{{ filterPassword(monitor.databaseConnectionString) }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'push'">Push: <a :href="pushURL" target="_blank" rel="noopener noreferrer">{{ pushURL }}</a></span> | ||||||
|  |                 <span v-if="monitor.type === 'radius'">Radius: {{ filterPassword(monitor.hostname) }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'redis'">{{ filterPassword(monitor.databaseConnectionString) }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'sqlserver'">SQL Server: {{ filterPassword(monitor.databaseConnectionString) }}</span> | ||||||
|  |                 <span v-if="monitor.type === 'steam'">Steam Game Server: {{ monitor.hostname }}:{{ monitor.port }}</span> | ||||||
|             </p> |             </p> | ||||||
| 
 | 
 | ||||||
|             <div class="functions"> |             <div class="functions"> | ||||||
|  | @ -54,35 +69,41 @@ | ||||||
| 
 | 
 | ||||||
|             <div class="shadow-box big-padding text-center stats"> |             <div class="shadow-box big-padding text-center stats"> | ||||||
|                 <div class="row"> |                 <div class="row"> | ||||||
|                     <div class="col"> |                     <div class="col-12 col-sm col row d-flex align-items-center d-sm-block"> | ||||||
|                         <h4>{{ pingTitle() }}</h4> |                         <h4 class="col-4 col-sm-12">{{ pingTitle() }}</h4> | ||||||
|                         <p>({{ $t("Current") }})</p> |                         <p class="col-4 col-sm-12 mb-0 mb-sm-2">({{ $t("Current") }})</p> | ||||||
|                         <span class="num"> |                         <span class="col-4 col-sm-12 num"> | ||||||
|                             <a href="#" @click.prevent="showPingChartBox = !showPingChartBox"> |                             <a href="#" @click.prevent="showPingChartBox = !showPingChartBox"> | ||||||
|                                 <CountUp :value="ping" /> |                                 <CountUp :value="ping" /> | ||||||
|                             </a> |                             </a> | ||||||
|                         </span> |                         </span> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="col"> |                     <div class="col-12 col-sm col row d-flex align-items-center d-sm-block"> | ||||||
|                         <h4>{{ pingTitle(true) }}</h4> |                         <h4 class="col-4 col-sm-12">{{ pingTitle(true) }}</h4> | ||||||
|                         <p>(24{{ $t("-hour") }})</p> |                         <p class="col-4 col-sm-12 mb-0 mb-sm-2">(24{{ $t("-hour") }})</p> | ||||||
|                         <span class="num"><CountUp :value="avgPing" /></span> |                         <span class="col-4 col-sm-12 num"> | ||||||
|  |                             <CountUp :value="avgPing" /> | ||||||
|  |                         </span> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="col"> |                     <div class="col-12 col-sm col row d-flex align-items-center d-sm-block"> | ||||||
|                         <h4>{{ $t("Uptime") }}</h4> |                         <h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4> | ||||||
|                         <p>(24{{ $t("-hour") }})</p> |                         <p class="col-4 col-sm-12 mb-0 mb-sm-2">(24{{ $t("-hour") }})</p> | ||||||
|                         <span class="num"><Uptime :monitor="monitor" type="24" /></span> |                         <span class="col-4 col-sm-12 num"> | ||||||
|  |                             <Uptime :monitor="monitor" type="24" /> | ||||||
|  |                         </span> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="col"> |                     <div class="col-12 col-sm col row d-flex align-items-center d-sm-block"> | ||||||
|                         <h4>{{ $t("Uptime") }}</h4> |                         <h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4> | ||||||
|                         <p>(30{{ $t("-day") }})</p> |                         <p class="col-4 col-sm-12 mb-0 mb-sm-2">(30{{ $t("-day") }})</p> | ||||||
|                         <span class="num"><Uptime :monitor="monitor" type="720" /></span> |                         <span class="col-4 col-sm-12 num"> | ||||||
|  |                             <Uptime :monitor="monitor" type="720" /> | ||||||
|  |                         </span> | ||||||
|                     </div> |                     </div> | ||||||
| 
 | 
 | ||||||
|                     <div v-if="tlsInfo" class="col"> |                     <div v-if="tlsInfo" class="col-12 col-sm col row d-flex align-items-center d-sm-block"> | ||||||
|                         <h4>{{ $t("Cert Exp.") }}</h4> |                         <h4 class="col-4 col-sm-12">{{ $t("Cert Exp.") }}</h4> | ||||||
|                         <p>(<Datetime :value="tlsInfo.certInfo.validTo" date-only />)</p> |                         <p class="col-4 col-sm-12 mb-0 mb-sm-2">(<Datetime :value="tlsInfo.certInfo.validTo" date-only />)</p> | ||||||
|                         <span class="num"> |                         <span class="col-4 col-sm-12 num"> | ||||||
|                             <a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ tlsInfo.certInfo.daysRemaining }} {{ $tc("day", tlsInfo.certInfo.daysRemaining) }}</a> |                             <a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ tlsInfo.certInfo.daysRemaining }} {{ $tc("day", tlsInfo.certInfo.daysRemaining) }}</a> | ||||||
|                         </span> |                         </span> | ||||||
|                     </div> |                     </div> | ||||||
|  | @ -136,7 +157,7 @@ | ||||||
|                         </tr> |                         </tr> | ||||||
|                     </thead> |                     </thead> | ||||||
|                     <tbody> |                     <tbody> | ||||||
|                         <tr v-for="(beat, index) in displayedRecords" :key="index" :class="{ 'shadow-box': $root.windowWidth <= 550}" style="padding: 10px;"> |                         <tr v-for="(beat, index) in displayedRecords" :key="index" style="padding: 10px;"> | ||||||
|                             <td><Status :status="beat.status" /></td> |                             <td><Status :status="beat.status" /></td> | ||||||
|                             <td :class="{ 'border-0':! beat.msg}"><Datetime :value="beat.time" /></td> |                             <td :class="{ 'border-0':! beat.msg}"><Datetime :value="beat.time" /></td> | ||||||
|                             <td class="border-0">{{ beat.msg }}</td> |                             <td class="border-0">{{ beat.msg }}</td> | ||||||
|  | @ -193,6 +214,7 @@ import Pagination from "v-pagination-3"; | ||||||
| const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue")); | const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue")); | ||||||
| import Tag from "../components/Tag.vue"; | import Tag from "../components/Tag.vue"; | ||||||
| import CertificateInfo from "../components/CertificateInfo.vue"; | import CertificateInfo from "../components/CertificateInfo.vue"; | ||||||
|  | import { URL } from "whatwg-url"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|     components: { |     components: { | ||||||
|  | @ -290,6 +312,10 @@ export default { | ||||||
|             const endIndex = startIndex + this.perPage; |             const endIndex = startIndex + this.perPage; | ||||||
|             return this.heartBeatList.slice(startIndex, endIndex); |             return this.heartBeatList.slice(startIndex, endIndex); | ||||||
|         }, |         }, | ||||||
|  | 
 | ||||||
|  |         pushURL() { | ||||||
|  |             return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?status=up&msg=OK&ping="; | ||||||
|  |         }, | ||||||
|     }, |     }, | ||||||
|     mounted() { |     mounted() { | ||||||
| 
 | 
 | ||||||
|  | @ -376,12 +402,26 @@ export default { | ||||||
|                 translationPrefix = "Avg. "; |                 translationPrefix = "Avg. "; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (this.monitor.type === "http") { |             if (this.monitor.type === "http" || this.monitor.type === "keyword") { | ||||||
|                 return this.$t(translationPrefix + "Response"); |                 return this.$t(translationPrefix + "Response"); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return this.$t(translationPrefix + "Ping"); |             return this.$t(translationPrefix + "Ping"); | ||||||
|         }, |         }, | ||||||
|  | 
 | ||||||
|  |         /** Filter and hide password in URL for display */ | ||||||
|  |         filterPassword(urlString) { | ||||||
|  |             try { | ||||||
|  |                 let parsedUrl = new URL(urlString); | ||||||
|  |                 if (parsedUrl.password !== "") { | ||||||
|  |                     parsedUrl.password = "******"; | ||||||
|  |                 } | ||||||
|  |                 return parsedUrl.toString(); | ||||||
|  |             } catch (e) { | ||||||
|  |                 // Handle SQL Server | ||||||
|  |                 return urlString.replaceAll(/Password=(.+);/ig, "Password=******;"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  | @ -415,6 +455,7 @@ export default { | ||||||
|         flex-direction: column; |         flex-direction: column; | ||||||
|         align-items: center; |         align-items: center; | ||||||
|         padding-top: 10px; |         padding-top: 10px; | ||||||
|  |         font-size: 0.9em; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     a.btn { |     a.btn { | ||||||
|  | @ -471,6 +512,18 @@ table { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @media (max-width: 550px) { | ||||||
|  |     .stats { | ||||||
|  |         .col { | ||||||
|  |             margin: 10px 0 !important; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         h4 { | ||||||
|  |             font-size: 1.1rem; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .keyword { | .keyword { | ||||||
|     color: black; |     color: black; | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue