diff --git a/db/patch-add-monitor-checks-table.sql b/db/patch-add-monitor-checks-table.sql index 07322ea0e..20ab785ed 100644 --- a/db/patch-add-monitor-checks-table.sql +++ b/db/patch-add-monitor-checks-table.sql @@ -8,7 +8,7 @@ create table monitor_checks constraint monitor_checks_pk primary key autoincrement, type VARCHAR(50) not null, - value TEXTt, + value TEXT, monitor_id INTEGER not null constraint monitor_checks_monitor_id_fk references monitor diff --git a/package-lock.json b/package-lock.json index 511f02d24..ec055bb2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,10 @@ { "name": "uptime-kuma", "version": "1.6.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, +<<<<<<< HEAD +======= "packages": { "": { "name": "uptime-kuma", @@ -7946,6 +7948,7 @@ } } }, +>>>>>>> master "dependencies": { "@babel/code-frame": { "version": "7.12.11", @@ -8991,7 +8994,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -10294,8 +10296,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "devOptional": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { "version": "1.3.0", @@ -10306,8 +10307,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.7", @@ -10325,8 +10325,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -10973,8 +10972,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "devOptional": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-unicode-supported": { "version": "0.1.0", @@ -10990,8 +10988,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "devOptional": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { "version": "0.1.2", @@ -11042,8 +11039,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -11489,7 +11485,8 @@ "nanoid": { "version": "3.1.25", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==" + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "dev": true }, "natural-compare": { "version": "1.4.0", @@ -12314,8 +12311,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "devOptional": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qrcode": { "version": "1.4.4", @@ -12917,6 +12913,7 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, +<<<<<<< HEAD "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -12924,6 +12921,13 @@ "requires": { "safe-buffer": "~5.1.0" } +======= + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", + "dev": true +>>>>>>> monitor-checks }, "string-width": { "version": "1.0.2", @@ -12950,6 +12954,14 @@ } } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -13556,7 +13568,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "devOptional": true, "requires": { "punycode": "^2.1.0" } @@ -13677,12 +13688,68 @@ "resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-0.5.8.tgz", "integrity": "sha512-VJEBTdMgWOaYqekXtz4LVBIeYyIx3qDlQnFyY4Ao1GwizokYZBycCeRN3oKDcYbbZi5yxYqTy6+Tm+m+SOPUPA==", "requires": { - "@vue/runtime-core": "latest", - "@vue/runtime-dom": "latest", - "csstype": "latest", - "lodash": "latest", - "nanoid": "latest", + "@vue/runtime-core": "^3.2.11", + "@vue/runtime-dom": "^3.2.11", + "csstype": "^3.0.9", + "lodash": "^4.17.21", + "nanoid": "^3.1.25", "vue-demi": "^0.10.1" + }, + "dependencies": { + "@vue/reactivity": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.11.tgz", + "integrity": "sha512-hEQstxPQbgGZq5qApzrvbDmRdK1KP96O/j4XrwT8fVkT1ytkFs4fH2xNEh9QKwXfybbQkLs77W7OfXCv5o6qbA==", + "requires": { + "@vue/shared": "3.2.11" + } + }, + "@vue/runtime-core": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.11.tgz", + "integrity": "sha512-horlxjWwSvModC87WdsWswzzHE5IexmKkQA65S5vFgP5hLUBW+HRyScDeuB/RRcFmqnf+ozacNCfap0kqcpODw==", + "requires": { + "@vue/reactivity": "3.2.11", + "@vue/shared": "3.2.11" + } + }, + "@vue/runtime-dom": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.11.tgz", + "integrity": "sha512-cOK1g0INdiCbds2xrrJKrrN+pDHuLz6esUs/crdEiupDuX7IeiMbdqrAQCkYHp5P1KLWcbGlkmwfVD7HQGii0Q==", + "requires": { + "@vue/runtime-core": "3.2.11", + "@vue/shared": "3.2.11", + "csstype": "^2.6.8" + }, + "dependencies": { + "csstype": { + "version": "2.6.18", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", + "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" + } + } + }, + "@vue/shared": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.11.tgz", + "integrity": "sha512-ovfXAsSsCvV9JVceWjkqC/7OF5HbgLOtCWjCIosmPGG8lxbPuavhIxRH1dTx4Dg9xLgRTNLvI3pVxG4ItQZekg==" + }, + "csstype": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", + "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "nanoid": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==" + } } }, "vue-confirm-dialog": { @@ -13804,7 +13871,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, "requires": { "isexe": "^2.0.0" } diff --git a/package.json b/package.json index 5c3f8e034..95febd5af 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "form-data": "^4.0.0", "http-graceful-shutdown": "^3.1.4", "jsonwebtoken": "^8.5.1", + "lodash.get": "^4.4.2", "nodemailer": "^6.6.5", "notp": "^2.0.3", "password-hash": "^1.2.2", diff --git a/server/model/monitor.js b/server/model/monitor.js index 9a80225e0..6e2dfaf65 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -7,10 +7,11 @@ dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); -const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom } = require("../util-server"); +const { tcping, ping, dnsResolve, checkCertificate, getTotalClientInRoom } = require("../util-server"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); -const { Notification } = require("../notification"); +const { Notification } = require("../notification") +const validateMonitorChecks = require("./validate-monitor-checks"); const version = require("../../package.json").version; /** @@ -61,15 +62,14 @@ class Monitor extends BeanModel { type: this.type, interval: this.interval, retryInterval: this.retryInterval, - keyword: this.keyword, ignoreTls: this.getIgnoreTls(), upsideDown: this.isUpsideDown(), maxredirects: this.maxredirects, - accepted_statuscodes: this.getAcceptedStatuscodes(), dns_resolve_type: this.dns_resolve_type, dns_resolve_server: this.dns_resolve_server, dns_last_result: this.dns_last_result, notificationIDList, + checks: this.checks, tags: tags, }; } @@ -90,10 +90,6 @@ class Monitor extends BeanModel { return Boolean(this.upsideDown); } - getAcceptedStatuscodes() { - return JSON.parse(this.accepted_statuscodes_json); - } - start(io) { let previousBeat = null; let retries = 0; @@ -131,7 +127,7 @@ class Monitor extends BeanModel { } try { - if (this.type === "http" || this.type === "keyword") { + if (this.type === "http") { // Do not do any queries/high loading things before the "bean.ping" let startTime = dayjs().valueOf(); @@ -146,9 +142,6 @@ class Monitor extends BeanModel { rejectUnauthorized: ! this.getIgnoreTls(), }), maxRedirects: this.maxredirects, - validateStatus: (status) => { - return checkStatusCode(status, this.getAcceptedStatuscodes()); - }, }); bean.msg = `${res.status} - ${res.statusText}`; bean.ping = dayjs().valueOf() - startTime; @@ -167,6 +160,8 @@ class Monitor extends BeanModel { debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); + + validateMonitorChecks(res, this.checks, bean); if (this.type === "http") { bean.status = UP; } else { diff --git a/server/model/validate-monitor-checks.js b/server/model/validate-monitor-checks.js new file mode 100644 index 000000000..b43098c1c --- /dev/null +++ b/server/model/validate-monitor-checks.js @@ -0,0 +1,102 @@ +const { checkStatusCode } = require("../util-server"); +const { UP } = require("../../src/util"); +const get = require("lodash.get"); + +function validateMonitorChecks(res, checks, bean) { + const responseText = typeof data === "string" ? res.data : JSON.stringify(res.data); + let checkObj; + + this.checks.forEach(check => { + switch (check.type) { + case "HTTP_STATUS_CODE_SHOULD_EQUAL": + if (checkStatusCode(res.status, check.value)) { + bean.msg += `, status matches '${check.value}'` + bean.status = UP; + } else { + throw new Error(bean.msg + ", but status code dit not match " + check.value) + } + break; + + case "RESPONSE_SHOULD_CONTAIN_TEXT": + if (responseText.includes(check.value)) { + bean.msg += `, response contains '${check.value}'` + bean.status = UP; + } else { + throw new Error(bean.msg + ", but response does not contain '" + check.value + "'"); + } + break; + + case "RESPONSE_SHOULD_NOT_CONTAIN_TEXT": + if (!responseText.includes(check.value)) { + bean.msg += `, response does not contain '${check.value}'` + bean.status = UP; + } else { + throw new Error(bean.msg + ", but response does contain '" + check.value + "'"); + } + break; + + case "RESPONSE_SHOULD_MATCH_REGEX": + if (responseText.test(new RegExp(check.value))) { + bean.msg += `, regex '${check.value}' matches` + bean.status = UP; + } else { + throw new Error(bean.msg + ", but response does not match regex: '" + check.value + "'"); + } + break; + + case "RESPONSE_SHOULD_NOT_MATCH_REGEX": + if (!responseText.test(new RegExp(check.value))) { + bean.msg += `, regex '${check.value}' does not matches` + bean.status = UP; + } else { + throw new Error(bean.msg + ", but response does match regex: '" + check.value + "'"); + } + break; + + case "RESPONSE_SELECTOR_SHOULD_EQUAL": + checkObj = JSON.parse(check.value); + if (get(res, checkObj.selector) === checkObj.value) { + bean.msg += `, response selector equals '${checkObj.value}'` + bean.status = UP; + } else { + throw new Error(`${bean.msg}, but response selector '${checkObj.selector}' does not equal '${checkObj.value}'`); + } + break; + + case "RESPONSE_SELECTOR_SHOULD_NOT_EQUAL": + checkObj = JSON.parse(check.value); + if (get(res, checkObj.selector) !== checkObj.value) { + bean.msg += `, response selector does not equal '${checkObj.value}'` + bean.status = UP; + } else { + throw new Error(`${bean.msg}, but response selector '${checkObj.selector}' does equal '${checkObj.value}'`); + } + break; + + case "RESPONSE_SELECTOR_SHOULD_MATCH_REGEX": + checkObj = JSON.parse(check.value); + if (get(res, checkObj.selector).test(new RegExp(checkObj.value))) { + bean.msg += `, response selector matches regex '${checkObj.value}'` + bean.status = UP; + } else { + throw new Error(`${bean.msg}, but response selector '${checkObj.selector}' does not match regex '${checkObj.value}'`); + } + break; + + case "RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX": + checkObj = JSON.parse(check.value); + if (!get(res, checkObj.selector).test(new RegExp(checkObj.value))) { + bean.msg += `, response selector does not match regex '${checkObj.value}'` + bean.status = UP; + } else { + throw new Error(`${bean.msg}, but response selector '${checkObj.selector}' does match regex '${checkObj.value}'`); + } + break; + + default: + throw new Error(`${bean.msg}, encountered unknown monitor_check.type`); + } + }); +} + +module.exports = validateMonitorChecks; diff --git a/server/server.js b/server/server.js index f5a8b16e3..c8101b62e 100644 --- a/server/server.js +++ b/server/server.js @@ -6,7 +6,7 @@ if (! process.env.NODE_ENV) { console.log("Node Env: " + process.env.NODE_ENV); -const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util"); +const { sleep, debug, getRandomInt } = require("../src/util"); console.log("Importing Node libraries"); const fs = require("fs"); @@ -459,8 +459,8 @@ exports.entryPage = "dashboard"; let notificationIDList = monitor.notificationIDList; delete monitor.notificationIDList; - monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); - delete monitor.accepted_statuscodes; + monitor.checks_json = JSON.stringify(monitor.checks); + delete monitor.checks; bean.import(monitor); bean.user_id = socket.userID; @@ -504,13 +504,12 @@ exports.entryPage = "dashboard"; bean.hostname = monitor.hostname; bean.maxretries = monitor.maxretries; bean.port = monitor.port; - bean.keyword = monitor.keyword; bean.ignoreTls = monitor.ignoreTls; bean.upsideDown = monitor.upsideDown; bean.maxredirects = monitor.maxredirects; - bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); bean.dns_resolve_type = monitor.dns_resolve_type; bean.dns_resolve_server = monitor.dns_resolve_server; + bean.checks_json = JSON.stringify(monitor.checks); await R.store(bean); @@ -1015,22 +1014,19 @@ exports.entryPage = "dashboard"; // --- End --- let monitor = { - // Define the new variable from earlier here - name: monitorListData[i].name, - type: monitorListData[i].type, - url: monitorListData[i].url, - interval: monitorListData[i].interval, - retryInterval: retryInterval, - hostname: monitorListData[i].hostname, - maxretries: monitorListData[i].maxretries, - port: monitorListData[i].port, - keyword: monitorListData[i].keyword, - ignoreTls: monitorListData[i].ignoreTls, - upsideDown: monitorListData[i].upsideDown, - maxredirects: monitorListData[i].maxredirects, - accepted_statuscodes: monitorListData[i].accepted_statuscodes, - dns_resolve_type: monitorListData[i].dns_resolve_type, - dns_resolve_server: monitorListData[i].dns_resolve_server, + name: monitorList[i].name, + type: monitorList[i].type, + url: monitorList[i].url, + interval: monitorList[i].interval, + hostname: monitorList[i].hostname, + maxretries: monitorList[i].maxretries, + port: monitorList[i].port, + ignoreTls: monitorList[i].ignoreTls, + upsideDown: monitorList[i].upsideDown, + maxredirects: monitorList[i].maxredirects, + checks: monitorList[i].checks, + dns_resolve_type: monitorList[i].dns_resolve_type, + dns_resolve_server: monitorList[i].dns_resolve_server, notificationIDList: {}, }; @@ -1038,8 +1034,8 @@ exports.entryPage = "dashboard"; let notificationIDList = monitor.notificationIDList; delete monitor.notificationIDList; - - monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); + + monitor.checks_json = JSON.stringify(monitor.checks); delete monitor.accepted_statuscodes; bean.import(monitor); diff --git a/src/components/MonitorCheckEditor.vue b/src/components/MonitorCheckEditor.vue new file mode 100644 index 000000000..72b38de9f --- /dev/null +++ b/src/components/MonitorCheckEditor.vue @@ -0,0 +1,141 @@ + + + + + + + diff --git a/src/pages/Details.vue b/src/pages/Details.vue index c93e47d0b..1885493ef 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -6,13 +6,9 @@

- {{ monitor.url }} + {{ monitor.url }} TCP Ping {{ monitor.hostname }}:{{ monitor.port }} Ping: {{ monitor.hostname }} - -
- {{ $t("Keyword") }}: {{ monitor.keyword }} -
[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
{{ $t("Last Result") }}: {{ monitor.dns_last_result }} diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 84231b1ae..2e60073cf 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -12,19 +12,16 @@ @@ -34,19 +31,11 @@ -

+
-
- - -
- {{ $t("keywordDescription") }} -
-
-
@@ -116,7 +105,7 @@

{{ $t("Advanced") }}

-
+
- -