Browse Source

Merge remote-tracking branch 'bertyhell/feature/monitor-checks' into feature/monitor-checks

bertyhell/feature/monitor-checks
Bert Verhelst 3 years ago
parent
commit
5943c76dfb
  1. 2
      db/patch-add-monitor-checks-table.sql
  2. 114
      package-lock.json
  3. 1
      package.json
  4. 19
      server/model/monitor.js
  5. 102
      server/model/validate-monitor-checks.js
  6. 42
      server/server.js
  7. 141
      src/components/MonitorCheckEditor.vue
  8. 6
      src/pages/Details.vue
  9. 62
      src/pages/EditMonitor.vue

2
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

114
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"
}

1
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",

19
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 {

102
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;

42
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);

141
src/components/MonitorCheckEditor.vue

@ -0,0 +1,141 @@
<template>
<div class="monitor-check mb-4">
<div>
<select id="type" v-model="monitorCheck.type" :class="{'form-select': true, 'mb-1': !!monitorCheck.type}">
<option value="HTTP_STATUS_CODE_SHOULD_EQUAL">
{{ $t("HTTP status code should equal") }}
</option>
<option value="RESPONSE_SHOULD_CONTAIN_TEXT">
{{ $t("Response should contain text") }}
</option>
<option value="RESPONSE_SHOULD_NOT_CONTAIN_TEXT">
{{ $t("Response should not contain text") }}
</option>
<option value="RESPONSE_SHOULD_MATCH_REGEX">
{{ $t("Response should match regex") }}
</option>
<option value="RESPONSE_SHOULD_NOT_MATCH_REGEX">
{{ $t("Response should not match regex") }}
</option>
<option value="RESPONSE_SELECTOR_SHOULD_EQUAL">
{{ $t("Response selector should equal") }}
</option>
<option value="RESPONSE_SELECTOR_SHOULD_NOT_EQUAL">
{{ $t("Response selector should not equal") }}
</option>
<option value="RESPONSE_SELECTOR_SHOULD_MATCH_REGEX">
{{ $t("Response selector should match regex") }}
</option>
<option value="RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX">
{{ $t("Response selector should not match regex") }}
</option>
</select>
<div v-if="monitorCheck.type === 'HTTP_STATUS_CODE_SHOULD_EQUAL'">
<VueMultiselect
id="acceptedStatusCodes"
v-model="monitorCheck.value"
:options="acceptedStatusCodeOptions"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick Accepted Status Codes..."
:preselect-first="false"
:max-height="600"
:taggable="true"
></VueMultiselect>
</div>
<div v-if="monitorCheck.type === 'RESPONSE_SHOULD_CONTAIN_TEXT' || monitorCheck.type === 'RESPONSE_SHOULD_NOT_CONTAIN_TEXT'">
<input v-model="monitorCheck.value" type="text" class="form-control" required :placeholder="$t('Value')">
</div>
<div v-if="monitorCheck.type === 'RESPONSE_SHOULD_MATCH_REGEX' || monitorCheck.type === 'RESPONSE_SHOULD_NOT_MATCH_REGEX'">
<input v-model="monitorCheck.value" type="text" class="form-control" required
:placeholder="$t('Regexp, Example: [a-z0-9.]+@gmail\.com')">
</div>
<div
v-if="monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_EQUAL' || monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_NOT_EQUAL'">
<input v-model="monitorCheck.value.selector" type="text" class="form-control mb-1" required
:placeholder="$t('Selector, Example: customer.address.street')">
<input v-model="monitorCheck.value.value" type="text" class="form-control" required :placeholder="$t('Value, Example: First street')">
</div>
<div
v-if="monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_MATCH_REGEX' || monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX'">
<input v-model="monitorCheck.value.selector" type="text" class="form-control mb-1" required
:placeholder="$t('Selector, Example: customer.contactInfo.email')">
<input v-model="monitorCheck.value.value" type="text" class="form-control" required
:placeholder="$t('Regexp, Example: [a-z0-9.]+@gmail\.com')">
</div>
</div>
<button class="btn btn-outline-danger" type="button" @click="deleteMonitorCheck">
<font-awesome-icon icon="times" />
</button>
</div>
</template>
<script>
import VueMultiselect from "vue-multiselect";
export default {
components: {
VueMultiselect,
},
props: {
monitorCheck: {
type: Object,
},
},
data() {
return {
acceptedStatusCodeOptions: [],
}
},
mounted() {
let acceptedStatusCodeOptions = [
"100-199",
"200-299",
"300-399",
"400-499",
"500-599",
];
for (let i = 100; i <= 999; i++) {
acceptedStatusCodeOptions.push(i.toString());
}
this.acceptedStatusCodeOptions = acceptedStatusCodeOptions;
},
methods: {
deleteMonitorCheck() {
this.$emit('delete');
},
},
}
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.monitor-check {
display: flex;
input,
select {
border-radius: 19px 0 0 19px;
}
button {
margin-left: 0.25rem;
padding-left: 15px;
padding-right: 15px;
border-radius: 0 19px 19px 0;
}
}
</style>
<style lang="scss">
.monitor-check {
.multiselect__tags {
border-radius: 19px 0 0 19px;
}
}
</style>

