Added initial working "Health Check" feature

This commit is contained in:
XGhozt 2025-03-23 04:43:49 -07:00
parent a7d1b99719
commit 18a2c8168b
4 changed files with 78 additions and 2 deletions
server/model
src
components/settings
lang
pages

View file

@ -7,6 +7,7 @@ const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MI
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
} = require("../util-server");
const { Settings } = require("../settings");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
@ -345,6 +346,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;
@ -1737,6 +1748,42 @@ class Monitor extends BeanModel {
await this.checkCertExpiryNotifications(tlsInfo);
}
}
/**
* Checks if the monitor selected for the health check is down.
* @returns {Promise<boolean>} 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.id === UP;
}
// Default to indicative of being healthy, this shouldn't happen
// Better to be safe if we can't find the selector monitor, it may have been deleted
return true;
}
}
module.exports = Monitor;

View file

@ -53,6 +53,29 @@
</div>
</div>
<div v-if="settingsLoaded" class="my-4 pt-4">
<h5 class="my-4 settings-subheading">{{ $t("Heath Check") }}</h5>
<p>{{ $t("HealthCheckDescription") }}</p>
<div class="my-4">
<label for="timezone" class="form-label">
{{ $t("Monitor") }}
</label>
<select id="timezone" v-model="settings.healthCheckMonitorId" class="form-select">
<option :value="null">
{{ $t("Select") }}
</option>
<option
v-for="(monitor, index) in $root.monitorList"
:key="index"
:value="monitor.id"
>
{{ monitor.name }}
</option>
</select>
</div>
</div>
<div class="my-4 pt-4">
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
<p>{{ $t("certificationExpiryDescription") }}</p>
@ -184,7 +207,7 @@ export default {
this.toastErrorTimeoutSecs = parsedTimeout > 0 ? parsedTimeout / 1000 : parsedTimeout;
}
}
},
}
},
};
</script>

View file

@ -1067,5 +1067,7 @@
"YZJ Robot Token": "YZJ Robot token",
"Plain Text": "Plain Text",
"Message Template": "Message Template",
"Template Format": "Template Format"
"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."
}

View file

@ -183,6 +183,10 @@ export default {
this.settings.trustProxy = false;
}
if (this.settings.healthCheckMonitorId === undefined) {
this.settings.healthCheckMonitorId = null;
}
this.settingsLoaded = true;
});
},