mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-02 03:26:31 -04:00
Merge branch 'master' into 1.23.X-merge-to-2.X.X
# Conflicts: # docker/debian-base.dockerfile # package-lock.json # server/database.js # server/model/monitor.js # server/uptime-kuma-server.js # server/util-server.js
This commit is contained in:
commit
ace1fe00c2
316 changed files with 9487 additions and 4997 deletions
|
@ -2,10 +2,10 @@ const https = require("https");
|
|||
const dayjs = require("dayjs");
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||
SQL_DATETIME_FORMAT, isDev, sleep, getRandomInt
|
||||
} = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
||||
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
||||
redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||
} = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
|
@ -18,10 +18,10 @@ const apicache = require("../modules/apicache");
|
|||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
|
||||
const { DockerHost } = require("../docker");
|
||||
const { UptimeCacheList } = require("../uptime-cache-list");
|
||||
const Gamedig = require("gamedig");
|
||||
const jsonata = require("jsonata");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { UptimeCalculator } = require("../uptime-calculator");
|
||||
|
||||
const rootCertificates = rootCertificatesFingerprints();
|
||||
|
||||
|
@ -35,9 +35,12 @@ const rootCertificates = rootCertificatesFingerprints();
|
|||
class Monitor extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
* Return an object that ready to parse to JSON for public Only show
|
||||
* necessary data to public
|
||||
* @param {boolean} showTags Include tags in JSON
|
||||
* @param {boolean} certExpiry Include certificate expiry info in
|
||||
* JSON
|
||||
* @returns {object} Object ready to parse
|
||||
*/
|
||||
async toPublicJSON(showTags = false, certExpiry = false) {
|
||||
let obj = {
|
||||
|
@ -66,7 +69,9 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
* @param {boolean} includeSensitiveData Include sensitive data in
|
||||
* JSON
|
||||
* @returns {object} Object ready to parse
|
||||
*/
|
||||
async toJSON(includeSensitiveData = true) {
|
||||
|
||||
|
@ -184,9 +189,9 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if the monitor is active based on itself and its parents
|
||||
* @returns {Promise<Boolean>}
|
||||
*/
|
||||
* Checks if the monitor is active based on itself and its parents
|
||||
* @returns {Promise<boolean>} Is the monitor active?
|
||||
*/
|
||||
async isActive() {
|
||||
const parentActive = await Monitor.isParentActive(this.id);
|
||||
|
||||
|
@ -195,7 +200,8 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Get all tags applied to this monitor
|
||||
* @returns {Promise<LooseObject<any>[]>}
|
||||
* @returns {Promise<LooseObject<any>[]>} List of tags on the
|
||||
* monitor
|
||||
*/
|
||||
async getTags() {
|
||||
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ? ORDER BY tag.name", [ this.id ]);
|
||||
|
@ -204,7 +210,8 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Gets certificate expiry for this monitor
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
* @returns {Promise<LooseObject<any>>} Certificate expiry info for
|
||||
* monitor
|
||||
*/
|
||||
async getCertExpiry(monitorID) {
|
||||
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||
|
@ -229,7 +236,9 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Encode user and password to Base64 encoding
|
||||
* for HTTP "basic" auth, as per RFC-7617
|
||||
* @returns {string}
|
||||
* @param {string} user Username to encode
|
||||
* @param {string} pass Password to encode
|
||||
* @returns {string} Encoded username:password
|
||||
*/
|
||||
encodeBase64(user, pass) {
|
||||
return Buffer.from(user + ":" + pass).toString("base64");
|
||||
|
@ -237,7 +246,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Is the TLS expiry notification enabled?
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Enabled?
|
||||
*/
|
||||
isEnabledExpiryNotification() {
|
||||
return Boolean(this.expiryNotification);
|
||||
|
@ -245,7 +254,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Should TLS errors be ignored?
|
||||
*/
|
||||
getIgnoreTls() {
|
||||
return Boolean(this.ignoreTls);
|
||||
|
@ -253,7 +262,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Is the monitor in upside down mode?
|
||||
*/
|
||||
isUpsideDown() {
|
||||
return Boolean(this.upsideDown);
|
||||
|
@ -261,7 +270,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Invert keyword match?
|
||||
*/
|
||||
isInvertKeyword() {
|
||||
return Boolean(this.invertKeyword);
|
||||
|
@ -269,7 +278,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Enable TLS for gRPC?
|
||||
*/
|
||||
getGrpcEnableTls() {
|
||||
return Boolean(this.grpcEnableTls);
|
||||
|
@ -277,12 +286,16 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Get accepted status codes
|
||||
* @returns {Object}
|
||||
* @returns {object} Accepted status codes
|
||||
*/
|
||||
getAcceptedStatuscodes() {
|
||||
return JSON.parse(this.accepted_statuscodes_json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if game dig should only use the port which was provided
|
||||
* @returns {boolean} gamedig should only use the provided port
|
||||
*/
|
||||
getGameDigGivenPortOnly() {
|
||||
return Boolean(this.gamedigGivenPortOnly);
|
||||
}
|
||||
|
@ -306,6 +319,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Start monitor
|
||||
* @param {Server} io Socket server instance
|
||||
* @returns {void}
|
||||
*/
|
||||
start(io) {
|
||||
let previousBeat = null;
|
||||
|
@ -360,13 +374,6 @@ class Monitor extends BeanModel {
|
|||
bean.status = flipStatus(bean.status);
|
||||
}
|
||||
|
||||
// Duration
|
||||
if (!isFirstBeat) {
|
||||
bean.duration = dayjs(bean.time).diff(dayjs(previousBeat.time), "second");
|
||||
} else {
|
||||
bean.duration = 0;
|
||||
}
|
||||
|
||||
// Runtime patch timeout if it is 0
|
||||
// See https://github.com/louislam/uptime-kuma/pull/3961#issuecomment-1804149144
|
||||
if (this.timeout <= 0) {
|
||||
|
@ -458,6 +465,9 @@ class Monitor extends BeanModel {
|
|||
} catch (e) {
|
||||
throw new Error("Your JSON body is invalid. " + e.message);
|
||||
}
|
||||
} else if (this.httpBodyEncoding === "form") {
|
||||
bodyValue = this.body;
|
||||
contentType = "application/x-www-form-urlencoded";
|
||||
} else if (this.httpBodyEncoding === "xml") {
|
||||
bodyValue = this.body;
|
||||
contentType = "text/xml; charset=utf-8";
|
||||
|
@ -471,7 +481,6 @@ class Monitor extends BeanModel {
|
|||
timeout: this.timeout * 1000,
|
||||
headers: {
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
...(contentType ? { "Content-Type": contentType } : {}),
|
||||
...(basicAuthHeader),
|
||||
...(oauth2AuthHeader),
|
||||
|
@ -609,46 +618,6 @@ class Monitor extends BeanModel {
|
|||
bean.ping = await ping(this.hostname, this.packetSize);
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
} else if (this.type === "dns") {
|
||||
let startTime = dayjs().valueOf();
|
||||
let dnsMessage = "";
|
||||
|
||||
let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.port, this.dns_resolve_type);
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
if (this.dns_resolve_type === "A" || this.dns_resolve_type === "AAAA" || this.dns_resolve_type === "TXT" || this.dns_resolve_type === "PTR") {
|
||||
dnsMessage += "Records: ";
|
||||
dnsMessage += dnsRes.join(" | ");
|
||||
} else if (this.dns_resolve_type === "CNAME") {
|
||||
dnsMessage += dnsRes[0];
|
||||
} else if (this.dns_resolve_type === "CAA") {
|
||||
dnsMessage += dnsRes[0].issue;
|
||||
} else if (this.dns_resolve_type === "MX") {
|
||||
dnsRes.forEach(record => {
|
||||
dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `;
|
||||
});
|
||||
dnsMessage = dnsMessage.slice(0, -2);
|
||||
} else if (this.dns_resolve_type === "NS") {
|
||||
dnsMessage += "Servers: ";
|
||||
dnsMessage += dnsRes.join(" | ");
|
||||
} else if (this.dns_resolve_type === "SOA") {
|
||||
dnsMessage += `NS-Name: ${dnsRes.nsname} | Hostmaster: ${dnsRes.hostmaster} | Serial: ${dnsRes.serial} | Refresh: ${dnsRes.refresh} | Retry: ${dnsRes.retry} | Expire: ${dnsRes.expire} | MinTTL: ${dnsRes.minttl}`;
|
||||
} else if (this.dns_resolve_type === "SRV") {
|
||||
dnsRes.forEach(record => {
|
||||
dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `;
|
||||
});
|
||||
dnsMessage = dnsMessage.slice(0, -2);
|
||||
}
|
||||
|
||||
if (this.dnsLastResult !== dnsMessage && dnsMessage !== undefined) {
|
||||
R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [
|
||||
dnsMessage,
|
||||
this.id
|
||||
]);
|
||||
}
|
||||
|
||||
bean.msg = dnsMessage;
|
||||
bean.status = UP;
|
||||
} else if (this.type === "push") { // Type: Push
|
||||
log.debug("monitor", `[${this.name}] Checking monitor at ${dayjs().format("YYYY-MM-DD HH:mm:ss.SSS")}`);
|
||||
const bufferTime = 1000; // 1s buffer to accommodate clock differences
|
||||
|
@ -692,7 +661,6 @@ class Monitor extends BeanModel {
|
|||
timeout: this.timeout * 1000,
|
||||
headers: {
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
},
|
||||
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
|
@ -744,7 +712,6 @@ class Monitor extends BeanModel {
|
|||
timeout: this.interval * 1000 * 0.8,
|
||||
headers: {
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
},
|
||||
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
|
@ -1000,11 +967,17 @@ class Monitor extends BeanModel {
|
|||
log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
|
||||
}
|
||||
|
||||
// Calculate uptime
|
||||
let uptimeCalculator = await UptimeCalculator.getUptimeCalculator(this.id);
|
||||
let endTimeDayjs = await uptimeCalculator.update(bean.status, parseFloat(bean.ping));
|
||||
bean.end_time = R.isoDateTimeMillis(endTimeDayjs);
|
||||
|
||||
// Send to frontend
|
||||
log.debug("monitor", `[${this.name}] Send to socket`);
|
||||
UptimeCacheList.clearCache(this.id);
|
||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||
Monitor.sendStats(io, this.id, this.user_id);
|
||||
|
||||
// Store to database
|
||||
log.debug("monitor", `[${this.name}] Store`);
|
||||
await R.store(bean);
|
||||
|
||||
|
@ -1015,7 +988,15 @@ class Monitor extends BeanModel {
|
|||
|
||||
if (! this.isStop) {
|
||||
log.debug("monitor", `[${this.name}] SetTimeout for next check.`);
|
||||
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
|
||||
|
||||
let intervalRemainingMs = Math.max(
|
||||
1,
|
||||
beatInterval * 1000 - dayjs().diff(dayjs.utc(bean.time))
|
||||
);
|
||||
|
||||
log.debug("monitor", `[${this.name}] Next heartbeat in: ${intervalRemainingMs}ms`);
|
||||
|
||||
this.heartbeatInterval = setTimeout(safeBeat, intervalRemainingMs);
|
||||
this.lastScheduleBeatTime = dayjs();
|
||||
} else {
|
||||
log.info("monitor", `[${this.name}] isStop = true, no next check.`);
|
||||
|
@ -1023,7 +1004,10 @@ class Monitor extends BeanModel {
|
|||
|
||||
};
|
||||
|
||||
/** Get a heartbeat and handle errors */
|
||||
/**
|
||||
* Get a heartbeat and handle errors7
|
||||
* @returns {void}
|
||||
*/
|
||||
const safeBeat = async () => {
|
||||
try {
|
||||
this.lastStartBeatTime = dayjs();
|
||||
|
@ -1056,10 +1040,10 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Make a request using axios
|
||||
* @param {Object} options Options for Axios
|
||||
* @param {object} options Options for Axios
|
||||
* @param {boolean} finalCall Should this be the final call i.e
|
||||
* don't retry on faliure
|
||||
* @returns {Object} Axios response
|
||||
* don't retry on failure
|
||||
* @returns {object} Axios response
|
||||
*/
|
||||
async makeAxiosRequest(options, finalCall = false) {
|
||||
try {
|
||||
|
@ -1094,7 +1078,10 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/** Stop monitor */
|
||||
/**
|
||||
* Stop monitor
|
||||
* @returns {void}
|
||||
*/
|
||||
stop() {
|
||||
clearTimeout(this.heartbeatInterval);
|
||||
this.isStop = true;
|
||||
|
@ -1104,7 +1091,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Get prometheus instance
|
||||
* @returns {Prometheus|undefined}
|
||||
* @returns {Prometheus|undefined} Current prometheus instance
|
||||
*/
|
||||
getPrometheus() {
|
||||
return this.prometheus;
|
||||
|
@ -1114,7 +1101,7 @@ class Monitor extends BeanModel {
|
|||
* Helper Method:
|
||||
* returns URL object for further usage
|
||||
* returns null if url is invalid
|
||||
* @returns {(null|URL)}
|
||||
* @returns {(null|URL)} Monitor URL
|
||||
*/
|
||||
getUrl() {
|
||||
try {
|
||||
|
@ -1126,7 +1113,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Example: http: or https:
|
||||
* @returns {(null|string)}
|
||||
* @returns {(null|string)} URL's protocol
|
||||
*/
|
||||
getURLProtocol() {
|
||||
const url = this.getUrl();
|
||||
|
@ -1139,8 +1126,8 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Store TLS info to database
|
||||
* @param checkCertificateResult
|
||||
* @returns {Promise<Object>}
|
||||
* @param {object} checkCertificateResult Certificate to update
|
||||
* @returns {Promise<object>} Updated certificate
|
||||
*/
|
||||
async updateTlsInfo(checkCertificateResult) {
|
||||
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||
|
@ -1187,47 +1174,41 @@ class Monitor extends BeanModel {
|
|||
* @param {Server} io Socket server instance
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @param {number} userID ID of user to send to
|
||||
* @returns {void}
|
||||
*/
|
||||
static async sendStats(io, monitorID, userID) {
|
||||
const hasClients = getTotalClientInRoom(io, userID) > 0;
|
||||
let uptimeCalculator = await UptimeCalculator.getUptimeCalculator(monitorID);
|
||||
|
||||
if (hasClients) {
|
||||
await Monitor.sendAvgPing(24, io, monitorID, userID);
|
||||
await Monitor.sendUptime(24, io, monitorID, userID);
|
||||
await Monitor.sendUptime(24 * 30, io, monitorID, userID);
|
||||
// Send 24 hour average ping
|
||||
let data24h = await uptimeCalculator.get24Hour();
|
||||
io.to(userID).emit("avgPing", monitorID, (data24h.avgPing) ? Number(data24h.avgPing.toFixed(2)) : null);
|
||||
|
||||
// Send 24 hour uptime
|
||||
io.to(userID).emit("uptime", monitorID, 24, data24h.uptime);
|
||||
|
||||
// Send 30 day uptime
|
||||
let data30d = await uptimeCalculator.get30Day();
|
||||
io.to(userID).emit("uptime", monitorID, 720, data30d.uptime);
|
||||
|
||||
// Send 1-year uptime
|
||||
let data1y = await uptimeCalculator.get1Year();
|
||||
io.to(userID).emit("uptime", monitorID, "1y", data1y.uptime);
|
||||
|
||||
// Send Cert Info
|
||||
await Monitor.sendCertInfo(io, monitorID, userID);
|
||||
} else {
|
||||
log.debug("monitor", "No clients in the room, no need to send stats");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the average ping to user
|
||||
* @param {number} duration Hours
|
||||
*/
|
||||
static async sendAvgPing(duration, io, monitorID, userID) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
||||
let avgPing = parseInt(await R.getCell(`
|
||||
SELECT AVG(ping)
|
||||
FROM heartbeat
|
||||
WHERE time > DATETIME('now', ? || ' hours')
|
||||
AND ping IS NOT NULL
|
||||
AND monitor_id = ? `, [
|
||||
-duration,
|
||||
monitorID,
|
||||
]));
|
||||
|
||||
timeLogger.print(`[Monitor: ${monitorID}] avgPing`);
|
||||
|
||||
io.to(userID).emit("avgPing", monitorID, avgPing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send certificate information to client
|
||||
* @param {Server} io Socket server instance
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @param {number} userID ID of user to send to
|
||||
* @returns {void}
|
||||
*/
|
||||
static async sendCertInfo(io, monitorID, userID) {
|
||||
let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||
|
@ -1238,98 +1219,6 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uptime with calculation
|
||||
* Calculation based on:
|
||||
* https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime
|
||||
* @param {number} duration Hours
|
||||
* @param {number} monitorID ID of monitor to calculate
|
||||
*/
|
||||
static async calcUptime(duration, monitorID, forceNoCache = false) {
|
||||
|
||||
if (!forceNoCache) {
|
||||
let cachedUptime = UptimeCacheList.getUptime(monitorID, duration);
|
||||
if (cachedUptime != null) {
|
||||
return cachedUptime;
|
||||
}
|
||||
}
|
||||
|
||||
const timeLogger = new TimeLogger();
|
||||
|
||||
const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour"));
|
||||
|
||||
// Handle if heartbeat duration longer than the target duration
|
||||
// e.g. If the last beat's duration is bigger that the 24hrs window, it will use the duration between the (beat time - window margin) (THEN case in SQL)
|
||||
let result = await R.getRow(`
|
||||
SELECT
|
||||
-- SUM all duration, also trim off the beat out of time window
|
||||
SUM(
|
||||
CASE
|
||||
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
|
||||
THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
|
||||
ELSE duration
|
||||
END
|
||||
) AS total_duration,
|
||||
|
||||
-- SUM all uptime duration, also trim off the beat out of time window
|
||||
SUM(
|
||||
CASE
|
||||
WHEN (status = 1 OR status = 3)
|
||||
THEN
|
||||
CASE
|
||||
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
|
||||
THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
|
||||
ELSE duration
|
||||
END
|
||||
END
|
||||
) AS uptime_duration
|
||||
FROM heartbeat
|
||||
WHERE time > ?
|
||||
AND monitor_id = ?
|
||||
`, [
|
||||
startTime, startTime, startTime, startTime, startTime,
|
||||
monitorID,
|
||||
]);
|
||||
|
||||
timeLogger.print(`[Monitor: ${monitorID}][${duration}] sendUptime`);
|
||||
|
||||
let totalDuration = result.total_duration;
|
||||
let uptimeDuration = result.uptime_duration;
|
||||
let uptime = 0;
|
||||
|
||||
if (totalDuration > 0) {
|
||||
uptime = uptimeDuration / totalDuration;
|
||||
if (uptime < 0) {
|
||||
uptime = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Handle new monitor with only one beat, because the beat's duration = 0
|
||||
let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ]));
|
||||
|
||||
if (status === UP) {
|
||||
uptime = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache
|
||||
UptimeCacheList.addUptime(monitorID, duration, uptime);
|
||||
|
||||
return uptime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Uptime
|
||||
* @param {number} duration Hours
|
||||
* @param {Server} io Socket server instance
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @param {number} userID ID of user to send to
|
||||
*/
|
||||
static async sendUptime(duration, io, monitorID, userID) {
|
||||
const uptime = await this.calcUptime(duration, monitorID);
|
||||
io.to(userID).emit("uptime", monitorID, duration, uptime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has status of monitor changed since last beat?
|
||||
* @param {boolean} isFirstBeat Is this the first beat of this monitor?
|
||||
|
@ -1398,6 +1287,7 @@ class Monitor extends BeanModel {
|
|||
* @param {boolean} isFirstBeat Is this beat the first of this monitor?
|
||||
* @param {Monitor} monitor The monitor to send a notificaton about
|
||||
* @param {Bean} bean Status information about monitor
|
||||
* @returns {void}
|
||||
*/
|
||||
static async sendNotification(isFirstBeat, monitor, bean) {
|
||||
if (!isFirstBeat || bean.status === DOWN) {
|
||||
|
@ -1438,7 +1328,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Get list of notification providers for a given monitor
|
||||
* @param {Monitor} monitor Monitor to get notification providers for
|
||||
* @returns {Promise<LooseObject<any>[]>}
|
||||
* @returns {Promise<LooseObject<any>[]>} List of notifications
|
||||
*/
|
||||
static async getNotificationList(monitor) {
|
||||
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
|
||||
|
@ -1449,7 +1339,8 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* checks certificate chain for expiring certificates
|
||||
* @param {Object} tlsInfoObject Information about certificate
|
||||
* @param {object} tlsInfoObject Information about certificate
|
||||
* @returns {void}
|
||||
*/
|
||||
async checkCertExpiryNotifications(tlsInfoObject) {
|
||||
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
||||
|
@ -1539,7 +1430,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Get the status of the previous heartbeat
|
||||
* @param {number} monitorID ID of monitor to check
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
* @returns {Promise<LooseObject<any>>} Previous heartbeat
|
||||
*/
|
||||
static async getPreviousHeartbeat(monitorID) {
|
||||
return await R.getRow(`
|
||||
|
@ -1553,7 +1444,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Check if monitor is under maintenance
|
||||
* @param {number} monitorID ID of monitor to check
|
||||
* @returns {Promise<boolean>}
|
||||
* @returns {Promise<boolean>} Is the monitor under maintenance
|
||||
*/
|
||||
static async isUnderMaintenance(monitorID) {
|
||||
const maintenanceIDList = await R.getCol(`
|
||||
|
@ -1576,7 +1467,11 @@ class Monitor extends BeanModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** Make sure monitor interval is between bounds */
|
||||
/**
|
||||
* Make sure monitor interval is between bounds
|
||||
* @returns {void}
|
||||
* @throws Interval is outside of range
|
||||
*/
|
||||
validate() {
|
||||
if (this.interval > MAX_INTERVAL_SECOND) {
|
||||
throw new Error(`Interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`);
|
||||
|
@ -1589,7 +1484,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Gets Parent of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
* @returns {Promise<LooseObject<any>>} Parent
|
||||
*/
|
||||
static async getParent(monitorID) {
|
||||
return await R.getRow(`
|
||||
|
@ -1605,7 +1500,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Gets all Children of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
* @returns {Promise<LooseObject<any>>} Children
|
||||
*/
|
||||
static async getChildren(monitorID) {
|
||||
return await R.getAll(`
|
||||
|
@ -1618,7 +1513,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Gets Full Path-Name (Groups and Name)
|
||||
* @returns {Promise<String>}
|
||||
* @returns {Promise<string>} Full path name of this monitor
|
||||
*/
|
||||
async getPathName() {
|
||||
let path = this.name;
|
||||
|
@ -1638,8 +1533,8 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Gets recursive all child ids
|
||||
* @param {number} monitorID ID of the monitor to get
|
||||
* @returns {Promise<Array>}
|
||||
* @param {number} monitorID ID of the monitor to get
|
||||
* @returns {Promise<Array>} IDs of all children
|
||||
*/
|
||||
static async getAllChildrenIDs(monitorID) {
|
||||
const childs = await Monitor.getChildren(monitorID);
|
||||
|
@ -1670,10 +1565,10 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks recursive if parent (ancestors) are active
|
||||
* @param {number} monitorID ID of the monitor to get
|
||||
* @returns {Promise<Boolean>}
|
||||
*/
|
||||
* Checks recursive if parent (ancestors) are active
|
||||
* @param {number} monitorID ID of the monitor to get
|
||||
* @returns {Promise<boolean>} Is the parent monitor active?
|
||||
*/
|
||||
static async isParentActive(monitorID) {
|
||||
const parent = await Monitor.getParent(monitorID);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue