Merge branch 'eslint_stylelint'
# Conflicts: # server/server.js
This commit is contained in:
		
						commit
						4a9690437f
					
				
					 33 changed files with 1298 additions and 1141 deletions
				
			
		
							
								
								
									
										73
									
								
								.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| module.exports = { | ||||
|     env: { | ||||
|         browser: true, | ||||
|         commonjs: true, | ||||
|         es2017: true, | ||||
|         node: true, | ||||
|     }, | ||||
|     extends: [ | ||||
|         "eslint:recommended", | ||||
|         "plugin:vue/vue3-recommended", | ||||
|     ], | ||||
|     parserOptions: { | ||||
|         ecmaVersion: 2018, | ||||
|         sourceType: "module", | ||||
|     }, | ||||
|     rules: { | ||||
|         // override/add rules settings here, such as:
 | ||||
|         // 'vue/no-unused-vars': 'error'
 | ||||
|         indent: ["error", 4], | ||||
|         quotes: ["warn", "double"], | ||||
|         //semi: ['off', 'never'],
 | ||||
|         "vue/html-indent": ["warn", 4], // default: 2
 | ||||
|         "vue/max-attributes-per-line": "off", | ||||
|         "no-multi-spaces": ["error", { | ||||
|             ignoreEOLComments: true, | ||||
|         }], | ||||
|         "curly": "error", | ||||
|         "object-curly-spacing": ["error", "always"], | ||||
|         "object-curly-newline": ["error", { | ||||
|             "ObjectExpression": { | ||||
|                 "minProperties": 1, | ||||
|             }, | ||||
|             "ObjectPattern": { | ||||
|                 "multiline": true, | ||||
|                 "minProperties": 2, | ||||
|             }, | ||||
|             "ImportDeclaration": { | ||||
|                 "multiline": true, | ||||
|             }, | ||||
|             "ExportDeclaration": { | ||||
|                 "multiline": true, | ||||
|                 //'minProperties': 2,
 | ||||
|             }, | ||||
|         }], | ||||
|         "object-property-newline": "error", | ||||
|         "comma-spacing": "error", | ||||
|         "brace-style": "error", | ||||
|         "no-var": "error", | ||||
|         "key-spacing": "warn", | ||||
|         "keyword-spacing": "warn", | ||||
|         "space-infix-ops": "warn", | ||||
|         "arrow-spacing": "warn", | ||||
|         "no-trailing-spaces": "warn", | ||||
|         "space-before-blocks": "warn", | ||||
|         //'no-console': 'warn',
 | ||||
|         "no-extra-boolean-cast": "off", | ||||
|         "no-multiple-empty-lines": ["warn", { | ||||
|             "max": 1, | ||||
|             "maxBOF": 0, | ||||
|         }], | ||||
|         "lines-between-class-members": ["warn", "always", { | ||||
|             exceptAfterSingleLine: true, | ||||
|         }], | ||||
|         "no-unneeded-ternary": "error", | ||||
|         "no-else-return": ["error", { | ||||
|             "allowElseIf": false, | ||||
|         }], | ||||
|         "array-bracket-newline": ["error", "consistent"], | ||||
|         "eol-last": ["error", "always"], | ||||
|         //'prefer-template': 'error',
 | ||||
|         "comma-dangle": ["warn", "always-multiline"], | ||||
|     }, | ||||
| } | ||||
							
								
								
									
										3
									
								
								.stylelintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.stylelintrc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| { | ||||
|   "extends": "stylelint-config-recommended", | ||||
| } | ||||
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								package.json
									
									
									
									
									
								
							|  | @ -52,11 +52,16 @@ | |||
|         "vue-toastification": "^2.0.0-rc.1" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@vitejs/plugin-legacy": "^1.4.4", | ||||
|         "@vitejs/plugin-vue": "^1.2.5", | ||||
|         "@vitejs/plugin-legacy": "^1.5.0", | ||||
|         "@vitejs/plugin-vue": "^1.3.0", | ||||
|         "@vue/compiler-sfc": "^3.1.5", | ||||
|         "core-js": "^3.15.2", | ||||
|         "sass": "^1.35.2", | ||||
|         "vite": "^2.4.2" | ||||
|         "eslint": "^7.31.0", | ||||
|         "eslint-plugin-vue": "^7.14.0", | ||||
|         "sass": "^1.36.0", | ||||
|         "stylelint": "^13.13.1", | ||||
|         "stylelint-config-recommended": "^5.0.0", | ||||
|         "stylelint-config-standard": "^22.0.0", | ||||
|         "vite": "^2.4.4" | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| const basicAuth = require('express-basic-auth') | ||||
| const passwordHash = require('./password-hash'); | ||||
| const {R} = require("redbean-node"); | ||||
| const basicAuth = require("express-basic-auth") | ||||
| const passwordHash = require("./password-hash"); | ||||
| const { R } = require("redbean-node"); | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  | @ -10,7 +10,7 @@ const {R} = require("redbean-node"); | |||
|  */ | ||||
| exports.login = async function (username, password) { | ||||
|     let user = await R.findOne("user", " username = ? AND active = 1 ", [ | ||||
|         username | ||||
|         username, | ||||
|     ]) | ||||
| 
 | ||||
|     if (user && passwordHash.verify(password, user.password)) { | ||||
|  | @ -18,13 +18,13 @@ exports.login = async function (username, password) { | |||
|         if (passwordHash.needRehash(user.password)) { | ||||
|             await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ | ||||
|                 passwordHash.generate(password), | ||||
|                 user.id | ||||
|                 user.id, | ||||
|             ]); | ||||
|         } | ||||
|         return user; | ||||
|     } else { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
| } | ||||
| 
 | ||||
