Browse Source

feat(monitor-checks): try to get monitor checks fetched and stored in db

bertyhell/feature/monitor-checks
Bert Verhelst 4 years ago
parent
commit
07a37534b1
  1. 16
      server/model/monitor-check.js
  2. 3
      server/model/monitor.js
  3. 22
      server/model/validate-monitor-checks.js
  4. 53
      server/server.js
  5. 89
      src/components/MonitorCheckEditor.vue
  6. 40
      src/pages/EditMonitor.vue

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;

3
server/model/monitor.js

@ -10,7 +10,7 @@ const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/
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;
@ -69,7 +69,6 @@ class Monitor extends BeanModel {
dns_resolve_server: this.dns_resolve_server,
dns_last_result: this.dns_last_result,
notificationIDList,
checks: this.checks,
tags: tags,
};
}

22
server/model/validate-monitor-checks.js

@ -6,20 +6,20 @@ function validateMonitorChecks(res, checks, bean) {
const responseText = typeof data === "string" ? res.data : JSON.stringify(res.data);
let checkObj;
this.checks.forEach(check => {
(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.msg += `, status matches '${check.value}'`;
bean.status = UP;
} else {
throw new Error(bean.msg + ", but status code dit not match " + check.value)
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.msg += `, response contains '${check.value}'`;
bean.status = UP;
} else {
throw new Error(bean.msg + ", but response does not contain '" + check.value + "'");
@ -28,7 +28,7 @@ function validateMonitorChecks(res, checks, bean) {
case "RESPONSE_SHOULD_NOT_CONTAIN_TEXT":
if (!responseText.includes(check.value)) {
bean.msg += `, response does not contain '${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 + "'");
@ -37,7 +37,7 @@ function validateMonitorChecks(res, checks, bean) {
case "RESPONSE_SHOULD_MATCH_REGEX":
if (responseText.test(new RegExp(check.value))) {
bean.msg += `, regex '${check.value}' matches`
bean.msg += `, regex '${check.value}' matches`;
bean.status = UP;
} else {
throw new Error(bean.msg + ", but response does not match regex: '" + check.value + "'");
@ -46,7 +46,7 @@ function validateMonitorChecks(res, checks, bean) {
case "RESPONSE_SHOULD_NOT_MATCH_REGEX":
if (!responseText.test(new RegExp(check.value))) {
bean.msg += `, regex '${check.value}' does not matches`
bean.msg += `, regex '${check.value}' does not matches`;
bean.status = UP;
} else {
throw new Error(bean.msg + ", but response does match regex: '" + check.value + "'");
@ -56,7 +56,7 @@ function validateMonitorChecks(res, checks, bean) {
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.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}'`);
@ -66,7 +66,7 @@ function validateMonitorChecks(res, checks, bean) {
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.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}'`);
@ -76,7 +76,7 @@ function validateMonitorChecks(res, checks, bean) {
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.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}'`);
@ -86,7 +86,7 @@ function validateMonitorChecks(res, checks, bean) {
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.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}'`);

53
server/server.js

@ -71,7 +71,7 @@ if (demoMode) {
console.log("==== Demo Mode ====");
}
console.log("Creating express and socket.io instance")
console.log("Creating express and socket.io instance");
const app = express();
let server;
@ -459,13 +459,23 @@ exports.entryPage = "dashboard";
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
monitor.checks_json = JSON.stringify(monitor.checks);
const checks = monitor.checks;
delete monitor.checks;
// Store monitor info
bean.import(monitor);
bean.user_id = socket.userID;
await R.store(bean);
// Store checks info
for (let i = 0; i < (checks || []).length; i++) {
let checkBean = R.dispense("monitor_checks");
checks[i].monitor_id = bean.id;
checks[i].value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
checkBean.import(checks[i]);
await R.store(checkBean);
}
await updateMonitorNotification(bean.id, notificationIDList);
await startMonitor(socket.userID, bean.id);
@ -509,10 +519,21 @@ exports.entryPage = "dashboard";
bean.maxredirects = monitor.maxredirects;
bean.dns_resolve_type = monitor.dns_resolve_type;
bean.dns_resolve_server = monitor.dns_resolve_server;
bean.checks_json = JSON.stringify(monitor.checks);
const checks = monitor.checks;
delete monitor.checks;
await R.store(bean);
// Store checks info
for (let i = 0; i < (checks || []).length; i++) {
let checkBean = R.dispense("monitor_checks");
checks[i].monitor_id = bean.id;
checks[i].value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
checkBean.import(checks[i]);
await R.store(checkBean);
}
await updateMonitorNotification(bean.id, monitor.notificationIDList);
if (bean.active) {
@ -1034,14 +1055,28 @@ exports.entryPage = "dashboard";
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
monitor.checks_json = JSON.stringify(monitor.checks);
delete monitor.accepted_statuscodes;
delete monitor.accepted_statuscodes; // TODO convert to check
delete monitor.keyword; // TODO convert to check
const checks = monitor.checks;
delete monitor.checks;
bean.import(monitor);
bean.user_id = socket.userID;
await R.store(bean);
// Store monitor checks
for (let i = 0; i < (checks || []).length; i++) {
let checkBean = R.dispense("monitor_checks");
checks[i].monitor_id = bean.id;
checks[i].value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
checkBean.import(checks[i]);
await R.store(checkBean);
}
// 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
@ -1276,6 +1311,14 @@ async function getMonitorJSONList(userID) {
]);
for (let monitor of monitorList) {
const monitorChecks = await R.find("monitor_checks", " monitor_id = ? ORDER BY id", [
monitor.id,
]);
const monitorCheckObj = [];
for (let monitorCheck of monitorChecks) {
monitorCheckObj.push(await monitorCheck.toJSON());
}
monitor.checks = monitorCheckObj;
result[monitor.id] = await monitor.toJSON();
}

89
src/components/MonitorCheckEditor.vue

@ -1,7 +1,7 @@
<template>
<div class="monitor-check mb-4">
<div>
<select id="type" v-model="monitorCheck.type" :class="{'form-select': true, 'mb-1': !!monitorCheck.type}">
<select id="type" :value="monitorCheck.type" :class="{'form-select': true, 'mb-1': !!monitorCheck.type}" @input="changeType($event.target.value)">
<option value="HTTP_STATUS_CODE_SHOULD_EQUAL">
{{ $t("HTTP status code should equal") }}
</option>
@ -33,7 +33,6 @@
<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"
@ -43,27 +42,35 @@
:preselect-first="false"
:max-height="600"
:taggable="true"
:modelValue="monitorCheck.value"
@update:modelValue="changeValue($event.target.value)"
></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')">
<input :value="monitorCheck.value" type="text" class="form-control" required :placeholder="$t('Value')" @input="changeValue($event.target.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')">
<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="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')">
v-if="monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_EQUAL' || monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_NOT_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="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')">
v-if="monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_MATCH_REGEX' || monitorCheck.type === 'RESPONSE_SELECTOR_SHOULD_NOT_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">
@ -82,12 +89,17 @@ export default {
props: {
monitorCheck: {
type: Object,
default: () => ({
type: undefined,
value: undefined,
}),
},
},
emits: ["change", "delete"],
data() {
return {
acceptedStatusCodeOptions: [],
}
};
},
mounted() {
let acceptedStatusCodeOptions = [
@ -106,10 +118,51 @@ export default {
},
methods: {
deleteMonitorCheck() {
this.$emit('delete');
this.$emit("delete");
},
changeType(type) {
const stringValueTypes = ["HTTP_STATUS_CODE_SHOULD_EQUAL", "RESPONSE_SHOULD_CONTAIN_TEXT", "RESPONSE_SHOULD_NOT_CONTAIN_TEXT", "RESPONSE_SHOULD_MATCH_REGEX", "RESPONSE_SHOULD_NOT_MATCH_REGEX"];
if (stringValueTypes.includes(type) && stringValueTypes.includes(this.monitorCheck.type) ||
!stringValueTypes.includes(type) && !stringValueTypes.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>
@ -122,7 +175,7 @@ export default {
select {
border-radius: 19px 0 0 19px;
}
button {
margin-left: 0.25rem;
padding-left: 15px;

40
src/pages/EditMonitor.vue

@ -122,7 +122,7 @@
</div>
</div>
<!-- HTTP only -->
<!-- HTTP only -->
<template v-if="monitor.type === 'http'">
<div class="my-3">
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
@ -134,11 +134,11 @@
<h2 class="mt-5 mb-2">{{ $t("Checks") }}</h2>
<div class="my-3">
<div class="my-3 mb-5">
<div v-for="(monitorCheck, index) in monitor.checks" :key="index" class="mb-3">
<MonitorCheckEditor :monitorCheck="monitorCheck" :index="index" @delete="deleteMonitorCheck(index)"></MonitorCheckEditor>
<MonitorCheckEditor :monitorCheck="monitorCheck" @change="(newMonitorCheck) => updateMonitorCheck(newMonitorCheck, index)" @delete="() => deleteMonitorCheck(index)"></MonitorCheckEditor>
</div>
<button class="btn btn-light" type="button" @click="addMonitorCheck()">{{ $t("Add check") }}</button>
<button class="btn btn-outline-secondary btn-add-check" type="button" @click="addMonitorCheck()">{{ $t("Add check") }}</button>
</div>
</template>
@ -187,10 +187,10 @@
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 { useToast } from "vue-toastification";
import VueMultiselect from "vue-multiselect";
import { isDev } from "../util.ts";
const toast = useToast()
const toast = useToast();
export default {
components: {
@ -214,7 +214,7 @@ export default {
ipRegexPattern: "((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))",
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
hostnameRegexPattern: "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
}
};
},
computed: {
@ -298,7 +298,7 @@ export default {
accepted_statuscodes: ["200-299"],
dns_resolve_type: "A",
dns_resolve_server: "1.1.1.1",
}
};
for (let i = 0; i < this.$root.notificationList.length; i++) {
if (this.$root.notificationList[i].isDefault == true) {
@ -315,19 +315,23 @@ export default {
this.monitor.retryInterval = this.monitor.interval;
}
} else {
toast.error(res.msg)
toast.error(res.msg);
}
})
});
}
},
addMonitorCheck() {
this.monitor.checks = [...(this.monitor.checks || []), {
type: null,
value: '',
value: "",
}];
},
updateMonitorCheck(newMonitorCheck, index) {
this.monitor.checks[index] = newMonitorCheck;
},
deleteMonitorCheck(index) {
this.monitor.checks = this.monitor.checks.splice(index, 1);
},
@ -344,13 +348,13 @@ export default {
toast.success(res.msg);
this.processing = false;
this.$root.getMonitorList();
this.$router.push("/dashboard/" + res.monitorID)
this.$router.push("/dashboard/" + res.monitorID);
} else {
toast.error(res.msg);
this.processing = false;
}
})
});
} else {
await this.$refs.tagsManager.submit(this.monitor.id);
@ -358,7 +362,7 @@ export default {
this.processing = false;
this.$root.toastRes(res);
this.init();
})
});
}
},
@ -368,7 +372,7 @@ export default {
this.monitor.notificationIDList[id] = true;
},
},
}
};
</script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
@ -421,6 +425,10 @@ export default {
color: $dark-font-color2;
}
}
.btn-add-check {
width: 100%;
}
</style>
<style scoped>

Loading…
Cancel
Save