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. -- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION; BEGIN TRANSACTION;
ALTER TABLE monitor
ADD no_notification_if_master_down BOOLEAN default 0 NOT NULL;
CREATE TABLE dependent_monitors CREATE TABLE dependent_monitors
( (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,

View file

@ -72,6 +72,7 @@ class Monitor extends BeanModel {
keyword: this.keyword, keyword: this.keyword,
ignoreTls: this.getIgnoreTls(), ignoreTls: this.getIgnoreTls(),
upsideDown: this.isUpsideDown(), upsideDown: this.isUpsideDown(),
noNotificationIfMasterDown: this.getNoNotificationIfMasterDown(),
maxredirects: this.maxredirects, maxredirects: this.maxredirects,
accepted_statuscodes: this.getAcceptedStatuscodes(), accepted_statuscodes: this.getAcceptedStatuscodes(),
dns_resolve_type: this.dns_resolve_type, dns_resolve_type: this.dns_resolve_type,
@ -108,6 +109,14 @@ class Monitor extends BeanModel {
return Boolean(this.upsideDown); return Boolean(this.upsideDown);
} }
/**
* Parse to boolean
* @returns {boolean}
*/
getNoNotificationIfMasterDown() {
return Boolean(this.noNotificationIfMasterDown);
}
getAcceptedStatuscodes() { getAcceptedStatuscodes() {
return JSON.parse(this.accepted_statuscodes_json); return JSON.parse(this.accepted_statuscodes_json);
} }
@ -148,7 +157,10 @@ class Monitor extends BeanModel {
bean.duration = 0; bean.duration = 0;
} }
let isDegraded = false;
try { try {
isDegraded = await Monitor.isDegraded(this.id);
if (this.type === "http" || this.type === "keyword") { if (this.type === "http" || this.type === "keyword") {
// Do not do any queries/high loading things before the "bean.ping" // Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf(); let startTime = dayjs().valueOf();
@ -363,8 +375,8 @@ class Monitor extends BeanModel {
retries = 0; retries = 0;
if (bean.status === UP && await Monitor.isDegraded(this.id)) { if (bean.status === UP && isDegraded) {
bean.msg = "Monitor is degraded, because at least one dependent monitor is DOWN"; bean.msg = "Monitor is degraded, because at least one master monitor is pending, down or degraded";
bean.status = DEGRADED; bean.status = DEGRADED;
} }
@ -381,6 +393,12 @@ class Monitor extends BeanModel {
retries++; retries++;
bean.status = PENDING; 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; let beatInterval = this.interval;
@ -393,7 +411,10 @@ class Monitor extends BeanModel {
if (isImportant) { if (isImportant) {
bean.important = true; 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`); debug(`[${this.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, this, bean); await Monitor.sendNotification(isFirstBeat, this, bean);
} }
@ -801,7 +822,7 @@ class Monitor extends BeanModel {
static async getPreviousHeartbeat(monitorID) { static async getPreviousHeartbeat(monitorID) {
return await R.getRow(` 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 = ?) WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
`, [ `, [
monitorID monitorID
@ -811,7 +832,7 @@ class Monitor extends BeanModel {
static async isDegraded(monitorID) { static async isDegraded(monitorID) {
const monitors = await R.getAll(` 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) 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 monitorID
]); ]);

View file

@ -51,7 +51,9 @@ router.get("/api/push/:pushToken", async (request, response) => {
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second"); 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"; msg = "Monitor is degraded, because at least one dependent monitor is DOWN";
status = DEGRADED; status = DEGRADED;
} }
@ -75,7 +77,10 @@ router.get("/api/push/:pushToken", async (request, response) => {
ok: true, 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); await Monitor.sendNotification(isFirstBeat, monitor, bean);
} }

View file

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

View file

@ -6,8 +6,11 @@ export default {
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites", ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.", 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.", 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", Degraded: "Degraded",
"Dependent Monitors": "Dependent Monitors", "Dependent Monitors": "Dependent Monitors",
monitorDependsOn: "The status of this monitor depends on",
"Pick Dependent Monitors...": "Pick Dependent Monitors...", "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.", 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.", acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.",

View file

@ -103,6 +103,14 @@
</div> </div>
</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="shadow-box table-shadow-box">
<div class="dropdown dropdown-clear-data"> <div class="dropdown dropdown-clear-data">
<button class="btn btn-sm btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown"> <button class="btn btn-sm btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown">
@ -154,14 +162,6 @@
</div> </div>
</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"> <Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseMonitor">
{{ $t("pauseMonitorMsg") }} {{ $t("pauseMonitorMsg") }}
</Confirm> </Confirm>

View file

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