Feat: Implement MaxRedirects & StatusCodes
This commit is contained in:
		
							parent
							
								
									2c2ac9dc59
								
							
						
					
					
						commit
						8f7885e58a
					
				
					 8 changed files with 184 additions and 19 deletions
				
			
		
							
								
								
									
										74
									
								
								db/patch6.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								db/patch6.sql
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| -- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | ||||
| PRAGMA foreign_keys = off; | ||||
| 
 | ||||
| BEGIN TRANSACTION; | ||||
| 
 | ||||
| create table monitor_dg_tmp ( | ||||
| 	id INTEGER not null primary key autoincrement, | ||||
| 	name VARCHAR(150), | ||||
| 	active BOOLEAN default 1 not null, | ||||
| 	user_id INTEGER references user on update cascade on delete | ||||
| 	set | ||||
| 		null, | ||||
| 		interval INTEGER default 20 not null, | ||||
| 		url TEXT, | ||||
| 		type VARCHAR(20), | ||||
| 		weight INTEGER default 2000, | ||||
| 		hostname VARCHAR(255), | ||||
| 		port INTEGER, | ||||
| 		created_date DATETIME default (DATETIME('now')) not null, | ||||
| 		keyword VARCHAR(255), | ||||
| 		maxretries INTEGER NOT NULL DEFAULT 0, | ||||
| 		ignore_tls BOOLEAN default 0 not null, | ||||
| 		upside_down BOOLEAN default 0 not null, | ||||
|         maxredirects INTEGER default 10 not null, | ||||
|         accepted_statuscodes_json TEXT default '["200-299"]' not null | ||||
| ); | ||||
| 
 | ||||
| insert into | ||||
| 	monitor_dg_tmp( | ||||
| 		id, | ||||
| 		name, | ||||
| 		active, | ||||
| 		user_id, | ||||
| 		interval, | ||||
| 		url, | ||||
| 		type, | ||||
| 		weight, | ||||
| 		hostname, | ||||
| 		port, | ||||
|         created_date, | ||||
| 		keyword, | ||||
| 		maxretries, | ||||
| 		ignore_tls, | ||||
| 		upside_down | ||||
| 	) | ||||
| select | ||||
| 	id, | ||||
| 	name, | ||||
| 	active, | ||||
| 	user_id, | ||||
| 	interval, | ||||
| 	url, | ||||
| 	type, | ||||
| 	weight, | ||||
| 	hostname, | ||||
| 	port, | ||||
|     created_date, | ||||
| 	keyword, | ||||
| 	maxretries, | ||||
| 	ignore_tls, | ||||
| 	upside_down | ||||
| from | ||||
| 	monitor; | ||||
| 
 | ||||
| drop table monitor; | ||||
| 
 | ||||
| alter table | ||||
| 	monitor_dg_tmp rename to monitor; | ||||
| 
 | ||||
| create index user_id on monitor (user_id); | ||||
| 
 | ||||
| COMMIT; | ||||
| 
 | ||||
