diff --git a/server/model/monitor.js b/server/model/monitor.js index 3b8d89dd4..274feceac 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -42,6 +42,7 @@ const { axiosAbortSignal, checkCertificateHostname, } = require("../util-server"); +const { Settings } = require("../settings"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); const { Notification } = require("../notification"); @@ -441,6 +442,16 @@ class Monitor extends BeanModel { } } + /** + * If the monitor designated to represent healthy connectivity is down, + * then we can just stop here. + */ + const systemIsHealthy = await this.systemIsHealthy(); + if (systemIsHealthy === false) { + log.warn("monitor", "Health check monitor is down, monitoring paused!"); + return; + } + // Expose here for prometheus update // undefined if not https let tlsInfo = undefined; @@ -2158,6 +2169,42 @@ class Monitor extends BeanModel { await this.checkCertExpiryNotifications(tlsInfo); } } + + /** + * Checks if the monitor selected for the health check is down. + * @returns {Promise} If true, the system is healthy. + */ + async systemIsHealthy() { + let healthCheckMonitorId = await Settings.get("healthCheckMonitorId"); + + // User hasn't made a selection yet, save in the database as null + if (healthCheckMonitorId === undefined) { + await setSetting("healthCheckMonitorId", null); + healthCheckMonitorId = null; + } + + // No health check monitor is specified, nothing to do! + if (healthCheckMonitorId === null) { + return true; + } + + // We still need to check the health check monitor + if (healthCheckMonitorId === this.id) { + return true; + } + + const healthCheckMonitor = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ + healthCheckMonitorId, + ]); + + if (healthCheckMonitor) { + return healthCheckMonitor.status === UP; + } + + // Default to indicative of being healthy, this shouldn't happen + // Better to be safe if we can't find the selected monitor, it may have been deleted + return true; + } } module.exports = Monitor; diff --git a/src/components/HealthCheckAlert.vue b/src/components/HealthCheckAlert.vue new file mode 100644 index 000000000..6b2dfd204 --- /dev/null +++ b/src/components/HealthCheckAlert.vue @@ -0,0 +1,83 @@ + + + diff --git a/src/components/settings/Notifications.vue b/src/components/settings/Notifications.vue index 6db2518ec..1e5c9db7e 100644 --- a/src/components/settings/Notifications.vue +++ b/src/components/settings/Notifications.vue @@ -54,6 +54,25 @@ +
+
{{ $t("Heath Check") }}
+

{{ $t("HealthCheckDescription") }}

+ +
+ + +
+
+
{{ $t("settingsCertificateExpiry") }}

{{ $t("certificationExpiryDescription") }}

diff --git a/src/lang/en.json b/src/lang/en.json index a2a87669d..409479922 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1278,6 +1278,8 @@ "Plain Text": "Plain Text", "Message Template": "Message Template", "Template Format": "Template Format", + "Heath Check": "Heath Check", + "HealthCheckDescription": "If the selected monitor is offline, all notifications will be paused and downtime ignored. Ideal for monitoring connectivity to the internet.", "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under", "smsplanetApiToken": "Token for the SMSPlanet API", "smsplanetApiDocs": "Detailed information on obtaining API tokens can be found in {the_smsplanet_documentation}.", diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 70480cd95..37660ba46 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -126,6 +126,7 @@
+
@@ -169,10 +170,12 @@ import Login from "../components/Login.vue"; import compareVersions from "compare-versions"; import { useToast } from "vue-toastification"; +import HealthCheckAlert from "../components/HealthCheckAlert.vue"; const toast = useToast(); export default { components: { + HealthCheckAlert, Login, }, diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index b8cc42748..18c3d279e 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -188,6 +188,10 @@ export default { this.settings.trustProxy = false; } + if (this.settings.healthCheckMonitorId === undefined) { + this.settings.healthCheckMonitorId = null; + } + this.settingsLoaded = true; }); },