Ports UI update (#16115)

* WIP Device Ports porting to Laravel

* WIP port links

* Port Links WIP

* Port Links

* in_array -> isset

* Add request to DeviceTab data

* Add initial Pagination

* Missing select component

* Collapsed and expandable port neighbors
New expandable component

* Port sorting

* Fix port transfer

* Use menu entries to filter ports

* Add translatable strings

* style fixes and cleanup

* update css

* graph views and tidy controller
basic port link view

* cleanup

* port row blade to reuse in legacy port view

* Legacy tab url handling
work properly in subdirectory
remove includes from sub tab directory to prevent oddity

* fallback to detail list when the view doesn't exist

* Use named variable to simplify

* Fix issue from file that was a symlink

* Submenu handle sub items and query string urls

* extract pageLinks to improve readability

* fix typo

* Apply fixes from StyleCI

* phpstan was not happy using the relationship HasMany query

* Don't allow *bps etc to be on a second line

* Improve table on small screens

* Fix sort

---------

Co-authored-by: Tony Murray <murrant@users.noreply.github.com>
This commit is contained in:
Tony Murray 2024-06-16 11:29:06 -05:00 committed by GitHub
parent a717e084f0
commit 075ba4c932
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
86 changed files with 1135 additions and 990 deletions

View File

@ -26,6 +26,7 @@
namespace LibreNMS\Interfaces\UI;
use App\Models\Device;
use Illuminate\Http\Request;
interface DeviceTab
{
@ -62,7 +63,8 @@ interface DeviceTab
* Collect data to send to the view
*
* @param Device $device
* @param Request $request
* @return array
*/
public function data(Device $device): array;
public function data(Device $device, Request $request): array;
}

View File

@ -85,6 +85,15 @@ class Color
];
}
public static function percent(int|float $numerator = null, int|float $denominator = null, int|float $percent = null): string
{
$percent = $percent ? round($percent) : Number::calculatePercent($numerator, $denominator, 0);
$r = min(255, 5 * ($percent - 25));
$b = max(0, 255 - (5 * ($percent + 25)));
return sprintf('#%02x%02x%02x', $r, $b, $b);
}
/**
* Get highlight color based on device status
*/

View File