| PRAGMA foreign_keys = on; | ||||
							
								
								
									
										7
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "uptime-kuma", | ||||
|   "version": "1.0.7", | ||||
|   "version": "1.0.10", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|  | @ -7021,6 +7021,11 @@ | |||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "vue-multiselect": { | ||||
|       "version": "3.0.0-alpha.2", | ||||
|       "resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.0.0-alpha.2.tgz", | ||||
|       "integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q==" | ||||
|     }, | ||||
|     "vue-router": { | ||||
|       "version": "4.0.10", | ||||
|       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz", | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ | |||
|         "v-pagination-3": "^0.1.6", | ||||
|         "vue": "^3.0.5", | ||||
|         "vue-confirm-dialog": "^1.0.2", | ||||
|         "vue-multiselect": "^3.0.0-alpha.2", | ||||
|         "vue-router": "^4.0.10", | ||||
|         "vue-toastification": "^2.0.0-rc.1" | ||||
|     }, | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ class Database { | |||
| 
 | ||||
|     static templatePath = "./db/kuma.db" | ||||
|     static path = "./data/kuma.db"; | ||||
|     static latestVersion = 5; | ||||
|     static latestVersion = 6; | ||||
|     static noReject = true; | ||||
| 
 | ||||
|     static async patch() { | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ dayjs.extend(timezone) | |||
| const axios = require("axios"); | ||||
| const { Prometheus } = require("../prometheus"); | ||||
| const { debug, UP, DOWN, PENDING, flipStatus } = require("../../src/util"); | ||||
| const { tcping, ping, checkCertificate } = require("../util-server"); | ||||
| const { tcping, ping, checkCertificate, checkStatusCode } = require("../util-server"); | ||||
| const { R } = require("redbean-node"); | ||||
| const { BeanModel } = require("redbean-node/dist/bean-model"); | ||||
| const { Notification } = require("../notification") | ||||
|  | @ -45,6 +45,8 @@ class Monitor extends BeanModel { | |||
|             keyword: this.keyword, | ||||
|             ignoreTls: this.getIgnoreTls(), | ||||
|             upsideDown: this.isUpsideDown(), | ||||
|             maxredirects: this.maxredirects, | ||||
|             accepted_statuscodes: this.getAcceptedStatuscodes(), | ||||
|             notificationIDList, | ||||
|         }; | ||||
|     } | ||||
|  | @ -65,6 +67,10 @@ class Monitor extends BeanModel { | |||
|         return Boolean(this.upsideDown); | ||||
|     } | ||||
| 
 | ||||
|     getAcceptedStatuscodes() { | ||||
|         return JSON.parse(this.accepted_statuscodes_json); | ||||
|     } | ||||
| 
 | ||||
|     start(io) { | ||||
|         let previousBeat = null; | ||||
|         let retries = 0; | ||||
|  | @ -111,6 +117,10 @@ class Monitor extends BeanModel { | |||
|                             maxCachedSessions: 0, | ||||
|                             rejectUnauthorized: ! this.getIgnoreTls(), | ||||
|                         }), | ||||
|                         maxRedirects: this.maxredirects, | ||||
|                         validateStatus: (status) => { | ||||
|                             return checkStatusCode(status, this.getAcceptedStatuscodes()); | ||||
|                         }, | ||||
|                     }); | ||||
|                     bean.msg = `${res.status} - ${res.statusText}` | ||||
|                     bean.ping = dayjs().valueOf() - startTime; | ||||
|  |  | |||
|  | @ -274,6 +274,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); | |||
|                 bean.keyword = monitor.keyword; | ||||
|                 bean.ignoreTls = monitor.ignoreTls; | ||||
|                 bean.upsideDown = monitor.upsideDown; | ||||
|                 bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); | ||||
| 
 | ||||
