mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-27 12:00:35 +03:00
[WIP] Handle timezone offset for timeRange
This commit is contained in:
parent
f11dfc8f43
commit
3f63cb246b
7 changed files with 110 additions and 32 deletions
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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, () => {
|
||||||
|
|
10
src/util.js
10
src/util.js
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue