committed by
GitHub
14 changed files with 734 additions and 125 deletions
@ -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; |
||||
|
|
||||
|
|
@ -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; |
@ -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; |
@ -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> |
Loading…
Reference in new issue