diff --git a/server/model/maintenance.js b/server/model/maintenance.js index d46b9d4b4..2ab2a5bb4 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -2,13 +2,14 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC } = require("../../src/util"); const { isArray } = require("chart.js/helpers"); const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); +const { R } = require("redbean-node"); +const dayjs = require("dayjs"); class Maintenance extends BeanModel { /** * Return an object that ready to parse to JSON for public * Only show necessary data to public - * @param {string} timezone If not specified, the timeRange will be in UTC * @returns {Object} */ async toPublicJSON() { @@ -38,6 +39,7 @@ class Maintenance extends BeanModel { timeRange: timeRange, weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [], daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [], + timeslotList: await this.getTimeslotList(), }; if (!isArray(obj.weekdays)) { @@ -48,9 +50,45 @@ class Maintenance extends BeanModel { obj.daysOfMonth = []; } + // Maintenance Status + if (!obj.active) { + obj.status = "inactive"; + } else if (obj.strategy === "manual" || obj.timeslotList.length > 0) { + for (let timeslot of obj.timeslotList) { + if (dayjs.utc(timeslot.start_date) <= dayjs.utc() && dayjs.utc(timeslot.end_date) >= dayjs.utc()) { + obj.status = "under-maintenance"; + break; + } + } + + if (!obj.status) { + obj.status = "scheduled"; + } + } else if (obj.timeslotList.length === 0) { + obj.status = "ended"; + } else { + obj.status = "unknown"; + } + return obj; } + /** + * Only get future or current timeslots only + * @returns {Promise<[]>} + */ + async getTimeslotList() { + return await R.getAll(` + SELECT maintenance_timeslot.* + FROM maintenance_timeslot, maintenance + WHERE maintenance_timeslot.maintenance_id = maintenance.id + AND maintenance.id = ? + AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()} + `, [ + this.id + ]); + } + /** * Return an object that ready to parse to JSON * @param {string} timezone If not specified, the timeRange will be in UTC @@ -111,6 +149,19 @@ class Maintenance extends BeanModel { `; } + + /** + * SQL conditions for active and future maintenance + * @returns {string} + */ + static getActiveAndFutureMaintenanceSQLCondition() { + return ` + (maintenance_timeslot.end_date >= DATETIME('now') + AND maintenance.active = 1) + OR + (maintenance.strategy = 'manual' AND active = 1) + `; + } } module.exports = Maintenance; diff --git a/server/model/monitor.js b/server/model/monitor.js index 4c51d2209..d77c55297 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1109,10 +1109,10 @@ class Monitor extends BeanModel { FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id - JOIN maintenance_timeslot + AND mm.monitor_id = ? + LEFT JOIN maintenance_timeslot ON maintenance_timeslot.maintenance_id = maintenance.id - WHERE mm.monitor_id = ? - AND ${activeCondition} + WHERE ${activeCondition} LIMIT 1`, [ monitorID ]); return maintenance.count !== 0; } diff --git a/src/assets/app.scss b/src/assets/app.scss index 81cf77245..be324afdf 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -269,6 +269,20 @@ optgroup { color: white; } + .btn-normal { + $bg-color: $dark-header-bg; + + color: $dark-font-color; + background-color: $bg-color; + border-color: $bg-color; + + &:hover { + $hover-color: darken($bg-color, 3%); + background-color: $hover-color; + border-color: $hover-color; + } + } + .btn-warning { color: $dark-font-color2; diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 7ece4982e..acd9446c1 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -58,7 +58,7 @@
  • - + {{ $t("Settings") }}
  • diff --git a/src/mixins/datetime.js b/src/mixins/datetime.js index 3bbe11307..4fa2fa831 100644 --- a/src/mixins/datetime.js +++ b/src/mixins/datetime.js @@ -12,10 +12,6 @@ export default { }, methods: { - isActiveMaintenance(endDate) { - return (dayjs.utc(endDate).unix() >= dayjs.utc().unix()); - }, - toUTC(value) { return dayjs.tz(value, this.timezone).utc().format(); }, diff --git a/src/pages/ManageMaintenance.vue b/src/pages/ManageMaintenance.vue index 51e3ee28e..28bea9b82 100644 --- a/src/pages/ManageMaintenance.vue +++ b/src/pages/ManageMaintenance.vue @@ -20,7 +20,7 @@ v-for="(item, index) in sortedMaintenanceList" :key="index" class="item" - :class="{ 'ended': !$root.isActiveMaintenance(item.end_date) }" + :class="item.status" >
    {{ $t("Details") }} - @@ -43,7 +43,7 @@ {{ $t("Resume") }} - + {{ $t("Edit") }} @@ -90,36 +90,6 @@ export default { let result = Object.values(this.$root.maintenanceList); result.sort((m1, m2) => { - - if (this.$root.isActiveMaintenance(m1.end_date) !== this.$root.isActiveMaintenance(m2.end_date)) { - if (!this.$root.isActiveMaintenance(m2.end_date)) { - return -1; - } - if (!this.$root.isActiveMaintenance(m1.end_date)) { - return 1; - } - } - - if (this.$root.isActiveMaintenance(m1.end_date) && this.$root.isActiveMaintenance(m2.end_date)) { - if (Date.parse(m1.end_date) < Date.parse(m2.end_date)) { - return -1; - } - - if (Date.parse(m2.end_date) < Date.parse(m1.end_date)) { - return 1; - } - } - - if (!this.$root.isActiveMaintenance(m1.end_date) && !this.$root.isActiveMaintenance(m2.end_date)) { - if (Date.parse(m1.end_date) < Date.parse(m2.end_date)) { - return 1; - } - - if (Date.parse(m2.end_date) < Date.parse(m1.end_date)) { - return -1; - } - } - return m1.title.localeCompare(m2.title); }); @@ -173,7 +143,7 @@ export default { /** * Pause maintenance */ - pauseMonitor() { + pauseMaintenance() { return; this.$root.getSocket().emit("pauseMaintenance", selectedMaintenanceID, (res) => { this.$root.toastRes(res); @@ -211,13 +181,43 @@ export default { background-color: $highlight-white; } + &.under-maintenance { + background-color: rgba(23, 71, 245, 0.16); + + &:hover { + background-color: rgba(23, 71, 245, 0.3) !important; + } + + .circle { + background-color: $maintenance; + } + } + + &.scheduled { + .circle { + background-color: $primary; + } + } + + &.inactive { + .circle { + background-color: $danger; + } + } + &.ended { .left-part { - opacity: 0.5; + opacity: 0.3; + } - .circle { - background-color: $dark-font-color; - } + .circle { + background-color: $dark-font-color; + } + } + + &.unknown { + .circle { + background-color: $dark-font-color; } } @@ -230,7 +230,6 @@ export default { width: 25px; height: 25px; border-radius: 50rem; - background-color: $maintenance; } .info {