[WIP] Handle timezone offset for timeRange

This commit is contained in:
Louis Lam 2022-09-25 19:38:28 +08:00
parent f11dfc8f43
commit 3f63cb246b
7 changed files with 110 additions and 32 deletions

View file

@ -4,17 +4,19 @@ let timezone = require("dayjs/plugin/timezone");
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
const { parseVueDatePickerTimeFormat, parseTimeFormatFromVueDatePicker } = require("../../src/util"); const { parseTimeObject, parseTimeFromTimeObject } = require("../../src/util");
const { isArray } = require("chart.js/helpers"); const { isArray } = require("chart.js/helpers");
const { timeObjectToUTC, timeObjectToLocal } = require("../util-server");
class Maintenance extends BeanModel { class Maintenance extends BeanModel {
/** /**
* Return an object that ready to parse to JSON for public * Return an object that ready to parse to JSON for public
* Only show necessary data to public * Only show necessary data to public
* @param {string} timezone If not specified, the timeRange will be in UTC
* @returns {Object} * @returns {Object}
*/ */
async toPublicJSON() { async toPublicJSON(timezone = null) {
let dateTimeRange = []; let dateTimeRange = [];
if (this.start_datetime) { if (this.start_datetime) {
@ -33,11 +35,21 @@ class Maintenance extends BeanModel {
} }
let timeRange = []; let timeRange = [];
let startTime = parseVueDatePickerTimeFormat(this.start_time); let startTime = parseTimeObject(this.start_time);
timeRange.push(startTime); timeRange.push(startTime);
let endTime = parseVueDatePickerTimeFormat(this.end_time); let endTime = 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,
@ -65,17 +77,28 @@ class Maintenance extends BeanModel {
/** /**
* Return an object that ready to parse to JSON * Return an object that ready to parse to JSON
* @param {string} timezone If not specified, the timeRange will be in UTC
* @returns {Object} * @returns {Object}
*/ */
async toJSON() { async toJSON(timezone = null) {
return this.toPublicJSON(); return this.toPublicJSON(timezone);
} }
static jsonToBean(bean, obj) { static jsonToBean(bean, obj, timezone) {
if (obj.id) { if (obj.id) {
bean.id = obj.id; bean.id = obj.id;
} }
// Apply timezone offset to timeRange, as it cannot apply automatically.
if (timezone) {
if (obj.timeRange[0]) {
timeObjectToUTC(obj.timeRange[0], timezone);
if (obj.timeRange[1]) {
timeObjectToUTC(obj.timeRange[1], timezone);
}
}
}
bean.title = obj.title; bean.title = obj.title;
bean.description = obj.description; bean.description = obj.description;
bean.strategy = obj.strategy; bean.strategy = obj.strategy;
@ -98,8 +121,8 @@ class Maintenance extends BeanModel {
} }
} }
bean.start_time = parseTimeFormatFromVueDatePicker(obj.timeRange[0]); bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
bean.end_time = parseTimeFormatFromVueDatePicker(obj.timeRange[1]); bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
bean.weekdays = JSON.stringify(obj.weekdays); bean.weekdays = JSON.stringify(obj.weekdays);
bean.days_of_month = JSON.stringify(obj.daysOfMonth); bean.days_of_month = JSON.stringify(obj.daysOfMonth);

View file

@ -5,6 +5,11 @@ 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 utc = require("dayjs/plugin/utc");
let timezone = require("dayjs/plugin/timezone");
dayjs.extend(utc);
dayjs.extend(timezone);
/** /**
* Handlers for Maintenance * Handlers for Maintenance
@ -12,13 +17,13 @@ const server = UptimeKumaServer.getInstance();
*/ */
module.exports.maintenanceSocketHandler = (socket) => { module.exports.maintenanceSocketHandler = (socket) => {
// Add a new maintenance // Add a new maintenance
socket.on("addMaintenance", async (maintenance, callback) => { socket.on("addMaintenance", async (maintenance, timezone, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
log.debug("maintenance", maintenance); log.debug("maintenance", maintenance);
let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance); let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance, timezone);
bean.user_id = socket.userID; bean.user_id = socket.userID;
let maintenanceID = await R.store(bean); let maintenanceID = await R.store(bean);
@ -39,7 +44,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
}); });
// Edit a maintenance // Edit a maintenance
socket.on("editMaintenance", async (maintenance, callback) => { socket.on("editMaintenance", async (maintenance, timezone, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
@ -49,7 +54,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
throw new Error("Permission denied."); throw new Error("Permission denied.");
} }
Maintenance.jsonToBean(bean, maintenance); Maintenance.jsonToBean(bean, maintenance, timezone);
await R.store(bean); await R.store(bean);
@ -138,7 +143,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
} }
}); });
socket.on("getMaintenance", async (maintenanceID, callback) => { socket.on("getMaintenance", async (maintenanceID, timezone, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
@ -151,7 +156,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
callback({ callback({
ok: true, ok: true,
maintenance: await bean.toJSON(), maintenance: await bean.toJSON(timezone),
}); });
} catch (e) { } catch (e) {

View file

@ -21,6 +21,11 @@ const {
rfc2865: { file, attributes }, rfc2865: { file, attributes },
}, },
} = require("node-radius-utils"); } = require("node-radius-utils");
const dayjs = require("dayjs");
const utc = require("dayjs/plugin/utc");
let timezone = require("dayjs/plugin/timezone");
dayjs.extend(utc);
dayjs.extend(timezone);
// From ping-lite // From ping-lite
exports.WIN = /^win/.test(process.platform); exports.WIN = /^win/.test(process.platform);
@ -645,3 +650,44 @@ module.exports.send403 = (res, msg = "") => {
"msg": msg, "msg": msg,
}); });
}; };
function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
// e.g. +08:00
let offsetString = dayjs().tz(timezone).format("Z");
let hours = parseInt(offsetString.substring(1, 3));
let minutes = parseInt(offsetString.substring(4, 6));
if (
(timeObjectToUTC && offsetString.startsWith("+")) ||
(!timeObjectToUTC && offsetString.startsWith("-"))
) {
hours *= -1;
minutes *= -1;
}
obj.hours += hours;
obj.minutes += minutes;
// Handle out of bound
if (obj.hours < 0) {
obj.hours += 24;
} else if (obj.hours > 24) {
obj.hours -= 24;
}
if (obj.minutes < 0) {
obj.minutes += 24;
} else if (obj.minutes > 24) {
obj.minutes -= 24;
}
return obj;
}
module.exports.timeObjectToUTC = (obj, timezone) => {
return timeObjectConvertTimezone(obj, timezone, true);
};
module.exports.timeObjectToLocal = (obj, timezone) => {
return timeObjectConvertTimezone(obj, timezone, false);
};

