Some Poller page cleanups (#11287)

* Some Poller page cleanups
Some queries loaded all devices into memory and the counted them, we should have sql count them if we only need a count.
Models should be singular
Use named routes for url generation
Try to keep presentation and data collection separated in blade and controller.

* Update PollerController.php

* Fix style

* Fix new PollerGroup references
This commit is contained in:
Tony Murray 2020-03-22 13:29:31 -05:00 committed by GitHub
parent 7e7320139a
commit 43a8616efd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 90 additions and 131 deletions

View File

@ -5,26 +5,18 @@ namespace App\Http\Controllers;
use App\Models\Device;
use App\Models\Poller;
use App\Models\PollerCluster;
use App\Models\PollerGroups;
use App\Models\PollerGroup;
use Carbon\Carbon;
use Illuminate\Http\Request;
use LibreNMS\Config;
class PollerController extends Controller
{
public $rrdstep;
public $defaultPollerId;
public $defaultGroup = [
'id' => 0,
'group_name' => 'General',
'descr' => ''
];
public $defaultPollerMarker = '(default Poller)';
public function __construct()
{
$this->authorizeResource(PollerGroups::class, 'poller_groups'); // FIXME is this correct? not a resource anymore
$this->rrdstep = \LibreNMS\Config::get('rrd.step');
$this->defaultPollerId = \LibreNMS\Config::get('distributed_poller_group');
$this->rrdstep = Config::get('rrd.step');
}
public function logTab(Request $request)
@ -35,33 +27,16 @@ class PollerController extends Controller
]);
}
// output for poller groups
public function groupsTab()
{
$group_list = PollerGroups::get();
# default poller_group
$defaultGroup = $this->defaultGroup;
$defaultGroup['devices'] = Device::where('poller_group', $defaultGroup['id'])->get();
$defaultGroup['is_default_poller'] = ($defaultGroup['id'] == $this->defaultPollerId) ? true : false;
# poller_groups
$poller_group_list = [];
foreach ($group_list as $group) {
$group['is_default_poller'] = ($group['id'] == $this->defaultPollerId) ? true : false;
$poller_group_list[] = $group;
}
return view('poller.groups', [
'current_tab' => 'groups',
'default_poller_marker' => $this->defaultPollerMarker,
'poller_groups' => $poller_group_list,
'default_poller_group' => $defaultGroup,
'poller_groups' => PollerGroup::query()->withCount('devices')->get(),
'default_group_id' => Config::get('distributed_poller_group'),
'ungrouped_count' => Device::where('poller_group', 0)->count(),
]);
}
// data output for poller view
public function pollerTab()
{
return view('poller.poller', [
@ -76,54 +51,38 @@ class PollerController extends Controller
return view('poller.performance', ['current_tab' => 'performance']);
}
protected function pollerStatus($poller)
protected function pollerStatus($poller, $last)
{
$old = $poller['now'] - strtotime($poller['last_polled']);
$since_last_poll = Carbon::parse($last)->diffInSeconds();
if ($old >= $this->rrdstep) {
$poller['row_class'] = 'danger';
} elseif ($old >= ($this->rrdstep * 0.95)) {
$poller['row_class'] = 'warning';
} else {
$poller['row_class'] = 'success';
}
$poller['long_not_polled'] = (\Auth::user()->hasGlobalAdmin() && ($old > ($this->rrdstep * 2))) ? true : false;
$poller->row_class = $this->checkTimeSinceLastPoll($since_last_poll);
$poller->long_not_polled = (\Auth::user()->hasGlobalAdmin() && ($since_last_poll > ($this->rrdstep * 2)));
return $poller;
}
private function poller()
{
$rows = Poller::orderBy('poller_name')->get();
$time = time();
$groups = [];
foreach ($rows as $poller) {
$poller['now'] = $time;
$poller = $this->pollerStatus($poller);
$groups[] = $poller;
}
return $groups;
return Poller::query()->orderBy('poller_name')->get()->map(function ($poller) {
return $this->pollerStatus($poller, $poller->last_polled);
});
}
private function pollerCluster()
{
$rows = PollerCluster::orderBy('poller_name')->get();
return PollerCluster::with('stats')->orderBy('poller_name')->get()->map(function ($poller) {
return $this->pollerStatus($poller, $poller->last_report);
});
}
$cluster = [];
foreach ($rows as $poller) {
$poller = $this->pollerStatus($poller);
$cluster[] = $poller;
private function checkTimeSinceLastPoll($seconds)
{
if ($seconds >= $this->rrdstep) {
return 'danger';
} elseif ($seconds >= ($this->rrdstep * 0.95)) {
return 'warning';
}
return $cluster;
return 'success';
}
}

View File

@ -2,13 +2,12 @@
namespace App\Http\Controllers;
use App\Models\Device;
use App\Models\PollerGroups;
use App\Models\PollerGroup;
use Illuminate\Http\Request;
class PollerGroupController extends Controller
{
public function destroy(Request $request, PollerGroups $pollergroup)
public function destroy(Request $request, PollerGroup $pollergroup)
{
if ($request->user()->isAdmin()) {
$pollergroup->delete();

View File

@ -1,6 +1,6 @@
<?php
/**
* Widget.php
* PollerCluster.php
*
* -Description-
*
@ -36,6 +36,6 @@ class PollerCluster extends Model
public function stats()
{
return $this->hasMany('App\Models\PollerClusterStats', 'parent_poller', 'id');
return $this->hasMany('App\Models\PollerClusterStat', 'parent_poller', 'id');
}
}

View File

@ -1,6 +1,6 @@
<?php
/**
* Widget.php
* PollerClusterStat.php
*
* -Description-
*
@ -27,7 +27,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PollerClusterStats extends Model
class PollerClusterStat extends Model
{
public $timestamps = false;
protected $primaryKey = 'id';

View File

@ -1,6 +1,6 @@
<?php
/**
* Widget.php
* PollerGroup.php
*
* -Description-
*
@ -27,7 +27,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PollerGroups extends Model
class PollerGroup extends Model
{
public $timestamps = false;
protected $primaryKey = 'id';
@ -40,7 +40,7 @@ class PollerGroups extends Model
{
parent::boot();
static::deleting(function (PollerGroups $pollergroup) {
static::deleting(function (PollerGroup $pollergroup) {
// handle device pollergroup fallback to default poller
$default_poller_id = \LibreNMS\Config::get('distributed_poller_group');
$pollergroup->devices()->update(['poller_group' => $default_poller_id]);

View File

@ -528,12 +528,12 @@
<li class="dropdown-submenu">
<a href="{{ url('poller') }}"><i class="fa fa-th-large fa-fw fa-lg" aria-hidden="true"></i> @lang('Poller')</a>
<ul class="dropdown-menu">
<li><a href="{{ url('poller') }}"><i class="fa fa-th-large fa-fw fa-lg" aria-hidden="true"></i> @lang('Poller')</a></li>
<li><a href="{{ route('poller') }}"><i class="fa fa-th-large fa-fw fa-lg" aria-hidden="true"></i> @lang('Poller')</a></li>
@config('distributed_poller')
<li><a href="{{ url('poller/groups') }}"><i class="fa fa-th fa-fw fa-lg" aria-hidden="true"></i> @lang('Groups')</a></li>
<li><a href="{{ route('poller.groups') }}"><i class="fa fa-th fa-fw fa-lg" aria-hidden="true"></i> @lang('Groups')</a></li>
@endconfig
<li><a href="{{ url('poller/performance') }}"><i class="fa fa-line-chart fa-fw fa-lg" aria-hidden="true"></i> @lang('Performance')</a></li>
<li><a href="{{ url('poller/log') }}"><i class="fa fa-file-text fa-fw fa-lg" aria-hidden="true"></i> @lang('Log')</a></li>
<li><a href="{{ route('poller.performance') }}"><i class="fa fa-line-chart fa-fw fa-lg" aria-hidden="true"></i> @lang('Performance')</a></li>
<li><a href="{{ route('poller.log') }}"><i class="fa fa-file-text fa-fw fa-lg" aria-hidden="true"></i> @lang('Log')</a></li>
</ul>
</li>
<li role="presentation" class="divider"></li>

View File

@ -18,24 +18,22 @@
<th>@lang('Description')</th>
<th>@lang('Action')</th>
</tr>
<tr id="{{ $default_poller_group['id'] }}">
<td>{{ $default_poller_group['id'] }}</td>
<td>{{ $default_poller_group['group_name'] }}@if($default_poller_group['is_default_poller']) {{ $default_poller_marker }}@endif</td>
<td><a href="/devices/poller_group={{ $default_poller_group['id'] }}">{{ $default_poller_group['devices']->count() }}</a></td>
<td>{{ $default_poller_group['descr'] }}</td>
<tr id="0">
<td>0</td>
<td>General @if($default_group_id == 0) (@lang('default')) @endif</td>
<td><a href="{{ url('devices/poller_group=0') }}">{{ $ungrouped_count }}</a></td>
<td></td>
<td>
</tr>
@foreach ($poller_groups as $group)
<tr id="{{ $group['id'] }}">
<td>{{ $group['id'] }}</td>
<td>{{ $group['group_name'] }}@if($group['is_default_poller']) {{ $default_poller_marker }}@endif</td>
<td><a href="/devices/poller_group={{ $group['id'] }}">{{ $group['devices']->count() }}</a></td>
<td>{{ $group['descr'] }}</td>
<tr id="{{ $group->id }}">
<td>{{ $group->id }}</td>
<td>{{ $group->group_name }}@if($group->id == $default_group_id) (@lang('default')) @endif</td>
<td><a href="{{ url('devices/poller_group=' . $group->id) }}">{{ $group->devices_count }}</a></td>
<td>{{ $group->descr }}</td>
<td>
@if($group['id'])
<button type="button" class="btn btn-success btn-xs" id="{{$group['id']}}" data-group_id="{{$group['id']}}" data-toggle="modal" data-target="#poller-groups">@lang('Edit')</button>
<button type="button" class="btn btn-danger btn-xs" id="{{$group['id']}}" data-group_id="{{$group['id']}}" data-toggle="modal" data-target="#confirm-delete">@lang('Delete')</button>
@endif
<button type="button" class="btn btn-success btn-xs" data-group_id="{{ $group->id }}" data-toggle="modal" data-target="#poller-groups">@lang('Edit')</button>
<button type="button" class="btn btn-danger btn-xs" data-group_id="{{ $group->id }}" data-toggle="modal" data-target="#confirm-delete">@lang('Delete')</button>
</td>
@endforeach
</tr>

View File

@ -5,20 +5,23 @@
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs">
<li role="presentation" @if( $current_tab == 'poller' ) ' class="active"' @endif>
<a href="/poller"><i class="fa fa-th-large fa-lg icon-theme" aria-hidden="true"></i>@lang('Poller')</a>
<li role="presentation" @if( $current_tab == 'poller' ) class="active" @endif>
<a href="{{ route('poller') }}"><i class="fa fa-th-large fa-lg icon-theme" aria-hidden="true"></i> @lang('Poller')</a>
</li>
<li role="presentation" @if( $current_tab == 'groups' ) ' class="active"' @endif>
<a href="/poller/groups"><i class="fa fa-th fa-lg icon-theme" aria-hidden="true"></i>@lang('Groups')</a>
@config('distributed_poller')
<li role="presentation" @if( $current_tab == 'groups' ) class="active" @endif>
<a href="{{ route('poller.groups') }}"><i class="fa fa-th fa-lg icon-theme" aria-hidden="true"></i> @lang('Groups')</a>
</li>
<li role="presentation" @if( $current_tab == 'performance' ) ' class="active"' @endif>
<a href="/poller/performance"><i class="fa fa-line-chart fa-lg icon-theme" aria-hidden="true"></i>@lang('Performance')</a>
@endconfig
<li role="presentation" @if( $current_tab == 'performance' ) class="active" @endif>
<a href="{{ route('poller.performance') }}"><i class="fa fa-line-chart fa-lg icon-theme" aria-hidden="true"></i> @lang('Performance')</a>
</li>
<li role="presentation" @if( $current_tab == 'log' ) ' class="active"' @endif>
<a href="/poller/log"><i class="fa fa-file-text fa-lg icon-theme" aria-hidden="true"></i>@lang('Log')</a>
<li role="presentation" @if( $current_tab == 'log' ) class="active" @endif>
<a href="{{ route('poller.log') }}"><i class="fa fa-file-text fa-lg icon-theme" aria-hidden="true"></i> @lang('Log')</a>
</li>
</ul>
@endsection
@section('content_footer')
</div>
</div>

View File

@ -12,7 +12,7 @@
<th data-column-id="hostname">@lang('Hostname')</th>
<th data-column-id="last_polled">@lang('Last Polled')</th>
<th data-column-id="poller_group">@lang('Poller Group')</th>
<th data-column-id="last_polled_timetaken" data-order="desc">@lang('Polling Duration (Seconds)')</th>
<th data-column-id="last_polled_timetaken" data-order="desc">@lang('Polling Duration') (@lang('Seconds'))</th>
</tr>
</thead>
</table>
@ -22,8 +22,8 @@
<script>
searchbar = "<div id=\"\{\{ctx.id\}\}\" class=\"\{\{css.header\}\}\"><div class=\"row\">"+
"<div class=\"col-sm-8 actionBar\"><span class=\"pull-left\">"+
"<a href='poller/log' class='btn btn-primary btn-sm @if($filter == 'unpolled') 'active' @endif'>All devices</a> "+
"<a href='poller/log?filter=unpolled' class='btn btn-danger btn-sm @if($filter == 'unpolled') 'active' @endif'>Unpolled devices</a>"+
"<a href='{{ route('poller.log') }}' class='btn btn-primary btn-sm @if($filter == 'unpolled') 'active' @endif'>All devices</a> "+
"<a href='{{ route('poller.log') }}?filter=unpolled' class='btn btn-danger btn-sm @if($filter == 'unpolled') 'active' @endif'>Unpolled devices</a>"+
"</div><div class=\"col-sm-4 actionBar\"><p class=\"\{\{css.search\}\}\"></p><p class=\"\{\{css.actions\}\}\"></p></div>";
var grid = $("#poll-log").bootgrid({

View File

@ -7,7 +7,7 @@
@parent
<br />
@if( $pollers )
@if( $pollers->isNotEmpty() )
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">@lang('Standard Pollers')</h3>
@ -37,7 +37,7 @@
</div>
@endif
@if( $poller_cluster )
@if( $poller_cluster->isNotEmpty() )
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">@lang('Poller Cluster Health')</h3>
@ -60,26 +60,26 @@
<th>@lang('Actions')</th>
</tr>
@foreach($poller_cluster as $poller)
@foreach($poller['stats'] as $stats)
<tr class="{{ $poller['row_class'] }}" id="row_{{ $poller['id'] }}">
@if( $loop->first )
<td rowspan="{{ $poller['stats']->count() }}">{{ $poller['poller_name'] }}</td>
<td rowspan="{{ $poller['stats']->count() }}"@if($poller['node_id'] == '') ' class="danger"' @endif>{{ $poller['node_id'] }}</td>
<td rowspan="{{ $poller['stats']->count() }}">{{ $poller['poller_version'] }}</td>
<td rowspan="{{ $poller['stats']->count() }}">{{ $poller['poller_groups'] }}</td>
<td rowspan="{{ $poller['stats']->count() }}">{{ $poller['last_report'] }}</td>
<td rowspan="{{ $poller['stats']->count() }}">@if( $poller['master'] ) "@lang('Yes')" @else "@lang('No')" @endif</td>
@endif
<td>{{ $stats['poller_type'] }}</td>
<td>{{ $stats['workers'] }}</td>
<td>{{ $stats['devices'] }}</td>
<td>{{ $stats['depth'] }}</td>
<td>{{ $stats['worker_seconds'] }} / {{ $stats['frequency'] * $stats['workers'] }}</td>
@if( $loop->first )
<td rowspan="{{ $poller['stats']->count() }}">@if( $poller['long_not_polled'] )<button type='button' class='btn btn-danger btn-sm' aria-label=@lang('Delete') data-toggle='modal' data-target='#confirm-delete' data-id='{{ $poller['id'] }}' data-pollertype='delete-cluster-poller' name='delete-cluster-poller'><i class='fa fa-trash' aria-hidden='true'></i></button>@endif</td>
@endif
</tr>
@endforeach
@foreach($poller->stats as $stat)
<tr class="{{ $poller['row_class'] }}" id="row_{{ $poller->id }}">
@if( $loop->first )
<td rowspan="{{ $poller->stats->count() }}">{{ $poller->poller_name }}</td>
<td rowspan="{{ $poller->stats->count() }}" @if($poller->node_id == '') class="danger" @endif>{{ $poller->node_id }}</td>
<td rowspan="{{ $poller->stats->count() }}">{{ $poller->poller_version }}</td>
<td rowspan="{{ $poller->stats->count() }}">{{ $poller->poller_groups }}</td>
<td rowspan="{{ $poller->stats->count() }}">{{ $poller->last_report }}</td>
<td rowspan="{{ $poller->stats->count() }}">@lang($poller->master ? 'Yes' : 'No')</td>
@endif
<td>{{ $stat->poller_type }}</td>
<td>{{ $stat->workers }}</td>
<td>{{ $stat->devices }}</td>
<td>{{ $stat->depth }}</td>
<td>{{ $stat->worker_seconds }} / {{ $stat->frequency * $stat->workers }}</td>
@if( $loop->first )
<td rowspan="{{ $poller->stats->count() }}">@if($poller->long_not_polled)<button type='button' class='btn btn-danger btn-sm' aria-label=@lang('Delete') data-toggle='modal' data-target='#confirm-delete' data-id='{{ $poller->id }}' data-pollertype='delete-cluster-poller' name='delete-cluster-poller'><i class='fa fa-trash' aria-hidden='true'></i></button>@endif</td>
@endif
</tr>
@endforeach
@endforeach
</table>
<small>

View File

@ -23,10 +23,10 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () {
// pages
Route::resource('device-groups', 'DeviceGroupController');
Route::get('poller', 'PollerController@pollerTab');
Route::get('poller/log', 'PollerController@logTab');
Route::get('poller/groups', 'PollerController@groupsTab');
Route::get('poller/performance', 'PollerController@performanceTab');
Route::get('poller', 'PollerController@pollerTab')->name('poller');
Route::get('poller/log', 'PollerController@logTab')->name('poller.log');
Route::get('poller/groups', 'PollerController@groupsTab')->name('poller.groups');
Route::get('poller/performance', 'PollerController@performanceTab')->name('poller.performance');
Route::get('locations', 'LocationController@index');
Route::resource('preferences', 'UserPreferencesController', ['only' => ['index', 'store']]);
Route::resource('users', 'UserController');