6
src/pages/Details.vue

@ -6,13 +6,9 @@
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
</div>
<p class="url">
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a>
<a v-if="monitor.type === 'http'" :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>
<span>{{ $t("Keyword") }}:</span> <span class="keyword">{{ monitor.keyword }}</span>
</span>
<span v-if="monitor.type === 'dns'">[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
<br>
<span>{{ $t("Last Result") }}:</span> <span class="keyword">{{ monitor.dns_last_result }}</span>

62
src/pages/EditMonitor.vue

@ -12,19 +12,16 @@
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
<select id="type" v-model="monitor.type" class="form-select">
<option value="http">
HTTP(s)
{{ $t("HTTP(s)") }}
</option>
<option value="port">
TCP Port
{{ $t("TCP Port") }}
</option>
<option value="ping">
Ping
</option>
<option value="keyword">
HTTP(s) - {{ $t("Keyword") }}
{{ $t("Ping") }}
</option>
<option value="dns">
DNS
{{ $t("DNS") }}
</option>
</select>
</div>
@ -34,19 +31,11 @@
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
</div>
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
<div v-if="monitor.type === 'http'" class="my-3">
<label for="url" class="form-label">{{ $t("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="my-3">
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
<div class="form-text">
{{ $t("keywordDescription") }}
</div>
</div>
<!-- TCP Port / Ping / DNS only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
@ -116,7 +105,7 @@
<h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
<div v-if="monitor.type === 'http'" 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">
{{ $t("ignoreTLSError") }}
@ -133,8 +122,8 @@
</div>
</div>
<!-- HTTP / Keyword only -->
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
<!-- HTTP only -->
<template v-if="monitor.type === 'http'">
<div class="my-3">
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
<input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
@ -143,26 +132,13 @@
</div>
</div>
<div class="my-3">
<label for="acceptedStatusCodes" class="form-label">{{ $t("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"
:max-height="600"
:taggable="true"
></VueMultiselect>
<h2 class="mt-5 mb-2">{{ $t("Checks") }}</h2>
<div class="form-text">
{{ $t("acceptedStatusCodesDescription") }}
<div class="my-3">
<div v-for="(monitorCheck, index) in monitor.checks" :key="index" class="mb-3">
<MonitorCheckEditor :monitorCheck="monitorCheck" :index="index" @delete="deleteMonitorCheck(index)"></MonitorCheckEditor>
</div>
<button class="btn btn-light" type="button" @click="addMonitorCheck()">{{ $t("Add check") }}</button>
</div>
</template>
@ -210,6 +186,7 @@
<script>
import NotificationDialog from "../components/NotificationDialog.vue";
import TagsManager from "../components/TagsManager.vue";
import MonitorCheckEditor from "../components/MonitorCheckEditor.vue";
import { useToast } from "vue-toastification"
import VueMultiselect from "vue-multiselect"
import { isDev } from "../util.ts";
@ -219,6 +196,7 @@ export default {
components: {
NotificationDialog,
TagsManager,
MonitorCheckEditor,
VueMultiselect,
},
@ -341,7 +319,17 @@ export default {
}
})
}
},
addMonitorCheck() {
this.monitor.checks = [...(this.monitor.checks || []), {
type: null,
value: '',
}];
},
deleteMonitorCheck(index) {
this.monitor.checks = this.monitor.checks.splice(index, 1);
},
async submit() {

Loading…
Cancel
Save