diff --git a/server/model/monitor.js b/server/model/monitor.js index fbc9dc0e5..c1dfee891 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -66,6 +66,8 @@ class Monitor extends BeanModel { io.to(this.user_id).emit("heartbeat", bean.toJSON()); + Monitor.sendStats(io, this.id, this.user_id) + await R.store(bean) previousBeat = bean; @@ -78,6 +80,29 @@ class Monitor extends BeanModel { stop() { clearInterval(this.heartbeatInterval) } + + static async sendStats(io, monitorID, userID) { + Monitor.sendAvgPing(24, io, monitorID, userID); + //Monitor.sendUptime(24, io, this.id); + //Monitor.sendUptime(24 * 30, io, this.id); + } + + static async sendAvgPing(duration, io, monitorID, userID) { + let avgPing = parseInt(await R.getCell(` + SELECT AVG(ping) + FROM heartbeat + WHERE time > DATE('now', ? || ' hours') + AND monitor_id = ? `, [ + -duration, + monitorID + ])); + + io.to(userID).emit("avgPing", monitorID, avgPing); + } + + sendUptime(duration) { + + } } module.exports = Monitor; diff --git a/server/server.js b/server/server.js index 3a082c49f..87bad452b 100644 --- a/server/server.js +++ b/server/server.js @@ -39,10 +39,6 @@ let monitorList = {}; // Public API - /* - firstConnect - true = send monitor list + heartbeat list history - false = do not send - */ socket.on("loginByToken", async (token, callback) => { try { @@ -336,6 +332,8 @@ async function afterLogin(socket, user) { for (let monitorID in monitorList) { await sendHeartbeatList(socket, monitorID); + await sendImportantHeartbeatList(socket, monitorID); + await Monitor.sendStats(io, monitorID, user.id) } } @@ -348,11 +346,8 @@ async function getMonitorJSONList(userID) { for (let monitor of monitorList) { result[monitor.id] = monitor.toJSON(); - } - - return result; } @@ -455,3 +450,15 @@ async function sendHeartbeatList(socket, monitorID) { socket.emit("heartbeatList", monitorID, result) } +async function sendImportantHeartbeatList(socket, monitorID) { + let list = await R.find("heartbeat", ` + monitor_id = ? + AND important = 1 + ORDER BY time DESC + LIMIT 500 + `, [ + monitorID + ]) + + socket.emit("importantHeartbeatList", monitorID, list) +} diff --git a/src/components/CountUp.vue b/src/components/CountUp.vue new file mode 100644 index 000000000..113c3cbe6 --- /dev/null +++ b/src/components/CountUp.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/src/components/Datetime.vue b/src/components/Datetime.vue new file mode 100644 index 000000000..e438d72b0 --- /dev/null +++ b/src/components/Datetime.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/components/Status.vue b/src/components/Status.vue new file mode 100644 index 000000000..c5fec2241 --- /dev/null +++ b/src/components/Status.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index c4165cde0..f2deae079 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -8,7 +8,7 @@
- + Uptime Kuma @@ -21,7 +21,8 @@
- + +
@@ -66,4 +67,8 @@ export default { background-color: crimson; color: white; } + + main { + margin-bottom: 30px; + } diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 6093a535b..132a5d10d 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -20,10 +20,15 @@ export default { monitorList: [ ], - heartbeatList: { }, + importantHeartbeatList: { + + }, + avgPingList: { + + } } }, @@ -42,6 +47,30 @@ export default { } this.heartbeatList[data.monitorID].push(data) + + // Add to important list if it is important + // Also toast + if (data.important) { + + if (data.status === 0) { + toast.error(`[${this.monitorList[data.monitorID].name}] [DOWN] ${data.msg}`, { + timeout: false, + }); + } else if (data.status === 1) { + toast.success(`[${this.monitorList[data.monitorID].name}] [Up] ${data.msg}`, { + timeout: 20000, + }); + } else { + toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`); + } + + + if (! (data.monitorID in this.importantHeartbeatList)) { + this.importantHeartbeatList[data.monitorID] = []; + } + + this.importantHeartbeatList[data.monitorID].unshift(data) + } }); socket.on('heartbeatList', (monitorID, data) => { @@ -52,6 +81,17 @@ export default { } }); + socket.on('avgPing', (monitorID, data) => { + this.avgPingList[monitorID] = data + }); + + socket.on('importantHeartbeatList', (monitorID, data) => { + if (! (monitorID in this.importantHeartbeatList)) { + this.importantHeartbeatList[monitorID] = data; + } else { + this.importantHeartbeatList[monitorID] = data.concat(this.importantHeartbeatList[monitorID]) + } + }); socket.on('disconnect', () => { console.log("disconnect") @@ -65,8 +105,7 @@ export default { // Reset Heartbeat list if it is re-connect if (this.socket.connectCount >= 2) { - console.log("reset heartbeat list") - this.heartbeatList = {} + this.clearData() } if (storage.token) { @@ -128,10 +167,9 @@ export default { logout() { storage.removeItem("token"); this.socket.token = null; + this.loggedIn = false; - socket.emit("logout", () => { - window.location.reload() - }) + this.clearData() }, add(monitor, callback) { @@ -142,6 +180,12 @@ export default { socket.emit("deleteMonitor", monitorID, callback) }, + clearData() { + console.log("reset heartbeat list") + this.heartbeatList = {} + this.importantHeartbeatList = {} + }, + }, computed: { @@ -156,26 +200,6 @@ export default { return result; }, - // TODO: handle history + real time - importantHeartbeatList() { - let result = {} - - for (let monitorID in this.heartbeatList) { - result[monitorID] = []; - - let index = this.heartbeatList[monitorID].length - 1; - let list = this.heartbeatList[monitorID]; - - for (let heartbeat of list) { - if (heartbeat.important) { - result[monitorID].push(heartbeat) - } - } - } - - return result; - }, - statusList() { let result = {} diff --git a/src/pages/Dashboard.vue b/src/pages/Dashboard.vue index 87950b6d8..d274bed05 100644 --- a/src/pages/Dashboard.vue +++ b/src/pages/Dashboard.vue @@ -2,7 +2,7 @@
-
+
Add New Monitor
@@ -31,7 +31,7 @@
-
+
diff --git a/src/pages/Details.vue b/src/pages/Details.vue index ffd70494e..79128a266 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -5,14 +5,11 @@
- Edit + Edit
- - -
@@ -24,6 +21,54 @@
+
+
+
+

{{ pingTitle }}

+

(Current)

+ +
+
+

Avg.{{ pingTitle }}

+

(24-hour)

+ +
+
+

Uptime

+

(24-hour)

+ +
+
+

Uptime

+

(30-day)

+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
StatusDateTimeMessage
{{ beat.msg }}
No important events
+
+ Are you sure want to pause? @@ -38,11 +83,17 @@ import { useToast } from 'vue-toastification' const toast = useToast() import Confirm from "../components/Confirm.vue"; import HeartbeatBar from "../components/HeartbeatBar.vue"; +import Status from "../components/Status.vue"; +import Datetime from "../components/Datetime.vue"; +import CountUp from "../components/CountUp.vue"; export default { components: { + CountUp, + Datetime, HeartbeatBar, - Confirm + Confirm, + Status, }, mounted() { @@ -53,6 +104,15 @@ export default { } }, computed: { + + pingTitle() { + if (this.monitor.type === "http") { + return "Response" + } else { + return "Ping" + } + }, + monitor() { let id = this.$route.params.id return this.$root.monitorList[id]; @@ -66,34 +126,65 @@ export default { } }, + ping() { + if (this.lastHeartBeat.ping) { + return this.lastHeartBeat.ping; + } else { + return "N/A" + } + }, + + avgPing() { + if (this.$root.avgPingList[this.monitor.id]) { + return this.$root.avgPingList[this.monitor.id]; + } else { + return "N/A" + } + }, + + importantHeartBeatList() { + if (this.$root.importantHeartbeatList[this.monitor.id]) { + return this.$root.importantHeartbeatList[this.monitor.id] + } else { + return []; + } + }, + status() { if (this.$root.statusList[this.monitor.id]) { return this.$root.statusList[this.monitor.id] } else { - return { - - } + return { } } } }, methods: { + testNotification() { + this.$root.getSocket().emit("testNotification", this.monitor.id) + toast.success("Test notification is requested.") + }, + pauseDialog() { this.$refs.confirmPause.show(); }, + resumeMonitor() { this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => { this.$root.toastRes(res) }) }, + pauseMonitor() { this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => { this.$root.toastRes(res) }) }, + deleteDialog() { this.$refs.confirmDelete.show(); }, + deleteMonitor() { this.$root.deleteMonitor(this.monitor.id, (res) => { if (res.ok) { @@ -104,6 +195,7 @@ export default { } }) } + } } @@ -136,4 +228,18 @@ export default { color: #AAA; font-size: 14px; } + +table { + font-size: 14px; + + tr { + --bs-table-accent-bg: white; + transition: all ease-in-out 0.2ms; + } +} + +.stats p { + font-size: 13px; + color: #AAA; +}