Graphing Device Dependency (#10916)

* graphing Device Dependency

* remove uneeded code

* fix link to go to device Overview

* rebuild dependency map to blade/laravel

* remove uneeded file

* code climate fixes

* remove not used code

* remove blank line

* device access filter optimization

* remove blank line

* force new travis check ...

* fix deviceLink configuration

* .

* rewrite code

* moving to Maps Namespace

* retrigger tests

* some code changes

* further renaming

* retrigger tests

* some code improvements

* .

* move vis.min.js in javascript section

* Device Dependency for Device Groups

* .

* codeclimate fixes

* show child/parents of Device - even if not in Device Group

* Device Highlighting

* add missing function

* replace hardcoded get params with ->get in Controller

* redesign Controller

* code climate fixes

* fix binary operator to 'or'

* remove 'or'

* code climate fixes

* further Code changes

* move loadMissing behind merge
This commit is contained in:
SourceDoctor
2020-01-07 14:23:36 +01:00
committed by Jellyfrog
parent 03b6408e06
commit b8f2b094d7
5 changed files with 280 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
<?php
/**
* DependencyController.php
*
* Controller for graphing Relationships
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2019 Thomas Berberich
* @author Thomas Berberich <sourcehhdoctor@gmail.com>
*/
namespace App\Http\Controllers\Maps;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Util\Url;
class DeviceDependencyController extends MapController
{
protected static function deviceList($request)
{
$group_id = $request->get('group');
if (! $group_id) {
return Device::hasAccess($request->user())->with('parents', 'location')->get();
}
$devices = Device::inDeviceGroup($group_id)
->hasAccess($request->user())
->with([
'location',
'parents' => function ($query) use ($request) {
$query->hasAccess($request->user());
},
'children' => function ($query) use ($request) {
$query->hasAccess($request->user());
}])
->get();
return $devices->merge($devices->map->only('children', 'parents')->flatten())->loadMissing('parents', 'location');
}
// Device Dependency Map
public function dependencyMap(Request $request)
{
$group_id = $request->get('group');
$highlight_node = $request->get('highlight_node');
$dependencies = [];
$devices_by_id = [];
$device_list = [];
// List all devices
foreach (self::deviceList($request) as $device) {
$device_list[] = ['id' => $device->device_id, 'label' => $device->hostname];
// List all Device
$devices_by_id[] = array_merge(
[
'id' => $device->device_id,
'label' => $device->shortDisplayName(),
'title' => Url::deviceLink($device, null, [], 0, 0, 0, 0),
'shape' => 'box',
],
$this->deviceStyle($device, $highlight_node)
);
// List all Device Dependencies
$parents = $device->parents;
foreach ($parents as $parent) {
$dependencies[] = [
'from' => $device->device_id,
'to' => $parent->device_id,
'width' => 2,
];
};
}
array_multisort(array_column($device_list, 'label'), SORT_ASC, $device_list);
$data = [
'device_list' => $device_list,
'group_id' => $group_id,
'highlight_node' => $highlight_node,
'node_count' => count($devices_by_id),
'options' => $this->visOptions(),
'nodes' => json_encode(array_values($devices_by_id)),
'edges' => json_encode($dependencies),
];
return view('map.device-dependency', $data);
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* DependencyController.php
*
* Controller for graphing Relationships
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2019 Thomas Berberich
* @author Thomas Berberich <sourcehhdoctor@gmail.com>
*/
namespace App\Http\Controllers\Maps;
use App\Http\Controllers\Controller;
use LibreNMS\Config;
class MapController extends Controller
{
protected function visOptions()
{
return Config::get('network_map_vis_options');
}
protected function nodeDisabledStyle()
{
return ['color' => [
'highlight' => [
'background' => Config::get('network_map_legend.di.node'),
],
'border' => Config::get('network_map_legend.di.border'),
'background' => Config::get('network_map_legend.di.node'),
],
];
}
protected function nodeHighlightStyle()
{
return ['color' => [
'highlight' => [
'border' => Config::get('network_map_legend.highlight.border'),
],
'border' => Config::get('network_map_legend.highlight.border'),
],
'borderWidth' => Config::get('network_map_legend.highlight.borderWidth'),
];
}
protected function nodeDownStyle()
{
return ['color' => [
'highlight' => [
'background' => Config::get('network_map_legend.dn.node'),
'border' => Config::get('network_map_legend.dn.border'),
],
'border' => Config::get('network_map_legend.dn.border'),
'background' => Config::get('network_map_legend.dn.node'),
],
];
}
protected function nodeUpStyle()
{
return [];
}
protected function deviceStyle($device, $highlight_node = 0)
{
if ($device->disabled) {
$device_style = $this->nodeDisabledStyle();
} elseif (! $device->status) {
$device_style = $this->nodeDownStyle();
} else {
$device_style = $this->nodeUpStyle();
}
if ($device->device_id == $highlight_node) {
$device_style = array_merge($device_style, $this->nodeHighlightStyle());
}
return $device_style;
}
}

View File

@@ -34,6 +34,20 @@
<li><a href="{{ url('availability-map') }}"><i class="fa fa-arrow-circle-up fa-fw fa-lg" <li><a href="{{ url('availability-map') }}"><i class="fa fa-arrow-circle-up fa-fw fa-lg"
aria-hidden="true"></i> @lang('Availability') aria-hidden="true"></i> @lang('Availability')
</a></li> </a></li>
<li><a href="{{ url('maps/devicedependency') }}"><i class="fa fa-chain fa-fw fa-lg"
aria-hidden="true"></i> @lang('Device Dependency')</a></li>
@if($device_groups->isNotEmpty())
<li class="dropdown-submenu"><a><i class="fa fa-chain fa-fw fa-lg"
aria-hidden="true"></i> @lang('Device Groups Dependencies')
</a>
<ul class="dropdown-menu scrollable-menu">
@foreach($device_groups as $group)
<li><a href="{{ url("maps/devicedependency?group=$group->id") }}" title="{{ $group->desc }}"><i class="fa fa-chain fa-fw fa-lg" aria-hidden="true"></i>
{{ ucfirst($group->name) }}
</a></li>
@endforeach
</ul></li>
@endif
<li><a href="{{ url('map') }}"><i class="fa fa-sitemap fa-fw fa-lg" <li><a href="{{ url('map') }}"><i class="fa fa-sitemap fa-fw fa-lg"
aria-hidden="true"></i> @lang('Network')</a></li> aria-hidden="true"></i> @lang('Network')</a></li>
@if($device_groups->isNotEmpty()) @if($device_groups->isNotEmpty())

View File

@@ -0,0 +1,59 @@
@extends('layouts.librenmsv1')
@section('title', __('Device Dependency Map'))
@section('content')
@if($node_count)
<div class="pull-right">
<select name="highlight_node" id="highlight_node" class="input-sm" onChange="highlightNode()";>
<option value="0">None</option>
@foreach($device_list as $device)
<option value="{{ $device['id'] }}">{{ $device['label'] }}</option>
@endforeach
</select>
</div>
<div id="visualization"></div>
@else
<div class="alert alert-success" role="alert">@lang('No devices found')</div>
@endif
@endsection
@section('javascript')
<script type="text/javascript" src="{{ asset('js/vis.min.js') }}"></script>
@endsection
@section('scripts')
<script type="text/javascript">
var height = $(window).height() - 100;
$('#visualization').height(height + 'px');
// create an array with nodes
var nodes = {!! $nodes !!};
// create an array with edges
var edges = {!! $edges !!};
// create a network
var container = document.getElementById('visualization');
var data = {
nodes: nodes,
edges: edges,
stabilize: true
};
var options = {!! $options !!};
var network = new vis.Network(container, data, options);
network.on('click', function (properties) {
if (properties.nodes > 0) {
window.location.href = "device/device="+properties.nodes+"/"
}
});
function highlightNode(e) {
highlight_node = document.getElementById("highlight_node").value;
window.location.href = 'maps/devicedependency?group={{ $group_id }}&highlight_node=' + highlight_node;
}
$('#highlight_node option[value="{{$highlight_node}}"]').prop('selected', true);
</script>
@endsection

View File

@@ -29,6 +29,11 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () {
Route::get('about', 'AboutController@index'); Route::get('about', 'AboutController@index');
Route::get('authlog', 'UserController@authlog'); Route::get('authlog', 'UserController@authlog');
// Maps
Route::group(['prefix' => 'maps', 'namespace' => 'Maps'], function () {
Route::get('devicedependency', 'DeviceDependencyController@dependencyMap');
});
// admin pages // admin pages
Route::group(['guard' => 'admin'], function () { Route::group(['guard' => 'admin'], function () {
Route::get('settings/{tab?}/{section?}', 'SettingsController@index')->name('settings'); Route::get('settings/{tab?}/{section?}', 'SettingsController@index')->name('settings');