mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-27 16:54:04 +00:00
WIP
This commit is contained in:
parent
02291730fe
commit
227cec86a8
9 changed files with 241 additions and 227 deletions
|
@ -1,6 +1,8 @@
|
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
DROP TABLE maintenance_timeslot;
|
||||
|
||||
-- 999 characters. https://stackoverflow.com/questions/46134830/maximum-length-for-cron-job
|
||||
ALTER TABLE maintenance ADD cron TEXT;
|
||||
ALTER TABLE maintenance ADD timezone VARCHAR(255);
|
||||
|
|
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -88,6 +88,7 @@
|
|||
"chartjs-adapter-dayjs": "~1.0.0",
|
||||
"concurrently": "^7.1.0",
|
||||
"core-js": "~3.26.1",
|
||||
"cronstrue": "~2.24.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"cypress": "^10.1.0",
|
||||
"delay": "^5.0.0",
|
||||
|
@ -7252,6 +7253,15 @@
|
|||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cronstrue": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.24.0.tgz",
|
||||
"integrity": "sha512-A1of24mAGz+OWrdGsxT9BOnDqn2ba182hie8Jx0UcEC2t+ZKtfAJxaFntKUgR7sIisU297fgHBSlNhMIfvAkSA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"cronstrue": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
"chartjs-adapter-dayjs": "~1.0.0",
|
||||
"concurrently": "^7.1.0",
|
||||
"core-js": "~3.26.1",
|
||||
"cronstrue": "~2.24.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"cypress": "^10.1.0",
|
||||
"delay": "^5.0.0",
|
||||
|
|
|
@ -9,9 +9,6 @@ const apicache = require("../modules/apicache");
|
|||
|
||||
class Maintenance extends BeanModel {
|
||||
|
||||
static statusList = {};
|
||||
static jobList = {};
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
|
@ -47,16 +44,41 @@ class Maintenance extends BeanModel {
|
|||
timeslotList: [],
|
||||
cron: this.cron,
|
||||
duration: this.duration,
|
||||
durationMinutes: parseInt(this.duration / 60),
|
||||
timezone: await this.getTimezone(),
|
||||
timezoneOffset: await this.getTimezoneOffset(),
|
||||
status: await this.getStatus(),
|
||||
};
|
||||
|
||||
if (this.strategy === "single") {
|
||||
if (this.strategy === "manual") {
|
||||
// Do nothing, no timeslots
|
||||
} else if (this.strategy === "single") {
|
||||
obj.timeslotList.push({
|
||||
startDate: this.start_date,
|
||||
endDate: this.end_date,
|
||||
});
|
||||
} else {
|
||||
// Should be cron or recurring here
|
||||
if (this.beanMeta.job) {
|
||||
let runningTimeslot = this.getRunningTimeslot();
|
||||
|
||||
if (runningTimeslot) {
|
||||
obj.timeslotList.push(runningTimeslot);
|
||||
}
|
||||
|
||||
let nextRunDate = this.beanMeta.job.nextRun();
|
||||
if (nextRunDate) {
|
||||
let startDateDayjs = dayjs(nextRunDate);
|
||||
|
||||
let startDate = startDateDayjs.toISOString();
|
||||
let endDate = startDateDayjs.add(this.duration, "second").toISOString();
|
||||
|
||||
obj.timeslotList.push({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(obj.weekdays)) {
|
||||
|
@ -93,7 +115,7 @@ class Maintenance extends BeanModel {
|
|||
|
||||
/**
|
||||
* Get a list of days in month that maintenance is active for
|
||||
* @returns {number[]} Array of active days in month
|
||||
* @returns {number[]|string[]} Array of active days in month
|
||||
*/
|
||||
getDayOfMonthList() {
|
||||
return JSON.parse(this.days_of_month).sort(function (a, b) {
|
||||
|
@ -130,7 +152,6 @@ class Maintenance extends BeanModel {
|
|||
bean.strategy = obj.strategy;
|
||||
bean.interval_day = obj.intervalDay;
|
||||
bean.timezone = obj.timezone;
|
||||
bean.duration = obj.duration;
|
||||
bean.active = obj.active;
|
||||
|
||||
if (obj.dateRange[0]) {
|
||||
|
@ -141,13 +162,18 @@ class Maintenance extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
|
||||
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
|
||||
if (bean.strategy === "cron") {
|
||||
bean.duration = obj.durationMinutes * 60;
|
||||
bean.cron = obj.cron;
|
||||
}
|
||||
|
||||
bean.weekdays = JSON.stringify(obj.weekdays);
|
||||
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
|
||||
|
||||
await bean.generateCron();
|
||||
if (bean.strategy.startsWith("recurring-")) {
|
||||
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
|
||||
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
|
||||
bean.weekdays = JSON.stringify(obj.weekdays);
|
||||
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
|
||||
await bean.generateCron();
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
@ -155,8 +181,8 @@ class Maintenance extends BeanModel {
|
|||
/**
|
||||
* Run the cron
|
||||
*/
|
||||
async run() {
|
||||
if (Maintenance.jobList[this.id]) {
|
||||
async run(throwError = false) {
|
||||
if (this.beanMeta.job) {
|
||||
log.debug("maintenance", "Maintenance is already running, stop it first. id: " + this.id);
|
||||
this.stop();
|
||||
}
|
||||
|
@ -165,28 +191,106 @@ class Maintenance extends BeanModel {
|
|||
|
||||
// 1.21.2 migration
|
||||
if (!this.cron) {
|
||||
//this.generateCron();
|
||||
//this.timezone = "UTC";
|
||||
// this.duration =
|
||||
await this.generateCron();
|
||||
if (!this.timezone) {
|
||||
this.timezone = "UTC";
|
||||
}
|
||||
if (this.cron) {
|
||||
//await R.store(this);
|
||||
await R.store(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.strategy === "single") {
|
||||
Maintenance.jobList[this.id] = new Cron(this.start_date, { timezone: await this.getTimezone() }, () => {
|
||||
if (this.strategy === "manual") {
|
||||
// Do nothing, because it is controlled by the user
|
||||
} else if (this.strategy === "single") {
|
||||
this.beanMeta.job = new Cron(this.start_date, { timezone: await this.getTimezone() }, () => {
|
||||
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
apicache.clear();
|
||||
});
|
||||
}
|
||||
} else if (this.cron != null) {
|
||||
// Here should be cron or recurring
|
||||
try {
|
||||
this.beanMeta.status = "scheduled";
|
||||
|
||||
let startEvent = (customDuration = 0) => {
|
||||
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||
|
||||
this.beanMeta.status = "under-maintenance";
|
||||
clearTimeout(this.beanMeta.durationTimeout);
|
||||
|
||||
// Check if duration is still in the window. If not, use the duration from the current time to the end of the window
|
||||
let duration;
|
||||
|
||||
if (customDuration > 0) {
|
||||
duration = customDuration;
|
||||
} else if (this.end_date) {
|
||||
let d = dayjs(this.end_date).diff(dayjs(), "second");
|
||||
if (d < this.duration) {
|
||||
duration = d * 1000;
|
||||
}
|
||||
} else {
|
||||
duration = this.duration * 1000;
|
||||
}
|
||||
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
|
||||
this.beanMeta.durationTimeout = setTimeout(() => {
|
||||
// End of maintenance for this timeslot
|
||||
this.beanMeta.status = "scheduled";
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
}, duration);
|
||||
};
|
||||
|
||||
// Create Cron
|
||||
this.beanMeta.job = new Cron(this.cron, {
|
||||
timezone: await this.getTimezone(),
|
||||
}, startEvent);
|
||||
|
||||
// Continue if the maintenance is still in the window
|
||||
let runningTimeslot = this.getRunningTimeslot();
|
||||
let current = dayjs();
|
||||
|
||||
if (runningTimeslot) {
|
||||
let duration = dayjs(runningTimeslot.endDate).diff(current, "second") * 1000;
|
||||
log.debug("maintenance", "Maintenance id: " + this.id + " Remaining duration: " + duration + "ms");
|
||||
startEvent(duration);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log.error("maintenance", "Error in maintenance id: " + this.id);
|
||||
log.error("maintenance", "Cron: " + this.cron);
|
||||
log.error("maintenance", e);
|
||||
|
||||
if (throwError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.error("maintenance", "Maintenance id: " + this.id + " has no cron");
|
||||
}
|
||||
}
|
||||
|
||||
getRunningTimeslot() {
|
||||
let start = dayjs(this.beanMeta.job.nextRun(dayjs().add(-this.duration, "second").format("YYYY-MM-DD HH:mm:ss")));
|
||||
let end = start.add(this.duration, "second");
|
||||
let current = dayjs();
|
||||
|
||||
if (current.isAfter(start) && current.isBefore(end)) {
|
||||
return {
|
||||
startDate: start.toISOString(),
|
||||
endDate: end.toISOString(),
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (Maintenance.jobList[this.id]) {
|
||||
Maintenance.jobList[this.id].stop();
|
||||
delete Maintenance.jobList[this.id];
|
||||
if (this.beanMeta.job) {
|
||||
this.beanMeta.job.stop();
|
||||
delete this.beanMeta.job;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,21 +332,25 @@ class Maintenance extends BeanModel {
|
|||
return "under-maintenance";
|
||||
}
|
||||
|
||||
if (!Maintenance.statusList[this.id]) {
|
||||
Maintenance.statusList[this.id] = "unknown";
|
||||
if (!this.beanMeta.status) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
return Maintenance.statusList[this.id];
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
Maintenance.statusList[this.id] = status;
|
||||
return this.beanMeta.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Cron for recurring maintenance
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async generateCron() {
|
||||
log.info("maintenance", "Generate cron for maintenance id: " + this.id);
|
||||
|
||||
if (this.strategy === "recurring-interval") {
|
||||
if (this.strategy === "cron") {
|
||||
// Do nothing for cron
|
||||
} else if (!this.strategy.startsWith("recurring-")) {
|
||||
this.cron = "";
|
||||
} else if (this.strategy === "recurring-interval") {
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
|
@ -250,6 +358,37 @@ class Maintenance extends BeanModel {
|
|||
this.duration = this.calcDuration();
|
||||
log.debug("maintenance", "Cron: " + this.cron);
|
||||
log.debug("maintenance", "Duration: " + this.duration);
|
||||
} else if (this.strategy === "recurring-weekday") {
|
||||
let list = this.getDayOfWeekList();
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
this.cron = minute + " " + hour + " * * " + list.join(",");
|
||||
this.duration = this.calcDuration();
|
||||
} else if (this.strategy === "recurring-day-of-month") {
|
||||
let list = this.getDayOfMonthList();
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
|
||||
let dayList = [];
|
||||
|
||||
for (let day of list) {
|
||||
if (typeof day === "string" && day.startsWith("lastDay")) {
|
||||
if (day === "lastDay1") {
|
||||
dayList.push("L");
|
||||
}
|
||||
// Unfortunately, lastDay2-4 is not supported by cron
|
||||
} else {
|
||||
dayList.push(day);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate
|
||||
dayList = [ ...new Set(dayList) ];
|
||||
|
||||
this.cron = minute + " " + hour + " " + dayList.join(",") + " * *";
|
||||
this.duration = this.calcDuration();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { R } = require("redbean-node");
|
||||
const dayjs = require("dayjs");
|
||||
const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND, localToUTC } = require("../../src/util");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
|
||||
class MaintenanceTimeslot extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON() {
|
||||
const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||
|
||||
const obj = {
|
||||
id: this.id,
|
||||
startDate: this.start_date,
|
||||
endDate: this.end_date,
|
||||
startDateServerTimezone: utcToLocal(this.start_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
||||
endDateServerTimezone: utcToLocal(this.end_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
||||
serverTimezoneOffset,
|
||||
};
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toJSON() {
|
||||
return await this.toPublicJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Maintenance} maintenance
|
||||
* @param {dayjs} minDate (For recurring type only) Generate a next timeslot from this date.
|
||||
* @param {boolean} removeExist Remove existing timeslot before create
|
||||
* @returns {Promise<MaintenanceTimeslot>}
|
||||
*/
|
||||
static async generateTimeslot(maintenance, minDate = null, removeExist = false) {
|
||||
log.info("maintenance", "Generate Timeslot for maintenance id: " + maintenance.id);
|
||||
|
||||
if (removeExist) {
|
||||
await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [
|
||||
maintenance.id
|
||||
]);
|
||||
}
|
||||
|
||||
if (maintenance.strategy === "manual") {
|
||||
log.debug("maintenance", "No need to generate timeslot for manual type");
|
||||
|
||||
} else if (maintenance.strategy === "single") {
|
||||
let bean = R.dispense("maintenance_timeslot");
|
||||
bean.maintenance_id = maintenance.id;
|
||||
bean.start_date = maintenance.start_date;
|
||||
bean.end_date = maintenance.end_date;
|
||||
bean.generated_next = true;
|
||||
|
||||
if (!await this.isDuplicateTimeslot(bean)) {
|
||||
await R.store(bean);
|
||||
return bean;
|
||||
} else {
|
||||
log.debug("maintenance", "Duplicate timeslot, skip");
|
||||
return null;
|
||||
}
|
||||
|
||||
} else if (maintenance.strategy === "recurring-interval") {
|
||||
// Prevent dead loop, in case interval_day is not set
|
||||
if (!maintenance.interval_day || maintenance.interval_day <= 0) {
|
||||
maintenance.interval_day = 1;
|
||||
}
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
return startDateTime.add(maintenance.interval_day, "day");
|
||||
}, () => {
|
||||
return true;
|
||||
});
|
||||
|
||||
} else if (maintenance.strategy === "recurring-weekday") {
|
||||
let dayOfWeekList = maintenance.getDayOfWeekList();
|
||||
log.debug("timeslot", dayOfWeekList);
|
||||
|
||||
if (dayOfWeekList.length <= 0) {
|
||||
log.debug("timeslot", "No weekdays selected?");
|
||||
return null;
|
||||
}
|
||||
|
||||
const isValid = (startDateTime) => {
|
||||
log.debug("timeslot", "nextDateTime: " + startDateTime);
|
||||
|
||||
let day = startDateTime.local().day();
|
||||
log.debug("timeslot", "nextDateTime.day(): " + day);
|
||||
|
||||
return dayOfWeekList.includes(day);
|
||||
};
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
while (true) {
|
||||
startDateTime = startDateTime.add(1, "day");
|
||||
|
||||
if (isValid(startDateTime)) {
|
||||
return startDateTime;
|
||||
}
|
||||
}
|
||||
}, isValid);
|
||||
|
||||
} else if (maintenance.strategy === "recurring-day-of-month") {
|
||||
let dayOfMonthList = maintenance.getDayOfMonthList();
|
||||
if (dayOfMonthList.length <= 0) {
|
||||
log.debug("timeslot", "No day selected?");
|
||||
return null;
|
||||
}
|
||||
|
||||
const isValid = (startDateTime) => {
|
||||
let day = parseInt(startDateTime.local().format("D"));
|
||||
|
||||
log.debug("timeslot", "day: " + day);
|
||||
|
||||
// Check 1-31
|
||||
if (dayOfMonthList.includes(day)) {
|
||||
return startDateTime;
|
||||
}
|
||||
|
||||
// Check "lastDay1","lastDay2"...
|
||||
let daysInMonth = startDateTime.daysInMonth();
|
||||
let lastDayList = [];
|
||||
|
||||
// Small first, e.g. 28 > 29 > 30 > 31
|
||||
for (let i = 4; i >= 1; i--) {
|
||||
if (dayOfMonthList.includes("lastDay" + i)) {
|
||||
lastDayList.push(daysInMonth - i + 1);
|
||||
}
|
||||
}
|
||||
log.debug("timeslot", lastDayList);
|
||||
return lastDayList.includes(day);
|
||||
};
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
while (true) {
|
||||
startDateTime = startDateTime.add(1, "day");
|
||||
if (isValid(startDateTime)) {
|
||||
return startDateTime;
|
||||
}
|
||||
}
|
||||
}, isValid);
|
||||
} else {
|
||||
throw new Error("Unknown maintenance strategy");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MaintenanceTimeslot;
|
|
@ -23,7 +23,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
|||
let maintenanceID = await R.store(bean);
|
||||
|
||||
server.maintenanceList[maintenanceID] = bean;
|
||||
bean.run();
|
||||
await bean.run(true);
|
||||
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
|
@ -54,7 +54,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
|||
|
||||
await Maintenance.jsonToBean(bean, maintenance);
|
||||
await R.store(bean);
|
||||
await bean.run();
|
||||
await bean.run(true);
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
callback({
|
||||
|
@ -274,7 +274,6 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
|||
}
|
||||
|
||||
maintenance.active = false;
|
||||
maintenance.setStatus("inactive");
|
||||
await R.store(maintenance);
|
||||
maintenance.stop();
|
||||
|
||||
|
|
|
@ -3,16 +3,23 @@
|
|||
<div v-if="maintenance.strategy === 'manual'" class="timeslot">
|
||||
{{ $t("Manual") }}
|
||||
</div>
|
||||
<div v-else-if="maintenance.timeslotList.length > 0" class="timeslot">
|
||||
{{ maintenance.timeslotList[0].startDate }}
|
||||
<span class="to">-</span>
|
||||
{{ maintenance.timeslotList[0].endDate }}
|
||||
(UTC{{ maintenance.timezoneOffset }})
|
||||
<div v-else-if="maintenance.timeslotList.length > 0">
|
||||
<div class="timeslot">
|
||||
{{ startDateTime }}
|
||||
<span class="to">-</span>
|
||||
{{ endDateTime }}
|
||||
</div>
|
||||
<div class="timeslot">
|
||||
UTC{{ maintenance.timezoneOffset }} {{ maintenance.timezone }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from "dayjs";
|
||||
import { SQL_DATETIME_FORMAT_WITHOUT_SECOND } from "../util.ts";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
maintenance: {
|
||||
|
@ -20,6 +27,14 @@ export default {
|
|||
required: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
startDateTime() {
|
||||
return dayjs(this.maintenance.timeslotList[0].startDate).tz(this.maintenance.timezone).format(SQL_DATETIME_FORMAT_WITHOUT_SECOND);
|
||||
},
|
||||
endDateTime() {
|
||||
return dayjs(this.maintenance.timeslotList[0].endDate).tz(this.maintenance.timezone).format(SQL_DATETIME_FORMAT_WITHOUT_SECOND);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -31,6 +46,7 @@ export default {
|
|||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 20px;
|
||||
padding: 0 10px;
|
||||
margin-right: 5px;
|
||||
|
||||
.to {
|
||||
margin: 0 6px;
|
||||
|
|
|
@ -433,7 +433,7 @@
|
|||
"dnsCacheDescription": "It may be not working in some IPv6 environments, disable it if you encounter any issues.",
|
||||
"Single Maintenance Window": "Single Maintenance Window",
|
||||
"Maintenance Time Window of a Day": "Maintenance Time Window of a Day",
|
||||
"Effective Date Range": "Effective Date Range",
|
||||
"Effective Date Range": "Effective Date Range (Optional)",
|
||||
"Schedule Maintenance": "Schedule Maintenance",
|
||||
"Date and Time": "Date and Time",
|
||||
"DateTime Range": "DateTime Range",
|
||||
|
|
|
@ -95,7 +95,6 @@
|
|||
<option value="recurring-interval">{{ $t("Recurring") }} - {{ $t("recurringInterval") }}</option>
|
||||
<option value="recurring-weekday">{{ $t("Recurring") }} - {{ $t("dayOfWeek") }}</option>
|
||||
<option value="recurring-day-of-month">{{ $t("Recurring") }} - {{ $t("dayOfMonth") }}</option>
|
||||
<option v-if="false" value="recurring-day-of-year">{{ $t("Recurring") }} - Day of Year</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
@ -103,6 +102,25 @@
|
|||
<template v-if="maintenance.strategy === 'single'">
|
||||
</template>
|
||||
|
||||
<template v-if="maintenance.strategy === 'cron'">
|
||||
<!-- Cron -->
|
||||
<div class="my-3">
|
||||
<label for="cron" class="form-label">
|
||||
{{ $t("cronExpression") }}
|
||||
</label>
|
||||
<p>Run: {{ cronDescription }}</p>
|
||||
<input id="cron" v-model="maintenance.cron" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<!-- Duration -->
|
||||
<label for="duration" class="form-label">
|
||||
{{ $t("Duration (Minutes)") }}
|
||||
</label>
|
||||
<input id="duration" v-model="maintenance.durationMinutes" type="number" class="form-control" required min="1" step="1">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Recurring - Interval -->
|
||||
<template v-if="maintenance.strategy === 'recurring-interval'">
|
||||
<div class="my-3">
|
||||
|
@ -205,26 +223,12 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">{{ $t("startDateTime") }}</div>
|
||||
<Datepicker
|
||||
v-model="maintenance.dateRange[0]"
|
||||
:dark="$root.isDark"
|
||||
datePicker
|
||||
:monthChangeOnScroll="false"
|
||||
format="yyyy-MM-dd HH:mm:ss"
|
||||
modelType="yyyy-MM-dd HH:mm:ss"
|
||||
/>
|
||||
<input v-model="maintenance.dateRange[0]" type="datetime-local" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="mb-2">{{ $t("endDateTime") }}</div>
|
||||
<Datepicker
|
||||
v-model="maintenance.dateRange[1]"
|
||||
:dark="$root.isDark"
|
||||
datePicker
|
||||
:monthChangeOnScroll="false"
|
||||
format="yyyy-MM-dd HH:mm:ss"
|
||||
modelType="yyyy-MM-dd HH:mm:ss"
|
||||
/>
|
||||
<input v-model="maintenance.dateRange[1]" type="datetime-local" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -247,12 +251,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { useToast } from "vue-toastification";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import dayjs from "dayjs";
|
||||
import Datepicker from "@vuepic/vue-datepicker";
|
||||
import { timezoneList } from "../util-frontend";
|
||||
import cronstrue from "cronstrue";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
|
@ -279,18 +283,6 @@ export default {
|
|||
langKey: "lastDay1",
|
||||
value: "lastDay1",
|
||||
},
|
||||
{
|
||||
langKey: "lastDay2",
|
||||
value: "lastDay2",
|
||||
},
|
||||
{
|
||||
langKey: "lastDay3",
|
||||
value: "lastDay3",
|
||||
},
|
||||
{
|
||||
langKey: "lastDay4",
|
||||
value: "lastDay4",
|
||||
}
|
||||
],
|
||||
weekdays: [
|
||||
{
|
||||
|
@ -334,6 +326,15 @@ export default {
|
|||
|
||||
computed: {
|
||||
|
||||
cronDescription() {
|
||||
if (! this.maintenance.cron) {
|
||||
return "";
|
||||
}
|
||||
return cronstrue.toString(this.maintenance.cron, {
|
||||
locale: "zh-TW",
|
||||
});
|
||||
},
|
||||
|
||||
selectedStatusPagesOptions() {
|
||||
return Object.values(this.$root.statusPageList).map(statusPage => {
|
||||
return {
|
||||
|
@ -393,6 +394,8 @@ export default {
|
|||
description: "",
|
||||
strategy: "single",
|
||||
active: 1,
|
||||
cron: "30 3 * * *",
|
||||
durationMinutes: 60,
|
||||
intervalDay: 1,
|
||||
dateRange: [ this.minDate ],
|
||||
timeRange: [{
|
||||
|
|
Loading…
Reference in a new issue