mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-01-24 15:43:53 +08:00
feat(maintenance): add quick duration buttons and pre-fill datetime fields (#6718)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
@@ -36,6 +36,9 @@ export default defineConfig({
|
||||
srcDir: "src",
|
||||
filename: "serviceWorker.ts",
|
||||
strategies: "injectManifest",
|
||||
injectManifest: {
|
||||
maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // 3 MiB
|
||||
},
|
||||
}),
|
||||
],
|
||||
css: {
|
||||
|
||||
@@ -85,12 +85,12 @@ export default {
|
||||
|
||||
title() {
|
||||
if (this.type === "1y") {
|
||||
return `1 ${this.$tc("year", 1)}`;
|
||||
return this.$tc("years", 1, { n: 1 });
|
||||
}
|
||||
if (this.type === "720") {
|
||||
return `30 ${this.$tc("day", 30)}`;
|
||||
return this.$tc("days", 30, { n: 30 });
|
||||
}
|
||||
return `24 ${this.$tc("hour", 24)}`;
|
||||
return this.$tc("hours", 24, { n: 24 });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<ActionInput
|
||||
v-model="tlsExpiryNotifInput"
|
||||
:type="'number'"
|
||||
:placeholder="$t('day')"
|
||||
:placeholder="$tc('days', 1, { n: 1 })"
|
||||
:icon="'plus'"
|
||||
:action="() => addTlsExpiryNotifDay(tlsExpiryNotifInput)"
|
||||
:action-aria-label="$t('Add a new expiry notification day')"
|
||||
@@ -117,7 +117,7 @@
|
||||
<ActionInput
|
||||
v-model="domainExpiryNotifInput"
|
||||
:type="'number'"
|
||||
:placeholder="$t('day')"
|
||||
:placeholder="$tc('days', 1, { n: 1 })"
|
||||
:icon="'plus'"
|
||||
:action="() => addDomainExpiryNotifDay(domainExpiryNotifInput)"
|
||||
:action-aria-label="$t('Add a new expiry notification day')"
|
||||
|
||||
@@ -55,9 +55,11 @@
|
||||
"Monitor": "Monitor | Monitors",
|
||||
"now": "now",
|
||||
"time ago": "{0} ago",
|
||||
"day": "day | days",
|
||||
"hour": "hour | hours",
|
||||
"year": "year | years",
|
||||
"days": "{n} day | {n} days",
|
||||
"hours": "{n} hour | {n} hours",
|
||||
"minutes": "{n} minute | {n} minutes",
|
||||
"minuteShort": "{n} min | {n} min",
|
||||
"years": "{n} year | {n} years",
|
||||
"Response": "Response",
|
||||
"Ping": "Ping",
|
||||
"Monitor Type": "Monitor Type",
|
||||
@@ -669,6 +671,8 @@
|
||||
"recurringIntervalMessage": "Run once every day | Run once every {0} days",
|
||||
"affectedMonitorsDescription": "Select monitors that are affected by current maintenance",
|
||||
"affectedStatusPages": "Show this maintenance message on selected status pages",
|
||||
"Sets end time based on start time": "Sets end time based on start time",
|
||||
"Please set start time first": "Please set start time first",
|
||||
"noMonitorsSelectedWarning": "You are creating a maintenance without any affected monitors. Are you sure you want to continue?",
|
||||
"noMonitorsOrStatusPagesSelectedError": "Cannot create maintenance without affected monitors or status pages",
|
||||
"passwordNotMatchMsg": "The repeat password does not match.",
|
||||
|
||||
@@ -123,9 +123,6 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Single Maintenance Window -->
|
||||
<template v-if="maintenance.strategy === 'single'"></template>
|
||||
|
||||
<template v-if="maintenance.strategy === 'cron'">
|
||||
<!-- Cron -->
|
||||
<div class="my-3">
|
||||
@@ -331,6 +328,102 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="maintenance.strategy === 'single'">
|
||||
<div class="my-3">
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 15 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 15"
|
||||
@click="setQuickDuration(15)"
|
||||
>
|
||||
{{ $tc("minuteShort", 15, { n: 15 }) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 30 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 30"
|
||||
@click="setQuickDuration(30)"
|
||||
>
|
||||
{{ $tc("minuteShort", 30, { n: 30 }) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 60 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 60"
|
||||
@click="setQuickDuration(60)"
|
||||
>
|
||||
{{ $tc("hours", 1, { n: 1 }) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 120 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 120"
|
||||
@click="setQuickDuration(120)"
|
||||
>
|
||||
{{ $tc("hours", 2, { n: 2 }) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 240 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 240"
|
||||
@click="setQuickDuration(240)"
|
||||
>
|
||||
{{ $tc("hours", 4, { n: 4 }) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 480 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 480"
|
||||
@click="setQuickDuration(480)"
|
||||
>
|
||||
{{ $tc("hours", 8, { n: 8 }) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 720 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 720"
|
||||
@click="setQuickDuration(720)"
|
||||
>
|
||||
{{ $tc("hours", 12, { n: 12 }) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="
|
||||
currentDurationMinutes === 1440 ? 'btn-primary' : 'btn-outline-primary'
|
||||
"
|
||||
:disabled="currentDurationMinutes === 1440"
|
||||
@click="setQuickDuration(1440)"
|
||||
>
|
||||
{{ $tc("hours", 24, { n: 24 }) }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">{{ $t("Sets end time based on start time") }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -511,6 +604,22 @@ export default {
|
||||
hasStatusPages() {
|
||||
return this.showOnAllPages || this.selectedStatusPages.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate the current duration in minutes between start and end dates
|
||||
* @returns {number|null} Duration in minutes, or null if dates are invalid
|
||||
*/
|
||||
currentDurationMinutes() {
|
||||
if (!this.maintenance.dateRange?.[0] || !this.maintenance.dateRange?.[1]) {
|
||||
return null;
|
||||
}
|
||||
const start = new Date(this.maintenance.dateRange[0]);
|
||||
const end = new Date(this.maintenance.dateRange[1]);
|
||||
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
|
||||
return null;
|
||||
}
|
||||
return Math.round((end.getTime() - start.getTime()) / 60000);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"$route.fullPath"() {
|
||||
@@ -570,6 +679,19 @@ export default {
|
||||
this.selectedStatusPages = [];
|
||||
|
||||
if (this.isAdd) {
|
||||
// Get current date/time in local timezone
|
||||
const now = new Date();
|
||||
const oneHourLater = new Date(now.getTime() + 60 * 60000);
|
||||
|
||||
const formatDateTime = (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
this.maintenance = {
|
||||
title: "",
|
||||
description: "",
|
||||
@@ -578,7 +700,7 @@ export default {
|
||||
cron: "30 3 * * *",
|
||||
durationMinutes: 60,
|
||||
intervalDay: 1,
|
||||
dateRange: [],
|
||||
dateRange: [formatDateTime(now), formatDateTime(oneHourLater)],
|
||||
timeRange: [
|
||||
{
|
||||
hours: 2,
|
||||
@@ -591,7 +713,7 @@ export default {
|
||||
],
|
||||
weekdays: [],
|
||||
daysOfMonth: [],
|
||||
timezoneOption: null,
|
||||
timezoneOption: "SAME_AS_SERVER",
|
||||
};
|
||||
} else if (this.isEdit || this.isClone) {
|
||||
this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => {
|
||||
@@ -655,6 +777,30 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set quick duration for single maintenance
|
||||
* Calculates end time based on start time + duration in minutes
|
||||
* @param {number} minutes Duration in minutes
|
||||
* @returns {void}
|
||||
*/
|
||||
setQuickDuration(minutes) {
|
||||
if (!this.maintenance.dateRange[0]) {
|
||||
this.$root.toastError(this.$t("Please set start time first"));
|
||||
return;
|
||||
}
|
||||
|
||||
const startDate = new Date(this.maintenance.dateRange[0]);
|
||||
const endDate = new Date(startDate.getTime() + minutes * 60000);
|
||||
|
||||
const year = endDate.getFullYear();
|
||||
const month = String(endDate.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(endDate.getDate()).padStart(2, "0");
|
||||
const hours = String(endDate.getHours()).padStart(2, "0");
|
||||
const mins = String(endDate.getMinutes()).padStart(2, "0");
|
||||
|
||||
this.maintenance.dateRange[1] = `${year}-${month}-${day}T${hours}:${mins}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle form submission - show confirmation if no monitors selected
|
||||
* @returns {void}
|
||||
|
||||
Reference in New Issue
Block a user