This commit is contained in:
Louis Lam 2022-10-11 18:23:17 +08:00
parent 2271ac4a5a
commit e07aa982c3
10 changed files with 152 additions and 33 deletions

View file

@ -125,7 +125,7 @@ async function sendInfo(socket) {
latestVersion: checkVersion.latestVersion, latestVersion: checkVersion.latestVersion,
primaryBaseURL: await setting("primaryBaseURL"), primaryBaseURL: await setting("primaryBaseURL"),
serverTimezone: await server.getTimezone(), serverTimezone: await server.getTimezone(),
serverTimezoneOffset: dayjs().format("Z"), serverTimezoneOffset: server.getTimezoneOffset(),
}); });
} }

View file

@ -39,9 +39,15 @@ class Maintenance extends BeanModel {
timeRange: timeRange, timeRange: timeRange,
weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [], weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [],
daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [], daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [],
timeslotList: await this.getTimeslotList(), timeslotList: [],
}; };
const timeslotList = await this.getTimeslotList();
for (let timeslot of timeslotList) {
obj.timeslotList.push(await timeslot.toPublicJSON());
}
if (!isArray(obj.weekdays)) { if (!isArray(obj.weekdays)) {
obj.weekdays = []; obj.weekdays = [];
} }
@ -53,7 +59,9 @@ class Maintenance extends BeanModel {
// Maintenance Status // Maintenance Status
if (!obj.active) { if (!obj.active) {
obj.status = "inactive"; obj.status = "inactive";
} else if (obj.strategy === "manual" || obj.timeslotList.length > 0) { } else if (obj.strategy === "manual") {
obj.status = "under-maintenance";
} else if (obj.timeslotList.length > 0) {
for (let timeslot of obj.timeslotList) { for (let timeslot of obj.timeslotList) {
if (dayjs.utc(timeslot.start_date) <= dayjs.utc() && dayjs.utc(timeslot.end_date) >= dayjs.utc()) { if (dayjs.utc(timeslot.start_date) <= dayjs.utc() && dayjs.utc(timeslot.end_date) >= dayjs.utc()) {
obj.status = "under-maintenance"; obj.status = "under-maintenance";
@ -78,7 +86,7 @@ class Maintenance extends BeanModel {
* @returns {Promise<[]>} * @returns {Promise<[]>}
*/ */
async getTimeslotList() { async getTimeslotList() {
return await R.getAll(` return R.convertToBeans("maintenance_timeslot", await R.getAll(`
SELECT maintenance_timeslot.* SELECT maintenance_timeslot.*
FROM maintenance_timeslot, maintenance FROM maintenance_timeslot, maintenance
WHERE maintenance_timeslot.maintenance_id = maintenance.id WHERE maintenance_timeslot.maintenance_id = maintenance.id
@ -86,7 +94,7 @@ class Maintenance extends BeanModel {
AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()} AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()}
`, [ `, [
this.id this.id
]); ]));
} }
/** /**
@ -156,10 +164,10 @@ class Maintenance extends BeanModel {
*/ */
static getActiveAndFutureMaintenanceSQLCondition() { static getActiveAndFutureMaintenanceSQLCondition() {
return ` return `
(maintenance_timeslot.end_date >= DATETIME('now') ((maintenance_timeslot.end_date >= DATETIME('now')
AND maintenance.active = 1) AND maintenance.active = 1)
OR OR
(maintenance.strategy = 'manual' AND active = 1) (maintenance.strategy = 'manual' AND active = 1))
`; `;
} }
} }

View file

@ -1,16 +1,28 @@
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { log } = require("../../src/util"); const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND } = require("../../src/util");
const { UptimeKumaServer } = require("../uptime-kuma-server");
class MaintenanceTimeslot extends BeanModel { class MaintenanceTimeslot extends BeanModel {
async toPublicJSON() { async toPublicJSON() {
const serverTimezoneOffset = await 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() { async toJSON() {
return await this.toPublicJSON();
} }
/** /**

View file

@ -258,4 +258,54 @@ module.exports.maintenanceSocketHandler = (socket) => {
}); });
} }
}); });
socket.on("pauseMaintenance", async (maintenanceID, callback) => {
try {
checkLogin(socket);
log.debug("maintenance", `Pause Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
await R.exec("UPDATE maintenance SET active = 0 WHERE id = ? ", [
maintenanceID,
]);
callback({
ok: true,
msg: "Paused Successfully.",
});
await server.sendMaintenanceList(socket);
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("resumeMaintenance", async (maintenanceID, callback) => {
try {
checkLogin(socket);
log.debug("maintenance", `Resume Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
await R.exec("UPDATE maintenance SET active = 1 WHERE id = ? ", [
maintenanceID,
]);
callback({
ok: true,
msg: "Resume Successfully",
});
await server.sendMaintenanceList(socket);
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
}; };

View file

@ -204,6 +204,10 @@ class UptimeKumaServer {
} }
} }
async getTimezoneOffset() {
return dayjs().format("Z");
}
async setTimezone(timezone) { async setTimezone(timezone) {
await Settings.set("serverTimezone", timezone, "general"); await Settings.set("serverTimezone", timezone, "general");
process.env.TZ = timezone; process.env.TZ = timezone;

View file

@ -101,11 +101,6 @@ optgroup {
} }
} }
// Override Bootstrap
.btn-group > .btn:hover {
z-index: initial;
}
.btn { .btn {
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
@ -125,6 +120,19 @@ optgroup {
} }
} }
.btn-normal {
$bg-color: #F5F5F5;
background-color: $bg-color;
border-color: $bg-color;
&:hover {
$hover-color: darken($bg-color, 3%);
background-color: $hover-color;
border-color: $hover-color;
}
}
.btn-warning { .btn-warning {
color: white; color: white;

View file

@ -631,4 +631,10 @@ export default {
lastDay3: "3rd Last Day of Month", lastDay3: "3rd Last Day of Month",
lastDay4: "4th Last Day of Month", lastDay4: "4th Last Day of Month",
"No Maintenance": "No Maintenance", "No Maintenance": "No Maintenance",
pauseMaintenanceMsg: "Are you sure want to pause?",
"maintenanceStatus-under-maintenance": "Under Maintenance",
"maintenanceStatus-inactive": "Inactive",
"maintenanceStatus-scheduled": "Scheduled",
"maintenanceStatus-ended": "Ended",
"maintenanceStatus-unknown": "Unknown",
}; };

View file

@ -28,7 +28,18 @@
></div> ></div>
<div class="info"> <div class="info">
<div class="title">{{ item.title }}</div> <div class="title">{{ item.title }}</div>
<div>{{ item.description }}</div> <div v-if="false">{{ item.description }}</div>
<div class="status">
{{ $t("maintenanceStatus-" + item.status) }}
</div>
<div v-if="item.strategy === 'manual'" class="timeslot">
{{ $t("Manual") }}
</div>
<div v-else-if="item.timeslotList.length > 0" class="timeslot">
{{ item.timeslotList[0].startDateServerTimezone }} <span class="to">-</span> {{ item.timeslotList[0].endDateServerTimezone }}
(UTC{{ item.timeslotList[0].serverTimezoneOffset }})
</div>
</div> </div>
</div> </div>
@ -36,11 +47,11 @@
<router-link v-if="false" :to="maintenanceURL(item.id)" class="btn btn-light">{{ $t("Details") }}</router-link> <router-link v-if="false" :to="maintenanceURL(item.id)" class="btn btn-light">{{ $t("Details") }}</router-link>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button v-if="item.active" class="btn btn-normal" @click="pauseDialog"> <button v-if="item.active" class="btn btn-normal" @click="pauseDialog(item.id)">
<font-awesome-icon icon="pause" /> {{ $t("Pause") }} <font-awesome-icon icon="pause" /> {{ $t("Pause") }}
</button> </button>
<button v-if="!item.active" class="btn btn-primary" @click="resumeMaintenance"> <button v-if="!item.active" class="btn btn-primary" @click="resumeMaintenance(item.id)">
<font-awesome-icon icon="play" /> {{ $t("Resume") }} <font-awesome-icon icon="play" /> {{ $t("Resume") }}
</button> </button>
@ -149,7 +160,8 @@ export default {
/** /**
* Show dialog to confirm pause * Show dialog to confirm pause
*/ */
pauseDialog() { pauseDialog(maintenanceID) {
this.selectedMaintenanceID = maintenanceID;
this.$refs.confirmPause.show(); this.$refs.confirmPause.show();
}, },
@ -157,8 +169,7 @@ export default {
* Pause maintenance * Pause maintenance
*/ */
pauseMaintenance() { pauseMaintenance() {
return; this.$root.getSocket().emit("pauseMaintenance", this.selectedMaintenanceID, (res) => {
this.$root.getSocket().emit("pauseMaintenance", selectedMaintenanceID, (res) => {
this.$root.toastRes(res); this.$root.toastRes(res);
}); });
}, },
@ -166,9 +177,8 @@ export default {
/** /**
* Resume maintenance * Resume maintenance
*/ */
resumeMaintenance() { resumeMaintenance(id) {
return; this.$root.getSocket().emit("resumeMaintenance", id, (res) => {
this.$root.getSocket().emit("resumeMaintenance", selectedMaintenanceID, (res) => {
this.$root.toastRes(res); this.$root.toastRes(res);
}); });
}, },
@ -189,6 +199,7 @@ export default {
justify-content: space-between; justify-content: space-between;
padding: 10px; padding: 10px;
min-height: 90px; min-height: 90px;
margin-bottom: 5px;
&:hover { &:hover {
background-color: $highlight-white; background-color: $highlight-white;
@ -251,9 +262,27 @@ export default {
font-size: 20px; font-size: 20px;
} }
.slug { .status {
font-size: 14px; font-size: 14px;
} }
.timeslot {
margin-top: 5px;
display: inline-block;
font-size: 14px;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 20px;
padding: 0 10px;
.to {
margin: 0 6px;
}
.dark & {
color: white;
background-color: rgba(255, 255, 255, 0.1);
}
}
} }
} }

View file

@ -7,7 +7,7 @@
// Backend uses the compiled file util.js // Backend uses the compiled file util.js
// Frontend uses util.ts // Frontend uses util.ts
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
const dayjs = require("dayjs"); const dayjs = require("dayjs");
exports.isDev = process.env.NODE_ENV === "development"; exports.isDev = process.env.NODE_ENV === "development";
exports.appName = "Uptime Kuma"; exports.appName = "Uptime Kuma";
@ -21,6 +21,7 @@ exports.STATUS_PAGE_PARTIAL_DOWN = 2;
exports.STATUS_PAGE_MAINTENANCE = 3; exports.STATUS_PAGE_MAINTENANCE = 3;
exports.SQL_DATE_FORMAT = "YYYY-MM-DD"; exports.SQL_DATE_FORMAT = "YYYY-MM-DD";
exports.SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; exports.SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm";
/** Flip the status of s */ /** Flip the status of s */
function flipStatus(s) { function flipStatus(s) {
if (s === exports.UP) { if (s === exports.UP) {
@ -366,11 +367,11 @@ exports.utcToISODateTime = utcToISODateTime;
/** /**
* For SQL_DATETIME_FORMAT * For SQL_DATETIME_FORMAT
*/ */
function utcToLocal(input) { function utcToLocal(input, format = exports.SQL_DATETIME_FORMAT) {
return dayjs.utc(input).local().format(exports.SQL_DATETIME_FORMAT); return dayjs.utc(input).local().format(format);
} }
exports.utcToLocal = utcToLocal; exports.utcToLocal = utcToLocal;
function localToUTC(input) { function localToUTC(input, format = exports.SQL_DATETIME_FORMAT) {
return dayjs(input).utc().format(exports.SQL_DATETIME_FORMAT); return dayjs(input).utc().format(format);
} }
exports.localToUTC = localToUTC; exports.localToUTC = localToUTC;

View file

@ -25,6 +25,7 @@ export const STATUS_PAGE_MAINTENANCE = 3;
export const SQL_DATE_FORMAT = "YYYY-MM-DD"; export const SQL_DATE_FORMAT = "YYYY-MM-DD";
export const SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; export const SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
export const SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm";
/** Flip the status of s */ /** Flip the status of s */
export function flipStatus(s: number) { export function flipStatus(s: number) {
@ -412,10 +413,10 @@ export function utcToISODateTime(input : string) {
/** /**
* For SQL_DATETIME_FORMAT * For SQL_DATETIME_FORMAT
*/ */
export function utcToLocal(input : string) { export function utcToLocal(input : string, format = SQL_DATETIME_FORMAT) {
return dayjs.utc(input).local().format(SQL_DATETIME_FORMAT); return dayjs.utc(input).local().format(format);
} }
export function localToUTC(input : string) { export function localToUTC(input : string, format = SQL_DATETIME_FORMAT) {
return dayjs(input).utc().format(SQL_DATETIME_FORMAT); return dayjs(input).utc().format(format);
} }