Browse Source

Merge 3a2864d86b into 5df34cd137

pull/518/merge
Bert Verhelst 4 years ago
committed by GitHub
parent
commit
2ba2d4dfac
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      db/patch-add-monitor-checks-table.sql
  2. 3
      server/database.js
  3. 16
      server/model/monitor-check.js
  4. 127
      server/model/monitor.js
  5. 94
      server/server.js
  6. 22
      server/util-server.js
  7. 107
      server/validate-monitor-checks.js
  8. 244
      src/components/MonitorCheckEditor.vue
  9. 18
      src/languages/en.js
  10. 20
      src/languages/nl-NL.js
  11. 6
      src/pages/Details.vue
  12. 94
      src/pages/EditMonitor.vue
  13. 31
      src/util.js
  14. 35
      src/util.ts

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

3
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

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

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

94
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");

22
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";

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

244
src/components/MonitorCheckEditor.vue

@ -0,0 +1,244 @@
<template>
<div class="monitor-check mb-4">
<div>
<div class="side-by-side">
<select id="invert-check" :value="invertedTypeOption" :class="{'form-select': true, 'mb-1': !!monitorCheck.type, 'me-1': true}"
@input="changeTypeInversion($event.target.value)" required>
<option value="SHOULD">{{ $t("MonitorCheckTypeShould") }}</option>
<option value="SHOULD_NOT">{{ $t("MonitorCheckTypeShouldNot") }}</option>
</select>
<select id="type" :value="monitorType" :class="{'form-select': true, 'mb-1': !!monitorCheck.type}"
@input="changeType($event.target.value)" required>
<option value="HTTP_STATUS_CODE_SHOULD_EQUAL">
{{ $t("MonitorCheckTypeHttpStatusCodeShouldEqual") }}
</option>
<option value="RESPONSE_SHOULD_CONTAIN_TEXT">
{{ $t("MonitorCheckTypeResponseShouldContainText") }}
</option>
<option value="RESPONSE_SHOULD_MATCH_REGEX">
{{ $t("MonitorCheckTypeResponseShouldMatchRegex") }}
</option>
<option value="RESPONSE_SELECTOR_SHOULD_EQUAL">
{{ $t("MonitorCheckTypeResponseSelectorShouldEqual") }}
</option>
<option value="RESPONSE_SELECTOR_SHOULD_MATCH_REGEX">
{{ $t("MonitorCheckTypeResponseSelectorShouldMatchRegex") }}
</option>
</select>
</div>
<div v-if="monitorType === 'HTTP_STATUS_CODE_SHOULD_EQUAL'">
<VueMultiselect
id="acceptedStatusCodes"
: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"
:modelValue="monitorCheck.value"
@update:model-value="changeValue"
></VueMultiselect>
</div>
<div v-if="monitorType === 'RESPONSE_SHOULD_CONTAIN_TEXT'">
<input :value="monitorCheck.value" type="text" class="form-control" required :placeholder="$t('MonitorCheckValuePlaceholder')"
@input="changeValue($event.target.value)">
</div>
<div v-if="monitorType === 'RESPONSE_SHOULD_MATCH_REGEX'">
<input type="text" class="form-control" required :value="monitorCheck.value"
:placeholder="$t('Regexp, Example: [a-z0-9.]+@gmail\.com')" @input="changeValue($event.target.value)"
>
</div>
<div v-if="monitorType === 'RESPONSE_SELECTOR_SHOULD_EQUAL'">
<input :value="monitorCheck?.value?.selectorPath || ''" type="text" class="form-control mb-1" required
:placeholder="$t('Selector, Example: customer.address.street')"
@input="changeSelectorPath($event.target.value)"
>
<input :value="monitorCheck?.value?.selectorValue || ''" type="text" class="form-control" required
:placeholder="$t('Value, Example: First street')" @input="changeSelectorValue($event.target.value)">
</div>
<div v-if="monitorType === 'RESPONSE_SELECTOR_SHOULD_MATCH_REGEX'">
<input :value="monitorCheck?.value?.selectorPath || ''" type="text" class="form-control mb-1" required
:placeholder="$t('Selector, Example: customer.contactInfo.email')"
@input="changeSelectorPath($event.target.value)"
>
<input :value="monitorCheck?.value?.selectorValue || ''" type="text" class="form-control" required
:placeholder="$t('Regexp, Example: [a-z0-9.]+@gmail\.com')"
@input="changeSelectorValue($event.target.value)"
>
</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";
import {
MONITOR_CHECK_INVERTED_TYPES,
MONITOR_CHECK_SELECTOR_TYPES,
MONITOR_CHECK_STRING_TYPES,
MONITOR_CHECK_SHOULD,
MONITOR_CHECK_SHOULD_NOT,
MONITOR_CHECK_MAP_NORMAL_TO_INVERTED, MONITOR_CHECK_MAP_INVERTED_TO_NORMAL, MONITOR_CHECK_HTTP_CODE_TYPES,
} from "../util.ts";
export default {
components: {
VueMultiselect,
},
props: {
monitorCheck: {
type: Object,
default: () => ({
type: undefined,
value: undefined,
}),
},
},
emits: ["change", "delete"],
data() {
return {
acceptedStatusCodeOptions: [],
// Contains SHOULD or SHOULD_NOT
invertedTypeOption: MONITOR_CHECK_SHOULD,
// Always contains the normal type (never the NOT variant)
monitorType: undefined,
};
},
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;
if (this.monitorCheck.type) {
this.invertedTypeOption = MONITOR_CHECK_INVERTED_TYPES.includes(this.monitorCheck.type) ? MONITOR_CHECK_SHOULD_NOT : MONITOR_CHECK_SHOULD;
this.monitorType = MONITOR_CHECK_MAP_INVERTED_TO_NORMAL[this.monitorCheck.type] || this.monitorCheck.type;
}
},
methods: {
deleteMonitorCheck() {
this.$emit("delete");
},
changeTypeInversion(inversionType) {
this.invertedTypeOption = inversionType;
this.emitType();
},
changeType(type) {
this.monitorType = type;
this.emitType();
},
// Combine invertedTypeOption with monitorType to produce the combined this.monitorCheck.type
emitType() {
if (!this.monitorType) {
return;
}
const type = this.invertedTypeOption === MONITOR_CHECK_SHOULD ? this.monitorType : MONITOR_CHECK_MAP_NORMAL_TO_INVERTED[this.monitorType];
if (MONITOR_CHECK_HTTP_CODE_TYPES.includes(type) && MONITOR_CHECK_HTTP_CODE_TYPES.includes(this.monitorCheck.type) ||
MONITOR_CHECK_STRING_TYPES.includes(type) && MONITOR_CHECK_STRING_TYPES.includes(this.monitorCheck.type) ||
MONITOR_CHECK_SELECTOR_TYPES.includes(type) && MONITOR_CHECK_SELECTOR_TYPES.includes(this.monitorCheck.type)) {
// Check value stays same type (string => string or object => object)
this.$emit("change", {
...this.monitorCheck,
type,
});
} else {
// Check value switches (string => object or object => string)
this.$emit("change", {
type,
value: undefined,
});
}
},
changeValue(value) {
this.$emit("change", {
...this.monitorCheck,
value,
});
},
changeSelectorPath(selectorPath) {
this.$emit("change", {
...this.monitorCheck,
value: {
...(this.monitorCheck.value || {}),
selectorPath,
},
});
},
changeSelectorValue(selectorValue) {
this.$emit("change", {
...this.monitorCheck,
value: {
...(this.monitorCheck.value || {}),
selectorValue,
},
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.monitor-check {
display: flex;
> div:first-child {
width: 100%;
}
.side-by-side {
display: flex;
> select:first-child {
width: 40%;
+ select {
border-radius: 0;
margin-left: -1px;
}
}
}
input,
select {
border-radius: 19px 0 0 19px;
&:focus {
z-index: 1;
}
}
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>

18
src/languages/en.js

@ -307,4 +307,22 @@ export default {
steamApiKeyDescription: "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key here: ",
"Current User": "Current User",
recent: "Recent",
MonitorCheckTypeShould: "Should",
MonitorCheckTypeShouldNot: "Should not",
MonitorCheckTypeHttpStatusCodeShouldEqual: "equal status code",
MonitorCheckTypeResponseShouldContainText: "contain text",
MonitorCheckTypeResponseShouldMatchRegex: "match regex",
MonitorCheckTypeResponseSelectorShouldEqual: "equal text (JSON selector)",
MonitorCheckTypeResponseSelectorShouldMatchRegex: "match regex (JSON selector)",
MonitorCheckValuePlaceholder: "Value",
MonitorCheckSelectorPathPlaceholder: "Selector, Example: customer.address.street",
MonitorCheckSelectorValuePlaceholder: "Value, Example: First street",
MonitorCheckSelectorPathRegexPlaceholder: "Selector, Example: customer.contactInfo.email",
MonitorCheckSelectorValueRegexPlaceholder: "Regexp, Example: [a-z0-9.]+@gmail\\.com",
MonitorTypeHttps: "HTTP(s)",
MonitorTypeTcpPort: "TCP Port",
MonitorTypePing: "Ping",
MonitorTypeDns: "DNS",
MonitorChecks: "Checks",
AddMonitorCheck: "Add check",
};

20
src/languages/nl-NL.js

@ -204,4 +204,24 @@ export default {
PushUrl: "Push URL",
HeadersInvalidFormat: "The request headers is geen geldige JSON: ",
BodyInvalidFormat: "De request body is geen geldige JSON: ",
BodyPlaceholder: "&lbrace;\n\t\"id\": 124357,\n\t\"gebruikersnaam\": \"admin\",\n\t\"wachtwoord\": \"mijnAdminWachtwoord\"\n&rbrace;",
HeadersPlaceholder: "&lbrace;\n\t\"Authorization\": \"Bearer abc123\",\n\t\"Content-Type\": \"application/json\"\n&rbrace;",
MonitorCheckTypeShould: "Moet",
MonitorCheckTypeShouldNot: "Mag niet",
MonitorCheckTypeHttpStatusCodeShouldEqual: "gelijk aan status code",
MonitorCheckTypeResponseShouldContainText: "text bevatten",
MonitorCheckTypeResponseShouldMatchRegex: "regex matchen",
MonitorCheckTypeResponseSelectorShouldEqual: "gelijk zijn aan tekst (JSON selectie)",
MonitorCheckTypeResponseSelectorShouldMatchRegex: "regex matchen (JSON selectie)",
MonitorCheckValuePlaceholder: "Waarde",
MonitorCheckSelectorPathPlaceholder: "Selectie, Voorbeeld: klant.adres.straat",
MonitorCheckSelectorValuePlaceholder: "Waarde, Voorbeeld: Hogestraat",
MonitorCheckSelectorPathRegexPlaceholder: "Selectie, Voorbeeld: klant.contactInfo.email",
MonitorCheckSelectorValueRegexPlaceholder: "Reg. Expr., Voorbeeld: [a-z0-9.]+@gmail\\.com",
MonitorTypeHttps: "HTTP(s)",
MonitorTypeTcpPort: "TCP Poort",
MonitorTypePing: "Ping",
MonitorTypeDns: "DNS",
MonitorChecks: "Controles",
AddMonitorCheck: "Controle toevoegen",
};

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>

94
src/pages/EditMonitor.vue

@ -10,21 +10,18 @@
<div class="my-3">
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
<select id="type" v-model="monitor.type" class="form-select">
<select id="type" :value="monitor.type" @input="changeMonitorType($event.target.value)" 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>
<option value="push">
Push
@ -42,7 +39,7 @@
</div>
<!-- URL -->
<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>
@ -57,17 +54,8 @@
</div>
</div>
<!-- Keyword -->
<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>
<!-- Hostname -->
<!-- TCP Port / Ping / DNS / Steam only -->
<!-- TCP Port / Ping / DNS only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam'" class="my-3">
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
@ -139,7 +127,7 @@
<h2 v-if="monitor.type !== 'push'" 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") }}
@ -156,8 +144,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">
@ -166,26 +154,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 mb-5">
<div v-for="(monitorCheck, index) in monitor.checks" :key="index" class="mb-3">
<MonitorCheckEditor :monitorCheck="monitorCheck" @change="(newMonitorCheck) => updateMonitorCheck(newMonitorCheck, index)" @delete="() => deleteMonitorCheck(index)"></MonitorCheckEditor>
</div>
<button class="btn btn-outline-secondary btn-add-check" type="button" @click="addMonitorCheck()">{{ $t("Add check") }}</button>
</div>
</template>
@ -280,11 +255,10 @@
import NotificationDialog from "../components/NotificationDialog.vue";
import TagsManager from "../components/TagsManager.vue";
import CopyableInput from "../components/CopyableInput.vue";
import MonitorCheckEditor from "../components/MonitorCheckEditor.vue";
import { useToast } from "vue-toastification";
import VueMultiselect from "vue-multiselect";
import { genSecret, isDev } from "../util.ts";
import { genSecret, HTTP_STATUS_CODE_SHOULD_EQUAL, isDev } from "../util.ts";
const toast = useToast();
export default {
@ -292,6 +266,7 @@ export default {
CopyableInput,
NotificationDialog,
TagsManager,
MonitorCheckEditor,
VueMultiselect,
},
@ -423,7 +398,10 @@ export default {
ignoreTls: false,
upsideDown: false,
maxredirects: 10,
accepted_statuscodes: ["200-299"],
checks: [{
type: HTTP_STATUS_CODE_SHOULD_EQUAL,
value: ["200-299"],
}],
dns_resolve_type: "A",
dns_resolve_server: "1.1.1.1",
};
@ -447,7 +425,35 @@ export default {
}
});
}
},
changeMonitorType(type) {
this.monitor.type = type;
if (type === 'http') {
this.monitor.checks = [{
type: HTTP_STATUS_CODE_SHOULD_EQUAL,
value: ["200-299"],
}];
} else {
delete this.monitor.checks;
}
},
addMonitorCheck() {
this.monitor.checks = [...(this.monitor.checks || []), {
type: null,
value: "",
}];
},
updateMonitorCheck(newMonitorCheck, index) {
this.monitor.checks[index] = newMonitorCheck;
},
deleteMonitorCheck(index) {
const newList = [...this.monitor.checks];
newList.splice(index, 1);
this.monitor.checks = newList;
},
isInputValid() {

31
src/util.js

@ -18,6 +18,37 @@ exports.PENDING = 2;
exports.STATUS_PAGE_ALL_DOWN = 0;
exports.STATUS_PAGE_ALL_UP = 1;
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
// Monitor check types
exports.MONITOR_CHECK_SHOULD = "SHOULD";
exports.MONITOR_CHECK_SHOULD_NOT = "SHOULD_NOT";
exports.HTTP_STATUS_CODE_SHOULD_EQUAL = "HTTP_STATUS_CODE_SHOULD_EQUAL";
exports.HTTP_STATUS_CODE_SHOULD_NOT_EQUAL = "HTTP_STATUS_CODE_SHOULD_NOT_EQUAL";
exports.RESPONSE_SHOULD_CONTAIN_TEXT = "RESPONSE_SHOULD_CONTAIN_TEXT";
exports.RESPONSE_SHOULD_NOT_CONTAIN_TEXT = "RESPONSE_SHOULD_NOT_CONTAIN_TEXT";
exports.RESPONSE_SHOULD_MATCH_REGEX = "RESPONSE_SHOULD_MATCH_REGEX";
exports.RESPONSE_SHOULD_NOT_MATCH_REGEX = "RESPONSE_SHOULD_NOT_MATCH_REGEX";
exports.RESPONSE_SELECTOR_SHOULD_EQUAL = "RESPONSE_SELECTOR_SHOULD_EQUAL";
exports.RESPONSE_SELECTOR_SHOULD_NOT_EQUAL = "RESPONSE_SELECTOR_SHOULD_NOT_EQUAL";
exports.RESPONSE_SELECTOR_SHOULD_MATCH_REGEX = "RESPONSE_SELECTOR_SHOULD_MATCH_REGEX";
exports.RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX = "RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX";
exports.MONITOR_CHECK_HTTP_CODE_TYPES = [exports.HTTP_STATUS_CODE_SHOULD_EQUAL, exports.HTTP_STATUS_CODE_SHOULD_NOT_EQUAL];
exports.MONITOR_CHECK_STRING_TYPES = [exports.RESPONSE_SHOULD_CONTAIN_TEXT, exports.RESPONSE_SHOULD_NOT_CONTAIN_TEXT, exports.RESPONSE_SHOULD_MATCH_REGEX, exports.RESPONSE_SHOULD_NOT_MATCH_REGEX];
exports.MONITOR_CHECK_SELECTOR_TYPES = [exports.RESPONSE_SELECTOR_SHOULD_EQUAL, exports.RESPONSE_SELECTOR_SHOULD_NOT_EQUAL, exports.RESPONSE_SELECTOR_SHOULD_MATCH_REGEX, exports.RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX];
exports.MONITOR_CHECK_INVERTED_TYPES = [exports.HTTP_STATUS_CODE_SHOULD_NOT_EQUAL, exports.RESPONSE_SHOULD_NOT_CONTAIN_TEXT, exports.RESPONSE_SHOULD_NOT_MATCH_REGEX, exports.RESPONSE_SELECTOR_SHOULD_NOT_EQUAL, exports.RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX];
exports.MONITOR_CHECK_MAP_NORMAL_TO_INVERTED = {
[exports.HTTP_STATUS_CODE_SHOULD_EQUAL]: exports.HTTP_STATUS_CODE_SHOULD_NOT_EQUAL,
[exports.RESPONSE_SHOULD_CONTAIN_TEXT]: exports.RESPONSE_SHOULD_NOT_CONTAIN_TEXT,
[exports.RESPONSE_SHOULD_MATCH_REGEX]: exports.RESPONSE_SHOULD_NOT_MATCH_REGEX,
[exports.RESPONSE_SELECTOR_SHOULD_EQUAL]: exports.RESPONSE_SELECTOR_SHOULD_NOT_EQUAL,
[exports.RESPONSE_SELECTOR_SHOULD_MATCH_REGEX]: exports.RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX,
};
exports.MONITOR_CHECK_MAP_INVERTED_TO_NORMAL = {
[exports.HTTP_STATUS_CODE_SHOULD_NOT_EQUAL]: exports.HTTP_STATUS_CODE_SHOULD_EQUAL,
[exports.RESPONSE_SHOULD_NOT_CONTAIN_TEXT]: exports.RESPONSE_SHOULD_CONTAIN_TEXT,
[exports.RESPONSE_SHOULD_NOT_MATCH_REGEX]: exports.RESPONSE_SHOULD_MATCH_REGEX,
[exports.RESPONSE_SELECTOR_SHOULD_NOT_EQUAL]: exports.RESPONSE_SELECTOR_SHOULD_EQUAL,
[exports.RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX]: exports.RESPONSE_SELECTOR_SHOULD_MATCH_REGEX,
};
function flipStatus(s) {
if (s === exports.UP) {
return exports.DOWN;

35
src/util.ts

@ -19,6 +19,41 @@ export const STATUS_PAGE_ALL_DOWN = 0;
export const STATUS_PAGE_ALL_UP = 1;
export const STATUS_PAGE_PARTIAL_DOWN = 2;
// Monitor check types
export const MONITOR_CHECK_SHOULD = "SHOULD";
export const MONITOR_CHECK_SHOULD_NOT = "SHOULD_NOT";
export const HTTP_STATUS_CODE_SHOULD_EQUAL = "HTTP_STATUS_CODE_SHOULD_EQUAL";
export const HTTP_STATUS_CODE_SHOULD_NOT_EQUAL = "HTTP_STATUS_CODE_SHOULD_NOT_EQUAL";
export const RESPONSE_SHOULD_CONTAIN_TEXT = "RESPONSE_SHOULD_CONTAIN_TEXT";
export const RESPONSE_SHOULD_NOT_CONTAIN_TEXT = "RESPONSE_SHOULD_NOT_CONTAIN_TEXT";
export const RESPONSE_SHOULD_MATCH_REGEX = "RESPONSE_SHOULD_MATCH_REGEX";
export const RESPONSE_SHOULD_NOT_MATCH_REGEX = "RESPONSE_SHOULD_NOT_MATCH_REGEX";
export const RESPONSE_SELECTOR_SHOULD_EQUAL = "RESPONSE_SELECTOR_SHOULD_EQUAL";
export const RESPONSE_SELECTOR_SHOULD_NOT_EQUAL = "RESPONSE_SELECTOR_SHOULD_NOT_EQUAL";
export const RESPONSE_SELECTOR_SHOULD_MATCH_REGEX = "RESPONSE_SELECTOR_SHOULD_MATCH_REGEX";
export const RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX = "RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX";
export const MONITOR_CHECK_HTTP_CODE_TYPES = [HTTP_STATUS_CODE_SHOULD_EQUAL, HTTP_STATUS_CODE_SHOULD_NOT_EQUAL];
export const MONITOR_CHECK_STRING_TYPES = [RESPONSE_SHOULD_CONTAIN_TEXT, RESPONSE_SHOULD_NOT_CONTAIN_TEXT, RESPONSE_SHOULD_MATCH_REGEX, RESPONSE_SHOULD_NOT_MATCH_REGEX];
export const MONITOR_CHECK_SELECTOR_TYPES = [RESPONSE_SELECTOR_SHOULD_EQUAL, RESPONSE_SELECTOR_SHOULD_NOT_EQUAL, RESPONSE_SELECTOR_SHOULD_MATCH_REGEX, RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX];
export const MONITOR_CHECK_INVERTED_TYPES = [HTTP_STATUS_CODE_SHOULD_NOT_EQUAL, RESPONSE_SHOULD_NOT_CONTAIN_TEXT, RESPONSE_SHOULD_NOT_MATCH_REGEX, RESPONSE_SELECTOR_SHOULD_NOT_EQUAL, RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX];
export const MONITOR_CHECK_MAP_NORMAL_TO_INVERTED = {
[HTTP_STATUS_CODE_SHOULD_EQUAL]: HTTP_STATUS_CODE_SHOULD_NOT_EQUAL,
[RESPONSE_SHOULD_CONTAIN_TEXT]: RESPONSE_SHOULD_NOT_CONTAIN_TEXT,
[RESPONSE_SHOULD_MATCH_REGEX]: RESPONSE_SHOULD_NOT_MATCH_REGEX,
[RESPONSE_SELECTOR_SHOULD_EQUAL]: RESPONSE_SELECTOR_SHOULD_NOT_EQUAL,
[RESPONSE_SELECTOR_SHOULD_MATCH_REGEX]: RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX,
};
export const MONITOR_CHECK_MAP_INVERTED_TO_NORMAL = {
[HTTP_STATUS_CODE_SHOULD_NOT_EQUAL]: HTTP_STATUS_CODE_SHOULD_EQUAL,
[RESPONSE_SHOULD_NOT_CONTAIN_TEXT]: RESPONSE_SHOULD_CONTAIN_TEXT,
[RESPONSE_SHOULD_NOT_MATCH_REGEX]: RESPONSE_SHOULD_MATCH_REGEX,
[RESPONSE_SELECTOR_SHOULD_NOT_EQUAL]: RESPONSE_SELECTOR_SHOULD_EQUAL,
[RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX]: RESPONSE_SELECTOR_SHOULD_MATCH_REGEX,
};
export function flipStatus(s: number) {
if (s === UP) {

Loading…
Cancel
Save