mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-07-21 00:24:04 +02:00
Merge 7a94fc86cd
into 510056fbbc
This commit is contained in:
commit
45837a7a02
6 changed files with 161 additions and 2 deletions
|
@ -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,
|
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
||||||
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||||
} = require("../util-server");
|
} = require("../util-server");
|
||||||
|
const { Settings } = require("../settings");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
const { Notification } = require("../notification");
|
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
|
// Expose here for prometheus update
|
||||||
// undefined if not https
|
// undefined if not https
|
||||||
let tlsInfo = undefined;
|
let tlsInfo = undefined;
|
||||||
|
@ -1737,6 +1748,42 @@ class Monitor extends BeanModel {
|
||||||
await this.checkCertExpiryNotifications(tlsInfo);
|
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.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;
|
module.exports = Monitor;
|
||||||
|
|
80
src/components/HealthCheckAlert.vue
Normal file
80
src/components/HealthCheckAlert.vue
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="! healthCheckStatus" id="health-check" class="d-flex flex-wrap py-3 mx-4">
|
||||||
|
<div class="alert alert-danger w-100 d-inline-flex align-items-center justify-content-between">
|
||||||
|
<div class="px-3">Monitoring is paused, the health check monitor is down!</div>
|
||||||
|
<div>
|
||||||
|
<router-link :to="monitorURL(healthCheckMonitor.id)" class="btn btn-danger text-nowrap">
|
||||||
|
View {{ healthCheckMonitor.name }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { UP } from "../util.ts";
|
||||||
|
import { getMonitorRelativeURL } from "../util.ts";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
settings: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* Find the designated health check monitor from the monitor list
|
||||||
|
* @returns {*|null} A monitor object if the health check monitor id is set
|
||||||
|
*/
|
||||||
|
healthCheckMonitor() {
|
||||||
|
const healthCheckMonitorId = this.settings?.healthCheckMonitorId;
|
||||||
|
|
||||||
|
if (this.$root.monitorList[healthCheckMonitorId]) {
|
||||||
|
return this.$root.monitorList[healthCheckMonitorId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the designated health check monitor is down
|
||||||
|
* @returns {boolean} The health check monitor status
|
||||||
|
*/
|
||||||
|
healthCheckStatus() {
|
||||||
|
const healthCheckMonitorId = this.healthCheckMonitor?.id;
|
||||||
|
|
||||||
|
if (healthCheckMonitorId in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[healthCheckMonitorId]) {
|
||||||
|
return this.$root.lastHeartbeatList[healthCheckMonitorId].status === UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.loadSettings();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Load settings from server
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
loadSettings() {
|
||||||
|
this.$root.getSocket().emit("getSettings", (res) => {
|
||||||
|
this.settings = res.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL of monitor
|
||||||
|
* @param {number} id ID of monitor
|
||||||
|
* @returns {string} Relative URL of monitor
|
||||||
|
*/
|
||||||
|
monitorURL(id) {
|
||||||
|
return getMonitorRelativeURL(id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -53,6 +53,29 @@
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="my-4 pt-4">
|
||||||
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
|
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
|
||||||
<p>{{ $t("certificationExpiryDescription") }}</p>
|
<p>{{ $t("certificationExpiryDescription") }}</p>
|
||||||
|
@ -184,7 +207,7 @@ export default {
|
||||||
this.toastErrorTimeoutSecs = parsedTimeout > 0 ? parsedTimeout / 1000 : parsedTimeout;
|
this.toastErrorTimeoutSecs = parsedTimeout > 0 ? parsedTimeout / 1000 : parsedTimeout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1067,5 +1067,7 @@
|
||||||
"YZJ Robot Token": "YZJ Robot token",
|
"YZJ Robot Token": "YZJ Robot token",
|
||||||
"Plain Text": "Plain Text",
|
"Plain Text": "Plain Text",
|
||||||
"Message Template": "Message Template",
|
"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."
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
<health-check-alert v-if="$root.loggedIn" />
|
||||||
<router-view v-if="$root.loggedIn" />
|
<router-view v-if="$root.loggedIn" />
|
||||||
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
||||||
</main>
|
</main>
|
||||||
|
@ -133,11 +134,13 @@
|
||||||
import Login from "../components/Login.vue";
|
import Login from "../components/Login.vue";
|
||||||
import compareVersions from "compare-versions";
|
import compareVersions from "compare-versions";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
import HealthCheckAlert from "../components/HealthCheckAlert.vue";
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
HealthCheckAlert,
|
||||||
Login,
|
Login,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,10 @@ export default {
|
||||||
this.settings.trustProxy = false;
|
this.settings.trustProxy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.settings.healthCheckMonitorId === undefined) {
|
||||||
|
this.settings.healthCheckMonitorId = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.settingsLoaded = true;
|
this.settingsLoaded = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue