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 @@
+
+ {{ output }} {{ unit }}
+ {{ value }}
+
+
+
+
+
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 @@
+
+ {{ displayText }}
+
+
+
+
+
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 @@
+
+ {{ text }}
+
+
+
+
+
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
-
-
-
+
+
+
+
{{ pingTitle }}
+
(Current)
+
+
+
+
Avg.{{ pingTitle }}
+
(24-hour)
+
+
+
+
+
+
+
+
+
+
+
+ Status |
+ DateTime |
+ Message |
+
+
+
+
+ |
+ |
+ {{ 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;
+}