mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-07-18 15:24:03 +02:00
Merge 771845c62f
into 2fd4e1cc72
This commit is contained in:
commit
8ad23abcd6
5 changed files with 188 additions and 86 deletions
|
@ -1477,10 +1477,12 @@ class Monitor extends BeanModel {
|
||||||
* @returns {Promise<boolean>} Is the monitor under maintenance
|
* @returns {Promise<boolean>} Is the monitor under maintenance
|
||||||
*/
|
*/
|
||||||
static async isUnderMaintenance(monitorID) {
|
static async isUnderMaintenance(monitorID) {
|
||||||
|
const ancestorIDs = await Monitor.getAllAncestorIDs(monitorID);
|
||||||
|
const allIDs = [ monitorID, ...ancestorIDs ];
|
||||||
const maintenanceIDList = await R.getCol(`
|
const maintenanceIDList = await R.getCol(`
|
||||||
SELECT maintenance_id FROM monitor_maintenance
|
SELECT maintenance_id FROM monitor_maintenance
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id IN (${allIDs.map((_) => "?").join(",")})
|
||||||
`, [ monitorID ]);
|
`, allIDs);
|
||||||
|
|
||||||
for (const maintenanceID of maintenanceIDList) {
|
for (const maintenanceID of maintenanceIDList) {
|
||||||
const maintenance = await UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
|
const maintenance = await UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
|
||||||
|
@ -1489,11 +1491,6 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = await Monitor.getParent(monitorID);
|
|
||||||
if (parent != null) {
|
|
||||||
return await Monitor.isUnderMaintenance(parent.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1640,13 +1637,32 @@ class Monitor extends BeanModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all active monitors
|
||||||
|
* @returns {Promise<Bean[]>} Active Monitors
|
||||||
|
*/
|
||||||
|
static async getAllActiveMonitors() {
|
||||||
|
// Gets all monitors but only if they and all their ancestors are active
|
||||||
|
return R.convertToBeans("monitor", await R.getAll(`
|
||||||
|
WITH RECURSIVE MonitorHierarchy AS (
|
||||||
|
SELECT * FROM monitor
|
||||||
|
WHERE parent IS NULL AND active = 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT m.* FROM monitor m
|
||||||
|
INNER JOIN MonitorHierarchy mh ON m.parent = mh.id
|
||||||
|
WHERE m.active = 1
|
||||||
|
)
|
||||||
|
SELECT * FROM MonitorHierarchy;
|
||||||
|
`));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets Parent of the monitor
|
* Gets Parent of the monitor
|
||||||
* @param {number} monitorID ID of monitor to get
|
* @param {number} monitorID ID of monitor to get
|
||||||
* @returns {Promise<LooseObject<any>>} Parent
|
* @returns {Promise<Bean | null>} Parent
|
||||||
*/
|
*/
|
||||||
static async getParent(monitorID) {
|
static async getParent(monitorID) {
|
||||||
return await R.getRow(`
|
const result = await R.getRow(`
|
||||||
SELECT parent.* FROM monitor parent
|
SELECT parent.* FROM monitor parent
|
||||||
LEFT JOIN monitor child
|
LEFT JOIN monitor child
|
||||||
ON child.parent = parent.id
|
ON child.parent = parent.id
|
||||||
|
@ -1654,20 +1670,25 @@ class Monitor extends BeanModel {
|
||||||
`, [
|
`, [
|
||||||
monitorID,
|
monitorID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return R.convertToBean("monitor", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all Children of the monitor
|
* Gets all Children of the monitor
|
||||||
* @param {number} monitorID ID of monitor to get
|
* @param {number} monitorID ID of monitor to get
|
||||||
* @returns {Promise<LooseObject<any>[]>} Children
|
* @returns {Promise<Bean[]>} Children
|
||||||
*/
|
*/
|
||||||
static async getChildren(monitorID) {
|
static async getChildren(monitorID) {
|
||||||
return await R.getAll(`
|
return R.convertToBeans("monitor", await R.getAll(`
|
||||||
SELECT * FROM monitor
|
SELECT * FROM monitor
|
||||||
WHERE parent = ?
|
WHERE parent = ?
|
||||||
`, [
|
`, [
|
||||||
monitorID,
|
monitorID,
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1693,25 +1714,64 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets recursive all child ids
|
* Gets recursive all ancestor ids
|
||||||
* @param {number} monitorID ID of the monitor to get
|
* @param {number} monitorID ID of the monitor to get
|
||||||
* @returns {Promise<Array>} IDs of all children
|
* @returns {Promise<number[]>} IDs of all ancestors
|
||||||
*/
|
*/
|
||||||
static async getAllChildrenIDs(monitorID) {
|
static async getAllAncestorIDs(monitorID) {
|
||||||
const childs = await Monitor.getChildren(monitorID);
|
// Gets all ancestor monitor ids recursive
|
||||||
|
return await R.getCol(`
|
||||||
|
WITH RECURSIVE Ancestors AS (
|
||||||
|
SELECT parent FROM monitor
|
||||||
|
WHERE id = ?
|
||||||
|
UNION ALL
|
||||||
|
SELECT m.parent FROM monitor m
|
||||||
|
JOIN Ancestors a ON m.id = a.parent
|
||||||
|
)
|
||||||
|
SELECT parent AS ancestor_id FROM Ancestors
|
||||||
|
WHERE parent IS NOT NULL;
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (childs === null) {
|
/**
|
||||||
return [];
|
* Gets recursive all children ids
|
||||||
|
* @param {number} monitorID ID of the monitor to get
|
||||||
|
* @param {boolean} onlyActive Return only monitors which are active (including all ancestors)
|
||||||
|
* @returns {Promise<number[]>} IDs of all children
|
||||||
|
*/
|
||||||
|
static async getAllChildrenIDs(monitorID, onlyActive = false) {
|
||||||
|
if (onlyActive) {
|
||||||
|
// Gets all children monitor ids recursive but only if they and all their ancestors are active
|
||||||
|
return await R.getCol(`
|
||||||
|
WITH RECURSIVE MonitorHierarchy(id, active_chain) AS (
|
||||||
|
SELECT id, active FROM monitor
|
||||||
|
WHERE id = ?
|
||||||
|
UNION ALL
|
||||||
|
SELECT m.id, m.active * mh.active_chain FROM monitor m
|
||||||
|
INNER JOIN MonitorHierarchy mh ON m.parent = mh.id
|
||||||
|
WHERE mh.active_chain = 1
|
||||||
|
)
|
||||||
|
SELECT id FROM MonitorHierarchy
|
||||||
|
WHERE id != ? AND active_chain = 1;
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
monitorID
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
// Gets all children monitor ids recursive
|
||||||
let childrenIDs = [];
|
return await R.getCol(`
|
||||||
|
WITH RECURSIVE MonitorHierarchy(id) AS (
|
||||||
for (const child of childs) {
|
SELECT id FROM monitor WHERE id = ?
|
||||||
childrenIDs.push(child.id);
|
UNION ALL
|
||||||
childrenIDs = childrenIDs.concat(await Monitor.getAllChildrenIDs(child.id));
|
SELECT m.id FROM monitor m INNER JOIN MonitorHierarchy mh ON m.parent = mh.id
|
||||||
}
|
)
|
||||||
|
SELECT id FROM MonitorHierarchy WHERE id != ?;
|
||||||
return childrenIDs;
|
`, [
|
||||||
|
monitorID,
|
||||||
|
monitorID
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1731,14 +1791,32 @@ class Monitor extends BeanModel {
|
||||||
* @returns {Promise<boolean>} Is the parent monitor active?
|
* @returns {Promise<boolean>} Is the parent monitor active?
|
||||||
*/
|
*/
|
||||||
static async isParentActive(monitorID) {
|
static async isParentActive(monitorID) {
|
||||||
const parent = await Monitor.getParent(monitorID);
|
// Checks recursive if the parent and all its ancestors are active
|
||||||
|
const result = await R.getRow(`
|
||||||
|
WITH RECURSIVE MonitorHierarchy AS (
|
||||||
|
SELECT parent FROM monitor
|
||||||
|
WHERE id = ?
|
||||||
|
UNION ALL
|
||||||
|
SELECT m.parent FROM monitor m
|
||||||
|
JOIN MonitorHierarchy mh ON m.id = mh.parent
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN (SELECT parent FROM monitor WHERE id = ?) IS NULL THEN 1
|
||||||
|
ELSE
|
||||||
|
CASE
|
||||||
|
WHEN COUNT(m.id) = SUM(m.active) THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
END AS all_active
|
||||||
|
FROM MonitorHierarchy mh
|
||||||
|
LEFT JOIN monitor m ON mh.parent = m.id;
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
monitorID
|
||||||
|
]);
|
||||||
|
|
||||||
if (parent === null) {
|
return result.all_active === 1;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentActive = await Monitor.isParentActive(parent.id);
|
|
||||||
return parent.active && parentActive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,34 +11,41 @@ class GroupMonitorType extends MonitorType {
|
||||||
async check(monitor, heartbeat, _server) {
|
async check(monitor, heartbeat, _server) {
|
||||||
const children = await Monitor.getChildren(monitor.id);
|
const children = await Monitor.getChildren(monitor.id);
|
||||||
|
|
||||||
if (children.length > 0) {
|
if (children.length == 0) {
|
||||||
heartbeat.status = UP;
|
|
||||||
heartbeat.msg = "All children up and running";
|
|
||||||
for (const child of children) {
|
|
||||||
if (!child.active) {
|
|
||||||
// Ignore inactive childs
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
|
||||||
|
|
||||||
// Only change state if the monitor is in worse conditions then the ones before
|
|
||||||
// lastBeat.status could be null
|
|
||||||
if (!lastBeat) {
|
|
||||||
heartbeat.status = PENDING;
|
|
||||||
} else if (heartbeat.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
|
||||||
heartbeat.status = lastBeat.status;
|
|
||||||
} else if (heartbeat.status === PENDING && lastBeat.status === DOWN) {
|
|
||||||
heartbeat.status = lastBeat.status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (heartbeat.status !== UP) {
|
|
||||||
heartbeat.msg = "Child inaccessible";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Set status pending if group is empty
|
// Set status pending if group is empty
|
||||||
heartbeat.status = PENDING;
|
heartbeat.status = PENDING;
|
||||||
heartbeat.msg = "Group empty";
|
heartbeat.msg = "Group empty";
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (children.filter(child => child.active).length === 0) {
|
||||||
|
// Set status pending if all children are paused
|
||||||
|
bean.status = PENDING;
|
||||||
|
bean.msg = "All Children are paused.";
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeat.status = UP;
|
||||||
|
heartbeat.msg = "All children up and running";
|
||||||
|
for (const child of children) {
|
||||||
|
if (!child.active) {
|
||||||
|
// Ignore inactive childs
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||||
|
|
||||||
|
// Only change state if the monitor is in worse conditions then the ones before
|
||||||
|
// lastBeat.status could be null
|
||||||
|
if (!lastBeat) {
|
||||||
|
heartbeat.status = PENDING;
|
||||||
|
} else if (heartbeat.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||||
|
heartbeat.status = lastBeat.status;
|
||||||
|
} else if (heartbeat.status === PENDING && lastBeat.status === DOWN) {
|
||||||
|
heartbeat.status = lastBeat.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heartbeat.status !== UP) {
|
||||||
|
heartbeat.msg = "Child inaccessible";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -729,7 +729,7 @@ let needSetup = false;
|
||||||
|
|
||||||
await updateMonitorNotification(bean.id, notificationIDList);
|
await updateMonitorNotification(bean.id, notificationIDList);
|
||||||
|
|
||||||
await server.sendUpdateMonitorIntoList(socket, bean.id);
|
await server.sendUpdateMonitorsIntoList(socket, bean.id);
|
||||||
|
|
||||||
if (monitor.active !== false) {
|
if (monitor.active !== false) {
|
||||||
await startMonitor(socket.userID, bean.id);
|
await startMonitor(socket.userID, bean.id);
|
||||||
|
@ -898,7 +898,7 @@ let needSetup = false;
|
||||||
await restartMonitor(socket.userID, bean.id);
|
await restartMonitor(socket.userID, bean.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await server.sendUpdateMonitorIntoList(socket, bean.id);
|
await server.sendUpdateMonitorsIntoList(socket, bean.id);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -999,7 +999,9 @@ let needSetup = false;
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await startMonitor(socket.userID, monitorID);
|
await startMonitor(socket.userID, monitorID);
|
||||||
await server.sendUpdateMonitorIntoList(socket, monitorID);
|
|
||||||
|
const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
|
||||||
|
await server.sendUpdateMonitorsIntoList(socket, [ monitorID, ...childrenIDs ]);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -1019,7 +1021,9 @@ let needSetup = false;
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await pauseMonitor(socket.userID, monitorID);
|
await pauseMonitor(socket.userID, monitorID);
|
||||||
await server.sendUpdateMonitorIntoList(socket, monitorID);
|
|
||||||
|
const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
|
||||||
|
await server.sendUpdateMonitorsIntoList(socket, [ monitorID, ...childrenIDs ]);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -1764,16 +1768,19 @@ async function startMonitor(userID, monitorID) {
|
||||||
userID,
|
userID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let monitor = await R.findOne("monitor", " id = ? ", [
|
const childrenIDs = await Monitor.getAllChildrenIDs(monitorID, true);
|
||||||
monitorID,
|
const monitorIDs = [ monitorID, ...childrenIDs ];
|
||||||
]);
|
|
||||||
|
|
||||||
if (monitor.id in server.monitorList) {
|
let monitors = await R.find("monitor", ` id IN (${monitorIDs.map((_) => "?").join(",")})`, monitorIDs);
|
||||||
await server.monitorList[monitor.id].stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
server.monitorList[monitor.id] = monitor;
|
await Promise.all(monitors.map(async (monitor) => {
|
||||||
await monitor.start(io);
|
if (monitor.id in server.monitorList) {
|
||||||
|
await server.monitorList[monitor.id].stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
server.monitorList[monitor.id] = monitor;
|
||||||
|
await monitor.start(io);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1802,10 +1809,16 @@ async function pauseMonitor(userID, monitorID) {
|
||||||
userID,
|
userID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (monitorID in server.monitorList) {
|
const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
|
||||||
await server.monitorList[monitorID].stop();
|
const monitorIDs = [ monitorID, ...childrenIDs ];
|
||||||
server.monitorList[monitorID].active = 0;
|
|
||||||
}
|
await Promise.all(monitorIDs.map(async (currentMonitorID) => {
|
||||||
|
if (currentMonitorID in server.monitorList) {
|
||||||
|
await server.monitorList[currentMonitorID].stop();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.monitorList[monitorID].active = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1813,7 +1826,7 @@ async function pauseMonitor(userID, monitorID) {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function startMonitors() {
|
async function startMonitors() {
|
||||||
let list = await R.find("monitor", " active = 1 ");
|
let list = await Monitor.getAllActiveMonitors();
|
||||||
|
|
||||||
for (let monitor of list) {
|
for (let monitor of list) {
|
||||||
server.monitorList[monitor.id] = monitor;
|
server.monitorList[monitor.id] = monitor;
|
||||||
|
|
|
@ -212,12 +212,16 @@ class UptimeKumaServer {
|
||||||
/**
|
/**
|
||||||
* Update Monitor into list
|
* Update Monitor into list
|
||||||
* @param {Socket} socket Socket to send list on
|
* @param {Socket} socket Socket to send list on
|
||||||
* @param {number} monitorID update or deleted monitor id
|
* @param {number | number[]} monitorIDs update or deleted monitor ids
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async sendUpdateMonitorIntoList(socket, monitorID) {
|
async sendUpdateMonitorsIntoList(socket, monitorIDs) {
|
||||||
let list = await this.getMonitorJSONList(socket.userID, monitorID);
|
if (!Array.isArray(monitorIDs)) {
|
||||||
this.io.to(socket.userID).emit("updateMonitorIntoList", list);
|
monitorIDs = [ monitorIDs ];
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = await this.getMonitorJSONList(socket.userID, monitorIDs);
|
||||||
|
this.io.to(socket.userID).emit("updateMonitorsIntoList", list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,19 +237,19 @@ class UptimeKumaServer {
|
||||||
/**
|
/**
|
||||||
* Get a list of monitors for the given user.
|
* Get a list of monitors for the given user.
|
||||||
* @param {string} userID - The ID of the user to get monitors for.
|
* @param {string} userID - The ID of the user to get monitors for.
|
||||||
* @param {number} monitorID - The ID of monitor for.
|
* @param {number[]} monitorIDs - The IDs of monitors for.
|
||||||
* @returns {Promise<object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
* @returns {Promise<object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
||||||
*
|
*
|
||||||
* Generated by Trelent
|
* Generated by Trelent
|
||||||
*/
|
*/
|
||||||
async getMonitorJSONList(userID, monitorID = null) {
|
async getMonitorJSONList(userID, monitorIDs = null) {
|
||||||
|
|
||||||
let query = " user_id = ? ";
|
let query = " user_id = ? ";
|
||||||
let queryParams = [ userID ];
|
let queryParams = [ userID ];
|
||||||
|
|
||||||
if (monitorID) {
|
if (monitorIDs) {
|
||||||
query += "AND id = ? ";
|
query += `AND id IN (${monitorIDs.map((_) => "?").join(",")}) `;
|
||||||
queryParams.push(monitorID);
|
queryParams.push(...monitorIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
let monitorList = await R.find("monitor", query + "ORDER BY weight DESC, name", queryParams);
|
let monitorList = await R.find("monitor", query + "ORDER BY weight DESC, name", queryParams);
|
||||||
|
|
|
@ -145,7 +145,7 @@ export default {
|
||||||
this.monitorList = data;
|
this.monitorList = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("updateMonitorIntoList", (data) => {
|
socket.on("updateMonitorsIntoList", (data) => {
|
||||||
this.assignMonitorUrlParser(data);
|
this.assignMonitorUrlParser(data);
|
||||||
Object.entries(data).forEach(([ monitorID, updatedMonitor ]) => {
|
Object.entries(data).forEach(([ monitorID, updatedMonitor ]) => {
|
||||||
this.monitorList[monitorID] = updatedMonitor;
|
this.monitorList[monitorID] = updatedMonitor;
|
||||||
|
|
Loading…
Add table
Reference in a new issue