This commit is contained in:
Louis Lam 2022-10-10 20:48:11 +08:00
parent 539683f8e9
commit c1ccaa7a9f
8 changed files with 93 additions and 45 deletions

2
package-lock.json generated
View File

@ -69,7 +69,7 @@
"@vitejs/plugin-legacy": "~2.1.0", "@vitejs/plugin-legacy": "~2.1.0",
"@vitejs/plugin-vue": "~3.1.0", "@vitejs/plugin-vue": "~3.1.0",
"@vue/compiler-sfc": "~3.2.36", "@vue/compiler-sfc": "~3.2.36",
"@vuepic/vue-datepicker": "^3.4.8", "@vuepic/vue-datepicker": "~3.4.8",
"aedes": "^0.46.3", "aedes": "^0.46.3",
"babel-plugin-rewire": "~1.2.0", "babel-plugin-rewire": "~1.2.0",
"bootstrap": "5.1.3", "bootstrap": "5.1.3",

View File

@ -1,5 +1,5 @@
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
const { parseTimeObject, parseTimeFromTimeObject, isoToUTCDateTime, utcToISODateTime, SQL_DATETIME_FORMAT, utcToLocal, localToUTC } = require("../../src/util"); const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC } = require("../../src/util");
const { isArray } = require("chart.js/helpers"); const { isArray } = require("chart.js/helpers");
const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); const { timeObjectToUTC, timeObjectToLocal } = require("../util-server");
@ -11,7 +11,7 @@ class Maintenance extends BeanModel {
* @param {string} timezone If not specified, the timeRange will be in UTC * @param {string} timezone If not specified, the timeRange will be in UTC
* @returns {Object} * @returns {Object}
*/ */
async toPublicJSON(timezone = null) { async toPublicJSON() {
let dateRange = []; let dateRange = [];
if (this.start_date) { if (this.start_date) {
@ -22,21 +22,11 @@ class Maintenance extends BeanModel {
} }
let timeRange = []; let timeRange = [];
let startTime = parseTimeObject(this.start_time); let startTime = timeObjectToLocal(parseTimeObject(this.start_time));
timeRange.push(startTime); timeRange.push(startTime);
let endTime = parseTimeObject(this.end_time); let endTime = timeObjectToLocal(parseTimeObject(this.end_time));
timeRange.push(endTime); timeRange.push(endTime);
// Apply timezone offset
if (timezone) {
if (this.start_time) {
timeObjectToLocal(startTime, timezone);
}
if (this.end_time) {
timeObjectToLocal(endTime, timezone);
}
}
let obj = { let obj = {
id: this.id, id: this.id,
title: this.title, title: this.title,
@ -70,18 +60,16 @@ class Maintenance extends BeanModel {
return this.toPublicJSON(timezone); return this.toPublicJSON(timezone);
} }
static jsonToBean(bean, obj, timezone) { static jsonToBean(bean, obj) {
if (obj.id) { if (obj.id) {
bean.id = obj.id; bean.id = obj.id;
} }
// Apply timezone offset to timeRange, as it cannot apply automatically. // Apply timezone offset to timeRange, as it cannot apply automatically.
if (timezone) { if (obj.timeRange[0]) {
if (obj.timeRange[0]) { timeObjectToUTC(obj.timeRange[0]);
timeObjectToUTC(obj.timeRange[0], timezone); if (obj.timeRange[1]) {
if (obj.timeRange[1]) { timeObjectToUTC(obj.timeRange[1]);
timeObjectToUTC(obj.timeRange[1], timezone);
}
} }
} }
@ -118,7 +106,7 @@ class Maintenance extends BeanModel {
(maintenance_timeslot.start_date <= DATETIME('now') (maintenance_timeslot.start_date <= DATETIME('now')
AND maintenance_timeslot.end_date >= DATETIME('now') AND maintenance_timeslot.end_date >= DATETIME('now')
AND maintenance.active = 1) AND maintenance.active = 1)
AND OR
(maintenance.strategy = 'manual' AND active = 1) (maintenance.strategy = 'manual' AND active = 1)
`; `;

View File

@ -40,6 +40,12 @@ class MaintenanceTimeslot extends BeanModel {
bean.end_date = maintenance.end_date; bean.end_date = maintenance.end_date;
bean.generated_next = true; bean.generated_next = true;
await R.store(bean); await R.store(bean);
} else if (maintenance.strategy === "recurring-interval") {
// TODO
} else if (maintenance.strategy === "recurring-weekday") {
// TODO
} else if (maintenance.strategy === "recurring-day-of-month") {
// TODO
} else { } else {
throw new Error("Unknown maintenance strategy"); throw new Error("Unknown maintenance strategy");
} }

View File

@ -5,7 +5,6 @@ const apicache = require("../modules/apicache");
const { UptimeKumaServer } = require("../uptime-kuma-server"); const { UptimeKumaServer } = require("../uptime-kuma-server");
const Maintenance = require("../model/maintenance"); const Maintenance = require("../model/maintenance");
const server = UptimeKumaServer.getInstance(); const server = UptimeKumaServer.getInstance();
const dayjs = require("dayjs");
const MaintenanceTimeslot = require("../model/maintenance_timeslot"); const MaintenanceTimeslot = require("../model/maintenance_timeslot");
/** /**
@ -14,13 +13,13 @@ const MaintenanceTimeslot = require("../model/maintenance_timeslot");
*/ */
module.exports.maintenanceSocketHandler = (socket) => { module.exports.maintenanceSocketHandler = (socket) => {
// Add a new maintenance // Add a new maintenance
socket.on("addMaintenance", async (maintenance, timezone, callback) => { socket.on("addMaintenance", async (maintenance, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
log.debug("maintenance", maintenance); log.debug("maintenance", maintenance);
let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance, timezone); let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance);
bean.user_id = socket.userID; bean.user_id = socket.userID;
let maintenanceID = await R.store(bean); let maintenanceID = await R.store(bean);
await MaintenanceTimeslot.generateTimeslot(bean); await MaintenanceTimeslot.generateTimeslot(bean);
@ -42,7 +41,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
}); });
// Edit a maintenance // Edit a maintenance
socket.on("editMaintenance", async (maintenance, timezone, callback) => { socket.on("editMaintenance", async (maintenance, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
@ -52,7 +51,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
throw new Error("Permission denied."); throw new Error("Permission denied.");
} }
Maintenance.jsonToBean(bean, maintenance, timezone); Maintenance.jsonToBean(bean, maintenance);
await R.store(bean); await R.store(bean);
await MaintenanceTimeslot.generateTimeslot(bean, null, true); await MaintenanceTimeslot.generateTimeslot(bean, null, true);
@ -142,7 +141,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
} }
}); });
socket.on("getMaintenance", async (maintenanceID, timezone, callback) => { socket.on("getMaintenance", async (maintenanceID, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
@ -155,7 +154,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
callback({ callback({
ok: true, ok: true,
maintenance: await bean.toJSON(timezone), maintenance: await bean.toJSON(),
}); });
} catch (e) { } catch (e) {

View File

@ -648,8 +648,14 @@ module.exports.send403 = (res, msg = "") => {
}; };
function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) { function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
// e.g. +08:00 let offsetString;
let offsetString = dayjs().tz(timezone).format("Z");
if (timezone) {
offsetString = dayjs().tz(timezone).format("Z");
} else {
offsetString = dayjs().format("Z");
}
let hours = parseInt(offsetString.substring(1, 3)); let hours = parseInt(offsetString.substring(1, 3));
let minutes = parseInt(offsetString.substring(4, 6)); let minutes = parseInt(offsetString.substring(4, 6));
@ -680,10 +686,22 @@ function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
return obj; return obj;
} }
module.exports.timeObjectToUTC = (obj, timezone) => { /**
*
* @param {object} obj
* @param {string} timezone
* @returns {object}
*/
module.exports.timeObjectToUTC = (obj, timezone = undefined) => {
return timeObjectConvertTimezone(obj, timezone, true); return timeObjectConvertTimezone(obj, timezone, true);
}; };
module.exports.timeObjectToLocal = (obj, timezone) => { /**
*
* @param {object} obj
* @param {string} timezone
* @returns {object}
*/
module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
return timeObjectConvertTimezone(obj, timezone, false); return timeObjectConvertTimezone(obj, timezone, false);
}; };

View File

@ -630,4 +630,5 @@ export default {
lastDay2: "2nd Last Day of Month", lastDay2: "2nd Last Day of Month",
lastDay3: "3rd Last Day of Month", lastDay3: "3rd Last Day of Month",
lastDay4: "4th Last Day of Month", lastDay4: "4th Last Day of Month",
"No Maintenance": "No Maintenance",
}; };

View File

@ -200,7 +200,8 @@
:monthChangeOnScroll="false" :monthChangeOnScroll="false"
:minDate="minDate" :minDate="minDate"
:enableTimePicker="false" :enableTimePicker="false"
:utc="true" format="yyyy-MM-dd"
modelType="yyyy-MM-dd HH:mm:ss"
/> />
</div> </div>
</template> </template>
@ -356,9 +357,6 @@ export default {
}, },
methods: { methods: {
init() { init() {
// Use browser's timezone!
let timezone = dayjs.tz.guess();
this.affectedMonitors = []; this.affectedMonitors = [];
this.selectedStatusPages = []; this.selectedStatusPages = [];
@ -381,7 +379,7 @@ export default {
daysOfMonth: [], daysOfMonth: [],
}; };
} else if (this.isEdit) { } else if (this.isEdit) {
this.$root.getSocket().emit("getMaintenance", this.$route.params.id, timezone, (res) => { this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => {
if (res.ok) { if (res.ok) {
this.maintenance = res.maintenance; this.maintenance = res.maintenance;
@ -440,11 +438,8 @@ export default {
this.maintenance.end_date = this.$root.toUTC(this.maintenance.end_date); this.maintenance.end_date = this.$root.toUTC(this.maintenance.end_date);
*/ */
// Use browser's timezone!
let timezone = dayjs.tz.guess();
if (this.isAdd) { if (this.isAdd) {
this.$root.addMaintenance(this.maintenance, timezone, async (res) => { this.$root.addMaintenance(this.maintenance, async (res) => {
if (res.ok) { if (res.ok) {
await this.addMonitorMaintenance(res.maintenanceID, async () => { await this.addMonitorMaintenance(res.maintenanceID, async () => {
await this.addMaintenanceStatusPage(res.maintenanceID, () => { await this.addMaintenanceStatusPage(res.maintenanceID, () => {
@ -461,7 +456,7 @@ export default {
}); });
} else { } else {
this.$root.getSocket().emit("editMaintenance", this.maintenance, timezone, async (res) => { this.$root.getSocket().emit("editMaintenance", this.maintenance, async (res) => {
if (res.ok) { if (res.ok) {
await this.addMonitorMaintenance(res.maintenanceID, async () => { await this.addMonitorMaintenance(res.maintenanceID, async () => {
await this.addMaintenanceStatusPage(res.maintenanceID, () => { await this.addMaintenanceStatusPage(res.maintenanceID, () => {

View File

@ -13,7 +13,7 @@
<div class="shadow-box"> <div class="shadow-box">
<span v-if="Object.keys(sortedMaintenanceList).length === 0" class="d-flex align-items-center justify-content-center my-3"> <span v-if="Object.keys(sortedMaintenanceList).length === 0" class="d-flex align-items-center justify-content-center my-3">
{{ $t("No maintenance") }} {{ $t("No Maintenance") }}
</span> </span>
<div <div
@ -34,9 +34,19 @@
<div class="buttons"> <div class="buttons">
<router-link v-if="false" :to="maintenanceURL(item.id)" class="btn btn-light">{{ $t("Details") }}</router-link> <router-link v-if="false" :to="maintenanceURL(item.id)" class="btn btn-light">{{ $t("Details") }}</router-link>
<button v-if="item.active" class="btn btn-light" @click="pauseDialog">
<font-awesome-icon icon="pause" /> {{ $t("Pause") }}
</button>
<button v-if="!item.active" class="btn btn-primary" @click="resumeMaintenance">
<font-awesome-icon icon="play" /> {{ $t("Resume") }}
</button>
<router-link :to="'/maintenance/edit/' + item.id" class="btn btn-secondary"> <router-link :to="'/maintenance/edit/' + item.id" class="btn btn-secondary">
<font-awesome-icon icon="edit" /> {{ $t("Edit") }} <font-awesome-icon icon="edit" /> {{ $t("Edit") }}
</router-link> </router-link>
<button class="btn btn-danger" @click="deleteDialog(item.id)"> <button class="btn btn-danger" @click="deleteDialog(item.id)">
<font-awesome-icon icon="trash" /> {{ $t("Delete") }} <font-awesome-icon icon="trash" /> {{ $t("Delete") }}
</button> </button>
@ -48,6 +58,10 @@
<a href="https://github.com/louislam/uptime-kuma/wiki/Maintenance" target="_blank">Learn More</a> <a href="https://github.com/louislam/uptime-kuma/wiki/Maintenance" target="_blank">Learn More</a>
</div> </div>
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseMaintenance">
{{ $t("pauseMaintenanceMsg") }}
</Confirm>
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteMaintenance"> <Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteMaintenance">
{{ $t("deleteMaintenanceMsg") }} {{ $t("deleteMaintenanceMsg") }}
</Confirm> </Confirm>
@ -148,6 +162,33 @@ export default {
} }
}); });
}, },
/**
* Show dialog to confirm pause
*/
pauseDialog() {
this.$refs.confirmPause.show();
},
/**
* Pause maintenance
*/
pauseMonitor() {
return;
this.$root.getSocket().emit("pauseMaintenance", selectedMaintenanceID, (res) => {
this.$root.toastRes(res);
});
},
/**
* Resume maintenance
*/
resumeMaintenance() {
return;
this.$root.getSocket().emit("resumeMaintenance", selectedMaintenanceID, (res) => {
this.$root.toastRes(res);
});
},
}, },
}; };
</script> </script>