feat: different monitor timezones from user timezone (#5201)

This commit is contained in:
Marcello Domenis 2024-10-16 18:20:34 -07:00
parent ed6087e233
commit a6610340a5
No known key found for this signature in database
GPG key ID: 922689009EBCEE9F
7 changed files with 70 additions and 14 deletions

View file

@ -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");
});
};

View file

@ -154,6 +154,7 @@ class Monitor extends BeanModel {
jsonPathOperator: this.jsonPathOperator, jsonPathOperator: this.jsonPathOperator,
snmpVersion: this.snmpVersion, snmpVersion: this.snmpVersion,
conditions: JSON.parse(this.conditions), conditions: JSON.parse(this.conditions),
timezone: this.timezone,
}; };
if (includeSensitiveData) { if (includeSensitiveData) {

View file

@ -869,6 +869,7 @@ let needSetup = false;
bean.jsonPathOperator = monitor.jsonPathOperator; bean.jsonPathOperator = monitor.jsonPathOperator;
bean.timeout = monitor.timeout; bean.timeout = monitor.timeout;
bean.conditions = JSON.stringify(monitor.conditions); bean.conditions = JSON.stringify(monitor.conditions);
bean.timezone = monitor.timezone;
bean.validate(); bean.validate();

View file

@ -40,6 +40,11 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
/** Monitor Timezone */
monitorTimezone: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
@ -251,7 +256,7 @@ export default {
}, },
// push datapoint to chartData // push datapoint to chartData
pushDatapoint(datapoint, avgPingData, minPingData, maxPingData, downData, colorData) { 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 // Show ping values if it was up in this period
avgPingData.push({ avgPingData.push({
@ -306,7 +311,7 @@ export default {
let heartbeatList = (this.monitorId in this.$root.heartbeatList && this.$root.heartbeatList[this.monitorId]) || []; let heartbeatList = (this.monitorId in this.$root.heartbeatList && this.$root.heartbeatList[this.monitorId]) || [];
for (const beat of heartbeatList) { 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"); const x = beatTime.format("YYYY-MM-DD HH:mm:ss");
// Insert empty datapoint to separate big gaps // Insert empty datapoint to separate big gaps
@ -407,7 +412,7 @@ export default {
continue; continue;
} }
const beatTime = this.$root.unixToDayjs(datapoint.timestamp); const beatTime = this.$root.unixToDayjs(datapoint.timestamp, this.monitorTimezone);
// Insert empty datapoint to separate big gaps // Insert empty datapoint to separate big gaps
if (lastHeartbeatTime && monitorInterval) { if (lastHeartbeatTime && monitorInterval) {
@ -427,7 +432,7 @@ export default {
const gapX = [ const gapX = [
lastHeartbeatTime.subtract(monitorInterval, "second").format("YYYY-MM-DD HH:mm:ss"), 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) { for (const x of gapX) {

View file

@ -44,28 +44,31 @@ export default {
/** /**
* Converts a Unix timestamp to a formatted date and time string. * Converts a Unix timestamp to a formatted date and time string.
* @param {number} value - The Unix timestamp to convert. * @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. * @returns {string} The formatted date and time string.
*/ */
unixToDateTime(value) { unixToDateTime(value, timezone) {
return dayjs.unix(value).tz(this.timezone).format("YYYY-MM-DD HH:mm:ss"); 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. * Converts a Unix timestamp to a dayjs object.
* @param {number} value - The Unix timestamp to convert. * @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. * @returns {dayjs.Dayjs} The dayjs object representing the given timestamp.
*/ */
unixToDayjs(value) { unixToDayjs(value, timezone) {
return dayjs.unix(value).tz(this.timezone); return dayjs.unix(value).tz(timezone === "auto" ? this.timezone : timezone);
}, },
/** /**
* Converts the given value to a dayjs object. * Converts the given value to a dayjs object.
* @param {string} value - the value to be converted * @param {string} value - The value to be converted.
* @returns {dayjs.Dayjs} a dayjs object in the timezone of this instance * @param {string} timezone - The timezone to use for the conversion.
* @returns {dayjs.Dayjs} The dayjs object in the timezone of this instance.
*/ */
toDayjs(value) { toDayjs(value, timezone) {
return dayjs.utc(value).tz(this.timezone); return dayjs.utc(value).tz(timezone === "auto" ? this.timezone : timezone);
}, },
/** /**

View file

@ -184,7 +184,7 @@
<div v-if="showPingChartBox" class="shadow-box big-padding text-center ping-chart-wrapper"> <div v-if="showPingChartBox" class="shadow-box big-padding text-center ping-chart-wrapper">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<PingChart :monitor-id="monitor.id" /> <PingChart :monitor-id="monitor.id" :monitor-timezone="monitor.timezone" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -8,6 +8,7 @@
<div class="col-md-6"> <div class="col-md-6">
<h2 class="mb-2">{{ $t("General") }}</h2> <h2 class="mb-2">{{ $t("General") }}</h2>
<!-- Monitor Type -->
<div class="my-3"> <div class="my-3">
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label> <label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
<select id="type" v-model="monitor.type" class="form-select" data-testid="monitor-type-select"> <select id="type" v-model="monitor.type" class="form-select" data-testid="monitor-type-select">
@ -563,6 +564,23 @@
<input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1"> <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1">
</div> </div>
<!-- Monitor Timezone -->
<div class="my-3">
<label for="timezone" class="form-label">{{ $t("Monitor Timezone") }}</label>
<select id="timezone" v-model="monitor.timezone" class="form-select">
<option value="auto">
{{ $t("Default") }}: {{ getUserTimezone }}
</option>
<option
v-for="(timezone, index) in timezoneList"
:key="index"
:value="timezone.value"
>
{{ timezone.name }}
</option>
</select>
</div>
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2> <h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check" :title="monitor.ignoreTls ? $t('ignoredTLSError') : ''"> <div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check" :title="monitor.ignoreTls ? $t('ignoredTLSError') : ''">
@ -1077,6 +1095,8 @@ import { hostNameRegexPattern } from "../util-frontend";
import HiddenInput from "../components/HiddenInput.vue"; import HiddenInput from "../components/HiddenInput.vue";
import EditMonitorConditions from "../components/EditMonitorConditions.vue"; import EditMonitorConditions from "../components/EditMonitorConditions.vue";
import { version } from "../../package.json"; import { version } from "../../package.json";
import dayjs from "dayjs";
import { timezoneList } from "../util-frontend";
const userAgent = `'Uptime-Kuma/${version}'`; const userAgent = `'Uptime-Kuma/${version}'`;
const toast = useToast(); const toast = useToast();
@ -1122,7 +1142,8 @@ const monitorDefaults = {
kafkaProducerAllowAutoTopicCreation: false, kafkaProducerAllowAutoTopicCreation: false,
gamedigGivenPortOnly: true, gamedigGivenPortOnly: true,
remote_browser: null, remote_browser: null,
conditions: [] conditions: [],
timezone: "auto"
}; };
export default { export default {
@ -1165,6 +1186,7 @@ export default {
}, },
draftGroupName: null, draftGroupName: null,
remoteBrowsersEnabled: false, remoteBrowsersEnabled: false,
timezoneList: timezoneList(),
}; };
}, },
@ -1217,6 +1239,18 @@ export default {
return command.join(" "); return command.join(" ");
}, },
getUserTimezone() {
let timezone = localStorage.timezone;
if (timezone === null || timezone === "auto") {
timezone = this.guessTimezone;
}
return timezone;
},
guessTimezone() {
return dayjs.tz.guess();
},
ipRegex() { ipRegex() {
// Allow to test with simple dns server with port (127.0.0.1:5300) // Allow to test with simple dns server with port (127.0.0.1:5300)