diff --git a/db/knex_migrations/2024-10-16-0000-timezones.js b/db/knex_migrations/2024-10-16-0000-timezones.js new file mode 100644 index 000000000..36303c6ac --- /dev/null +++ b/db/knex_migrations/2024-10-16-0000-timezones.js @@ -0,0 +1,12 @@ +exports.up = function (knex) { + return knex.schema + .alterTable("monitor", function (table) { + table.text("timezone").notNullable().defaultTo("auto"); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.dropColumn("timezone"); + }); +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index da0c0d5c8..be0893e76 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -154,6 +154,7 @@ class Monitor extends BeanModel { jsonPathOperator: this.jsonPathOperator, snmpVersion: this.snmpVersion, conditions: JSON.parse(this.conditions), + timezone: this.timezone, }; if (includeSensitiveData) { diff --git a/server/server.js b/server/server.js index db58ae829..903c7d339 100644 --- a/server/server.js +++ b/server/server.js @@ -869,6 +869,7 @@ let needSetup = false; bean.jsonPathOperator = monitor.jsonPathOperator; bean.timeout = monitor.timeout; bean.conditions = JSON.stringify(monitor.conditions); + bean.timezone = monitor.timezone; bean.validate(); diff --git a/src/components/PingChart.vue b/src/components/PingChart.vue index 60b0d3c9c..dc0fa495f 100644 --- a/src/components/PingChart.vue +++ b/src/components/PingChart.vue @@ -40,6 +40,11 @@ export default { type: Number, required: true, }, + /** Monitor Timezone */ + monitorTimezone: { + type: String, + required: true, + }, }, data() { return { @@ -251,7 +256,7 @@ export default { }, // push datapoint to chartData pushDatapoint(datapoint, avgPingData, minPingData, maxPingData, downData, colorData) { - const x = this.$root.unixToDateTime(datapoint.timestamp); + const x = this.$root.unixToDateTime(datapoint.timestamp, this.monitorTimezone); // Show ping values if it was up in this period avgPingData.push({ @@ -306,7 +311,7 @@ export default { let heartbeatList = (this.monitorId in this.$root.heartbeatList && this.$root.heartbeatList[this.monitorId]) || []; for (const beat of heartbeatList) { - const beatTime = this.$root.toDayjs(beat.time); + const beatTime = this.$root.toDayjs(beat.time, this.monitorTimezone); const x = beatTime.format("YYYY-MM-DD HH:mm:ss"); // Insert empty datapoint to separate big gaps @@ -407,7 +412,7 @@ export default { continue; } - const beatTime = this.$root.unixToDayjs(datapoint.timestamp); + const beatTime = this.$root.unixToDayjs(datapoint.timestamp, this.monitorTimezone); // Insert empty datapoint to separate big gaps if (lastHeartbeatTime && monitorInterval) { @@ -427,7 +432,7 @@ export default { const gapX = [ lastHeartbeatTime.subtract(monitorInterval, "second").format("YYYY-MM-DD HH:mm:ss"), - this.$root.unixToDateTime(datapoint.timestamp + 60), + this.$root.unixToDateTime(datapoint.timestamp + 60, this.monitorTimezone), ]; for (const x of gapX) { diff --git a/src/mixins/datetime.js b/src/mixins/datetime.js index ca2d8f018..daaf65fc3 100644 --- a/src/mixins/datetime.js +++ b/src/mixins/datetime.js @@ -44,28 +44,31 @@ export default { /** * Converts a Unix timestamp to a formatted date and time string. * @param {number} value - The Unix timestamp to convert. + * @param {string} timezone - The timezone to use for the conversion. * @returns {string} The formatted date and time string. */ - unixToDateTime(value) { - return dayjs.unix(value).tz(this.timezone).format("YYYY-MM-DD HH:mm:ss"); + unixToDateTime(value, timezone) { + return dayjs.unix(value).tz(timezone === "auto" ? this.timezone : timezone).format("YYYY-MM-DD HH:mm:ss"); }, /** * Converts a Unix timestamp to a dayjs object. * @param {number} value - The Unix timestamp to convert. + * @param {string} timezone - The timezone to use for the conversion. * @returns {dayjs.Dayjs} The dayjs object representing the given timestamp. */ - unixToDayjs(value) { - return dayjs.unix(value).tz(this.timezone); + unixToDayjs(value, timezone) { + return dayjs.unix(value).tz(timezone === "auto" ? this.timezone : timezone); }, /** * Converts the given value to a dayjs object. - * @param {string} value - the value to be converted - * @returns {dayjs.Dayjs} a dayjs object in the timezone of this instance + * @param {string} value - The value to be converted. + * @param {string} timezone - The timezone to use for the conversion. + * @returns {dayjs.Dayjs} The dayjs object in the timezone of this instance. */ - toDayjs(value) { - return dayjs.utc(value).tz(this.timezone); + toDayjs(value, timezone) { + return dayjs.utc(value).tz(timezone === "auto" ? this.timezone : timezone); }, /** diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 17d32365c..0c9feba76 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -184,7 +184,7 @@
- +
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 5d999b597..7751f041f 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -8,6 +8,7 @@

{{ $t("General") }}

+
+ +
+ + +
+

{{ $t("Advanced") }}

@@ -1077,6 +1095,8 @@ import { hostNameRegexPattern } from "../util-frontend"; import HiddenInput from "../components/HiddenInput.vue"; import EditMonitorConditions from "../components/EditMonitorConditions.vue"; import { version } from "../../package.json"; +import dayjs from "dayjs"; +import { timezoneList } from "../util-frontend"; const userAgent = `'Uptime-Kuma/${version}'`; const toast = useToast(); @@ -1122,7 +1142,8 @@ const monitorDefaults = { kafkaProducerAllowAutoTopicCreation: false, gamedigGivenPortOnly: true, remote_browser: null, - conditions: [] + conditions: [], + timezone: "auto" }; export default { @@ -1165,6 +1186,7 @@ export default { }, draftGroupName: null, remoteBrowsersEnabled: false, + timezoneList: timezoneList(), }; }, @@ -1217,6 +1239,18 @@ export default { return command.join(" "); }, + getUserTimezone() { + let timezone = localStorage.timezone; + if (timezone === null || timezone === "auto") { + timezone = this.guessTimezone; + } + return timezone; + }, + + guessTimezone() { + return dayjs.tz.guess(); + }, + ipRegex() { // Allow to test with simple dns server with port (127.0.0.1:5300)