From f10cbddacc647cd9cea19694c666620c80d8a8e2 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Thu, 3 Jan 2019 16:42:12 -0600 Subject: [PATCH] Refactored Alert schedule (#9514) * add AlertSchedule model and relationships change table structure to match the expected layout * Update maint schedule map ui * better index name * Laravel queries fix some issues with the ui: restricting start incorrectly and loading empty days error * handle date limiting properly * Another attempt add schedule constraints * use Auth * Update WorldMap widget to use check isUnderMaintenance * Rename 275.sql to 276.sql * Rename 276.sql to 277.sql --- .../Widgets/WorldMapController.php | 4 +- app/Models/AlertSchedule.php | 86 ++++++++ app/Models/Device.php | 26 +++ app/Models/DeviceGroup.php | 6 + app/Providers/AppServiceProvider.php | 2 + .../forms/schedule-maintenance.inc.php | 36 +++- html/includes/modal/alert_schedule.inc.php | 190 ++++++++---------- includes/alerts.inc.php | 15 +- misc/db_schema.yaml | 18 +- sql-schema/277.sql | 7 + 10 files changed, 249 insertions(+), 141 deletions(-) create mode 100644 app/Models/AlertSchedule.php create mode 100644 sql-schema/277.sql diff --git a/app/Http/Controllers/Widgets/WorldMapController.php b/app/Http/Controllers/Widgets/WorldMapController.php index 3584c27e0d..655648f64a 100644 --- a/app/Http/Controllers/Widgets/WorldMapController.php +++ b/app/Http/Controllers/Widgets/WorldMapController.php @@ -62,6 +62,7 @@ class WorldMapController extends WidgetController ->whereIn('status', $status) ->get() ->filter(function ($device) use ($status) { + /** @var Device $device */ if (!($device->location_id && $device->location->coordinatesValid())) { return false; } @@ -74,8 +75,7 @@ class WorldMapController extends WidgetController $device->markerIcon = 'redMarker'; $device->zOffset = 10000; -// TODO if ($device->isUnderMaintenance()) - if (false) { + if ($device->isUnderMaintenance()) { if ($status == 0) { return false; } diff --git a/app/Models/AlertSchedule.php b/app/Models/AlertSchedule.php new file mode 100644 index 0000000000..f3b0f2c3cf --- /dev/null +++ b/app/Models/AlertSchedule.php @@ -0,0 +1,86 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2018 Tony Murray + * @author Tony Murray + */ + +namespace App\Models; + +use DB; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; + +class AlertSchedule extends Model +{ + public $timestamps = false; + protected $table = 'alert_schedule'; + protected $primaryKey = 'schedule_id'; + + // ---- Query scopes ---- + + public function scopeIsActive($query) + { + // TODO use Carbon? + return $query->where(function ($query) { + $query->where(function ($query) { + // Non recurring simply between start and end + $query->where('recurring', 0) + ->where('start', '<=', DB::raw('NOW()')) + ->where('end', '>=', DB::raw('NOW()')); + })->orWhere(function ($query) { + $query->where('recurring', 1) + // Check the time is after the start date and before the end date, or end date is not set + ->where(function ($query) { + $query->where('start_recurring_dt', '<=', DB::raw("date_format(NOW(), '%Y-%m-%d')")) + ->where(function ($query) { + $query->where('end_recurring_dt', '>=', DB::raw("date_format(NOW(), '%Y-%m-%d')")) + ->orWhereNull('end_recurring_dt') + ->orWhere('end_recurring_dt', '0000-00-00') + ->orWhere('end_recurring_dt', ''); + }); + }) + // Check the time is between the start and end hour/minutes/seconds + ->where('start_recurring_hr', '<=', DB::raw("date_format(NOW(), '%H:%i:%s')")) + ->where('end_recurring_hr', '>=', DB::raw("date_format(NOW(), '%H:%i:%s')")) + // Check we are on the correct day of the week + ->where(function ($query) { + /** @var Builder $query */ + $query->where('recurring_day', 'like', DB::raw("CONCAT('%', date_format(NOW(), '%w'), '%')")) + ->orWhereNull('recurring_day') + ->orWhere('recurring_day', ''); + }); + }); + }); + } + + // ---- Define Relationships ---- + + public function devices() + { + return $this->morphedByMany('App\Models\Device', 'alert_schedulable', 'alert_schedulables', 'schedule_id', 'schedule_id'); + } + + public function deviceGroups() + { + return $this->morphedByMany('App\Models\DeviceGroup', 'alert_schedulable'); + } +} diff --git a/app/Models/Device.php b/app/Models/Device.php index f0aee95353..9ea78812eb 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -151,6 +151,27 @@ class Device extends BaseModel return $this->hostname; } + public function isUnderMaintenance() + { + $query = AlertSchedule::isActive() + ->join('alert_schedulables', 'alert_schedule.schedule_id', 'alert_schedulables.schedule_id') + ->where(function ($query) { + $query->where(function ($query) { + $query->where('alert_schedulable_type', 'device') + ->where('alert_schedulable_id', $this->device_id); + }); + + if ($this->groups) { + $query->orWhere(function ($query) { + $query->where('alert_schedulable_type', 'device_group') + ->whereIn('alert_schedulable_id', $this->groups->pluck('id')); + }); + } + }); + + return $query->exists(); + } + /** * Get the shortened display name of this device. * Length is always overridden by shorthost_target_length. @@ -434,6 +455,11 @@ class Device extends BaseModel return $this->hasMany('App\Models\Alert', 'device_id'); } + public function alertSchedules() + { + return $this->morphToMany('App\Models\AlertSchedule', 'alert_schedulable', 'alert_schedulables', 'schedule_id', 'schedule_id'); + } + public function applications() { return $this->hasMany('App\Models\Application', 'device_id'); diff --git a/app/Models/DeviceGroup.php b/app/Models/DeviceGroup.php index baa793e36f..3561d6f803 100644 --- a/app/Models/DeviceGroup.php +++ b/app/Models/DeviceGroup.php @@ -302,6 +302,12 @@ class DeviceGroup extends BaseModel // ---- Define Relationships ---- + + public function alertSchedules() + { + return $this->morphToMany('App\Models\AlertSchedule', 'alert_schedulable', 'alert_schedulables', 'schedule_id', 'schedule_id'); + } + public function rules() { return $this->belongsToMany('App\Models\AlertRule', 'alert_group_map', 'group_id', 'rule_id'); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d53d77a165..f9ca34a6b9 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -80,6 +80,8 @@ class AppServiceProvider extends ServiceProvider Relation::morphMap([ 'interface' => \App\Models\Port::class, 'sensor' => \App\Models\Sensor::class, + 'device' => \App\Models\Device::class, + 'device_group' => \App\Models\DeviceGroup::class, ]); } diff --git a/html/includes/forms/schedule-maintenance.inc.php b/html/includes/forms/schedule-maintenance.inc.php index 8ef953edbd..5d08d3af45 100644 --- a/html/includes/forms/schedule-maintenance.inc.php +++ b/html/includes/forms/schedule-maintenance.inc.php @@ -123,16 +123,21 @@ if ($sub_type == 'new-maintenance') { $fail = 0; if ($update == 1) { - dbDelete('alert_schedule_items', '`schedule_id`=?', array($schedule_id)); + dbDelete('alert_schedulables', '`schedule_id`=?', array($schedule_id)); } foreach ($_POST['maps'] as $target) { - $target = target_to_id($target); - $item = dbInsert(array('schedule_id' => $schedule_id, 'target' => $target), 'alert_schedule_items'); - if ($notes && get_user_pref('add_schedule_note_to_device', false)) { - $device_notes = dbFetchCell('SELECT `notes` FROM `devices` WHERE `device_id` = ?;', array($target)); + $type = 'device'; + if (starts_with($target, 'g')) { + $type = 'device_group'; + $target = substr($target, 1); + } + + $item = dbInsert(['schedule_id' => $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; - dbUpdate(array('notes' => $device_notes), 'devices', '`device_id` = ?', array($target)); + dbUpdate(['notes' => $device_notes], 'devices', '`device_id` = ?', [$target]); } if ($item > 0) { array_push($items, $item); @@ -143,7 +148,7 @@ if ($sub_type == 'new-maintenance') { if ($fail == 1 && $update == 0) { foreach ($items as $item) { - dbDelete('alert_schedule_items', '`item_id`=?', array($item)); + dbDelete('alert_schedulables', '`item_id`=?', array($item)); } dbDelete('alert_schedule', '`schedule_id`=?', array($schedule_id)); @@ -164,10 +169,19 @@ 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)); - $items = array(); - foreach (dbFetchRows('SELECT `target` FROM `alert_schedule_items` WHERE `schedule_id`=?', array($schedule_id)) as $targets) { - $targets = id_to_target($targets['target']); - array_push($items, $targets); + $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']; + if ($target['alert_schedulable_type'] == 'device_group') { + $text = dbFetchCell('SELECT name FROM device_groups WHERE id = ?', [$id]); + $id = 'g' . $id; + } else { + $text = dbFetchCell('SELECT hostname FROM devices WHERE device_id = ?', [$id]); + } + $items[] = [ + 'id' => $id, + 'text' => $text, + ]; } $response = array( diff --git a/html/includes/modal/alert_schedule.inc.php b/html/includes/modal/alert_schedule.inc.php index 567e71cbbb..ca1cd2211f 100644 --- a/html/includes/modal/alert_schedule.inc.php +++ b/html/includes/modal/alert_schedule.inc.php @@ -12,9 +12,7 @@ * the source code distribution for details. */ -use LibreNMS\Authentication\LegacyAuth; - -if (LegacyAuth::user()->hasGlobalAdmin()) { +if (\Auth::user()->hasGlobalAdmin()) { ?> @@ -75,25 +73,25 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
- +
- +
- +
- +
@@ -110,17 +108,9 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
- -
- -
-
- -
-
-
-
- + +
+
@@ -135,32 +125,31 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {