diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js new file mode 100644 index 00000000..aaea4772 --- /dev/null +++ b/server/model/heartbeat.js @@ -0,0 +1,31 @@ +const dayjs = require("dayjs"); +const utc = require('dayjs/plugin/utc') +var timezone = require('dayjs/plugin/timezone') +dayjs.extend(utc) +dayjs.extend(timezone) +const axios = require("axios"); +const {R} = require("redbean-node"); +const {BeanModel} = require("redbean-node/dist/bean-model"); + + +/** + * status: + * 0 = DOWN + * 1 = UP + */ +class Heartbeat extends BeanModel { + + toJSON() { + return { + monitorID: this.monitor_id, + status: this.status, + time: this.time, + msg: this.msg, + ping: this.ping, + important: this.important, + }; + } + +} + +module.exports = Heartbeat; diff --git a/server/model/monitor.js b/server/model/monitor.js index 9c491146..fbc9dc0e 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -28,9 +28,17 @@ class Monitor extends BeanModel { } start(io) { + let previousBeat = null; + const beat = async () => { console.log(`Monitor ${this.id}: Heartbeat`) + if (! previousBeat) { + previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ + this.id + ]) + } + let bean = R.dispense("heartbeat") bean.monitor_id = this.id; bean.time = R.isoDateTime(dayjs.utc()); @@ -49,15 +57,18 @@ class Monitor extends BeanModel { bean.msg = error.message; } - io.to(this.user_id).emit("heartbeat", { - monitorID: this.id, - status: bean.status, - time: bean.time, - msg: bean.msg, - ping: bean.ping, - }); + // Mark as important if status changed + if (! previousBeat || previousBeat.status !== bean.status) { + bean.important = true; + } else { + bean.important = false; + } + + io.to(this.user_id).emit("heartbeat", bean.toJSON()); await R.store(bean) + + previousBeat = bean; } beat(); diff --git a/server/server.js b/server/server.js index e9f62c49..3a082c49 100644 --- a/server/server.js +++ b/server/server.js @@ -9,7 +9,6 @@ const {R} = require("redbean-node"); const passwordHash = require('password-hash'); const jwt = require('jsonwebtoken'); const Monitor = require("./model/monitor"); -const {sleep} = require("./util"); let stop = false; let interval = 6000; @@ -40,6 +39,10 @@ let monitorList = {}; // Public API + /* + firstConnect - true = send monitor list + heartbeat list history + false = do not send + */ socket.on("loginByToken", async (token, callback) => { try { @@ -320,13 +323,20 @@ async function checkOwner(userID, monitorID) { } async function sendMonitorList(socket) { - io.to(socket.userID).emit("monitorList", await getMonitorJSONList(socket.userID)) + let list = await getMonitorJSONList(socket.userID); + io.to(socket.userID).emit("monitorList", list) + return list; } async function afterLogin(socket, user) { socket.userID = user.id; socket.join(user.id) - socket.emit("monitorList", await getMonitorJSONList(user.id)) + + let monitorList = await sendMonitorList(socket) + + for (let monitorID in monitorList) { + await sendHeartbeatList(socket, monitorID); + } } async function getMonitorJSONList(userID) { @@ -338,8 +348,11 @@ async function getMonitorJSONList(userID) { for (let monitor of monitorList) { result[monitor.id] = monitor.toJSON(); + } + + return result; } @@ -421,3 +434,24 @@ async function startMonitors() { } } +/** + * Send Heartbeat History list to socket + */ +async function sendHeartbeatList(socket, monitorID) { + let list = await R.find("heartbeat", ` + monitor_id = ? + ORDER BY time DESC + LIMIT 100 + `, [ + monitorID + ]) + + let result = []; + + for (let bean of list) { + result.unshift(bean.toJSON()) + } + + socket.emit("heartbeatList", monitorID, result) +} + diff --git a/server/util.js b/server/util.js index fe3ed4a0..94026797 100644 --- a/server/util.js +++ b/server/util.js @@ -1,3 +1,12 @@ -exports.sleep = (ms) => { +/* + * Common functions - can be used in frontend or backend + */ + +export function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } + +export function ucfirst(str) { + const firstLetter = str.substr(0, 1); + return firstLetter.toUpperCase() + str.substr(1); +} diff --git a/src/assets/app.scss b/src/assets/app.scss index 980af501..cb72fb9d 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -31,4 +31,8 @@ } } +.modal-content { + border-radius: 1rem; + backdrop-filter: blur(3px); +} diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index ede199ee..48ffd292 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -1,7 +1,13 @@