Maintenance Windows: recurring now works overnight (#11389)

* Alert Rules use Carbon
Use carbon for time queries, need to be careful of timezone quirks.  Timestamps won't have that issue

* Convert existing schedules to UTC

* we only need start end... we already have start/end

* Make AlertSchedule::isActive work

* Table display working (and using Eloquent)

* Schedule creation updated

* WIP

* use Eloquent mutators/accessors

* recurring_day mutator/accessor

* WIP UI

* Fix SQL to handle outside times

* fix up some ui issues

* Convert alert-schedule to new style
Improved sorting customization

* fix whitespace

* support sqlite

* Fix schedule creation missing id

* Fix Device::isUnderMaintenance() add location relation
This commit is contained in:
Tony Murray
2020-06-14 16:35:50 -05:00
committed by GitHub
20 changed files with 424 additions and 229 deletions

View File

@@ -14,6 +14,8 @@ use Illuminate\Support\Str;
* the source code distribution for details.
*/
use Carbon\Carbon;
if (!Auth::user()->hasGlobalAdmin()) {
header('Content-type: text/plain');
die('ERROR: You need to be admin');
@@ -34,7 +36,7 @@ if ($sub_type == 'new-maintenance') {
$title = mres($_POST['title']);
$notes = mres($_POST['notes']);
$recurring = mres($_POST['recurring']);
$recurring = $_POST['recurring'] ? 1 : 0;
$start_recurring_dt = mres($_POST['start_recurring_dt']);
$end_recurring_dt = mres($_POST['end_recurring_dt']);
$start_recurring_hr = mres($_POST['start_recurring_hr']);
@@ -75,7 +77,7 @@ if ($sub_type == 'new-maintenance') {
$message .= 'Please check end recurring date<br />';
}
} else {
$end_recurring_dt = null;
$end_recurring_dt = '9000-09-09';
}
if (empty($start_recurring_hr)) {
@@ -117,18 +119,27 @@ if ($sub_type == 'new-maintenance') {
}
if (empty($message)) {
if (empty($schedule_id)) {
$schedule_id = dbInsert(array('recurring' => $recurring, 'start' => $start, 'end' => $end, 'start_recurring_dt' => $start_recurring_dt, 'end_recurring_dt' => $end_recurring_dt, 'start_recurring_hr' => $start_recurring_hr, 'end_recurring_hr' => $end_recurring_hr, 'recurring_day' => $recurring_day, 'title' => $title, 'notes' => $notes), 'alert_schedule');
} else {
dbUpdate(array('recurring' => $recurring, 'start' => $start, 'end' => $end, 'start_recurring_dt' => $start_recurring_dt, 'end_recurring_dt' => $end_recurring_dt, 'start_recurring_hr' => $start_recurring_hr, 'end_recurring_hr' => $end_recurring_hr, 'recurring_day' => $recurring_day, 'title' => $title, 'notes' => $notes), 'alert_schedule', '`schedule_id`=?', array($schedule_id));
}
$alert_schedule = \App\Models\AlertSchedule::findOrNew($schedule_id);
$alert_schedule->title = $title;
$alert_schedule->notes = $notes;
$alert_schedule->recurring = $recurring;
$alert_schedule->start = $start;
$alert_schedule->end = $end;
if ($schedule_id > 0) {
if ($recurring) {
$alert_schedule->start_recurring_dt = $start_recurring_dt;
$alert_schedule->start_recurring_hr = $start_recurring_hr;
$alert_schedule->end_recurring_dt = $end_recurring_dt;
$alert_schedule->end_recurring_hr = $end_recurring_hr;
}
$alert_schedule->save();
if ($alert_schedule->schedule_id > 0) {
$items = array();
$fail = 0;
if ($update == 1) {
dbDelete('alert_schedulables', '`schedule_id`=?', array($schedule_id));
dbDelete('alert_schedulables', '`schedule_id`=?', [$alert_schedule->schedule_id]);
}
foreach ($_POST['maps'] as $target) {
@@ -141,7 +152,7 @@ if ($sub_type == 'new-maintenance') {
$target = substr($target, 1);
}
$item = dbInsert(['schedule_id' => $schedule_id, 'alert_schedulable_type' => $type, 'alert_schedulable_id' => $target], 'alert_schedulables');
$item = dbInsert(['schedule_id' => $alert_schedule->schedule_id, 'alert_schedulable_type' => $type, 'alert_schedulable_id' => $target], 'alert_schedulables');
if ($notes && $type = 'device' && get_user_pref('add_schedule_note_to_device', false)) {
$device_notes = dbFetchCell('SELECT `notes` FROM `devices` WHERE `device_id` = ?;', [$target]);
$device_notes.= ((empty($device_notes)) ? '' : PHP_EOL) . date("Y-m-d H:i") . ' Alerts delayed: ' . $notes;
@@ -159,7 +170,7 @@ if ($sub_type == 'new-maintenance') {
dbDelete('alert_schedulables', '`item_id`=?', array($item));
}
dbDelete('alert_schedule', '`schedule_id`=?', array($schedule_id));
dbDelete('alert_schedule', '`schedule_id`=?', [$alert_schedule->schedule_id]);
$message = 'Issue scheduling maintenance';
} else {
$status = 'ok';
@@ -176,7 +187,7 @@ if ($sub_type == 'new-maintenance') {
);
} elseif ($sub_type == 'parse-maintenance') {
$schedule_id = mres($_POST['schedule_id']);
$schedule = dbFetchRow('SELECT * FROM `alert_schedule` WHERE `schedule_id`=?', array($schedule_id));
$alert_schedule = \App\Models\AlertSchedule::findOrFail($schedule_id);
$items = [];
foreach (dbFetchRows('SELECT `alert_schedulable_type`, `alert_schedulable_id` FROM `alert_schedulables` WHERE `schedule_id`=?', [$schedule_id]) as $target) {
$id = $target['alert_schedulable_id'];
@@ -195,19 +206,9 @@ if ($sub_type == 'new-maintenance') {
];
}
$response = array(
'start' => $schedule['start'],
'end' => $schedule['end'],
'title' => $schedule['title'],
'notes' => $schedule['notes'],
'recurring' => $schedule['recurring'],
'start_recurring_dt' => ($schedule['start_recurring_dt'] != '0000-00-00' ? $schedule['start_recurring_dt']: '1970-01-02 00:00:01'),
'end_recurring_dt' => ($schedule['end_recurring_dt']!= '0000-00-00' ? $schedule['end_recurring_dt'] : '1970-01-02 00:00:01'),
'start_recurring_hr' => substr($schedule['start_recurring_hr'], 0, 5),
'end_recurring_hr' => substr($schedule['end_recurring_hr'], 0, 5),
'recurring_day' => $schedule['recurring_day'],
'targets' => $items,
);
$response = $alert_schedule->toArray();
$response['recurring_day'] = $alert_schedule->getOriginal('recurring_day');
$response['targets'] = $items;
} elseif ($sub_type == 'del-maintenance') {
$schedule_id = mres($_POST['del_schedule_id']);
dbDelete('alert_schedule', '`schedule_id`=?', array($schedule_id));

View File

@@ -93,13 +93,13 @@ if (\Auth::user()->hasGlobalAdmin()) {
<div class="form-group">
<label for="recurring_day" class="col-sm-4 control-label">Only on weekday: </label>
<div class="col-sm-8">
<div style="float: left;"><label><input type="checkbox" style="width: 20px;" class="form-control" id="recurring_day" name="recurring_day[]" value="1" />Mo</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" id="recurring_day" name="recurring_day[]" value="2" />Tu</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" id="recurring_day" name="recurring_day[]" value="3" />We</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" id="recurring_day" name="recurring_day[]" value="4" />Th</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" id="recurring_day" name="recurring_day[]" value="5" />Fr</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" id="recurring_day" name="recurring_day[]" value="6" />Sa</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" id="recurring_day" name="recurring_day[]" value="0" />Su</label></div>
<div style="float: left;"><label><input type="checkbox" style="width: 20px;" class="form-control" name="recurring_day[]" value="1" />Mo</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" name="recurring_day[]" value="2" />Tu</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" name="recurring_day[]" value="3" />We</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" name="recurring_day[]" value="4" />Th</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" name="recurring_day[]" value="5" />Fr</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" name="recurring_day[]" value="6" />Sa</label></div>
<div style="float: left;padding-left: 20px;"><label><input type="checkbox" style="width: 20px;" class="form-control" name="recurring_day[]" value="7" />Su</label></div>
</div>
</div>
</div>
@@ -126,7 +126,6 @@ $('#schedule-maintenance').on('hide.bs.modal', function (event) {
$('#schedule_id').val('');
$('#title').val('');
$('#notes').val('');
$('#recurring').val('');
$('#start').val(moment().format('YYYY-MM-DD HH:mm')).data("DateTimePicker").maxDate(false).minDate(moment());
$('#end').val(moment().add(1, 'hour').format('YYYY-MM-DD HH:mm')).data("DateTimePicker").maxDate(false).minDate(moment());
var $startRecurringDt = $('#start_recurring_dt');
@@ -138,7 +137,7 @@ $('#schedule-maintenance').on('hide.bs.modal', function (event) {
$('#start_recurring_hr').val('').data("DateTimePicker").minDate(false).maxDate(false);
$('#end_recurring_hr').val('').data("DateTimePicker").minDate(false).maxDate(false);
$('#recurring_day').prop('checked', false);
$("input[name='recurring_day[]']").prop('checked', false);
$("#recurring").bootstrapSwitch('state', false);
$('#recurring').val(0);
$('#norecurringgroup').show();
@@ -184,7 +183,7 @@ $('#schedule-maintenance').on('show.bs.modal', function (event) {
$('#end_recurring_dt').val('');
$('#start_recurring_hr').val('');
$('#end_recurring_hr').val('');
$('#recurring_day').prop('checked', false);
$("input[name='recurring_day[]']").prop('checked', false);
$("#recurring").bootstrapSwitch('state', false);
$('#recurring').val(0);
}else{
@@ -211,7 +210,7 @@ $('#schedule-maintenance').on('show.bs.modal', function (event) {
$("input[name='recurring_day[]'][value="+checkedday+"]").prop('checked', true);
});
}else{
$('#recurring_day').prop('checked', false);
$("input[name='recurring_day[]']").prop('checked', false);
}
$('#norecurringgroup').hide();

View File

@@ -31,8 +31,8 @@ if (Auth::user()->hasGlobalAdmin()) {
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger danger" id="sched-maintenance-removal" data-target="sched-maintenance-removal">Delete</button>
<input type="hidden" name="del_schedule_id" id="del_schedule_id">
<input type="hidden" name="type" id="type" value="schedule-maintenance">
<input type="hidden" name="sub_type" id="sub_type" value="del-maintenance">
<input type="hidden" name="type" value="schedule-maintenance">
<input type="hidden" name="sub_type" value="del-maintenance">
</form>
</div>
</div>

View File

@@ -77,12 +77,7 @@ var grid = $("#alert-schedule").bootgrid({
"<div class=\"col-sm-4 actionBar\"><p class=\"{{css.search}}\"></p><p class=\"{{css.actions}}\"></p></div></div></div>"
},
rowCount: [50, 100, 250, -1],
post: function () {
return {
id: "alert-schedule",
};
},
url: "ajax_table.php"
url: "ajax/table/alert-schedule"
}).on("loaded.rs.jquery.bootgrid", function() {
/* Executes after data is loaded and rendered */
grid.find(".command-edit").on("click", function(e) {

View File

@@ -307,7 +307,8 @@ If `devices.ignore = 0` or `macros.device = 1` condition is is set and ignore al
<script>
$('[type="checkbox"]').bootstrapSwitch('offColor', 'danger');
$("#maintenance").click(function() {
$("#maintenance").click(function(event) {
event.preventDefault();
var device_id = $(this).data("device_id");
var title = '<?=display($device['hostname']);?>';
var notes = '';

View File

@@ -1,133 +0,0 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. Please see LICENSE.txt at the top level of
* the source code distribution for details.
*/
$where = 1;
$sql = " FROM `alert_schedule` AS S WHERE $where";
if (!Auth::user()->hasGlobalRead()) {
$param[] = Auth::id();
}
if (isset($searchPhrase) && !empty($searchPhrase)) {
$sql .= " AND (`S`.`title` LIKE '%$searchPhrase%' OR `S`.`start` LIKE '%$searchPhrase%' OR `S`.`end` LIKE '%$searchPhrase%')";
}
$count_sql = "SELECT COUNT(`schedule_id`) $sql";
$total = dbFetchCell($count_sql, $param);
if (empty($total)) {
$total = 0;
}
if (isset($sort) && !empty($sort)) {
list($sort_column, $sort_order) = explode(' ', trim($sort));
if ($sort_column == 'status') {
$sort_by_status = true;
$sort = "`S`.`start` $sort_order";
}
} else {
$sort = '`S`.`start` DESC ';
}
$sql .= " ORDER BY $sort";
if (isset($current)) {
$limit_low = (($current * $rowCount) - ($rowCount));
$limit_high = $rowCount;
}
if ($rowCount != -1) {
$sql .= " LIMIT $limit_low,$limit_high";
}
$sql = "SELECT `S`.`schedule_id`, `S`.`recurring`, DATE_FORMAT(NOW(), '" . \LibreNMS\Config::get('dateformat.mysql.compact') . "') AS now, DATE_FORMAT(`S`.`start`, '" . \LibreNMS\Config::get('dateformat.mysql.compact') . "') AS `start`, DATE_FORMAT(`S`.`end`, '" . \LibreNMS\Config::get('dateformat.mysql.compact') . "') AS `end`, DATE_FORMAT(`S`.`start_recurring_dt`, '" . \LibreNMS\Config::get('dateformat.mysql.date') . "') AS `start_recurring_dt`, DATE_FORMAT(`S`.`end_recurring_dt`, '" . \LibreNMS\Config::get('dateformat.mysql.date') . "') AS `end_recurring_dt`, `S`.`start_recurring_hr`, `S`.`end_recurring_hr`, `S`.`recurring_day`, `S`.`title` $sql";
foreach (dbFetchRows($sql, $param) as $schedule) {
$status = 0;
if ($schedule['recurring'] == 0) {
$start = strtotime($schedule['start']);
$end = strtotime($schedule['end']);
$now = strtotime($schedule['now']);
if ($end < $now) {
$status = 1;
}
if ($now >= $start && $now < $end) {
$status = 2;
}
} else {
$start = strtotime($schedule['start_recurring_dt']);
$end = $schedule['end_recurring_dt'] != '' && $schedule['end_recurring_dt'] != '0000-00-00' ? strtotime($schedule['end_recurring_dt'].' '. $schedule['end_recurring_hr']) : 0;
$now = strtotime($schedule['now']);
if ($end < $now && $end != 0) {
$status =1;
}
if ($start <= $now && ($now <= $end || $end == 0)) {
$status = 2;
}
}
$table_rd = '';
if ($schedule['recurring_day'] != '') {
$array_days = array(
0 => 'Su',
1 => 'Mo',
2 => 'Tu',
3 => 'We',
4 => 'Th',
5 => 'Fr',
6 => 'Sa'
);
$array_rd = explode(',', $schedule['recurring_day']);
foreach ($array_rd as $key_rd => $val_rd) {
if (array_key_exists($val_rd, $array_days)) {
$table_rd .= $table_rd != '' ? ','. $array_days[$val_rd] : $array_days[$val_rd];
}
}
}
$response[] = array(
'title' => $schedule['title'],
'recurring' => $schedule['recurring'] == 1 ? 'yes' : 'no',
'start' => $schedule['recurring'] == 1 ? '' : $schedule['start'],
'end' => $schedule['recurring'] == 1 ? '' : $schedule['end'],
'start_recurring_dt' => $schedule['recurring'] == 0 || $schedule['start_recurring_dt'] == '0000-00-00' ? '' : $schedule['start_recurring_dt'],
'end_recurring_dt' => $schedule['recurring'] == 0 || $schedule['end_recurring_dt'] == '0000-00-00' ? '' : $schedule['end_recurring_dt'],
'start_recurring_hr' => $schedule['recurring'] == 0 ? '' : substr($schedule['start_recurring_hr'], 0, 5),
'end_recurring_hr' => $schedule['recurring'] == 0 ? '' : substr($schedule['end_recurring_hr'], 0, 5),
'recurring_day' => $schedule['recurring'] == 0 ? '' : $table_rd,
'id' => $schedule['schedule_id'],
'status' => $status,
);
}
if (isset($sort_by_status) && $sort_by_status) {
if ($sort_order == 'asc') {
usort($response, function ($a, $b) {
return $a['status'] - $b['status'];
});
} else {
usort($response, function ($a, $b) {
return $b['status'] - $a['status'];
});
}
}
$output = array(
'current' => $current,
'rowCount' => $rowCount,
'rows' => $response,
'total' => $total,
);
echo _json_encode($output);