Added the option not to send notifications if the master monitor is pending, down or degraded.

This commit is contained in:
Karel Krýda 2022-01-30 20:55:53 +01:00
parent 64c8a90e8a
commit 7e2503c0dc
7 changed files with 60 additions and 17 deletions

View file

@ -1,6 +1,9 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
ALTER TABLE monitor
ADD no_notification_if_master_down BOOLEAN default 0 NOT NULL;
CREATE TABLE dependent_monitors
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,

View file

@ -72,6 +72,7 @@ class Monitor extends BeanModel {
keyword: this.keyword,
ignoreTls: this.getIgnoreTls(),
upsideDown: this.isUpsideDown(),
noNotificationIfMasterDown: this.getNoNotificationIfMasterDown(),
maxredirects: this.maxredirects,
accepted_statuscodes: this.getAcceptedStatuscodes(),
dns_resolve_type: this.dns_resolve_type,
@ -108,6 +109,14 @@ class Monitor extends BeanModel {
return Boolean(this.upsideDown);
}
/**
* Parse to boolean
* @returns {boolean}
*/
getNoNotificationIfMasterDown() {
return Boolean(this.noNotificationIfMasterDown);
}
getAcceptedStatuscodes() {
return JSON.parse(this.accepted_statuscodes_json);
}
@ -148,7 +157,10 @@ class Monitor extends BeanModel {
bean.duration = 0;
}
let isDegraded = false;
try {
isDegraded = await Monitor.isDegraded(this.id);
if (this.type === "http" || this.type === "keyword") {
// Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf();
@ -363,8 +375,8 @@ class Monitor extends BeanModel {
retries = 0;
if (bean.status === UP && await Monitor.isDegraded(this.id)) {
bean.msg = "Monitor is degraded, because at least one dependent monitor is DOWN";
if (bean.status === UP && isDegraded) {
bean.msg = "Monitor is degraded, because at least one master monitor is pending, down or degraded";
bean.status = DEGRADED;
}
@ -381,6 +393,12 @@ class Monitor extends BeanModel {
retries++;
bean.status = PENDING;
}
// Do not change this text!
// Condition below and in api-router depends on it
if (isDegraded && bean.status === DOWN) {
bean.msg = "Monitor is down and degraded";
}
}
let beatInterval = this.interval;
@ -393,7 +411,10 @@ class Monitor extends BeanModel {
if (isImportant) {
bean.important = true;
if (Monitor.isImportantForNotification(isFirstBeat, previousBeat?.status, bean.status)) {
if (this.noNotificationIfMasterDown && isDegraded || previousBeat.msg === "Monitor is down and degraded") {
debug(`[${this.name}] will not sendNotification because it is/was degraded`);
}
else if (Monitor.isImportantForNotification(isFirstBeat, previousBeat?.status, bean.status)) {
debug(`[${this.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, this, bean);
}
@ -801,7 +822,7 @@ class Monitor extends BeanModel {
static async getPreviousHeartbeat(monitorID) {
return await R.getRow(`
SELECT status, time FROM heartbeat
SELECT msg, status, time FROM heartbeat
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
`, [
monitorID
@ -811,7 +832,7 @@ class Monitor extends BeanModel {
static async isDegraded(monitorID) {
const monitors = await R.getAll(`
SELECT hb.id FROM heartbeat hb JOIN dependent_monitors dm on hb.monitor_id = dm.depends_on JOIN (SELECT MAX(id) AS id FROM heartbeat GROUP BY monitor_id) USING (id)
WHERE dm.monitor_id = ? AND hb.status = 0
WHERE dm.monitor_id = ? AND (hb.status = 0 OR hb.status = 2 OR hb.status = 4)
`, [
monitorID
]);

View file

@ -51,7 +51,9 @@ router.get("/api/push/:pushToken", async (request, response) => {
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
}
if (status === UP && await Monitor.isDegraded(monitor.id)) {
const isDegraded = await Monitor.isDegraded(monitor.id)
if (status === UP && isDegraded) {
msg = "Monitor is degraded, because at least one dependent monitor is DOWN";
status = DEGRADED;
}
@ -75,7 +77,10 @@ router.get("/api/push/:pushToken", async (request, response) => {
ok: true,
});
if (Monitor.isImportantForNotification(isFirstBeat, previousStatus, status)) {
if (monitor.noNotificationIfMasterDown && isDegraded || previousHeartbeat.msg === "Monitor is down and degraded") {
debug(`[${monitor.name}] will not sendNotification because it is/was degraded`);
}
else if (Monitor.isImportantForNotification(isFirstBeat, previousStatus, status)) {
await Monitor.sendNotification(isFirstBeat, monitor, bean);
}

View file

@ -594,6 +594,7 @@ exports.entryPage = "dashboard";
bean.keyword = monitor.keyword;
bean.ignoreTls = monitor.ignoreTls;
bean.upsideDown = monitor.upsideDown;
bean.noNotificationIfMasterDown = monitor.noNotificationIfMasterDown;
bean.maxredirects = monitor.maxredirects;
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
bean.dns_resolve_type = monitor.dns_resolve_type;

View file

@ -6,8 +6,11 @@ export default {
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.",
sendNotificationTitle: "Do not send notification in case of master monitor failure",
sendNotificationDescription: "Do not send any notifications if this monitor is DOWN and at least one master monitor is also DOWN.",
Degraded: "Degraded",
"Dependent Monitors": "Dependent Monitors",
monitorDependsOn: "The status of this monitor depends on",
"Pick Dependent Monitors...": "Pick Dependent Monitors...",
dependentMonitorsDescription: "Select the monitor(s) on which this monitor depends. If the dependent monitor(s) fails, this monitor will be affected too.",
acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.",

View file

@ -103,6 +103,14 @@
</div>
</div>
<div class="shadow-box table-shadow-box">
<label for="dependent-monitors" class="form-label" style="margin-top: 20px; font-weight: bold">{{ $t("monitorDependsOn") }}:</label>
<br>
<button v-for="monitor in this.dependentMonitors" class="btn btn-monitor" style="margin: 5px; cursor: auto; color: white; font-weight: 500">
{{ monitor }}
</button>
</div>
<div class="shadow-box table-shadow-box">
<div class="dropdown dropdown-clear-data">
<button class="btn btn-sm btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown">
@ -154,14 +162,6 @@
</div>
</div>
<div class="shadow-box table-shadow-box">
<label for="dependent-monitors" class="form-label" style="margin-top: 20px">{{ $t("Dependent Monitors") }}</label>
<br>
<button v-for="monitor in this.dependentMonitors" class="btn btn-monitor" style="margin: 5px; cursor: auto; color: white; font-weight: bold">
{{ monitor }}
</button>
</div>
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseMonitor">
{{ $t("pauseMonitorMsg") }}
</Confirm>

View file

@ -156,6 +156,16 @@
</div>
</div>
<div v-if="dependentMonitors.length !== 0" class="my-3 form-check">
<input id="send-notification" v-model="monitor.noNotificationIfMasterDown" class="form-check-input" type="checkbox">
<label class="form-check-label" for="send-notification">
{{ $t("sendNotificationTitle") }}
</label>
<div class="form-text">
{{ $t("sendNotificationDescription") }}
</div>
</div>
<!-- HTTP / Keyword only -->
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
<div class="my-3">
@ -200,14 +210,13 @@
track-by="id"
label="name"
:multiple="true"
:allow-empty="false"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
:placeholder="$t('Pick Dependent Monitors...')"
:preselect-first="false"
:max-height="600"
:taggable="false"
:taggable="true"
></VueMultiselect>
<div class="form-text">
@ -476,6 +485,7 @@ export default {
notificationIDList: {},
ignoreTls: false,
upsideDown: false,
noNotificationIfMasterDown: false,
maxredirects: 10,
accepted_statuscodes: ["200-299"],
dns_resolve_type: "A",