[status page] send uptime

This commit is contained in:
LouisLam 2021-09-22 15:10:08 +08:00
parent 8c7ee94769
commit fe0fc63843
5 changed files with 95 additions and 63 deletions

45
package-lock.json generated
View File

@ -8501,7 +8501,8 @@
"@fortawesome/vue-fontawesome": { "@fortawesome/vue-fontawesome": {
"version": "3.0.0-4", "version": "3.0.0-4",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.0-4.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.0-4.tgz",
"integrity": "sha512-dQVhhMRcUPCb0aqk5ohm0KGk5OJ7wFZ9aYapLzJB3Z+xs7LhkRWLTb87reelUAG5PFDjutDAXuloT9hi6cz72A==" "integrity": "sha512-dQVhhMRcUPCb0aqk5ohm0KGk5OJ7wFZ9aYapLzJB3Z+xs7LhkRWLTb87reelUAG5PFDjutDAXuloT9hi6cz72A==",
"requires": {}
}, },
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.5.0", "version": "0.5.0",
@ -8905,7 +8906,8 @@
"version": "1.6.2", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.6.2.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.6.2.tgz",
"integrity": "sha512-Pf+dqkT4pWPfziPm51VtDXsPwE74CEGRiK6Vgm5EDBewHw1EgcxG7V2ZI/Yqj5gcDy5nVtjgx0AbsTL+F3gddg==", "integrity": "sha512-Pf+dqkT4pWPfziPm51VtDXsPwE74CEGRiK6Vgm5EDBewHw1EgcxG7V2ZI/Yqj5gcDy5nVtjgx0AbsTL+F3gddg==",
"dev": true "dev": true,
"requires": {}
}, },
"@vue/compiler-core": { "@vue/compiler-core": {
"version": "3.2.11", "version": "3.2.11",
@ -9038,7 +9040,8 @@
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true "dev": true,
"requires": {}
}, },
"agent-base": { "agent-base": {
"version": "6.0.2", "version": "6.0.2",
@ -9419,7 +9422,8 @@
"bootstrap": { "bootstrap": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.1.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.1.tgz",
"integrity": "sha512-/jUa4sSuDZWlDLQ1gwQQR8uoYSvLJzDd8m5o6bPKh3asLAMYVZKdRCjb1joUd5WXf0WwCNzd2EjwQQhupou0dA==" "integrity": "sha512-/jUa4sSuDZWlDLQ1gwQQR8uoYSvLJzDd8m5o6bPKh3asLAMYVZKdRCjb1joUd5WXf0WwCNzd2EjwQQhupou0dA==",
"requires": {}
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
@ -9565,7 +9569,8 @@
"chartjs-adapter-dayjs": { "chartjs-adapter-dayjs": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-dayjs/-/chartjs-adapter-dayjs-1.0.0.tgz", "resolved": "https://registry.npmjs.org/chartjs-adapter-dayjs/-/chartjs-adapter-dayjs-1.0.0.tgz",
"integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==" "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==",
"requires": {}
}, },
"chokidar": { "chokidar": {
"version": "3.5.2", "version": "3.5.2",
@ -10905,7 +10910,8 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true "dev": true,
"requires": {}
}, },
"ieee754": { "ieee754": {
"version": "1.2.1", "version": "1.2.1",
@ -12119,7 +12125,8 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-modules-local-by-default": { "postcss-modules-local-by-default": {
"version": "4.0.0", "version": "4.0.0",
@ -12438,7 +12445,8 @@
"version": "0.36.2", "version": "0.36.2",
"resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz",
"integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-value-parser": { "postcss-value-parser": {
"version": "4.1.0", "version": "4.1.0",
@ -13367,7 +13375,8 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-5.0.0.tgz", "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-5.0.0.tgz",
"integrity": "sha512-c8aubuARSu5A3vEHLBeOSJt1udOdS+1iue7BmJDTSXoCBmfEQmmWX+59vYIj3NQdJBY6a/QRv1ozVFpaB9jaqA==", "integrity": "sha512-c8aubuARSu5A3vEHLBeOSJt1udOdS+1iue7BmJDTSXoCBmfEQmmWX+59vYIj3NQdJBY6a/QRv1ozVFpaB9jaqA==",
"dev": true "dev": true,
"requires": {}
}, },
"stylelint-config-standard": { "stylelint-config-standard": {
"version": "22.0.0", "version": "22.0.0",
@ -13891,17 +13900,20 @@
"vue-confirm-dialog": { "vue-confirm-dialog": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz", "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz",
"integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==" "integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==",
"requires": {}
}, },
"vue-contenteditable": { "vue-contenteditable": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/vue-contenteditable/-/vue-contenteditable-3.0.4.tgz", "resolved": "https://registry.npmjs.org/vue-contenteditable/-/vue-contenteditable-3.0.4.tgz",
"integrity": "sha512-CmtqT4zHQwLoJEyNVaXUjjUFPUVYlXXBHfSbRCHCUjODMqrn6L293LM1nc1ELx8epitZZvecTfIqOLlSzB3d+w==" "integrity": "sha512-CmtqT4zHQwLoJEyNVaXUjjUFPUVYlXXBHfSbRCHCUjODMqrn6L293LM1nc1ELx8epitZZvecTfIqOLlSzB3d+w==",
"requires": {}
}, },
"vue-demi": { "vue-demi": {
"version": "0.10.1", "version": "0.10.1",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.10.1.tgz", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.10.1.tgz",
"integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA==" "integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA==",
"requires": {}
}, },
"vue-eslint-parser": { "vue-eslint-parser": {
"version": "7.11.0", "version": "7.11.0",
@ -13973,7 +13985,8 @@
"vue-demi": { "vue-demi": {
"version": "0.11.4", "version": "0.11.4",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
"integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==" "integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==",
"requires": {}
} }
} }
}, },
@ -13988,7 +14001,8 @@
"vue-toastification": { "vue-toastification": {
"version": "2.0.0-rc.1", "version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.1.tgz", "resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.1.tgz",
"integrity": "sha512-hjauv/FyesNZdwcr5m1SCyvu1JmlB+Ts5bTptDLDmsYYlj6Oqv8NYakiElpCF+Abwkn9J/AChh6FwkTL1HOb7Q==" "integrity": "sha512-hjauv/FyesNZdwcr5m1SCyvu1JmlB+Ts5bTptDLDmsYYlj6Oqv8NYakiElpCF+Abwkn9J/AChh6FwkTL1HOb7Q==",
"requires": {}
}, },
"vuedraggable": { "vuedraggable": {
"version": "4.1.0", "version": "4.1.0",
@ -14107,7 +14121,8 @@
"ws": { "ws": {
"version": "7.4.6", "version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"requires": {}
}, },
"xmlhttprequest-ssl": { "xmlhttprequest-ssl": {
"version": "2.0.0", "version": "2.0.0",

View File

@ -430,7 +430,7 @@ class Monitor extends BeanModel {
* https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime * https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime
* @param duration : int Hours * @param duration : int Hours
*/ */
static async sendUptime(duration, io, monitorID, userID) { static async calcUptime(duration, monitorID) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour")); const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour"));
@ -489,6 +489,15 @@ class Monitor extends BeanModel {
} }
} }
return uptime;
}
/**
* Send Uptime
* @param duration : int Hours
*/
static async sendUptime(duration, io, monitorID, userID) {
const uptime = await this.calcUptime(duration, monitorID);
io.to(userID).emit("uptime", monitorID, duration, uptime); io.to(userID).emit("uptime", monitorID, duration, uptime);
} }
} }

