Cleanup and optimize the availability widget (#14329)

* Cleanup and optimize the availability widget
Default sort is display name
Sort applies to services too (services always last)
May need to refresh the page to get new css

* style

* We don't need request (lint fix)

* Wrong service field name
This commit is contained in:
Tony Murray
2022-09-09 11:22:58 -05:00
committed by GitHub
parent fbcaccdbd4
commit ad8580d694
6 changed files with 174 additions and 142 deletions

View File

@@ -25,11 +25,14 @@
namespace App\Http\Controllers\Widgets;
use App\Models\AlertSchedule;
use App\Models\Device;
use App\Models\DeviceGroup;
use App\Models\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use LibreNMS\Config;
use LibreNMS\Util\Url;
class AvailabilityMapController extends WidgetController
{
@@ -44,7 +47,7 @@ class AvailabilityMapController extends WidgetController
'color_only_select' => 0,
'show_disabled_and_ignored' => 0,
'mode_select' => 0,
'order_by' => Config::get('webui.availability_map_sort_status') ? 'status' : 'hostname',
'order_by' => Config::get('webui.availability_map_sort_status') ? 'status' : 'display-name',
'device_group' => null,
];
}
@@ -53,20 +56,8 @@ class AvailabilityMapController extends WidgetController
{
$data = $this->getSettings();
$devices = [];
$device_totals = [];
$services = [];
$services_totals = [];
$mode = $data['mode_select'];
if ($mode == 0 || $mode == 2) {
[$devices, $device_totals] = $this->getDevices($request);
}
if ($mode > 0) {
[$services, $services_totals] = $this->getServices($request);
}
$data['device'] = Device::first();
[$devices, $device_totals] = $this->getDevices();
[$services, $services_totals] = $this->getServices();
$data['devices'] = $devices;
$data['device_totals'] = $device_totals;
@@ -81,142 +72,206 @@ class AvailabilityMapController extends WidgetController
return view('widgets.settings.availability-map', $this->getSettings(true));
}
/**
* @param Request $request
* @return array
*/
private function getDevices(Request $request)
private function getDevices(): array
{
$settings = $this->getSettings();
if ($settings['mode_select'] == 1) { // services only
return [[], []];
}
// filter for by device group or show all
if ($settings['device_group']) {
$device_query = DeviceGroup::find($settings['device_group'])->devices()->hasAccess($request->user());
$device_query = DeviceGroup::find($settings['device_group'])->devices()->hasAccess(Auth::user());
} else {
$device_query = Device::hasAccess($request->user());
$device_query = Device::hasAccess(Auth::user());
}
if (! $settings['show_disabled_and_ignored']) {
$device_query->isNotDisabled();
}
$devices = $device_query->select(['devices.device_id', 'hostname', 'sysName', 'display', 'status', 'uptime', 'last_polled', 'disabled', 'disable_notify', 'location_id'])->get();
$devices = $device_query->select(['devices.device_id', 'hostname', 'sysName', 'display', 'status', 'uptime', 'last_polled', 'disabled', 'ignore'])->get();
// process status
$uptime_warn = Config::get('uptime_warning', 86400);
$uptime_warn = (int) Config::get('uptime_warning', 86400);
$check_maintenance = AlertSchedule::isActive()->exists(); // check if any maintenance schedule is active
$totals = ['warn' => 0, 'up' => 0, 'down' => 0, 'maintenance' => 0, 'ignored' => 0, 'disabled' => 0];
$data = [];
// add another field for the selected device label
$label_type = $settings['color_only_select'];
foreach ($devices as $device) {
$row = ['device' => $device];
if ($device->disabled) {
$totals['disabled']++;
$row['stateName'] = 'disabled';
$row['labelClass'] = 'blackbg';
} elseif ($device->disable_notify) {
$totals['ignored']++;
$row['stateName'] = 'alert-dis';
$row['labelClass'] = 'label-default';
} elseif ($device->status == 1) {
if (($device->uptime < $uptime_warn) && ($device->uptime != 0)) {
$totals['warn']++;
$row['stateName'] = 'warn';
$row['labelClass'] = 'label-warning';
} else {
$totals['up']++;
$row['stateName'] = 'up';
$row['labelClass'] = 'label-success';
}
} else {
$totals['down']++;
$row['stateName'] = 'down';
$row['labelClass'] = 'label-danger';
}
// parse state and count
[$state_name, $class] = $this->parseDeviceState($device, $uptime_warn);
$totals[$state_name]++;
if ($device->isUnderMaintenance()) {
$row['labelClass'] = 'label-default';
if ($check_maintenance && $device->isUnderMaintenance()) {
$class = 'label-default';
$totals['maintenance']++;
}
if ($label_type == 1) {
$row['label'] = null;
} elseif ($label_type == 4) {
$row['label'] = strtolower($device->shortDisplayName());
} elseif ($label_type == 2) {
$row['label'] = strtolower($device->hostname);
} elseif ($label_type == 3) {
$row['label'] = strtolower($device->sysName);
} else {
$row['label'] = $device->status;
if ($settings['type'] == 1) {
$class = "availability-map-oldview-box-$state_name";
}
$data[] = $row;
$data[] = [
'status' => $device->status,
'link' => Url::deviceUrl($device),
'tooltip' => $this->getDeviceTooltip($device, $state_name),
'label' => $this->getDeviceLabel($device, $state_name), // add another field for the selected label
'labelClass' => $class,
];
}
// now apply sorting, depending on the selected device label
$order_by = $settings['order_by'];
if ($order_by == 'status') {
usort($data, function ($l, $r) {
$retval = $l['device']->status <=> $r['device']->status;
if ($retval == 0) {
$retval = $l['label'] <=> $r['label'];
}
return $retval;
});
} elseif ($order_by == 'label') {
usort($data, function ($l, $r) {
return $l['label'] <=> $r['label'];
});
}
$this->sort($data);
return [$data, $totals];
}
private function getServices($request)
private function getServices(): array
{
$settings = $this->getSettings();
// filter for by device group or show all
if ($settings['device_group']) {
$services_query = DeviceGroup::find($settings['device_group'])->services()->hasAccess($request->user());
} else {
$services_query = Service::hasAccess($request->user());
if ($settings['mode_select'] == 0) { // devices only
return [[], []];
}
if ($settings['order_by'] == 'status') {
$services_query->orderBy('service_status', 'DESC')->orderBy('service_type');
} elseif ($settings['order_by'] == 'hostname') {
$services_query->leftJoin('devices', 'services.device_id', 'devices.device_id')->orderBy('hostname')->orderBy('service_type');
// filter for by device group or show all
if ($settings['device_group']) {
$services_query = DeviceGroup::find($settings['device_group'])->services()->hasAccess(Auth::user());
} else {
$services_query = Service::hasAccess(Auth::user());
}
$services = $services_query->with(['device' => function ($query) {
$query->select(['devices.device_id', 'hostname', 'sysName']);
}])->select(['service_id', 'services.device_id', 'service_type', 'service_desc', 'service_status'])->get();
$services = $services_query->with([
'device' => function ($query) {
$query->select(['devices.device_id', 'hostname', 'sysName', 'display']);
},
])->select(['service_id', 'services.device_id', 'service_type', 'service_name', 'service_desc', 'service_status'])->get();
// process status
$totals = ['warn' => 0, 'up' => 0, 'down' => 0];
$data = [];
foreach ($services as $service) {
$row = ['service' => $service];
if ($service->service_status == 0) {
$row['labelClass'] = 'label-success';
$row['stateName'] = 'up';
$totals['up']++;
} elseif ($service->service_status == 1) {
$row['labelClass'] = 'label-warning';
$row['stateName'] = 'warn';
$totals['warn']++;
} else {
$row['labelClass'] = 'label-danger';
$row['stateName'] = 'down';
$totals['down']++;
[$state_name, $class] = $this->parseServiceState($service);
$totals[$state_name]++;
if ($settings['type'] == 1) {
$class = "availability-map-oldview-box-$state_name";
}
$data[] = $row;
$data[] = [
'status' => $service->service_status,
'link' => Url::deviceUrl($service->device),
'tooltip' => $this->getServiceTooltip($service),
'label' => $this->getServiceLabel($service, $state_name),
'labelClass' => $class,
];
}
$this->sort($data);
return [$data, $totals];
}
private function sort(array &$data): void
{
switch ($this->getSettings()['order_by']) {
case 'status':
usort($data, function ($l, $r) {
return ($l['status'] <=> $r['status']) ?: strcasecmp($l['label'], $r['label']);
});
break;
case 'label':
usort($data, function ($l, $r) {
return strcasecmp($l['label'], $r['label']);
});
break;
default: // device display name (tooltip starts with the display name)
usort($data, function ($l, $r) {
return strcasecmp($l['tooltip'], $r['tooltip']) ?: strcasecmp($l['label'], $r['label']);
});
}
}
private function getDeviceLabel(Device $device, string $state_name): string
{
switch ($this->getSettings()['color_only_select']) {
case 1:
return '';
case 4:
return $device->shortDisplayName();
case 2:
return strtolower($device->hostname);
case 3:
return strtolower($device->sysName);
default:
return __($state_name);
}
}
private function getServiceLabel(Service $service, string $state_name): string
{
if ($this->getSettings()['color_only_select'] == 1) {
return '';
}
return $service->service_type . ' - ' . __($state_name);
}
private function getDeviceTooltip(Device $device, string $state_name): string
{
$tooltip = $device->displayName();
$time = $device->formatDownUptime(true);
if ($time) {
$tooltip .= ' - ' . ($state_name == 'down' ? 'downtime ' : '') . $time;
}
return $tooltip;
}
private function getServiceTooltip(Service $service): string
{
$tooltip = $service->device->displayName() . ' [' . $service->service_type . ']';
$description = $service->service_name ?: $service->service_desc;
if ($description) {
$tooltip .= ' ' . $description;
}
return $tooltip;
}
private function parseDeviceState(Device $device, int $uptime_warn): array
{
if ($device->disabled) {
return ['disabled', 'blackbg'];
}
if ($device->ignore) {
return ['ignored', 'label-default'];
}
if ($device->status == 1) {
if (($device->uptime < $uptime_warn) && ($device->uptime != 0)) {
return ['warn', 'label-warning'];
}
return ['up', 'label-success'];
}
return ['down', 'label-danger'];
}
private function parseServiceState(Service $service): array
{
if ($service->service_status == 0) {
return ['up', 'label-success'];
}
if ($service->service_status == 1) {
return ['warn', 'label-warning'];
}
return ['down', 'label-danger'];
}
}

View File

@@ -2060,7 +2060,8 @@ label {
margin:2px;
}
.widget-availability-fixed {
.widget-availability:empty {
display: inline;
min-width: 2.4em;
min-height: 1.3em;
max-height: 1.6em;

View File

@@ -7630,16 +7630,6 @@ parameters:
count: 1
path: app/Http/Controllers/UserPreferencesController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Widgets\\\\AvailabilityMapController\\:\\:getServices\\(\\) has no return type specified\\.$#"
count: 1
path: app/Http/Controllers/Widgets/AvailabilityMapController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Widgets\\\\AvailabilityMapController\\:\\:getServices\\(\\) has parameter \\$request with no type specified\\.$#"
count: 1
path: app/Http/Controllers/Widgets/AvailabilityMapController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Widgets\\\\DeviceSummaryController\\:\\:getData\\(\\) has no return type specified\\.$#"
count: 1

View File

@@ -42,7 +42,7 @@
<link href="{{ asset('css/query-builder.default.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=20210421" rel="stylesheet">
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=20220910" rel="stylesheet">
<link href="{{ asset('css/' . LibreNMS\Config::get('applied_site_style', 'light') . '.css?ver=632417643') }}" rel="stylesheet">
@foreach(LibreNMS\Config::get('webui.custom_css', []) as $custom_css)
<link href="{{ $custom_css }}" rel="stylesheet">

View File

@@ -26,35 +26,21 @@
<br style="clear:both;">
@foreach($devices as $row)
<a href="@deviceUrl($row['device'])" title="{{$row['device']->displayName() }}@if($row['stateName'] == 'up' or $row['stateName'] == 'warn')@if($row['device']->formatDownUptime(true)) - @endif{{ $row['device']->formatDownUptime(true) }}@elseif($row['stateName'] == 'down')@if($row['device']->formatDownUptime(true)) - downtime @endif{{$row['device']->formatDownUptime(true)}}@endif">
<a href="{{ $row['link'] }}" title="{{$row['tooltip'] }}">
@if($type == 0)
@if($color_only_select == 1)
<span class="label {{ $row['labelClass'] }} widget-availability-fixed widget-availability label-font-border"> </span>
@elseif($color_only_select == 2)
<span class="label {{ $row['labelClass'] }} widget-availability label-font-border">{{ $row['device']->hostname }}</span>
@elseif($color_only_select == 3)
<span class="label {{ $row['labelClass'] }} widget-availability label-font-border">{{ $row['device']->sysName }}</span>
@elseif($color_only_select == 4)
<span class="label {{ $row['labelClass'] }} widget-availability label-font-border">{{ $row['device']->shortDisplayName() }}</span>
@else
<span class="label {{ $row['labelClass'] }} widget-availability label-font-border">{{ __($row['stateName']) }}</span>
@endif
<span class="label {{ $row['labelClass'] }} widget-availability label-font-border">{{ $row['label'] }}</span>
@else
<div class="availability-map-oldview-box-{{ $row['stateName'] }}" style="width:{{ $tile_size }}px;height:{{ $tile_size }}px;"></div>
<div class="{{ $row['labelClass'] }}" style="width:{{ $tile_size }}px;height:{{ $tile_size }}px;"></div>
@endif
</a>
@endforeach
@foreach($services as $row)
<a href="@deviceUrl($row['service']->device, ['tab' => 'services'])" title="{{ $row['service']->device->displayName() }} - {{ $row['service']->service_type }} - {{ $row['service']->service_desc }}">
<a href="{{ $row['link'] }}" title="{{$row['tooltip'] }}">
@if($type == 0)
@if($color_only_select)
<span class="label {{ $row['labelClass'] }} widget-availability-fixed widget-availability label-font-border"> </span>
@else
<span class="label {{ $row['labelClass'] }} widget-availability label-font-border">{{ $row['service']->service_type }} - {{ $row['stateName'] }}</span>
@endif
<span class="label {{ $row['labelClass'] }} widget-availability label-font-border">{{ $row['label'] }}</span>
@else
<div class="availability-map-oldview-box-{{ $row['stateName'] }}" style="width:{{ $tile_size }}px;height:{{ $tile_size }}px;"></div>
<div class="{{ $row['labelClass'] }}" style="width:{{ $tile_size }}px;height:{{ $tile_size }}px;"></div>
@endif
</a>
@endforeach

View File

@@ -21,7 +21,7 @@
<option value="4" @if($color_only_select == 4) selected @endunless>{{ __('Display Name') }}</option>
<option value="2" @if($color_only_select == 2) selected @endunless>{{ __('Hostname') }}</option>
<option value="3" @if($color_only_select == 3) selected @endunless>{{ __('SNMP sysName') }}</option>
<option value="0" @unless($color_only_select) selected @endunless>{{ __('Device Status') }}</option>
<option value="0" @unless($color_only_select) selected @endunless>{{ __('Status') }}</option>
</select>
</div>
@@ -54,6 +54,7 @@
<select class="form-control" name="order_by" id="order_by-{{ $id }}">
<option value="label" @if($order_by == 'label') selected @endif>{{ __('Label') }}</option>
<option value="status" @if($order_by == 'status') selected @endif>{{ __('Status') }}</option>
<option value="display-name" @if($order_by == 'display-name') selected @endif>{{ __('Display Name') }}</option>
</select>
</div>
@@ -72,7 +73,6 @@
init_select2('#device_group-{{ $id }}', 'device-group', {});
function toggle_availability_type(el, id) {
console.log(el.value);
if (el.value === '0') {
$('#tile_size-group-' + id).hide();
$('#color_only_select-group-' + id).show();