commit
b459408b10
132 changed files with 9438 additions and 4212 deletions
@ -0,0 +1,24 @@ |
||||
name: 'Automatically close stale issues and PRs' |
||||
on: |
||||
workflow_dispatch: |
||||
schedule: |
||||
- cron: '0 */6 * * *' |
||||
#Run every 6 hours |
||||
|
||||
jobs: |
||||
stale: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/stale@v5 |
||||
with: |
||||
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.' |
||||
stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.' |
||||
close-issue-message: 'This issue was closed because it has been stalled for 2 days with no activity.' |
||||
close-pr-message: 'This PR was closed because it has been stalled for 2 days with no activity.' |
||||
days-before-stale: 90 |
||||
days-before-close: 2 |
||||
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,feature-request' |
||||
exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,feature-request' |
||||
exempt-issue-assignees: 'louislam' |
||||
exempt-pr-assignees: 'louislam' |
||||
operations-per-run: 200 |
@ -0,0 +1,28 @@ |
||||
const { defineConfig } = require("cypress"); |
||||
|
||||
module.exports = defineConfig({ |
||||
projectId: "vyjuem", |
||||
e2e: { |
||||
experimentalStudio: true, |
||||
setupNodeEvents(on, config) { |
||||
|
||||
}, |
||||
fixturesFolder: "test/cypress/fixtures", |
||||
screenshotsFolder: "test/cypress/screenshots", |
||||
videosFolder: "test/cypress/videos", |
||||
downloadsFolder: "test/cypress/downloads", |
||||
supportFile: "test/cypress/support/e2e.js", |
||||
baseUrl: "http://localhost:3002", |
||||
defaultCommandTimeout: 10000, |
||||
pageLoadTimeout: 60000, |
||||
viewportWidth: 1920, |
||||
viewportHeight: 1080, |
||||
specPattern: [ |
||||
"test/cypress/e2e/setup.cy.js", |
||||
"test/cypress/e2e/**/*.js" |
||||
], |
||||
}, |
||||
env: { |
||||
baseUrl: "http://localhost:3002", |
||||
}, |
||||
}); |
@ -1,33 +0,0 @@ |
||||
const PuppeteerEnvironment = require("jest-environment-puppeteer"); |
||||
const util = require("util"); |
||||
|
||||
class DebugEnv extends PuppeteerEnvironment { |
||||
async handleTestEvent(event, state) { |
||||
const ignoredEvents = [ |
||||
"setup", |
||||
"add_hook", |
||||
"start_describe_definition", |
||||
"add_test", |
||||
"finish_describe_definition", |
||||
"run_start", |
||||
"run_describe_start", |
||||
"test_start", |
||||
"hook_start", |
||||
"hook_success", |
||||
"test_fn_start", |
||||
"test_fn_success", |
||||
"test_done", |
||||
"run_describe_finish", |
||||
"run_finish", |
||||
"teardown", |
||||
"test_fn_failure", |
||||
]; |
||||
if (!ignoredEvents.includes(event.name)) { |
||||
console.log( |
||||
new Date().toString() + ` Unhandled event [${event.name}] ` + util.inspect(event) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
module.exports = DebugEnv; |
@ -1,5 +0,0 @@ |
||||
module.exports = { |
||||
"rootDir": "..", |
||||
"testRegex": "./test/frontend.spec.js", |
||||
}; |
||||
|
@ -1,20 +0,0 @@ |
||||
module.exports = { |
||||
"launch": { |
||||
"dumpio": true, |
||||
"slowMo": 500, |
||||
"headless": process.env.HEADLESS_TEST || false, |
||||
"userDataDir": "./data/test-chrome-profile", |
||||
args: [ |
||||
"--disable-setuid-sandbox", |
||||
"--disable-gpu", |
||||
"--disable-dev-shm-usage", |
||||
"--no-default-browser-check", |
||||
"--no-experiments", |
||||
"--no-first-run", |
||||
"--no-pings", |
||||
"--no-sandbox", |
||||
"--no-zygote", |
||||
"--single-process", |
||||
], |
||||
} |
||||
}; |
@ -1,12 +0,0 @@ |
||||
module.exports = { |
||||
"verbose": true, |
||||
"preset": "jest-puppeteer", |
||||
"globals": { |
||||
"__DEV__": true |
||||
}, |
||||
"testRegex": "./test/e2e.spec.js", |
||||
"testEnvironment": "./config/jest-debug-env.js", |
||||
"rootDir": "..", |
||||
"testTimeout": 30000, |
||||
}; |
||||
|
@ -0,0 +1,83 @@ |
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
BEGIN TRANSACTION; |
||||
|
||||
-- Just for someone who tested maintenance before (patch-maintenance-table.sql) |
||||
DROP TABLE IF EXISTS maintenance_status_page; |
||||
DROP TABLE IF EXISTS monitor_maintenance; |
||||
DROP TABLE IF EXISTS maintenance; |
||||
DROP TABLE IF EXISTS maintenance_timeslot; |
||||
|
||||
-- maintenance |
||||
CREATE TABLE [maintenance] ( |
||||
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, |
||||
[title] VARCHAR(150) NOT NULL, |
||||
[description] TEXT NOT NULL, |
||||
[user_id] INTEGER REFERENCES [user]([id]) ON DELETE SET NULL ON UPDATE CASCADE, |
||||
[active] BOOLEAN NOT NULL DEFAULT 1, |
||||
[strategy] VARCHAR(50) NOT NULL DEFAULT 'single', |
||||
[start_date] DATETIME, |
||||
[end_date] DATETIME, |
||||
[start_time] TIME, |
||||
[end_time] TIME, |
||||
[weekdays] VARCHAR2(250) DEFAULT '[]', |
||||
[days_of_month] TEXT DEFAULT '[]', |
||||
[interval_day] INTEGER |
||||
); |
||||
|
||||
CREATE INDEX [manual_active] ON [maintenance] ( |
||||
[strategy], |
||||
[active] |
||||
); |
||||
|
||||
CREATE INDEX [active] ON [maintenance] ([active]); |
||||
|
||||
CREATE INDEX [maintenance_user_id] ON [maintenance] ([user_id]); |
||||
|
||||
-- maintenance_status_page |
||||
CREATE TABLE maintenance_status_page ( |
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |
||||
status_page_id INTEGER NOT NULL, |
||||
maintenance_id INTEGER NOT NULL, |
||||
CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE, |
||||
CONSTRAINT FK_status_page FOREIGN KEY (status_page_id) REFERENCES status_page (id) ON DELETE CASCADE ON UPDATE CASCADE |
||||
); |
||||
|
||||
CREATE INDEX [status_page_id_index] |
||||
ON [maintenance_status_page]([status_page_id]); |
||||
|
||||
CREATE INDEX [maintenance_id_index] |
||||
ON [maintenance_status_page]([maintenance_id]); |
||||
|
||||
-- maintenance_timeslot |
||||
CREATE TABLE [maintenance_timeslot] ( |
||||
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, |
||||
[maintenance_id] INTEGER NOT NULL CONSTRAINT [FK_maintenance] REFERENCES [maintenance]([id]) ON DELETE CASCADE ON UPDATE CASCADE, |
||||
[start_date] DATETIME NOT NULL, |
||||
[end_date] DATETIME, |
||||
[generated_next] BOOLEAN DEFAULT 0 |
||||
); |
||||
|
||||
CREATE INDEX [maintenance_id] ON [maintenance_timeslot] ([maintenance_id] DESC); |
||||
|
||||
CREATE INDEX [active_timeslot_index] ON [maintenance_timeslot] ( |
||||
[maintenance_id] DESC, |
||||
[start_date] DESC, |
||||
[end_date] DESC |
||||
); |
||||
|
||||
CREATE INDEX [generated_next_index] ON [maintenance_timeslot] ([generated_next]); |
||||
|
||||
-- monitor_maintenance |
||||
CREATE TABLE monitor_maintenance ( |
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |
||||
monitor_id INTEGER NOT NULL, |
||||
maintenance_id INTEGER NOT NULL, |
||||
CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE, |
||||
CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE |
||||
); |
||||
|
||||
CREATE INDEX [maintenance_id_index2] ON [monitor_maintenance]([maintenance_id]); |
||||
|
||||
CREATE INDEX [monitor_id_index] ON [monitor_maintenance]([monitor_id]); |
||||
|
||||
COMMIT; |
@ -0,0 +1,33 @@ |
||||
const childProcess = require("child_process"); |
||||
|
||||
if (!process.env.UPTIME_KUMA_GH_REPO) { |
||||
console.error("Please set a repo to the environment variable 'UPTIME_KUMA_GH_REPO' (e.g. mhkarimi1383:goalert-notification)"); |
||||
process.exit(1); |
||||
} |
||||
|
||||
let inputArray = process.env.UPTIME_KUMA_GH_REPO.split(":"); |
||||
|
||||
if (inputArray.length !== 2) { |
||||
console.error("Invalid format. Please set a repo to the environment variable 'UPTIME_KUMA_GH_REPO' (e.g. mhkarimi1383:goalert-notification)"); |
||||
} |
||||
|
||||
let name = inputArray[0]; |
||||
let branch = inputArray[1]; |
||||
|
||||
console.log("Checkout pr"); |
||||
|
||||
// Checkout the pr
|
||||
let result = childProcess.spawnSync("git", [ "remote", "add", name, `https://github.com/${name}/uptime-kuma` ]); |
||||
|
||||
console.log(result.stdout.toString()); |
||||
console.error(result.stderr.toString()); |
||||
|
||||
result = childProcess.spawnSync("git", [ "fetch", name, branch ]); |
||||
|
||||
console.log(result.stdout.toString()); |
||||
console.error(result.stderr.toString()); |
||||
|
||||
result = childProcess.spawnSync("git", [ "checkout", `${name}/${branch}`, "--force" ]); |
||||
|
||||
console.log(result.stdout.toString()); |
||||
console.error(result.stderr.toString()); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,215 @@ |
||||
const { BeanModel } = require("redbean-node/dist/bean-model"); |
||||
const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log } = require("../../src/util"); |
||||
const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); |
||||
const { R } = require("redbean-node"); |
||||
const dayjs = require("dayjs"); |
||||
|
||||
class Maintenance extends BeanModel { |
||||
|
||||
/** |
||||
* Return an object that ready to parse to JSON for public |
||||
* Only show necessary data to public |
||||
* @returns {Object} |
||||
*/ |
||||
async toPublicJSON() { |
||||
|
||||
let dateRange = []; |
||||
if (this.start_date) { |
||||
dateRange.push(utcToLocal(this.start_date)); |
||||
if (this.end_date) { |
||||
dateRange.push(utcToLocal(this.end_date)); |
||||
} |
||||
} |
||||
|
||||
let timeRange = []; |
||||
let startTime = timeObjectToLocal(parseTimeObject(this.start_time)); |
||||
timeRange.push(startTime); |
||||
let endTime = timeObjectToLocal(parseTimeObject(this.end_time)); |
||||
timeRange.push(endTime); |
||||
|
||||
let obj = { |
||||
id: this.id, |
||||
title: this.title, |
||||
description: this.description, |
||||
strategy: this.strategy, |
||||
intervalDay: this.interval_day, |
||||
active: !!this.active, |
||||
dateRange: dateRange, |
||||
timeRange: timeRange, |
||||
weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [], |
||||
daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [], |
||||
timeslotList: [], |
||||
}; |
||||
|
||||
const timeslotList = await this.getTimeslotList(); |
||||
|
||||
for (let timeslot of timeslotList) { |
||||
obj.timeslotList.push(await timeslot.toPublicJSON()); |
||||
} |
||||
|
||||
if (!Array.isArray(obj.weekdays)) { |
||||
obj.weekdays = []; |
||||
} |
||||
|
||||
if (!Array.isArray(obj.daysOfMonth)) { |
||||
obj.daysOfMonth = []; |
||||
} |
||||
|
||||
// Maintenance Status
|
||||
if (!obj.active) { |
||||
obj.status = "inactive"; |
||||
} else if (obj.strategy === "manual") { |
||||
obj.status = "under-maintenance"; |
||||
} else if (obj.timeslotList.length > 0) { |
||||
let currentTimestamp = dayjs().unix(); |
||||
|
||||
for (let timeslot of obj.timeslotList) { |
||||
if (dayjs.utc(timeslot.startDate).unix() <= currentTimestamp && dayjs.utc(timeslot.endDate).unix() >= currentTimestamp) { |
||||
log.debug("timeslot", "Timeslot ID: " + timeslot.id); |
||||
log.debug("timeslot", "currentTimestamp:" + currentTimestamp); |
||||
log.debug("timeslot", "timeslot.start_date:" + dayjs.utc(timeslot.startDate).unix()); |
||||
log.debug("timeslot", "timeslot.end_date:" + dayjs.utc(timeslot.endDate).unix()); |
||||
|
||||
obj.status = "under-maintenance"; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (!obj.status) { |
||||
obj.status = "scheduled"; |
||||
} |
||||
} else if (obj.timeslotList.length === 0) { |
||||
obj.status = "ended"; |
||||
} else { |
||||
obj.status = "unknown"; |
||||
} |
||||
|
||||
return obj; |
||||
} |
||||
|
||||
/** |
||||
* Only get future or current timeslots only |
||||
* @returns {Promise<[]>} |
||||
*/ |
||||
async getTimeslotList() { |
||||
return R.convertToBeans("maintenance_timeslot", await R.getAll(` |
||||
SELECT maintenance_timeslot.* |
||||
FROM maintenance_timeslot, maintenance |
||||
WHERE maintenance_timeslot.maintenance_id = maintenance.id |
||||
AND maintenance.id = ? |
||||
AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()} |
||||
`, [
|
||||
this.id |
||||
])); |
||||
} |
||||
|
||||
/** |
||||
* Return an object that ready to parse to JSON |
||||
* @param {string} timezone If not specified, the timeRange will be in UTC |
||||
* @returns {Object} |
||||
*/ |
||||
async toJSON(timezone = null) { |
||||
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) { |
||||
if (obj.id) { |
||||
bean.id = obj.id; |
||||
} |
||||
|
||||
// Apply timezone offset to timeRange, as it cannot apply automatically.
|
||||
if (obj.timeRange[0]) { |
||||
timeObjectToUTC(obj.timeRange[0]); |
||||
if (obj.timeRange[1]) { |
||||
timeObjectToUTC(obj.timeRange[1]); |
||||
} |
||||
} |
||||
|
||||
bean.title = obj.title; |
||||
bean.description = obj.description; |
||||
bean.strategy = obj.strategy; |
||||
bean.interval_day = obj.intervalDay; |
||||
bean.active = obj.active; |
||||
|
||||
if (obj.dateRange[0]) { |
||||
bean.start_date = localToUTC(obj.dateRange[0]); |
||||
|
||||
if (obj.dateRange[1]) { |
||||
bean.end_date = localToUTC(obj.dateRange[1]); |
||||
} |
||||
} |
||||
|
||||
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); |
||||
|
||||
return bean; |
||||
} |
||||
|
||||
/** |
||||
* SQL conditions for active maintenance |
||||
* @returns {string} |
||||
*/ |
||||
static getActiveMaintenanceSQLCondition() { |
||||
return ` |
||||
|
||||
(maintenance_timeslot.start_date <= DATETIME('now') |
||||
AND maintenance_timeslot.end_date >= DATETIME('now') |
||||
AND maintenance.active = 1) |
||||
OR |
||||
(maintenance.strategy = 'manual' AND active = 1) |
||||
|
||||
`;
|
||||
} |
||||
|
||||
/** |
||||
* SQL conditions for active and future maintenance |
||||
* @returns {string} |
||||
*/ |
||||
static getActiveAndFutureMaintenanceSQLCondition() { |
||||
return ` |
||||
((maintenance_timeslot.end_date >= DATETIME('now') |
||||
AND maintenance.active = 1) |
||||
OR |
||||
(maintenance.strategy = 'manual' AND active = 1)) |
||||
`;
|
||||
} |
||||
} |
||||
|
||||
module.exports = Maintenance; |
@ -0,0 +1,189 @@ |
||||
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 { |
||||
|
||||
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; |
||||
} |
||||
|
||||
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) { |
||||
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; |
||||
return await R.store(bean); |
||||
|
||||
} 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"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Generate a next timeslot for all recurring types |
||||
* @param maintenance |
||||
* @param minDate |
||||
* @param {function} nextDayCallback The logic how to get the next possible day |
||||
* @param {function} isValidCallback Check the day whether is matched the current strategy |
||||
* @returns {Promise<null|MaintenanceTimeslot>} |
||||
*/ |
||||
static async handleRecurringType(maintenance, minDate, nextDayCallback, isValidCallback) { |
||||
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) { |
||||
log.debug("timeslot", "startDateTime: " + startDateTime.format()); |
||||
|
||||
// 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
|
||||
// Is valid under current recurring strategy
|
||||
if ( |
||||
(!minDate || endDateTime.diff(minDate) > 0) && |
||||
endDateTime.diff(dayjs()) > 0 && |
||||
isValidCallback(startDateTime) |
||||
) { |
||||
break; |
||||
} |
||||
startDateTime = nextDayCallback(startDateTime); |
||||
} |
||||
|
||||
bean.maintenance_id = maintenance.id; |
||||
bean.start_date = localToUTC(startDateTime); |
||||
bean.end_date = localToUTC(endDateTime); |
||||
bean.generated_next = false; |
||||
return await R.store(bean); |
||||
} |
||||
} |
||||
|
||||
module.exports = MaintenanceTimeslot; |
@ -1,12 +1,8 @@ |
||||
const https = require("https"); |
||||
const dayjs = require("dayjs"); |
||||
const utc = require("dayjs/plugin/utc"); |
||||
let timezone = require("dayjs/plugin/timezone"); |
||||
dayjs.extend(utc); |
||||
dayjs.extend(timezone); |
||||
const axios = require("axios"); |
||||
const { Prometheus } = require("../prometheus"); |
||||
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); |
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger } = require("../../src/util"); |
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery } = require("../util-server"); |
||||