Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A complete maintenance planning system #1213

Merged
merged 60 commits into from Oct 15, 2022
Merged

Conversation

karelkryda
Copy link
Contributor

@karelkryda karelkryda commented Jan 23, 2022

Description

This pull request includes the addition of maintenance planning functionality.

Affected monitors are not tested during maintenance, but status "3" (MAINTENANCE) is returned.
No notifications are also sent during maintenance. Notifications will only be sent if the monitor status after maintenance is "DOWN" (MAINTENANCE -> DOWN).

Due to the addition of this function, the UI has also been changed (adding buttons, texts, ...).

This pull request also requires the addition of some new translations.

Related issue: #191

Type of change

Please delete any options that are not relevant.

  • User interface (UI)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) - shouldn't

Checklist

  • My code follows the style guidelines of this project
  • I ran ESLint and other linters for modified files
  • I have performed a self-review of my own code and tested it
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • My code needed automated testing. I have added them (this is optional task)

Screenshots (if any)

image

image

image

image

title: this.title,
description: this.description,
start_date: this.start_date,
end_date: this.end_date
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better git diff in next updates to this object

Suggested change
end_date: this.end_date
end_date: this.end_date,

title: this.title,
description: this.description,
start_date: this.start_date,
end_date: this.end_date
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
end_date: this.end_date
end_date: this.end_date,

@@ -6,7 +6,7 @@ dayjs.extend(utc);
dayjs.extend(timezone);
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
const { debug, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger} = require("../../src/util");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { debug, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger} = require("../../src/util");
const { debug, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger } = require("../../src/util");

@@ -5,7 +5,7 @@ const server = require("../server");
const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor");
const dayjs = require("dayjs");
const { UP, flipStatus, debug } = require("../../src/util");
const { UP, MAINTENANCE, flipStatus, debug} = require("../../src/util");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { UP, MAINTENANCE, flipStatus, debug} = require("../../src/util");
const { UP, MAINTENANCE, flipStatus, debug } = require("../../src/util");

@@ -47,7 +66,7 @@
import HeartbeatBar from "../components/HeartbeatBar.vue";
import Uptime from "../components/Uptime.vue";
import Tag from "../components/Tag.vue";
import { getMonitorRelativeURL } from "../util.ts";
import {getMaintenanceRelativeURL, getMonitorRelativeURL} from "../util.ts";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import {getMaintenanceRelativeURL, getMonitorRelativeURL} from "../util.ts";
import { getMaintenanceRelativeURL, getMonitorRelativeURL } from "../util.ts";

Object.values(this.$root.monitorList).map(monitor => {
this.affectedMonitorsOptions.push({
id: monitor.id,
name: monitor.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: monitor.name
name: monitor.name,

Comment on lines 126 to 130

"$route.fullPath"() {
this.init();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"$route.fullPath"() {
this.init();
}
"$route.fullPath"() {
this.init();
}

Comment on lines 175 to 177
}

},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
},
}
},

this.processing = true;

