diff --git a/db/patch-add-monitor-checks-table.sql b/db/patch-add-monitor-checks-table.sql
new file mode 100644
index 0000000..e032ee7
--- /dev/null
+++ b/db/patch-add-monitor-checks-table.sql
@@ -0,0 +1,42 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+-- Create new monitor_checks table
+create table monitor_checks
+(
+ id INTEGER
+ constraint monitor_checks_pk
+ primary key autoincrement,
+ type VARCHAR(50) not null,
+ value TEXT,
+ monitor_id INTEGER NOT NULL,
+ CONSTRAINT "monitor_checks_monitor_id_fk"
+ FOREIGN KEY ("monitor_id")
+ REFERENCES "monitor" ("id")
+ ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+create unique index monitor_checks_id_uindex
+ on monitor_checks (id);
+
+
+-- Copy over the http status to the new monitor_checks table as a separate check
+insert into monitor_checks(monitor_id, type, value)
+select id, 'HTTP_STATUS_CODE_SHOULD_EQUAL', accepted_statuscodes_json
+from monitor;
+
+-- Copy over the keyword column from the monitor table to the new monitor_checks table as a separate check
+insert into monitor_checks(monitor_id, type, value)
+select id, 'RESPONSE_SHOULD_CONTAIN_TEXT', keyword
+from monitor
+WHERE monitor.type = 'keyword';
+
+-- Delete the http status and keyword columns from the monitor table
+ALTER TABLE monitor DROP COLUMN accepted_statuscodes_json;
+ALTER TABLE monitor DROP COLUMN keyword;
+
+UPDATE monitor SET type = 'http' WHERE type = 'keyword';
+
+COMMIT;
+
+
diff --git a/server/database.js b/server/database.js
index 41d91e8..d7e86f7 100644
--- a/server/database.js
+++ b/server/database.js
@@ -4,6 +4,7 @@ const { setSetting, setting } = require("./util-server");
const { debug, sleep } = require("../src/util");
const dayjs = require("dayjs");
const knex = require("knex");
+const MonitorCheck = require("./model/monitor-check");
/**
* Database & App Data Folder
@@ -52,6 +53,7 @@ class Database {
"patch-http-monitor-method-body-and-headers.sql": true,
"patch-2fa-invalidate-used-token.sql": true,
"patch-notification_sent_history.sql": true,
+ "patch-add-monitor-checks-table.sql": true,
}
/**
@@ -110,6 +112,7 @@ class Database {
// Auto map the model to a bean object
R.freeze(true);
await R.autoloadModels("./server/model");
+ R.modelList["monitor_checks"] = MonitorCheck;
await R.exec("PRAGMA foreign_keys = ON");
// Change to WAL
diff --git a/server/model/monitor-check.js b/server/model/monitor-check.js
new file mode 100644
index 0000000..0bcf887
--- /dev/null
+++ b/server/model/monitor-check.js
@@ -0,0 +1,16 @@
+const { BeanModel } = require("redbean-node/dist/bean-model");
+
+class MonitorCheck extends BeanModel {
+ /**
+ * Return a object that ready to parse to JSON
+ */
+ async toJSON() {
+ return {
+ id: this.id,
+ type: this.type,
+ value: this.value,
+ };
+ }
+}
+
+module.exports = MonitorCheck;
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 980e0ac..82d020c 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -6,12 +6,33 @@ dayjs.extend(utc);
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, setting, errorLog } = require("../util-server");
+const {
+ debug,
+ UP,
+ DOWN,
+ PENDING,
+ flipStatus,
+ TimeLogger,
+ MONITOR_CHECK_HTTP_CODE_TYPES,
+ MONITOR_CHECK_SELECTOR_TYPES,
+ HTTP_STATUS_CODE_SHOULD_EQUAL,
+ RESPONSE_SHOULD_CONTAIN_TEXT,
+} = require("../../src/util");
+const {
+ tcping,
+ ping,
+ dnsResolve,
+ checkCertificate,
+ checkStatusCode,
+ getTotalClientInRoom,
+ setting,
+ errorLog,
+} = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
const { demoMode } = require("../config");
+const validateMonitorChecks = require("../validate-monitor-checks");
const version = require("../../package.json").version;
const apicache = require("../modules/apicache");
@@ -50,6 +71,7 @@ class Monitor extends BeanModel {
}
const tags = await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]);
+ const checks = await this.getMonitorChecks();
return {
id: this.id,
@@ -66,17 +88,20 @@ 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,
pushToken: this.pushToken,
notificationIDList,
+ checks: checks,
tags: tags,
+
+ // Deprecated: Use the values in the checks list instead
+ accepted_statuscodes: ((this.checks || []).find(check => check.type === HTTP_STATUS_CODE_SHOULD_EQUAL) || {}).value,
+ keyword: ((this.checks || []).find(check => check.type === RESPONSE_SHOULD_CONTAIN_TEXT) || {}).value,
};
}
@@ -96,10 +121,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;
@@ -112,7 +133,7 @@ class Monitor extends BeanModel {
// undefined if not https
let tlsInfo = undefined;
- if (! previousBeat) {
+ if (!previousBeat) {
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
this.id,
]);
@@ -130,14 +151,14 @@ class Monitor extends BeanModel {
}
// Duration
- if (! isFirstBeat) {
+ if (!isFirstBeat) {
bean.duration = dayjs(bean.time).diff(dayjs(previousBeat.time), "second");
} else {
bean.duration = 0;
}
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();
@@ -154,12 +175,10 @@ class Monitor extends BeanModel {
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
- rejectUnauthorized: ! this.getIgnoreTls(),
+ rejectUnauthorized: !this.getIgnoreTls(),
}),
maxRedirects: this.maxredirects,
- validateStatus: (status) => {
- return checkStatusCode(status, this.getAcceptedStatuscodes());
- },
+ validateStatus: undefined,
};
debug(`[${this.name}] Axios Request`);
@@ -195,26 +214,11 @@ class Monitor extends BeanModel {
console.log(res.data);
}
- if (this.type === "http") {
- bean.status = UP;
- } else {
-
- let data = res.data;
-
- // Convert to string for object/array
- if (typeof data !== "string") {
- data = JSON.stringify(data);
- }
-
- if (data.includes(this.keyword)) {
- bean.msg += ", keyword is found";
- bean.status = UP;
- } else {
- throw new Error(bean.msg + ", but keyword is not found");
- }
+ bean.status = UP;
- }
+ validateMonitorChecks(res, await this.getMonitorChecks(), bean);
+ bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "port") {
bean.ping = await tcping(this.hostname, this.port);
bean.msg = "";
@@ -258,7 +262,7 @@ class Monitor extends BeanModel {
if (this.dnsLastResult !== dnsMessage) {
R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [
dnsMessage,
- this.id
+ this.id,
]);
}
@@ -269,7 +273,7 @@ class Monitor extends BeanModel {
let heartbeatCount = await R.count("heartbeat", " monitor_id = ? AND time > ? ", [
this.id,
- time
+ time,
]);
debug("heartbeatCount" + heartbeatCount + " " + time);
@@ -339,7 +343,7 @@ class Monitor extends BeanModel {
retries = 0;
} catch (error) {
-
+ bean.status = DOWN;
bean.msg = error.message;
// If UP come in here, it must be upside down mode
@@ -397,7 +401,7 @@ class Monitor extends BeanModel {
previousBeat = bean;
- if (! this.isStop) {
+ if (!this.isStop) {
if (demoMode) {
if (beatInterval < 20) {
@@ -526,9 +530,11 @@ class Monitor extends BeanModel {
let avgPing = parseInt(await R.getCell(`
SELECT AVG(ping)
FROM heartbeat
- WHERE time > DATETIME('now', ? || ' hours')
- AND ping IS NOT NULL
- AND monitor_id = ? `, [
+ WHERE time
+ > DATETIME('now'
+ , ? || ' hours')
+ AND ping IS NOT NULL
+ AND monitor_id = ? `, [
-duration,
monitorID,
]));
@@ -562,30 +568,31 @@ class Monitor extends BeanModel {
// e.g. If the last beat's duration is bigger that the 24hrs window, it will use the duration between the (beat time - window margin) (THEN case in SQL)
let result = await R.getRow(`
SELECT
- -- SUM all duration, also trim off the beat out of time window
+ -- SUM all duration, also trim off the beat out of time window
SUM(
CASE
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
- THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
+ THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
ELSE duration
- END
- ) AS total_duration,
+ END
+ ) AS total_duration,
- -- SUM all uptime duration, also trim off the beat out of time window
+ -- SUM all uptime duration, also trim off the beat out of time window
SUM(
CASE
WHEN (status = 1)
- THEN
+ THEN
CASE
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
ELSE duration
- END
+ END
END
- ) AS uptime_duration
+ ) AS uptime_duration
FROM heartbeat
- WHERE time > ?
- AND monitor_id = ?
+ WHERE time
+ > ?
+ AND monitor_id = ?
`, [
startTime, startTime, startTime, startTime, startTime,
monitorID,
@@ -605,7 +612,7 @@ class Monitor extends BeanModel {
} else {
// Handle new monitor with only one beat, because the beat's duration = 0
- let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ]));
+ let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [monitorID]));
if (status === UP) {
uptime = 1;
@@ -618,6 +625,9 @@ class Monitor extends BeanModel {
/**
* Send Uptime
* @param duration : int Hours
+ * @param io
+ * @param monitorID
+ * @param userID
*/
static async sendUptime(duration, io, monitorID, userID) {
const uptime = await this.calcUptime(duration, monitorID);
@@ -730,6 +740,21 @@ class Monitor extends BeanModel {
debug("No notification, no need to send cert notification");
}
}
+
+ async getMonitorChecks() {
+ const checks = await R.getAll("SELECT mc.type, mc.value FROM monitor_checks mc WHERE mc.monitor_id = ?", [this.id]);
+
+ checks.forEach(check => {
+ if (MONITOR_CHECK_HTTP_CODE_TYPES.includes(check.type) && typeof check.value === "string" && check.value.startsWith("[")) {
+ check.value = JSON.parse(check.value);
+ }
+ if (MONITOR_CHECK_SELECTOR_TYPES.includes(check.type) && typeof check.value === "string" && check.value.startsWith("{")) {
+ check.value = JSON.parse(check.value);
+ }
+ });
+
+ return checks;
+ }
}
module.exports = Monitor;
diff --git a/server/server.js b/server/server.js
index d1fd7ff..d0e87ad 100644
--- a/server/server.js
+++ b/server/server.js
@@ -5,7 +5,7 @@ const config = require("./config");
debug(args);
-if (! process.env.NODE_ENV) {
+if (!process.env.NODE_ENV) {
process.env.NODE_ENV = "production";
}
@@ -41,7 +41,17 @@ console.log("Importing this project modules");
debug("Importing Monitor");
const Monitor = require("./model/monitor");
debug("Importing Settings");
-const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog } = require("./util-server");
+const {
+ getSettings,
+ setSettings,
+ setting,
+ initJWTSecret,
+ checkLogin,
+ startUnitTest,
+ updateMonitorChecks,
+ FBSD,
+ errorLog
+} = require("./util-server");
debug("Importing Notification");
const { Notification } = require("./notification");
@@ -106,7 +116,7 @@ if (sslKey && sslCert) {
console.log("Server Type: HTTPS");
server = https.createServer({
key: fs.readFileSync(sslKey),
- cert: fs.readFileSync(sslCert)
+ cert: fs.readFileSync(sslCert),
}, app);
} else {
console.log("Server Type: HTTP");
@@ -117,7 +127,12 @@ const io = new Server(server);
module.exports.io = io;
// Must be after io instantiation
-const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo } = require("./client");
+const {
+ sendNotificationList,
+ sendHeartbeatList,
+ sendImportantHeartbeatList,
+ sendInfo,
+} = require("./client");
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
@@ -198,7 +213,7 @@ exports.entryPage = "dashboard";
// Robots.txt
app.get("/robots.txt", async (_request, response) => {
let txt = "User-agent: *\nDisallow:";
- if (! await setting("searchEngineIndex")) {
+ if (!await setting("searchEngineIndex")) {
txt += " /";
}
response.setHeader("Content-Type", "text/plain");
@@ -532,13 +547,16 @@ exports.entryPage = "dashboard";
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
- monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
- delete monitor.accepted_statuscodes;
+ const checks = monitor.checks;
+ delete monitor.checks;
+ // Store monitor info
bean.import(monitor);
bean.user_id = socket.userID;
await R.store(bean);
+ await updateMonitorChecks(bean.id, checks);
+
await updateMonitorNotification(bean.id, notificationIDList);
await startMonitor(socket.userID, bean.id);
@@ -563,7 +581,7 @@ exports.entryPage = "dashboard";
try {
checkLogin(socket);
- let bean = await R.findOne("monitor", " id = ? ", [ monitor.id ]);
+ let bean = await R.findOne("monitor", " id = ? ", [monitor.id]);
if (bean.user_id !== socket.userID) {
throw new Error("Permission denied.");
@@ -580,17 +598,19 @@ 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.pushToken = monitor.pushToken;
+ const checks = monitor.checks;
+ delete monitor.checks;
+
await R.store(bean);
+ await updateMonitorChecks(bean.id, checks);
await updateMonitorNotification(bean.id, monitor.notificationIDList);
if (bean.active) {
@@ -803,7 +823,7 @@ exports.entryPage = "dashboard";
try {
checkLogin(socket);
- let bean = await R.findOne("monitor", " id = ? ", [ tag.id ]);
+ let bean = await R.findOne("monitor", " id = ? ", [tag.id]);
bean.name = tag.name;
bean.color = tag.color;
await R.store(bean);
@@ -825,7 +845,7 @@ exports.entryPage = "dashboard";
try {
checkLogin(socket);
- await R.exec("DELETE FROM tag WHERE id = ? ", [ tagID ]);
+ await R.exec("DELETE FROM tag WHERE id = ? ", [tagID]);
callback({
ok: true,
@@ -916,7 +936,7 @@ exports.entryPage = "dashboard";
try {
checkLogin(socket);
- if (! password.newPassword) {
+ if (!password.newPassword) {
throw new Error("Invalid new password");
}
@@ -974,7 +994,7 @@ exports.entryPage = "dashboard";
callback({
ok: true,
- msg: "Saved"
+ msg: "Saved",
});
sendInfo(socket);
@@ -1132,7 +1152,6 @@ 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,
@@ -1140,15 +1159,13 @@ exports.entryPage = "dashboard";
body: monitorListData[i].body,
headers: monitorListData[i].headers,
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,
+ checks: monitorListData[i].checks,
dns_resolve_type: monitorListData[i].dns_resolve_type,
dns_resolve_server: monitorListData[i].dns_resolve_server,
notificationIDList: {},
@@ -1163,13 +1180,40 @@ exports.entryPage = "dashboard";
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
- monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
- delete monitor.accepted_statuscodes;
+ // TODO remove this if clause once we no longer expect export files to contain the old accepted_statuscodes and keyword format
+ if (monitor.type === "http" || monitor.type === "keyword") {
+ if (monitorListData[i].accepted_statuscodes) {
+ // old format for checking http status codes
+ // Convert to new format which uses separate monitor check
+ monitor.checks = monitor.checks || [];
+ monitor.checks.push({
+ type: "HTTP_STATUS_CODE_SHOULD_EQUAL",
+ value: monitorListData[i].accepted_statuscodes,
+ });
+ }
+
+ if (monitorListData[i].keyword) {
+ // old format for checking response contains keyword
+ // Convert to new format which uses separate monitor check
+ monitor.checks = monitor.checks || [];
+ monitor.checks.push({
+ type: "RESPONSE_SHOULD_CONTAIN_TEXT",
+ value: monitorListData[i].keyword,
+ });
+ }
+
+ monitor.type = "http";
+ }
+
+ const checks = monitor.checks;
+ delete monitor.checks;
bean.import(monitor);
bean.user_id = socket.userID;
await R.store(bean);
+ await updateMonitorChecks(bean.id, checks);
+
// Only for backup files with the version 1.7.0 or higher, since there was the tag feature implemented
if (version17x) {
// Only import if the specific monitor has tags assigned
@@ -1181,7 +1225,7 @@ exports.entryPage = "dashboard";
]);
let tagId;
- if (! tag) {
+ if (!tag) {
// -> If it doesn't exist, create new tag from backup file
let beanTag = R.dispense("tag");
beanTag.name = oldTag.name;
@@ -1266,7 +1310,7 @@ exports.entryPage = "dashboard";
console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`);
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
- monitorID
+ monitorID,
]);
await sendHeartbeatList(socket, monitorID, true, true);
@@ -1370,7 +1414,7 @@ async function checkOwner(userID, monitorID) {
userID,
]);
- if (! row) {
+ if (!row) {
throw new Error("You do not own this monitor.");
}
}
@@ -1418,7 +1462,7 @@ async function getMonitorJSONList(userID) {
}
async function initDatabase() {
- if (! fs.existsSync(Database.path)) {
+ if (!fs.existsSync(Database.path)) {
console.log("Copying Database");
fs.copyFileSync(Database.templatePath, Database.path);
}
@@ -1434,7 +1478,7 @@ async function initDatabase() {
"jwtSecret",
]);
- if (! jwtSecretBean) {
+ if (!jwtSecretBean) {
console.log("JWT secret is not found, generate one.");
jwtSecretBean = await initJWTSecret();
console.log("Stored JWT secret into database");
diff --git a/server/util-server.js b/server/util-server.js
index 68f59f6..9aaf50d 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -320,6 +320,28 @@ exports.checkLogin = (socket) => {
}
};
+exports.updateMonitorChecks = async (monitorId, checks) => {
+ let trx = await R.begin();
+ try {
+ // delete existing checks for monitor
+ await trx.exec("DELETE FROM monitor_checks WHERE monitor_id = ?", [monitorId]);
+
+ // Replace them with new checks
+ for (let i = 0; i < (checks || []).length; i++) {
+ let checkBean = trx.dispense("monitor_checks");
+ checkBean.monitor_id = monitorId;
+ checkBean.type = checks[i].type;
+ checkBean.value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
+ await trx.store(checkBean);
+ }
+
+ await trx.commit();
+ } catch (err) {
+ await trx.rollback();
+ throw err;
+ }
+};
+
exports.startUnitTest = async () => {
console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
diff --git a/server/validate-monitor-checks.js b/server/validate-monitor-checks.js
new file mode 100644
index 0000000..f9fd1dd
--- /dev/null
+++ b/server/validate-monitor-checks.js
@@ -0,0 +1,107 @@
+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);
+
+ (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 does not match " + check.value);
+ }
+ break;
+
+ case "HTTP_STATUS_CODE_SHOULD_NOT_EQUAL":
+ if (!checkStatusCode(res.status, check.value)) {
+ bean.msg += `, status does not match '${check.value}'`;
+ bean.status = UP;
+ } else {
+ throw new Error(bean.msg + ", but status code does 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":
+ if (get(res.data, check.value.selectorPath) === check.value.selectorValue) {
+ bean.msg += `, response selector equals '${check.value.selectorValue}'`;
+ bean.status = UP;
+ } else {
+ throw new Error(`${bean.msg}, but response selector '${check.value.selectorPath}' does not equal '${check.value.selectorValue}'`);
+ }
+ break;
+
+ case "RESPONSE_SELECTOR_SHOULD_NOT_EQUAL":
+ if (get(res.data, check.value.selectorPath) !== check.value.selectorValue) {
+ bean.msg += `, response selector does not equal '${check.value.selectorValue}'`;
+ bean.status = UP;
+ } else {
+ throw new Error(`${bean.msg}, but response selector '${check.value.selectorPath}' does equal '${check.value.selectorValue}'`);
+ }
+ break;
+
+ case "RESPONSE_SELECTOR_SHOULD_MATCH_REGEX":
+ if (get(res.data, check.value.selectorPath).test(new RegExp(check.value.selectorValue))) {
+ bean.msg += `, response selector matches regex '${check.value.selectorValue}'`;
+ bean.status = UP;
+ } else {
+ throw new Error(`${bean.msg}, but response selector '${check.value.selectorPath}' does not match regex '${check.value.selectorValue}'`);
+ }
+ break;
+
+ case "RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX":
+ if (!get(res.data, check.value.selectorPath).test(new RegExp(check.value.selectorValue))) {
+ bean.msg += `, response selector does not match regex '${check.value.selectorValue}'`;
+ bean.status = UP;
+ } else {
+ throw new Error(`${bean.msg}, but response selector '${check.value.selectorPath}' does match regex '${check.value.selectorValue}'`);
+ }
+ break;
+
+ default:
+ throw new Error(`${bean.msg}, encountered unknown monitor_check.type`);
+ }
+ });
+ bean.status = UP;
+}
+
+module.exports = validateMonitorChecks;
diff --git a/src/components/MonitorCheckEditor.vue b/src/components/MonitorCheckEditor.vue
new file mode 100644
index 0000000..afd9ac8
--- /dev/null
+++ b/src/components/MonitorCheckEditor.vue
@@ -0,0 +1,244 @@
+
+
- {{ 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 65c3dad..aa02454 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -10,21 +10,18 @@
{{ $t("Advanced") }}
- {{ $t("Checks") }}
-