@ -210,6 +210,18 @@ class Rewrite
return $location;
}
public static function dslLineType(string $lineType): string
{
return match ($lineType) {
'noChannel' => 'No Channel',
'fastOnly' => 'Fastpath',
'interleavedOnly' => 'Interleaved',
'fastOrInterleaved' => 'Fast/Interleaved',
'fastAndInterleaved' => 'Fast+Interleaved',
default => $lineType,
};
}
public static function vmwareGuest($guest_id)
{
$guests = [

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class AccessPointsController implements DeviceTab
@ -50,7 +51,7 @@ class AccessPointsController implements DeviceTab
return __('Access Points');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class AlertStatsController implements DeviceTab
@ -50,7 +51,7 @@ class AlertStatsController implements DeviceTab
return __('Alert Stats');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class AlertsController implements DeviceTab
@ -50,7 +51,7 @@ class AlertsController implements DeviceTab
return __('Alerts');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class AppsController implements DeviceTab
@ -50,7 +51,7 @@ class AppsController implements DeviceTab
return __('Apps');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
class CaptureController implements \LibreNMS\Interfaces\UI\DeviceTab
{
@ -49,7 +50,7 @@ class CaptureController implements \LibreNMS\Interfaces\UI\DeviceTab
return __('Capture');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Config;
use LibreNMS\Interfaces\UI\DeviceTab;
@ -51,7 +52,7 @@ class CollectdController implements DeviceTab
return __('CollectD');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
class EditController implements \LibreNMS\Interfaces\UI\DeviceTab
{
@ -49,7 +50,7 @@ class EditController implements \LibreNMS\Interfaces\UI\DeviceTab
return __('Edit');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class GraphsController implements DeviceTab
@ -50,7 +51,7 @@ class GraphsController implements DeviceTab
return __('Graphs');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class HealthController implements DeviceTab
@ -50,7 +51,7 @@ class HealthController implements DeviceTab
return __('Health');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -27,6 +27,7 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Facades\DeviceCache;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Config;
use LibreNMS\Interfaces\UI\DeviceTab;
@ -67,7 +68,7 @@ class InventoryController implements DeviceTab
return __('Inventory');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'tab' => $this->type, // inject to load correct legacy file

View File

@ -27,10 +27,10 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Carbon\Carbon;
use Illuminate\Http\Request;
use LibreNMS\Config;
use LibreNMS\Interfaces\UI\DeviceTab;
use LibreNMS\Util\Smokeping;
use Request;
class LatencyController implements DeviceTab
{
@ -54,10 +54,10 @@ class LatencyController implements DeviceTab
return __('Latency');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
$from = Request::get('dtpickerfrom', Carbon::now(session('preferences.timezone'))->subDays(2)->format(Config::get('dateformat.byminute')));
$to = Request::get('dtpickerto', Carbon::now(session('preferences.timezone'))->format(Config::get('dateformat.byminute')));
$from = $request->get('dtpickerfrom', Carbon::now(session('preferences.timezone'))->subDays(2)->format(Config::get('dateformat.byminute')));
$to = $request->get('dtpickerto', Carbon::now(session('preferences.timezone'))->format(Config::get('dateformat.byminute')));
$smokeping = new Smokeping($device);
$smokeping_tabs = [];

View File

@ -27,6 +27,7 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Facades\DeviceCache;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class LoadBalancerController implements DeviceTab
@ -93,7 +94,7 @@ class LoadBalancerController implements DeviceTab
return __('Load Balancer');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'loadbalancer_tabs' => $this->tabs,

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class LogsController implements DeviceTab
@ -50,7 +51,7 @@ class LogsController implements DeviceTab
return __('Logs');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class MefController implements DeviceTab
@ -50,7 +51,7 @@ class MefController implements DeviceTab
return __('Metro Ethernet');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class MuninController implements DeviceTab
@ -50,7 +51,7 @@ class MuninController implements DeviceTab
return __('Munin');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class NacController implements DeviceTab
@ -50,7 +51,7 @@ class NacController implements DeviceTab
return __('NAC');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -27,6 +27,7 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use App\Models\Link;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class NeighboursController implements DeviceTab
@ -53,7 +54,7 @@ class NeighboursController implements DeviceTab
return __('Neighbours');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Config;
use LibreNMS\Interfaces\UI\DeviceTab;
@ -72,7 +73,7 @@ class NetflowController implements DeviceTab
return __('Netflow');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'tab' => 'nfsen',

View File

@ -54,7 +54,7 @@ class NotesController implements DeviceTab
return __('Notes');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
use Session;
@ -51,7 +52,7 @@ class OverviewController implements DeviceTab
return __('Overview');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class PackagesController implements DeviceTab
@ -50,7 +51,7 @@ class PackagesController implements DeviceTab
return __('Pkgs');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class PortController implements DeviceTab
@ -50,7 +51,7 @@ class PortController implements DeviceTab
return __('Port');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,10 +26,25 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use App\Models\Link;
use App\Models\Port;
use App\Models\Pseudowire;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use LibreNMS\Config;
use LibreNMS\Interfaces\UI\DeviceTab;
class PortsController implements DeviceTab
{
private bool $detail = false;
private int $perPage = 15;
private string $sortOrder = 'asc';
private string $sortColumn = 'default';
public function visible(Device $device): bool
{
return $device->ports()->exists();
@ -50,8 +65,340 @@ class PortsController implements DeviceTab
return __('Ports');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
Validator::validate($request->all(), [
'perPage' => 'int',
'sort' => 'in:media,mac,port,traffic,speed',
'order' => 'in:asc,desc',
'disabled' => 'in:0,1',
'ignore' => 'in:0,1',
'admin' => 'in:up,down,testing,any',
'status' => 'in:up,down,testing,unknown,dormant,notPresent,lowerLayerDown,any',
'type' => 'in:bits,upkts,nupkts,errors,etherlike',
'from' => ['regex:/^(int|[+-]\d+[hdmy])$/'],
'to' => ['regex:/^(int|[+-]\d+[hdmy])$/'],
]);
$tab = $this->parseTab($request);
$this->detail = $tab == 'detail';
$data = match ($tab) {
'links' => $this->linksData($device),
'xdsl' => $this->xdslData($device),
'graphs', 'mini_graphs' => $this->graphData($device, $request),
default => $this->portData($device, $request),
};
return array_merge([
'tab' => $tab,
'details' => $this->detail,
'submenu' => [
$this->getTabs($device),
__('Graphs') => $this->getGraphLinks(),
],
'page_links' => $this->pageLinks($request),
'perPage' => $this->perPage,
'sort' => $this->sortColumn,
'next_order' => $this->sortOrder == 'asc' ? 'desc' : 'asc',
], $data);
}
private function portData(Device $device, Request $request): array
{
$relationships = ['groups', 'ipv4', 'ipv6', 'vlans', 'adsl', 'vdsl'];
if ($this->detail) {
$relationships[] = 'links';
$relationships[] = 'pseudowires.endpoints';
$relationships[] = 'ipv4Networks.ipv4';
$relationships[] = 'ipv6Networks.ipv6';
}
/** @var Collection|LengthAwarePaginator<Port> $ports */
$ports = $this->getFilteredPortsQuery($device, $request, $relationships)->paginate($this->perPage);
$data = [
'ports' => $ports,
'neighbors' => $ports->keyBy('port_id')->map(fn (Port $port) => $this->findPortNeighbors($port)),
'graphs' => [
'bits' => [['type' => 'port_bits', 'title' => trans('Traffic'), 'vars' => [['from' => '-1d'], ['from' => '-7d'], ['from' => '-30d'], ['from' => '-1y']]]],
'upkts' => [['type' => 'port_upkts', 'title' => trans('Packets (Unicast)'), 'vars' => [['from' => '-1d'], ['from' => '-7d'], ['from' => '-30d'], ['from' => '-1y']]]],
'errors' => [['type' => 'port_errors', 'title' => trans('Errors'), 'vars' => [['from' => '-1d'], ['from' => '-7d'], ['from' => '-30d'], ['from' => '-1y']]]],
],
];
if ($this->detail) {
$data['neighbor_ports'] = Port::with('device')
->hasAccess(Auth::user())
->whereIn('port_id', $data['neighbors']->map(fn ($a) => array_keys($a))->flatten())
->get()->keyBy('port_id');
}
return $data;
}
public function findPortNeighbors(Port $port): array
{
// only do for detail
if (! $this->detail) {
return [];
}
// skip ports that cannot have neighbors
if (in_array($port->ifType, ['softwareLoopback', 'rs232'])) {
return [];
}
$neighbors = [];
// Links always included
// fa-plus black portlink on devicelink
foreach ($port->links as $link) {
/** @var Link $link */
if ($link->remote_port_id) {
$this->addPortNeighbor($neighbors, 'link', $link->remote_port_id);
}
}
if ($this->detail) {
// IPv4 + IPv6 subnet if detailed
// fa-arrow-right green portlink on devicelink
if ($port->ipv4Networks->isNotEmpty()) {
$ids = $port->ipv4Networks->map(fn ($net) => $net->ipv4->pluck('port_id'))->flatten();
foreach ($ids as $port_id) {
if ($port_id !== $port->port_id) {
$this->addPortNeighbor($neighbors, 'ipv4_network', $port_id);
}
}
}
if ($port->ipv6Networks->isNotEmpty()) {
$ids = $port->ipv6Networks->map(fn ($net) => $net->ipv6->pluck('port_id'))->flatten();
foreach ($ids as $port_id) {
if ($port_id !== $port->port_id) {
$this->addPortNeighbor($neighbors, 'ipv6_network', $port_id);
}
}
}
}
// pseudowires
// fa-cube green portlink on devicelink: cpwVcID
/** @var Pseudowire $pseudowire */
foreach ($port->pseudowires as $pseudowire) {
foreach ($pseudowire->endpoints as $endpoint) {
if ($endpoint->port_id != $port->port_id) {
$this->addPortNeighbor($neighbors, 'pseudowire', $endpoint->port_id);
}
}
}
// port stack
// fa-expand portlink: local is low port
// fa-compress portlink: local is high portPort
$stacks = \DB::table('ports_stack')->where('device_id', $port->device_id)
->where(fn ($q) => $q->where('port_id_high', $port->port_id)->orWhere('port_id_low', $port->port_id))->get();
foreach ($stacks as $stack) {
if ($stack->port_id_low) {
$this->addPortNeighbor($neighbors, 'stack_low', $stack->port_id_low);
}
if ($stack->port_id_high) {
$this->addPortNeighbor($neighbors, 'stack_high', $stack->port_id_high);
}
}
// PAGP members/parent
// fa-cube portlink: pagpGroupIfIndex = ifIndex parent
// fa-cube portlink: if (not parent, pagpGroupIfIndex != ifIndex) ifIndex = pagpGroupIfIndex member
if ($port->pagpGroupIfIndex) {
if ($port->pagpGroupIfIndex == $port->ifIndex) {
$this->addPortNeighbor($neighbors, 'pagp', $port->port_id);
} else {
$this->addPortNeighbor($neighbors, 'pagp', $port->pagpParent->port_id);
}
}
return $neighbors;
}
private function addPortNeighbor(array &$neighbors, string $type, int $port_id): void
{
if (empty($neighbors[$port_id])) {
$neighbors[$port_id] = [
'port_id' => $port_id,
];
}
$neighbors[$port_id][$type] = 1;
}
private function graphData(Device $device, Request $request): array
{
return [
'graph_type' => 'port_' . $request->get('type'),
'ports' => $this->getFilteredPortsQuery($device, $request)->get(),
];
}
private function xdslData(Device $device): array
{
$device->portsAdsl->load('port');
$device->portsVdsl->load('port');
return [
'adsl' => $device->portsAdsl->sortBy('port.ifIndex'),
'vdsl' => $device->portsVdsl->sortBy('port.ifIndex'),
];
}
private function linksData(Device $device): array
{
$device->links->load(['port', 'remotePort', 'remoteDevice']);
return ['links' => $device->links];
}
private function getTabs(Device $device): array
{
$tabs = [
['name' => __('Basic'), 'url' => 'basic'],
['name' => __('Detail'), 'url' => ''],
];
if ($device->macs()->exists()) {
$tabs[] = ['name' => __('port.tabs.arp'), 'url' => 'arp'];
}
if ($device->portsFdb()->exists()) {
$tabs[] = ['name' => __('port.tabs.fdb'), 'url' => 'fdb'];
}
if ($device->links()->exists()) {
$tabs[] = ['name' => __('port.tabs.links'), 'url' => 'links'];
}
if ($device->portsAdsl()->exists() || $device->portsVdsl()->exists()) {
$tabs[] = ['name' => __('port.tabs.xdsl'), 'url' => 'xdsl'];
}
return $tabs;
}
/**
* @return array[]
*/
private function getGraphLinks(): array
{
$graph_links = [
[
'name' => __('port.graphs.bits'),
'url' => 'graphs?type=bits',
'sub_name' => __('Mini'),
'sub_url' => 'mini_graphs?type=bits',
],
[
'name' => __('port.graphs.upkts'),
'url' => 'graphs?type=upkts',
'sub_name' => __('Mini'),
'sub_url' => 'mini_graphs?type=upkts',
],
[
'name' => __('port.graphs.nupkts'),
'url' => 'graphs?type=nupkts',
'sub_name' => __('Mini'),
'sub_url' => 'mini_graphs?type=nupkts',
],
[
'name' => __('port.graphs.errors'),
'url' => 'graphs?type=errors',
'sub_name' => __('Mini'),
'sub_url' => 'mini_graphs?type=errors',
],
];
if (Config::get('enable_ports_etherlike')) {
$graph_links[] = [
'name' => __('port.graphs.etherlike'),
'url' => 'graphs?type=etherlike',
'sub_name' => __('Mini'),
'sub_url' => 'mini_graphs?type=etherlike',
];
}
return $graph_links;
}
private function getFilteredPortsQuery(Device $device, Request $request, array $relationships = []): Builder
{
$this->perPage = $request->input('perPage', 15);
$this->sortOrder = $request->input('order', 'asc');
$this->sortColumn = $request->input('sort', 'default');
$orderBy = match ($this->sortColumn) {
'traffic' => \DB::raw('ports.ifInOctets_rate + ports.ifOutOctets_rate'),
'speed' => 'ifSpeed',
'media' => 'ifType',
'mac' => 'ifPhysAddress',
'port' => 'ifName',
default => 'ifIndex',
};
return Port::where('device_id', $device->device_id)
->isNotDeleted()
->hasAccess(Auth::user())->with($relationships)
->when(! $request->input('disabled'), fn (Builder $q, $disabled) => $q->where('disabled', 0))
->when(! $request->input('ignore'), fn (Builder $q, $disabled) => $q->where('ignore', 0))
->when($request->input('admin') != 'any', fn (Builder $q, $admin) => $q->where('ifAdminStatus', $request->input('admin', 'up')))
->when($request->input('status', 'any') != 'any', fn (Builder $q, $admin) => $q->where('ifOperStatus', $request->input('status')))
->orderBy($orderBy, $this->sortOrder);
}
/**
* get the ports sub tab name including handling legacy urls
*/
private function parseTab(Request $request): string
{
if (preg_match('#view=([^/]+)#', $request->fullUrl(), $matches)) {
return match ($matches[1]) {
'neighbours' => 'links',
default => $matches[1],
};
}
return $request->route('vars', 'detail'); // fourth segment is called vars to handle legacy urls
}
private function pageLinks(Request $request): array
{
$disabled = $request->input('disabled');
$ignore = $request->input('ignore');
$admin = $request->input('admin') == 'any';
$status = $request->input('status') == 'up';
return [
[
'icon' => $status ? 'fa-regular fa-square-check' : 'fa-regular fa-square',
'url' => $status ? $request->fullUrlWithoutQuery('status') : $request->fullUrlWithQuery(['status' => 'up']),
'title' => __('port.filters.status_up'),
'external' => false,
],
[
'icon' => $admin ? 'fa-regular fa-square-check' : 'fa-regular fa-square',
'url' => $admin ? $request->fullUrlWithoutQuery('admin') : $request->fullUrlWithQuery(['admin' => 'any']),
'title' => __('port.filters.admin_down'),
'external' => false,
],
[
'icon' => $disabled ? 'fa-regular fa-square-check' : 'fa-regular fa-square',
'url' => $disabled ? $request->fullUrlWithoutQuery('disabled') : $request->fullUrlWithQuery(['disabled' => 1]),
'title' => __('port.filters.disabled'),
'external' => false,
],
[
'icon' => $ignore ? 'fa-regular fa-square-check' : 'fa-regular fa-square',
'url' => $ignore ? $request->fullUrlWithoutQuery('ignore') : $request->fullUrlWithQuery(['ignore' => 1]),
'title' => __('port.filters.ignored'),
'external' => false,
],
];
}
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class PrinterController implements DeviceTab
@ -50,7 +51,7 @@ class PrinterController implements DeviceTab
return __('Printer');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'tab' => 'toner',

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use LibreNMS\Interfaces\UI\DeviceTab;
@ -51,7 +52,7 @@ class ProcessesController implements DeviceTab
return __('Processes');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class PseudowiresController implements DeviceTab
@ -50,7 +51,7 @@ class PseudowiresController implements DeviceTab
return __('Pseudowires');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -28,6 +28,7 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Facades\DeviceCache;
use App\Models\Component;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class RoutingController implements DeviceTab
@ -72,7 +73,7 @@ class RoutingController implements DeviceTab
return __('Routing');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'routing_tabs' => array_filter($this->tabs),

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class ServicesController implements DeviceTab
@ -50,7 +51,7 @@ class ServicesController implements DeviceTab
return __('Services');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -28,6 +28,7 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Facades\DeviceCache;
use App\Http\Controllers\Controller;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Config;
use LibreNMS\Interfaces\UI\DeviceTab;
@ -60,7 +61,7 @@ class ShowConfigController extends Controller implements DeviceTab
return __('Config');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'rancid_path' => $this->getRancidPath(),

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class SlasController implements DeviceTab
@ -50,7 +51,7 @@ class SlasController implements DeviceTab
return __('SLAs');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
use LibreNMS\Util\Url;
@ -51,7 +52,7 @@ class StpController implements DeviceTab
return __('STP');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
$active_vlan = Url::parseOptions('vlan', 1);
$stpInstances = $device->stpInstances;

View File

@ -27,6 +27,7 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use App\Models\TnmsneInfo;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class TnmsneController implements DeviceTab
@ -51,7 +52,7 @@ class TnmsneController implements DeviceTab
return __('Hardware');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -27,6 +27,7 @@ namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use App\Models\PortVlan;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class VlansController implements DeviceTab
@ -51,7 +52,7 @@ class VlansController implements DeviceTab
return __('VLANs');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'vlans' => self::getVlans($device),

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class VmInfoController implements DeviceTab
@ -50,7 +51,7 @@ class VmInfoController implements DeviceTab
return __('Virtual Machines');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [
'vms' => self::getVms($device),

View File

@ -26,6 +26,7 @@
namespace App\Http\Controllers\Device\Tabs;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\UI\DeviceTab;
class WirelessController implements DeviceTab
@ -50,7 +51,7 @@ class WirelessController implements DeviceTab
return __('Wireless');
}
public function data(Device $device): array
public function data(Device $device, Request $request): array
{
return [];
}

View File

@ -12,6 +12,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Blade;
use LibreNMS\Config;
use LibreNMS\Interfaces\UI\DeviceTab;
use LibreNMS\Util\Debug;
use LibreNMS\Util\Graph;
use LibreNMS\Util\Url;
@ -83,11 +84,14 @@ class DeviceController extends Controller
$parent_id = Vminfo::guessFromDevice($device)->value('device_id');
$overview_graphs = $this->buildDeviceGraphArrays($device);
/** @var DeviceTab[] $tabs */
$tabs = array_map(function ($class) {
return app()->make($class);
}, array_filter($this->tabs, 'class_exists')); // TODO remove filter
$title = $tabs[$current_tab]->name();
$data = $tabs[$current_tab]->data($device);
$tab_controller = $tabs[$current_tab];
$title = $tab_controller->name();
$data = $tab_controller->data($device, $request);
$page_links = $data['page_links'] ?? [];
// Device Link Menu, select the primary link
$device_links = $this->deviceLinkMenu($device, $current_tab);

View File

@ -26,6 +26,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Ipv4Address extends PortRelatedModel
{
@ -40,4 +41,9 @@ class Ipv4Address extends PortRelatedModel
'port_id',
'context_name',
];
public function network(): BelongsTo
{
return $this->belongsTo(Ipv4Network::class, 'ipv4_network_id', 'ipv4_network_id');
}
}

View File

@ -28,6 +28,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Ipv4Network extends Model
{
@ -45,4 +46,9 @@ class Ipv4Network extends Model
{
return $this->hasMany(Ipv4Address::class, 'ipv4_network_id');
}
public function connectedPorts(): HasManyThrough
{
return $this->hasManyThrough(Port::class, Ipv4Address::class, 'ipv4_network_id', 'port_id', 'ipv4_network_id', 'port_id');
}
}

View File

@ -26,6 +26,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Ipv6Address extends PortRelatedModel
{
public $timestamps = false;
@ -39,4 +41,9 @@ class Ipv6Address extends PortRelatedModel
'port_id',
'context_name',
];
public function network(): BelongsTo
{
return $this->belongsTo(Ipv6Network::class, 'ipv6_network_id', 'ipv6_network_id');
}
}

View File

@ -28,6 +28,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Ipv6Network extends Model
{
@ -44,4 +45,9 @@ class Ipv6Network extends Model
{
return $this->hasMany(\App\Models\Ipv6Address::class, 'ipv6_network_id');
}
public function connectedPorts(): HasManyThrough
{
return $this->hasManyThrough(Port::class, Ipv6Address::class, 'ipv6_network_id', 'port_id', 'ipv6_network_id', 'port_id');
}
}

View File

@ -4,8 +4,10 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -276,14 +278,14 @@ class Port extends DeviceRelatedModel
// ---- Define Relationships ----
public function adsl(): HasMany
public function adsl(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasMany(PortAdsl::class, 'port_id');
return $this->hasOne(PortAdsl::class, 'port_id');
}
public function vdsl(): HasMany
public function vdsl(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasMany(PortVdsl::class, 'port_id');
return $this->hasOne(PortVdsl::class, 'port_id');
}
public function events(): MorphMany
@ -303,12 +305,22 @@ class Port extends DeviceRelatedModel
public function ipv4(): HasMany
{
return $this->hasMany(\App\Models\Ipv4Address::class, 'port_id');
return $this->hasMany(Ipv4Address::class, 'port_id');
}
public function ipv4Networks(): HasManyThrough
{
return $this->hasManyThrough(Ipv4Network::class, Ipv4Address::class, 'port_id', 'ipv4_network_id', 'port_id', 'ipv4_network_id');
}
public function ipv6(): HasMany
{
return $this->hasMany(\App\Models\Ipv6Address::class, 'port_id');
return $this->hasMany(Ipv6Address::class, 'port_id');
}
public function ipv6Networks(): HasManyThrough
{
return $this->hasManyThrough(Ipv6Network::class, Ipv6Address::class, 'port_id', 'ipv6_network_id', 'port_id', 'ipv6_network_id');
}
public function links(): HasMany
@ -351,6 +363,11 @@ class Port extends DeviceRelatedModel
return $this->hasMany(OspfPort::class, 'port_id');
}
public function pagpParent(): BelongsTo
{
return $this->belongsTo(Port::class, 'pagpGroupIfIndex', 'ifIndex');
}
public function pseudowires(): HasMany
{
return $this->hasMany(Pseudowire::class, 'port_id');

View File

@ -25,8 +25,15 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Pseudowire extends PortRelatedModel
{
public $timestamps = false;
protected $primaryKey = 'pseudowire_id';
public function endpoints(): HasMany
{
return $this->hasMany(Pseudowire::class, 'cpwVcId', 'cpwVcId');
}
}

View File

@ -50,8 +50,6 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
\Illuminate\Pagination\Paginator::useBootstrap();
$this->bootCustomBladeDirectives();
$this->bootCustomValidators();
$this->configureMorphAliases();

View File

@ -34,14 +34,16 @@ class PortLink extends Component
* @var string
*/
public $status;
public bool $basic;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct(Port $port, ?array $graphs = null)
public function __construct(Port $port, ?array $graphs = null, bool $basic = false)
{
$this->basic = $basic;
$this->port = $port;
$this->link = Url::portUrl($port);
$this->label = Rewrite::normalizeIfName($port->getLabel());
@ -64,7 +66,9 @@ class PortLink extends Component
*/
public function render()
{
return view('components.port-link');
return $this->basic
? view('components.port-link_basic')
: view('components.port-link');
}
private function status(): string

View File

@ -63,6 +63,20 @@ class Submenu extends Component
*/
public function isSelected($url)
{
// check for get parameters
$parsed_url = parse_url($url);
if (isset($parsed_url['query']) && $parsed_url['path'] === $this->selected) {
parse_str($parsed_url['query'], $vars);
$request = request();
foreach ($vars as $key => $value) {
if ($request->input($key) !== $value) {
return false;
}
}
return true;
}
return $url === $this->selected;
}

File diff suppressed because one or more lines are too long

View File

@ -793,7 +793,7 @@ p.vspace { padding-top: 3px; padding-bottom: 3px; }
}
.graphcell, .ifcell, .devicecell, .datacell { margin: 0px 0px 7px 0px; padding: 7px; border: 0px; background: #e8e8e8; float: left; }
.ifcell { float: left; clear:right; background:none; }
.iftable td { padding: 0 15px; }
.datacell { clear: both; }
.devicecell { margin: 2px auto; }

View File

@ -2,7 +2,7 @@
"/js/app.js": "/js/app.js?id=1ecd9b13d60fe23a9729684f4d9dc663",
"/js/manifest.js": "/js/manifest.js?id=2eb19d92c19953027907b72ff5963ebb",
"/css/vendor.css": "/css/vendor.css?id=d520734ded0ec75b0a572aa8db1c2161",
"/css/app.css": "/css/app.css?id=38985fa8ef06f355f2934f256b639666",
"/css/app.css": "/css/app.css?id=71ba24df332b4132f71ad82f23679d4f",
"/js/vendor.js": "/js/vendor.js?id=3b22b85b4e5a64e37dd954c0b147b3f3",
"/js/lang/de.js": "/js/lang/de.js?id=9a6f9c23a4b209504cce12ce85315a3c",
"/js/lang/en.js": "/js/lang/en.js?id=43cfd926c2a415bdbb2e59676ab29875",

View File

@ -71,10 +71,7 @@ function logfile($string)
function percent_colour($perc)
{
$r = min(255, 5 * ($perc - 25));
$b = max(0, 255 - (5 * ($perc + 25)));
return sprintf('#%02x%02x%02x', $r, $b, $b);
return \LibreNMS\Util\Color::percent(percent: $perc);
}
/**

View File

@ -88,7 +88,6 @@ foreach (dbFetchRows('SELECT * FROM `loadbalancer_rservers` WHERE `device_id` =
require 'includes/html/print-graphrow.inc.php';
// include("includes/html/print-interface-graphs.inc.php");
echo '
</td>
</tr>';

View File

@ -18,7 +18,7 @@ echo "<span style='font-weight: bold;'>Neighbours</span> &#187; ";
$selection = basename($vars['selection'] ?? 'list');
unset($sep);
$sep = '';
foreach ($datas as $type) {
echo $sep;

View File

@ -1 +0,0 @@
../ports/neighbours.inc.php

View File

@ -0,0 +1,8 @@
<?php
DeviceCache::get($device['device_id'])->load(['links.port', 'links.remoteDevice', 'links.remotePort']);
echo view('device.tabs.ports.links', [
'data' => [
'links' => DeviceCache::get($device['device_id'])->links,
],
]);

View File

@ -45,9 +45,9 @@ $bg = '#ffffff';
$show_all = 1;
echo "<div class=ifcell style='margin: 0px;'><table width=100% cellpadding=10 cellspacing=0>";
echo "<div style='margin: 0px; width: 100%'><table class='iftable' cellpadding=10 cellspacing=0A>";
require 'includes/html/print-interface.inc.php';
echo view('device.tabs.includes.port_row', ['port' => $port, 'collapsing' => false]);
echo '</table></div>';

View File

@ -1,170 +0,0 @@
<?php
use App\Models\Port;
use LibreNMS\Config;
use LibreNMS\Util\Url;
if (empty($vars['view'])) {
$vars['view'] = trim(Config::get('ports_page_default'), '/');
}
if ($vars['view'] == 'graphs' || $vars['view'] == 'minigraphs') {
if (isset($vars['graph'])) {
$graph_type = 'port_' . $vars['graph'];
} else {
$graph_type = 'port_bits';
}
}
$link_array = [
'page' => 'device',
'device' => $device['device_id'],
'tab' => 'ports',
];
print_optionbar_start();
$menu_options['basic'] = 'Basic';
$menu_options['details'] = 'Details';
$menu_options['arp'] = 'ARP Table';
$menu_options['fdb'] = 'FDB Table';
if (dbFetchCell("SELECT * FROM links AS L, ports AS I WHERE I.device_id = '" . $device['device_id'] . "' AND I.port_id = L.local_port_id")) {
$menu_options['neighbours'] = 'Neighbours';
}
if (DeviceCache::getPrimary()->portsAdsl()->exists() || DeviceCache::getPrimary()->portsVdsl()->exists()) {
$menu_options['xdsl'] = 'xDSL';
}
$sep = '';
foreach ($menu_options as $option => $text) {
echo $sep;
if ($vars['view'] == $option) {
echo "<span class='pagemenu-selected'>";
}
echo generate_link($text, $link_array, ['view' => $option]);
if ($vars['view'] == $option) {
echo '</span>';
}
$sep = ' | ';
}
unset($sep);
echo ' | Graphs: ';
$graph_types = [
'bits' => 'Bits',
'upkts' => 'Unicast Packets',
'nupkts' => 'Non-Unicast Packets',
'errors' => 'Errors',
];
if (Config::get('enable_ports_etherlike')) {
$graph_types['etherlike'] = 'Etherlike';
}
$type_sep = '';
$vars['graph'] = $vars['graph'] ?? '';
foreach ($graph_types as $type => $descr) {
echo $type_sep;
if ($vars['graph'] == $type && $vars['view'] == 'graphs') {
echo "<span class='pagemenu-selected'>";
}
echo generate_link($descr, $link_array, ['view' => 'graphs', 'graph' => $type]);
if ($vars['graph'] == $type && $vars['view'] == 'graphs') {
echo '</span>';
}
echo ' (';
if ($vars['graph'] == $type && $vars['view'] == 'minigraphs') {
echo "<span class='pagemenu-selected'>";
}
echo generate_link('Mini', $link_array, ['view' => 'minigraphs', 'graph' => $type]);
if ($vars['graph'] == $type && $vars['view'] == 'minigraphs') {
echo '</span>';
}
echo ')';
$type_sep = ' | ';
}//end foreach
print_optionbar_end();
if ($vars['view'] == 'minigraphs') {
$timeperiods = [
'-1day',
'-1week',
'-1month',
'-1year',
];
$from = '-1day';
echo "<div style='display: block; clear: both; margin: auto; min-height: 500px;'>";
unset($seperator);
foreach (Port::where('device_id', $device['device_id'])->where('disabled', 0)->orderBy('ifIndex')->get() as $port) {
echo '<div class="minigraph-div">'
. Url::portLink($port,
'<div style="font-weight: bold;">' . $port->getShortLabel() . '</div>' .
Url::graphTag([
'type' => $graph_type,
'id' => $port['port_id'],
'from' => $from,
'width' => 180,
'height' => 55,
'legend' => 'no',
]))
. '</div>';
}
echo '</div>';
} elseif ($vars['view'] == 'arp' || $vars['view'] == 'xdsl' || $vars['view'] == 'neighbours' || $vars['view'] == 'fdb') {
include 'ports/' . $vars['view'] . '.inc.php';
} else {
if ($vars['view'] == 'details') {
$port_details = 1;
} ?>
<div style='margin: 0px;'><table class='table'>
<tr>
<th width="350"><A href="<?php echo Url::generate($vars, ['sort' => 'port']); ?>">Port</a></th>
<th width="100">Port Group</a></th>
<th width="100"></th>
<th width="120"><a href="<?php echo Url::generate($vars, ['sort' => 'traffic']); ?>">Traffic</a></th>
<th width="75">Speed</th>
<th width="100">Media</th>
<th width="100">Mac Address</th>
<th width="375"></th>
</tr>
<?php
$i = '1';
global $port_index_cache;
/** @var \Illuminate\Support\Collection<\App\Models\Port> $ports */
$ports = DeviceCache::getPrimary()->ports()->orderBy('ifIndex')->isValid()->get();
// As we've dragged the whole database, lets pre-populate our caches :)
foreach ($ports as $key => $port) {
$port_index_cache[$port['device_id']][$port['ifIndex']] = $port;
}
if (isset($vars['sort']) && $vars['sort'] == 'traffic') {
$ports = $ports->sortByDesc(function (Port $port) {
return $port->ifInOctets_rate + $port->ifOutOctets_rate;
});
}
foreach ($ports as $port) {
include 'includes/html/print-interface.inc.php';
}
echo '</table></div>';
}//end if
$pagetitle[] = 'Ports';

View File

@ -1,27 +0,0 @@
<?php
echo '<table class="table table-hover table-condensed">
<thead>
<tr>
<th>Local Port</th>
<th>Remote Device</th>
<th>Remote Port</th>
<th>Protocol</th>
</tr>
</thead>';
foreach (dbFetchRows('SELECT * FROM links AS L, ports AS I WHERE I.device_id = ? AND I.port_id = L.local_port_id order by ifName', [$device['device_id']]) as $neighbour) {
$neighbour = cleanPort($neighbour);
echo '<td>' . generate_port_link($neighbour) . '<br>' . htmlspecialchars($neighbour['ifAlias']) . '</td>';
if (is_numeric($neighbour['remote_port_id']) && $neighbour['remote_port_id']) {
$remote_port = cleanPort(get_port_by_id($neighbour['remote_port_id']));
$remote_device = device_by_id_cache($remote_port['device_id']);
echo '<td>' . generate_device_link($remote_device) . '<br>' . htmlspecialchars($remote_device['hardware']) . '</td>
<td>' . generate_port_link($remote_port) . '<br>' . htmlspecialchars($remote_port['ifAlias']) . '</td>';
} else {
echo '<td>' . htmlspecialchars($neighbour['remote_hostname']) . '<br>' . htmlspecialchars($neighbour['remote_platform']) . '</td>
<td>' . htmlspecialchars($neighbour['remote_port']) . '</td>';
}
echo '<td>' . strtoupper(htmlspecialchars($neighbour['protocol'])) . '</td></tr>';
}
echo '</table>';

View File

@ -1,28 +0,0 @@
<?php
echo "<div style='margin: 5px;'><table border=0 cellspacing=0 cellpadding=5 width=100%>";
echo '<tr><th>Port</th><th>Traffic</th><th>Sync Speed</th><th>Attainable Speed</th><th>Attenuation</th><th>SNR Margin</th><th>Output Powers</th></tr>';
$i = '0';
$ports = DeviceCache::getPrimary()->ports()->join('ports_adsl', 'ports.port_id', 'ports_adsl.port_id')
->where('ports.deleted', '0')
->orderby('ports.ifIndex', 'ASC')
->get();
foreach ($ports as $port) {
include 'includes/html/print-interface-adsl.inc.php';
$i++;
}
$ports = DeviceCache::getPrimary()->ports()->join('ports_vdsl', 'ports.port_id', '=', 'ports_vdsl.port_id')
->where('ports.deleted', '0')
->orderby('ports.ifIndex', 'ASC')
->get();
foreach ($ports as $port) {
include 'includes/html/print-interface-vdsl.inc.php';
$i++;
}
echo '</table></div>';
echo "<div style='min-height: 150px;'></div>";

View File

@ -1,115 +0,0 @@
<?php
// This file prints a table row for each interface
use LibreNMS\Config;
use LibreNMS\Util\IP;
$port['device_id'] = $device['device_id'];
$port['hostname'] = $device['hostname'];
$if_id = $port['port_id'];
$port = cleanPort($port);
if (! is_integer($i / 2)) {
$row_colour = Config::get('list_colour.even');
} else {
$row_colour = Config::get('list_colour.odd');
}
if ($port['ifInErrors_delta'] > 0 || $port['ifOutErrors_delta'] > 0) {
$error_img = generate_port_link($port, "<i class='fa fa-flag fa-lg' style='color:red' aria-hidden='true'></i>", 'port_errors');
} else {
$error_img = '';
}
echo "<tr style=\"background-color: $row_colour; padding: 5px;\" valign=top onmouseover=\"this.style.backgroundColor='" . Config::get('list_colour.highlight') . "';\" onmouseout=\"this.style.backgroundColor='$row_colour';\" style='cursor: pointer;'>
<td valign=top width=350>";
echo ' <span class=list-large>
' . generate_port_link($port, $port['ifIndex'] . '. ' . $port['label']) . '
</span><br /><span class=interface-desc>' . \LibreNMS\Util\Clean::html($port['ifAlias'], []) . '</span>';
if ($port['ifAlias']) {
echo '<br />';
}
$break = '';
if ($port_details) {
foreach (dbFetchRows('SELECT * FROM `ipv4_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip) {
echo "$break <a class=interface-desc href=\"javascript:popUp('ajax/netcmd?cmd=whois&amp;query=" . $ip['ipv4_address'] . "')\">" . $ip['ipv4_address'] . '/' . $ip['ipv4_prefixlen'] . '</a>';
$break = ',';
}
foreach (dbFetchRows('SELECT * FROM `ipv6_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip6) {
echo "$break <a class=interface-desc href=\"javascript:popUp('ajax/netcmd?cmd=whois&amp;query=" . $ip6['ipv6_address'] . "')\">" . IP::parse($ip6['ipv6_address'], true) . '/' . $ip6['ipv6_prefixlen'] . '</a>';
$break = ',';
}
}
echo '</span>';
$width = '120';
$height = '40';
$from = Config::get('time.day');
echo '</td><td width=135>';
echo \LibreNMS\Util\Number::formatSi($port['ifInOctets_rate'] * 8, 2, 3, 'bps') . " <i class='fa fa-arrows-v fa-lg icon-theme' aria-hidden='true'></i> " . \LibreNMS\Util\Number::formatSi($port['ifOutOctets_rate'] * 8, 2, 3, 'bps');
echo '<br />';
$port['graph_type'] = 'port_bits';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
echo '' . \LibreNMS\Util\Number::formatSi($port['adslAtucChanCurrTxRate'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['adslAturChanCurrTxRate'], 2, 3, 'bps');
echo '<br />';
$port['graph_type'] = 'port_adsl_speed';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
echo '' . \LibreNMS\Util\Number::formatSi($port['adslAturCurrAttainableRate'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['adslAtucCurrAttainableRate'], 2, 3, 'bps');
echo '<br />';
$port['graph_type'] = 'port_adsl_attainable';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
echo '' . $port['adslAturCurrAtn'] . 'dB/' . $port['adslAtucCurrAtn'] . 'dB';
echo '<br />';
$port['graph_type'] = 'port_adsl_attenuation';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
echo '' . $port['adslAturCurrSnrMgn'] . 'dB/' . $port['adslAtucCurrSnrMgn'] . 'dB';
echo '<br />';
$port['graph_type'] = 'port_adsl_snr';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
echo '' . $port['adslAturCurrOutputPwr'] . 'dBm/' . $port['adslAtucCurrOutputPwr'] . 'dBm';
echo '<br />';
$port['graph_type'] = 'port_adsl_power';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td>';

View File

@ -1,117 +0,0 @@
<?php
// This file prints a table row for each interface
use app\Models\Ipv4Address;
use app\Models\Ipv6Address;
use LibreNMS\Config;
use LibreNMS\Util\IP;
$port['device_id'] = $device['device_id'];
$port['hostname'] = $device['hostname'];
$if_id = $port['port_id'];
$port = cleanPort($port);
if (! is_integer($i / 2)) {
$row_colour = Config::get('list_colour.even');
} else {
$row_colour = Config::get('list_colour.odd');
}
if ($port['ifInErrors_delta'] > 0 || $port['ifOutErrors_delta'] > 0) {
$error_img = generate_port_link($port, "<i class='fa fa-flag fa-lg' style='color:red' aria-hidden='true'></i>", 'port_errors');
} else {
$error_img = '';
}
echo "<tr style=\"background-color: $row_colour; padding: 5px;\" valign=top onmouseover=\"this.style.backgroundColor='" . Config::get('list_colour.highlight') . "';\" onmouseout=\"this.style.backgroundColor='$row_colour';\" style='cursor: pointer;'>
<td valign=top width=350>";
echo ' <span class=list-large>
' . generate_port_link($port, $port['ifIndex'] . '. ' . $port['label']) . '
</span><br /><span class=interface-desc>' . \LibreNMS\Util\Clean::html($port['ifAlias'], []) . '</span>';
if ($port['ifAlias']) {
echo '<br />';
}
$break = '';
if ($port_details) {
foreach (Ipv4Address::where('port_id', (string) $port['port_id']) as $ip) {
echo "$break <a class=interface-desc href=\"javascript:popUp('ajax/netcmd?cmd=whois&amp;query=" . $ip['ipv4_address'] . "')\">" . $ip['ipv4_address'] . '/' . $ip['ipv4_prefixlen'] . '</a>';
$break = ',';
}
foreach (Ipv6Address::where('port_id', (string) $port['port_id']) as $ip6) {
echo "$break <a class=interface-desc href=\"javascript:popUp('ajax/netcmd?cmd=whois&amp;query=" . $ip6['ipv6_address'] . "')\">" . IP::parse($ip6['ipv6_address'], true) . '/' . $ip6['ipv6_prefixlen'] . '</a>';
$break = ',';
}
}
echo '</span>';
$width = '120';
$height = '40';
$from = Config::get('time.day');
echo '</td><td width=135>';
echo \LibreNMS\Util\Number::formatSi($port['ifInOctets_rate'] * 8, 2, 3, 'bps') . " <i class='fa fa-arrows-v fa-lg icon-theme' aria-hidden='true'></i> " . \LibreNMS\Util\Number::formatSi($port['ifOutOctets_rate'] * 8, 2, 3, 'bps');
echo '<br />';
$port['graph_type'] = 'port_bits';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
echo '' . \LibreNMS\Util\Number::formatSi($port['xdsl2ChStatusActDataRateXtur'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['xdsl2ChStatusActDataRateXtuc'], 2, 3, 'bps');
echo '<br />';
$port['graph_type'] = 'port_vdsl_speed';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
echo '' . \LibreNMS\Util\Number::formatSi($port['xdsl2LineStatusAttainableRateDs'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['xdsl2LineStatusAttainableRateUs'], 2, 3, 'bps');
echo '<br />';
$port['graph_type'] = 'port_vdsl_attainable';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td><td width=135>';
//echo '' . $port['adslAturCurrAtn'] . 'dB/' . $port['adslAtucCurrAtn'] . 'dB';
//echo '<br />';
//$port['graph_type'] = 'port_adsl_attenuation';
//echo generate_port_link(
// $port,
// "<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
// $port['graph_type']
//);
echo '</td><td width=135>';
//echo '' . $port['adslAturCurrSnrMgn'] . 'dB/' . $port['adslAtucCurrSnrMgn'] . 'dB';
//echo '<br />';
//$port['graph_type'] = 'port_adsl_snr';
//echo generate_port_link(
// $port,
// "<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
// $port['graph_type']
//);
echo '</td><td width=135>';
echo '' . $port['xdsl2LineStatusActAtpDs'] . 'dBm/' . $port['xdsl2LineStatusActAtpUs'] . 'dBm';
echo '<br />';
$port['graph_type'] = 'port_vdsl_power';
echo generate_port_link(
$port,
"<img src='graph.php?type=" . $port['graph_type'] . '&amp;id=' . $port['port_id'] . '&amp;from=' . $from . '&amp;to=' . Config::get('time.now') . '&amp;width=' . $width . '&amp;height=' . $height . '&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "'>",
$port['graph_type']
);
echo '</td>';

View File

@ -1,370 +0,0 @@
<script>
$(function () {
$('[data-toggle="popover"]').popover()
})
</script>
<?php
use App\Models\Port;
use App\Models\PortAdsl;
use App\Models\PortVdsl;
use LibreNMS\Config;
use LibreNMS\Util\IP;
use LibreNMS\Util\Number;
// This file prints a table row for each interface
$port['device_id'] = $device['device_id'];
$port['hostname'] = $device['hostname'];
$if_id = $port['port_id'];
$port = cleanPort($port);
if (isset($int_colour)) {
$row_colour = $int_colour;
} else {
$i = $i ?? 0;
if (! is_integer($i / 2)) {
$row_colour = Config::get('list_colour.even');
} else {
$row_colour = Config::get('list_colour.odd');
}
$i++;
}
$port_adsl = PortAdsl::where('port_id', '=', $port['port_id']);
$port_vdsl = PortVdsl::where('port_id', '=', $port['port_id']);
if ($port['ifInErrors_delta'] > 0 || $port['ifOutErrors_delta'] > 0) {
$error_img = generate_port_link($port, "<i class='fa fa-flag fa-lg' style='color:red' aria-hidden='true'></i>", 'port_errors');
} else {
$error_img = '';
}
if (dbFetchCell('SELECT COUNT(*) FROM `mac_accounting` WHERE `port_id` = ?', [$port['port_id']])) {
$mac = "<a href='" . generate_port_url($port, ['view' => 'macaccounting']) . "'><i class='fa fa-pie-chart fa-lg icon-theme' aria-hidden='true'></i></a>";
} else {
$mac = '';
}
echo "<tr style=\"background-color: $row_colour;\" valign=top onmouseover=\"this.style.backgroundColor='" . Config::get('list_colour.highlight') . "';\" onmouseout=\"this.style.backgroundColor='$row_colour';\" style='cursor: pointer;'>
<td valign=top width=350>";
if (Auth::user()->hasGlobalRead()) {
$port_data = array_to_htmljson($port);
echo '<i class="fa fa-tag" data-toggle="popover" data-content="' . $port_data . '" data-html="true"></i>';
}
echo ' <span class=list-large>
' . generate_port_link($port, $port['label']) . " $error_img $mac
</span><br /><span class=interface-desc>" . $port['ifAlias'] . '</span>';
if ($port['ifAlias']) {
echo '<br />';
}
$break = '';
if (! empty($port_details)) {
foreach (dbFetchRows('SELECT * FROM `ipv4_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip) {
echo "$break <a class=interface-desc href=\"javascript:popUp('ajax/netcmd?cmd=whois&amp;query=$ip[ipv4_address]')\">" . $ip['ipv4_address'] . '/' . $ip['ipv4_prefixlen'] . '</a>';
$break = '<br />';
}
foreach (dbFetchRows('SELECT * FROM `ipv6_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip6) {
echo "$break <a class=interface-desc href=\"javascript:popUp('ajax/netcmd?cmd=whois&amp;query=" . $ip6['ipv6_address'] . "')\">" . IP::parse($ip6['ipv6_address'], true) . '/' . $ip6['ipv6_prefixlen'] . '</a>';
$break = '<br />';
}
}
echo '</span>';
$port_group_name_list = Port::find($port['port_id'])->groups->pluck('name')->toArray() ?: ['Default'];
echo '</td><td width=100>';
echo implode('<br>', $port_group_name_list);
echo "</td><td width=100 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
if (! empty($port_details)) {
$port['graph_type'] = 'port_bits';
echo generate_port_link($port, "<img src='graph.php?type=port_bits&amp;id=" . $port['port_id'] . '&amp;from=' . Config::get('time.day') . '&amp;to=' . Config::get('time.now') . '&amp;width=100&amp;height=20&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "00'>");
$port['graph_type'] = 'port_upkts';
echo generate_port_link($port, "<img src='graph.php?type=port_upkts&amp;id=" . $port['port_id'] . '&amp;from=' . Config::get('time.day') . '&amp;to=' . Config::get('time.now') . '&amp;width=100&amp;height=20&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "00'>");
$port['graph_type'] = 'port_errors';
echo generate_port_link($port, "<img src='graph.php?type=port_errors&amp;id=" . $port['port_id'] . '&amp;from=' . Config::get('time.day') . '&amp;to=' . Config::get('time.now') . '&amp;width=100&amp;height=20&amp;legend=no&amp;bg=' . str_replace('#', '', $row_colour) . "00'>");
}
echo "</td><td width=120 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
if ($port['ifOperStatus'] == 'up') {
$port['in_rate'] = ($port['ifInOctets_rate'] * 8);
$port['out_rate'] = ($port['ifOutOctets_rate'] * 8);
$in_perc = Number::calculatePercent($port['in_rate'], $port['ifSpeed'], 0);
$out_perc = Number::calculatePercent($port['in_rate'], $port['ifSpeed'], 0);
echo "<i class='fa fa-long-arrow-left fa-lg' style='color:green' aria-hidden='true'></i> <span style='color: " . percent_colour($in_perc) . "'>" . Number::formatSi($port['in_rate'], 2, 3, 'bps') . "<br />
<i class='fa fa-long-arrow-right fa-lg' style='color:blue' aria-hidden='true'></i> <span style='color: " . percent_colour($out_perc) . "'>" . Number::formatSi($port['out_rate'], 2, 3, 'bps') . "<br />
<i class='fa fa-long-arrow-left fa-lg' style='color:purple' aria-hidden='true'></i> " . Number::formatBi($port['ifInUcastPkts_rate'], 2, 3, 'pps') . "</span><br />
<i class='fa fa-long-arrow-right fa-lg' style='color:darkorange' aria-hidden='true'></i> " . Number::formatBi($port['ifOutUcastPkts_rate'], 2, 3, 'pps') . '</span>';
}
echo "</td><td width=75 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
if ($port['ifSpeed']) {
echo '<span class=box-desc>' . \LibreNMS\Util\Number::formatSi($port['ifSpeed'], 2, 3, 'bps') . '</span>';
}
echo '<br />';
if ($port['ifDuplex'] != 'unknown') {
echo '<span class=box-desc>' . $port['ifDuplex'] . '</span>';
} else {
echo '-';
}
$vlans = dbFetchColumn(
'SELECT vlan FROM `ports_vlans` AS PV, vlans AS V ' .
'WHERE PV.`port_id`=? AND PV.`device_id`=? AND V.`vlan_vlan`=PV.vlan AND V.device_id = PV.device_id',
[$port['port_id'], $device['device_id']]
);
$vlan_count = count($vlans);
if ($vlan_count > 1) {
echo '<p class=box-desc><span class=purple><a href="';
echo \LibreNMS\Util\Url::deviceUrl((int) $device['device_id'], ['tab' => 'vlans']);
echo '" title="';
echo implode(', ', $vlans);
echo '">VLANs: ';
echo $vlan_count;
echo '</a></span></p>';
} elseif ($vlan_count == 1 || $port['ifVlan']) {
echo '<p class=box-desc><span class=blue>VLAN: ';
echo $vlans[0] ?: $port['ifVlan'];
echo '</span></p>';
} elseif ($port['ifVrf']) {
$vrf = dbFetchRow('SELECT * FROM vrfs WHERE vrf_id = ?', [$port['ifVrf']]);
echo "<p style='color: green;'>" . $vrf['vrf_name'] . '</p>';
}//end if
if (! empty($port_adsl->adslLineCoding)) {
echo "</td><td width=150 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
echo $port_adsl->adslLineCoding . '/' . rewrite_adslLineType($port_adsl->adslLineType);
echo '<br />';
// ATU-C is CO -> ATU-C TX is the download speed for the CPE
// ATU-R is the CPE -> ATU-R TX is the upload speed of the CPE
echo 'Sync:' . Number::formatSi($port_adsl->adslAtucChanCurrTxRate, 2, 3, 'bps') . '/' . Number::formatSi($port_adsl->adslAturChanCurrTxRate, 2, 3, 'bps');
echo '<br />';
// This is the Receive Max AttainableRate, so :
// adslAturCurrAttainableRate is DownloadMaxRate
// adslAtucCurrAttainableRate is UploadMaxRate
echo 'Max:' . Number::formatSi($port_adsl->adslAturCurrAttainableRate, 2, 3, 'bps') . '/' . Number::formatSi($port_adsl->adslAtucCurrAttainableRate, 2, 3, 'bps');
echo "</td><td width=150 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
echo 'Atten:' . $port_adsl->adslAturCurrAtn . 'dB/' . $port_adsl->adslAtucCurrAtn . 'dB';
echo '<br />';
echo 'SNR:' . $port_adsl->adslAturCurrSnrMgn . 'dB/' . $port_adsl->adslAtucCurrSnrMgn . 'dB';
} elseif (! empty($port_vdsl->xdsl2LineStatusAttainableRateDs)) {
echo "</td><td width=150 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
echo '<br />';
// ATU-C is CO -> ATU-C TX is the download speed for the CPE
// ATU-R is the CPE -> ATU-R TX is the upload speed of the CPE
echo 'Sync:' . Number::formatSi($port_vdsl->xdsl2ChStatusActDataRateXtur, 2, 3, 'bps') . '/' . Number::formatSi($port_vdsl->xdsl2ChStatusActDataRateXtuc, 2, 3, 'bps');
echo '<br />';
echo 'Max:' . Number::formatSi($port_vdsl->xdsl2LineStatusAttainableRateDs, 2, 3, 'bps') . '/' . Number::formatSi($port_vdsl->xdsl2LineStatusAttainableRateUs, 2, 3, 'bps');
} else {
echo "</td><td width=150 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
if ($port['ifType'] && $port['ifType'] != '') {
echo '<span class=box-desc>' . \LibreNMS\Util\Rewrite::normalizeIfType($port['ifType']) . '</span>';
} else {
echo '-';
}
echo '<br />';
if (! empty($ifHardType)) {
echo '<span class=box-desc>' . $ifHardType . '</span>';
} else {
echo '-';
}
echo "</td><td width=150 onclick=\"location.href='" . generate_port_url($port) . "'\" >";
if ($port['ifPhysAddress'] && $port['ifPhysAddress'] != '') {
echo '<span class=box-desc>' . $port->ifPhysAddress . '</span>';
} else {
echo '-';
}
echo '<br />';
if ($port['ifMtu'] && $port['ifMtu'] != '') {
echo '<span class=box-desc>MTU ' . $port['ifMtu'] . '</span>';
} else {
echo '-';
}
}//end if
echo '</td>';
echo '<td width=375 valign=top class="interface-desc">';
$neighborsCount = 0;
$nbLinks = 0;
$int_links = [];
if (strpos($port['label'], 'oopback') === false && empty($graph_type)) {
foreach (dbFetchRows('SELECT * FROM `links` AS L, `ports` AS I, `devices` AS D WHERE L.local_port_id = ? AND L.remote_port_id = I.port_id AND I.device_id = D.device_id', [$if_id]) as $link) {
$int_links[$link['port_id']] = $link['port_id'];
$int_links_phys[$link['port_id']] = 1;
$nbLinks++;
}
unset($br);
if (! empty($port_details) && Config::get('enable_port_relationship') === true) {
// Show which other devices are on the same subnet as this interface
foreach (dbFetchRows("SELECT `ipv4_network_id` FROM `ipv4_addresses` WHERE `port_id` = ? AND `ipv4_address` NOT LIKE '127.%'", [$port['port_id']]) as $net) {
$ipv4_network_id = $net['ipv4_network_id'];
$sql = 'SELECT I.port_id FROM ipv4_addresses AS A, ports AS I, devices AS D
WHERE A.port_id = I.port_id
AND A.ipv4_network_id = ? AND D.device_id = I.device_id
AND D.device_id != ?';
$array = [
$net['ipv4_network_id'],
$device['device_id'],
];
foreach (dbFetchRows($sql, $array) as $new) {
echo $new['ipv4_network_id'];
$this_ifid = $new['port_id'];
$this_hostid = $new['device_id'];
$this_hostname = $new['hostname'];
$this_ifname = \LibreNMS\Util\Rewrite::normalizeIfName($new['label']);
$int_links[$this_ifid] = $this_ifid;
$int_links_v4[$this_ifid] = 1;
}
}//end foreach
foreach (dbFetchRows('SELECT ipv6_network_id FROM ipv6_addresses WHERE port_id = ?', [$port['port_id']]) as $net) {
$ipv6_network_id = $net['ipv6_network_id'];
$sql = "SELECT I.port_id FROM ipv6_addresses AS A, ports AS I, devices AS D
WHERE A.port_id = I.port_id
AND A.ipv6_network_id = ? AND D.device_id = I.device_id
AND D.device_id != ? AND A.ipv6_origin != 'linklayer' AND A.ipv6_origin != 'wellknown'";
$array = [
$net['ipv6_network_id'],
$device['device_id'],
];
foreach (dbFetchRows($sql, $array) as $new) {
echo $new['ipv6_network_id'];
$this_ifid = $new['port_id'];
$this_hostid = $new['device_id'];
$this_hostname = $new['hostname'];
$this_ifname = \LibreNMS\Util\Rewrite::normalizeIfName($new['label']);
$int_links[$this_ifid] = $this_ifid;
$int_links_v6[$this_ifid] = 1;
}
}//end foreach
}//end if
if (count($int_links) > 3) {
echo '<div class="collapse-neighbors"><i class="neighbors-button fa fa-plus fa-lg" aria-hidden="true"></i>
<span class="neighbors-interface-list-firsts" style="display: inline;">';
}
if (! empty($port_details) && Config::get('enable_port_relationship') === true && port_permitted($int_link, $device['device_id'])) {
foreach ($int_links as $int_link) {
$neighborsCount++;
if ($neighborsCount == 4) {
echo '<span class="neighbors-list-continued" style="display: inline;"></br>[...]</span>';
echo '</span>';
echo '<span class="neighbors-interface-list" style="display: none;">';
}
$link_if = dbFetchRow('SELECT * from ports AS I, devices AS D WHERE I.device_id = D.device_id and I.port_id = ?', [$int_link]);
$link_if = cleanPort($link_if);
echo "$br";
if ($int_links_phys[$int_link]) {
echo "<i class='fa fa-plus fa-lg' style='color:black' aria-hidden='true'></i> ";
} else {
echo "<i class='fa fa-arrow-right fa-lg' style='color:green' aria-hidden='true'></i> ";
}
echo '<b>' . generate_port_link($link_if, makeshortif($link_if['label'])) . ' on ' . generate_device_link($link_if);
if ($int_links_v6[$int_link]) {
echo " <b style='color: #a10000;'>v6</b>";
}
if ($int_links_v4[$int_link]) {
echo " <b style='color: #00a100'>v4</b>";
}
$br = '<br />';
}//end foreach
}//end if
// unset($int_links, $int_links_v6, $int_links_v4, $int_links_phys, $br);
}//end if
$br = '';
if (! empty($port_details) && Config::get('enable_port_relationship') === true && port_permitted($port['port_id'], $device['device_id'])) {
foreach (dbFetchRows('SELECT * FROM `pseudowires` WHERE `port_id` = ?', [$port['port_id']]) as $pseudowire) {
// `port_id`,`peer_device_id`,`peer_ldp_id`,`cpwVcID`,`cpwOid`
$pw_peer_dev = dbFetchRow('SELECT * FROM `devices` WHERE `device_id` = ?', [$pseudowire['peer_device_id']]);
$pw_peer_int = dbFetchRow('SELECT * FROM `ports` AS I, pseudowires AS P WHERE I.device_id = ? AND P.cpwVcID = ? AND P.port_id = I.port_id', [$pseudowire['peer_device_id'], $pseudowire['cpwVcID']]);
$pw_peer_int = cleanPort($pw_peer_int);
echo "$br<i class='fa fa-cube fa-lg' style='color:green' aria-hidden='true'></i><b> " . generate_port_link($pw_peer_int, makeshortif($pw_peer_int['label'])) . ' on ' . generate_device_link($pw_peer_dev) . '</b>';
$br = '<br />';
}
foreach (dbFetchRows('SELECT * FROM `ports` WHERE `pagpGroupIfIndex` = ? and `device_id` = ?', [$port['ifIndex'], $device['device_id']]) as $member) {
$member = cleanPort($member);
echo "$br<i class='fa fa-cube fa-lg icon-theme' aria-hidden='true'></i> <strong>" . generate_port_link($member) . ' (PAgP)</strong>';
$br = '<br />';
}
if ($port['pagpGroupIfIndex'] && $port['pagpGroupIfIndex'] != $port['ifIndex']) {
$parent = dbFetchRow('SELECT * FROM `ports` WHERE `ifIndex` = ? and `device_id` = ?', [$port['pagpGroupIfIndex'], $device['device_id']]);
$parent = cleanPort($parent);
echo "$br<i class='fa fa-cube fa-lg icon-theme' aria-hidden='true'></i> <strong>" . generate_port_link($parent) . ' (PAgP)</strong>';
$br = '<br />';
}
foreach (dbFetchRows('SELECT * FROM `ports_stack` WHERE `port_id_low` = ? and `device_id` = ?', [$port['ifIndex'], $device['device_id']]) as $higher_if) {
if ($higher_if['port_id_high']) {
$this_port = get_port_by_index_cache($device['device_id'], $higher_if['port_id_high']);
$this_port = cleanPort($this_port);
echo "$br<i class='fa fa-expand fa-lg icon-theme' aria-hidden='true'></i> <strong>" . generate_port_link($this_port) . '</strong>';
$br = '<br />';
}
}
foreach (dbFetchRows('SELECT * FROM `ports_stack` WHERE `port_id_high` = ? and `device_id` = ?', [$port['ifIndex'], $device['device_id']]) as $lower_if) {
if ($lower_if['port_id_low']) {
$this_port = get_port_by_index_cache($device['device_id'], $lower_if['port_id_low']);
$this_port = cleanPort($this_port);
echo "$br<i class='fa fa-compress fa-lg icon-theme' aria-hidden='true'></i> <strong>" . generate_port_link($this_port) . '</strong>';
$br = '<br />';
}
}
}//end if
unset($int_links, $int_links_v6, $int_links_v4, $int_links_phys, $br);
if ($nbLinks > 3) {
echo '</span></div>';
}
echo '</td></tr>';
// If we're showing graphs, generate the graph and print the img tags
if (isset($graph_type)) {
if ($graph_type == 'etherlike') {
$graph_file = get_port_rrdfile_path($device['hostname'], $if_id, 'dot3');
} else {
$graph_file = get_port_rrdfile_path($device['hostname'], $if_id);
}
if (is_file($graph_file)) {
$type = $graph_type;
echo "<tr style='background-color: $row_colour; padding: 0px;'><td colspan=7>";
include 'includes/html/print-interface-graphs.inc.php';
echo '</td></tr>';
}
}

View File

@ -165,21 +165,7 @@ function short_port_descr($desc)
function rewrite_adslLineType($adslLineType)
{
$adslLineTypes = [
'noChannel' => 'No Channel',
'fastOnly' => 'Fastpath',
'interleavedOnly' => 'Interleaved',
'fastOrInterleaved' => 'Fast/Interleaved',
'fastAndInterleaved' => 'Fast+Interleaved',
];
foreach ($adslLineTypes as $type => $text) {
if ($adslLineType == $type) {
$adslLineType = $text;
}
}
return $adslLineType;
return \LibreNMS\Util\Rewrite::dslLineType($adslLineType);
}
function ipmiSensorName($hardwareId, $sensorIpmi)

View File

@ -5,4 +5,37 @@ return [
'updated' => ':port: groups updated',
'none' => ':port no update requested',
],
'filters' => [
'status_up' => 'Only Show Up',
'admin_down' => 'Show Admin Down',
'disabled' => 'Show Disabled',
'ignored' => 'Show Ignored',
],
'graphs' => [
'bits' => 'Bits',
'upkts' => 'Unicast Packets',
'nupkts' => 'Non-Unicast Packets',
'errors' => 'Errors',
'etherlike' => 'Etherlike',
],
'mtu_label' => 'MTU :mtu',
'tabs' => [
'arp' => 'ARP Table',
'fdb' => 'FDB Table',
'links' => 'Neighbors',
'xdsl' => 'xDSL',
],
'vlan_count' => 'VLANs: :count',
'vlan_label' => 'VLAN: :label',
'xdsl' => [
'sync_stat' => 'Sync: :down/:up',
'attainable_stat' => 'Max: :down/:up',
'attenuation_stat' => 'Atten: :down/:up',
'snr_stat' => 'SNR: :down/:up',
'sync' => 'Sync Speed',
'attainable' => 'Attainable Speed',
'attenuation' => 'Attenuation',
'snr' => 'SNR Margin',
'power' => 'Output Powers',
],
];

View File

@ -1719,10 +1719,6 @@
"default": false,
"type": "boolean"
},
"enable_port_relationship": {
"default": true,
"type": "boolean"
},
"enable_ports_adsl": {
"default": true,
"type": "boolean"

View File

@ -3344,14 +3344,6 @@ parameters:
count: 1
path: includes/html/pages/device/munin.inc.php
-
message: """
#^Call to deprecated function dbFetchRows\\(\\)\\:
Please use Eloquent instead; https\\://laravel\\.com/docs/eloquent$#
"""
count: 1
path: includes/html/pages/device/neighbours/list.inc.php
-
message: """
#^Call to deprecated function dbFetchRow\\(\\)\\:
@ -4072,14 +4064,6 @@ parameters:
count: 1
path: includes/html/print-graph-alerts.inc.php
-
message: """
#^Call to deprecated function dbFetchRows\\(\\)\\:
Please use Eloquent instead; https\\://laravel\\.com/docs/eloquent$#
"""
count: 2
path: includes/html/print-interface-adsl.inc.php
-
message: """
#^Call to deprecated function dbFetchCell\\(\\)\\:

View File

@ -0,0 +1,15 @@
<div
x-data="{overflowed: false, expand() { this.overflowed=false; $refs.container.style.height = 'auto' }}"
x-init="$nextTick(() => {overflowed = $refs.container.offsetHeight < $refs.container.scrollHeight})">
<div x-ref="container" class="tw-overflow-y-hidden"
style="height:{{ $attributes->get('height') }}">{{ $slot }}</div>
<div
x-cloak
x-on:click="expand()" x-show="overflowed"
x-transition:enter="tw-transition tw-ease-out tw-duration-700"
x-transition:enter-start="tw-opacity-0"
x-transition:enter-end="tw-opacity-100"
class="tw-cursor-pointer tw-leading-6"
title="{{ __('More') }}">...
</div>
</div>

View File

@ -1,9 +1,5 @@
<x-popup>
<a class="@if($status=='disabled') tw-text-gray-400 visited:tw-text-gray-400 @elseif($status=='down') tw-text-red-600 visited:tw-text-red-600 @else tw-text-blue-900 visited:tw-text-blue-900 dark:tw-text-dark-white-100 dark:visited:tw-text-dark-white-100 @endif"
href="{{ $link }}"
{{ $attributes }}>
{{ $slot->isNotEmpty() ? $slot : $label }}
</a>
@include('components.port-link_basic')
<x-slot name="title">
<div class="tw-text-xl tw-font-bold">{{ $port->device->displayName() }} - {{ $label }}</div>
<div>{{ $description }}</div>

View File

@ -0,0 +1,5 @@
<a class="@if($status=='disabled') tw-text-gray-400 visited:tw-text-gray-400 @elseif($status=='down') tw-text-red-600 visited:tw-text-red-600 @else tw-text-blue-900 visited:tw-text-blue-900 dark:tw-text-dark-white-100 dark:visited:tw-text-dark-white-100 @endif"
href="{{ $link }}"
{{ $attributes }}>
{{ $slot->isNotEmpty() ? $slot : $label }}
</a>

View File

@ -0,0 +1,22 @@
@props(['label', 'name', 'options', 'selected', 'hint'])
<div {{ $attributes }}>
@isset($label)
<label for="{{ $name }}" class="tw-mb-0 tw-text-sm tw-leading-5 tw-font-medium tw-text-gray-700 dark:tw-text-gray-400">
{{ $label }}
</label>
@endif
<select id="{{ $name }}" class="tw-p-5px tw-text-sm tw-bg-gray-50 tw-border tw-border-gray-300 tw-text-gray-900 tw-rounded-lg focus:tw-ring-blue-500 focus:tw-border-blue-500 tw-p-1 dark:tw-bg-gray-700 dark:tw-border-gray-600 dark:tw-placeholder-gray-400 dark:tw-text-white dark:tw-focus:ring-blue-500 dark:focus:tw-border-blue-500">
<option hidden disabled @empty($selected)selected @endempty>{{ $hint ?? __('Choose') }}</option>
@foreach($options ?? [] as $option)
<option value="{{ $option['value'] ?? $option }}"
@if(isset($selected) && $selected == ($option['value'] ?? $option))selected @endif
>
@isset($option['icon'])
<i class="fa fa-fw fa-lg {{ $option['icon'] }}"></i>
@endisset
{{ $option['text'] ?? $option }}
</option>
@endforeach
</select>
</div>

View File

@ -2,15 +2,19 @@
<div class="panel-heading">
@foreach ($menu as $header => $m)
@if($loop->first)
<b>{{ $title }}</b>
»
<span class="tw-font-bold">{{ $title }} »</span>
@else
| {{ $header }}:
<span class="tw-ml-4 tw-font-bold">{{ $header }}:</span>
@endif
@foreach($m as $sm)
@if($isSelected($sm['url']))<span class="pagemenu-selected">@endif
<a href="{{ route('device', ['device' => $device_id, 'tab' => $current_tab, 'vars' => $sm['url']]) }}">{{ $sm['name'] }}</a>@if($isSelected($sm['url']))</span>@endif
<span @if($isSelected($sm['url']))class="pagemenu-selected"@endif>
<a href="{{ route('device', ['device' => $device_id, 'tab' => $current_tab, 'vars' => $sm['url']]) }}">{{ $sm['name'] }}</a>
</span>
@isset($sm['sub_name'])
(<span @if($isSelected($sm['sub_url']))class="pagemenu-selected"@endif><a href="{{ route('device', ['device' => $device_id, 'tab' => $current_tab, 'vars' => $sm['sub_url']]) }}">{{ $sm['sub_name'] }}</a></span>)
@endisset
@if(!$loop->last)
|

View File

@ -36,6 +36,15 @@
@if($link['external'])target="_blank" rel="noopener" @endif
><i class="fa {{ $link['icon'] }} fa-lg fa-fw icon-theme" aria-hidden="true"></i> {{ $link['title'] }}</a></li>
@endforeach
@if($page_links)
<li role="presentation" class="divider"></li>
@foreach($page_links as $link)
<li><a href="{{ $link['url'] }}"
@if(isset($link['onclick']))onclick="{{ $link['onclick'] }}" @endif
@if($link['external'])target="_blank" rel="noopener" @endif
><i class="fa {{ $link['icon'] }} fa-lg fa-fw icon-theme" aria-hidden="true"></i> {{ $link['title'] }}</a></li>
@endforeach
@endif
</ul>
</div>
</div>

View File

@ -0,0 +1,5 @@
@extends('device.submenu')
@section('tabcontent')
@includeFirst(['device.tabs.ports.' . $data['tab'], 'device.tabs.ports.detail'])
@endsection

View File

@ -1,37 +1,33 @@
<?php
$no_refresh = true;
?>
<table id="ports-arp" class="table table-condensed table-hover table-striped">
<thead>
<x-panel body-class="!tw-p-0">
<table id="ports-arp" class="table table-condensed table-hover table-striped tw-mt-1 !tw-mb-0">
<thead>
<tr>
<th data-column-id="interface">Port</th>
<th data-column-id="mac_address" data-formatter="tooltip">MAC address</th>
<?php
if (\LibreNMS\Config::get('mac_oui.enabled') === true) {
echo ' <th data-column-id="mac_oui" data-sortable="false" data-width="150px" data-visible="false" data-formatter="tooltip">Vendor</th>';
}
?>
@config('mac_oui.enabled')
<th data-column-id="mac_oui" data-sortable="false" data-width="150px" data-visible="false" data-formatter="tooltip">Vendor</th>
@endconfig
<th data-column-id="ipv4_address" data-formatter="tooltip">IPv4 address</th>
<th data-column-id="remote_device" data-sortable="false">Remote device</th>
<th data-column-id="remote_interface" data-sortable="false">Remote interface</th>
</tr>
</thead>
</table>
</thead>
</table>
</x-panel>
<script>
var grid = $("#ports-arp").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
post: function ()
{
return {
id: "arp-search",
device_id: "<?php echo $device['device_id']; ?>"
};
},
formatters: {
"tooltip": function (column, row) {
var grid = $("#ports-arp").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
post: function ()
{
return {
id: "arp-search",
device_id: "{{ $device->device_id }}"
};
},
formatters: {
"tooltip": function (column, row) {
var value = row[column.id];
var vendor = '';
if (column.id == 'mac_address' && ((vendor = row['mac_oui']) != '' )) {
@ -39,8 +35,7 @@ var grid = $("#ports-arp").bootgrid({
}
return "<span title=\'" + value + "\' data-toggle=\'tooltip\'>" + value + "</span>";
},
},
url: "ajax_table.php"
});
},
url: "ajax_table.php"
});
</script>

View File

@ -0,0 +1 @@
@include('device.tabs.ports.detail')

View File

@ -0,0 +1,35 @@
<x-panel body-class="!tw-p-0">
<table id="ports-fdb" class="table table-condensed table-hover table-striped tw-mt-1 !tw-mb-0">
<thead>
<tr>
<th width="350"><a href="{{ $request->fullUrlWithQuery(['sort' => 'port', 'order' => $data['sort'] == 'port' ? $data['next_order'] : 'asc']) }} ">{{ __('Port') }}</a></th>
<th width="100" class="tw-hidden md:tw-table-cell">{{ __('Port Groups') }}</th>
<th width="100">{{ __('Graphs') }}</th>
<th width="120"><a href="{{ $request->fullUrlWithQuery(['sort' => 'traffic', 'order' => $data['sort'] == 'traffic' ? $data['next_order'] : 'desc']) }} ">{{ __('Traffic') }}</a></th>
<th width="75"><a href="{{ $request->fullUrlWithQuery(['sort' => 'speed', 'order' => $data['sort'] == 'speed' ? $data['next_order'] : 'desc']) }} ">{{ __('Speed') }}</a></th>
<th width="100" class="tw-hidden sm:tw-table-cell"><a href="{{ $request->fullUrlWithQuery(['sort' => 'media', 'order' => $data['sort'] == 'media' ? $data['next_order'] : 'asc']) }} ">{{ __('Media') }}</a></th>
<th width="100"><a href="{{ $request->fullUrlWithQuery(['sort' => 'mac', 'order' => $data['sort'] == 'mac' ? $data['next_order'] : 'asc']) }} ">{{ __('MAC Address') }}</a></th>
<th width="375" class="tw-hidden md:tw-table-cell"></th>
</tr>
</thead>
@foreach($data['ports'] as $port)
@include('device.tabs.ports.includes.port_row', ['collapsing' => true])
@endforeach
</table>
<div class="tw-flex tw-flex-row-reverse tw-m-3">
{{ $data['ports']->links('pagination::tailwind', ['perPage' => $data['perPage']]) }}
@isset($data['perPage'])
<x-select :options="['10', '25', '100', 'all']"
{{-- location.herf = --}}
x-on:change="
const params = new URLSearchParams(window.location.search);
params.set('perPage', $event.target.value);
window.location.search = params.toString();
" x-data="{}"
:selected="$data['perPage']"
name="perPage"
label="{{ __('Per Page') }}"
class="tw-mx-4"></x-select>
@endisset
</div>
</x-panel>

View File

@ -1,11 +1,9 @@
<?php
$no_refresh = true;
?>
<table id="ports-fdb" class="table table-condensed table-hover table-striped">
<thead>
<x-panel body-class="!tw-p-0">
<table id="ports-fdb" class="table table-condensed table-hover table-striped tw-mt-1 !tw-mb-0">
<thead>
<tr>
<th data-column-id="mac_address" data-width="150px" data-formatter="tooltip">MAC Address</th>
<th data-column-id="mac_oui" data-sortable="false" data-width="150px" data-visible="<?php echo \LibreNMS\Config::get('mac_oui.enabled') ? 'true' : 'false' ?>" data-formatter="tooltip">Vendor</th>
<th data-column-id="mac_oui" data-sortable="false" data-width="150px" @notconfig('mac_oui.enabled')data-visible="false"@endnotconfig data-formatter="tooltip">Vendor</th>
<th data-column-id="ipv4_address" data-sortable="false" data-formatter="tooltip">IPv4 Address</th>
<th data-column-id="interface">Port</th>
<th data-column-id="description" data-formatter="tooltip">Description</th>
@ -14,22 +12,22 @@ $no_refresh = true;
<th data-column-id="first_seen" data-width="165px">First seen</th>
<th data-column-id="last_seen" data-width="165px">Last seen</th>
</tr>
</thead>
</table>
</thead>
</table>
</x-panel>
<script>
var grid = $("#ports-fdb").bootgrid({
ajax: true,
post: function ()
{
return {
device_id: "<?php echo $device['device_id']; ?>",
dns: $("#ports-fdb").bootgrid("getColumnSettings").find(col => col.id === "dnsname").visible,
};
},
formatters: {
"tooltip": function (column, row) {
var grid = $("#ports-fdb").bootgrid({
ajax: true,
post: function ()
{
return {
device_id: "{{ $device->device_id }}",
dns: $("#ports-fdb").bootgrid("getColumnSettings").find(col => col.id === "dnsname").visible,
};
},
formatters: {
"tooltip": function (column, row) {
var value = row[column.id];
var vendor = '';
if (column.id == 'mac_address' && ((vendor = row['mac_oui']) != '' )) {
@ -37,8 +35,7 @@ var grid = $("#ports-fdb").bootgrid({
}
return "<span title=\'" + value + "\' data-toggle=\'tooltip\'>" + value + "</span>";
},
},
url: "<?php echo url('/ajax/table/fdb-tables') ?>"
});
},
url: "{{ url('/ajax/table/fdb-tables') }}"
});
</script>

View File

@ -0,0 +1,19 @@
@foreach($data['ports'] as $port)
<x-panel>
<x-slot name="title">
<div>
{{-- div to allow color to override boostrap title link color --}}
<x-port-link basic :port="$port">
<span class="tw-text-3xl tw-font-bold">
<i class="fa fa-tag" aria-hidden='true'></i>
{{ $port->getLabel() }}
@if($port->getLabel() !== $port->getDescription())
<span class="tw-text-xl tw-font-normal">{{ $port->getDescription() }}</span>
@endif
</span>
</x-port-link>
</div>
</x-slot>
<x-graph-row loading="lazy" columns="responsive" :port="$port" :type="$data['graph_type']" :graphs="[['from' => '-1d'], ['from' => '-1w'], ['from' => '-1m'], ['from' => '-1y']]" legend="no"></x-graph-row>
</x-panel>
@endforeach

View File

@ -0,0 +1,138 @@
<tr>
<td>
<x-port-link :port="$port">
<span class="tw-text-3xl tw-font-bold"><i class="fa fa-tag" aria-hidden='true'></i> {{ $port->getLabel() }}</span>
</x-port-link>
<div>
@if($port->ifInErrors_delta > 0 || $port->ifOutErrors_delta > 0)
<a href="{{ route('device', ['device' => $port->device_id, 'tab' => 'port', 'vars' => 'port=' . $port->port_id]) }}"><i class="fa fa-flag fa-lg tw-text-red-600"></i></a>
@endif
@if($port->getLabel() !== $port->getDescription())
<span class="tw-text-base">{{ $port->getDescription() }}</span>
@endif
</div>
@if($data['tab'] != 'basic')
@foreach($port->ipv4 as $ipv4)
<div><a class="tw-text-base" href="javascript:popUp('{{ url('ajax/netcmd?cmd=whois&query=' . $ipv4->ipv4_address) }}')">{{ $ipv4->ipv4_address }}/{{ $ipv4->ipv4_prefixlen }}</a></div>
@endforeach
@foreach($port->ipv6 as $ipv6)
<div><a class="tw-text-base" href="javascript:popUp('{{ url('ajax/netcmd?cmd=whois&query=' . $ipv6->ipv6_compressed) }}')">{{ $ipv6->ipv6_compressed }}/{{ $ipv6->ipv6_prefixlen }}</a></div>
@endforeach
@endif
</td>
<td @if($collapsing)class="tw-hidden md:tw-table-cell"@endif>
@forelse($port->groups as $group)
<div>{{ $group->name }}</div>
@empty
<div>{{ __('Default') }}</div>
@endforelse
</td>
<td>
<div class="tw-flex tw-flex-col">
<x-port-link :port="$port" :graphs="$data['graphs']['bits']">
<x-graph :port="$port" type="port_bits" width="100" height="20" legend="no"></x-graph>
</x-port-link>
<x-port-link :port="$port" :graphs="$data['graphs']['upkts']">
<x-graph :port="$port" type="port_upkts" width="100" height="20" legend="no"></x-graph>
</x-port-link>
<x-port-link :port="$port" :graphs="$data['graphs']['errors']">
<x-graph :port="$port" type="port_errors" width="100" height="20" legend="no"></x-graph>
</x-port-link>
</div>
</td>
<td class="tw-whitespace-nowrap">
<div>
<i class="fa fa-long-arrow-left fa-lg tw-text-green-600" aria-hidden="true"></i>
<span style="color: {{ \LibreNMS\Util\Color::percent($port->in_rate, $port->ifSpeed) }}">{{ \LibreNMS\Util\Number::formatSi($port->ifInOctets_rate * 8, 2, 3, 'bps') }}</span>
</div>
<div>
<i class="fa fa-long-arrow-right fa-lg" style="color:blue" aria-hidden="true"></i>
<span style="color: {{ \LibreNMS\Util\Color::percent($port->out_rate, $port->ifSpeed) }}">{{ \LibreNMS\Util\Number::formatSi($port->ifOutOctets_rate * 8, 2, 3, 'bps') }}</span>
</div>
<div>
<i class="fa fa-long-arrow-left fa-lg" style="color:purple" aria-hidden="true"></i>
{{ \LibreNMS\Util\Number::formatBi($port->ifInUcastPkts_rate, 2, 3, 'pps') }}
</div>
<div>
<i class="fa fa-long-arrow-right fa-lg" style="color:darkorange" aria-hidden="true"></i>
{{ \LibreNMS\Util\Number::formatBi($port->ifOutUcastPkts_rate, 2, 3, 'pps') }}
</div>
</td>
<td>
@if($port->ifSpeed)
<div>{{ \LibreNMS\Util\Number::formatSi($port->ifSpeed, 2, 3, 'bps') }}</div>
@endif
@if($port->ifDuplex != 'unknown')
<div>{{ $port->ifDuplex }}</div>
@endif
@if($port->vlans->isNotEmpty())
<div class="tw-text-blue-800">
<a href="{{ \LibreNMS\Util\Url::deviceUrl($device->device_id, ['tab' => 'vlans']) }}">
@if($port->vlans->count() > 1)
<span title="{{ $port->vlans->pluck('vlan')->implode(',') }}">{{ __('port.vlan_count', ['count' => $port->vlans->count()]) }}</span>
@elseif($port->vlans->count() == 1 || $port->ifVlan)
{{ __('port.vlan_label', ['label' => $port->vlans->first()->vlan ?: $port->ifVlan]) }}
@elseif($port->ifVrf)
{{ Vrf::where('vrf_id', $port->ifVrf)->value('vrf_name') }}
@endif
</a>
</div>
@endif
</td>
<td @if($collapsing)class="tw-hidden sm:tw-table-cell"@endif>
@if($port->adsl)
<div>{{ $port->adsl->adslLineCoding }}/{{ \LibreNMS\Util\Rewrite::dslLineType($port->adsl->adslLineType) }}</div>
<div>{{ __('port.xdsl.sync_stat', ['down' => \LibreNMS\Util\Number::formatSi($port->adsl->adslAtucChanCurrTxRate, 2, 3, 'bps'), 'up' => \LibreNMS\Util\Number::formatSi($port->adsl->adslAturChanCurrTxRate, 2, 3, 'bps')]) }}</div>
<div>{{ __('port.xdsl.attainable_stat', ['down' => \LibreNMS\Util\Number::formatSi($port->adsl->adslAtucCurrAttainableRate, 2, 3, 'bps'), 'up' => \LibreNMS\Util\Number::formatSi($port->adsl->adslAturCurrAttainableRate, 2, 3, 'bps')]) }}</div>
<div>{{ __('port.xdsl.attenuation_stat', ['down' => $port->adsl->adslAtucCurrAtn . 'dB', 'up' => $port->adsl->adslAturCurrAtn . 'dB']) }}</div>
<div>{{ __('port.xdsl.snr_stat', ['down' => $port->adsl->adslAtucCurrSnrMgn . 'dB','up' => $port->adsl->adslAturCurrSnrMgn . 'dB']) }}</div>
@elseif($port->vdsl)
<div>{{ __('port.xdsl.sync_stat', ['down' => \LibreNMS\Util\Number::formatSi($port->vdsl->xdsl2ChStatusActDataRateXtuc, 2, 3, 'bps'), 'up' => \LibreNMS\Util\Number::formatSi($port->vdsl->xdsl2ChStatusActDataRateXtur, 2, 3, 'bps')]) }}</div>
<div>{{ __('port.xdsl.attainable_stat', ['down' => \LibreNMS\Util\Number::formatSi($port->vdsl->xdsl2LineStatusAttainableRateDs, 2, 3, 'bps'), 'up' => \LibreNMS\Util\Number::formatSi($port->vdsl->xdsl2LineStatusAttainableRateUs, 2, 3, 'bps')]) }}</div>
@else
<div>{{ \LibreNMS\Util\Rewrite::normalizeIfType($port->ifType) }}</div>
@endif
</td>
<td>
<div>{{ $port->ifPhysAddress }}</div>
<div>{{ $port->ifMtu ? __('port.mtu_label', ['mtu' => $port->ifMtu]) : '' }}</div>
</td>
<td @if($collapsing)class="tw-hidden md:tw-table-cell"@endif>
<x-expandable height="4em">
@foreach($data['neighbors'][$port->port_id] as $port_id => $neighbor)
<div>
@php
$np = $data['neighbor_ports']->get($neighbor['port_id']);
@endphp
@if($np)
@if(isset($neighbor['link']))
<i class="fa fa-link fa-lg" aria-hidden="true"></i>
@elseif(isset($neighbor['pseudowire']))
<i class="fa fa-arrows-left-right fa-lg" aria-hidden="true"></i>
@elseif(isset($neighbor['stack_low']))
<i class="fa fa-expand fa-lg" aria-hidden="true"></i>
@elseif(isset($neighbor['stack_high']))
<i class="fa fa-compress fa-lg" aria-hidden="true"></i>
@elseif(isset($neighbor['pagp']))
<i class="fa fa-cube fa-lg tw-text-green-600" aria-hidden="true"></i>
@else
<i class="fa fa-arrow-right fa-lg" aria-hidden="true"></i>
@endif
<x-port-link :port="$np"></x-port-link>
on
<x-device-link :device="$np->device"></x-device-link>
@isset($neighbor['ipv6_network'])
<b class="tw-text-red-700">v6</b>
@endisset
@isset($neighbor['ipv4_network'])
<b class="tw-text-green-600">v4</b>
@endisset
@endif
</div>
@endforeach
</x-expandable>
</td>
</tr>

View File

@ -0,0 +1,16 @@
<td>
<x-port-link :port="$dslPort->port">{{ $dslPort->port->ifIndex }}. {{ $dslPort->port->getLabel() }}</x-port-link>
@if($dslPort->port->ifInErrors_delta > 0 || $dslPort->port->ifOutErrors_delta > 0)
<a href="{{ \LibreNMS\Util\Url::portUrl($dslPort->port, ['graph_type' => 'port_errors']) }}"><i class='fa fa-flag fa-lg tw-text-red-600'></i></a>
@endif
@if($dslPort->port->getLabel() !== $dslPort->port->getDescription())
<br/>{{ $dslPort->port->getDescription() }}
@endif
</td>
<td>
{{ \LibreNMS\Util\Number::formatSi($dslPort->port->ifInOctets_rate * 8, 2, 3, 'bps') }}
<i class='fa fa-arrows-v fa-lg icon-theme' aria-hidden='true'></i>
{{ \LibreNMS\Util\Number::formatSi($dslPort->port->ifOutOctets_rate * 8, 2, 3, 'bps') }}
<br />
<x-graph :port="$dslPort->port" type="port_bits" width="120" height="40" legend="no"></x-graph>
</td>

View File

@ -0,0 +1,41 @@
<x-panel body-class="!tw-p-0">
<table class="table table-hover table-condensed tw-mt-1 !tw-mb-0">
<thead>
<tr>
<th>Local Port</th>
<th>Remote Device</th>
<th>Remote Port</th>
<th>Protocol</th>
</tr>
</thead>
@foreach($data['links'] as $link)
<tr>
<td><x-port-link :port="$link->port"></x-port-link>
@if($link->port->getLabel() !== $link->port->getDescription() )
<br />{{ $link->port->getDescription() }}
@endif
</td>
<td>
@if($link->remoteDevice)
<x-device-link :device="$link->remoteDevice"></x-device-link>
@else
{{ $link->remote_hostname }}
@endif
<br />
{{ $link->remote_platform }}
</td>
<td>
@if($link->remotePort)
<x-port-link :port="$link->remotePort"></x-port-link>
@if($link->remotePort->getLabel() !== $link->remotePort->getDescription() )
<br />{{ $link->remotePort->getDescription() }}
@endif
@else
{{ $link->remote_port }}
@endif
</td>
<td>{{ strtoupper($link->protocol) }}</td>
</tr>
@endforeach
</table>
</x-panel>

View File

@ -0,0 +1,8 @@
@foreach($data['ports'] as $port)
<div class="minigraph-div">
<x-port-link :port="$port">
<div class="tw-font-bold">{{ $port->getShortLabel() }}</div>
<x-graph :port="$port" :type="$data['graph_type']" :from="$request->input('from', '-1d')" width="180" height="55" legend="no"></x-graph>
</x-port-link>
</div>
@endforeach

View File

@ -0,0 +1,66 @@
<x-panel body-class="!tw-p-0">
<table class="table table-condensed table-striped table-hover tw-mt-1 !tw-mb-0">
<thead>
<tr>
<th>{{ __('Port') }}</th>
<th>{{ __('Traffic') }}</th>
<th>{{ __('port.xdsl.sync') }}</th>
<th>{{ __('port.xdsl.attainable') }}</th>
<th>{{ __('port.xdsl.attenuation') }}</th>
<th>{{ __('port.xdsl.snr') }}</th>
<th>{{ __('port.xdsl.power') }}</th>
</tr>
</thead>
@foreach($data['adsl'] as $dslPort)
<tr>
@include('device.tabs.ports.includes.xdsl_base_columns')
<td>
{{ \LibreNMS\Util\Number::formatSi($dslPort->adslAtucChanCurrTxRate, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->adslAturChanCurrTxRate, 2, 3, 'bps') }}
<br />
<x-graph :port="$dslPort->port" type="port_adsl_speed" width="120" height="40" legend="no"></x-graph>
</td>
<td>
{{ \LibreNMS\Util\Number::formatSi($dslPort->adslAtucCurrAttainableRate, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->adslAturCurrAttainableRate, 2, 3, 'bps') }}
<br />
<x-graph :port="$dslPort->port" type="port_adsl_attainable" width="120" height="40" legend="no"></x-graph>
</td>
<td>
{{ $dslPort->adslAtucCurrAtn }}dB/{{ $dslPort->adslAturCurrAtn }}dB
<br />
<x-graph :port="$dslPort->port" type="port_adsl_attenuation" width="120" height="40" legend="no"></x-graph>
</td>
<td>
{{ $dslPort->adslAtucCurrSnrMgn }}dB/{{ $dslPort->adslAturCurrSnrMgn }}dB
<br />
<x-graph :port="$dslPort->port" type="port_adsl_snr" width="120" height="40" legend="no"></x-graph>
</td>
<td>
{{ $dslPort->adslAturCurrOutputPwr }}dBm/{{ $dslPort->adslAtucCurrOutputPwr }}dBm
<br />
<x-graph :port="$dslPort->port" type="port_adsl_power" width="120" height="40" legend="no"></x-graph>
</td>
</tr>
@endforeach
@foreach($data['vdsl'] as $dslPort)
@include('device.tabs.ports.includes.xdsl_base_columns')
<td>
{{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2ChStatusActDataRateXtuc, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2ChStatusActDataRateXtur, 2, 3, 'bps') }}
<br />
<x-graph :port="$dslPort->port" type="port_vdsl_speed" width="120" height="40" legend="no"></x-graph>
</td>
<td>
{{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2LineStatusAttainableRateDs, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2LineStatusAttainableRateUs, 2, 3, 'bps') }}
<br />
<x-graph :port="$dslPort->port" type="port_vdsl_attainable" width="120" height="40" legend="no"></x-graph>
</td>
<td></td>
<td></td>
<td>
{{ $dslPort->xdsl2LineStatusActAtpDs }}dBm/{{ $dslPort->xdsl2LineStatusActAtpUs }}dBm
<br />
<x-graph :port="$dslPort->port" type="port_vdsl_power" width="120" height="40" legend="no"></x-graph>
</td>
@endforeach
</table>
</x-panel>

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=22052024" rel="stylesheet">
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=13062024" 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

@ -0,0 +1,25 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="Pagination Navigation" class="tw-flex tw-justify-between">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<span class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-leading-5 tw-rounded-md dark:tw-text-gray-600 dark:tw-bg-gray-800 dark:tw-border-gray-600">
{!! __('pagination.previous') !!}
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-leading-5 tw-rounded-md hover:tw-text-gray-500 focus:tw-outline-none focus:tw-ring tw-ring-gray-300 focus:tw-border-blue-300 active:tw-bg-gray-100 active:tw-text-gray-700 tw-transition tw-ease-in-out tw-duration-150 dark:tw-bg-gray-800 dark:tw-border-gray-600 dark:tw-text-gray-300 dark:focus:tw-border-blue-700 dark:active:tw-bg-gray-700 dark:active:tw-text-gray-300">
{!! __('pagination.previous') !!}
</a>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next" class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-leading-5 tw-rounded-md hover:tw-text-gray-500 focus:tw-outline-none focus:tw-ring tw-ring-gray-300 focus:tw-border-blue-300 active:tw-bg-gray-100 active:tw-text-gray-700 tw-transition tw-ease-in-out tw-duration-150 dark:tw-bg-gray-800 dark:tw-border-gray-600 dark:tw-text-gray-300 dark:focus:tw-border-blue-700 dark:active:tw-bg-gray-700 dark:active:tw-text-gray-300">
{!! __('pagination.next') !!}
</a>
@else
<span class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-leading-5 tw-rounded-md dark:tw-text-gray-600 dark:tw-bg-gray-800 dark:tw-border-gray-600">
{!! __('pagination.next') !!}
</span>
@endif
</nav>
@endif

View File

@ -0,0 +1,102 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" class="tw-flex tw-items-center tw-justify-between">
<div class="tw-flex tw-justify-between tw-flex-1 sm:tw-hidden">
@if ($paginator->onFirstPage())
<span class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-leading-5 tw-rounded-md dark:tw-text-gray-600 dark:tw-bg-gray-800 dark:tw-border-gray-600">
{!! __('pagination.previous') !!}
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-leading-5 tw-rounded-md hover:tw-text-gray-500 focus:tw-outline-none focus:tw-ring tw-ring-gray-300 focus:tw-border-blue-300 active:tw-bg-gray-100 active:tw-text-gray-700 tw-transition tw-ease-in-out tw-duration-150 dark:tw-bg-gray-800 dark:tw-border-gray-600 dark:tw-text-gray-300 dark:focus:tw-border-blue-700 dark:active:tw-bg-gray-700 dark:active:tw-text-gray-300">
{!! __('pagination.previous') !!}
</a>
@endif
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-ml-3 tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-leading-5 tw-rounded-md hover:tw-text-gray-500 focus:tw-outline-none focus:tw-ring tw-ring-gray-300 focus:tw-border-blue-300 active:tw-bg-gray-100 active:tw-text-gray-700 tw-transition tw-ease-in-out tw-duration-150 dark:tw-bg-gray-800 dark:tw-border-gray-600 dark:tw-text-gray-300 dark:focus:tw-border-blue-700 dark:active:tw-bg-gray-700 dark:active:tw-text-gray-300">
{!! __('pagination.next') !!}
</a>
@else
<span class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-ml-3 tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-leading-5 tw-rounded-md dark:tw-text-gray-600 dark:tw-bg-gray-800 dark:tw-border-gray-600">
{!! __('pagination.next') !!}
</span>
@endif
</div>
<div class="tw-hidden sm:tw-flex-1 sm:tw-flex sm:tw-items-center sm:tw-justify-between">
<div>
<p class="tw-text-sm tw-text-gray-700 tw-leading-5 dark:tw-text-gray-400 tw-mr-2">
@if ($paginator->firstItem())
<span class="tw-font-medium">{{ $paginator->firstItem() }}</span>-<span class="tw-font-medium">{{ $paginator->lastItem() }}</span>
@else
{{ $paginator->count() }}
@endif
{!! __('of') !!}
<span class="tw-font-medium">{{ $paginator->total() }}</span>
</p>
</div>
<div>
<span class="tw-relative tw-z-0 tw-inline-flex rtl:tw-flex-row-reverse tw-shadow-sm tw-rounded-md">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<span aria-disabled="true" aria-label="{{ __('pagination.previous') }}">
<span class="tw-relative tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-rounded-l-md tw-leading-5 dark:tw-bg-gray-800 dark:tw-border-gray-600" aria-hidden="true">
<svg class="tw-w-5 tw-h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="tw-relative tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-rounded-l-md tw-leading-5 hover:tw-text-gray-400 focus:tw-z-10 focus:tw-outline-none focus:tw-ring tw-ring-gray-300 focus:tw-border-blue-300 active:tw-bg-gray-100 active:tw-text-gray-500 tw-transition tw-ease-in-out tw-duration-150 dark:tw-bg-gray-800 dark:tw-border-gray-600 dark:active:tw-bg-gray-700 dark:focus:tw-border-blue-800" aria-label="{{ __('pagination.previous') }}">
<svg class="tw-w-5 tw-h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</a>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<span aria-disabled="true">
<span class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw--ml-px tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-leading-5 dark:tw-bg-gray-800 dark:tw-border-gray-600">{{ $element }}</span>
</span>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<span aria-current="page">
<span class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw--ml-px tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-leading-5 dark:tw-bg-gray-800 dark:tw-border-gray-600">{{ $page }}</span>
</span>
@else
<a href="{{ $url }}" class="tw-relative tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw--ml-px tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-leading-5 hover:tw-text-gray-500 focus:tw-z-10 focus:tw-outline-none focus:tw-ring tw-ring-gray-300 focus:tw-border-blue-300 active:tw-bg-gray-100 active:tw-text-gray-700 tw-transition tw-ease-in-out tw-duration-150 dark:tw-bg-gray-800 dark:tw-border-gray-600 dark:tw-text-gray-400 dark:hover:tw-text-gray-300 dark:active:tw-bg-gray-700 dark:focus:tw-border-blue-800" aria-label="{{ __('Go to page :page', ['page' => $page]) }}">
{{ $page }}
</a>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next" class="tw-relative tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw--ml-px tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-rounded-r-md tw-leading-5 hover:tw-text-gray-400 focus:tw-z-10 focus:tw-outline-none focus:tw-ring tw-ring-gray-300 focus:tw-border-blue-300 active:tw-bg-gray-100 active:tw-text-gray-500 tw-transition tw-ease-in-out tw-duration-150 dark:tw-bg-gray-800 dark:tw-border-gray-600 dark:active:tw-bg-gray-700 dark:focus:tw-border-blue-800" aria-label="{{ __('pagination.next') }}">
<svg class="tw-w-5 tw-h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</a>
@else
<span aria-disabled="true" aria-label="{{ __('pagination.next') }}">
<span class="tw-relative tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw--ml-px tw-text-sm tw-font-medium tw-text-gray-500 tw-bg-white tw-border tw-border-gray-300 tw-cursor-default tw-rounded-r-md tw-leading-5 dark:tw-bg-gray-800 dark:tw-border-gray-600" aria-hidden="true">
<svg class="tw-w-5 tw-h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</span>
</span>
@endif
</span>
</div>
</div>
</nav>
@endif