if (this.affectedMonitors.length === 0) {
toast.error(this.$t("Select at least one affected monitor"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorter i18n key


<!-- Start Date Time -->
<div class="my-3">
<label for="start_date" class="form-label">{{ $t("Start of maintenance") }}</label>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be good to add current timezone in label?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean this way?

image

I see, for example, that heartbeats are stored in UTC format. Should I convert user input to UTC format and use the "old" datetime() function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, in label, not in input...

For example:

Start of Maintenance (GMT+1)
<input ...>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand. I will now commit with minor fixes (missing commas, spaces, translations) and try to make some adjustments to storing maintenance dates in the database.

Currently, the date is stored in the database in the time zone in which the date was entered by the user, not in UTC (+0) format, so it cannot be converted to the current time zone, eg when changing the time zone.
Ie. if the user enters 1:35 while creating maintenance and changes the time zone, it will still show 1:35.

@@ -28,9 +29,12 @@ class Monitor extends BeanModel {
* Only show necessary data to public
*/
async toPublicJSON() {
const maintenance = await R.getAll("SELECT mm.*, maintenance.start_date, maintenance.end_date FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now', 'localtime') AND datetime(maintenance.end_date) >= datetime('now', 'localtime')", [this.id]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have 3 this same queries in this file, 4th this same query is in api-router.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, can I ask your opinion?

The states of individual monitors are currently evaluated according to the state of the last heartbeat.
If I used this system in case of maintenance, any change in the maintenance settings (adding / removing the monitor from maintenance, changing the maintenance time) would not affect the monitor and the status page until the next heartbeat of the monitor.
By directly checking whether or not the monitor is in maintenance, it is possible to mark the monitor as in maintenance before a heartbeat occurs.
For this reason, this SQL query is also called when requesting monitor information instead of being called only twice (on the status page and at heartbeat).

In my opinion, this is more user-friendly, because the fact that the monitor is in maintenance or not can be seen immediately without waiting, even on the status page.

Do you think it will be better to leave the status display according to the last heartbeat, or do you find the my current solution better?

Thanks for the feedback

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO your (current) solution is better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your feedback.

In this case, 2 out of 4 calls are absolutely necessary (calls from the status page - display of maintenance information on the status page and at each beat - finding out if the monitor is under maintenance) and if I am not mistaken, in the methods "toJSON()" and "toPublicJSON()" this calls are also required to let the current solution described above work.

If I'm wrong, feel free to tell me your opinion.

Thank you

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know . I just wanted to have this long query-code line in one place (helper function etc)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, understand. It shouldn't be problem I think, I think I'll be able to move it into one helper function and just call it.

Few minutes later I commited some changes, so I'll check / do this tomorrow I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. It's now in its own method.

@@ -47,7 +66,7 @@
import HeartbeatBar from "../components/HeartbeatBar.vue";
import Uptime from "../components/Uptime.vue";
import Tag from "../components/Tag.vue";
import { getMonitorRelativeURL } from "../util.ts";
import {getMaintenanceRelativeURL, getMonitorRelativeURL } from "../util.ts";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import {getMaintenanceRelativeURL, getMonitorRelativeURL } from "../util.ts";
import { getMaintenanceRelativeURL, getMonitorRelativeURL } from "../util.ts";

@karelkryda
Copy link
Contributor Author

@Saibamen, Everything should be OK 🙂

@Saibamen
Copy link
Contributor

@louislam

@mathiskir
Copy link
Contributor

mathiskir commented Feb 19, 2022

Seems like this isn't working.
When trying to create a new monitor, it throws this:
image
installed with git clone -b master https://github.com/karelkryda/uptime-kuma && cd uptime-kuma && npm ci && npm run build && node server/server.js
No errors in console
This problem was on my end, works now!
@louislam

@karelkryda
Copy link
Contributor Author

@mathiskir make sure you removed folder with previous clone. Remove whole uptime-kuma folder, clone and try again, please

@mathiskir
Copy link
Contributor

mathiskir commented Feb 19, 2022

It works!

@mathiskir
Copy link
Contributor

This + #1253 + #1236 + a rest API to push incidents and maintenances would make uptime kuma fire

@nocturn9x
Copy link

We need more people with access to the repo so we can merge this and the PR for incident timelines! We really need this

@finnie2006
Copy link

Its such a nice update, hope he will finally merge this

@ckocyigit
Copy link

ckocyigit commented Mar 28, 2022

Is it possible to publish an endpoint to pause a specific monitor e.g. via REST.

example: https://kuma/api/pause/<MonitorName>?duration=100"

I think this would really be helpful to solve the issue regarding automated deployments with varying times.

@karelkryda
Copy link
Contributor Author

Is it possible to publish an endpoint to pause a specific monitor e.g. via REST.

example: https://kuma/api/pause/<MonitorName>?duration=100"

I think this would really be helpful to solve the issue regarding automated deployments with varying times.

That sounds like a good idea.

Personally, I would suggest merging this pull request and then making a pull request on it containing API endpoints for maintenance system and incident system.

My point is that now this pull request is in a state where it is ready for deployment. I do not want to unnecessarily prolong the time that will be needed to implement this pull request in the main branch.

@acki
Copy link

acki commented Apr 26, 2022

@louislam Please? 😄

Copy link
Contributor

@Computroniks Computroniks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to add JSDoc here as well. @karelkryda I know your busy currently with exams so don't worry if you can't do it right now (good luck BTW). If you like, I could lend a hand here, just let me know. I would probably merge from master first though to get all of the comments added from #1499 so we don't end up duplicating.

server/model/maintenance.js Show resolved Hide resolved
}

/**
* Return a object that ready to parse to JSON
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Return a object that ready to parse to JSON
* Return a object that ready to parse to JSON
* @returns {Object}

@karelkryda
Copy link
Contributor Author

Would it be possible to add JSDoc here as well. @karelkryda I know your busy currently with exams so don't worry if you can't do it right now (good luck BTW). If you like, I could lend a hand here, just let me know. I would probably merge from master first though to get all of the comments added from #1499 so we don't end up duplicating.

Yes, it's not a problem. I'm currently wondering if there will also be a need to redo the whole PR because of conflicts.

If it wasn't needed, adding comments is a relatively short-term job and maybe I could do it soon.

Btw. thank you for wishing me luck 😊

@gaby
Copy link
Contributor

gaby commented Apr 28, 2022

@karelkryda If I get sometime today or tomorrow, I will submit a PR to your Fork with all the merge conflicts solved. I've done this already for my own copy of uptime-kuma.

@karelkryda
Copy link
Contributor Author

@karelkryda If I get sometime today or tomorrow, I will submit a PR to your Fork with all the merge conflicts solved. I've done this already for my own copy of uptime-kuma.

That sounds great. If I had time on the weekend, I would try to look at it and check the code.

Do you think I should implement the option to choose which page to display maintenance information on?

@gaby
Copy link
Contributor

gaby commented Apr 28, 2022

@karelkryda I just submitted a PR in your fork with all the conflicts solved. Once that PR is merge it should make it easier for @louislam to review this PR. 😄

PR is here: karelkryda#3

@karelkryda
Copy link
Contributor Author

@karelkryda I just submitted a PR in your fork with all the conflicts solved. Once that PR is merge it should make it easier for @louislam to review this PR. 😄

PR is here: karelkryda#3

Thanks, it's a lot of code 😅. I'll go through it, test it and prepare for merge.

@gaby
Copy link
Contributor

gaby commented Apr 28, 2022

@karelkryda I just submitted a PR in your fork with all the conflicts solved. Once that PR is merge it should make it easier for @louislam to review this PR. 😄
PR is here: karelkryda#3

Thanks, it's a lot of code 😅. I'll go through it, test it and prepare for merge.

I'm not 100% sure if this would help but:

  • Create a new branch in your fork named: upstream-changes.
  • I will re-submit the PR against that branch.
  • Merge the changes from PR into upstream-changes.
  • Then create a new PR from upstream-changes to master in your fork.

That way the PR will only have the changes related to this PR

# Conflicts:
#	package-lock.json
#	server/database.js
#	server/model/monitor.js
#	server/routers/api-router.js
#	server/server.js
#	src/components/MonitorList.vue
#	src/components/PingChart.vue
#	src/icon.js
#	src/pages/DashboardHome.vue
#	src/pages/StatusPage.vue
#	src/router.js
#	src/util.js
Copy link
Contributor

@Saibamen Saibamen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix EFLinx errors and warnings

Comment on lines 215 to 216
}
else if (this.type === "http" || this.type === "keyword") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
else if (this.type === "http" || this.type === "keyword") {
} else if (this.type === "http" || this.type === "keyword") {

Comment on lines 485 to 486
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
else {
} else {

@@ -903,6 +948,11 @@ class Monitor extends BeanModel {
monitorID
]);
}

static async isUnderMaintenance(monitorID) {
const maintenance = await R.getRow("SELECT COUNT(*) AS count FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now') AND datetime(maintenance.end_date) >= datetime('now') LIMIT 1", [monitorID]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const maintenance = await R.getRow("SELECT COUNT(*) AS count FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now') AND datetime(maintenance.end_date) >= datetime('now') LIMIT 1", [monitorID]);
const maintenance = await R.getRow("SELECT COUNT(*) AS count FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now') AND datetime(maintenance.end_date) >= datetime('now') LIMIT 1", [ monitorID ]);

@@ -148,6 +156,30 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons
}

});
// TODO: make slug aware
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix TODO

Comment on lines 6 to 7
<option value="monitor" selected>{{$t('Monitor List')}}</option>
<option value="maintenance">{{$t('Maintenance List')}}</option>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<option value="monitor" selected>{{$t('Monitor List')}}</option>
<option value="maintenance">{{$t('Maintenance List')}}</option>
<option value="monitor" selected>{{ $t('Monitor List') }}</option>
<option value="maintenance">{{ $t('Maintenance List') }}</option>

@@ -15,6 +15,10 @@ export default {

computed: {
uptime() {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -26,6 +30,10 @@ export default {
},

color() {
if (this.type === "maintenance" || this.monitor.maintenance) {
return "maintenance"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return "maintenance"
return "maintenance";

if (this.type === "maintenance" || this.monitor.maintenance) {
return "maintenance"
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

"Pick Affected Monitors...": "Pick Affected Monitors...",
"Start of maintenance": "Start of maintenance",
"Expected end of maintenance": "Expected end of maintenance",
Start: "Start",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated key

Suggested change
Start: "Start",

Comment on lines 37 to 40
if (inputDate.getFullYear() === now.getUTCFullYear() && inputDate.getMonth() === now.getUTCMonth() && inputDate.getDay() === now.getUTCDay())
return this.datetimeFormat(value, "HH:mm");
else
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (inputDate.getFullYear() === now.getUTCFullYear() && inputDate.getMonth() === now.getUTCMonth() && inputDate.getDay() === now.getUTCDay())
return this.datetimeFormat(value, "HH:mm");
else
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm");
if (inputDate.getFullYear() === now.getUTCFullYear() && inputDate.getMonth() === now.getUTCMonth() && inputDate.getDay() === now.getUTCDay()) {
return this.datetimeFormat(value, "HH:mm");
} else {
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm");
}

@louislam louislam linked an issue Oct 11, 2022 that may be closed by this pull request
@@ -64,7 +64,7 @@ class Database {
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
"patch-add-radius-monitor.sql": true,
"patch-monitor-add-resend-interval.sql": true,
"patch-maintenance-table.sql": true,
"patch-maintenance-table2.sql": true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change name (delete 2) before merging into master

@louislam louislam marked this pull request as ready for review October 15, 2022 12:19
@louislam
Copy link
Owner

I think it is almost done. I will have a final file changes review on GitHub and quickly release a beta for this. So everyone can test it.

Based on @karelkryda's pull request, I also update followings:

  • Redesigned and imrpoved UI/UX, put everything under Maintenance Page.
  • Improved timezone handling, added server side timezone
  • Besides single time window, I also added 3 recurring types and active/inactive manually.
  • Update database schema for recurring types

LOUIS-AORUS-15P  006097
image

LOUIS-AORUS-15P  006096
LOUIS-AORUS-15P  006095

image

@karelkryda
Copy link
Contributor Author

Thank you @louislam for your cooperation, it looks very good, well done.
I look forward to the release 🎉.

@louislam louislam merged commit afe12cc into louislam:master Oct 15, 2022
@pierreozoux
Copy link

Not yet using kuma, but I was waiting this feature to test.

I see all the amazing work you did, and just wanted to say thanks :)

Thanks a lot for this hard work! Have a wonderful day everybody :)

@louislam
Copy link
Owner

Not yet using kuma, but I was waiting this feature to test.

I see all the amazing work you did, and just wanted to say thanks :)

Thanks a lot for this hard work! Have a wonderful day everybody :)

It is available in 1.19.0-beta.0 now, feel free to try.

I would expect it is not fully stable yet, also feel free to submit bug reports.

@karelkryda
Copy link
Contributor Author

karelkryda commented Oct 17, 2022

Not yet using kuma, but I was waiting this feature to test.

I see all the amazing work you did, and just wanted to say thanks :)

Thanks a lot for this hard work! Have a wonderful day everybody :)

I am glad that this functionality has finally been implemented and I was happy to help with it.
In the end, it's us developers and Uptime Kuma users who have a vested interest in improving open source software like this.

I hope that my next PR related to the incident system (and other PRs) will soon be included in the main branch and I look forward to further excellent cooperation with @louislam.

Have a nice rest of the day

@x86dev
Copy link

x86dev commented Oct 17, 2022

This is awesome news, a big thanks to all who made this possible! Can't wait to test this!

@spiezmaestro
Copy link

This is the one I have been waiting for! I have weekly reboots on a lot of systems and until now, I'm getting flooded by alert mails. Thanks a lot to all who contributed!!

@lachnerd
Copy link

Awesome feature, thanks a lot.
Is it possible to make upcoming maintenances visibile on the status pages ?

@ex0nuss
Copy link

ex0nuss commented Dec 24, 2022

Amazing feature! I have waited for this one sooo long :)
Is it possible to select container by tags? Would be pretty handy this way you can schedule regular maintenances dynamically.

Here a quick example:

  1. I have the tag "autoPatching_weekly_sunday" on some monitors.
  2. Now I schedule a recurring maintenance for every Sunday with the tag "autoPatching_weekly_sunday".
  3. When I add a new monitor, that will be also patched automatically every Sunday, I only need to add the tag ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet