mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-04 21:56:12 +08:00 
			
		
		
		
	Implement recurring day of month and day of week
This commit is contained in:
		@@ -112,6 +112,40 @@ class Maintenance extends BeanModel {
 | 
				
			|||||||
        return this.toPublicJSON(timezone);
 | 
					        return this.toPublicJSON(timezone);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getDayOfWeekList() {
 | 
				
			||||||
 | 
					        log.debug("timeslot", "List: " + this.weekdays);
 | 
				
			||||||
 | 
					        return JSON.parse(this.weekdays).sort(function (a, b) {
 | 
				
			||||||
 | 
					            return a - b;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getDayOfMonthList() {
 | 
				
			||||||
 | 
					        return JSON.parse(this.days_of_month).sort(function (a, b) {
 | 
				
			||||||
 | 
					            return a - b;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getStartDateTime() {
 | 
				
			||||||
 | 
					        let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm");
 | 
				
			||||||
 | 
					        log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Start Time
 | 
				
			||||||
 | 
					        let startTimeSecond = dayjs.utc(this.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
 | 
				
			||||||
 | 
					        log.debug("timeslot", "startTime: " + startTimeSecond);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Bake StartDate + StartTime = Start DateTime
 | 
				
			||||||
 | 
					        return dayjs.utc(this.start_date).add(startTimeSecond, "second");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getDuration() {
 | 
				
			||||||
 | 
					        let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
 | 
				
			||||||
 | 
					        // Add 24hours if it is across day
 | 
				
			||||||
 | 
					        if (duration < 0) {
 | 
				
			||||||
 | 
					            duration += 24 * 3600;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return duration;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static jsonToBean(bean, obj) {
 | 
					    static jsonToBean(bean, obj) {
 | 
				
			||||||
        if (obj.id) {
 | 
					        if (obj.id) {
 | 
				
			||||||
            bean.id = obj.id;
 | 
					            bean.id = obj.id;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,6 +40,7 @@ class MaintenanceTimeslot extends BeanModel {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (maintenance.strategy === "manual") {
 | 
					        if (maintenance.strategy === "manual") {
 | 
				
			||||||
            log.debug("maintenance", "No need to generate timeslot for manual type");
 | 
					            log.debug("maintenance", "No need to generate timeslot for manual type");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        } else if (maintenance.strategy === "single") {
 | 
					        } else if (maintenance.strategy === "single") {
 | 
				
			||||||
            let bean = R.dispense("maintenance_timeslot");
 | 
					            let bean = R.dispense("maintenance_timeslot");
 | 
				
			||||||
            bean.maintenance_id = maintenance.id;
 | 
					            bean.maintenance_id = maintenance.id;
 | 
				
			||||||
@@ -47,30 +48,94 @@ class MaintenanceTimeslot extends BeanModel {
 | 
				
			|||||||
            bean.end_date = maintenance.end_date;
 | 
					            bean.end_date = maintenance.end_date;
 | 
				
			||||||
            bean.generated_next = true;
 | 
					            bean.generated_next = true;
 | 
				
			||||||
            return await R.store(bean);
 | 
					            return await R.store(bean);
 | 
				
			||||||
        } else if (maintenance.strategy === "recurring-interval") {
 | 
					 | 
				
			||||||
            let bean = R.dispense("maintenance_timeslot");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (maintenance.strategy === "recurring-interval") {
 | 
				
			||||||
            // Prevent dead loop, in case interval_day is not set
 | 
					            // Prevent dead loop, in case interval_day is not set
 | 
				
			||||||
            if (!maintenance.interval_day || maintenance.interval_day <= 0) {
 | 
					            if (!maintenance.interval_day || maintenance.interval_day <= 0) {
 | 
				
			||||||
                maintenance.interval_day = 1;
 | 
					                maintenance.interval_day = 1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let startOfTheDay = dayjs.utc(maintenance.start_date).format("HH:mm");
 | 
					            return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
 | 
				
			||||||
            log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
 | 
					                return startDateTime.add(maintenance.interval_day, "day");
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Start Time
 | 
					        } else if (maintenance.strategy === "recurring-weekday") {
 | 
				
			||||||
            let startTimeSecond = dayjs.utc(maintenance.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
 | 
					            let dayOfWeekList = maintenance.getDayOfWeekList();
 | 
				
			||||||
            log.debug("timeslot", "startTime: " + startTimeSecond);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Duration
 | 
					            if (dayOfWeekList.length <= 0) {
 | 
				
			||||||
            let duration = dayjs.utc(maintenance.end_time, "HH:mm").diff(dayjs.utc(maintenance.start_time, "HH:mm"), "second");
 | 
					                log.debug("timeslot", "No weekdays selected?");
 | 
				
			||||||
            // Add 24hours if it is across day
 | 
					                return null;
 | 
				
			||||||
            if (duration < 0) {
 | 
					 | 
				
			||||||
                duration += 24 * 3600;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Bake StartDate + StartTime = Start DateTime
 | 
					            return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
 | 
				
			||||||
            let startDateTime = dayjs.utc(maintenance.start_date).add(startTimeSecond, "second");
 | 
					                while (true) {
 | 
				
			||||||
 | 
					                    startDateTime = startDateTime.add(1, "day");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    log.debug("timeslot", "nextDateTime: " + startDateTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let day = startDateTime.local().day();
 | 
				
			||||||
 | 
					                    log.debug("timeslot", "nextDateTime.day(): " + day);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (dayOfWeekList.includes(day)) {
 | 
				
			||||||
 | 
					                        return startDateTime;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (maintenance.strategy === "recurring-day-of-month") {
 | 
				
			||||||
 | 
					            let dayOfMonthList = maintenance.getDayOfMonthList();
 | 
				
			||||||
 | 
					            if (dayOfMonthList.length <= 0) {
 | 
				
			||||||
 | 
					                log.debug("timeslot", "No day selected?");
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
 | 
				
			||||||
 | 
					                while (true) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    startDateTime = startDateTime.add(1, "day");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    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: " + lastDayList);
 | 
				
			||||||
 | 
					                    if (lastDayList.includes(day)) {
 | 
				
			||||||
 | 
					                        return startDateTime;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            throw new Error("Unknown maintenance strategy");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Generate a next timeslot for all recurring types
 | 
				
			||||||
 | 
					     * @param maintenance
 | 
				
			||||||
 | 
					     * @param minDate
 | 
				
			||||||
 | 
					     * @param nextDayCallback The logic how to get the next possible day
 | 
				
			||||||
 | 
					     * @returns {Promise<null|MaintenanceTimeslot>}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    static async handleRecurringType(maintenance, minDate, nextDayCallback) {
 | 
				
			||||||
 | 
					        let bean = R.dispense("maintenance_timeslot");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let duration = maintenance.getDuration();
 | 
				
			||||||
 | 
					        let startDateTime = maintenance.getStartDateTime();
 | 
				
			||||||
        let endDateTime;
 | 
					        let endDateTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Keep generating from the first possible date, until it is ok
 | 
					        // Keep generating from the first possible date, until it is ok
 | 
				
			||||||
@@ -99,7 +164,7 @@ class MaintenanceTimeslot extends BeanModel {
 | 
				
			|||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                startDateTime = startDateTime.add(maintenance.interval_day, "day");
 | 
					            startDateTime = nextDayCallback(startDateTime);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bean.maintenance_id = maintenance.id;
 | 
					        bean.maintenance_id = maintenance.id;
 | 
				
			||||||
@@ -107,13 +172,6 @@ class MaintenanceTimeslot extends BeanModel {
 | 
				
			|||||||
        bean.end_date = localToUTC(endDateTime);
 | 
					        bean.end_date = localToUTC(endDateTime);
 | 
				
			||||||
        bean.generated_next = false;
 | 
					        bean.generated_next = false;
 | 
				
			||||||
        return await R.store(bean);
 | 
					        return await R.store(bean);
 | 
				
			||||||
        } else if (maintenance.strategy === "recurring-weekday") {
 | 
					 | 
				
			||||||
            // TODO
 | 
					 | 
				
			||||||
        } else if (maintenance.strategy === "recurring-day-of-month") {
 | 
					 | 
				
			||||||
            // TODO
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            throw new Error("Unknown maintenance strategy");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -620,6 +620,7 @@ export default {
 | 
				
			|||||||
    weekdayShortFri: "Fri",
 | 
					    weekdayShortFri: "Fri",
 | 
				
			||||||
    weekdayShortSat: "Sat",
 | 
					    weekdayShortSat: "Sat",
 | 
				
			||||||
    weekdayShortSun: "Sun",
 | 
					    weekdayShortSun: "Sun",
 | 
				
			||||||
 | 
					    dayOfWeek: "Day of Week",
 | 
				
			||||||
    dayOfMonth: "Day of Month",
 | 
					    dayOfMonth: "Day of Month",
 | 
				
			||||||
    lastDay: "Last Day",
 | 
					    lastDay: "Last Day",
 | 
				
			||||||
    lastDay1: "Last Day of Month",
 | 
					    lastDay1: "Last Day of Month",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,8 +91,8 @@
 | 
				
			|||||||
                                    <option value="manual">{{ $t("strategyManual") }}</option>
 | 
					                                    <option value="manual">{{ $t("strategyManual") }}</option>
 | 
				
			||||||
                                    <option value="single">Single Maintenance Window</option>
 | 
					                                    <option value="single">Single Maintenance Window</option>
 | 
				
			||||||
                                    <option value="recurring-interval">{{ $t("Recurring") }} - {{ $t("recurringInterval") }}</option>
 | 
					                                    <option value="recurring-interval">{{ $t("Recurring") }} - {{ $t("recurringInterval") }}</option>
 | 
				
			||||||
                                    <option value="recurring-weekday">{{ $t("Recurring") }} - Weekday</option>
 | 
					                                    <option value="recurring-weekday">{{ $t("Recurring") }} - {{ $t("dayOfWeek") }}</option>
 | 
				
			||||||
                                    <option value="recurring-day-of-month">{{ $t("Recurring") }} - Day of Month</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>
 | 
					                                    <option v-if="false" value="recurring-day-of-year">{{ $t("Recurring") }} - Day of Year</option>
 | 
				
			||||||
                                </select>
 | 
					                                </select>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
@@ -136,7 +136,7 @@
 | 
				
			|||||||
                            <template v-if="maintenance.strategy === 'recurring-weekday'">
 | 
					                            <template v-if="maintenance.strategy === 'recurring-weekday'">
 | 
				
			||||||
                                <div class="my-3">
 | 
					                                <div class="my-3">
 | 
				
			||||||
                                    <label for="interval-day" class="form-label">
 | 
					                                    <label for="interval-day" class="form-label">
 | 
				
			||||||
                                        {{ $t("Weekday") }}
 | 
					                                        {{ $t("dayOfWeek") }}
 | 
				
			||||||
                                    </label>
 | 
					                                    </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <!-- Weekday Picker -->
 | 
					                                    <!-- Weekday Picker -->
 | 
				
			||||||
@@ -201,6 +201,7 @@
 | 
				
			|||||||
                                        :minDate="minDate"
 | 
					                                        :minDate="minDate"
 | 
				
			||||||
                                        format="yyyy-MM-dd HH:mm:ss"
 | 
					                                        format="yyyy-MM-dd HH:mm:ss"
 | 
				
			||||||
                                        modelType="yyyy-MM-dd HH:mm:ss"
 | 
					                                        modelType="yyyy-MM-dd HH:mm:ss"
 | 
				
			||||||
 | 
					                                        required
 | 
				
			||||||
                                    />
 | 
					                                    />
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
@@ -297,9 +298,9 @@ export default {
 | 
				
			|||||||
                    value: 6,
 | 
					                    value: 6,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    id: "weekday7",
 | 
					                    id: "weekday0",
 | 
				
			||||||
                    langKey: "weekdayShortSun",
 | 
					                    langKey: "weekdayShortSun",
 | 
				
			||||||
                    value: 7,
 | 
					                    value: 0,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user