View File

@ -3,6 +3,7 @@ const { allowDevAllOrigin, getSettings, setting } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const server = require("../server"); const server = require("../server");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor");
let router = express.Router(); let router = express.Router();
let cache = apicache.middleware; let cache = apicache.middleware;
@ -86,6 +87,7 @@ router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, re
await checkPublished(); await checkPublished();
let heartbeatList = {}; let heartbeatList = {};
let uptimeList = {};
let monitorIDList = await R.getCol(` let monitorIDList = await R.getCol(`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\` SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
@ -98,17 +100,21 @@ router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, re
SELECT * FROM heartbeat SELECT * FROM heartbeat
WHERE monitor_id = ? WHERE monitor_id = ?
ORDER BY time DESC ORDER BY time DESC
LIMIT 100 LIMIT 50
`, [ `, [
monitorID, monitorID,
]); ]);
list = R.convertToBeans("heartbeat", list); list = R.convertToBeans("heartbeat", list);
heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON()); heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON());
const type = 24;
uptimeList[`${monitorID}_${type}`] = await Monitor.calcUptime(type, monitorID);
} }
response.json({ response.json({
heartbeatList, heartbeatList,
uptimeList
}); });
} catch (error) { } catch (error) {

View File

@ -99,8 +99,6 @@ module.exports.statusPageSocketHandler = (socket) => {
// Save Config // Save Config
await setSettings("statusPage", config); await setSettings("statusPage", config);
await R.transaction(async (trx) => {
// Save Public Group List // Save Public Group List
const groupIDList = []; const groupIDList = [];
let groupOrder = 1; let groupOrder = 1;
@ -108,7 +106,7 @@ module.exports.statusPageSocketHandler = (socket) => {
for (let group of publicGroupList) { for (let group of publicGroupList) {
let groupBean; let groupBean;
if (group.id) { if (group.id) {
groupBean = await trx.findOne("group", " id = ? AND public = 1 ", [ groupBean = await R.findOne("group", " id = ? AND public = 1 ", [
group.id group.id
]); ]);
} else { } else {
@ -119,9 +117,9 @@ module.exports.statusPageSocketHandler = (socket) => {
groupBean.public = true; groupBean.public = true;
groupBean.weight = groupOrder++; groupBean.weight = groupOrder++;
await trx.store(groupBean); await R.store(groupBean);
await trx.exec("DELETE FROM monitor_group WHERE group_id = ? ", [ await R.exec("DELETE FROM monitor_group WHERE group_id = ? ", [
groupBean.id groupBean.id
]); ]);
@ -131,7 +129,7 @@ module.exports.statusPageSocketHandler = (socket) => {
relationBean.weight = monitorOrder++; relationBean.weight = monitorOrder++;
relationBean.group_id = groupBean.id; relationBean.group_id = groupBean.id;
relationBean.monitor_id = monitor.id; relationBean.monitor_id = monitor.id;
await trx.store(relationBean); await R.store(relationBean);
} }
groupIDList.push(groupBean.id); groupIDList.push(groupBean.id);
@ -141,13 +139,13 @@ module.exports.statusPageSocketHandler = (socket) => {
// Delete groups that not in the list // Delete groups that not in the list
debug("Delete groups that not in the list"); debug("Delete groups that not in the list");
const slots = groupIDList.map(() => "?").join(","); const slots = groupIDList.map(() => "?").join(",");
await trx.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots})`, groupIDList); await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots})`, groupIDList);
callback({ callback({
ok: true, ok: true,
publicGroupList, publicGroupList,
}); });
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-if="loadedTheme" class="container mt-3"> <div v-if="loadedTheme" class="container mt-3">
<!-- Logo & Title --> <!-- Logo & Title -->
<h1> <h1 class="mb-4">
<!-- Logo --> <!-- Logo -->
<div class="logo-wrapper" @click="showImageCropUploadMethod"> <div class="logo-wrapper" @click="showImageCropUploadMethod">
<img :src="imgDataUrl" alt class="logo me-2" :class="logoClass" /> <img :src="imgDataUrl" alt class="logo me-2" :class="logoClass" />
@ -135,7 +135,7 @@
<!-- Overall Status --> <!-- Overall Status -->
<div class="shadow-box list p-4 overall-status mb-4"> <div class="shadow-box list p-4 overall-status mb-4">
<div v-if="Object.keys($root.publicMonitorList).length === 0"> <div v-if="Object.keys($root.publicMonitorList).length === 0 && loadedData">
<font-awesome-icon icon="question-circle" class="ok" /> <font-awesome-icon icon="question-circle" class="ok" />
No Services No Services
</div> </div>
@ -183,7 +183,7 @@
</div> </div>
<div class="mb-4"> <div class="mb-4">
<div v-if="$root.publicGroupList.length === 0" class="text-center"> <div v-if="$root.publicGroupList.length === 0 && loadedData" class="text-center">
👀 Nothing here, please add a group or a monitor. 👀 Nothing here, please add a group or a monitor.
</div> </div>
@ -240,6 +240,7 @@ export default {
showImageCropUpload: false, showImageCropUpload: false,
imgDataUrl: "/icon.svg", imgDataUrl: "/icon.svg",
loadedTheme: false, loadedTheme: false,
loadedData: false,
}; };
}, },
computed: { computed: {
@ -418,6 +419,8 @@ export default {
if (! this.editMode) { if (! this.editMode) {
axios.get("/api/status-page/heartbeat").then((res) => { axios.get("/api/status-page/heartbeat").then((res) => {
this.$root.heartbeatList = res.data.heartbeatList; this.$root.heartbeatList = res.data.heartbeatList;
this.$root.uptimeList = res.data.uptimeList;
this.loadedData = true;
}); });
} }
}, },
@ -432,6 +435,7 @@ export default {
if (res.ok) { if (res.ok) {
this.enableEditMode = false; this.enableEditMode = false;
this.$root.publicGroupList = res.publicGroupList; this.$root.publicGroupList = res.publicGroupList;
location.reload();
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }