Added global VLAN ports page (#16415)

* Global VLAN ports page

* Show a list of devices too

* Fix a little theme color issues

* oops

* Just put css in the theme

* Apply fixes from StyleCI

---------

Co-authored-by: Tony Murray <murrant@users.noreply.github.com>
Co-authored-by: Neil Lathwood <gh+n@laf.io>
This commit is contained in:
Tony Murray
2024-09-25 10:14:13 -05:00
committed by GitHub
parent 2ffe314dc7
commit d2fb66d3d5
7 changed files with 241 additions and 1 deletions

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Table;
use App\Models\Device;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use LibreNMS\Util\Url;
class VlanDevicesController extends TableController
{
protected function sortFields($request)
{
return [
'device' => 'device_id',
'ports_count',
'domain' => 'vlans.vlan_domain',
'name' => 'vlans.vlan_name',
'type' => 'vlans.vlan_type',
'mtu' => 'vlans.vlan_mtu',
];
}
private int $vlanId;
protected function baseQuery(Request $request)
{
$this->validate($request, ['vlan' => 'integer']);
$this->vlanId = $request->get('vlan', 1);
return Device::distinct()
->select([
'devices.*',
'vlans.vlan_name',
'vlans.vlan_type',
'vlans.vlan_mtu',
])
->withCount(['ports' => function ($query) {
$query->distinct()->where('ifVlan', $this->vlanId)
->orWhereHas('vlans', fn ($q) => $q->where('vlan', $this->vlanId));
}])
->where(function ($query) {
$query->where('vlans.vlan_vlan', $this->vlanId)
->orWhereHas('ports', fn ($q) => $q->where('ifVlan', $this->vlanId));
})
->leftJoin('vlans', function ($join) {
$join->on('devices.device_id', '=', 'vlans.device_id')
->on('vlans.vlan_vlan', '=', DB::raw($this->vlanId));
});
}
/**
* @param Device $model
*/
public function formatItem($model): array
{
return [
'device' => Url::deviceLink($model),
'ports_count' => $model->ports_count,
// left joined fields
'domain' => $model['vlan_domain'],
'name' => $model['vlan_name'],
'type' => $model['vlan_type'],
'mtu' => $model['vlan_mtu'],
];
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\Table;
use App\Models\Port;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use LibreNMS\Util\Url;
class VlanPortsController extends TableController
{
protected function sortFields($request): array
{
return [
'device' => 'device_id',
'port' => 'port_id',
'untagged',
'state' => 'ports_vlans.state',
'cost' => 'ports_vlans.cost',
];
}
private int $vlanId;
protected function baseQuery(Request $request): Builder
{
$this->validate($request, ['vlan' => 'integer']);
$this->vlanId = $request->get('vlan', 1);
return Port::with('device')
->leftJoin('ports_vlans', 'ports.port_id', 'ports_vlans.port_id')
->where(function ($query) {
$query->where('ifVlan', $this->vlanId)
->orWhere('ports_vlans.vlan', $this->vlanId);
})
->select([
'ports.port_id',
'ports.device_id',
'ports.ifName',
'ports.ifIndex',
'ports.ifDescr',
'ports.ifAlias',
'ports.ifVlan',
'ports.ifAdminStatus',
'ports.ifOperStatus',
'ports_vlans.untagged',
'ports_vlans.state',
'ports_vlans.cost',
DB::raw("CASE WHEN ports.ifVlan = $this->vlanId or ports_vlans.untagged <> 0 THEN \"yes\" ELSE \"no\" END as untagged"),
]);
}
/**
* @param Port $model
*/
public function formatItem($model): array
{
return [
'device' => Url::deviceLink($model->device),
'port' => Url::portLink($model),
// left joined columns
'untagged' => $model['untagged'],
'state' => $model['state'],
'cost' => $model['cost'],
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers;
use App\Models\Vlan;
use Illuminate\Contracts\View\View;
class VlansController extends Controller
{
public function index(): View
{
return view('vlans.index', [
'vlanIds' => Vlan::distinct()
->where('vlan_vlan', '>', 0)
->orderBy('vlan_vlan')
->pluck('vlan_vlan'),
]);
}
}

View File

@@ -123,6 +123,11 @@
}
.devices-graphs-select {
margin-right: 5px;
margin-right: 5px;
color: #555;
}
.panel-title select {
background-color: black;
color: white;
}

View File

@@ -322,6 +322,9 @@
</a></li>
@endif
<li><a href="{{ route('vlans.index') }}"><i class="fa fa-tasks fa-fw fa-lg"
aria-hidden="true"></i> {{ __('VLANs') }}</a></li>
@config('enable_billing')
<li><a href="{{ url('bills') }}"><i class="fa fa-money fa-fw fa-lg"
aria-hidden="true"></i> {{ __('Traffic Bills') }}</a></li>

View File

@@ -0,0 +1,75 @@
@extends('layouts.librenmsv1')
@section('title', __('Vlans'))
@section('content')
<div class="container-fluid">
<x-panel body-class="!tw-p-0">
<x-slot name="heading">
<h2 class="panel-title">{{ __('VLAN') }}
<select id="vlan-select">
@foreach($vlanIds as $vlanId)
<option>{{ $vlanId }}</option>
@endforeach
</select>
</h2>
</x-slot>
<x-tabs>
<x-tab name="{{ __('Devices') }}">
<table id="vlan-devices" class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th data-column-id="device">{{ __('Device') }}</th>
<th data-column-id="ports_count">{{ __('Ports') }}</th>
<th data-column-id="name">{{ __('Local Name') }}</th>
<th data-column-id="domain" data-visible="false">{{ __('Domain') }}</th>
<th data-column-id="type">{{ __('Type') }}</th>
<th data-column-id="mtu">{{ __('MTU') }}</th>
</tr>
</thead>
</table>
</x-tab>
<x-tab value="image" name="{{ __('Ports') }}">
<table id="vlan-ports" class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th data-column-id="device">{{ __('Device') }}</th>
<th data-column-id="port">{{ __('Port') }}</th>
<th data-column-id="untagged">{{ __('Untagged') }}</th>
<th data-column-id="state">{{ __('State') }}</th>
<th data-column-id="cost">{{ __('Cost') }}</th>
</tr>
</thead>
</table>
</x-tab>
</x-tabs>
</x-panel>
</div>
@endsection
@push('scripts')
<script>
var vlan_id = {{ (int) $vlanIds->first() }};
var grid = $("#vlan-ports").bootgrid({
ajax: true,
post: function () {
return {vlan: vlan_id}
},
url: "{{ route('table.vlan-ports') }}"
});
var grid = $("#vlan-devices").bootgrid({
ajax: true,
post: function () {
return {vlan: vlan_id}
},
url: "{{ route('table.vlan-devices') }}"
});
$('#vlan-select').on('change', function () {
vlan_id = this.value;
$("#vlan-ports").bootgrid('reload');
$("#vlan-devices").bootgrid('reload');
});
</script>
@endpush

View File

@@ -46,6 +46,7 @@ Route::middleware(['auth'])->group(function () {
Route::any('inventory', \App\Http\Controllers\InventoryController::class)->name('inventory');
Route::get('inventory/purge', [\App\Http\Controllers\InventoryController::class, 'purge'])->name('inventory.purge');
Route::resource('port', 'PortController')->only('update');
Route::get('vlans', [\App\Http\Controllers\VlansController::class, 'index'])->name('vlans.index');
Route::prefix('poller')->group(function () {
Route::get('', 'PollerController@pollerTab')->name('poller.index');
Route::get('log', 'PollerController@logTab')->name('poller.log');
@@ -215,6 +216,8 @@ Route::middleware(['auth'])->group(function () {
Route::post('routes', 'RoutesTablesController');
Route::post('syslog', 'SyslogController');
Route::post('tnmsne', 'TnmsneController')->name('table.tnmsne');
Route::post('vlan-ports', 'VlanPortsController')->name('table.vlan-ports');
Route::post('vlan-devices', 'VlanDevicesController')->name('table.vlan-devices');
Route::post('vminfo', 'VminfoController');
});