|                 await R.store(bean) | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ exports.tcping = function (hostname, port) { | |||
|             address: hostname, | ||||
|             port: port, | ||||
|             attempts: 1, | ||||
|         }, function(err, data) { | ||||
|         }, function (err, data) { | ||||
| 
 | ||||
|             if (err) { | ||||
|                 reject(err); | ||||
|  | @ -28,7 +28,7 @@ exports.ping = function (hostname) { | |||
|     return new Promise((resolve, reject) => { | ||||
|         const ping = new Ping(hostname); | ||||
| 
 | ||||
|         ping.send(function(err, ms) { | ||||
|         ping.send(function (err, ms) { | ||||
|             if (err) { | ||||
|                 reject(err) | ||||
|             } else if (ms === null) { | ||||
|  | @ -58,7 +58,7 @@ exports.setSetting = async function (key, value) { | |||
|     let bean = await R.findOne("setting", " `key` = ? ", [ | ||||
|         key, | ||||
|     ]) | ||||
|     if (! bean) { | ||||
|     if (!bean) { | ||||
|         bean = R.dispense("setting") | ||||
|         bean.key = key; | ||||
|     } | ||||
|  | @ -158,3 +158,32 @@ exports.checkCertificate = function (res) { | |||
|         fingerprint, | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| // Check if the provided status code is within the accepted ranges
 | ||||
| // Param: status - the status code to check
 | ||||
| // Param: accepted_codes - an array of accepted status codes
 | ||||
| // Return: true if the status code is within the accepted ranges, false otherwise
 | ||||
| // Will throw an error if the provided status code is not a valid range string or code string
 | ||||
| 
 | ||||
| exports.checkStatusCode = function (status, accepted_codes) { | ||||
|     if (accepted_codes == null || accepted_codes.length === 0) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (const code_range of accepted_codes) { | ||||
|         const code_range_split = code_range.split("-").map(string => parseInt(string)); | ||||
|         if (code_range_split.length === 1) { | ||||
|             if (status === code_range_split[0]) { | ||||
|                 return true; | ||||
|             } | ||||
|         } else if (code_range_split.length === 2) { | ||||
|             if (status >= code_range_split[0] && status <= code_range_split[1]) { | ||||
|                 return true; | ||||
|             } | ||||
|         } else { | ||||
|             throw new Error("Invalid status code range"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
|     <h1 class="mb-3"> | ||||
|     <h1 class="my-3"> | ||||
|         {{ pageName }} | ||||
|     </h1> | ||||
|     <form @submit.prevent="submit"> | ||||
|  | @ -8,7 +8,7 @@ | |||
|                 <div class="col-md-6"> | ||||
|                     <h2>General</h2> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                     <div class="my-3"> | ||||
|                         <label for="type" class="form-label">Monitor Type</label> | ||||
|                         <select id="type" v-model="monitor.type" class="form-select" aria-label="Default select example"> | ||||
|                             <option value="http"> | ||||
|  | @ -26,17 +26,17 @@ | |||
|                         </select> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                     <div class="my-3"> | ||||
|                         <label for="name" class="form-label">Friendly Name</label> | ||||
|                         <input id="name" v-model="monitor.name" type="text" class="form-control" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="mb-3"> | ||||
|                     <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3"> | ||||
|                         <label for="url" class="form-label">URL</label> | ||||
|                         <input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div v-if="monitor.type === 'keyword' " class="mb-3"> | ||||
|                     <div v-if="monitor.type === 'keyword' " class="my-3"> | ||||
|                         <label for="keyword" class="form-label">Keyword</label> | ||||
|                         <input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required> | ||||
|                         <div class="form-text"> | ||||
|  | @ -44,22 +44,22 @@ | |||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div v-if="monitor.type === 'port' || monitor.type === 'ping' " class="mb-3"> | ||||
|                     <div v-if="monitor.type === 'port' || monitor.type === 'ping' " class="my-3"> | ||||
|                         <label for="hostname" class="form-label">Hostname</label> | ||||
|                         <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div v-if="monitor.type === 'port' " class="mb-3"> | ||||
|                     <div v-if="monitor.type === 'port' " class="my-3"> | ||||
|                         <label for="port" class="form-label">Port</label> | ||||
|                         <input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1"> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                     <div class="my-3"> | ||||
|                         <label for="interval" class="form-label">Heartbeat Interval (Every {{ monitor.interval }} seconds)</label> | ||||
|                         <input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1"> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3"> | ||||
|                     <div class="my-3"> | ||||
|                         <label for="maxRetries" class="form-label">Retries</label> | ||||
|                         <input id="maxRetries" v-model="monitor.maxretries" type="number" class="form-control" required min="0" step="1"> | ||||
|                         <div class="form-text"> | ||||
|  | @ -67,16 +67,16 @@ | |||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <h2>Advanced</h2> | ||||
|                     <h2 class="my-3">Advanced</h2> | ||||
| 
 | ||||
|                     <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="mb-3 form-check"> | ||||
|                     <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check"> | ||||
|                         <input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value=""> | ||||
|                         <label class="form-check-label" for="ignore-tls"> | ||||
|                             Ignore TLS/SSL error for HTTPS websites | ||||
|                         </label> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="mb-3 form-check"> | ||||
|                     <div class="my-3 form-check"> | ||||
|                         <input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox"> | ||||
|                         <label class="form-check-label" for="upside-down"> | ||||
|                             Upside Down Mode | ||||
|  | @ -86,6 +86,24 @@ | |||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="my-3"> | ||||
|                         <label for="maxRedirects" class="form-label">Max. Redirects</label> | ||||
|                         <input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1"> | ||||
|                         <div class="form-text"> | ||||
|                             Maximum number of redirects to follow. Set to 0 to disable redirects. | ||||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="my-3"> | ||||
|                         <label for="acceptedStatusCodes" class="form-label">Accepted Status Codes</label> | ||||
|                         <VueMultiselect id="acceptedStatusCodes" v-model="monitor.accepted_statuscodes" :options="acceptedStatusCodeOptions" :multiple="true" :close-on-select="false" :clear-on-select="false" :preserve-search="true" placeholder="Pick Accepted Status Codes..." :preselect-first="false"> | ||||
|                             <template #selection="{ values, isOpen }"><span v-if="values.length && !isOpen" class="multiselect__single">{{ values.length }} options selected</span></template> | ||||
|                         </VueMultiselect> | ||||
|                         <div class="form-text"> | ||||
|                             Select status codes which are considered as a successful response. | ||||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div> | ||||
|                         <button class="btn btn-primary" type="submit" :disabled="processing"> | ||||
|                             Save | ||||
|  | @ -101,7 +119,7 @@ | |||
|                         Not available, please setup. | ||||
|                     </p> | ||||
| 
 | ||||
|                     <div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch mb-3"> | ||||
|                     <div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch my-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"> | ||||
|  | @ -124,18 +142,31 @@ | |||
| <script> | ||||
| import NotificationDialog from "../components/NotificationDialog.vue"; | ||||
| import { useToast } from "vue-toastification" | ||||
| import VueMultiselect from "vue-multiselect" | ||||
| const toast = useToast() | ||||
| 
 | ||||
| export default { | ||||
|     components: { | ||||
|         NotificationDialog, | ||||
|         VueMultiselect | ||||
|     }, | ||||
|     data() { | ||||
|         let acceptedStatusCodeOptions = [ | ||||
|             "100-199", | ||||
|             "200-299", | ||||
|             "300-399", | ||||
|             "400-499", | ||||
|             "500-599", | ||||
|         ]; | ||||
|         for (let i = 100; i <= 999; i++) { | ||||
|             acceptedStatusCodeOptions.push(i.toString()); | ||||
|         } | ||||
|         return { | ||||
|             processing: false, | ||||
|             monitor: { | ||||
|                 notificationIDList: {}, | ||||
|             }, | ||||
|             acceptedStatusCodeOptions, | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|  | @ -170,6 +201,8 @@ export default { | |||
|                     notificationIDList: {}, | ||||
|                     ignoreTls: false, | ||||
|                     upsideDown: false, | ||||
|                     maxredirects: 10, | ||||
|                     accepted_statuscodes: ["200-299"], | ||||
|                 } | ||||
|             } else if (this.isEdit) { | ||||
|                 this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => { | ||||
|  | @ -209,6 +242,18 @@ export default { | |||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style src="vue-multiselect/dist/vue-multiselect.css"></style> | ||||
| 
 | ||||
| <style> | ||||
|     .multiselect__tags { | ||||
|         border-radius: 2rem; | ||||
|         border: 1px solid #ced4da; | ||||
|     } | ||||
|     .multiselect--active .multiselect__tags { | ||||
|         border-radius: 1rem; | ||||
|     } | ||||
| </style> | ||||
| 
 | ||||
| <style scoped> | ||||
|     .shadow-box { | ||||
|         padding: 20px; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue