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