Compare commits

...

19 commits

Author SHA1 Message Date
Peace
3a7a4de3cb
Merge ac8d1d6346 into 277d6fe0ce 2024-10-27 12:30:58 +00:00
Louis Lam
277d6fe0ce
Fix #5087 and fix migration state (#5260)
Some checks failed
Auto Test / auto-test (18, ARM64) (push) Blocked by required conditions
Auto Test / auto-test (18, macos-latest) (push) Blocked by required conditions
Auto Test / auto-test (18, ubuntu-latest) (push) Blocked by required conditions
Auto Test / auto-test (18, windows-latest) (push) Blocked by required conditions
Auto Test / auto-test (20, ARM64) (push) Blocked by required conditions
Auto Test / auto-test (20, macos-latest) (push) Blocked by required conditions
Auto Test / auto-test (20, ubuntu-latest) (push) Blocked by required conditions
Auto Test / auto-test (20, windows-latest) (push) Blocked by required conditions
Auto Test / armv7-simple-test (18, ARMv7) (push) Waiting to run
Auto Test / armv7-simple-test (20, ARMv7) (push) Waiting to run
Auto Test / check-linters (push) Waiting to run
Auto Test / e2e-test (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
json-yaml-validate / json-yaml-validate (push) Has been cancelled
json-yaml-validate / check-lang-json (push) Has been cancelled
2024-10-27 20:30:44 +08:00
Peace
ac8d1d6346
docs: add comments for queries 2024-10-17 17:16:29 +02:00
Peace
ec2cebc5df
perf: option to only get ids of active children 2024-10-17 17:16:08 +02:00
Peace
0a479ecb50
Merge branch 'master' into fix/pause-child-monitors 2024-10-17 00:22:51 +02:00
Peace
c9e5dff162
perf: less recursion for isUnderMaintenance using db query 2024-10-17 00:07:07 +02:00
Peace
16c04f6ac2
perf: less recursion for isParentActive using db query 2024-10-17 00:06:40 +02:00
Peace
5dc66e1495
refactor: make getAllChildrenIDs more compact 2024-10-17 00:05:55 +02:00
Peace
c079971a7b
fix: cast getParent and getChildren to Beans 2024-10-17 00:05:29 +02:00
Peace
71c7ee69c7
feat: db statement to get all active monitors 2024-10-17 00:04:45 +02:00
Peace
22dcba17c8
fix: remove test logging 2024-10-16 22:56:14 +02:00
Peace
f622898ff1
perf: less sql statements and concurrent start/stop 2024-10-16 22:54:15 +02:00
Peace
6179f31348
Merge branch 'master' into fix/pause-child-monitors 2024-10-14 23:08:48 +02:00
Peace
d336d09d78
fix: display children as paused when pausing parent 2024-10-14 23:04:07 +02:00
Peace
b02b21299b
feat: set group to pending if all childs are paused. 2024-10-14 00:01:55 +02:00
Peace
9eda25d0b6
revert: fix: pause child monitors if parent is paused 2024-10-13 23:29:11 +02:00
Peace
d1677300a4
style: fix formatting 2024-10-13 01:16:16 +02:00
Peace
b4fabbb00f
fix: update children in ui on pause 2024-10-13 01:14:24 +02:00
Peace
703e3d0fae
fix: pause child monitors if parent is paused 2024-10-13 00:25:45 +02:00
6 changed files with 176 additions and 64 deletions

View file

@ -0,0 +1,13 @@
// Update info_json column to LONGTEXT mainly for MariaDB
exports.up = function (knex) {
return knex.schema
.alterTable("monitor_tls_info", function (table) {
table.text("info_json", "longtext").alter();
});
};
exports.down = function (knex) {
return knex.schema.alterTable("monitor_tls_info", function (table) {
table.text("info_json", "text").alter();
});
};

View file

@ -775,8 +775,6 @@ class Database {
await migrationServer.start(port, hostname); await migrationServer.start(port, hostname);
} }
await Settings.set("migrateAggregateTableState", "migrating");
log.info("db", "Migrating Aggregate Table"); log.info("db", "Migrating Aggregate Table");
log.info("db", "Getting list of unique monitors"); log.info("db", "Getting list of unique monitors");
@ -799,6 +797,8 @@ class Database {
} }
} }
await Settings.set("migrateAggregateTableState", "migrating");
let progressPercent = 0; let progressPercent = 0;
let part = 100 / monitors.length; let part = 100 / monitors.length;
let i = 1; let i = 1;

View file

@ -383,7 +383,11 @@ class Monitor extends BeanModel {
} else if (this.type === "group") { } else if (this.type === "group") {
const children = await Monitor.getChildren(this.id); const children = await Monitor.getChildren(this.id);
if (children.length > 0) { if (children.length > 0 && children.filter(child => child.active).length === 0) {
// Set status pending if all children are paused
bean.status = PENDING;
bean.msg = "All Children are paused.";
} else if (children.length > 0) {
bean.status = UP; bean.status = UP;
bean.msg = "All children up and running"; bean.msg = "All children up and running";
for (const child of children) { for (const child of children) {
@ -1468,10 +1472,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);
@ -1480,11 +1486,6 @@ class Monitor extends BeanModel {
} }
} }
const parent = await Monitor.getParent(monitorID);
if (parent != null) {
return await Monitor.isUnderMaintenance(parent.id);
}
return false; return false;
} }
@ -1604,13 +1605,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
@ -1618,20 +1638,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,
]); ]));
} }
/** /**
@ -1657,25 +1682,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
]);
} }
/** /**
@ -1695,14 +1759,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;
} }
/** /**

View file

@ -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);
@ -889,7 +889,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,
@ -990,7 +990,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,
@ -1010,7 +1012,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,
@ -1755,16 +1759,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);
}));
} }
/** /**
@ -1793,10 +1800,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;
} }
/** /**
@ -1804,7 +1817,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;

View file

@ -209,12 +209,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);
} }
/** /**
@ -230,19 +234,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);

View file

@ -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;