From 703e3d0faef1459dfd77233f84b6cff5e0bbba46 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Sun, 13 Oct 2024 00:25:45 +0200
Subject: [PATCH 01/15] fix: pause child monitors if parent is paused

---
 server/server.js | 67 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/server/server.js b/server/server.js
index db58ae829..0d5285d62 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1758,6 +1758,41 @@ async function startMonitor(userID, monitorID) {
 
     server.monitorList[monitor.id] = monitor;
     await monitor.start(io);
+
+    const children = await Monitor.getChildren(monitorID);
+    for (let child of children) {
+        await startMonitorAsChild(userID, child.id);
+    }
+}
+
+/**
+ * Start the specified monitor as child
+ * @param {number} userID ID of user who owns monitor
+ * @param {number} monitorID ID of monitor to start
+ * @returns {Promise<void>}
+ */
+async function startMonitorAsChild(userID, monitorID) {
+
+    let monitor = await R.findOne("monitor", " id = ? ", [
+        monitorID,
+    ]);
+    if (monitor.active !== 1) {
+        return;
+    }
+
+    log.info("manage", `Resume Monitor: ${monitorID} User ID: ${userID}`);
+
+    if (monitor.id in server.monitorList) {
+        await server.monitorList[monitor.id].stop();
+    }
+
+    server.monitorList[monitor.id] = monitor;
+    await monitor.start(io);
+
+    const children = await Monitor.getChildren(monitorID);
+    for (let child of children) {
+        await startMonitorAsChild(userID, child.id);
+    }
 }
 
 /**
@@ -1790,6 +1825,37 @@ async function pauseMonitor(userID, monitorID) {
         await server.monitorList[monitorID].stop();
         server.monitorList[monitorID].active = 0;
     }
+
+    const children = await Monitor.getChildren(monitorID);
+    for (let child of children) {
+        await pauseMonitorAsChild(userID, child.id);
+    }
+}
+
+/**
+ * Pause a given monitor as child
+ * @param {number} userID ID of user who owns monitor
+ * @param {number} monitorID ID of monitor to start
+ * @returns {Promise<void>}
+ */
+async function pauseMonitorAsChild(userID, monitorID) {
+    let monitor = await R.findOne("monitor", " id = ? ", [
+        monitorID,
+    ]);
+    if (monitor.active !== 1) {
+        return;
+    }
+
+    log.info("manage", `Pause Monitor: ${monitorID} User ID: ${userID}`);
+
+    if (monitorID in server.monitorList) {
+        await server.monitorList[monitorID].stop();
+    }
+
+    const children = await Monitor.getChildren(monitorID);
+    for (let child of children) {
+        await pauseMonitorAsChild(userID, child.id);
+    }
 }
 
 /**
@@ -1798,6 +1864,7 @@ async function pauseMonitor(userID, monitorID) {
  */
 async function startMonitors() {
     let list = await R.find("monitor", " active = 1 ");
+    list = list.filter((monitor) => Monitor.isActive(monitor.id, monitor.active));
 
     for (let monitor of list) {
         server.monitorList[monitor.id] = monitor;

From b4fabbb00f68a7bedc2876b9c8e867ad83ed29ed Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Sun, 13 Oct 2024 01:14:24 +0200
Subject: [PATCH 02/15] fix: update children in ui on pause

---
 server/server.js | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/server/server.js b/server/server.js
index 0d5285d62..2d2194f19 100644
--- a/server/server.js
+++ b/server/server.js
@@ -987,6 +987,11 @@ let needSetup = false;
                 await startMonitor(socket.userID, monitorID);
                 await server.sendUpdateMonitorIntoList(socket, monitorID);
 
+				const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+				for (let childID of childrenIDs) {
+					await server.sendUpdateMonitorIntoList(socket, childID);
+				}
+
                 callback({
                     ok: true,
                     msg: "successResumed",
@@ -1007,6 +1012,11 @@ let needSetup = false;
                 await pauseMonitor(socket.userID, monitorID);
                 await server.sendUpdateMonitorIntoList(socket, monitorID);
 
+				const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+				for (let childID of childrenIDs) {
+					await server.sendUpdateMonitorIntoList(socket, childID);
+				}
+
                 callback({
                     ok: true,
                     msg: "successPaused",

From d1677300a433500c7e389297dbd3c6cdd3db9617 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Sun, 13 Oct 2024 01:16:16 +0200
Subject: [PATCH 03/15] style: fix formatting

---
 server/server.js | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/server/server.js b/server/server.js
index 2d2194f19..9d6c801f9 100644
--- a/server/server.js
+++ b/server/server.js
@@ -987,10 +987,10 @@ let needSetup = false;
                 await startMonitor(socket.userID, monitorID);
                 await server.sendUpdateMonitorIntoList(socket, monitorID);
 
-				const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
-				for (let childID of childrenIDs) {
-					await server.sendUpdateMonitorIntoList(socket, childID);
-				}
+                const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+                for (let childID of childrenIDs) {
+                    await server.sendUpdateMonitorIntoList(socket, childID);
+                }
 
                 callback({
                     ok: true,
@@ -1012,10 +1012,10 @@ let needSetup = false;
                 await pauseMonitor(socket.userID, monitorID);
                 await server.sendUpdateMonitorIntoList(socket, monitorID);
 
-				const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
-				for (let childID of childrenIDs) {
-					await server.sendUpdateMonitorIntoList(socket, childID);
-				}
+                const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+                for (let childID of childrenIDs) {
+                    await server.sendUpdateMonitorIntoList(socket, childID);
+                }
 
                 callback({
                     ok: true,

From 9eda25d0b62ff19efd927b8b73fca6ea4957d04e Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Sun, 13 Oct 2024 23:29:11 +0200
Subject: [PATCH 04/15] revert: fix: pause child monitors if parent is paused

---
 server/server.js | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/server/server.js b/server/server.js
index 9d6c801f9..0d5285d62 100644
--- a/server/server.js
+++ b/server/server.js
@@ -987,11 +987,6 @@ let needSetup = false;
                 await startMonitor(socket.userID, monitorID);
                 await server.sendUpdateMonitorIntoList(socket, monitorID);
 
-                const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
-                for (let childID of childrenIDs) {
-                    await server.sendUpdateMonitorIntoList(socket, childID);
-                }
-
                 callback({
                     ok: true,
                     msg: "successResumed",
@@ -1012,11 +1007,6 @@ let needSetup = false;
                 await pauseMonitor(socket.userID, monitorID);
                 await server.sendUpdateMonitorIntoList(socket, monitorID);
 
-                const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
-                for (let childID of childrenIDs) {
-                    await server.sendUpdateMonitorIntoList(socket, childID);
-                }
-
                 callback({
                     ok: true,
                     msg: "successPaused",

From b02b21299beaade5aa922198b3122bf3b7cd57be Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Mon, 14 Oct 2024 00:01:55 +0200
Subject: [PATCH 05/15] feat: set group to pending if all childs are paused.

---
 server/model/monitor.js | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 5b7e5871a..aa3339582 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -380,7 +380,11 @@ class Monitor extends BeanModel {
                 } else if (this.type === "group") {
                     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.msg = "All children up and running";
                         for (const child of children) {

From d336d09d787372b1ef428512999e977e5d12a713 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Mon, 14 Oct 2024 23:04:07 +0200
Subject: [PATCH 06/15] fix: display children as paused when pausing parent

---
 server/server.js             | 12 ++++++++----
 server/uptime-kuma-server.js | 22 +++++++++++++---------
 src/mixins/socket.js         |  2 +-
 3 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/server/server.js b/server/server.js
index 0d5285d62..178b58b71 100644
--- a/server/server.js
+++ b/server/server.js
@@ -727,7 +727,7 @@ let needSetup = false;
 
                 await updateMonitorNotification(bean.id, notificationIDList);
 
-                await server.sendUpdateMonitorIntoList(socket, bean.id);
+                await server.sendUpdateMonitorsIntoList(socket, bean.id);
 
                 if (monitor.active !== false) {
                     await startMonitor(socket.userID, bean.id);
@@ -884,7 +884,7 @@ let needSetup = false;
                     await restartMonitor(socket.userID, bean.id);
                 }
 
-                await server.sendUpdateMonitorIntoList(socket, bean.id);
+                await server.sendUpdateMonitorsIntoList(socket, bean.id);
 
                 callback({
                     ok: true,
@@ -985,7 +985,9 @@ let needSetup = false;
             try {
                 checkLogin(socket);
                 await startMonitor(socket.userID, monitorID);
-                await server.sendUpdateMonitorIntoList(socket, monitorID);
+
+                const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+                await server.sendUpdateMonitorsIntoList(socket, [ monitorID, ...childrenIDs ]);
 
                 callback({
                     ok: true,
@@ -1005,7 +1007,9 @@ let needSetup = false;
             try {
                 checkLogin(socket);
                 await pauseMonitor(socket.userID, monitorID);
-                await server.sendUpdateMonitorIntoList(socket, monitorID);
+
+                const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+                await server.sendUpdateMonitorsIntoList(socket, [ monitorID, ...childrenIDs ]);
 
                 callback({
                     ok: true,
diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 76bf42565..5c1b281e1 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -208,12 +208,16 @@ class UptimeKumaServer {
     /**
      * Update Monitor into list
      * @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>}
      */
-    async sendUpdateMonitorIntoList(socket, monitorID) {
-        let list = await this.getMonitorJSONList(socket.userID, monitorID);
-        this.io.to(socket.userID).emit("updateMonitorIntoList", list);
+    async sendUpdateMonitorsIntoList(socket, monitorIDs) {
+        if (!Array.isArray(monitorIDs)) {
+            monitorIDs = [ monitorIDs ];
+        }
+
+        let list = await this.getMonitorJSONList(socket.userID, monitorIDs);
+        this.io.to(socket.userID).emit("updateMonitorsIntoList", list);
     }
 
     /**
@@ -229,19 +233,19 @@ class UptimeKumaServer {
     /**
      * Get a list of monitors for the given user.
      * @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.
      *
      * Generated by Trelent
      */
-    async getMonitorJSONList(userID, monitorID = null) {
+    async getMonitorJSONList(userID, monitorIDs = null) {
 
         let query = " user_id = ? ";
         let queryParams = [ userID ];
 
-        if (monitorID) {
-            query += "AND id = ? ";
-            queryParams.push(monitorID);
+        if (monitorIDs) {
+            query += `AND id IN (${monitorIDs.map((_) => "?").join(",")}) `;
+            queryParams.push(...monitorIDs);
         }
 
         let monitorList = await R.find("monitor", query + "ORDER BY weight DESC, name", queryParams);
diff --git a/src/mixins/socket.js b/src/mixins/socket.js
index 3272e042c..31b7d630d 100644
--- a/src/mixins/socket.js
+++ b/src/mixins/socket.js
@@ -145,7 +145,7 @@ export default {
                 this.monitorList = data;
             });
 
-            socket.on("updateMonitorIntoList", (data) => {
+            socket.on("updateMonitorsIntoList", (data) => {
                 this.assignMonitorUrlParser(data);
                 Object.entries(data).forEach(([ monitorID, updatedMonitor ]) => {
                     this.monitorList[monitorID] = updatedMonitor;

From f622898ff16f31a3aa58c1c532d551dc90ce2403 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Wed, 16 Oct 2024 22:54:15 +0200
Subject: [PATCH 07/15] perf: less sql statements and concurrent start/stop

---
 server/model/monitor.js | 29 ++++++-------
 server/server.js        | 94 +++++++++--------------------------------
 2 files changed, 33 insertions(+), 90 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index aa3339582..741284f1e 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1661,25 +1661,24 @@ class Monitor extends BeanModel {
     }
 
     /**
-     * Gets recursive all child ids
+     * Gets recursive all children ids
      * @param {number} monitorID ID of the monitor to get
-     * @returns {Promise<Array>} IDs of all children
+     * @returns {Promise<number[]>} IDs of all children
      */
     static async getAllChildrenIDs(monitorID) {
-        const childs = await Monitor.getChildren(monitorID);
+        const children = await R.getAll(`
+			WITH RECURSIVE MonitorHierarchy(id) AS (
+				SELECT id FROM monitor WHERE id = ?
+				UNION ALL
+				SELECT m.id FROM monitor m INNER JOIN MonitorHierarchy mh ON m.parent = mh.id
+			)
+			SELECT id FROM MonitorHierarchy WHERE id != ?;
+		`, [
+            monitorID,
+            monitorID
+        ]);
 
-        if (childs === null) {
-            return [];
-        }
-
-        let childrenIDs = [];
-
-        for (const child of childs) {
-            childrenIDs.push(child.id);
-            childrenIDs = childrenIDs.concat(await Monitor.getAllChildrenIDs(child.id));
-        }
-
-        return childrenIDs;
+        return children.map((child) => child.id);
     }
 
     /**
diff --git a/server/server.js b/server/server.js
index 178b58b71..dc28a5100 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1752,51 +1752,20 @@ async function startMonitor(userID, monitorID) {
         userID,
     ]);
 
-    let monitor = await R.findOne("monitor", " id = ? ", [
-        monitorID,
-    ]);
+    const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+    console.log(childrenIDs);
+    const monitorIDs = [ monitorID, ...childrenIDs ];
 
-    if (monitor.id in server.monitorList) {
-        await server.monitorList[monitor.id].stop();
-    }
+    let monitors = await R.find("monitor", ` id IN (${monitorIDs.map((_) => "?").join(",")})`, monitorIDs);
 
-    server.monitorList[monitor.id] = monitor;
-    await monitor.start(io);
+    await Promise.all(monitors.map(async (monitor) => {
+        if (monitor.id in server.monitorList) {
+            await server.monitorList[monitor.id].stop();
+        }
 
-    const children = await Monitor.getChildren(monitorID);
-    for (let child of children) {
-        await startMonitorAsChild(userID, child.id);
-    }
-}
-
-/**
- * Start the specified monitor as child
- * @param {number} userID ID of user who owns monitor
- * @param {number} monitorID ID of monitor to start
- * @returns {Promise<void>}
- */
-async function startMonitorAsChild(userID, monitorID) {
-
-    let monitor = await R.findOne("monitor", " id = ? ", [
-        monitorID,
-    ]);
-    if (monitor.active !== 1) {
-        return;
-    }
-
-    log.info("manage", `Resume Monitor: ${monitorID} User ID: ${userID}`);
-
-    if (monitor.id in server.monitorList) {
-        await server.monitorList[monitor.id].stop();
-    }
-
-    server.monitorList[monitor.id] = monitor;
-    await monitor.start(io);
-
-    const children = await Monitor.getChildren(monitorID);
-    for (let child of children) {
-        await startMonitorAsChild(userID, child.id);
-    }
+        server.monitorList[monitor.id] = monitor;
+        await monitor.start(io);
+    }));
 }
 
 /**
@@ -1825,41 +1794,16 @@ async function pauseMonitor(userID, monitorID) {
         userID,
     ]);
 
-    if (monitorID in server.monitorList) {
-        await server.monitorList[monitorID].stop();
-        server.monitorList[monitorID].active = 0;
-    }
+    const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+    const monitorIDs = [ monitorID, ...childrenIDs ];
 
-    const children = await Monitor.getChildren(monitorID);
-    for (let child of children) {
-        await pauseMonitorAsChild(userID, child.id);
-    }
-}
+    await Promise.all(monitorIDs.map(async (currentMonitorID) => {
+        if (currentMonitorID in server.monitorList) {
+            await server.monitorList[currentMonitorID].stop();
+        }
+    }));
 
-/**
- * Pause a given monitor as child
- * @param {number} userID ID of user who owns monitor
- * @param {number} monitorID ID of monitor to start
- * @returns {Promise<void>}
- */
-async function pauseMonitorAsChild(userID, monitorID) {
-    let monitor = await R.findOne("monitor", " id = ? ", [
-        monitorID,
-    ]);
-    if (monitor.active !== 1) {
-        return;
-    }
-
-    log.info("manage", `Pause Monitor: ${monitorID} User ID: ${userID}`);
-
-    if (monitorID in server.monitorList) {
-        await server.monitorList[monitorID].stop();
-    }
-
-    const children = await Monitor.getChildren(monitorID);
-    for (let child of children) {
-        await pauseMonitorAsChild(userID, child.id);
-    }
+    server.monitorList[monitorID].active = 0;
 }
 
 /**

From 22dcba17c8ca313e2388a95fcc3cf74bdd19a0c4 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Wed, 16 Oct 2024 22:56:14 +0200
Subject: [PATCH 08/15] fix: remove test logging

---
 server/server.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/server/server.js b/server/server.js
index dc28a5100..782c47f39 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1753,7 +1753,6 @@ async function startMonitor(userID, monitorID) {
     ]);
 
     const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
-    console.log(childrenIDs);
     const monitorIDs = [ monitorID, ...childrenIDs ];
 
     let monitors = await R.find("monitor", ` id IN (${monitorIDs.map((_) => "?").join(",")})`, monitorIDs);

From 71c7ee69c70b4aec4e72fde84b102fda321f0ee4 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Thu, 17 Oct 2024 00:04:45 +0200
Subject: [PATCH 09/15] feat: db statement to get all active monitors

---
 server/model/monitor.js | 18 ++++++++++++++++++
 server/server.js        |  3 +--
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 741284f1e..7f9185be2 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1608,6 +1608,24 @@ class Monitor extends BeanModel {
         };
     }
 
+    /**
+     * Gets all active monitors
+     * @returns {Promise<Bean[]>} Active Monitors
+     */
+    static async getAllActiveMonitors() {
+        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
      * @param {number} monitorID ID of monitor to get
diff --git a/server/server.js b/server/server.js
index 782c47f39..8dba73a1e 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1810,8 +1810,7 @@ async function pauseMonitor(userID, monitorID) {
  * @returns {Promise<void>}
  */
 async function startMonitors() {
-    let list = await R.find("monitor", " active = 1 ");
-    list = list.filter((monitor) => Monitor.isActive(monitor.id, monitor.active));
+    let list = await Monitor.getAllActiveMonitors();
 
     for (let monitor of list) {
         server.monitorList[monitor.id] = monitor;

From c079971a7b13eedec18b0885930700c4469827e0 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Thu, 17 Oct 2024 00:05:29 +0200
Subject: [PATCH 10/15] fix: cast getParent and getChildren to Beans

---
 server/model/monitor.js | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 7f9185be2..e5580a2e8 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1629,10 +1629,10 @@ class Monitor extends BeanModel {
     /**
      * Gets Parent of the monitor
      * @param {number} monitorID ID of monitor to get
-     * @returns {Promise<LooseObject<any>>} Parent
+     * @returns {Promise<Bean | null>} Parent
      */
     static async getParent(monitorID) {
-        return await R.getRow(`
+        const result = await R.getRow(`
             SELECT parent.* FROM monitor parent
     		LEFT JOIN monitor child
     			ON child.parent = parent.id
@@ -1640,20 +1640,25 @@ class Monitor extends BeanModel {
         `, [
             monitorID,
         ]);
+
+        if (!result) {
+            return null;
+        }
+        return R.convertToBean("monitor", result);
     }
 
     /**
      * Gets all Children of the monitor
      * @param {number} monitorID ID of monitor to get
-     * @returns {Promise<LooseObject<any>>} Children
+     * @returns {Promise<Bean[]>} Children
      */
     static async getChildren(monitorID) {
-        return await R.getAll(`
+        return R.convertToBeans("monitor", await R.getAll(`
             SELECT * FROM monitor
             WHERE parent = ?
         `, [
             monitorID,
-        ]);
+        ]));
     }
 
     /**

From 5dc66e1495c1becabe364eb96fee72c7ef645fee Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Thu, 17 Oct 2024 00:05:55 +0200
Subject: [PATCH 11/15] refactor: make getAllChildrenIDs more compact

---
 server/model/monitor.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index e5580a2e8..b51056643 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1689,7 +1689,7 @@ class Monitor extends BeanModel {
      * @returns {Promise<number[]>} IDs of all children
      */
     static async getAllChildrenIDs(monitorID) {
-        const children = await R.getAll(`
+        return await R.getCol(`
 			WITH RECURSIVE MonitorHierarchy(id) AS (
 				SELECT id FROM monitor WHERE id = ?
 				UNION ALL
@@ -1700,8 +1700,6 @@ class Monitor extends BeanModel {
             monitorID,
             monitorID
         ]);
-
-        return children.map((child) => child.id);
     }
 
     /**

From 16c04f6ac22cd3bd940c57bb7cb8d01984e257a2 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Thu, 17 Oct 2024 00:06:40 +0200
Subject: [PATCH 12/15] perf: less recursion for isParentActive using db query

---
 server/model/monitor.js | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index b51056643..593f7436b 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1719,14 +1719,31 @@ class Monitor extends BeanModel {
      * @returns {Promise<boolean>} Is the parent monitor active?
      */
     static async isParentActive(monitorID) {
-        const parent = await Monitor.getParent(monitorID);
+        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 true;
-        }
-
-        const parentActive = await Monitor.isParentActive(parent.id);
-        return parent.active && parentActive;
+        return result.all_active === 1;
     }
 
     /**

From c9e5dff162f1faab4daa09287b18658992fc76ab Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Thu, 17 Oct 2024 00:07:07 +0200
Subject: [PATCH 13/15] perf: less recursion for isUnderMaintenance using db
 query

---
 server/model/monitor.js | 32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 593f7436b..f38259fb2 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1469,10 +1469,12 @@ class Monitor extends BeanModel {
      * @returns {Promise<boolean>} Is the monitor under maintenance
      */
     static async isUnderMaintenance(monitorID) {
+        const ancestorIDs = await Monitor.getAllAncestorIDs(monitorID);
+        const allIDs = [ monitorID, ...ancestorIDs ];
         const maintenanceIDList = await R.getCol(`
             SELECT maintenance_id FROM monitor_maintenance
-            WHERE monitor_id = ?
-        `, [ monitorID ]);
+            WHERE monitor_id IN (${allIDs.map((_) => "?").join(",")})
+        `, allIDs);
 
         for (const maintenanceID of maintenanceIDList) {
             const maintenance = await UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
@@ -1481,11 +1483,6 @@ class Monitor extends BeanModel {
             }
         }
 
-        const parent = await Monitor.getParent(monitorID);
-        if (parent != null) {
-            return await Monitor.isUnderMaintenance(parent.id);
-        }
-
         return false;
     }
 
@@ -1683,6 +1680,27 @@ class Monitor extends BeanModel {
         return path;
     }
 
+    /**
+     * Gets recursive all ancestor ids
+     * @param {number} monitorID ID of the monitor to get
+     * @returns {Promise<number[]>} IDs of all ancestors
+     */
+    static async getAllAncestorIDs(monitorID) {
+        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,
+        ]);
+    }
+
     /**
      * Gets recursive all children ids
      * @param {number} monitorID ID of the monitor to get

From ec2cebc5dfa2bda5c6b207c7a5d0f51af1dbecad Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Thu, 17 Oct 2024 17:16:08 +0200
Subject: [PATCH 14/15] perf: option to only get ids of active children

---
 server/model/monitor.js | 22 +++++++++++++++++++++-
 server/server.js        |  2 +-
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 893379cbd..0149e11fb 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1701,9 +1701,29 @@ class Monitor extends BeanModel {
     /**
      * 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) {
+    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
         return await R.getCol(`
 			WITH RECURSIVE MonitorHierarchy(id) AS (
 				SELECT id FROM monitor WHERE id = ?
diff --git a/server/server.js b/server/server.js
index 8dba73a1e..ef0fb49af 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1752,7 +1752,7 @@ async function startMonitor(userID, monitorID) {
         userID,
     ]);
 
-    const childrenIDs = await Monitor.getAllChildrenIDs(monitorID);
+    const childrenIDs = await Monitor.getAllChildrenIDs(monitorID, true);
     const monitorIDs = [ monitorID, ...childrenIDs ];
 
     let monitors = await R.find("monitor", ` id IN (${monitorIDs.map((_) => "?").join(",")})`, monitorIDs);

From ac8d1d6346730cd4461b40e238db850c6266b678 Mon Sep 17 00:00:00 2001
From: Peace <Debitor.TooLP@gmx.de>
Date: Thu, 17 Oct 2024 17:16:29 +0200
Subject: [PATCH 15/15] docs: add comments for queries

---
 server/model/monitor.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 0149e11fb..c653590fe 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -1607,6 +1607,7 @@ class Monitor extends BeanModel {
      * @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
@@ -1683,6 +1684,7 @@ class Monitor extends BeanModel {
      * @returns {Promise<number[]>} IDs of all ancestors
      */
     static async getAllAncestorIDs(monitorID) {
+        // Gets all ancestor monitor ids recursive
         return await R.getCol(`
 			WITH RECURSIVE Ancestors AS (
 				SELECT parent FROM monitor
@@ -1754,6 +1756,7 @@ class Monitor extends BeanModel {
      * @returns {Promise<boolean>} Is the parent monitor active?
      */
     static async isParentActive(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