View file

@ -607,7 +607,7 @@ export default {
recurringInterval: "Interval", recurringInterval: "Interval",
"Recurring": "Recurring", "Recurring": "Recurring",
strategyManual: "Active/Inactive Manually", strategyManual: "Active/Inactive Manually",
warningTimezone: "It is NOT your current browser's timezone. It is your server's timezone.", warningTimezone: "It is using your current Device/PC's timezone.",
weekdayShortMon: "Mon", weekdayShortMon: "Mon",
weekdayShortTue: "Tue", weekdayShortTue: "Tue",
weekdayShortWed: "Wed", weekdayShortWed: "Wed",

View file

@ -109,7 +109,6 @@
:monthChangeOnScroll="false" :monthChangeOnScroll="false"
:minDate="minDate" :minDate="minDate"
format="yyyy-MM-dd HH:mm" format="yyyy-MM-dd HH:mm"
utc="preserve"
/> />
</div> </div>
</template> </template>
@ -185,8 +184,8 @@
<Datepicker <Datepicker
v-model="maintenance.timeRange" v-model="maintenance.timeRange"
:dark="$root.isDark" :dark="$root.isDark"
timePicker disableTimeRangeValidation range timePicker
placeholder="Select Time" disableTimeRangeValidation range
textInput textInput
/> />
</div> </div>
@ -201,7 +200,7 @@
:monthChangeOnScroll="false" :monthChangeOnScroll="false"
:minDate="minDate" :minDate="minDate"
:enableTimePicker="false" :enableTimePicker="false"
utc="preserve" :utc="true"
/> />
</div> </div>
</template> </template>
@ -357,6 +356,9 @@ export default {
}, },
methods: { methods: {
init() { init() {
// Use browser's timezone!
let timezone = dayjs.tz.guess();
this.affectedMonitors = []; this.affectedMonitors = [];
this.selectedStatusPages = []; this.selectedStatusPages = [];
@ -380,10 +382,8 @@ export default {
daysOfMonth: [], daysOfMonth: [],
}; };
} else if (this.isEdit) { } else if (this.isEdit) {
this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => { this.$root.getSocket().emit("getMaintenance", this.$route.params.id, timezone, (res) => {
if (res.ok) { if (res.ok) {
res.maintenance.start_date = this.$root.datetimeFormat(res.maintenance.start_date, "YYYY-MM-DDTHH:mm");
res.maintenance.end_date = this.$root.datetimeFormat(res.maintenance.end_date, "YYYY-MM-DDTHH:mm");
this.maintenance = res.maintenance; this.maintenance = res.maintenance;
this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => { this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => {
@ -441,8 +441,11 @@ 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, async (res) => { this.$root.addMaintenance(this.maintenance, timezone, 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, () => {
@ -459,7 +462,7 @@ export default {
}); });
} else { } else {
this.$root.getSocket().emit("editMaintenance", this.maintenance, async (res) => { this.$root.getSocket().emit("editMaintenance", this.maintenance, timezone, 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

@ -7,7 +7,7 @@
// Backend uses the compiled file util.js // Backend uses the compiled file util.js
// Frontend uses util.ts // Frontend uses util.ts
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.parseTimeFormatFromVueDatePicker = exports.parseVueDatePickerTimeFormat = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
const _dayjs = require("dayjs"); const _dayjs = require("dayjs");
const dayjs = _dayjs; const dayjs = _dayjs;
exports.isDev = process.env.NODE_ENV === "development"; exports.isDev = process.env.NODE_ENV === "development";
@ -314,7 +314,7 @@ exports.getMaintenanceRelativeURL = getMaintenanceRelativeURL;
* @param {string} time E.g. 12:00 * @param {string} time E.g. 12:00
* @returns object * @returns object
*/ */
function parseVueDatePickerTimeFormat(time) { function parseTimeObject(time) {
if (!time) { if (!time) {
return { return {
hours: 0, hours: 0,
@ -335,11 +335,11 @@ function parseVueDatePickerTimeFormat(time) {
} }
return obj; return obj;
} }
exports.parseVueDatePickerTimeFormat = parseVueDatePickerTimeFormat; exports.parseTimeObject = parseTimeObject;
/** /**
* @returns string e.g. 12:00 * @returns string e.g. 12:00
*/ */
function parseTimeFormatFromVueDatePicker(obj) { function parseTimeFromTimeObject(obj) {
if (!obj) { if (!obj) {
return obj; return obj;
} }
@ -350,4 +350,4 @@ function parseTimeFormatFromVueDatePicker(obj) {
} }
return result; return result;
} }
exports.parseTimeFormatFromVueDatePicker = parseTimeFormatFromVueDatePicker; exports.parseTimeFromTimeObject = parseTimeFromTimeObject;

View file

@ -348,7 +348,7 @@ export function getMaintenanceRelativeURL(id: string) {
* @param {string} time E.g. 12:00 * @param {string} time E.g. 12:00
* @returns object * @returns object
*/ */
export function parseVueDatePickerTimeFormat(time: string) { export function parseTimeObject(time: string) {
if (!time) { if (!time) {
return { return {
hours: 0, hours: 0,
@ -376,7 +376,7 @@ export function parseVueDatePickerTimeFormat(time: string) {
/** /**
* @returns string e.g. 12:00 * @returns string e.g. 12:00
*/ */
export function parseTimeFormatFromVueDatePicker(obj : any) { export function parseTimeFromTimeObject(obj : any) {
if (!obj) { if (!obj) {
return obj; return obj;
} }
@ -391,3 +391,4 @@ export function parseTimeFormatFromVueDatePicker(obj : any) {
return result; return result;
} }