2022-09-27 08:44:44 -04:00
|
|
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
|
|
|
const { R } = require("redbean-node");
|
|
|
|
const dayjs = require("dayjs");
|
2022-10-12 05:02:16 -04:00
|
|
|
const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND, localToUTC } = require("../../src/util");
|
2022-10-11 06:23:17 -04:00
|
|
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
2022-09-27 08:44:44 -04:00
|
|
|
|
|
|
|
class MaintenanceTimeslot extends BeanModel {
|
|
|
|
|
2023-01-05 17:19:05 -05:00
|
|
|
/**
|
|
|
|
* Return an object that ready to parse to JSON for public
|
|
|
|
* Only show necessary data to public
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2022-09-27 08:44:44 -04:00
|
|
|
async toPublicJSON() {
|
2022-10-11 09:48:43 -04:00
|
|
|
const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset();
|
2022-09-27 08:44:44 -04:00
|
|
|
|
2022-10-11 06:23:17 -04:00
|
|
|
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;
|
2022-09-27 08:44:44 -04:00
|
|
|
}
|
|
|
|
|
2023-01-05 17:19:05 -05:00
|
|
|
/**
|
|
|
|
* Return an object that ready to parse to JSON
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2022-09-27 08:44:44 -04:00
|
|
|
async toJSON() {
|
2022-10-11 06:23:17 -04:00
|
|
|
return await this.toPublicJSON();
|
2022-09-27 08:44:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Maintenance} maintenance
|
2022-10-12 05:02:16 -04:00
|
|
|
* @param {dayjs} minDate (For recurring type only) Generate a next timeslot from this date.
|
2022-09-27 08:44:44 -04:00
|
|
|
* @param {boolean} removeExist Remove existing timeslot before create
|
2022-10-12 05:02:16 -04:00
|
|
|
* @returns {Promise<MaintenanceTimeslot>}
|
2022-09-27 08:44:44 -04:00
|
|
|
*/
|
2022-10-12 05:02:16 -04:00
|
|
|
static async generateTimeslot(maintenance, minDate = null, removeExist = false) {
|
2023-03-07 07:47:57 -05:00
|
|
|
log.info("maintenance", "Generate Timeslot for maintenance id: " + maintenance.id);
|
|
|
|
|
2022-09-27 08:44:44 -04:00
|
|
|
if (removeExist) {
|
|
|
|
await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [
|
|
|
|
maintenance.id
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2022-09-27 12:20:17 -04:00
|
|
|
if (maintenance.strategy === "manual") {
|
|
|
|
log.debug("maintenance", "No need to generate timeslot for manual type");
|
2022-10-15 06:49:09 -04:00
|
|
|
|
2022-09-27 12:20:17 -04:00
|
|
|
} else if (maintenance.strategy === "single") {
|
2022-09-27 08:44:44 -04:00
|
|
|
let bean = R.dispense("maintenance_timeslot");
|
|
|
|
bean.maintenance_id = maintenance.id;
|
2022-09-27 12:48:15 -04:00
|
|
|
bean.start_date = maintenance.start_date;
|
|
|
|
bean.end_date = maintenance.end_date;
|
2022-09-27 08:44:44 -04:00
|
|
|
bean.generated_next = true;
|
2023-03-09 09:03:23 -05:00
|
|
|
|
|
|
|
if (!await this.isDuplicateTimeslot(bean)) {
|
|
|
|
await R.store(bean);
|
|
|
|
return bean;
|
|
|
|
} else {
|
|
|
|
log.debug("maintenance", "Duplicate timeslot, skip");
|
|
|
|
return null;
|
|
|
|
}
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 06:49:09 -04:00
|
|
|
} else if (maintenance.strategy === "recurring-interval") {
|
2022-10-12 05:02:16 -04:00
|
|
|
// Prevent dead loop, in case interval_day is not set
|
|
|
|
if (!maintenance.interval_day || maintenance.interval_day <= 0) {
|
|
|
|
maintenance.interval_day = 1;
|
|
|
|
}
|
|
|
|
|
2022-10-15 06:49:09 -04:00
|
|
|
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
|
|
|
return startDateTime.add(maintenance.interval_day, "day");
|
2022-10-15 08:15:50 -04:00
|
|
|
}, () => {
|
|
|
|
return true;
|
2022-10-15 06:49:09 -04:00
|
|
|
});
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 06:49:09 -04:00
|
|
|
} else if (maintenance.strategy === "recurring-weekday") {
|
|
|
|
let dayOfWeekList = maintenance.getDayOfWeekList();
|
2022-10-15 08:15:50 -04:00
|
|
|
log.debug("timeslot", dayOfWeekList);
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 06:49:09 -04:00
|
|
|
if (dayOfWeekList.length <= 0) {
|
|
|
|
log.debug("timeslot", "No weekdays selected?");
|
|
|
|
return null;
|
2022-10-12 05:02:16 -04:00
|
|
|
}
|
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
const isValid = (startDateTime) => {
|
|
|
|
log.debug("timeslot", "nextDateTime: " + startDateTime);
|
|
|
|
|
|
|
|
let day = startDateTime.local().day();
|
|
|
|
log.debug("timeslot", "nextDateTime.day(): " + day);
|
|
|
|
|
|
|
|
return dayOfWeekList.includes(day);
|
|
|
|
};
|
|
|
|
|
2022-10-15 06:49:09 -04:00
|
|
|
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
|
|
|
while (true) {
|
|
|
|
startDateTime = startDateTime.add(1, "day");
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
if (isValid(startDateTime)) {
|
2022-10-15 06:49:09 -04:00
|
|
|
return startDateTime;
|
|
|
|
}
|
2022-10-12 05:02:16 -04:00
|
|
|
}
|
2022-10-15 08:15:50 -04:00
|
|
|
}, isValid);
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 06:49:09 -04:00
|
|
|
} else if (maintenance.strategy === "recurring-day-of-month") {
|
|
|
|
let dayOfMonthList = maintenance.getDayOfMonthList();
|
|
|
|
if (dayOfMonthList.length <= 0) {
|
|
|
|
log.debug("timeslot", "No day selected?");
|
|
|
|
return null;
|
|
|
|
}
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
const isValid = (startDateTime) => {
|
|
|
|
let day = parseInt(startDateTime.local().format("D"));
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
log.debug("timeslot", "day: " + day);
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
// Check 1-31
|
|
|
|
if (dayOfMonthList.includes(day)) {
|
|
|
|
return startDateTime;
|
|
|
|
}
|
2022-10-12 05:02:16 -04:00
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
// Check "lastDay1","lastDay2"...
|
|
|
|
let daysInMonth = startDateTime.daysInMonth();
|
|
|
|
let lastDayList = [];
|
2022-10-15 06:49:09 -04:00
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
// 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);
|
2022-10-15 06:49:09 -04:00
|
|
|
}
|
2022-10-15 08:15:50 -04:00
|
|
|
}
|
|
|
|
log.debug("timeslot", lastDayList);
|
|
|
|
return lastDayList.includes(day);
|
|
|
|
};
|
2022-10-15 06:49:09 -04:00
|
|
|
|
2022-10-15 08:15:50 -04:00
|
|
|
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
|
|
|
while (true) {
|
|
|
|
startDateTime = startDateTime.add(1, "day");
|
|
|
|
if (isValid(startDateTime)) {
|
2022-10-15 06:49:09 -04:00
|
|
|
return startDateTime;
|
|
|
|
}
|
|
|
|
}
|
2022-10-15 08:15:50 -04:00
|
|
|
}, isValid);
|
2022-09-27 08:44:44 -04:00
|
|
|
} else {
|
|
|
|
throw new Error("Unknown maintenance strategy");
|
|
|
|
}
|
|
|
|
}
|
2022-10-15 06:49:09 -04:00
|
|
|
|
2023-03-09 09:03:23 -05:00
|
|
|
static async isDuplicateTimeslot(timeslot) {
|
|
|
|
let bean = await R.findOne("maintenance_timeslot", "maintenance_id = ? AND start_date = ? AND end_date = ?", [
|
|
|
|
timeslot.maintenance_id,
|
|
|
|
timeslot.start_date,
|
|
|
|
timeslot.end_date
|
|
|
|
]);
|
|
|
|
return bean !== null;
|
|
|
|
}
|
|
|
|
|
2022-10-15 06:49:09 -04:00
|
|
|
/**
|
|
|
|
* Generate a next timeslot for all recurring types
|
|
|
|
* @param maintenance
|
|
|
|
* @param minDate
|
2022-10-15 08:15:50 -04:00
|
|
|
* @param {function} nextDayCallback The logic how to get the next possible day
|
|
|
|
* @param {function} isValidCallback Check the day whether is matched the current strategy
|
2022-10-15 06:49:09 -04:00
|
|
|
* @returns {Promise<null|MaintenanceTimeslot>}
|
|
|
|
*/
|
2022-10-15 08:15:50 -04:00
|
|
|
static async handleRecurringType(maintenance, minDate, nextDayCallback, isValidCallback) {
|
2022-10-15 06:49:09 -04:00
|
|
|
let bean = R.dispense("maintenance_timeslot");
|
|
|
|
|
|
|
|
let duration = maintenance.getDuration();
|
|
|
|
let startDateTime = maintenance.getStartDateTime();
|
|
|
|
let endDateTime;
|
|
|
|
|
|
|
|
// Keep generating from the first possible date, until it is ok
|
|
|
|
while (true) {
|
2023-03-09 09:03:23 -05:00
|
|
|
//log.debug("timeslot", "startDateTime: " + startDateTime.format());
|
2022-10-15 06:49:09 -04:00
|
|
|
|
|
|
|
// Handling out of effective date range
|
|
|
|
if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
|
|
|
log.debug("timeslot", "Out of effective date range");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
endDateTime = startDateTime.add(duration, "second");
|
|
|
|
|
|
|
|
// If endDateTime is out of effective date range, use the end datetime from effective date range
|
|
|
|
if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
|
|
|
endDateTime = dayjs.utc(maintenance.end_date);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If minDate is set, the endDateTime must be bigger than it.
|
|
|
|
// And the endDateTime must be bigger current time
|
2022-10-15 08:15:50 -04:00
|
|
|
// Is valid under current recurring strategy
|
2022-10-15 06:49:09 -04:00
|
|
|
if (
|
|
|
|
(!minDate || endDateTime.diff(minDate) > 0) &&
|
2022-10-15 08:15:50 -04:00
|
|
|
endDateTime.diff(dayjs()) > 0 &&
|
|
|
|
isValidCallback(startDateTime)
|
2022-10-15 06:49:09 -04:00
|
|
|
) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
startDateTime = nextDayCallback(startDateTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
bean.maintenance_id = maintenance.id;
|
|
|
|
bean.start_date = localToUTC(startDateTime);
|
|
|
|
bean.end_date = localToUTC(endDateTime);
|
|
|
|
bean.generated_next = false;
|
2023-03-09 09:03:23 -05:00
|
|
|
|
|
|
|
if (!await this.isDuplicateTimeslot(bean)) {
|
|
|
|
await R.store(bean);
|
|
|
|
return bean;
|
|
|
|
} else {
|
|
|
|
log.debug("maintenance", "Duplicate timeslot, skip");
|
|
|
|
return null;
|
|
|
|
}
|
2022-10-15 06:49:09 -04:00
|
|
|
}
|
2022-09-27 08:44:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = MaintenanceTimeslot;
|