From 7ee98d989cf4eee13730170506f809af19f03840 Mon Sep 17 00:00:00 2001
From: LouisLam <louislam@users.noreply.github.com>
Date: Sun, 19 Sep 2021 23:24:51 +0800
Subject: [PATCH] [status page] implement rest api for heartbeat

---
 server/model/group.js        | 22 +++++++++++-----------
 server/model/heartbeat.js    | 17 +++++++++++++----
 server/routers/api-router.js | 36 ++++++++++++++++++++++++++++--------
 src/mixins/public.js         |  2 --
 src/pages/StatusPage.vue     | 28 +++++++++++++++-------------
 5 files changed, 67 insertions(+), 38 deletions(-)

diff --git a/server/model/group.js b/server/model/group.js
index 5bde67217..8e61f938f 100644
--- a/server/model/group.js
+++ b/server/model/group.js
@@ -4,17 +4,7 @@ const { R } = require("redbean-node");
 class Group extends BeanModel {
 
     async toPublicJSON() {
-
-        let monitorBeanList = R.convertToBeans("monitor", await R.getAll(`
-            SELECT * FROM monitor, monitor_group
-            WHERE monitor.id = monitor_group.monitor_id
-            AND group_id = ?
-        `, [
-            this.id,
-        ]));
-
-        console.log(monitorBeanList);
-
+        let monitorBeanList = await this.getMonitorList();
         let monitorList = [];
 
         for (let bean of monitorBeanList) {
@@ -28,6 +18,16 @@ class Group extends BeanModel {
             monitorList,
         };
     }
+
+    async getMonitorList() {
+        return R.convertToBeans("monitor", await R.getAll(`
+            SELECT monitor.* FROM monitor, monitor_group
+            WHERE monitor.id = monitor_group.monitor_id
+            AND group_id = ?
+        `, [
+            this.id,
+        ]));
+    }
 }
 
 module.exports = Group;
diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js
index 546794140..e0a77c069 100644
--- a/server/model/heartbeat.js
+++ b/server/model/heartbeat.js
@@ -1,8 +1,8 @@
 const dayjs = require("dayjs");
-const utc = require("dayjs/plugin/utc")
-let timezone = require("dayjs/plugin/timezone")
-dayjs.extend(utc)
-dayjs.extend(timezone)
+const utc = require("dayjs/plugin/utc");
+let timezone = require("dayjs/plugin/timezone");
+dayjs.extend(utc);
+dayjs.extend(timezone);
 const { BeanModel } = require("redbean-node/dist/bean-model");
 
 /**
@@ -13,6 +13,15 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
  */
 class Heartbeat extends BeanModel {
 
+    toPublicJSON() {
+        return {
+            status: this.status,
+            time: this.time,
+            msg: "",        // Hide for public
+            ping: this.ping,
+        };
+    }
+
     toJSON() {
         return {
             monitorID: this.monitor_id,
diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index f8f36bbde..ab2043b15 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -38,9 +38,15 @@ router.get("/api/status-page/incident", async (_, response) => {
     try {
         await checkPublished();
 
+        let incident = await R.findOne("incident", " pin = 1 AND active = 1");
+
+        if (incident) {
+            incident = incident.toPublicJSON();
+        }
+
         response.json({
             ok: true,
-            incident: (await R.findOne("incident", " pin = 1 AND active = 1")).toPublicJSON(),
+            incident,
         });
 
     } catch (error) {
@@ -56,7 +62,7 @@ router.get("/api/status-page/monitor-list", async (_request, response) => {
     try {
         await checkPublished();
         const publicGroupList = [];
-        let list = await R.find("group", " public = 1 ORDER BY weight, name ");
+        let list = await R.find("group", " public = 1 ORDER BY weight ");
 
         for (let groupBean of list) {
             publicGroupList.push(await groupBean.toPublicJSON());
@@ -76,16 +82,30 @@ router.get("/api/status-page/heartbeat", async (_request, response) => {
     try {
         await checkPublished();
 
-        const monitorList = {};
-        let list = await R.find("", "  ", [
-        ]);
+        let heartbeatList = {};
 
-        for (let monitor of list) {
-            monitorList[monitor.id] = await monitor.toJSON();
+        let monitorIDList = await R.getCol(`
+            SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
+            WHERE monitor_group.group_id = \`group\`.id
+            AND public = 1
+        `);
+
+        for (let monitorID of monitorIDList) {
+            let list = await R.getAll(`
+                    SELECT * FROM heartbeat
+                    WHERE monitor_id = ?
+                    ORDER BY time DESC
+                    LIMIT 100
+            `, [
+                monitorID,
+            ]);
+
+            list = R.convertToBeans("heartbeat", list);
+            heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON());
         }
 
         response.json({
-            monitorList: monitorList,
+            heartbeatList,
         });
 
     } catch (error) {
diff --git a/src/mixins/public.js b/src/mixins/public.js
index c0fffcedb..2aa180cd9 100644
--- a/src/mixins/public.js
+++ b/src/mixins/public.js
@@ -17,8 +17,6 @@ export default {
         publicMonitorList() {
             let result = {};
 
-            console.log(this.publicGroupList);
-
             for (let group of this.publicGroupList) {
                 for (let monitor of group.monitorList) {
                     result[monitor.id] = monitor;
diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue
index 4c02edca0..191cd0b19 100644
--- a/src/pages/StatusPage.vue
+++ b/src/pages/StatusPage.vue
@@ -30,10 +30,10 @@
                     Edit Status Page
                 </button>
 
-                <router-link to="/dashboard" class="btn btn-info">
+                <a href="/dashboard" class="btn btn-info">
                     <font-awesome-icon icon="tachometer-alt" />
                     Go to Dashboard
-                </router-link>
+                </a>
             </div>
 
             <div v-else>
@@ -168,13 +168,10 @@
             </div>
 
             <div class="mt-3">
-                <VueMultiselect
-                    v-model="selectedMonitor"
-                    :options="allMonitorList"
-                    :custom-label="monitorSelectorLabel"
-                    :searchable="true"
-                    placeholder="Add a monitor"
-                ></VueMultiselect>
+                <label>Add a monitor:</label>
+                <select v-model="selectedMonitor" class="form-control">
+                    <option v-for="monitor in allMonitorList" :key="monitor.id" :value="monitor">{{ monitor.name }}</option>
+                </select>
             </div>
         </div>
 
@@ -382,16 +379,21 @@ export default {
         });
 
         // 5mins a loop
+        this.updateHeartbeatList();
         feedInterval = setInterval(() => {
+            this.updateHeartbeatList();
+        }, 10 * 1000);
+    },
+    methods: {
+
+        updateHeartbeatList() {
             // If editMode, it will use the data from websocket.
             if (! this.editMode) {
                 axios.get("/api/status-page/heartbeat").then((res) => {
-                    // TODO
+                    this.$root.heartbeatList = res.data.heartbeatList;
                 });
             }
-        }, 5 * 60 * 1000);
-    },
-    methods: {
+        },
 
         edit() {
             this.$root.initSocketIO(true);