diff --git a/db/patch-http-monitor-method-body-and-headers.sql b/db/patch-http-monitor-method-body-and-headers.sql new file mode 100644 index 000000000..dc2526b4f --- /dev/null +++ b/db/patch-http-monitor-method-body-and-headers.sql @@ -0,0 +1,13 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD method TEXT default 'GET' not null; + +ALTER TABLE monitor + ADD body TEXT default null; + +ALTER TABLE monitor + ADD headers TEXT default null; + +COMMIT; diff --git a/server/database.js b/server/database.js index 47eca2835..297df655a 100644 --- a/server/database.js +++ b/server/database.js @@ -49,6 +49,7 @@ class Database { "patch-incident-table.sql": true, "patch-group-table.sql": true, "patch-monitor-push_token.sql": true, + "patch-http-monitor-method-body-and-headers.sql": true, } /** diff --git a/server/model/monitor.js b/server/model/monitor.js index c551fa7d7..dc95753db 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -53,6 +53,9 @@ class Monitor extends BeanModel { id: this.id, name: this.name, url: this.url, + method: this.method, + body: this.body, + headers: this.headers, hostname: this.hostname, port: this.port, maxretries: this.maxretries, @@ -95,6 +98,31 @@ class Monitor extends BeanModel { return JSON.parse(this.accepted_statuscodes_json); } + /** + * Convert header string into an object: + * eg: + * + * Authorization: Basic + * Content-Type: application/json + * + * into + * + * { + * "Authorization": "Basic ", + * "Content-Type": "application/json" + * } + **/ + getParsedHeaders() { + if (!this.headers || !this.headers.includes(":")) { + return {}; + } + return Object.fromEntries(this.headers.split("\n").map(header => { + const trimmedHeader = header.trim(); + const firstColonIndex = trimmedHeader.indexOf(":"); + return [trimmedHeader.slice(0, firstColonIndex), trimmedHeader.slice(firstColonIndex + 1) || ""]; + }).filter(arr => !!arr[0] && !!arr[1])); + } + start(io) { let previousBeat = null; let retries = 0; @@ -136,11 +164,15 @@ class Monitor extends BeanModel { // Do not do any queries/high loading things before the "bean.ping" let startTime = dayjs().valueOf(); - let res = await axios.get(this.url, { + const options = { + url: this.url, + method: (this.method || "get").toLowerCase(), + ...(this.body ? { data: JSON.parse(this.body) } : {}), timeout: this.interval * 1000 * 0.8, headers: { "Accept": "*/*", "User-Agent": "Uptime-Kuma/" + version, + ...this.getParsedHeaders(), }, httpsAgent: new https.Agent({ maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940) @@ -150,7 +182,8 @@ class Monitor extends BeanModel { validateStatus: (status) => { return checkStatusCode(status, this.getAcceptedStatuscodes()); }, - }); + }; + let res = await axios.request(options); bean.msg = `${res.status} - ${res.statusText}`; bean.ping = dayjs().valueOf() - startTime; diff --git a/server/server.js b/server/server.js index 0fbe8325b..6d1a939f3 100644 --- a/server/server.js +++ b/server/server.js @@ -505,6 +505,9 @@ exports.entryPage = "dashboard"; bean.name = monitor.name; bean.type = monitor.type; bean.url = monitor.url; + bean.method = monitor.method; + bean.body = monitor.body; + bean.headers = monitor.headers; bean.interval = monitor.interval; bean.retryInterval = monitor.retryInterval; bean.hostname = monitor.hostname; @@ -1028,6 +1031,9 @@ exports.entryPage = "dashboard"; name: monitorListData[i].name, type: monitorListData[i].type, url: monitorListData[i].url, + method: monitorListData[i].method || "GET", + body: monitorListData[i].body, + headers: monitorListData[i].headers, interval: monitorListData[i].interval, retryInterval: retryInterval, hostname: monitorListData[i].hostname, diff --git a/src/assets/app.scss b/src/assets/app.scss index 34a4560c5..fdb57b431 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -14,6 +14,10 @@ h2 { font-size: 26px; } +textarea.form-control { + border-radius: 19px; +} + ::-webkit-scrollbar { width: 10px; } @@ -413,4 +417,4 @@ h2 { // Localization -@import "localization.scss"; \ No newline at end of file +@import "localization.scss"; diff --git a/src/assets/multiselect.scss b/src/assets/multiselect.scss index 300230769..53b47c16e 100644 --- a/src/assets/multiselect.scss +++ b/src/assets/multiselect.scss @@ -21,7 +21,7 @@ } .multiselect__tag { - border-radius: 50rem; + border-radius: $border-radius; margin-bottom: 0; padding: 6px 26px 6px 10px; background: $primary !important; diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index 4dc2c712c..e62b95dfb 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -186,7 +186,7 @@ export default { .beat { display: inline-block; background-color: $primary; - border-radius: 50rem; + border-radius: $border-radius; &.empty { background-color: aliceblue; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 96f221e8b..e63f49862 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -44,6 +44,46 @@ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
@@ -285,6 +325,18 @@ export default { pushURL() { return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?msg=OK&ping="; + }, + + bodyPlaceholder() { + return `{ + "id": 124357, + "username": "admin", + "password": "myAdminPassword" +}`; + }, + + headersPlaceholder() { + return "Authorization: Bearer abc123\nContent-Type: application/json"; } }, @@ -295,7 +347,7 @@ export default { }, "monitor.interval"(value, oldValue) { - // Link interval and retryInerval if they are the same value. + // Link interval and retryInterval if they are the same value. if (this.monitor.retryInterval === oldValue) { this.monitor.retryInterval = value; } @@ -349,6 +401,7 @@ export default { type: "http", name: "", url: "https://", + method: "GET", interval: 60, retryInterval: this.interval, maxretries: 0, @@ -383,9 +436,32 @@ export default { }, + isInputValid() { + if (this.monitor.body) { + try { + JSON.parse(this.monitor.body); + } catch (err) { + toast.error(this.$t("The request body is not valid json: ") + err.message); + return false; + } + } + if (this.monitor.headers) { + if (!/^([^:]+:.*)([\s]+[^:]+:.*)+$/g.test(this.monitor.headers)) { + toast.error(this.$t("Headers do not have a valid format: \"key: valuekey: value...\"")); + return false; + } + } + return true; + }, + async submit() { this.processing = true; + if (!this.isInputValid()) { + this.processing = false; + return; + } + if (this.isAdd) { this.$root.add(this.monitor, async (res) => { @@ -422,8 +498,12 @@ export default { }; -