mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 14:54:05 +00:00
Compare commits
3 commits
4dde5a9234
...
13dea826e2
Author | SHA1 | Date | |
---|---|---|---|
|
13dea826e2 | ||
|
a7e9bdd43e | ||
|
a6610340a5 |
8 changed files with 123 additions and 47 deletions
12
db/knex_migrations/2024-10-16-0000-timezones.js
Normal file
12
db/knex_migrations/2024-10-16-0000-timezones.js
Normal 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");
|
||||
});
|
||||
};
|
|
@ -154,6 +154,7 @@ class Monitor extends BeanModel {
|
|||
jsonPathOperator: this.jsonPathOperator,
|
||||
snmpVersion: this.snmpVersion,
|
||||
conditions: JSON.parse(this.conditions),
|
||||
timezone: this.timezone,
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -4,11 +4,17 @@
|
|||
<div
|
||||
v-for="(beat, index) in shortBeatList"
|
||||
:key="index"
|
||||
class="beat"
|
||||
:class="{ 'empty': (beat === 0), 'down': (beat.status === 0), 'pending': (beat.status === 2), 'maintenance': (beat.status === 3) }"
|
||||
:style="beatStyle"
|
||||
class="beat-hover-area"
|
||||
:class="{ 'empty': (beat === 0) }"
|
||||
:style="beatHoverAreaStyle"
|
||||
:title="getBeatTitle(beat)"
|
||||
/>
|
||||
>
|
||||
<div
|
||||
class="beat"
|
||||
:class="{ 'empty': (beat === 0), 'down': (beat.status === 0), 'pending': (beat.status === 2), 'maintenance': (beat.status === 3) }"
|
||||
:style="beatStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!$root.isMobile && size !== 'small' && beatList.length > 4 && $root.styleElapsedTime !== 'none'"
|
||||
|
@ -47,7 +53,7 @@ export default {
|
|||
beatWidth: 10,
|
||||
beatHeight: 30,
|
||||
hoverScale: 1.5,
|
||||
beatMargin: 4,
|
||||
beatHoverAreaPadding: 4,
|
||||
move: false,
|
||||
maxBeat: -1,
|
||||
};
|
||||
|
@ -123,7 +129,7 @@ export default {
|
|||
|
||||
barStyle() {
|
||||
if (this.move && this.shortBeatList.length > this.maxBeat) {
|
||||
let width = -(this.beatWidth + this.beatMargin * 2);
|
||||
let width = -(this.beatWidth + this.beatHoverAreaPadding * 2);
|
||||
|
||||
return {
|
||||
transition: "all ease-in-out 0.25s",
|
||||
|
@ -137,12 +143,17 @@ export default {
|
|||
|
||||
},
|
||||
|
||||
beatHoverAreaStyle() {
|
||||
return {
|
||||
padding: this.beatHoverAreaPadding + "px",
|
||||
"--hover-scale": this.hoverScale,
|
||||
};
|
||||
},
|
||||
|
||||
beatStyle() {
|
||||
return {
|
||||
width: this.beatWidth + "px",
|
||||
height: this.beatHeight + "px",
|
||||
margin: this.beatMargin + "px",
|
||||
"--hover-scale": this.hoverScale,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -152,7 +163,7 @@ export default {
|
|||
*/
|
||||
timeStyle() {
|
||||
return {
|
||||
"margin-left": this.numPadding * (this.beatWidth + this.beatMargin * 2) + "px",
|
||||
"margin-left": this.numPadding * (this.beatWidth + this.beatHoverAreaPadding * 2) + "px",
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -219,20 +230,20 @@ export default {
|
|||
if (this.size !== "big") {
|
||||
this.beatWidth = 5;
|
||||
this.beatHeight = 16;
|
||||
this.beatMargin = 2;
|
||||
this.beatHoverAreaPadding = 2;
|
||||
}
|
||||
|
||||
// Suddenly, have an idea how to handle it universally.
|
||||
// If the pixel * ratio != Integer, then it causes render issue, round it to solve it!!
|
||||
const actualWidth = this.beatWidth * window.devicePixelRatio;
|
||||
const actualMargin = this.beatMargin * window.devicePixelRatio;
|
||||
const actualHoverAreaPadding = this.beatHoverAreaPadding * window.devicePixelRatio;
|
||||
|
||||
if (!Number.isInteger(actualWidth)) {
|
||||
this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio;
|
||||
}
|
||||
|
||||
if (!Number.isInteger(actualMargin)) {
|
||||
this.beatMargin = Math.round(actualMargin) / window.devicePixelRatio;
|
||||
if (!Number.isInteger(actualHoverAreaPadding)) {
|
||||
this.beatHoverAreaPadding = Math.round(actualHoverAreaPadding) / window.devicePixelRatio;
|
||||
}
|
||||
|
||||
window.addEventListener("resize", this.resize);
|
||||
|
@ -245,7 +256,7 @@ export default {
|
|||
*/
|
||||
resize() {
|
||||
if (this.$refs.wrap) {
|
||||
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2));
|
||||
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatHoverAreaPadding * 2));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -273,32 +284,41 @@ export default {
|
|||
}
|
||||
|
||||
.hp-bar-big {
|
||||
.beat {
|
||||
.beat-hover-area {
|
||||
display: inline-block;
|
||||
background-color: $primary;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&.empty {
|
||||
background-color: aliceblue;
|
||||
}
|
||||
|
||||
&.down {
|
||||
background-color: $danger;
|
||||
}
|
||||
|
||||
&.pending {
|
||||
background-color: $warning;
|
||||
}
|
||||
|
||||
&.maintenance {
|
||||
background-color: $maintenance;
|
||||
}
|
||||
|
||||
&:not(.empty):hover {
|
||||
transition: all ease-in-out 0.15s;
|
||||
opacity: 0.8;
|
||||
transform: scale(var(--hover-scale));
|
||||
}
|
||||
|
||||
.beat {
|
||||
background-color: $primary;
|
||||
border-radius: $border-radius;
|
||||
|
||||
/*
|
||||
pointer-events needs to be changed because
|
||||
tooltip momentarily disappears when crossing between .beat-hover-area and .beat
|
||||
*/
|
||||
pointer-events: none;
|
||||
|
||||
&.empty {
|
||||
background-color: aliceblue;
|
||||
}
|
||||
|
||||
&.down {
|
||||
background-color: $danger;
|
||||
}
|
||||
|
||||
&.pending {
|
||||
background-color: $warning;
|
||||
}
|
||||
|
||||
&.maintenance {
|
||||
background-color: $maintenance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -184,7 +184,7 @@
|
|||
<div v-if="showPingChartBox" class="shadow-box big-padding text-center ping-chart-wrapper">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<PingChart :monitor-id="monitor.id" />
|
||||
<PingChart :monitor-id="monitor.id" :monitor-timezone="monitor.timezone" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<div class="col-md-6">
|
||||
<h2 class="mb-2">{{ $t("General") }}</h2>
|
||||
|
||||
<!-- Monitor Type -->
|
||||
<div class="my-3">
|
||||
<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">
|
||||
|
@ -563,6 +564,23 @@
|
|||
<input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1">
|
||||
</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>
|
||||
|
||||
<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 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)
|
||||
|
|
Loading…
Reference in a new issue