From 4002b9f57787220eda4863b76417bbc33aaed81a Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 27 Sep 2022 20:44:44 +0800 Subject: [PATCH] [WIP] Checking maintenance time using maintenance_timeslot table --- server/model/maintenance_timeslot.js | 46 +++++++++++++++++++ server/model/monitor.js | 12 ++++- server/model/status_page.js | 18 ++++---- .../maintenance-socket-handler.js | 3 ++ src/components/Status.vue | 2 +- src/components/Uptime.vue | 2 +- src/languages/en.js | 1 + src/languages/zh-HK.js | 2 + src/mixins/socket.js | 2 +- 9 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 server/model/maintenance_timeslot.js diff --git a/server/model/maintenance_timeslot.js b/server/model/maintenance_timeslot.js new file mode 100644 index 000000000..f749caa5b --- /dev/null +++ b/server/model/maintenance_timeslot.js @@ -0,0 +1,46 @@ +const { BeanModel } = require("redbean-node/dist/bean-model"); +const { R } = require("redbean-node"); +const dayjs = require("dayjs"); + +class MaintenanceTimeslot extends BeanModel { + + async toPublicJSON() { + + } + + async toJSON() { + + } + + /** + * + * @param {Maintenance} maintenance + * @param {dayjs} startFrom (For recurring type only) Generate Timeslot from this date, if it is smaller than the current date, it will use the current date instead. As generating a passed timeslot is meaningless. + * @param {boolean} removeExist Remove existing timeslot before create + * @returns {Promise} + */ + static async generateTimeslot(maintenance, startFrom = null, removeExist = false) { + if (!startFrom) { + startFrom = dayjs(); + } + + if (removeExist) { + await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [ + maintenance.id + ]); + } + + if (maintenance.strategy === "single") { + let bean = R.dispense("maintenance_timeslot"); + bean.maintenance_id = maintenance.id; + bean.start_date = maintenance.start_datetime; + bean.end_date = maintenance.end_datetime; + bean.generated_next = true; + await R.store(bean); + } else { + throw new Error("Unknown maintenance strategy"); + } + } +} + +module.exports = MaintenanceTimeslot; diff --git a/server/model/monitor.js b/server/model/monitor.js index 5a74215eb..a13d70517 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1105,7 +1105,17 @@ class Monitor extends BeanModel { * @returns {Promise} */ static async isUnderMaintenance(monitorID) { - const maintenance = await R.getRow("SELECT COUNT(*) AS count FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now') AND datetime(maintenance.end_date) >= datetime('now') LIMIT 1", [ monitorID ]); + const maintenance = await R.getRow(` + SELECT COUNT(*) AS count + FROM monitor_maintenance mm + JOIN maintenance + ON mm.maintenance_id = maintenance.id + JOIN maintenance_timeslot + ON maintenance_timeslot.maintenance_id = maintenance.id + WHERE mm.monitor_id = ? + AND maintenance_timeslot.start_date <= DATETIME('now') + AND maintenance_timeslot.end_date >= DATETIME('now') + LIMIT 1`, [ monitorID ]); return maintenance.count !== 0; } } diff --git a/server/model/status_page.js b/server/model/status_page.js index d296470d8..4351db582 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -272,15 +272,15 @@ class StatusPage extends BeanModel { const publicMaintenanceList = []; let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(` - SELECT m.* - FROM maintenance m - JOIN maintenance_status_page msp - ON msp.maintenance_id = m.id - WHERE datetime(m.start_date) <= datetime('now') - AND datetime(m.end_date) >= datetime('now') - AND msp.status_page_id = ? - ORDER BY m.end_date - `, [ statusPageId ])); + SELECT m.* + FROM maintenance m, maintenance_status_page msp, maintenance_timeslot + WHERE msp.maintenance_id = m.id + AND maintenance_timeslot.maintenance.id = m.id + AND maintenance_timeslot.start_date <= DATETIME('now') + AND maintenance_timeslot.end_date >= DATETIME('now') + AND msp.status_page_id = ? + ORDER BY m.end_date + `, [ statusPageId ])); for (const bean of maintenanceBeanList) { publicMaintenanceList.push(await bean.toPublicJSON()); diff --git a/server/socket-handlers/maintenance-socket-handler.js b/server/socket-handlers/maintenance-socket-handler.js index 604f07bd1..5358b53e9 100644 --- a/server/socket-handlers/maintenance-socket-handler.js +++ b/server/socket-handlers/maintenance-socket-handler.js @@ -8,6 +8,7 @@ const server = UptimeKumaServer.getInstance(); const dayjs = require("dayjs"); const utc = require("dayjs/plugin/utc"); let timezone = require("dayjs/plugin/timezone"); +const MaintenanceTimeslot = require("../model/maintenance_timeslot"); dayjs.extend(utc); dayjs.extend(timezone); @@ -26,6 +27,7 @@ module.exports.maintenanceSocketHandler = (socket) => { let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance, timezone); bean.user_id = socket.userID; let maintenanceID = await R.store(bean); + await MaintenanceTimeslot.generateTimeslot(bean); await server.sendMaintenanceList(socket); @@ -57,6 +59,7 @@ module.exports.maintenanceSocketHandler = (socket) => { Maintenance.jsonToBean(bean, maintenance, timezone); await R.store(bean); + await MaintenanceTimeslot.generateTimeslot(bean, null, true); await server.sendMaintenanceList(socket); diff --git a/src/components/Status.vue b/src/components/Status.vue index 391fb6d59..92ed8a6b0 100644 --- a/src/components/Status.vue +++ b/src/components/Status.vue @@ -47,7 +47,7 @@ export default { } if (this.status === 3) { - return this.$t("Maintenance"); + return this.$t("statusMaintenance"); } return this.$t("Unknown"); diff --git a/src/components/Uptime.vue b/src/components/Uptime.vue index f089d4c12..8565975cc 100644 --- a/src/components/Uptime.vue +++ b/src/components/Uptime.vue @@ -26,7 +26,7 @@ export default { uptime() { if (this.type === "maintenance") { - return this.$t("Maintenance"); + return this.$t("statusMaintenance"); } let key = this.monitor.id + "_" + this.type; diff --git a/src/languages/en.js b/src/languages/en.js index e77a31f41..fdcaf98e9 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -10,6 +10,7 @@ export default { maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.", acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.", Maintenance: "Maintenance", + statusMaintenance: "Maintenance", "Schedule maintenance": "Schedule maintenance", "Affected Monitors": "Affected Monitors", "Pick Affected Monitors...": "Pick Affected Monitors...", diff --git a/src/languages/zh-HK.js b/src/languages/zh-HK.js index a55f4fb65..cd82be841 100644 --- a/src/languages/zh-HK.js +++ b/src/languages/zh-HK.js @@ -380,4 +380,6 @@ export default { proxyDescription: "必須將代理伺服器指派給監測器才能運作。", enableProxyDescription: "此代理伺服器在啟用前不會在監測器上生效,您可以藉由控制啟用狀態來暫時對所有的監測器停用代理伺服器。", setAsDefaultProxyDescription: "預設情況下,新監測器將啟用此代理伺服器。您仍可分別停用各監測器的代理伺服器。", + Maintenance: "維護", + statusMaintenance: "維護中", }; diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 6da6ee64a..74522ffef 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -588,7 +588,7 @@ export default { if (this.monitorList[monitorID].maintenance) { result[monitorID] = { - text: this.$t("Maintenance"), + text: this.$t("statusMaintenance"), color: "maintenance", }; } else if (! lastHeartBeat) {