| function myAuthorizer(username, password, callback) { | ||||
|  | @ -36,5 +36,5 @@ function myAuthorizer(username, password, callback) { | |||
| exports.basicAuth = basicAuth({ | ||||
|     authorizer: myAuthorizer, | ||||
|     authorizeAsync: true, | ||||
|     challenge: true | ||||
|     challenge: true, | ||||
| }); | ||||
|  |  | |||
|  | @ -1,15 +1,15 @@ | |||
| const dayjs = require("dayjs"); | ||||
| const utc = require('dayjs/plugin/utc') | ||||
| var timezone = require('dayjs/plugin/timezone') | ||||
| const utc = require("dayjs/plugin/utc") | ||||
| let timezone = require("dayjs/plugin/timezone") | ||||
| dayjs.extend(utc) | ||||
| dayjs.extend(timezone) | ||||
| const {BeanModel} = require("redbean-node/dist/bean-model"); | ||||
| 
 | ||||
| const { BeanModel } = require("redbean-node/dist/bean-model"); | ||||
| 
 | ||||
| /** | ||||
|  * status: | ||||
|  *      0 = DOWN | ||||
|  *      1 = UP | ||||
|  *      2 = PENDING | ||||
|  */ | ||||
| class Heartbeat extends BeanModel { | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ const customAgent = new https.Agent({ | |||
|  * status: | ||||
|  *      0 = DOWN | ||||
|  *      1 = UP | ||||
|  *      2 = PENDING | ||||
|  */ | ||||
| class Monitor extends BeanModel { | ||||
|     async toJSON() { | ||||
|  | @ -197,7 +198,7 @@ class Monitor extends BeanModel { | |||
|             if (bean.status === UP) { | ||||
|                 console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`) | ||||
|             } else if (bean.status === PENDING) { | ||||
|                 console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Type: ${this.type}`) | ||||
|                 console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Type: ${this.type}`) | ||||
|             } else { | ||||
|                 console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`) | ||||
|             } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| const axios = require("axios"); | ||||
| const {R} = require("redbean-node"); | ||||
| const FormData = require('form-data'); | ||||
| const { R } = require("redbean-node"); | ||||
| const FormData = require("form-data"); | ||||
| const nodemailer = require("nodemailer"); | ||||
| const child_process = require("child_process"); | ||||
| 
 | ||||
|  | @ -24,7 +24,7 @@ class Notification { | |||
|                     params: { | ||||
|                         chat_id: notification.telegramChatID, | ||||
|                         text: msg, | ||||
|                     } | ||||
|                     }, | ||||
|                 }) | ||||
|                 return okMsg; | ||||
| 
 | ||||
|  | @ -41,7 +41,7 @@ class Notification { | |||
|                 await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { | ||||
|                     "message": msg, | ||||
|                     "priority": notification.gotifyPriority || 8, | ||||
|                     "title": "Uptime-Kuma" | ||||
|                     "title": "Uptime-Kuma", | ||||
|                 }) | ||||
| 
 | ||||
|                 return okMsg; | ||||
|  | @ -62,10 +62,10 @@ class Notification { | |||
| 
 | ||||
|                 if (notification.webhookContentType === "form-data") { | ||||
|                     finalData = new FormData(); | ||||
|                     finalData.append('data', JSON.stringify(data)); | ||||
|                     finalData.append("data", JSON.stringify(data)); | ||||
| 
 | ||||
|                     config = { | ||||
|                         headers: finalData.getHeaders() | ||||
|                         headers: finalData.getHeaders(), | ||||
|                     } | ||||
| 
 | ||||
|                 } else { | ||||
|  | @ -84,63 +84,68 @@ class Notification { | |||
| 
 | ||||
|         } else if (notification.type === "discord") { | ||||
|             try { | ||||
|               // If heartbeatJSON is null, assume we're testing.
 | ||||
|               if(heartbeatJSON == null) { | ||||
|                 // If heartbeatJSON is null, assume we're testing.
 | ||||
|                 if (heartbeatJSON == null) { | ||||
|                     let data = { | ||||
|                         username: "Uptime-Kuma", | ||||
|                         content: msg, | ||||
|                     } | ||||
|                     await axios.post(notification.discordWebhookUrl, data) | ||||
|                     return okMsg; | ||||
|                 } | ||||
|                 // If heartbeatJSON is not null, we go into the normal alerting loop.
 | ||||
|                 if (heartbeatJSON["status"] == 0) { | ||||
|                     var alertColor = "16711680"; | ||||
|                 } else if (heartbeatJSON["status"] == 1) { | ||||
|                     var alertColor = "65280"; | ||||
|                 } | ||||
|                 let data = { | ||||
|                   username: 'Uptime-Kuma', | ||||
|                   content: msg | ||||
|                     username: "Uptime-Kuma", | ||||
|                     embeds: [{ | ||||
|                         title: "Uptime-Kuma Alert", | ||||
|                         color: alertColor, | ||||
|                         fields: [ | ||||
|                             { | ||||
|                                 name: "Time (UTC)", | ||||
|                                 value: heartbeatJSON["time"], | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: "Message", | ||||
|                                 value: msg, | ||||
|                             }, | ||||
|                         ], | ||||
|                     }], | ||||
|                 } | ||||
|                 await axios.post(notification.discordWebhookUrl, data) | ||||
|                 return okMsg; | ||||
|               } | ||||
|               // If heartbeatJSON is not null, we go into the normal alerting loop.
 | ||||
|               if(heartbeatJSON['status'] == 0) { | ||||
|                 var alertColor = "16711680"; | ||||
|               } else if(heartbeatJSON['status'] == 1) { | ||||
|                 var alertColor = "65280"; | ||||
|               } | ||||
|               let data = { | ||||
|                 username: 'Uptime-Kuma', | ||||
|                 embeds: [{ | ||||
|                   title: "Uptime-Kuma Alert", | ||||
|                   color: alertColor, | ||||
|                   fields: [ | ||||
|                     { | ||||
|                       name: "Time (UTC)", | ||||
|                       value: heartbeatJSON["time"] | ||||
|                     }, | ||||
|                     { | ||||
|                       name: "Message", | ||||
|                       value: msg | ||||
|                     } | ||||
|                   ] | ||||
|                 }] | ||||
|               } | ||||
|               await axios.post(notification.discordWebhookUrl, data) | ||||
|               return okMsg; | ||||
|             } catch(error) { | ||||
|               throwGeneralAxiosError(error) | ||||
|             } catch (error) { | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
| 
 | ||||
|         } else if (notification.type === "signal") { | ||||
|           try { | ||||
|             let data = { | ||||
|               "message": msg, | ||||
|               "number": notification.signalNumber, | ||||
|               "recipients": notification.signalRecipients.replace(/\s/g, '').split(",") | ||||
|             }; | ||||
|             let config = {}; | ||||
|             try { | ||||
|                 let data = { | ||||
|                     "message": msg, | ||||
|                     "number": notification.signalNumber, | ||||
|                     "recipients": notification.signalRecipients.replace(/\s/g, "").split(","), | ||||
|                 }; | ||||
|                 let config = {}; | ||||
| 
 | ||||
|             await axios.post(notification.signalURL, data, config) | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|               throwGeneralAxiosError(error) | ||||
|         } | ||||
|                 await axios.post(notification.signalURL, data, config) | ||||
|                 return okMsg; | ||||
|             } catch (error) { | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
| 
 | ||||
|         } else if (notification.type === "slack") { | ||||
|             try { | ||||
|                 if (heartbeatJSON == null) { | ||||
|                     let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo} | ||||
|                     let data = { | ||||
|                         "text": "Uptime Kuma Slack testing successful.", | ||||
|                         "channel": notification.slackchannel, | ||||
|                         "username": notification.slackusername, | ||||
|                         "icon_emoji": notification.slackiconemo, | ||||
|                     } | ||||
|                     await axios.post(notification.slackwebhookURL, data) | ||||
|                     return okMsg; | ||||
|                 } | ||||
|  | @ -148,44 +153,42 @@ class Notification { | |||
|                 const time = heartbeatJSON["time"]; | ||||
|                 let data = { | ||||
|                     "text": "Uptime Kuma Alert", | ||||
|                     "channel":notification.slackchannel, | ||||
|                     "channel": notification.slackchannel, | ||||
|                     "username": notification.slackusername, | ||||
|                     "icon_emoji": notification.slackiconemo, | ||||
|                     "blocks": [{ | ||||
|                             "type": "header", | ||||
|                             "text": { | ||||
|                                 "type": "plain_text", | ||||
|                                 "text": "Uptime Kuma Alert" | ||||
|                             } | ||||
|                         "type": "header", | ||||
|                         "text": { | ||||
|                             "type": "plain_text", | ||||
|                             "text": "Uptime Kuma Alert", | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "section", | ||||
|                         "fields": [{ | ||||
|                             "type": "mrkdwn", | ||||
|                             "text": "*Message*\n" + msg, | ||||
|                         }, | ||||
|                         { | ||||
|                             "type": "section", | ||||
|                             "fields": [{ | ||||
|                                     "type": "mrkdwn", | ||||
|                                     "text": '*Message*\n'+msg | ||||
|                             "type": "mrkdwn", | ||||
|                             "text": "*Time (UTC)*\n" + time, | ||||
|                         }], | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "actions", | ||||
|                         "elements": [ | ||||
|                             { | ||||
|                                 "type": "button", | ||||
|                                 "text": { | ||||
|                                     "type": "plain_text", | ||||
|                                     "text": "Visit Uptime Kuma", | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "type": "mrkdwn", | ||||
|                                     "text": "*Time (UTC)*\n"+time | ||||
|                                 } | ||||
|                             ] | ||||
|                         }, | ||||
|                         { | ||||
|                             "type": "actions", | ||||
|                             "elements": [ | ||||
|                                 { | ||||
|                                     "type": "button", | ||||
|                                     "text": { | ||||
|                                             "type": "plain_text", | ||||
|                                             "text": "Visit Uptime Kuma", | ||||
|                                         }, | ||||
|                                     "value": "Uptime-Kuma", | ||||
|                                     "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma" | ||||
|                                 } | ||||
|                                 ] | ||||
|                             } | ||||
|                         ] | ||||
|                     } | ||||
|                                 "value": "Uptime-Kuma", | ||||
|                                 "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma", | ||||
|                             }, | ||||
|                         ], | ||||
|                     }], | ||||
|                 } | ||||
|                 await axios.post(notification.slackwebhookURL, data) | ||||
|                 return okMsg; | ||||
|             } catch (error) { | ||||
|  | @ -193,27 +196,35 @@ class Notification { | |||
|             } | ||||
| 
 | ||||
|         } else if (notification.type === "pushover") { | ||||
|                     var pushoverlink = 'https://api.pushover.net/1/messages.json' | ||||
|             let pushoverlink = "https://api.pushover.net/1/messages.json" | ||||
|             try { | ||||
|                 if (heartbeatJSON == null) { | ||||
|                     let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>", | ||||
|                     'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds, | ||||
|                     'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1} | ||||
|                     let data = { | ||||
|                         "message": "<b>Uptime Kuma Pushover testing successful.</b>", | ||||
|                         "user": notification.pushoveruserkey, | ||||
|                         "token": notification.pushoverapptoken, | ||||
|                         "sound": notification.pushoversounds, | ||||
|                         "priority": notification.pushoverpriority, | ||||
|                         "title": notification.pushovertitle, | ||||
|                         "retry": "30", | ||||
|                         "expire": "3600", | ||||
|                         "html": 1, | ||||
|                     } | ||||
|                     await axios.post(pushoverlink, data) | ||||
|                     return okMsg; | ||||
|                 } | ||||
| 
 | ||||
|                 let data = { | ||||
|                     "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:"+msg+ '\n<b>Time (UTC)</b>:' +heartbeatJSON["time"], | ||||
|                     "user":notification.pushoveruserkey, | ||||
|                     "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"], | ||||
|                     "user": notification.pushoveruserkey, | ||||
|                     "token": notification.pushoverapptoken, | ||||
|                     "sound": notification.pushoversounds, | ||||
|                     "priority": notification.pushoverpriority, | ||||
|                     "title": notification.pushovertitle, | ||||
|                     "retry": "30", | ||||
|                     "expire": "3600", | ||||
|                     "html": 1 | ||||
|                     } | ||||
|                     "html": 1, | ||||
|                 } | ||||
|                 await axios.post(pushoverlink, data) | ||||
|                 return okMsg; | ||||
|             } catch (error) { | ||||
|  | @ -291,24 +302,23 @@ class Notification { | |||
|     static async apprise(notification, msg) { | ||||
|         let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) | ||||
| 
 | ||||
| 
 | ||||
|         let output = (s.stdout) ? s.stdout.toString() : 'ERROR: maybe apprise not found'; | ||||
|         let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; | ||||
| 
 | ||||
|         if (output) { | ||||
| 
 | ||||
|             if (! output.includes("ERROR")) { | ||||
|                 return "Sent Successfully"; | ||||
|             } else { | ||||
|                 throw new Error(output) | ||||
|             } | ||||
| 
 | ||||
|             throw new Error(output) | ||||
|         } else { | ||||
|             return "" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static checkApprise() { | ||||
|         let commandExistsSync = require('command-exists').sync; | ||||
|         let exists = commandExistsSync('apprise'); | ||||
|         let commandExistsSync = require("command-exists").sync; | ||||
|         let exists = commandExistsSync("apprise"); | ||||
|         return exists; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| const passwordHashOld = require('password-hash'); | ||||
| const bcrypt = require('bcrypt'); | ||||
| const passwordHashOld = require("password-hash"); | ||||
| const bcrypt = require("bcrypt"); | ||||
| const saltRounds = 10; | ||||
| 
 | ||||
| exports.generate = function (password) { | ||||
|  | @ -9,9 +9,9 @@ exports.generate = function (password) { | |||
| exports.verify = function (password, hash) { | ||||
|     if (isSHA1(hash)) { | ||||
|         return passwordHashOld.verify(password, hash) | ||||
|     } else { | ||||
|         return bcrypt.compareSync(password, hash); | ||||
|     } | ||||
| 
 | ||||
|     return bcrypt.compareSync(password, hash); | ||||
| } | ||||
| 
 | ||||
| function isSHA1(hash) { | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| // https://github.com/ben-bradley/ping-lite/blob/master/ping-lite.js
 | ||||
| // Fixed on Windows
 | ||||
| 
 | ||||
| var spawn = require('child_process').spawn, | ||||
|     events = require('events'), | ||||
|     fs = require('fs'), | ||||
| let spawn = require("child_process").spawn, | ||||
|     events = require("events"), | ||||
|     fs = require("fs"), | ||||
|     WIN = /^win/.test(process.platform), | ||||
|     LIN = /^linux/.test(process.platform), | ||||
|     MAC = /^darwin/.test(process.platform); | ||||
|  | @ -11,8 +11,9 @@ var spawn = require('child_process').spawn, | |||
| module.exports = Ping; | ||||
| 
 | ||||
| function Ping(host, options) { | ||||
|     if (!host) | ||||
|         throw new Error('You must specify a host to ping!'); | ||||
|     if (!host) { | ||||
|         throw new Error("You must specify a host to ping!"); | ||||
|     } | ||||
| 
 | ||||
|     this._host = host; | ||||
|     this._options = options = (options || {}); | ||||
|  | @ -20,26 +21,24 @@ function Ping(host, options) { | |||
|     events.EventEmitter.call(this); | ||||
| 
 | ||||
|     if (WIN) { | ||||
|         this._bin = 'c:/windows/system32/ping.exe'; | ||||
|         this._args = (options.args) ? options.args : [ '-n', '1', '-w', '5000', host ]; | ||||
|         this._bin = "c:/windows/system32/ping.exe"; | ||||
|         this._args = (options.args) ? options.args : [ "-n", "1", "-w", "5000", host ]; | ||||
|         this._regmatch = /[><=]([0-9.]+?)ms/; | ||||
|     } | ||||
|     else if (LIN) { | ||||
|         this._bin = '/bin/ping'; | ||||
|         this._args = (options.args) ? options.args : [ '-n', '-w', '2', '-c', '1', host ]; | ||||
|     } else if (LIN) { | ||||
|         this._bin = "/bin/ping"; | ||||
|         this._args = (options.args) ? options.args : [ "-n", "-w", "2", "-c", "1", host ]; | ||||
|         this._regmatch = /=([0-9.]+?) ms/; // need to verify this
 | ||||
|     } | ||||
|     else if (MAC) { | ||||
|         this._bin = '/sbin/ping'; | ||||
|         this._args = (options.args) ? options.args : [ '-n', '-t', '2', '-c', '1', host ]; | ||||
|     } else if (MAC) { | ||||
|         this._bin = "/sbin/ping"; | ||||
|         this._args = (options.args) ? options.args : [ "-n", "-t", "2", "-c", "1", host ]; | ||||
|         this._regmatch = /=([0-9.]+?) ms/; | ||||
|     } | ||||
|     else { | ||||
|         throw new Error('Could not detect your ping binary.'); | ||||
|     } else { | ||||
|         throw new Error("Could not detect your ping binary."); | ||||
|     } | ||||
| 
 | ||||
|     if (!fs.existsSync(this._bin)) | ||||
|         throw new Error('Could not detect '+this._bin+' on your system'); | ||||
|     if (!fs.existsSync(this._bin)) { | ||||
|         throw new Error("Could not detect " + this._bin + " on your system"); | ||||
|     } | ||||
| 
 | ||||
|     this._i = 0; | ||||
| 
 | ||||
|  | @ -51,48 +50,57 @@ Ping.prototype.__proto__ = events.EventEmitter.prototype; | |||
| // SEND A PING
 | ||||
| // ===========
 | ||||
| Ping.prototype.send = function(callback) { | ||||
|     var self = this; | ||||
|     let self = this; | ||||
|     callback = callback || function(err, ms) { | ||||
|         if (err) return self.emit('error', err); | ||||
|         else     return self.emit('result', ms); | ||||
|         if (err) { | ||||
|             return self.emit("error", err); | ||||
|         } | ||||
|         return self.emit("result", ms); | ||||
|     }; | ||||
| 
 | ||||
|     var _ended, _exited, _errored; | ||||
|     let _ended, _exited, _errored; | ||||
| 
 | ||||
|     this._ping = spawn(this._bin, this._args); // spawn the binary
 | ||||
| 
 | ||||
|     this._ping.on('error', function(err) { // handle binary errors
 | ||||
|     this._ping.on("error", function(err) { // handle binary errors
 | ||||
|         _errored = true; | ||||
|         callback(err); | ||||
|     }); | ||||
| 
 | ||||
|     this._ping.stdout.on('data', function(data) { // log stdout
 | ||||
|         this._stdout = (this._stdout || '') + data; | ||||
|     this._ping.stdout.on("data", function(data) { // log stdout
 | ||||
|         this._stdout = (this._stdout || "") + data; | ||||
|     }); | ||||
| 
 | ||||
|     this._ping.stdout.on('end', function() { | ||||
|     this._ping.stdout.on("end", function() { | ||||
|         _ended = true; | ||||
|         if (_exited && !_errored) onEnd.call(self._ping); | ||||
|         if (_exited && !_errored) { | ||||
|             onEnd.call(self._ping); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     this._ping.stderr.on('data', function(data) { // log stderr
 | ||||
|         this._stderr = (this._stderr || '') + data; | ||||
|     this._ping.stderr.on("data", function(data) { // log stderr
 | ||||
|         this._stderr = (this._stderr || "") + data; | ||||
|     }); | ||||
| 
 | ||||
|     this._ping.on('exit', function(code) { // handle complete
 | ||||
|     this._ping.on("exit", function(code) { // handle complete
 | ||||
|         _exited = true; | ||||
|         if (_ended && !_errored) onEnd.call(self._ping); | ||||
|         if (_ended && !_errored) { | ||||
|             onEnd.call(self._ping); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     function onEnd() { | ||||
|         var stdout = this.stdout._stdout, | ||||
|         let stdout = this.stdout._stdout, | ||||
|             stderr = this.stderr._stderr, | ||||
|             ms; | ||||
| 
 | ||||
|         if (stderr) | ||||
|         if (stderr) { | ||||
|             return callback(new Error(stderr)); | ||||
|         else if (!stdout) | ||||
|             return callback(new Error('No stdout detected')); | ||||
|         } | ||||
| 
 | ||||
|         if (!stdout) { | ||||
|             return callback(new Error("No stdout detected")); | ||||
|         } | ||||
| 
 | ||||
|         ms = stdout.match(self._regmatch); // parse out the ##ms response
 | ||||
|         ms = (ms && ms[1]) ? Number(ms[1]) : ms; | ||||
|  | @ -104,7 +112,7 @@ Ping.prototype.send = function(callback) { | |||
| // CALL Ping#send(callback) ON A TIMER
 | ||||
| // ===================================
 | ||||
| Ping.prototype.start = function(callback) { | ||||
|     var self = this; | ||||
|     let self = this; | ||||
|     this._i = setInterval(function() { | ||||
|         self.send(callback); | ||||
|     }, (self._options.interval || 5000)); | ||||
|  |  | |||
							
								
								
									
										138
									
								
								server/server.js
									
									
									
									
									
								
							
							
						
						
									
										138
									
								
								server/server.js
									
									
									
									
									
								
							|  | @ -1,24 +1,24 @@ | |||
| console.log("Welcome to Uptime Kuma ") | ||||
| console.log("Importing libraries") | ||||
| const express = require('express'); | ||||
| const http = require('http'); | ||||
| const express = require("express"); | ||||
| const http = require("http"); | ||||
| const { Server } = require("socket.io"); | ||||
| const dayjs = require("dayjs"); | ||||
| const {R} = require("redbean-node"); | ||||
| const jwt = require('jsonwebtoken'); | ||||
| const { R } = require("redbean-node"); | ||||
| const jwt = require("jsonwebtoken"); | ||||
| const Monitor = require("./model/monitor"); | ||||
| const fs = require("fs"); | ||||
| const {getSettings} = require("./util-server"); | ||||
| const {Notification} = require("./notification") | ||||
| const gracefulShutdown = require('http-graceful-shutdown'); | ||||
| const { getSettings } = require("./util-server"); | ||||
| const { Notification } = require("./notification") | ||||
| const gracefulShutdown = require("http-graceful-shutdown"); | ||||
| const Database = require("./database"); | ||||
| const {sleep} = require("./util"); | ||||
| const args = require('args-parser')(process.argv); | ||||
| const prometheusAPIMetrics = require('prometheus-api-metrics'); | ||||
| const { sleep } = require("./util"); | ||||
| const args = require("args-parser")(process.argv); | ||||
| const prometheusAPIMetrics = require("prometheus-api-metrics"); | ||||
| const { basicAuth } = require("./auth"); | ||||
| const {login} = require("./auth"); | ||||
| const { login } = require("./auth"); | ||||
| const passwordHash = require('./password-hash'); | ||||
| const version = require('../package.json').version; | ||||
| const version = require("../package.json").version; | ||||
| const hostname = args.host || "0.0.0.0" | ||||
| const port = args.port || 3001 | ||||
| 
 | ||||
|  | @ -64,12 +64,12 @@ let needSetup = false; | |||
| 
 | ||||
|     // Normal Router here
 | ||||
| 
 | ||||
|     app.use('/', express.static("dist")); | ||||
|     app.use("/", express.static("dist")); | ||||
| 
 | ||||
|     // Basic Auth Router here
 | ||||
| 
 | ||||
|     // For testing
 | ||||
|     basicAuthRouter.get('/test-auth', (req, res) => { | ||||
|     basicAuthRouter.get("/test-auth", (req, res) => { | ||||
|         res.end("OK") | ||||
|     }); | ||||
| 
 | ||||
|  | @ -78,12 +78,12 @@ let needSetup = false; | |||
|     basicAuthRouter.use(prometheusAPIMetrics()) | ||||
| 
 | ||||
|     // Universal Route Handler, must be at the end
 | ||||
|     app.get('*', function(request, response, next) { | ||||
|         response.sendFile(process.cwd() + '/dist/index.html'); | ||||
|     app.get("*", function(request, response, next) { | ||||
|         response.sendFile(process.cwd() + "/dist/index.html"); | ||||
|     }); | ||||
| 
 | ||||
|     console.log("Adding socket handler") | ||||
|     io.on('connection', async (socket) => { | ||||
|     io.on("connection", async (socket) => { | ||||
| 
 | ||||
|         socket.emit("info", { | ||||
|             version, | ||||
|  | @ -96,7 +96,7 @@ let needSetup = false; | |||
|             socket.emit("setup") | ||||
|         } | ||||
| 
 | ||||
|         socket.on('disconnect', () => { | ||||
|         socket.on("disconnect", () => { | ||||
|             totalClient--; | ||||
|         }); | ||||
| 
 | ||||
|  | @ -110,7 +110,7 @@ let needSetup = false; | |||
|                 console.log("Username from JWT: " + decoded.username) | ||||
| 
 | ||||
|                 let user = await R.findOne("user", " username = ? AND active = 1 ", [ | ||||
|                     decoded.username | ||||
|                     decoded.username, | ||||
|                 ]) | ||||
| 
 | ||||
|                 if (user) { | ||||
|  | @ -122,13 +122,13 @@ let needSetup = false; | |||
|                 } else { | ||||
|                     callback({ | ||||
|                         ok: false, | ||||
|                         msg: "The user is inactive or deleted." | ||||
|                         msg: "The user is inactive or deleted.", | ||||
|                     }) | ||||
|                 } | ||||
|             } catch (error) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: "Invalid token." | ||||
|                     msg: "Invalid token.", | ||||
|                 }) | ||||
|             } | ||||
| 
 | ||||
|  | @ -145,13 +145,13 @@ let needSetup = false; | |||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     token: jwt.sign({ | ||||
|                         username: data.username | ||||
|                     }, jwtSecret) | ||||
|                         username: data.username, | ||||
|                     }, jwtSecret), | ||||
|                 }) | ||||
|             } else { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: "Incorrect username or password." | ||||
|                     msg: "Incorrect username or password.", | ||||
|                 }) | ||||
|             } | ||||
| 
 | ||||
|  | @ -182,13 +182,13 @@ let needSetup = false; | |||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Added Successfully." | ||||
|                     msg: "Added Successfully.", | ||||
|                 }); | ||||
| 
 | ||||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -215,13 +215,13 @@ let needSetup = false; | |||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Added Successfully.", | ||||
|                     monitorID: bean.id | ||||
|                     monitorID: bean.id, | ||||
|                 }); | ||||
| 
 | ||||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -258,14 +258,14 @@ let needSetup = false; | |||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Saved.", | ||||
|                     monitorID: bean.id | ||||
|                     monitorID: bean.id, | ||||
|                 }); | ||||
| 
 | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -289,7 +289,7 @@ let needSetup = false; | |||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -303,13 +303,13 @@ let needSetup = false; | |||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Resumed Successfully." | ||||
|                     msg: "Resumed Successfully.", | ||||
|                 }); | ||||
| 
 | ||||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -322,14 +322,13 @@ let needSetup = false; | |||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Paused Successfully." | ||||
|                     msg: "Paused Successfully.", | ||||
|                 }); | ||||
| 
 | ||||
| 
 | ||||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -347,12 +346,12 @@ let needSetup = false; | |||
| 
 | ||||
|                 await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [ | ||||
|                     monitorID, | ||||
|                     socket.userID | ||||
|                     socket.userID, | ||||
|                 ]); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Deleted Successfully." | ||||
|                     msg: "Deleted Successfully.", | ||||
|                 }); | ||||
| 
 | ||||
|                 await sendMonitorList(socket); | ||||
|  | @ -360,7 +359,7 @@ let needSetup = false; | |||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -374,19 +373,19 @@ let needSetup = false; | |||
|                 } | ||||
| 
 | ||||
|                 let user = await R.findOne("user", " id = ? AND active = 1 ", [ | ||||
|                     socket.userID | ||||
|                     socket.userID, | ||||
|                 ]) | ||||
| 
 | ||||
|                 if (user && passwordHash.verify(password.currentPassword, user.password)) { | ||||
| 
 | ||||
|                     await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ | ||||
|                         passwordHash.generate(password.newPassword), | ||||
|                         socket.userID | ||||
|                         socket.userID, | ||||
|                     ]); | ||||
| 
 | ||||
|                     callback({ | ||||
|                         ok: true, | ||||
|                         msg: "Password has been updated successfully." | ||||
|                         msg: "Password has been updated successfully.", | ||||
|                     }) | ||||
|                 } else { | ||||
|                     throw new Error("Incorrect current password") | ||||
|  | @ -395,7 +394,7 @@ let needSetup = false; | |||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -404,7 +403,6 @@ let needSetup = false; | |||
|             try { | ||||
|                 checkLogin(socket) | ||||
| 
 | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     data: await getSettings(type), | ||||
|  | @ -413,7 +411,7 @@ let needSetup = false; | |||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -434,7 +432,7 @@ let needSetup = false; | |||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -454,7 +452,7 @@ let needSetup = false; | |||
|             } catch (e) { | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -467,7 +465,7 @@ let needSetup = false; | |||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg | ||||
|                     msg, | ||||
|                 }); | ||||
| 
 | ||||
|             } catch (e) { | ||||
|  | @ -475,7 +473,7 @@ let needSetup = false; | |||
| 
 | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                     msg: e.message, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | @ -500,7 +498,7 @@ let needSetup = false; | |||
| 
 | ||||
| async function updateMonitorNotification(monitorID, notificationIDList) { | ||||
|     R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [ | ||||
|         monitorID | ||||
|         monitorID, | ||||
|     ]) | ||||
| 
 | ||||
|     for (let notificationID in notificationIDList) { | ||||
|  | @ -533,7 +531,7 @@ async function sendMonitorList(socket) { | |||
| async function sendNotificationList(socket) { | ||||
|     let result = []; | ||||
|     let list = await R.find("notification", " user_id = ? ", [ | ||||
|         socket.userID | ||||
|         socket.userID, | ||||
|     ]); | ||||
| 
 | ||||
|     for (let bean of list) { | ||||
|  | @ -563,7 +561,7 @@ async function getMonitorJSONList(userID) { | |||
|     let result = {}; | ||||
| 
 | ||||
|     let monitorList = await R.find("monitor", " user_id = ? ", [ | ||||
|         userID | ||||
|         userID, | ||||
|     ]) | ||||
| 
 | ||||
|     for (let monitor of monitorList) { | ||||
|  | @ -586,8 +584,8 @@ async function initDatabase() { | |||
|     } | ||||
| 
 | ||||
|     console.log("Connecting to Database") | ||||
|     R.setup('sqlite', { | ||||
|         filename: Database.path | ||||
|     R.setup("sqlite", { | ||||
|         filename: Database.path, | ||||
|     }); | ||||
|     console.log("Connected") | ||||
| 
 | ||||
|  | @ -599,7 +597,7 @@ async function initDatabase() { | |||
|     await R.autoloadModels("./server/model"); | ||||
| 
 | ||||
|     let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [ | ||||
|         "jwtSecret" | ||||
|         "jwtSecret", | ||||
|     ]); | ||||
| 
 | ||||
|     if (! jwtSecretBean) { | ||||
|  | @ -630,11 +628,11 @@ async function startMonitor(userID, monitorID) { | |||
| 
 | ||||
|     await R.exec("UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? ", [ | ||||
|         monitorID, | ||||
|         userID | ||||
|         userID, | ||||
|     ]); | ||||
| 
 | ||||
|     let monitor = await R.findOne("monitor", " id = ? ", [ | ||||
|         monitorID | ||||
|         monitorID, | ||||
|     ]) | ||||
| 
 | ||||
|     if (monitor.id in monitorList) { | ||||
|  | @ -656,7 +654,7 @@ async function pauseMonitor(userID, monitorID) { | |||
| 
 | ||||
|     await R.exec("UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? ", [ | ||||
|         monitorID, | ||||
|         userID | ||||
|         userID, | ||||
|     ]); | ||||
| 
 | ||||
|     if (monitorID in monitorList) { | ||||
|  | @ -685,13 +683,13 @@ async function sendHeartbeatList(socket, monitorID) { | |||
|         ORDER BY time DESC | ||||
|         LIMIT 100 | ||||
|     `, [
 | ||||
|         monitorID | ||||
|         monitorID, | ||||
|     ]) | ||||
| 
 | ||||
|     let result = []; | ||||
| 
 | ||||
|     for (let bean of list) { | ||||
|        result.unshift(bean.toJSON()) | ||||
|         result.unshift(bean.toJSON()) | ||||
|     } | ||||
| 
 | ||||
|     socket.emit("heartbeatList", monitorID, result) | ||||
|  | @ -704,23 +702,20 @@ async function sendImportantHeartbeatList(socket, monitorID) { | |||
|         ORDER BY time DESC | ||||
|         LIMIT 500 | ||||
|     `, [
 | ||||
|         monitorID | ||||
|         monitorID, | ||||
|     ]) | ||||
| 
 | ||||
|     socket.emit("importantHeartbeatList", monitorID, list) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const startGracefulShutdown = async () => { | ||||
|     console.log('Shutdown requested'); | ||||
| 
 | ||||
|     console.log("Shutdown requested"); | ||||
| 
 | ||||
|     await (new Promise((resolve) => { | ||||
|         server.close(async function () { | ||||
|             console.log('Stopped Express.'); | ||||
|             console.log("Stopped Express."); | ||||
|             process.exit(0) | ||||
|             setTimeout(async () =>{ | ||||
|             setTimeout(async () => { | ||||
|                 await R.close(); | ||||
|                 console.log("Stopped DB") | ||||
| 
 | ||||
|  | @ -730,11 +725,10 @@ const startGracefulShutdown = async () => { | |||
|         }); | ||||
|     })); | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| async function shutdownFunction(signal) { | ||||
|     console.log('Called signal: ' + signal); | ||||
|     console.log("Called signal: " + signal); | ||||
| 
 | ||||
|     console.log("Stopping all monitors") | ||||
|     for (let id in monitorList) { | ||||
|  | @ -746,14 +740,14 @@ async function shutdownFunction(signal) { | |||
| } | ||||
| 
 | ||||
| function finalFunction() { | ||||
|     console.log('Graceful Shutdown') | ||||
|     console.log("Graceful Shutdown") | ||||
| } | ||||
| 
 | ||||
| gracefulShutdown(server, { | ||||
|     signals: 'SIGINT SIGTERM', | ||||
|     signals: "SIGINT SIGTERM", | ||||
|     timeout: 30000,                   // timeout: 30 secs
 | ||||
|     development: false,               // not in dev mode
 | ||||
|     forceExit: true,                  // triggers process.exit() at the end of shutdown process
 | ||||
|     onShutdown: shutdownFunction,     // shutdown function (async) - e.g. for cleanup DB, ...
 | ||||
|     finally: finalFunction            // finally function (sync) - e.g. for logging
 | ||||
|     finally: finalFunction,            // finally function (sync) - e.g. for logging
 | ||||
| }); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| const tcpp = require('tcp-ping'); | ||||
| const tcpp = require("tcp-ping"); | ||||
| const Ping = require("./ping-lite"); | ||||
| const {R} = require("redbean-node"); | ||||
| const { R } = require("redbean-node"); | ||||
| 
 | ||||
| exports.tcping = function (hostname, port) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|  | @ -41,13 +41,13 @@ exports.ping = function (hostname) { | |||
| 
 | ||||
| exports.setting = async function (key) { | ||||
|     return await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [ | ||||
|         key | ||||
|         key, | ||||
|     ]) | ||||
| } | ||||
| 
 | ||||
| exports.setSetting = async function (key, value) { | ||||
|     let bean = await R.findOne("setting", " `key` = ? ", [ | ||||
|         key | ||||
|         key, | ||||
|     ]) | ||||
|     if (! bean) { | ||||
|         bean = R.dispense("setting") | ||||
|  | @ -59,7 +59,7 @@ exports.setSetting = async function (key, value) { | |||
| 
 | ||||
| exports.getSettings = async function (type) { | ||||
|     let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [ | ||||
|         type | ||||
|         type, | ||||
|     ]) | ||||
| 
 | ||||
|     let result = {}; | ||||
|  | @ -71,7 +71,6 @@ exports.getSettings = async function (type) { | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // ssl-checker by @dyaa
 | ||||
| // param: res - response object from axios
 | ||||
| // return an object containing the certificate information
 | ||||
|  | @ -97,7 +96,9 @@ exports.checkCertificate = function (res) { | |||
|     } = res.request.res.socket.getPeerCertificate(false); | ||||
| 
 | ||||
|     if (!valid_from || !valid_to || !subjectaltname) { | ||||
|         throw { message: 'No TLS certificate in response' }; | ||||
|         throw { | ||||
|             message: "No TLS certificate in response", | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     const valid = res.request.res.socket.authorized || false; | ||||
|  | @ -118,4 +119,4 @@ exports.checkCertificate = function (res) { | |||
|         issuer, | ||||
|         fingerprint, | ||||
|     }; | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -23,4 +23,3 @@ exports.debug = (msg) => { | |||
|         console.log(msg) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,11 +3,5 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
| 
 | ||||
| } | ||||
| export default {} | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,17 +1,23 @@ | |||
| <template> | ||||
|     <div class="modal fade" tabindex="-1" ref="modal"> | ||||
|     <div ref="modal" class="modal fade" tabindex="-1"> | ||||
|         <div class="modal-dialog"> | ||||
|             <div class="modal-content"> | ||||
|                 <div class="modal-header"> | ||||
|                     <h5 class="modal-title" id="exampleModalLabel">Confirm</h5> | ||||
|                     <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> | ||||
|                     <h5 id="exampleModalLabel" class="modal-title"> | ||||
|                         Confirm | ||||
|                     </h5> | ||||
|                     <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" /> | ||||
|                 </div> | ||||
|                 <div class="modal-body"> | ||||
|                     <slot></slot> | ||||
|                     <slot /> | ||||
|                 </div> | ||||
|                 <div class="modal-footer"> | ||||
|                     <button type="button" class="btn" :class="btnStyle" @click="yes" data-bs-dismiss="modal">Yes</button> | ||||
|                     <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> | ||||
|                     <button type="button" class="btn" :class="btnStyle" data-bs-dismiss="modal" @click="yes"> | ||||
|                         Yes | ||||
|                     </button> | ||||
|                     <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> | ||||
|                         No | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | @ -19,17 +25,17 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { Modal } from 'bootstrap' | ||||
| import { Modal } from "bootstrap" | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|         btnStyle: { | ||||
|             type: String, | ||||
|             default: "btn-primary" | ||||
|         } | ||||
|             default: "btn-primary", | ||||
|         }, | ||||
|     }, | ||||
|     data: () => ({ | ||||
|         modal: null | ||||
|         modal: null, | ||||
|     }), | ||||
|     mounted() { | ||||
|         this.modal = new Modal(this.$refs.modal) | ||||
|  | @ -39,12 +45,8 @@ export default { | |||
|             this.modal.show() | ||||
|         }, | ||||
|         yes() { | ||||
|             this.$emit('yes'); | ||||
|         } | ||||
|     } | ||||
|             this.$emit("yes"); | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -5,24 +5,20 @@ | |||
| 
 | ||||
| <script> | ||||
| 
 | ||||
| import {sleep} from '../util-frontend' | ||||
| import { sleep } from "../util-frontend" | ||||
| 
 | ||||
| export default { | ||||
| 
 | ||||
|     props: { | ||||
|         value: [String, Number], | ||||
|         time: { | ||||
|             Number, | ||||
|             type: Number, | ||||
|             default: 0.3, | ||||
|         }, | ||||
|         unit: { | ||||
|             String, | ||||
|             type: String, | ||||
|             default: "ms", | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     mounted() { | ||||
|         this.output = this.value; | ||||
|         }, | ||||
|     }, | ||||
| 
 | ||||
|     data() { | ||||
|  | @ -32,14 +28,10 @@ export default { | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     methods: { | ||||
| 
 | ||||
|     }, | ||||
| 
 | ||||
|     computed: { | ||||
|         isNum() { | ||||
|             return typeof this.value === 'number' | ||||
|         } | ||||
|             return typeof this.value === "number" | ||||
|         }, | ||||
|     }, | ||||
| 
 | ||||
|     watch: { | ||||
|  | @ -61,9 +53,11 @@ export default { | |||
|         }, | ||||
|     }, | ||||
| 
 | ||||
|     mounted() { | ||||
|         this.output = this.value; | ||||
|     }, | ||||
| 
 | ||||
|     methods: {}, | ||||
| 
 | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -5,8 +5,8 @@ | |||
| <script> | ||||
| import dayjs from "dayjs"; | ||||
| import relativeTime from "dayjs/plugin/relativeTime" | ||||
| import utc from 'dayjs/plugin/utc' | ||||
| import timezone from 'dayjs/plugin/timezone' // dependent on utc plugin | ||||
| import utc from "dayjs/plugin/utc" | ||||
| import timezone from "dayjs/plugin/timezone" // dependent on utc plugin | ||||
| dayjs.extend(utc) | ||||
| dayjs.extend(timezone) | ||||
| dayjs.extend(relativeTime) | ||||
|  | @ -28,14 +28,10 @@ export default { | |||
|                     format = "YYYY-MM-DD"; | ||||
|                 } | ||||
|                 return dayjs.utc(this.value).tz(this.$root.timezone).format(format); | ||||
|             } else { | ||||
|                 return ""; | ||||
|             } | ||||
| 
 | ||||
|             return ""; | ||||
|         }, | ||||
|     } | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,28 +1,27 @@ | |||
| <template> | ||||
|     <div class="wrap" :style="wrapStyle" ref="wrap"> | ||||
|     <div ref="wrap" class="wrap" :style="wrapStyle"> | ||||
|         <div class="hp-bar-big" :style="barStyle"> | ||||
|             <div | ||||
|                 v-for="(beat, index) in shortBeatList" | ||||
|                 :key="index" | ||||
|                 class="beat" | ||||
|                 :class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }" | ||||
|                 :style="beatStyle" | ||||
|                 v-for="(beat, index) in shortBeatList" | ||||
|                 :key="index" | ||||
|                 :title="beat.msg"> | ||||
|             </div> | ||||
|                 :title="beat.msg" | ||||
|             /> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| 
 | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|         size: { | ||||
|             type: String, | ||||
|             default: "big" | ||||
|             default: "big", | ||||
|         }, | ||||
|         monitorId: Number | ||||
|         monitorId: Number, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|  | @ -34,26 +33,6 @@ export default { | |||
|             maxBeat: -1, | ||||
|         } | ||||
|     }, | ||||
|     unmounted() { | ||||
|         window.removeEventListener("resize", this.resize); | ||||
|     }, | ||||
|     mounted() { | ||||
|         if (this.size === "small") { | ||||
|             this.beatWidth = 5.6; | ||||
|             this.beatMargin = 2.4; | ||||
|             this.beatHeight = 16 | ||||
|         } | ||||
| 
 | ||||
|         window.addEventListener("resize", this.resize); | ||||
|         this.resize(); | ||||
|     }, | ||||
|     methods: { | ||||
|         resize() { | ||||
|             if (this.$refs.wrap) { | ||||
|                 this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)) | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
| 
 | ||||
|         beatList() { | ||||
|  | @ -80,8 +59,6 @@ export default { | |||
|                 start = 0; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|             return placeholders.concat(this.beatList.slice(start)) | ||||
|         }, | ||||
| 
 | ||||
|  | @ -92,13 +69,13 @@ export default { | |||
|             let width | ||||
|             if (this.maxBeat > 0) { | ||||
|                 width = (this.beatWidth + this.beatMargin * 2) * this.maxBeat + (leftRight * 2) + "px" | ||||
|             } { | ||||
|             } else { | ||||
|                 width = "100%" | ||||
|             } | ||||
| 
 | ||||
|             return { | ||||
|                 padding: `${topBottom}px ${leftRight}px`, | ||||
|                 width: width | ||||
|                 width: width, | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|  | @ -111,11 +88,11 @@ export default { | |||
|                     transform: `translateX(${width}px)`, | ||||
|                 } | ||||
| 
 | ||||
|             } else { | ||||
|                 return { | ||||
|                     transform: `translateX(0)`, | ||||
|                 } | ||||
|             } | ||||
|             return { | ||||
|                 transform: "translateX(0)", | ||||
|             } | ||||
| 
 | ||||
|         }, | ||||
| 
 | ||||
|         beatStyle() { | ||||
|  | @ -125,7 +102,7 @@ export default { | |||
|                 margin: this.beatMargin + "px", | ||||
|                 "--hover-scale": this.hoverScale, | ||||
|             } | ||||
|         } | ||||
|         }, | ||||
| 
 | ||||
|     }, | ||||
|     watch: { | ||||
|  | @ -138,8 +115,28 @@ export default { | |||
|                 }, 300) | ||||
|             }, | ||||
|             deep: true, | ||||
|         }, | ||||
|     }, | ||||
|     unmounted() { | ||||
|         window.removeEventListener("resize", this.resize); | ||||
|     }, | ||||
|     mounted() { | ||||
|         if (this.size === "small") { | ||||
|             this.beatWidth = 5.6; | ||||
|             this.beatMargin = 2.4; | ||||
|             this.beatHeight = 16 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|         window.addEventListener("resize", this.resize); | ||||
|         this.resize(); | ||||
|     }, | ||||
|     methods: { | ||||
|         resize() { | ||||
|             if (this.$refs.wrap) { | ||||
|                 this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)) | ||||
|             } | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,31 +2,32 @@ | |||
|     <div class="form-container"> | ||||
|         <div class="form"> | ||||
|             <form @submit.prevent="submit"> | ||||
| 
 | ||||
|                 <h1 class="h3 mb-3 fw-normal"></h1> | ||||
|                 <h1 class="h3 mb-3 fw-normal" /> | ||||
| 
 | ||||
|                 <div class="form-floating"> | ||||
|                     <input type="text" class="form-control" id="floatingInput" placeholder="Username" v-model="username"> | ||||
|                     <input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username"> | ||||
|                     <label for="floatingInput">Username</label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <div class="form-floating mt-3"> | ||||
|                     <input type="password" class="form-control" id="floatingPassword" placeholder="Password" v-model="password"> | ||||
|                     <input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password"> | ||||
|                     <label for="floatingPassword">Password</label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <div class="form-check mb-3 mt-3 d-flex justify-content-center pe-4"> | ||||
|                     <div class="form-check"> | ||||
|                         <input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember"> | ||||
|                         <input id="remember" v-model="$root.remember" type="checkbox" value="remember-me" class="form-check-input"> | ||||
| 
 | ||||
|                         <label class="form-check-label" for="remember"> | ||||
|                             Remember me | ||||
|                         </label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button> | ||||
|                 <button class="w-100 btn btn-primary" type="submit" :disabled="processing"> | ||||
|                     Login | ||||
|                 </button> | ||||
| 
 | ||||
|                 <div class="alert alert-danger mt-3" role="alert" v-if="res && !res.ok"> | ||||
|                 <div v-if="res && !res.ok" class="alert alert-danger mt-3" role="alert"> | ||||
|                     {{ res.msg }} | ||||
|                 </div> | ||||
|             </form> | ||||
|  | @ -52,8 +53,8 @@ export default { | |||
|                 this.processing = false; | ||||
|                 this.res = res; | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,48 +1,70 @@ | |||
| <template> | ||||
|     <form @submit.prevent="submit"> | ||||
| 
 | ||||
|         <div class="modal fade" tabindex="-1" ref="modal" data-bs-backdrop="static"> | ||||
|         <div ref="modal" 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" id="exampleModalLabel">Setup Notification</h5> | ||||
|                         <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> | ||||
|                         <h5 id="exampleModalLabel" class="modal-title"> | ||||
|                             Setup Notification | ||||
|                         </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">Notification Type</label> | ||||
|                             <select class="form-select"  id="type" v-model="notification.type"> | ||||
|                                 <option value="telegram">Telegram</option> | ||||
|                                 <option value="webhook">Webhook</option> | ||||
|                                 <option value="smtp">Email (SMTP)</option> | ||||
|                                 <option value="discord">Discord</option> | ||||
|                                 <option value="signal">Signal</option> | ||||
|                                 <option value="gotify">Gotify</option> | ||||
|                                 <option value="slack">Slack</option> | ||||
|                                 <option value="pushover">Pushover</option> | ||||
|                                 <option value="apprise">Apprise (Support 50+ Notification services)</option> | ||||
|                             <select id="type" v-model="notification.type" class="form-select"> | ||||
|                                 <option value="telegram"> | ||||
|                                     Telegram | ||||
|                                 </option> | ||||
|                                 <option value="webhook"> | ||||
|                                     Webhook | ||||
|                                 </option> | ||||
|                                 <option value="smtp"> | ||||
|                                     Email (SMTP) | ||||
|                                 </option> | ||||
|                                 <option value="discord"> | ||||
|                                     Discord | ||||
|                                 </option> | ||||
|                                 <option value="signal"> | ||||
|                                     Signal | ||||
|                                 </option> | ||||
|                                 <option value="gotify"> | ||||
|                                     Gotify | ||||
|                                 </option> | ||||
|                                 <option value="slack"> | ||||
|                                     Slack | ||||
|                                 </option> | ||||
|                                 <option value="pushover"> | ||||
|                                     Pushover | ||||
|                                 </option> | ||||
|                                 <option value="apprise"> | ||||
|                                     Apprise (Support 50+ Notification services) | ||||
|                                 </option> | ||||
|                             </select> | ||||
|                         </div> | ||||
| 
 | ||||
|                         <div class="mb-3"> | ||||
|                             <label for="name" class="form-label">Friendly Name</label> | ||||
|                             <input type="text" class="form-control" id="name" required v-model="notification.name"> | ||||
|                             <input id="name" v-model="notification.name" type="text" class="form-control" required> | ||||
|                         </div> | ||||
| 
 | ||||
|                         <template v-if="notification.type === 'telegram'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="telegram-bot-token" class="form-label">Bot Token</label> | ||||
|                                 <input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken"> | ||||
|                                 <div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div> | ||||
|                                 <input id="telegram-bot-token" v-model="notification.telegramBotToken" type="text" class="form-control" required> | ||||
|                                 <div class="form-text"> | ||||
|                                     You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. | ||||
|                                 </div> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="telegram-chat-id" class="form-label">Chat ID</label> | ||||
| 
 | ||||
|                                 <div class="input-group mb-3"> | ||||
|                                     <input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID"> | ||||
|                                     <button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button> | ||||
|                                     <input id="telegram-chat-id" v-model="notification.telegramChatID" type="text" class="form-control" required> | ||||
|                                     <button v-if="notification.telegramBotToken" class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID"> | ||||
|                                         Auto Get | ||||
|                                     </button> | ||||
|                                 </div> | ||||
| 
 | ||||
|                                 <div class="form-text"> | ||||
|  | @ -53,7 +75,6 @@ | |||
|                                     </p> | ||||
| 
 | ||||
|                                     <p style="margin-top: 8px;"> | ||||
| 
 | ||||
|                                         <template v-if="notification.telegramBotToken"> | ||||
|                                             <a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a> | ||||
|                                         </template> | ||||
|  | @ -69,15 +90,18 @@ | |||
|                         <template v-if="notification.type === 'webhook'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="webhook-url" class="form-label">Post URL</label> | ||||
|                                 <input type="url" pattern="https?://.+" class="form-control" id="webhook-url" required v-model="notification.webhookURL"> | ||||
| 
 | ||||
|                                 <input id="webhook-url" v-model="notification.webhookURL" type="url" pattern="https?://.+" class="form-control" required> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="webhook-content-type" class="form-label">Content Type</label> | ||||
|                                 <select class="form-select" id="webhook-content-type" v-model="notification.webhookContentType" required> | ||||
|                                     <option value="json">application/json</option> | ||||
|                                     <option value="form-data">multipart/form-data</option> | ||||
|                                 <select id="webhook-content-type" v-model="notification.webhookContentType" class="form-select" required> | ||||
|                                     <option value="json"> | ||||
|                                         application/json | ||||
|                                     </option> | ||||
|                                     <option value="form-data"> | ||||
|                                         multipart/form-data | ||||
|                                     </option> | ||||
|                                 </select> | ||||
| 
 | ||||
|                                 <div class="form-text"> | ||||
|  | @ -90,70 +114,71 @@ | |||
|                         <template v-if="notification.type === 'smtp'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="hostname" class="form-label">Hostname</label> | ||||
|                                 <input type="text" class="form-control" id="hostname" required v-model="notification.smtpHost"> | ||||
|                                 <input id="hostname" v-model="notification.smtpHost" type="text" class="form-control" required> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="port" class="form-label">Port</label> | ||||
|                                 <input type="number" class="form-control" id="port" v-model="notification.smtpPort" required min="0" max="65535" step="1"> | ||||
|                                 <input id="port" v-model="notification.smtpPort" type="number" class="form-control" required min="0" max="65535" step="1"> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <div class="form-check"> | ||||
|                                     <input class="form-check-input" type="checkbox" value="" id="secure" v-model="notification.smtpSecure"> | ||||
|                                     <input id="secure" v-model="notification.smtpSecure" class="form-check-input" type="checkbox" value=""> | ||||
|                                     <label class="form-check-label" for="secure"> | ||||
|                                         Secure | ||||
|                                     </label> | ||||
|                                 </div> | ||||
|                                 <div class="form-text">Generally, true for 465, false for other ports.</div> | ||||
|                                 <div class="form-text"> | ||||
|                                     Generally, true for 465, false for other ports. | ||||
|                                 </div> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="username" class="form-label">Username</label> | ||||
|                                 <input type="text" class="form-control" id="username" v-model="notification.smtpUsername" autocomplete="false"> | ||||
|                                 <input id="username" v-model="notification.smtpUsername" type="text" class="form-control" autocomplete="false"> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="password" class="form-label">Password</label> | ||||
|                                 <input type="password" class="form-control" id="password" v-model="notification.smtpPassword" autocomplete="false"> | ||||
|                                 <input id="password" v-model="notification.smtpPassword" type="password" class="form-control" autocomplete="false"> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="from-email" class="form-label">From Email</label> | ||||
|                                 <input type="email" class="form-control" id="from-email" required v-model="notification.smtpFrom" autocomplete="false"> | ||||
|                                 <input id="from-email" v-model="notification.smtpFrom" type="email" class="form-control" required autocomplete="false"> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="to-email" class="form-label">To Email</label> | ||||
|                                 <input type="email" class="form-control" id="to-email" required v-model="notification.smtpTo" autocomplete="false"> | ||||
|                                 <input id="to-email" v-model="notification.smtpTo" type="email" class="form-control" required autocomplete="false"> | ||||
|                             </div> | ||||
| 
 | ||||
|                         </template> | ||||
| 
 | ||||
|                         <template v-if="notification.type === 'discord'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="discord-webhook-url" class="form-label">Discord Webhook URL</label> | ||||
|                                 <input type="text" class="form-control" id="discord-webhook-url" required v-model="notification.discordWebhookUrl" autocomplete="false"> | ||||
|                                 <div class="form-text">You can get this by going to Server Settings -> Integrations -> Create Webhook</div> | ||||
|                                 <input id="discord-webhook-url" v-model="notification.discordWebhookUrl" type="text" class="form-control" required autocomplete="false"> | ||||
|                                 <div class="form-text"> | ||||
|                                     You can get this by going to Server Settings -> Integrations -> Create Webhook | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </template> | ||||
| 
 | ||||
|                         <template v-if="notification.type === 'signal'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="signal-url" class="form-label">Post URL</label> | ||||
|                                 <input type="url" pattern="https?://.+" class="form-control" id="signal-url" required v-model="notification.signalURL"> | ||||
| 
 | ||||
|                                 <input id="signal-url" v-model="notification.signalURL" type="url" pattern="https?://.+" class="form-control" required> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="signal-number" class="form-label">Number</label> | ||||
|                                 <input type="text" class="form-control" id="signal-number" required v-model="notification.signalNumber"> | ||||
| 
 | ||||
|                                 <input id="signal-number" v-model="notification.signalNumber" type="text" class="form-control" required> | ||||
|                             </div> | ||||
| 
 | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="signal-recipients" class="form-label">Recipients</label> | ||||
|                                 <input type="text" class="form-control" id="signal-recipients" required v-model="notification.signalRecipients"> | ||||
|                                 <input id="signal-recipients" v-model="notification.signalRecipients" type="text" class="form-control" required> | ||||
| 
 | ||||
|                                 <div class="form-text"> | ||||
|                                     You need to have a signal client with REST API. | ||||
|  | @ -174,37 +199,37 @@ | |||
|                         </template> | ||||
| 
 | ||||
|                         <template v-if="notification.type === 'gotify'"> | ||||
|                                 <div class="mb-3"> | ||||
|                                     <label for="gotify-application-token" class="form-label">Application Token</label> | ||||
|                                     <input type="text" class="form-control" id="gotify-application-token" required v-model="notification.gotifyapplicationToken"> | ||||
|                                 </div> | ||||
|                                 <div class="mb-3"> | ||||
|                                     <label for="gotify-server-url" class="form-label">Server URL</label> | ||||
|                                     <div class="input-group mb-3"> | ||||
|                                         <input type="text" class="form-control" id="gotify-server-url" required v-model="notification.gotifyserverurl"> | ||||
|                                     </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="gotify-application-token" class="form-label">Application Token</label> | ||||
|                                 <input id="gotify-application-token" v-model="notification.gotifyapplicationToken" type="text" class="form-control" required> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="gotify-server-url" class="form-label">Server URL</label> | ||||
|                                 <div class="input-group mb-3"> | ||||
|                                     <input id="gotify-server-url" v-model="notification.gotifyserverurl" type="text" class="form-control" required> | ||||
|                                 </div> | ||||
|                             </div> | ||||
| 
 | ||||
|                                 <div class="mb-3"> | ||||
|                                     <label for="gotify-priority" class="form-label">Priority</label> | ||||
|                                     <input type="number" class="form-control" id="gotify-priority" v-model="notification.gotifyPriority" required min="0" max="10" step="1"> | ||||
|                                 </div> | ||||
|                             </template> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="gotify-priority" class="form-label">Priority</label> | ||||
|                                 <input id="gotify-priority" v-model="notification.gotifyPriority" type="number" class="form-control" required min="0" max="10" step="1"> | ||||
|                             </div> | ||||
|                         </template> | ||||
| 
 | ||||
|                         <template v-if="notification.type === 'slack'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="slack-webhook-url" class="form-label">Webhook URL<span style="color:red;"><sup>*</sup></span></label> | ||||
|                                 <input type="text" class="form-control" id="slack-webhook-url" required v-model="notification.slackwebhookURL"> | ||||
|                                 <input id="slack-webhook-url" v-model="notification.slackwebhookURL" type="text" class="form-control" required> | ||||
|                                 <label for="slack-username" class="form-label">Username</label> | ||||
|                                 <input type="text" class="form-control" id="slack-username" v-model="notification.slackusername"> | ||||
|                                 <input id="slack-username" v-model="notification.slackusername" type="text" class="form-control"> | ||||
|                                 <label for="slack-iconemo" class="form-label">Icon Emoji</label> | ||||
|                                 <input type="text" class="form-control" id="slack-iconemo" v-model="notification.slackiconemo"> | ||||
|                                 <input id="slack-iconemo" v-model="notification.slackiconemo" type="text" class="form-control"> | ||||
|                                 <label for="slack-channel" class="form-label">Channel Name</label> | ||||
|                                 <input type="text" class="form-control" id="slack-channel-name" v-model="notification.slackchannel"> | ||||
|                                 <input id="slack-channel-name" v-model="notification.slackchannel" type="text" class="form-control"> | ||||
|                                 <label for="slack-button-url" class="form-label">Uptime Kuma URL</label> | ||||
|                                 <input type="text" class="form-control" id="slack-button" v-model="notification.slackbutton"> | ||||
|                                 <input id="slack-button" v-model="notification.slackbutton" type="text" class="form-control"> | ||||
|                                 <div class="form-text"> | ||||
|                                 <span style="color:red;"><sup>*</sup></span>Required | ||||
|                                     <span style="color:red;"><sup>*</sup></span>Required | ||||
|                                     <p style="margin-top: 8px;"> | ||||
|                                         More info about webhooks on: <a href="https://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a> | ||||
|                                     </p> | ||||
|  | @ -224,15 +249,15 @@ | |||
|                         <template v-if="notification.type === 'pushover'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label> | ||||
|                                 <input type="text" class="form-control" id="pushover-user" required v-model="notification.pushoveruserkey"> | ||||
|                                 <input id="pushover-user" v-model="notification.pushoveruserkey" type="text" class="form-control" required> | ||||
|                                 <label for="pushover-app-token" class="form-label">Application Token<span style="color:red;"><sup>*</sup></span></label> | ||||
|                                 <input type="text" class="form-control" id="pushover-app-token" required v-model="notification.pushoverapptoken"> | ||||
|                                 <input id="pushover-app-token" v-model="notification.pushoverapptoken" type="text" class="form-control" required> | ||||
|                                 <label for="pushover-device" class="form-label">Device</label> | ||||
|                                 <input type="text" class="form-control" id="pushover-device" v-model="notification.pushoverdevice"> | ||||
|                                 <input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control"> | ||||
|                                 <label for="pushover-device" class="form-label">Message Title</label> | ||||
|                                 <input type="text" class="form-control" id="pushover-title" v-model="notification.pushovertitle"> | ||||
|                                 <input id="pushover-title" v-model="notification.pushovertitle" type="text" class="form-control"> | ||||
|                                 <label for="pushover-priority" class="form-label">Priority</label> | ||||
|                                 <select class="form-select"  id="pushover-priority" v-model="notification.pushoverpriority"> | ||||
|                                 <select id="pushover-priority" v-model="notification.pushoverpriority" class="form-select"> | ||||
|                                     <option>-2</option> | ||||
|                                     <option>-1</option> | ||||
|                                     <option>0</option> | ||||
|  | @ -240,7 +265,7 @@ | |||
|                                     <option>2</option> | ||||
|                                 </select> | ||||
|                                 <label for="pushover-sound" class="form-label">Notification Sound</label> | ||||
|                                 <select class="form-select"  id="pushover-sound" v-model="notification.pushoversounds"> | ||||
|                                 <select id="pushover-sound" v-model="notification.pushoversounds" class="form-select"> | ||||
|                                     <option>pushover</option> | ||||
|                                     <option>bike</option> | ||||
|                                     <option>bugle</option> | ||||
|  | @ -265,16 +290,16 @@ | |||
|                                     <option>none</option> | ||||
|                                 </select> | ||||
|                                 <div class="form-text"> | ||||
|                                 <span style="color:red;"><sup>*</sup></span>Required | ||||
|                                 <p style="margin-top: 8px;"> | ||||
|                                     <span style="color:red;"><sup>*</sup></span>Required | ||||
|                                     <p style="margin-top: 8px;"> | ||||
|                                         More info on: <a href="https://pushover.net/api" target="_blank">https://pushover.net/api</a> | ||||
|                                 </p> | ||||
|                                  <p style="margin-top: 8px;"> | ||||
|                                     </p> | ||||
|                                     <p style="margin-top: 8px;"> | ||||
|                                         Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour. | ||||
|                                 </p> | ||||
|                                  <p style="margin-top: 8px;"> | ||||
|                                     </p> | ||||
|                                     <p style="margin-top: 8px;"> | ||||
|                                         If you want to send notifications to different devices, fill out Device field. | ||||
|                                 </p> | ||||
|                                     </p> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </template> | ||||
|  | @ -282,7 +307,7 @@ | |||
|                         <template v-if="notification.type === 'apprise'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="apprise-url" class="form-label">Apprise URL</label> | ||||
|                                 <input type="text" class="form-control" id="apprise-url" required v-model="notification.appriseURL"> | ||||
|                                 <input id="apprise-url" v-model="notification.appriseURL" type="text" class="form-control" required> | ||||
|                                 <div class="form-text"> | ||||
|                                     <p>Example: twilio://AccountSid:AuthToken@FromPhoneNo</p> | ||||
|                                     <p> | ||||
|  | @ -293,40 +318,46 @@ | |||
|                             <div class="mb-3"> | ||||
|                                 <p> | ||||
|                                     Status: | ||||
|                                     <span class="text-primary" v-if="appriseInstalled">Apprise is installed</span> | ||||
|                                     <span class="text-danger" v-else>Apprise is not installed. <a href="https://github.com/caronc/apprise">Read more</a></span> | ||||
|                                     <span v-if="appriseInstalled" class="text-primary">Apprise is installed</span> | ||||
|                                     <span v-else class="text-danger">Apprise is not installed. <a href="https://github.com/caronc/apprise">Read more</a></span> | ||||
|                                 </p> | ||||
|                             </div> | ||||
|                         </template> | ||||
| 
 | ||||
|                     </div> | ||||
|                     <div class="modal-footer"> | ||||
|                         <button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button> | ||||
|                         <button type="button" class="btn btn-warning" @click="test" :disabled="processing">Test</button> | ||||
|                         <button type="submit" class="btn btn-primary" :disabled="processing">Save</button> | ||||
|                         <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> | ||||
|                             Delete | ||||
|                         </button> | ||||
|                         <button type="button" class="btn btn-warning" :disabled="processing" @click="test"> | ||||
|                             Test | ||||
|                         </button> | ||||
|                         <button type="submit" class="btn btn-primary" :disabled="processing"> | ||||
|                             Save | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
| 
 | ||||
|     </form> | ||||
| 
 | ||||
|     <Confirm ref="confirmDelete" @yes="deleteNotification" btn-style="btn-danger">Are you sure want to delete this notification for all monitors?</Confirm> | ||||
|     <Confirm ref="confirmDelete" btn-style="btn-danger" @yes="deleteNotification"> | ||||
|         Are you sure want to delete this notification for all monitors? | ||||
|     </Confirm> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { Modal } from 'bootstrap' | ||||
| import { ucfirst } from '../util-frontend' | ||||
| import { Modal } from "bootstrap" | ||||
| import { ucfirst } from "../util-frontend" | ||||
| import axios from "axios"; | ||||
| import { useToast } from 'vue-toastification' | ||||
| import { useToast } from "vue-toastification" | ||||
| import Confirm from "./Confirm.vue"; | ||||
| const toast = useToast() | ||||
| 
 | ||||
| export default { | ||||
|     components: {Confirm}, | ||||
|     props: { | ||||
| 
 | ||||
|     components: { | ||||
|         Confirm, | ||||
|     }, | ||||
|     props: {}, | ||||
|     data() { | ||||
|         return { | ||||
|             model: null, | ||||
|  | @ -335,11 +366,37 @@ export default { | |||
|             notification: { | ||||
|                 name: "", | ||||
|                 type: null, | ||||
|                 gotifyPriority: 8 | ||||
|                 gotifyPriority: 8, | ||||
|             }, | ||||
|             appriseInstalled: false, | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         telegramGetUpdatesURL() { | ||||
|             let token = "<YOUR BOT TOKEN HERE>" | ||||
| 
 | ||||
|             if (this.notification.telegramBotToken) { | ||||
|                 token = this.notification.telegramBotToken; | ||||
|             } | ||||
| 
 | ||||
|             return `https://api.telegram.org/bot${token}/getUpdates`; | ||||
|         }, | ||||
|     }, | ||||
|     watch: { | ||||
|         "notification.type"(to, from) { | ||||
|             let oldName; | ||||
| 
 | ||||
|             if (from) { | ||||
|                 oldName = `My ${ucfirst(from)} Alert (1)`; | ||||
|             } else { | ||||
|                 oldName = ""; | ||||
|             } | ||||
| 
 | ||||
|             if (! this.notification.name || this.notification.name === oldName) { | ||||
|                 this.notification.name = `My ${ucfirst(to)} Alert (1)` | ||||
|             } | ||||
|         }, | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.modal = new Modal(this.$refs.modal) | ||||
| 
 | ||||
|  | @ -437,35 +494,5 @@ export default { | |||
|         }, | ||||
| 
 | ||||
|     }, | ||||
|     computed: { | ||||
|         telegramGetUpdatesURL() { | ||||
|             let token = "<YOUR BOT TOKEN HERE>" | ||||
| 
 | ||||
|             if (this.notification.telegramBotToken) { | ||||
|                 token = this.notification.telegramBotToken; | ||||
|             } | ||||
| 
 | ||||
|             return `https://api.telegram.org/bot${token}/getUpdates`; | ||||
|         }, | ||||
|     }, | ||||
|     watch: { | ||||
|         "notification.type"(to, from) { | ||||
|             let oldName; | ||||
| 
 | ||||
|             if (from) { | ||||
|                 oldName =  `My ${ucfirst(from)} Alert (1)`; | ||||
|             } else { | ||||
|                 oldName = ""; | ||||
|             } | ||||
| 
 | ||||
|             if (! this.notification.name || this.notification.name === oldName) { | ||||
|                 this.notification.name = `My ${ucfirst(to)} Alert (1)` | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -5,34 +5,42 @@ | |||
| <script> | ||||
| export default { | ||||
|     props: { | ||||
|         status: Number | ||||
|         status: Number, | ||||
|     }, | ||||
| 
 | ||||
|     computed: { | ||||
|         color() { | ||||
|             if (this.status === 0) { | ||||
|                 return "danger" | ||||
|             } else if (this.status === 1) { | ||||
|                 return "primary" | ||||
|             } else if (this.status === 2) { | ||||
|                 return "warning" | ||||
|             } else { | ||||
|                 return "secondary" | ||||
|             } | ||||
| 
 | ||||
|             if (this.status === 1) { | ||||
|                 return "primary" | ||||
|             } | ||||
| 
 | ||||
|             if (this.status === 2) { | ||||
|                 return "warning" | ||||
|             } | ||||
| 
 | ||||
|             return "secondary" | ||||
|         }, | ||||
| 
 | ||||
|         text() { | ||||
|             if (this.status === 0) { | ||||
|                 return "Down" | ||||
|             } else if (this.status === 1) { | ||||
|                 return "Up" | ||||
|             } else if (this.status === 2) { | ||||
|                 return "Pending" | ||||
|             } else { | ||||
|                 return "Unknown" | ||||
|             } | ||||
| 
 | ||||
|             if (this.status === 1) { | ||||
|                 return "Up" | ||||
|             } | ||||
| 
 | ||||
|             if (this.status === 2) { | ||||
|                 return "Pending" | ||||
|             } | ||||
| 
 | ||||
|             return "Unknown" | ||||
|         }, | ||||
|     } | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,10 +5,10 @@ | |||
| <script> | ||||
| export default { | ||||
|     props: { | ||||
|         monitor : Object, | ||||
|         monitor: Object, | ||||
|         type: String, | ||||
|         pill: { | ||||
|             Boolean, | ||||
|             type: Boolean, | ||||
|             default: false, | ||||
|         }, | ||||
|     }, | ||||
|  | @ -20,44 +20,44 @@ export default { | |||
| 
 | ||||
|             if (this.$root.uptimeList[key] !== undefined) { | ||||
|                 return Math.round(this.$root.uptimeList[key] * 10000) / 100 + "%"; | ||||
|             } else { | ||||
|                 return "N/A" | ||||
|             } | ||||
| 
 | ||||
|             return "N/A" | ||||
|         }, | ||||
| 
 | ||||
|         color() { | ||||
|             if (this.lastHeartBeat.status === 0) { | ||||
|                 return "danger" | ||||
|             } else if (this.lastHeartBeat.status === 1) { | ||||
|                 return "primary" | ||||
|             } else if (this.lastHeartBeat.status === 2) { | ||||
|                 return "warning" | ||||
|             } else { | ||||
|                 return "secondary" | ||||
|             } | ||||
| 
 | ||||
|             if (this.lastHeartBeat.status === 1) { | ||||
|                 return "primary" | ||||
|             } | ||||
| 
 | ||||
|             if (this.lastHeartBeat.status === 2) { | ||||
|                 return "warning" | ||||
|             } | ||||
| 
 | ||||
|             return "secondary" | ||||
|         }, | ||||
| 
 | ||||
|         lastHeartBeat() { | ||||
|             if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) { | ||||
|                 return this.$root.lastHeartbeatList[this.monitor.id] | ||||
|             } else { | ||||
|                 return { status: -1 } | ||||
|             } | ||||
| 
 | ||||
|             return { | ||||
|                 status: -1, | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         className() { | ||||
|             if (this.pill) { | ||||
|                 return `badge rounded-pill bg-${this.color}`; | ||||
|             } else { | ||||
|                 return ""; | ||||
|             } | ||||
| 
 | ||||
|             return ""; | ||||
|         }, | ||||
| 
 | ||||
|     }, | ||||
| 
 | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
							
								
								
									
										10
									
								
								src/icon.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/icon.js
									
									
									
									
									
								
							|  | @ -1,12 +1,10 @@ | |||
| import { library } from '@fortawesome/fontawesome-svg-core' | ||||
| import { library } from "@fortawesome/fontawesome-svg-core" | ||||
| import { faCog, faEdit, faList, faPause, faPlay, faPlus, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" | ||||
| //import { fa } from '@fortawesome/free-regular-svg-icons'
 | ||||
| import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' | ||||
| import { faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList } from '@fortawesome/free-solid-svg-icons' | ||||
| import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" | ||||
| 
 | ||||
| // Add Free Font Awesome Icons here
 | ||||
| // https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
 | ||||
| library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList) | ||||
| 
 | ||||
| export { | ||||
|     FontAwesomeIcon | ||||
| } | ||||
| export { FontAwesomeIcon } | ||||
|  |  | |||
|  | @ -3,11 +3,5 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
| 
 | ||||
| } | ||||
| export default {} | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,28 +1,35 @@ | |||
| <template> | ||||
| 
 | ||||
|     <div class="lost-connection" v-if="! $root.socket.connected && ! $root.socket.firstConnect"> | ||||
|     <div v-if="! $root.socket.connected && ! $root.socket.firstConnect" class="lost-connection"> | ||||
|         <div class="container-fluid"> | ||||
|             Lost connection to the socket server. Reconnecting... | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- Desktop header --> | ||||
|     <header class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom" v-if="! $root.isMobile"> | ||||
|     <header v-if="! $root.isMobile" class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom"> | ||||
|         <router-link to="/dashboard" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none"> | ||||
|             <object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg" alt="Logo"></object> | ||||
|             <object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg" alt="Logo" /> | ||||
|             <span class="fs-4 title">Uptime Kuma</span> | ||||
|         </router-link> | ||||
| 
 | ||||
|         <ul class="nav nav-pills" > | ||||
|             <li class="nav-item"><router-link to="/dashboard" class="nav-link"><font-awesome-icon icon="tachometer-alt" /> Dashboard</router-link></li> | ||||
|             <li class="nav-item"><router-link to="/settings" class="nav-link"><font-awesome-icon icon="cog" /> Settings</router-link></li> | ||||
|         <ul class="nav nav-pills"> | ||||
|             <li class="nav-item"> | ||||
|                 <router-link to="/dashboard" class="nav-link"> | ||||
|                     <font-awesome-icon icon="tachometer-alt" /> Dashboard | ||||
|                 </router-link> | ||||
|             </li> | ||||
|             <li class="nav-item"> | ||||
|                 <router-link to="/settings" class="nav-link"> | ||||
|                     <font-awesome-icon icon="cog" /> Settings | ||||
|                 </router-link> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </header> | ||||
| 
 | ||||
|     <!-- Mobile header --> | ||||
|     <header class="d-flex flex-wrap justify-content-center mt-3 mb-3" v-else> | ||||
|     <header v-else class="d-flex flex-wrap justify-content-center mt-3 mb-3"> | ||||
|         <router-link to="/dashboard" class="d-flex align-items-center  text-dark text-decoration-none"> | ||||
|             <object class="bi" width="40" height="40" data="/icon.svg"></object> | ||||
|             <object class="bi" width="40" height="40" data="/icon.svg" /> | ||||
|             <span class="fs-4 title ms-2">Uptime Kuma</span> | ||||
|         </router-link> | ||||
|     </header> | ||||
|  | @ -42,9 +49,8 @@ | |||
|     </footer> | ||||
| 
 | ||||
|     <!-- Mobile Only --> | ||||
|     <div style="width: 100%;height: 60px;" v-if="$root.isMobile"></div> | ||||
|     <nav class="bottom-nav" v-if="$root.isMobile"> | ||||
| 
 | ||||
|     <div v-if="$root.isMobile" style="width: 100%;height: 60px;" /> | ||||
|     <nav v-if="$root.isMobile" class="bottom-nav"> | ||||
|         <router-link to="/dashboard" class="nav-link" @click="$root.cancelActiveList"> | ||||
|             <div><font-awesome-icon icon="tachometer-alt" /></div> | ||||
|             Dashboard | ||||
|  | @ -64,7 +70,6 @@ | |||
|             <div><font-awesome-icon icon="cog" /></div> | ||||
|             Settings | ||||
|         </router-link> | ||||
| 
 | ||||
|     </nav> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -73,23 +78,19 @@ import Login from "../components/Login.vue"; | |||
| 
 | ||||
| export default { | ||||
|     components: { | ||||
|         Login | ||||
|         Login, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
| 
 | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
| 
 | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.init(); | ||||
|         return {} | ||||
|     }, | ||||
|     computed: {}, | ||||
|     watch: { | ||||
|         $route (to, from) { | ||||
|             this.init(); | ||||
|         } | ||||
|         }, | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.init(); | ||||
|     }, | ||||
|     methods: { | ||||
|         init() { | ||||
|  | @ -98,7 +99,7 @@ export default { | |||
|             } | ||||
|         }, | ||||
| 
 | ||||
|     } | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  | @ -154,10 +155,6 @@ export default { | |||
|     color: white; | ||||
| } | ||||
| 
 | ||||
| main { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| footer { | ||||
|     color: #AAA; | ||||
|     font-size: 13px; | ||||
|  |  | |||
							
								
								
									
										58
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								src/main.js
									
									
									
									
									
								
							|  | @ -1,59 +1,58 @@ | |||
| import {createApp, h} from "vue"; | ||||
| import {createRouter, createWebHistory} from 'vue-router' | ||||
| 
 | ||||
| import App from './App.vue' | ||||
| import Layout from './layouts/Layout.vue' | ||||
| import EmptyLayout from './layouts/EmptyLayout.vue' | ||||
| import Settings from "./pages/Settings.vue"; | ||||
| import "bootstrap"; | ||||
| import { createApp, h } from "vue"; | ||||
| import { createRouter, createWebHistory } from "vue-router"; | ||||
| import Toast from "vue-toastification"; | ||||
| import "vue-toastification/dist/index.css"; | ||||
| import App from "./App.vue"; | ||||
| import "./assets/app.scss"; | ||||
| import { FontAwesomeIcon } from "./icon.js"; | ||||
| import EmptyLayout from "./layouts/EmptyLayout.vue"; | ||||
| import Layout from "./layouts/Layout.vue"; | ||||
| import socket from "./mixins/socket"; | ||||
| import Dashboard from "./pages/Dashboard.vue"; | ||||
| import DashboardHome from "./pages/DashboardHome.vue"; | ||||
| import Details from "./pages/Details.vue"; | ||||
| import socket from "./mixins/socket" | ||||
| import "./assets/app.scss" | ||||
| import EditMonitor from "./pages/EditMonitor.vue"; | ||||
| import Toast from "vue-toastification"; | ||||
| import "vue-toastification/dist/index.css"; | ||||
| import "bootstrap" | ||||
| import Settings from "./pages/Settings.vue"; | ||||
| import Setup from "./pages/Setup.vue"; | ||||
| import {FontAwesomeIcon} from "./icon.js" | ||||
| 
 | ||||
| const routes = [ | ||||
|     { | ||||
|         path: '/', | ||||
|         path: "/", | ||||
|         component: Layout, | ||||
|         children: [ | ||||
|             { | ||||
|                 name: "root", | ||||
|                 path: '', | ||||
|                 path: "", | ||||
|                 component: Dashboard, | ||||
|                 children: [ | ||||
|                     { | ||||
|                         name: "DashboardHome", | ||||
|                         path: '/dashboard', | ||||
|                         path: "/dashboard", | ||||
|                         component: DashboardHome, | ||||
|                         children: [ | ||||
|                             { | ||||
|                                 path: '/dashboard/:id', | ||||
|                                 path: "/dashboard/:id", | ||||
|                                 component: EmptyLayout, | ||||
|                                 children: [ | ||||
|                                     { | ||||
|                                         path: '', | ||||
|                                         path: "", | ||||
|                                         component: Details, | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         path: '/edit/:id', | ||||
|                                         path: "/edit/:id", | ||||
|                                         component: EditMonitor, | ||||
|                                     }, | ||||
|                                 ] | ||||
|                                 ], | ||||
|                             }, | ||||
|                             { | ||||
|                                 path: '/add', | ||||
|                                 path: "/add", | ||||
|                                 component: EditMonitor, | ||||
|                             }, | ||||
|                         ] | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         path: '/settings', | ||||
|                         path: "/settings", | ||||
|                         component: Settings, | ||||
|                     }, | ||||
|                 ], | ||||
|  | @ -63,13 +62,13 @@ const routes = [ | |||
| 
 | ||||
|     }, | ||||
|     { | ||||
|         path: '/setup', | ||||
|         path: "/setup", | ||||
|         component: Setup, | ||||
|     }, | ||||
| ] | ||||
| 
 | ||||
| const router = createRouter({ | ||||
|     linkActiveClass: 'active', | ||||
|     linkActiveClass: "active", | ||||
|     history: createWebHistory(), | ||||
|     routes, | ||||
| }) | ||||
|  | @ -78,18 +77,17 @@ const app = createApp({ | |||
|     mixins: [ | ||||
|         socket, | ||||
|     ], | ||||
|     render: ()=>h(App) | ||||
|     render: () => h(App), | ||||
| }) | ||||
| 
 | ||||
| app.use(router) | ||||
| 
 | ||||
| const options = { | ||||
|     position: "bottom-right" | ||||
|     position: "bottom-right", | ||||
| }; | ||||
| 
 | ||||
| app.use(Toast, options); | ||||
| 
 | ||||
| app.component('font-awesome-icon', FontAwesomeIcon) | ||||
| 
 | ||||
| app.mount('#app') | ||||
| app.component("FontAwesomeIcon", FontAwesomeIcon) | ||||
| 
 | ||||
| app.mount("#app") | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import {io} from "socket.io-client"; | ||||
| import { useToast } from 'vue-toastification' | ||||
| import dayjs from "dayjs"; | ||||
| import { io } from "socket.io-client"; | ||||
| import { useToast } from "vue-toastification"; | ||||
| const toast = useToast() | ||||
| 
 | ||||
| let socket; | ||||
|  | @ -33,7 +33,7 @@ export default { | |||
|     }, | ||||
| 
 | ||||
|     created() { | ||||
|         window.addEventListener('resize', this.onResize); | ||||
|         window.addEventListener("resize", this.onResize); | ||||
| 
 | ||||
|         let wsHost; | ||||
|         const env = process.env.NODE_ENV || "production"; | ||||
|  | @ -44,18 +44,18 @@ export default { | |||
|         } | ||||
| 
 | ||||
|         socket = io(wsHost, { | ||||
|             transports: ['websocket'] | ||||
|             transports: ["websocket"], | ||||
|         }); | ||||
| 
 | ||||
|         socket.on("connect_error", (err) => { | ||||
|             console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`); | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('info', (info) => { | ||||
|         socket.on("info", (info) => { | ||||
|             this.info = info; | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('setup', (monitorID, data) => { | ||||
|         socket.on("setup", (monitorID, data) => { | ||||
|             this.$router.push("/setup") | ||||
|         }); | ||||
| 
 | ||||
|  | @ -73,11 +73,11 @@ export default { | |||
|             this.monitorList = data; | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('notificationList', (data) => { | ||||
|         socket.on("notificationList", (data) => { | ||||
|             this.notificationList = data; | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('heartbeat', (data) => { | ||||
|         socket.on("heartbeat", (data) => { | ||||
|             if (! (data.monitorID in this.heartbeatList)) { | ||||
|                 this.heartbeatList[data.monitorID] = []; | ||||
|             } | ||||
|  | @ -100,7 +100,6 @@ export default { | |||
|                     toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`); | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|                 if (! (data.monitorID in this.importantHeartbeatList)) { | ||||
|                     this.importantHeartbeatList[data.monitorID] = []; | ||||
|                 } | ||||
|  | @ -109,7 +108,7 @@ export default { | |||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('heartbeatList', (monitorID, data) => { | ||||
|         socket.on("heartbeatList", (monitorID, data) => { | ||||
|             if (! (monitorID in this.heartbeatList)) { | ||||
|                 this.heartbeatList[monitorID] = data; | ||||
|             } else { | ||||
|  | @ -117,19 +116,19 @@ export default { | |||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('avgPing', (monitorID, data) => { | ||||
|         socket.on("avgPing", (monitorID, data) => { | ||||
|             this.avgPingList[monitorID] = data | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('uptime', (monitorID, type, data) => { | ||||
|         socket.on("uptime", (monitorID, type, data) => { | ||||
|             this.uptimeList[`${monitorID}_${type}`] = data | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('certInfo', (monitorID, data) => { | ||||
|         socket.on("certInfo", (monitorID, data) => { | ||||
|             this.certInfoList[monitorID] = JSON.parse(data) | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('importantHeartbeatList', (monitorID, data) => { | ||||
|         socket.on("importantHeartbeatList", (monitorID, data) => { | ||||
|             if (! (monitorID in this.importantHeartbeatList)) { | ||||
|                 this.importantHeartbeatList[monitorID] = data; | ||||
|             } else { | ||||
|  | @ -137,12 +136,12 @@ export default { | |||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('disconnect', () => { | ||||
|         socket.on("disconnect", () => { | ||||
|             console.log("disconnect") | ||||
|             this.socket.connected = false; | ||||
|         }); | ||||
| 
 | ||||
|         socket.on('connect', () => { | ||||
|         socket.on("connect", () => { | ||||
|             console.log("connect") | ||||
|             this.socket.connectCount++; | ||||
|             this.socket.connected = true; | ||||
|  | @ -201,7 +200,7 @@ export default { | |||
|                     this.loggedIn = true; | ||||
| 
 | ||||
|                     // Trigger Chrome Save Password
 | ||||
|                     history.pushState({}, '') | ||||
|                     history.pushState({}, "") | ||||
|                 } | ||||
| 
 | ||||
|                 callback(res) | ||||
|  | @ -254,10 +253,9 @@ export default { | |||
| 
 | ||||
|             if (this.userTimezone === "auto") { | ||||
|                 return dayjs.tz.guess() | ||||
|             } else { | ||||
|                 return this.userTimezone | ||||
|             } | ||||
| 
 | ||||
|             return this.userTimezone | ||||
|         }, | ||||
| 
 | ||||
|         lastHeartbeatList() { | ||||
|  | @ -276,7 +274,7 @@ export default { | |||
| 
 | ||||
|             let unknown = { | ||||
|                 text: "Unknown", | ||||
|                 color: "secondary" | ||||
|                 color: "secondary", | ||||
|             } | ||||
| 
 | ||||
|             for (let monitorID in this.lastHeartbeatList) { | ||||
|  | @ -287,17 +285,17 @@ export default { | |||
|                 } else if (lastHeartBeat.status === 1) { | ||||
|                     result[monitorID] = { | ||||
|                         text: "Up", | ||||
|                         color: "primary" | ||||
|                         color: "primary", | ||||
|                     }; | ||||
|                 } else if (lastHeartBeat.status === 0) { | ||||
|                     result[monitorID] = { | ||||
|                         text: "Down", | ||||
|                         color: "danger" | ||||
|                         color: "danger", | ||||
|                     }; | ||||
|                 } else if (lastHeartBeat.status === 2) { | ||||
|                     result[monitorID] = { | ||||
|                         text: "Pending", | ||||
|                         color: "warning" | ||||
|                         color: "warning", | ||||
|                     }; | ||||
|                 } else { | ||||
|                     result[monitorID] = unknown; | ||||
|  | @ -305,23 +303,22 @@ export default { | |||
|             } | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
|         }, | ||||
|     }, | ||||
| 
 | ||||
|     watch: { | ||||
| 
 | ||||
|         // Reload the SPA if the server version is changed.
 | ||||
|         "info.version"(to, from) { | ||||
|               if (from && from !== to) { | ||||
|                   window.location.reload() | ||||
|               } | ||||
|             if (from && from !== to) { | ||||
|                 window.location.reload() | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         remember() { | ||||
|             localStorage.remember = (this.remember) ? "1" : "0" | ||||
|         } | ||||
|         }, | ||||
| 
 | ||||
|     } | ||||
|     }, | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,36 +1,33 @@ | |||
| <template> | ||||
| 
 | ||||
|     <div class="container-fluid"> | ||||
|         <div class="row"> | ||||
|             <div class="col-12 col-md-5 col-xl-4"> | ||||
|                 <div v-if="! $root.isMobile"> | ||||
|                     <router-link to="/add" class="btn btn-primary"><font-awesome-icon icon="plus" /> Add New Monitor</router-link> | ||||
|                     <router-link to="/add" class="btn btn-primary"> | ||||
|                         <font-awesome-icon icon="plus" /> Add New Monitor | ||||
|                     </router-link> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <div class="shadow-box list mb-4" v-if="showList"> | ||||
| 
 | ||||
|                     <div class="text-center mt-3" v-if="Object.keys($root.monitorList).length === 0"> | ||||
|                         No Monitors, please <router-link to="/add">add one</router-link>. | ||||
|                 <div v-if="showList" class="shadow-box list mb-4"> | ||||
|                     <div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3"> | ||||
|                         No Monitors, please <router-link to="/add"> | ||||
|                             add one | ||||
|                         </router-link>. | ||||
|                     </div> | ||||
| 
 | ||||
|                     <router-link :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" v-for="(item, index) in sortedMonitorList" @click="$root.cancelActiveList" :key="index"> | ||||
| 
 | ||||
|                     <router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" @click="$root.cancelActiveList"> | ||||
|                         <div class="row"> | ||||
|                         	<div class="col-6 col-md-8 small-padding"> | ||||
| 
 | ||||
|                             <div class="col-6 col-md-8 small-padding"> | ||||
|                                 <div class="info"> | ||||
|                                     <Uptime :monitor="item" type="24" :pill="true" /> | ||||
|                                     {{ item.name }} | ||||
|                                 </div> | ||||
| 
 | ||||
|                             </div> | ||||
|                         	<div class="col-6 col-md-4"> | ||||
|                             <div class="col-6 col-md-4"> | ||||
|                                 <HeartbeatBar size="small" :monitor-id="item.id" /> | ||||
|                             </div> | ||||
|                         </div> | ||||
| 
 | ||||
|                     </router-link> | ||||
| 
 | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col-12 col-md-7 col-xl-8"> | ||||
|  | @ -38,7 +35,6 @@ | |||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
|  | @ -49,12 +45,10 @@ import Uptime from "../components/Uptime.vue"; | |||
| export default { | ||||
|     components: { | ||||
|         Uptime, | ||||
|         HeartbeatBar | ||||
|         HeartbeatBar, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
| 
 | ||||
|         } | ||||
|         return {} | ||||
|     }, | ||||
|     computed: { | ||||
|         sortedMonitorList() { | ||||
|  | @ -94,8 +88,8 @@ export default { | |||
|     methods: { | ||||
|         monitorURL(id) { | ||||
|             return "/dashboard/" + id; | ||||
|         } | ||||
|     } | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,15 +1,16 @@ | |||
| <template> | ||||
| 
 | ||||
|     <div v-if="$route.name === 'DashboardHome'"> | ||||
|         <h1 class="mb-3">Quick Stats</h1> | ||||
|         <h1 class="mb-3"> | ||||
|             Quick Stats | ||||
|         </h1> | ||||
| 
 | ||||
|         <div class="shadow-box big-padding text-center"> | ||||
|             <div class="row"> | ||||
|             	<div class="col"> | ||||
|                 <div class="col"> | ||||
|                     <h3>Up</h3> | ||||
|                     <span class="num">{{ stats.up }}</span> | ||||
|                 </div> | ||||
|             	<div class="col"> | ||||
|                 <div class="col"> | ||||
|                     <h3>Down</h3> | ||||
|                     <span class="num text-danger">{{ stats.down }}</span> | ||||
|                 </div> | ||||
|  | @ -22,16 +23,16 @@ | |||
|                     <span class="num text-secondary">{{ stats.pause }}</span> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row" v-if="false"> | ||||
|             <div v-if="false" class="row"> | ||||
|                 <div class="col-3"> | ||||
|                     <h3>Uptime</h3> | ||||
|                     <p>(24-hour)</p> | ||||
|                     <span class="num"></span> | ||||
|                     <span class="num" /> | ||||
|                 </div> | ||||
|                 <div class="col-3"> | ||||
|                     <h3>Uptime</h3> | ||||
|                     <p>(30-day)</p> | ||||
|                     <span class="num"></span> | ||||
|                     <span class="num" /> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | @ -39,32 +40,35 @@ | |||
|         <div class="shadow-box" style="margin-top: 25px;"> | ||||
|             <table class="table table-borderless table-hover"> | ||||
|                 <thead> | ||||
|                 <tr> | ||||
|                     <th>Name</th> | ||||
|                     <th>Status</th> | ||||
|                     <th>DateTime</th> | ||||
|                     <th>Message</th> | ||||
|                 </tr> | ||||
|                     <tr> | ||||
|                         <th>Name</th> | ||||
|                         <th>Status</th> | ||||
|                         <th>DateTime</th> | ||||
|                         <th>Message</th> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                 <tr v-for="(beat, index) in displayedRecords" :key="index"> | ||||
|                     <td>{{ beat.name }}</td> | ||||
|                     <td><Status :status="beat.status" /></td> | ||||
|                     <td><Datetime :value="beat.time" /></td> | ||||
|                     <td>{{ beat.msg }}</td> | ||||
|                 </tr> | ||||
|                     <tr v-for="(beat, index) in displayedRecords" :key="index"> | ||||
|                         <td>{{ beat.name }}</td> | ||||
|                         <td><Status :status="beat.status" /></td> | ||||
|                         <td><Datetime :value="beat.time" /></td> | ||||
|                         <td>{{ beat.msg }}</td> | ||||
|                     </tr> | ||||
| 
 | ||||
|                 <tr v-if="importantHeartBeatList.length === 0"> | ||||
|                     <td colspan="4">No important events</td> | ||||
|                 </tr> | ||||
|                     <tr v-if="importantHeartBeatList.length === 0"> | ||||
|                         <td colspan="4"> | ||||
|                             No important events | ||||
|                         </td> | ||||
|                     </tr> | ||||
|                 </tbody> | ||||
|             </table> | ||||
| 
 | ||||
|             <div class="d-flex justify-content-center kuma_pagination"> | ||||
|                 <pagination | ||||
|                     v-model="page" | ||||
|                     :records=importantHeartBeatList.length | ||||
|                     :per-page="perPage" /> | ||||
|                     :records="importantHeartBeatList.length" | ||||
|                     :per-page="perPage" | ||||
|                 /> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | @ -111,7 +115,7 @@ export default { | |||
|                     } else if (beat.status === 0) { | ||||
|                         result.down++; | ||||
|                     } else if (beat.status === 2) { | ||||
|                         result.up++;                 | ||||
|                         result.up++; | ||||
|                     } else { | ||||
|                         result.unknown++; | ||||
|                     } | ||||
|  | @ -127,7 +131,7 @@ export default { | |||
|             let result = []; | ||||
| 
 | ||||
|             for (let monitorID in this.$root.importantHeartbeatList) { | ||||
|                 let list =  this.$root.importantHeartbeatList[monitorID] | ||||
|                 let list = this.$root.importantHeartbeatList[monitorID] | ||||
|                 result = result.concat(list); | ||||
|             } | ||||
| 
 | ||||
|  | @ -142,11 +146,13 @@ export default { | |||
|             result.sort((a, b) => { | ||||
|                 if (a.time > b.time) { | ||||
|                     return -1; | ||||
|                 } else if (a.time < b.time) { | ||||
|                     return 1; | ||||
|                 } else { | ||||
|                     return 0; | ||||
|                 } | ||||
| 
 | ||||
|                 if (a.time < b.time) { | ||||
|                     return 1; | ||||
|                 } | ||||
| 
 | ||||
|                 return 0; | ||||
|             }); | ||||
| 
 | ||||
|             this.heartBeatList = result; | ||||
|  | @ -159,7 +165,7 @@ export default { | |||
|             const endIndex = startIndex + this.perPage; | ||||
|             return this.heartBeatList.slice(startIndex, endIndex); | ||||
|         }, | ||||
|     } | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,20 +1,28 @@ | |||
| <template> | ||||
|     <h1> {{ monitor.name }}</h1> | ||||
|     <p class="url"> | ||||
|         <a :href="monitor.url" target="_blank" v-if="monitor.type === 'http' || monitor.type === 'keyword' ">{{ monitor.url }}</a> | ||||
|         <a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a> | ||||
|         <span v-if="monitor.type === 'port'">TCP Ping {{ monitor.hostname }}:{{ monitor.port }}</span> | ||||
|         <span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span> | ||||
|         <span v-if="monitor.type === 'keyword'"> | ||||
|             <br /> | ||||
|             <br> | ||||
|             <span>Keyword:</span> <span style="color: black">{{ monitor.keyword }}</span> | ||||
|         </span> | ||||
|     </p> | ||||
| 
 | ||||
|     <div class="functions"> | ||||
|         <button class="btn btn-light" @click="pauseDialog" v-if="monitor.active"><font-awesome-icon icon="pause" /> Pause</button> | ||||
|         <button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active"><font-awesome-icon icon="pause" /> Resume</button> | ||||
|         <router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary"><font-awesome-icon icon="edit" /> Edit</router-link> | ||||
|         <button class="btn btn-danger" @click="deleteDialog"><font-awesome-icon icon="trash" /> Delete</button> | ||||
|         <button v-if="monitor.active" class="btn btn-light" @click="pauseDialog"> | ||||
|             <font-awesome-icon icon="pause" /> Pause | ||||
|         </button> | ||||
|         <button v-if="! monitor.active" class="btn btn-primary" @click="resumeMonitor"> | ||||
|             <font-awesome-icon icon="pause" /> Resume | ||||
|         </button> | ||||
|         <router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary"> | ||||
|             <font-awesome-icon icon="edit" /> Edit | ||||
|         </router-link> | ||||
|         <button class="btn btn-danger" @click="deleteDialog"> | ||||
|             <font-awesome-icon icon="trash" /> Delete | ||||
|         </button> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="shadow-box"> | ||||
|  | @ -37,7 +45,7 @@ | |||
|                 <span class="num"><CountUp :value="ping" /></span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|                 <h4>Avg.{{ pingTitle }}</h4> | ||||
|                 <h4>Avg. {{ pingTitle }}</h4> | ||||
|                 <p>(24-hour)</p> | ||||
|                 <span class="num"><CountUp :value="avgPing" /></span> | ||||
|             </div> | ||||
|  | @ -52,40 +60,50 @@ | |||
|                 <span class="num"><Uptime :monitor="monitor" type="720" /></span> | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="col" v-if="certInfo"> | ||||
|                 <h4>CertExp.</h4> | ||||
|             <div v-if="certInfo" class="col"> | ||||
|                 <h4>Cert Exp.</h4> | ||||
|                 <p>(<Datetime :value="certInfo.validTo" date-only />)</p> | ||||
|                 <span class="num" > | ||||
|                     <a href="#"  @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{certInfo.daysRemaining}} days</a> | ||||
|                 <span class="num"> | ||||
|                     <a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ certInfo.daysRemaining }} days</a> | ||||
|                 </span> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="shadow-box big-padding text-center" v-if="showCertInfoBox"> | ||||
|     <div v-if="showCertInfoBox" class="shadow-box big-padding text-center"> | ||||
|         <div class="row"> | ||||
|             <div class="col"> | ||||
|                 <h4>Certificate Info</h4> | ||||
|                 <table class="text-start"> | ||||
|                     <tbody> | ||||
|                         <tr class="my-3"> | ||||
|                             <td class="px-3">Valid: </td> | ||||
|                             <td class="px-3"> | ||||
|                                 Valid: | ||||
|                             </td> | ||||
|                             <td>{{ certInfo.valid }}</td> | ||||
|                         </tr> | ||||
|                         <tr class="my-3"> | ||||
|                             <td class="px-3">Valid To: </td> | ||||
|                             <td class="px-3"> | ||||
|                                 Valid To: | ||||
|                             </td> | ||||
|                             <td><Datetime :value="certInfo.validTo" /></td> | ||||
|                         </tr> | ||||
|                         <tr class="my-3"> | ||||
|                             <td class="px-3">Days Remaining: </td> | ||||
|                             <td class="px-3"> | ||||
|                                 Days Remaining: | ||||
|                             </td> | ||||
|                             <td>{{ certInfo.daysRemaining }}</td> | ||||
|                         </tr> | ||||
|                         <tr class="my-3"> | ||||
|                             <td class="px-3">Issuer: </td> | ||||
|                             <td class="px-3"> | ||||
|                                 Issuer: | ||||
|                             </td> | ||||
|                             <td>{{ certInfo.issuer }}</td> | ||||
|                         </tr> | ||||
|                         <tr class="my-3"> | ||||
|                             <td class="px-3">Fingerprint: </td> | ||||
|                             <td class="px-3"> | ||||
|                                 Fingerprint: | ||||
|                             </td> | ||||
|                             <td>{{ certInfo.fingerprint }}</td> | ||||
|                         </tr> | ||||
|                     </tbody> | ||||
|  | @ -111,7 +129,9 @@ | |||
|                 </tr> | ||||
| 
 | ||||
|                 <tr v-if="importantHeartBeatList.length === 0"> | ||||
|                     <td colspan="3">No important events</td> | ||||
|                     <td colspan="3"> | ||||
|                         No important events | ||||
|                     </td> | ||||
|                 </tr> | ||||
|             </tbody> | ||||
|         </table> | ||||
|  | @ -119,8 +139,9 @@ | |||
|         <div class="d-flex justify-content-center kuma_pagination"> | ||||
|             <pagination | ||||
|                 v-model="page" | ||||
|                 :records=importantHeartBeatList.length | ||||
|                 :per-page="perPage" /> | ||||
|                 :records="importantHeartBeatList.length" | ||||
|                 :per-page="perPage" | ||||
|             /> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|  | @ -128,13 +149,13 @@ | |||
|         Are you sure want to pause? | ||||
|     </Confirm> | ||||
| 
 | ||||
|     <Confirm ref="confirmDelete" btnStyle="btn-danger" @yes="deleteMonitor"> | ||||
|     <Confirm ref="confirmDelete" btn-style="btn-danger" @yes="deleteMonitor"> | ||||
|         Are you sure want to delete this monitor? | ||||
|     </Confirm> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { useToast } from 'vue-toastification' | ||||
| import { useToast } from "vue-toastification" | ||||
| const toast = useToast() | ||||
| import Confirm from "../components/Confirm.vue"; | ||||
| import HeartbeatBar from "../components/HeartbeatBar.vue"; | ||||
|  | @ -153,9 +174,6 @@ export default { | |||
|         Confirm, | ||||
|         Status, | ||||
|         Pagination, | ||||
|     }, | ||||
|     mounted() { | ||||
| 
 | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|  | @ -170,9 +188,9 @@ export default { | |||
|         pingTitle() { | ||||
|             if (this.monitor.type === "http") { | ||||
|                 return "Response" | ||||
|             } else { | ||||
|                 return "Ping" | ||||
|             } | ||||
| 
 | ||||
|             return "Ping" | ||||
|         }, | ||||
| 
 | ||||
|         monitor() { | ||||
|  | @ -183,50 +201,52 @@ export default { | |||
|         lastHeartBeat() { | ||||
|             if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) { | ||||
|                 return this.$root.lastHeartbeatList[this.monitor.id] | ||||
|             } else { | ||||
|                 return { status: -1 } | ||||
|             } | ||||
| 
 | ||||
|             return { | ||||
|                 status: -1, | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         ping() { | ||||
|             if (this.lastHeartBeat.ping || this.lastHeartBeat.ping === 0) { | ||||
|                 return this.lastHeartBeat.ping; | ||||
|             } else { | ||||
|                 return "N/A" | ||||
|             } | ||||
| 
 | ||||
|             return "N/A" | ||||
|         }, | ||||
| 
 | ||||
|         avgPing() { | ||||
|             if (this.$root.avgPingList[this.monitor.id] || this.$root.avgPingList[this.monitor.id] === 0) { | ||||
|                 return this.$root.avgPingList[this.monitor.id]; | ||||
|             } else { | ||||
|                 return "N/A" | ||||
|             } | ||||
| 
 | ||||
|             return "N/A" | ||||
|         }, | ||||
| 
 | ||||
|         importantHeartBeatList() { | ||||
|             if (this.$root.importantHeartbeatList[this.monitor.id]) { | ||||
|                 this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id]; | ||||
|                 return this.$root.importantHeartbeatList[this.monitor.id] | ||||
|             } else { | ||||
|                 return []; | ||||
|             } | ||||
| 
 | ||||
|             return []; | ||||
|         }, | ||||
| 
 | ||||
|         status() { | ||||
|             if (this.$root.statusList[this.monitor.id]) { | ||||
|                 return this.$root.statusList[this.monitor.id] | ||||
|             } else { | ||||
|                 return { } | ||||
|             } | ||||
| 
 | ||||
|             return { } | ||||
|         }, | ||||
| 
 | ||||
|         certInfo() { | ||||
|             if (this.$root.certInfoList[this.monitor.id]) { | ||||
|                 return this.$root.certInfoList[this.monitor.id] | ||||
|             } else { | ||||
|                 return null | ||||
|             } | ||||
| 
 | ||||
|             return null | ||||
|         }, | ||||
| 
 | ||||
|         showCertInfoBox() { | ||||
|  | @ -238,6 +258,9 @@ export default { | |||
|             const endIndex = startIndex + this.perPage; | ||||
|             return this.heartBeatList.slice(startIndex, endIndex); | ||||
|         }, | ||||
|     }, | ||||
|     mounted() { | ||||
| 
 | ||||
|     }, | ||||
|     methods: { | ||||
|         testNotification() { | ||||
|  | @ -274,9 +297,9 @@ export default { | |||
|                     toast.error(res.msg); | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|         }, | ||||
| 
 | ||||
|     } | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,85 +1,102 @@ | |||
| <template> | ||||
|     <h1 class="mb-3">{{ pageName }}</h1> | ||||
|     <h1 class="mb-3"> | ||||
|         {{ pageName }} | ||||
|     </h1> | ||||
|     <form @submit.prevent="submit"> | ||||
| 
 | ||||
|     <div class="shadow-box"> | ||||
|         <div class="row"> | ||||
|             <div class="col-md-6"> | ||||
|                 <h2>General</h2> | ||||
|         <div class="shadow-box"> | ||||
|             <div class="row"> | ||||
|                 <div class="col-md-6"> | ||||
|                     <h2>General</h2> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="type" class="form-label">Monitor Type</label> | ||||
|                         <select class="form-select" aria-label="Default select example" id="type" v-model="monitor.type"> | ||||
|                             <option value="http">HTTP(s)</option> | ||||
|                             <option value="port">TCP Port</option> | ||||
|                             <option value="ping">Ping</option> | ||||
|                             <option value="keyword">HTTP(s) - Keyword</option> | ||||
|                         <select id="type" v-model="monitor.type" class="form-select" aria-label="Default select example"> | ||||
|                             <option value="http"> | ||||
|                                 HTTP(s) | ||||
|                             </option> | ||||
|                             <option value="port"> | ||||
|                                 TCP Port | ||||
|                             </option> | ||||
|                             <option value="ping"> | ||||
|                                 Ping | ||||
|                             </option> | ||||
|                             <option value="keyword"> | ||||
|                                 HTTP(s) - Keyword | ||||
|                             </option> | ||||
|                         </select> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="name" class="form-label">Friendly Name</label> | ||||
|                         <input type="text" class="form-control" id="name" v-model="monitor.name" required> | ||||
|                         <input id="name" v-model="monitor.name" type="text" class="form-control" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3" v-if="monitor.type === 'http' || monitor.type === 'keyword' "> | ||||
|                     <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="mb-3"> | ||||
|                         <label for="url" class="form-label">URL</label> | ||||
|                         <input type="url" class="form-control" id="url" v-model="monitor.url" pattern="https?://.+" required> | ||||
|                         <input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3" v-if="monitor.type === 'keyword' "> | ||||
|                     <div v-if="monitor.type === 'keyword' " class="mb-3"> | ||||
|                         <label for="keyword" class="form-label">Keyword</label> | ||||
|                         <input type="text" class="form-control" id="keyword" v-model="monitor.keyword" required> | ||||
|                         <div class="form-text">Search keyword in plain html or JSON response and it is case-sensitive</div> | ||||
|                         <input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required> | ||||
|                         <div class="form-text"> | ||||
|                             Search keyword in plain html or JSON response and it is case-sensitive | ||||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3" v-if="monitor.type === 'port' || monitor.type === 'ping' "> | ||||
|                     <div v-if="monitor.type === 'port' || monitor.type === 'ping' " class="mb-3"> | ||||
|                         <label for="hostname" class="form-label">Hostname</label> | ||||
|                         <input type="text" class="form-control" id="hostname" v-model="monitor.hostname" required> | ||||
|                         <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3" v-if="monitor.type === 'port' "> | ||||
|                     <div v-if="monitor.type === 'port' " class="mb-3"> | ||||
|                         <label for="port" class="form-label">Port</label> | ||||
|                         <input type="number" class="form-control" id="port" v-model="monitor.port" required min="0" max="65535" step="1"> | ||||
|                         <input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1"> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="interval" class="form-label">Heartbeat Interval (Every {{ monitor.interval }} seconds)</label> | ||||
|                         <input type="number" class="form-control" id="interval" v-model="monitor.interval" required min="20" step="1"> | ||||
|                         <input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1"> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="maxRetries" class="form-label">Retries</label> | ||||
|                         <input type="number" class="form-control" id="maxRetries" v-model="monitor.maxretries" required min="0" step="1"> | ||||
|                         <div class="form-text">Maximum retries before the service is marked as down and a notification is sent</div> | ||||
|                         <input id="maxRetries" v-model="monitor.maxretries" type="number" class="form-control" required min="0" step="1"> | ||||
|                         <div class="form-text"> | ||||
|                             Maximum retries before the service is marked as down and a notification is sent | ||||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div> | ||||
|                         <button class="btn btn-primary" type="submit" :disabled="processing">Save</button> | ||||
|                         <button class="btn btn-primary" type="submit" :disabled="processing"> | ||||
|                             Save | ||||
|                         </button> | ||||
|                     </div> | ||||
| 
 | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="col-md-6"> | ||||
| 
 | ||||
|                 <div class="mt-3" v-if="$root.isMobile"></div> | ||||
| 
 | ||||
|                 <h2>Notifications</h2> | ||||
|                 <p v-if="$root.notificationList.length === 0">Not available, please setup.</p> | ||||
| 
 | ||||
|                 <div class="form-check form-switch mb-3" :key="notification.id" v-for="notification in $root.notificationList"> | ||||
|                     <input class="form-check-input" type="checkbox" :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]"> | ||||
| 
 | ||||
|                     <label class="form-check-label" :for=" 'notification' + notification.id"> | ||||
|                         {{ notification.name }} | ||||
|                         <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a> | ||||
|                     </label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <button class="btn btn-primary me-2" @click="$refs.notificationDialog.show()" type="button">Setup Notification</button> | ||||
|                 <div class="col-md-6"> | ||||
|                     <div v-if="$root.isMobile" class="mt-3" /> | ||||
| 
 | ||||
|                     <h2>Notifications</h2> | ||||
|                     <p v-if="$root.notificationList.length === 0"> | ||||
|                         Not available, please setup. | ||||
|                     </p> | ||||
| 
 | ||||
|                     <div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch mb-3"> | ||||
|                         <input :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]" class="form-check-input" type="checkbox"> | ||||
| 
 | ||||
|                         <label class="form-check-label" :for=" 'notification' + notification.id"> | ||||
|                             {{ notification.name }} | ||||
|                             <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a> | ||||
|                         </label> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()"> | ||||
|                         Setup Notification | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     </form> | ||||
| 
 | ||||
|     <NotificationDialog ref="notificationDialog" /> | ||||
|  | @ -87,15 +104,12 @@ | |||
| 
 | ||||
| <script> | ||||
| import NotificationDialog from "../components/NotificationDialog.vue"; | ||||
| import { useToast } from 'vue-toastification' | ||||
| import { useToast } from "vue-toastification" | ||||
| const toast = useToast() | ||||
| 
 | ||||
| export default { | ||||
|     components: { | ||||
|         NotificationDialog | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.init(); | ||||
|         NotificationDialog, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|  | @ -114,7 +128,15 @@ export default { | |||
|         }, | ||||
|         isEdit() { | ||||
|             return this.$route.path.startsWith("/edit"); | ||||
|         } | ||||
|         }, | ||||
|     }, | ||||
|     watch: { | ||||
|         "$route.fullPath" () { | ||||
|             this.init(); | ||||
|         }, | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.init(); | ||||
|     }, | ||||
|     methods: { | ||||
|         init() { | ||||
|  | @ -161,12 +183,7 @@ export default { | |||
|                     this.$root.toastRes(res) | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         '$route.fullPath' () { | ||||
|             this.init(); | ||||
|         } | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,22 +1,29 @@ | |||
| <template> | ||||
|     <h1 class="mb-3">Settings</h1> | ||||
|     <h1 class="mb-3"> | ||||
|         Settings | ||||
|     </h1> | ||||
| 
 | ||||
|     <div class="shadow-box"> | ||||
|         <div class="row"> | ||||
| 
 | ||||
|             <div class="col-md-6"> | ||||
|                 <h2>General</h2> | ||||
|                 <form class="mb-3" @submit.prevent="saveGeneral"> | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="timezone" class="form-label">Timezone</label> | ||||
|                         <select class="form-select" id="timezone" v-model="$root.userTimezone"> | ||||
|                             <option value="auto">Auto: {{ guessTimezone }}</option> | ||||
|                             <option v-for="(timezone, index) in timezoneList" :value="timezone.value" :key="index">{{ timezone.name }}</option> | ||||
|                         <select id="timezone" v-model="$root.userTimezone" class="form-select"> | ||||
|                             <option value="auto"> | ||||
|                                 Auto: {{ guessTimezone }} | ||||
|                             </option> | ||||
|                             <option v-for="(timezone, index) in timezoneList" :key="index" :value="timezone.value"> | ||||
|                                 {{ timezone.name }} | ||||
|                             </option> | ||||
|                         </select> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div> | ||||
|                         <button class="btn btn-primary" type="submit">Save</button> | ||||
|                         <button class="btn btn-primary" type="submit"> | ||||
|                             Save | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </form> | ||||
| 
 | ||||
|  | @ -24,51 +31,58 @@ | |||
|                 <form class="mb-3" @submit.prevent="savePassword"> | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="current-password" class="form-label">Current Password</label> | ||||
|                         <input type="password" class="form-control" id="current-password" required v-model="password.currentPassword"> | ||||
|                         <input id="current-password" v-model="password.currentPassword" type="password" class="form-control" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="new-password" class="form-label">New Password</label> | ||||
|                         <input type="password" class="form-control" id="new-password" required v-model="password.newPassword"> | ||||
|                         <input id="new-password" v-model="password.newPassword" type="password" class="form-control" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                         <label for="repeat-new-password" class="form-label">Repeat New Password</label> | ||||
|                         <input type="password" class="form-control" :class="{ 'is-invalid' : invalidPassword }" id="repeat-new-password" required v-model="password.repeatNewPassword"> | ||||
|                         <input id="repeat-new-password" v-model="password.repeatNewPassword" type="password" class="form-control" :class="{ 'is-invalid' : invalidPassword }" required> | ||||
|                         <div class="invalid-feedback"> | ||||
|                             The repeat password does not match. | ||||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div> | ||||
|                         <button class="btn btn-primary" type="submit">Update Password</button> | ||||
|                         <button class="btn btn-primary" type="submit"> | ||||
|                             Update Password | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </form> | ||||
| 
 | ||||
|                 <div> | ||||
|                     <button class="btn btn-danger" @click="$root.logout">Logout</button> | ||||
|                     <button class="btn btn-danger" @click="$root.logout"> | ||||
|                         Logout | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="col-md-6"> | ||||
| 
 | ||||
|                 <div class="mt-3" v-if="$root.isMobile"></div> | ||||
|                 <div v-if="$root.isMobile" class="mt-3" /> | ||||
| 
 | ||||
|                 <h2>Notifications</h2> | ||||
|                 <p v-if="$root.notificationList.length === 0">Not available, please setup.</p> | ||||
|                 <p v-else>Please assign a notification to monitor(s) to get it to work.</p> | ||||
|                 <p v-if="$root.notificationList.length === 0"> | ||||
|                     Not available, please setup. | ||||
|                 </p> | ||||
|                 <p v-else> | ||||
|                     Please assign a notification to monitor(s) to get it to work. | ||||
|                 </p> | ||||
| 
 | ||||
|                 <ul class="list-group mb-3" style="border-radius: 1rem;"> | ||||
|                     <li class="list-group-item" v-for="(notification, index) in $root.notificationList" :key="index"> | ||||
|                         {{ notification.name }}<br /> | ||||
|                     <li v-for="(notification, index) in $root.notificationList" :key="index" class="list-group-item"> | ||||
|                         {{ notification.name }}<br> | ||||
|                         <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a> | ||||
|                     </li> | ||||
|                 </ul> | ||||
| 
 | ||||
|                 <button class="btn btn-primary me-2" @click="$refs.notificationDialog.show()" type="button">Setup Notification</button> | ||||
|                 <button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()"> | ||||
|                     Setup Notification | ||||
|                 </button> | ||||
|             </div> | ||||
| 
 | ||||
| 
 | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|  | @ -77,18 +91,18 @@ | |||
| 
 | ||||
| <script> | ||||
| import dayjs from "dayjs"; | ||||
| import utc from 'dayjs/plugin/utc' | ||||
| import timezone from 'dayjs/plugin/timezone' | ||||
| import utc from "dayjs/plugin/utc" | ||||
| import timezone from "dayjs/plugin/timezone" | ||||
| import NotificationDialog from "../components/NotificationDialog.vue"; | ||||
| dayjs.extend(utc) | ||||
| dayjs.extend(timezone) | ||||
| import {timezoneList} from "../util-frontend"; | ||||
| import { useToast } from 'vue-toastification' | ||||
| import { timezoneList } from "../util-frontend"; | ||||
| import { useToast } from "vue-toastification" | ||||
| const toast = useToast() | ||||
| 
 | ||||
| export default { | ||||
|     components: { | ||||
|         NotificationDialog | ||||
|         NotificationDialog, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|  | @ -100,9 +114,14 @@ export default { | |||
|                 currentPassword: "", | ||||
|                 newPassword: "", | ||||
|                 repeatNewPassword: "", | ||||
|             } | ||||
|             }, | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         "password.repeatNewPassword"() { | ||||
|             this.invalidPassword = false; | ||||
|         }, | ||||
|     }, | ||||
| 
 | ||||
|     mounted() { | ||||
| 
 | ||||
|  | @ -130,11 +149,6 @@ export default { | |||
|             } | ||||
|         }, | ||||
|     }, | ||||
|     watch: { | ||||
|         "password.repeatNewPassword"() { | ||||
|             this.invalidPassword = false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,38 +2,42 @@ | |||
|     <div class="form-container"> | ||||
|         <div class="form"> | ||||
|             <form @submit.prevent="submit"> | ||||
| 
 | ||||
|                 <div> | ||||
|                     <object width="64" height="64" data="/icon.svg"></object> | ||||
|                     <div style="font-size: 28px; font-weight: bold; margin-top: 5px;">Uptime Kuma</div> | ||||
|                     <object width="64" height="64" data="/icon.svg" /> | ||||
|                     <div style="font-size: 28px; font-weight: bold; margin-top: 5px;"> | ||||
|                         Uptime Kuma | ||||
|                     </div> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <p class="mt-3">Create your admin account</p> | ||||
|                 <p class="mt-3"> | ||||
|                     Create your admin account | ||||
|                 </p> | ||||
| 
 | ||||
|                 <div class="form-floating"> | ||||
|                     <input type="text" class="form-control" id="floatingInput" placeholder="Username" v-model="username" required> | ||||
|                     <input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username" required> | ||||
|                     <label for="floatingInput">Username</label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <div class="form-floating mt-3"> | ||||
|                     <input type="password" class="form-control" id="floatingPassword" placeholder="Password" v-model="password" required> | ||||
|                     <input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password" required> | ||||
|                     <label for="floatingPassword">Password</label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <div class="form-floating mt-3"> | ||||
|                     <input type="password" class="form-control" id="repeat" placeholder="Repeat Password" v-model="repeatPassword" required> | ||||
|                     <input id="repeat" v-model="repeatPassword" type="password" class="form-control" placeholder="Repeat Password" required> | ||||
|                     <label for="repeat">Repeat Password</label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing">Create</button> | ||||
| 
 | ||||
|                 <button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing"> | ||||
|                     Create | ||||
|                 </button> | ||||
|             </form> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { useToast } from 'vue-toastification' | ||||
| import { useToast } from "vue-toastification" | ||||
| const toast = useToast() | ||||
| 
 | ||||
| export default { | ||||
|  | @ -70,8 +74,8 @@ export default { | |||
|                     this.$router.push("/") | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import dayjs from "dayjs"; | ||||
| import utc from 'dayjs/plugin/utc' | ||||
| import timezone from 'dayjs/plugin/timezone' | ||||
| import timezone from "dayjs/plugin/timezone"; | ||||
| import utc from "dayjs/plugin/utc"; | ||||
| 
 | ||||
| dayjs.extend(utc) | ||||
| dayjs.extend(timezone) | ||||
|  | @ -18,11 +18,12 @@ export function ucfirst(str) { | |||
|     return firstLetter.toUpperCase() + str.substr(1); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function getTimezoneOffset(timeZone) { | ||||
|     const now = new Date(); | ||||
|     const tzString = now.toLocaleString('en-US', { timeZone }); | ||||
|     const localString = now.toLocaleString('en-US'); | ||||
|     const tzString = now.toLocaleString("en-US", { | ||||
|         timeZone, | ||||
|     }); | ||||
|     const localString = now.toLocaleString("en-US"); | ||||
|     const diff = (Date.parse(localString) - Date.parse(tzString)) / 3600000; | ||||
|     const offset = diff + now.getTimezoneOffset() / 60; | ||||
|     return -offset; | ||||
|  | @ -31,355 +32,354 @@ function getTimezoneOffset(timeZone) { | |||
| // From: https://stackoverflow.com/questions/38399465/how-to-get-list-of-all-timezones-in-javascript
 | ||||
| // TODO: Move to separate file
 | ||||
| const aryIannaTimeZones = [ | ||||
|     'Europe/Andorra', | ||||
|     'Asia/Dubai', | ||||
|     'Asia/Kabul', | ||||
|     'Europe/Tirane', | ||||
|     'Asia/Yerevan', | ||||
|     'Antarctica/Casey', | ||||
|     'Antarctica/Davis', | ||||
|     'Antarctica/Mawson', | ||||
|     'Antarctica/Palmer', | ||||
|     'Antarctica/Rothera', | ||||
|     'Antarctica/Syowa', | ||||
|     'Antarctica/Troll', | ||||
|     'Antarctica/Vostok', | ||||
|     'America/Argentina/Buenos_Aires', | ||||
|     'America/Argentina/Cordoba', | ||||
|     'America/Argentina/Salta', | ||||
|     'America/Argentina/Jujuy', | ||||
|     'America/Argentina/Tucuman', | ||||
|     'America/Argentina/Catamarca', | ||||
|     'America/Argentina/La_Rioja', | ||||
|     'America/Argentina/San_Juan', | ||||
|     'America/Argentina/Mendoza', | ||||
|     'America/Argentina/San_Luis', | ||||
|     'America/Argentina/Rio_Gallegos', | ||||
|     'America/Argentina/Ushuaia', | ||||
|     'Pacific/Pago_Pago', | ||||
|     'Europe/Vienna', | ||||
|     'Australia/Lord_Howe', | ||||
|     'Antarctica/Macquarie', | ||||
|     'Australia/Hobart', | ||||
|     'Australia/Currie', | ||||
|     'Australia/Melbourne', | ||||
|     'Australia/Sydney', | ||||
|     'Australia/Broken_Hill', | ||||
|     'Australia/Brisbane', | ||||
|     'Australia/Lindeman', | ||||
|     'Australia/Adelaide', | ||||
|     'Australia/Darwin', | ||||
|     'Australia/Perth', | ||||
|     'Australia/Eucla', | ||||
|     'Asia/Baku', | ||||
|     'America/Barbados', | ||||
|     'Asia/Dhaka', | ||||
|     'Europe/Brussels', | ||||
|     'Europe/Sofia', | ||||
|     'Atlantic/Bermuda', | ||||
|     'Asia/Brunei', | ||||
|     'America/La_Paz', | ||||
|     'America/Noronha', | ||||
|     'America/Belem', | ||||
|     'America/Fortaleza', | ||||
|     'America/Recife', | ||||
|     'America/Araguaina', | ||||
|     'America/Maceio', | ||||
|     'America/Bahia', | ||||
|     'America/Sao_Paulo', | ||||
|     'America/Campo_Grande', | ||||
|     'America/Cuiaba', | ||||
|     'America/Santarem', | ||||
|     'America/Porto_Velho', | ||||
|     'America/Boa_Vista', | ||||
|     'America/Manaus', | ||||
|     'America/Eirunepe', | ||||
|     'America/Rio_Branco', | ||||
|     'America/Nassau', | ||||
|     'Asia/Thimphu', | ||||
|     'Europe/Minsk', | ||||
|     'America/Belize', | ||||
|     'America/St_Johns', | ||||
|     'America/Halifax', | ||||
|     'America/Glace_Bay', | ||||
|     'America/Moncton', | ||||
|     'America/Goose_Bay', | ||||
|     'America/Blanc-Sablon', | ||||
|     'America/Toronto', | ||||
|     'America/Nipigon', | ||||
|     'America/Thunder_Bay', | ||||
|     'America/Iqaluit', | ||||
|     'America/Pangnirtung', | ||||
|     'America/Atikokan', | ||||
|     'America/Winnipeg', | ||||
|     'America/Rainy_River', | ||||
|     'America/Resolute', | ||||
|     'America/Rankin_Inlet', | ||||
|     'America/Regina', | ||||
|     'America/Swift_Current', | ||||
|     'America/Edmonton', | ||||
|     'America/Cambridge_Bay', | ||||
|     'America/Yellowknife', | ||||
|     'America/Inuvik', | ||||
|     'America/Creston', | ||||
|     'America/Dawson_Creek', | ||||
|     'America/Fort_Nelson', | ||||
|     'America/Vancouver', | ||||
|     'America/Whitehorse', | ||||
|     'America/Dawson', | ||||
|     'Indian/Cocos', | ||||
|     'Europe/Zurich', | ||||
|     'Africa/Abidjan', | ||||
|     'Pacific/Rarotonga', | ||||
|     'America/Santiago', | ||||
|     'America/Punta_Arenas', | ||||
|     'Pacific/Easter', | ||||
|     'Asia/Shanghai', | ||||
|     'Asia/Urumqi', | ||||
|     'America/Bogota', | ||||
|     'America/Costa_Rica', | ||||
|     'America/Havana', | ||||
|     'Atlantic/Cape_Verde', | ||||
|     'America/Curacao', | ||||
|     'Indian/Christmas', | ||||
|     'Asia/Nicosia', | ||||
|     'Asia/Famagusta', | ||||
|     'Europe/Prague', | ||||
|     'Europe/Berlin', | ||||
|     'Europe/Copenhagen', | ||||
|     'America/Santo_Domingo', | ||||
|     'Africa/Algiers', | ||||
|     'America/Guayaquil', | ||||
|     'Pacific/Galapagos', | ||||
|     'Europe/Tallinn', | ||||
|     'Africa/Cairo', | ||||
|     'Africa/El_Aaiun', | ||||
|     'Europe/Madrid', | ||||
|     'Africa/Ceuta', | ||||
|     'Atlantic/Canary', | ||||
|     'Europe/Helsinki', | ||||
|     'Pacific/Fiji', | ||||
|     'Atlantic/Stanley', | ||||
|     'Pacific/Chuuk', | ||||
|     'Pacific/Pohnpei', | ||||
|     'Pacific/Kosrae', | ||||
|     'Atlantic/Faroe', | ||||
|     'Europe/Paris', | ||||
|     'Europe/London', | ||||
|     'Asia/Tbilisi', | ||||
|     'America/Cayenne', | ||||
|     'Africa/Accra', | ||||
|     'Europe/Gibraltar', | ||||
|     'America/Godthab', | ||||
|     'America/Danmarkshavn', | ||||
|     'America/Scoresbysund', | ||||
|     'America/Thule', | ||||
|     'Europe/Athens', | ||||
|     'Atlantic/South_Georgia', | ||||
|     'America/Guatemala', | ||||
|     'Pacific/Guam', | ||||
|     'Africa/Bissau', | ||||
|     'America/Guyana', | ||||
|     'Asia/Hong_Kong', | ||||
|     'America/Tegucigalpa', | ||||
|     'America/Port-au-Prince', | ||||
|     'Europe/Budapest', | ||||
|     'Asia/Jakarta', | ||||
|     'Asia/Pontianak', | ||||
|     'Asia/Makassar', | ||||
|     'Asia/Jayapura', | ||||
|     'Europe/Dublin', | ||||
|     'Asia/Jerusalem', | ||||
|     'Asia/Kolkata', | ||||
|     'Indian/Chagos', | ||||
|     'Asia/Baghdad', | ||||
|     'Asia/Tehran', | ||||
|     'Atlantic/Reykjavik', | ||||
|     'Europe/Rome', | ||||
|     'America/Jamaica', | ||||
|     'Asia/Amman', | ||||
|     'Asia/Tokyo', | ||||
|     'Africa/Nairobi', | ||||
|     'Asia/Bishkek', | ||||
|     'Pacific/Tarawa', | ||||
|     'Pacific/Enderbury', | ||||
|     'Pacific/Kiritimati', | ||||
|     'Asia/Pyongyang', | ||||
|     'Asia/Seoul', | ||||
|     'Asia/Almaty', | ||||
|     'Asia/Qyzylorda', | ||||
|     'Asia/Aqtobe', | ||||
|     'Asia/Aqtau', | ||||
|     'Asia/Atyrau', | ||||
|     'Asia/Oral', | ||||
|     'Asia/Beirut', | ||||
|     'Asia/Colombo', | ||||
|     'Africa/Monrovia', | ||||
|     'Europe/Vilnius', | ||||
|     'Europe/Luxembourg', | ||||
|     'Europe/Riga', | ||||
|     'Africa/Tripoli', | ||||
|     'Africa/Casablanca', | ||||
|     'Europe/Monaco', | ||||
|     'Europe/Chisinau', | ||||
|     'Pacific/Majuro', | ||||
|     'Pacific/Kwajalein', | ||||
|     'Asia/Yangon', | ||||
|     'Asia/Ulaanbaatar', | ||||
|     'Asia/Hovd', | ||||
|     'Asia/Choibalsan', | ||||
|     'Asia/Macau', | ||||
|     'America/Martinique', | ||||
|     'Europe/Malta', | ||||
|     'Indian/Mauritius', | ||||
|     'Indian/Maldives', | ||||
|     'America/Mexico_City', | ||||
|     'America/Cancun', | ||||
|     'America/Merida', | ||||
|     'America/Monterrey', | ||||
|     'America/Matamoros', | ||||
|     'America/Mazatlan', | ||||
|     'America/Chihuahua', | ||||
|     'America/Ojinaga', | ||||
|     'America/Hermosillo', | ||||
|     'America/Tijuana', | ||||
|     'America/Bahia_Banderas', | ||||
|     'Asia/Kuala_Lumpur', | ||||
|     'Asia/Kuching', | ||||
|     'Africa/Maputo', | ||||
|     'Africa/Windhoek', | ||||
|     'Pacific/Noumea', | ||||
|     'Pacific/Norfolk', | ||||
|     'Africa/Lagos', | ||||
|     'America/Managua', | ||||
|     'Europe/Amsterdam', | ||||
|     'Europe/Oslo', | ||||
|     'Asia/Kathmandu', | ||||
|     'Pacific/Nauru', | ||||
|     'Pacific/Niue', | ||||
|     'Pacific/Auckland', | ||||
|     'Pacific/Chatham', | ||||
|     'America/Panama', | ||||
|     'America/Lima', | ||||
|     'Pacific/Tahiti', | ||||
|     'Pacific/Marquesas', | ||||
|     'Pacific/Gambier', | ||||
|     'Pacific/Port_Moresby', | ||||
|     'Pacific/Bougainville', | ||||
|     'Asia/Manila', | ||||
|     'Asia/Karachi', | ||||
|     'Europe/Warsaw', | ||||
|     'America/Miquelon', | ||||
|     'Pacific/Pitcairn', | ||||
|     'America/Puerto_Rico', | ||||
|     'Asia/Gaza', | ||||
|     'Asia/Hebron', | ||||
|     'Europe/Lisbon', | ||||
|     'Atlantic/Madeira', | ||||
|     'Atlantic/Azores', | ||||
|     'Pacific/Palau', | ||||
|     'America/Asuncion', | ||||
|     'Asia/Qatar', | ||||
|     'Indian/Reunion', | ||||
|     'Europe/Bucharest', | ||||
|     'Europe/Belgrade', | ||||
|     'Europe/Kaliningrad', | ||||
|     'Europe/Moscow', | ||||
|     'Europe/Simferopol', | ||||
|     'Europe/Kirov', | ||||
|     'Europe/Astrakhan', | ||||
|     'Europe/Volgograd', | ||||
|     'Europe/Saratov', | ||||
|     'Europe/Ulyanovsk', | ||||
|     'Europe/Samara', | ||||
|     'Asia/Yekaterinburg', | ||||
|     'Asia/Omsk', | ||||
|     'Asia/Novosibirsk', | ||||
|     'Asia/Barnaul', | ||||
|     'Asia/Tomsk', | ||||
|     'Asia/Novokuznetsk', | ||||
|     'Asia/Krasnoyarsk', | ||||
|     'Asia/Irkutsk', | ||||
|     'Asia/Chita', | ||||
|     'Asia/Yakutsk', | ||||
|     'Asia/Khandyga', | ||||
|     'Asia/Vladivostok', | ||||
|     'Asia/Ust-Nera', | ||||
|     'Asia/Magadan', | ||||
|     'Asia/Sakhalin', | ||||
|     'Asia/Srednekolymsk', | ||||
|     'Asia/Kamchatka', | ||||
|     'Asia/Anadyr', | ||||
|     'Asia/Riyadh', | ||||
|     'Pacific/Guadalcanal', | ||||
|     'Indian/Mahe', | ||||
|     'Africa/Khartoum', | ||||
|     'Europe/Stockholm', | ||||
|     'Asia/Singapore', | ||||
|     'America/Paramaribo', | ||||
|     'Africa/Juba', | ||||
|     'Africa/Sao_Tome', | ||||
|     'America/El_Salvador', | ||||
|     'Asia/Damascus', | ||||
|     'America/Grand_Turk', | ||||
|     'Africa/Ndjamena', | ||||
|     'Indian/Kerguelen', | ||||
|     'Asia/Bangkok', | ||||
|     'Asia/Dushanbe', | ||||
|     'Pacific/Fakaofo', | ||||
|     'Asia/Dili', | ||||
|     'Asia/Ashgabat', | ||||
|     'Africa/Tunis', | ||||
|     'Pacific/Tongatapu', | ||||
|     'Europe/Istanbul', | ||||
|     'America/Port_of_Spain', | ||||
|     'Pacific/Funafuti', | ||||
|     'Asia/Taipei', | ||||
|     'Europe/Kiev', | ||||
|     'Europe/Uzhgorod', | ||||
|     'Europe/Zaporozhye', | ||||
|     'Pacific/Wake', | ||||
|     'America/New_York', | ||||
|     'America/Detroit', | ||||
|     'America/Kentucky/Louisville', | ||||
|     'America/Kentucky/Monticello', | ||||
|     'America/Indiana/Indianapolis', | ||||
|     'America/Indiana/Vincennes', | ||||
|     'America/Indiana/Winamac', | ||||
|     'America/Indiana/Marengo', | ||||
|     'America/Indiana/Petersburg', | ||||
|     'America/Indiana/Vevay', | ||||
|     'America/Chicago', | ||||
|     'America/Indiana/Tell_City', | ||||
|     'America/Indiana/Knox', | ||||
|     'America/Menominee', | ||||
|     'America/North_Dakota/Center', | ||||
|     'America/North_Dakota/New_Salem', | ||||
|     'America/North_Dakota/Beulah', | ||||
|     'America/Denver', | ||||
|     'America/Boise', | ||||
|     'America/Phoenix', | ||||
|     'America/Los_Angeles', | ||||
|     'America/Anchorage', | ||||
|     'America/Juneau', | ||||
|     'America/Sitka', | ||||
|     'America/Metlakatla', | ||||
|     'America/Yakutat', | ||||
|     'America/Nome', | ||||
|     'America/Adak', | ||||
|     'Pacific/Honolulu', | ||||
|     'America/Montevideo', | ||||
|     'Asia/Samarkand', | ||||
|     'Asia/Tashkent', | ||||
|     'America/Caracas', | ||||
|     'Asia/Ho_Chi_Minh', | ||||
|     'Pacific/Efate', | ||||
|     'Pacific/Wallis', | ||||
|     'Pacific/Apia', | ||||
|     'Africa/Johannesburg', | ||||
|     "Europe/Andorra", | ||||
|     "Asia/Dubai", | ||||
|     "Asia/Kabul", | ||||
|     "Europe/Tirane", | ||||
|     "Asia/Yerevan", | ||||
|     "Antarctica/Casey", | ||||
|     "Antarctica/Davis", | ||||
|     "Antarctica/Mawson", | ||||
|     "Antarctica/Palmer", | ||||
|     "Antarctica/Rothera", | ||||
|     "Antarctica/Syowa", | ||||
|     "Antarctica/Troll", | ||||
|     "Antarctica/Vostok", | ||||
|     "America/Argentina/Buenos_Aires", | ||||
|     "America/Argentina/Cordoba", | ||||
|     "America/Argentina/Salta", | ||||
|     "America/Argentina/Jujuy", | ||||
|     "America/Argentina/Tucuman", | ||||
|     "America/Argentina/Catamarca", | ||||
|     "America/Argentina/La_Rioja", | ||||
|     "America/Argentina/San_Juan", | ||||
|     "America/Argentina/Mendoza", | ||||
|     "America/Argentina/San_Luis", | ||||
|     "America/Argentina/Rio_Gallegos", | ||||
|     "America/Argentina/Ushuaia", | ||||
|     "Pacific/Pago_Pago", | ||||
|     "Europe/Vienna", | ||||
|     "Australia/Lord_Howe", | ||||
|     "Antarctica/Macquarie", | ||||
|     "Australia/Hobart", | ||||
|     "Australia/Currie", | ||||
|     "Australia/Melbourne", | ||||
|     "Australia/Sydney", | ||||
|     "Australia/Broken_Hill", | ||||
|     "Australia/Brisbane", | ||||
|     "Australia/Lindeman", | ||||
|     "Australia/Adelaide", | ||||
|     "Australia/Darwin", | ||||
|     "Australia/Perth", | ||||
|     "Australia/Eucla", | ||||
|     "Asia/Baku", | ||||
|     "America/Barbados", | ||||
|     "Asia/Dhaka", | ||||
|     "Europe/Brussels", | ||||
|     "Europe/Sofia", | ||||
|     "Atlantic/Bermuda", | ||||
|     "Asia/Brunei", | ||||
|     "America/La_Paz", | ||||
|     "America/Noronha", | ||||
|     "America/Belem", | ||||
|     "America/Fortaleza", | ||||
|     "America/Recife", | ||||
|     "America/Araguaina", | ||||
|     "America/Maceio", | ||||
|     "America/Bahia", | ||||
|     "America/Sao_Paulo", | ||||
|     "America/Campo_Grande", | ||||
|     "America/Cuiaba", | ||||
|     "America/Santarem", | ||||
|     "America/Porto_Velho", | ||||
|     "America/Boa_Vista", | ||||
|     "America/Manaus", | ||||
|     "America/Eirunepe", | ||||
|     "America/Rio_Branco", | ||||
|     "America/Nassau", | ||||
|     "Asia/Thimphu", | ||||
|     "Europe/Minsk", | ||||
|     "America/Belize", | ||||
|     "America/St_Johns", | ||||
|     "America/Halifax", | ||||
|     "America/Glace_Bay", | ||||
|     "America/Moncton", | ||||
|     "America/Goose_Bay", | ||||
|     "America/Blanc-Sablon", | ||||
|     "America/Toronto", | ||||
|     "America/Nipigon", | ||||
|     "America/Thunder_Bay", | ||||
|     "America/Iqaluit", | ||||
|     "America/Pangnirtung", | ||||
|     "America/Atikokan", | ||||
|     "America/Winnipeg", | ||||
|     "America/Rainy_River", | ||||
|     "America/Resolute", | ||||
|     "America/Rankin_Inlet", | ||||
|     "America/Regina", | ||||
|     "America/Swift_Current", | ||||
|     "America/Edmonton", | ||||
|     "America/Cambridge_Bay", | ||||
|     "America/Yellowknife", | ||||
|     "America/Inuvik", | ||||
|     "America/Creston", | ||||
|     "America/Dawson_Creek", | ||||
|     "America/Fort_Nelson", | ||||
|     "America/Vancouver", | ||||
|     "America/Whitehorse", | ||||
|     "America/Dawson", | ||||
|     "Indian/Cocos", | ||||
|     "Europe/Zurich", | ||||
|     "Africa/Abidjan", | ||||
|     "Pacific/Rarotonga", | ||||
|     "America/Santiago", | ||||
|     "America/Punta_Arenas", | ||||
|     "Pacific/Easter", | ||||
|     "Asia/Shanghai", | ||||
|     "Asia/Urumqi", | ||||
|     "America/Bogota", | ||||
|     "America/Costa_Rica", | ||||
|     "America/Havana", | ||||
|     "Atlantic/Cape_Verde", | ||||
|     "America/Curacao", | ||||
|     "Indian/Christmas", | ||||
|     "Asia/Nicosia", | ||||
|     "Asia/Famagusta", | ||||
|     "Europe/Prague", | ||||
|     "Europe/Berlin", | ||||
|     "Europe/Copenhagen", | ||||
|     "America/Santo_Domingo", | ||||
|     "Africa/Algiers", | ||||
|     "America/Guayaquil", | ||||
|     "Pacific/Galapagos", | ||||
|     "Europe/Tallinn", | ||||
|     "Africa/Cairo", | ||||
|     "Africa/El_Aaiun", | ||||
|     "Europe/Madrid", | ||||
|     "Africa/Ceuta", | ||||
|     "Atlantic/Canary", | ||||
|     "Europe/Helsinki", | ||||
|     "Pacific/Fiji", | ||||
|     "Atlantic/Stanley", | ||||
|     "Pacific/Chuuk", | ||||
|     "Pacific/Pohnpei", | ||||
|     "Pacific/Kosrae", | ||||
|     "Atlantic/Faroe", | ||||
|     "Europe/Paris", | ||||
|     "Europe/London", | ||||
|     "Asia/Tbilisi", | ||||
|     "America/Cayenne", | ||||
|     "Africa/Accra", | ||||
|     "Europe/Gibraltar", | ||||
|     "America/Godthab", | ||||
|     "America/Danmarkshavn", | ||||
|     "America/Scoresbysund", | ||||
|     "America/Thule", | ||||
|     "Europe/Athens", | ||||
|     "Atlantic/South_Georgia", | ||||
|     "America/Guatemala", | ||||
|     "Pacific/Guam", | ||||
|     "Africa/Bissau", | ||||
|     "America/Guyana", | ||||
|     "Asia/Hong_Kong", | ||||
|     "America/Tegucigalpa", | ||||
|     "America/Port-au-Prince", | ||||
|     "Europe/Budapest", | ||||
|     "Asia/Jakarta", | ||||
|     "Asia/Pontianak", | ||||
|     "Asia/Makassar", | ||||
|     "Asia/Jayapura", | ||||
|     "Europe/Dublin", | ||||
|     "Asia/Jerusalem", | ||||
|     "Asia/Kolkata", | ||||
|     "Indian/Chagos", | ||||
|     "Asia/Baghdad", | ||||
|     "Asia/Tehran", | ||||
|     "Atlantic/Reykjavik", | ||||
|     "Europe/Rome", | ||||
|     "America/Jamaica", | ||||
|     "Asia/Amman", | ||||
|     "Asia/Tokyo", | ||||
|     "Africa/Nairobi", | ||||
|     "Asia/Bishkek", | ||||
|     "Pacific/Tarawa", | ||||
|     "Pacific/Enderbury", | ||||
|     "Pacific/Kiritimati", | ||||
|     "Asia/Pyongyang", | ||||
|     "Asia/Seoul", | ||||
|     "Asia/Almaty", | ||||
|     "Asia/Qyzylorda", | ||||
|     "Asia/Aqtobe", | ||||
|     "Asia/Aqtau", | ||||
|     "Asia/Atyrau", | ||||
|     "Asia/Oral", | ||||
|     "Asia/Beirut", | ||||
|     "Asia/Colombo", | ||||
|     "Africa/Monrovia", | ||||
|     "Europe/Vilnius", | ||||
|     "Europe/Luxembourg", | ||||
|     "Europe/Riga", | ||||
|     "Africa/Tripoli", | ||||
|     "Africa/Casablanca", | ||||
|     "Europe/Monaco", | ||||
|     "Europe/Chisinau", | ||||
|     "Pacific/Majuro", | ||||
|     "Pacific/Kwajalein", | ||||
|     "Asia/Yangon", | ||||
|     "Asia/Ulaanbaatar", | ||||
|     "Asia/Hovd", | ||||
|     "Asia/Choibalsan", | ||||
|     "Asia/Macau", | ||||
|     "America/Martinique", | ||||
|     "Europe/Malta", | ||||
|     "Indian/Mauritius", | ||||
|     "Indian/Maldives", | ||||
|     "America/Mexico_City", | ||||
|     "America/Cancun", | ||||
|     "America/Merida", | ||||
|     "America/Monterrey", | ||||
|     "America/Matamoros", | ||||
|     "America/Mazatlan", | ||||
|     "America/Chihuahua", | ||||
|     "America/Ojinaga", | ||||
|     "America/Hermosillo", | ||||
|     "America/Tijuana", | ||||
|     "America/Bahia_Banderas", | ||||
|     "Asia/Kuala_Lumpur", | ||||
|     "Asia/Kuching", | ||||
|     "Africa/Maputo", | ||||
|     "Africa/Windhoek", | ||||
|     "Pacific/Noumea", | ||||
|     "Pacific/Norfolk", | ||||
|     "Africa/Lagos", | ||||
|     "America/Managua", | ||||
|     "Europe/Amsterdam", | ||||
|     "Europe/Oslo", | ||||
|     "Asia/Kathmandu", | ||||
|     "Pacific/Nauru", | ||||
|     "Pacific/Niue", | ||||
|     "Pacific/Auckland", | ||||
|     "Pacific/Chatham", | ||||
|     "America/Panama", | ||||
|     "America/Lima", | ||||
|     "Pacific/Tahiti", | ||||
|     "Pacific/Marquesas", | ||||
|     "Pacific/Gambier", | ||||
|     "Pacific/Port_Moresby", | ||||
|     "Pacific/Bougainville", | ||||
|     "Asia/Manila", | ||||
|     "Asia/Karachi", | ||||
|     "Europe/Warsaw", | ||||
|     "America/Miquelon", | ||||
|     "Pacific/Pitcairn", | ||||
|     "America/Puerto_Rico", | ||||
|     "Asia/Gaza", | ||||
|     "Asia/Hebron", | ||||
|     "Europe/Lisbon", | ||||
|     "Atlantic/Madeira", | ||||
|     "Atlantic/Azores", | ||||
|     "Pacific/Palau", | ||||
|     "America/Asuncion", | ||||
|     "Asia/Qatar", | ||||
|     "Indian/Reunion", | ||||
|     "Europe/Bucharest", | ||||
|     "Europe/Belgrade", | ||||
|     "Europe/Kaliningrad", | ||||
|     "Europe/Moscow", | ||||
|     "Europe/Simferopol", | ||||
|     "Europe/Kirov", | ||||
|     "Europe/Astrakhan", | ||||
|     "Europe/Volgograd", | ||||
|     "Europe/Saratov", | ||||
|     "Europe/Ulyanovsk", | ||||
|     "Europe/Samara", | ||||
|     "Asia/Yekaterinburg", | ||||
|     "Asia/Omsk", | ||||
|     "Asia/Novosibirsk", | ||||
|     "Asia/Barnaul", | ||||
|     "Asia/Tomsk", | ||||
|     "Asia/Novokuznetsk", | ||||
|     "Asia/Krasnoyarsk", | ||||
|     "Asia/Irkutsk", | ||||
|     "Asia/Chita", | ||||
|     "Asia/Yakutsk", | ||||
|     "Asia/Khandyga", | ||||
|     "Asia/Vladivostok", | ||||
|     "Asia/Ust-Nera", | ||||
|     "Asia/Magadan", | ||||
|     "Asia/Sakhalin", | ||||
|     "Asia/Srednekolymsk", | ||||
|     "Asia/Kamchatka", | ||||
|     "Asia/Anadyr", | ||||
|     "Asia/Riyadh", | ||||
|     "Pacific/Guadalcanal", | ||||
|     "Indian/Mahe", | ||||
|     "Africa/Khartoum", | ||||
|     "Europe/Stockholm", | ||||
|     "Asia/Singapore", | ||||
|     "America/Paramaribo", | ||||
|     "Africa/Juba", | ||||
|     "Africa/Sao_Tome", | ||||
|     "America/El_Salvador", | ||||
|     "Asia/Damascus", | ||||
|     "America/Grand_Turk", | ||||
|     "Africa/Ndjamena", | ||||
|     "Indian/Kerguelen", | ||||
|     "Asia/Bangkok", | ||||
|     "Asia/Dushanbe", | ||||
|     "Pacific/Fakaofo", | ||||
|     "Asia/Dili", | ||||
|     "Asia/Ashgabat", | ||||
|     "Africa/Tunis", | ||||
|     "Pacific/Tongatapu", | ||||
|     "Europe/Istanbul", | ||||
|     "America/Port_of_Spain", | ||||
|     "Pacific/Funafuti", | ||||
|     "Asia/Taipei", | ||||
|     "Europe/Kiev", | ||||
|     "Europe/Uzhgorod", | ||||
|     "Europe/Zaporozhye", | ||||
|     "Pacific/Wake", | ||||
|     "America/New_York", | ||||
|     "America/Detroit", | ||||
|     "America/Kentucky/Louisville", | ||||
|     "America/Kentucky/Monticello", | ||||
|     "America/Indiana/Indianapolis", | ||||
|     "America/Indiana/Vincennes", | ||||
|     "America/Indiana/Winamac", | ||||
|     "America/Indiana/Marengo", | ||||
|     "America/Indiana/Petersburg", | ||||
|     "America/Indiana/Vevay", | ||||
|     "America/Chicago", | ||||
|     "America/Indiana/Tell_City", | ||||
|     "America/Indiana/Knox", | ||||
|     "America/Menominee", | ||||
|     "America/North_Dakota/Center", | ||||
|     "America/North_Dakota/New_Salem", | ||||
|     "America/North_Dakota/Beulah", | ||||
|     "America/Denver", | ||||
|     "America/Boise", | ||||
|     "America/Phoenix", | ||||
|     "America/Los_Angeles", | ||||
|     "America/Anchorage", | ||||
|     "America/Juneau", | ||||
|     "America/Sitka", | ||||
|     "America/Metlakatla", | ||||
|     "America/Yakutat", | ||||
|     "America/Nome", | ||||
|     "America/Adak", | ||||
|     "Pacific/Honolulu", | ||||
|     "America/Montevideo", | ||||
|     "Asia/Samarkand", | ||||
|     "Asia/Tashkent", | ||||
|     "America/Caracas", | ||||
|     "Asia/Ho_Chi_Minh", | ||||
|     "Pacific/Efate", | ||||
|     "Pacific/Wallis", | ||||
|     "Pacific/Apia", | ||||
|     "Africa/Johannesburg", | ||||
| ]; | ||||
| 
 | ||||
| 
 | ||||
| export function timezoneList() { | ||||
| 
 | ||||
|     let result = []; | ||||
|  | @ -404,12 +404,14 @@ export function timezoneList() { | |||
|     result.sort((a, b) => { | ||||
|         if (a.time > b.time) { | ||||
|             return 1; | ||||
|         } else if (b.time > a.time) { | ||||
|             return -1; | ||||
|         } else { | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         if (b.time > a.time) { | ||||
|             return -1; | ||||
|         } | ||||
| 
 | ||||
|         return 0; | ||||
|     }) | ||||
| 
 | ||||
|     return result; | ||||
| }; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue