diff --git a/LibreNMS/Interfaces/UI/DeviceTab.php b/LibreNMS/Interfaces/UI/DeviceTab.php index e448c36e08..b2d6e2c45b 100644 --- a/LibreNMS/Interfaces/UI/DeviceTab.php +++ b/LibreNMS/Interfaces/UI/DeviceTab.php @@ -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; } diff --git a/LibreNMS/Util/Color.php b/LibreNMS/Util/Color.php index 8b801bc60e..401ef4b56d 100644 --- a/LibreNMS/Util/Color.php +++ b/LibreNMS/Util/Color.php @@ -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 */ diff --git a/LibreNMS/Util/Rewrite.php b/LibreNMS/Util/Rewrite.php index 27d1fb64ec..c16df5d42d 100644 --- a/LibreNMS/Util/Rewrite.php +++ b/LibreNMS/Util/Rewrite.php @@ -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 = [ diff --git a/app/Http/Controllers/Device/Tabs/AccessPointsController.php b/app/Http/Controllers/Device/Tabs/AccessPointsController.php index 316bd1db4b..e8d4cf5106 100644 --- a/app/Http/Controllers/Device/Tabs/AccessPointsController.php +++ b/app/Http/Controllers/Device/Tabs/AccessPointsController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/AlertStatsController.php b/app/Http/Controllers/Device/Tabs/AlertStatsController.php index 9e90f2c8bc..9331ad8f59 100644 --- a/app/Http/Controllers/Device/Tabs/AlertStatsController.php +++ b/app/Http/Controllers/Device/Tabs/AlertStatsController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/AlertsController.php b/app/Http/Controllers/Device/Tabs/AlertsController.php index 4cd0125d21..286eb8b68e 100644 --- a/app/Http/Controllers/Device/Tabs/AlertsController.php +++ b/app/Http/Controllers/Device/Tabs/AlertsController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/AppsController.php b/app/Http/Controllers/Device/Tabs/AppsController.php index bdde17dfc4..ab0815e40d 100644 --- a/app/Http/Controllers/Device/Tabs/AppsController.php +++ b/app/Http/Controllers/Device/Tabs/AppsController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/CaptureController.php b/app/Http/Controllers/Device/Tabs/CaptureController.php index f2ba93a61a..0498e6bfe7 100644 --- a/app/Http/Controllers/Device/Tabs/CaptureController.php +++ b/app/Http/Controllers/Device/Tabs/CaptureController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/CollectdController.php b/app/Http/Controllers/Device/Tabs/CollectdController.php index dc907a8893..d4591bbc71 100644 --- a/app/Http/Controllers/Device/Tabs/CollectdController.php +++ b/app/Http/Controllers/Device/Tabs/CollectdController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/EditController.php b/app/Http/Controllers/Device/Tabs/EditController.php index bf137f3460..9efa0fe888 100644 --- a/app/Http/Controllers/Device/Tabs/EditController.php +++ b/app/Http/Controllers/Device/Tabs/EditController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/GraphsController.php b/app/Http/Controllers/Device/Tabs/GraphsController.php index d2dad30505..81e4d74dd3 100644 --- a/app/Http/Controllers/Device/Tabs/GraphsController.php +++ b/app/Http/Controllers/Device/Tabs/GraphsController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/HealthController.php b/app/Http/Controllers/Device/Tabs/HealthController.php index 24388ea24e..6de6dfeb4e 100644 --- a/app/Http/Controllers/Device/Tabs/HealthController.php +++ b/app/Http/Controllers/Device/Tabs/HealthController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/InventoryController.php b/app/Http/Controllers/Device/Tabs/InventoryController.php index 15adf9a3f4..c4d5267945 100644 --- a/app/Http/Controllers/Device/Tabs/InventoryController.php +++ b/app/Http/Controllers/Device/Tabs/InventoryController.php @@ -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 diff --git a/app/Http/Controllers/Device/Tabs/LatencyController.php b/app/Http/Controllers/Device/Tabs/LatencyController.php index f94bd9fd63..98f7e51876 100644 --- a/app/Http/Controllers/Device/Tabs/LatencyController.php +++ b/app/Http/Controllers/Device/Tabs/LatencyController.php @@ -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 = []; diff --git a/app/Http/Controllers/Device/Tabs/LoadBalancerController.php b/app/Http/Controllers/Device/Tabs/LoadBalancerController.php index c4377cda28..d050b91748 100644 --- a/app/Http/Controllers/Device/Tabs/LoadBalancerController.php +++ b/app/Http/Controllers/Device/Tabs/LoadBalancerController.php @@ -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, diff --git a/app/Http/Controllers/Device/Tabs/LogsController.php b/app/Http/Controllers/Device/Tabs/LogsController.php index d884d84a3a..63b62874b1 100644 --- a/app/Http/Controllers/Device/Tabs/LogsController.php +++ b/app/Http/Controllers/Device/Tabs/LogsController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/MefController.php b/app/Http/Controllers/Device/Tabs/MefController.php index eb6d2b1abf..34aefbe88b 100644 --- a/app/Http/Controllers/Device/Tabs/MefController.php +++ b/app/Http/Controllers/Device/Tabs/MefController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/MuninController.php b/app/Http/Controllers/Device/Tabs/MuninController.php index e02f1cf51f..4ffed97238 100644 --- a/app/Http/Controllers/Device/Tabs/MuninController.php +++ b/app/Http/Controllers/Device/Tabs/MuninController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/NacController.php b/app/Http/Controllers/Device/Tabs/NacController.php index 97920fe44a..f1618f1a70 100644 --- a/app/Http/Controllers/Device/Tabs/NacController.php +++ b/app/Http/Controllers/Device/Tabs/NacController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/NeighboursController.php b/app/Http/Controllers/Device/Tabs/NeighboursController.php index 371137fe0c..f35f57e9a8 100644 --- a/app/Http/Controllers/Device/Tabs/NeighboursController.php +++ b/app/Http/Controllers/Device/Tabs/NeighboursController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/NetflowController.php b/app/Http/Controllers/Device/Tabs/NetflowController.php index 731ab6e6bc..016fcef1fc 100644 --- a/app/Http/Controllers/Device/Tabs/NetflowController.php +++ b/app/Http/Controllers/Device/Tabs/NetflowController.php @@ -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', diff --git a/app/Http/Controllers/Device/Tabs/NotesController.php b/app/Http/Controllers/Device/Tabs/NotesController.php index 1e672bfba1..34e39424f7 100644 --- a/app/Http/Controllers/Device/Tabs/NotesController.php +++ b/app/Http/Controllers/Device/Tabs/NotesController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/OverviewController.php b/app/Http/Controllers/Device/Tabs/OverviewController.php index 8eecf3e9b8..69a6f1975d 100644 --- a/app/Http/Controllers/Device/Tabs/OverviewController.php +++ b/app/Http/Controllers/Device/Tabs/OverviewController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/PackagesController.php b/app/Http/Controllers/Device/Tabs/PackagesController.php index 9cdb38a905..0e906e0f9c 100644 --- a/app/Http/Controllers/Device/Tabs/PackagesController.php +++ b/app/Http/Controllers/Device/Tabs/PackagesController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/PortController.php b/app/Http/Controllers/Device/Tabs/PortController.php index de8eba35a1..c793ecc711 100644 --- a/app/Http/Controllers/Device/Tabs/PortController.php +++ b/app/Http/Controllers/Device/Tabs/PortController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/PortsController.php b/app/Http/Controllers/Device/Tabs/PortsController.php index 20b68ea975..fdd45e6bf8 100644 --- a/app/Http/Controllers/Device/Tabs/PortsController.php +++ b/app/Http/Controllers/Device/Tabs/PortsController.php @@ -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 $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, + ], + ]; } } diff --git a/app/Http/Controllers/Device/Tabs/PrinterController.php b/app/Http/Controllers/Device/Tabs/PrinterController.php index 4d52ae487d..17643ea1c8 100644 --- a/app/Http/Controllers/Device/Tabs/PrinterController.php +++ b/app/Http/Controllers/Device/Tabs/PrinterController.php @@ -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', diff --git a/app/Http/Controllers/Device/Tabs/ProcessesController.php b/app/Http/Controllers/Device/Tabs/ProcessesController.php index 9a4f082835..2f968385ee 100644 --- a/app/Http/Controllers/Device/Tabs/ProcessesController.php +++ b/app/Http/Controllers/Device/Tabs/ProcessesController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/PseudowiresController.php b/app/Http/Controllers/Device/Tabs/PseudowiresController.php index 9d40169944..9aa2fa5f2c 100644 --- a/app/Http/Controllers/Device/Tabs/PseudowiresController.php +++ b/app/Http/Controllers/Device/Tabs/PseudowiresController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/RoutingController.php b/app/Http/Controllers/Device/Tabs/RoutingController.php index f318f63a6c..b5aeaca234 100644 --- a/app/Http/Controllers/Device/Tabs/RoutingController.php +++ b/app/Http/Controllers/Device/Tabs/RoutingController.php @@ -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), diff --git a/app/Http/Controllers/Device/Tabs/ServicesController.php b/app/Http/Controllers/Device/Tabs/ServicesController.php index bdfa1a7625..4b0867c14b 100644 --- a/app/Http/Controllers/Device/Tabs/ServicesController.php +++ b/app/Http/Controllers/Device/Tabs/ServicesController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/ShowConfigController.php b/app/Http/Controllers/Device/Tabs/ShowConfigController.php index a724dda733..8a981c2ccb 100644 --- a/app/Http/Controllers/Device/Tabs/ShowConfigController.php +++ b/app/Http/Controllers/Device/Tabs/ShowConfigController.php @@ -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(), diff --git a/app/Http/Controllers/Device/Tabs/SlasController.php b/app/Http/Controllers/Device/Tabs/SlasController.php index 46b5448905..5394d99be0 100644 --- a/app/Http/Controllers/Device/Tabs/SlasController.php +++ b/app/Http/Controllers/Device/Tabs/SlasController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/StpController.php b/app/Http/Controllers/Device/Tabs/StpController.php index 37d10cd81c..a0e6550b55 100644 --- a/app/Http/Controllers/Device/Tabs/StpController.php +++ b/app/Http/Controllers/Device/Tabs/StpController.php @@ -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; diff --git a/app/Http/Controllers/Device/Tabs/TnmsneController.php b/app/Http/Controllers/Device/Tabs/TnmsneController.php index 6da7579abd..1962631556 100644 --- a/app/Http/Controllers/Device/Tabs/TnmsneController.php +++ b/app/Http/Controllers/Device/Tabs/TnmsneController.php @@ -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 []; } diff --git a/app/Http/Controllers/Device/Tabs/VlansController.php b/app/Http/Controllers/Device/Tabs/VlansController.php index 20c6fcfde7..61d1114d4e 100644 --- a/app/Http/Controllers/Device/Tabs/VlansController.php +++ b/app/Http/Controllers/Device/Tabs/VlansController.php @@ -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), diff --git a/app/Http/Controllers/Device/Tabs/VmInfoController.php b/app/Http/Controllers/Device/Tabs/VmInfoController.php index 3bfb8ec833..ec002c3812 100644 --- a/app/Http/Controllers/Device/Tabs/VmInfoController.php +++ b/app/Http/Controllers/Device/Tabs/VmInfoController.php @@ -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), diff --git a/app/Http/Controllers/Device/Tabs/WirelessController.php b/app/Http/Controllers/Device/Tabs/WirelessController.php index d27d2476ab..a6ac9fff27 100644 --- a/app/Http/Controllers/Device/Tabs/WirelessController.php +++ b/app/Http/Controllers/Device/Tabs/WirelessController.php @@ -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 []; } diff --git a/app/Http/Controllers/DeviceController.php b/app/Http/Controllers/DeviceController.php index b43c70602a..b7b61cf724 100644 --- a/app/Http/Controllers/DeviceController.php +++ b/app/Http/Controllers/DeviceController.php @@ -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); diff --git a/app/Models/Ipv4Address.php b/app/Models/Ipv4Address.php index 4253a56542..2127638106 100644 --- a/app/Models/Ipv4Address.php +++ b/app/Models/Ipv4Address.php @@ -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'); + } } diff --git a/app/Models/Ipv4Network.php b/app/Models/Ipv4Network.php index 0ef6999fbe..477c132b07 100644 --- a/app/Models/Ipv4Network.php +++ b/app/Models/Ipv4Network.php @@ -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'); + } } diff --git a/app/Models/Ipv6Address.php b/app/Models/Ipv6Address.php index 8333dc59bf..520f70e42e 100644 --- a/app/Models/Ipv6Address.php +++ b/app/Models/Ipv6Address.php @@ -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'); + } } diff --git a/app/Models/Ipv6Network.php b/app/Models/Ipv6Network.php index 83e211f402..e0a3a6fea0 100644 --- a/app/Models/Ipv6Network.php +++ b/app/Models/Ipv6Network.php @@ -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'); + } } diff --git a/app/Models/Port.php b/app/Models/Port.php index eb0dbcb036..5bf452b3e5 100644 --- a/app/Models/Port.php +++ b/app/Models/Port.php @@ -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'); diff --git a/app/Models/Pseudowire.php b/app/Models/Pseudowire.php index bbe19da7ad..bf682b90d5 100644 --- a/app/Models/Pseudowire.php +++ b/app/Models/Pseudowire.php @@ -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'); + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f08a20cb05..8f8919f9ae 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -50,8 +50,6 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { - \Illuminate\Pagination\Paginator::useBootstrap(); - $this->bootCustomBladeDirectives(); $this->bootCustomValidators(); $this->configureMorphAliases(); diff --git a/app/View/Components/PortLink.php b/app/View/Components/PortLink.php index a25ffcb288..a9b161b03e 100644 --- a/app/View/Components/PortLink.php +++ b/app/View/Components/PortLink.php @@ -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 diff --git a/app/View/Components/Submenu.php b/app/View/Components/Submenu.php index 61a59f753b..f99cae9295 100644 --- a/app/View/Components/Submenu.php +++ b/app/View/Components/Submenu.php @@ -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; } diff --git a/html/css/app.css b/html/css/app.css index d39581aa0e..f03062fb9b 100644 --- a/html/css/app.css +++ b/html/css/app.css @@ -1,2 +1,2 @@ #toast-container-top-right{position:fixed;right:12px;top:55px;z-index:999999}#toast-container-top-right a{font-weight:700}#toast-container-top-right>div{background-position:10px;background-repeat:no-repeat;min-height:50px;width:304px}.toast-error{background-image:url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzIgMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTUiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTYgMEExNiAxNiAwIDAgMCAwIDE2YTE2IDE2IDAgMCAwIDE2IDE2IDE2IDE2IDAgMCAwIDE2LTE2QTE2IDE2IDAgMCAwIDE2IDB6bS02IDlhMSAxIDAgMCAxIC43MDcuMjkzTDE2IDE0LjU4Nmw1LjI5My01LjI5M2ExIDEgMCAwIDEgMS40MTQgMCAxIDEgMCAwIDEgMCAxLjQxNEwxNy40MTQgMTZsNS4yOTMgNS4yOTNhMSAxIDAgMCAxIDAgMS40MTQgMSAxIDAgMCAxLTEuNDE0IDBMMTYgMTcuNDE0bC01LjI5MyA1LjI5M2ExIDEgMCAwIDEtMS40MTQgMCAxIDEgMCAwIDEgMC0xLjQxNEwxNC41ODYgMTZsLTUuMjkzLTUuMjkzYTEgMSAwIDAgMSAwLTEuNDE0QTEgMSAwIDAgMSAxMCA5eiIgZmlsbD0iI2RjMjYyNiIvPjwvc3ZnPg==");background-size:32px}.toast-info{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTUiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTYgMEExNiAxNiAwIDAgMCAwIDE2YTE2IDE2IDAgMCAwIDE2IDE2IDE2IDE2IDAgMCAwIDE2LTE2QTE2IDE2IDAgMCAwIDE2IDB6bTAgNmM1LjUxMSAwIDEwIDQuNDg5IDEwIDEwcy00LjQ4OSAxMC0xMCAxMFM2IDIxLjUxMSA2IDE2IDEwLjQ4OSA2IDE2IDZ6bTAgMmMtNC40MyAwLTggMy41Ny04IDhzMy41NyA4IDggOCA4LTMuNTcgOC04LTMuNTctOC04LTh6bTAgM2ExIDEgMCAwIDEgMSAxdjRhMSAxIDAgMCAxLTEgMSAxIDEgMCAwIDEtMS0xdi00YTEgMSAwIDAgMSAxLTF6bTAgOGguMDFhMSAxIDAgMCAxIDEgMSAxIDEgMCAwIDEtMSAxSDE2YTEgMSAwIDAgMS0xLTEgMSAxIDAgMCAxIDEtMXoiIGZpbGw9IiMyNTYzZWIiLz48L3N2Zz4=");background-size:32px}.toast-success{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTUiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTYgMEExNiAxNiAwIDAgMCAwIDE2YTE2IDE2IDAgMCAwIDE2IDE2IDE2IDE2IDAgMCAwIDE2LTE2QTE2IDE2IDAgMCAwIDE2IDB6bTcgMTBhMSAxIDAgMCAxIC43MDcuMjkzIDEgMSAwIDAgMSAwIDEuNDE0bC0xMCAxMGExIDEgMCAwIDEtMS40MTQgMGwtNC00YTEgMSAwIDAgMSAwLTEuNDE0IDEgMSAwIDAgMSAxLjQxNCAwTDEzIDE5LjU4Nmw5LjI5My05LjI5M0ExIDEgMCAwIDEgMjMgMTB6IiBmaWxsPSIjMDU5NjY5Ii8+PC9zdmc+");background-size:32px}.toast-warning{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTUiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTYgMEExNiAxNiAwIDAgMCAwIDE2YTE2IDE2IDAgMCAwIDE2IDE2IDE2IDE2IDAgMCAwIDE2LTE2QTE2IDE2IDAgMCAwIDE2IDB6bTAgNi4xNTZjMS4wMTYgMCAyLjAzMi40OSAyLjU5OCAxLjQ2OWw2LjkyNyAxMmMxLjEzMSAxLjk1OC0uMzM2IDQuNS0yLjU5NyA0LjVIOS4wNzJjLTIuMjYxIDAtMy43MjgtMi41NDItMi41OTctNC41bDYuOTI3LTEyYy41NjYtLjk3OSAxLjU4Mi0xLjQ2OSAyLjU5OC0xLjQ2OXptMCAxLjkzOGMtLjMzIDAtLjY2LjE3Ny0uODY1LjUzMWwtNi45MyAxMmMtLjQwOS43MDguMDQ5IDEuNS44NjcgMS41aDEzLjg1NmMuODE4IDAgMS4yNzYtLjc5Mi44NjctMS41bC02LjkzLTEyYy0uMjA0LS4zNTQtLjUzNC0uNTMxLS44NjUtLjUzMXptMCA0LjAzMWExIDEgMCAwIDEgMSAxdjJhMSAxIDAgMCAxLTEgMSAxIDEgMCAwIDEtMS0xdi0yYTEgMSAwIDAgMSAxLTF6bTAgNmguMDFhMSAxIDAgMCAxIDEgMSAxIDEgMCAwIDEtMSAxSDE2YTEgMSAwIDAgMS0xLTEgMSAxIDAgMCAxIDEtMXoiIGZpbGw9IiNkOTc3MDYiLz48L3N2Zz4=");background-size:32px}.toast-progress{background-color:#000;bottom:0;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);height:4px;left:0;opacity:.4;position:absolute} -/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.tw-absolute{position:absolute}.tw-relative{position:relative}.tw-bottom-0{bottom:0}.tw-left-0{left:0}.tw-z-0{z-index:0}.tw-z-50{z-index:50}.tw-float-right{float:right}.tw-m-3{margin:.75rem}.tw-mx-10{margin-left:2.5rem;margin-right:2.5rem}.tw-mx-4{margin-left:1rem;margin-right:1rem}.tw-mx-auto{margin-left:auto;margin-right:auto}.\!tw-mb-0{margin-bottom:0!important}.-tw-mb-px{margin-bottom:-1px}.tw--ml-px{margin-left:-1px}.tw-mb-0{margin-bottom:0}.tw-mb-1{margin-bottom:.25rem}.tw-mb-2{margin-bottom:.5rem}.tw-me-2{margin-inline-end:.5rem}.tw-ml-2{margin-left:.5rem}.tw-ml-3{margin-left:.75rem}.tw-ml-auto{margin-left:auto}.tw-mr-0{margin-right:0}.tw-mr-0\.5{margin-right:.125rem}.tw-mr-1{margin-right:.25rem}.tw-mr-2{margin-right:.5rem}.tw-mr-3{margin-right:.75rem}.tw-mt-1{margin-top:.25rem}.tw-mt-10{margin-top:2.5rem}.tw-mt-2{margin-top:.5rem}.tw-mt-5{margin-top:1.25rem}.tw-block{display:block}.tw-inline-block{display:inline-block}.tw-flex{display:flex}.tw-inline-flex{display:inline-flex}.tw-grid{display:grid}.tw-hidden{display:none}.tw-h-1{height:.25rem}.tw-h-24{height:6rem}.tw-h-5{height:1.25rem}.\!tw-w-auto{width:auto!important}.tw-w-48{width:12rem}.tw-w-5{width:1.25rem}.tw-w-full{width:100%}.tw-max-w-screen-lg{max-width:992px}.tw-flex-1{flex:1 1 0%}.tw-flex-grow{flex-grow:1}.tw-cursor-crosshair{cursor:crosshair}.tw-cursor-default{cursor:default}.tw-cursor-pointer{cursor:pointer}.tw-list-none{list-style-type:none}.tw-flex-row-reverse{flex-direction:row-reverse}.tw-flex-col{flex-direction:column}.tw-flex-wrap{flex-wrap:wrap}.tw-place-items-center{place-items:center}.tw-items-center{align-items:center}.tw-items-baseline{align-items:baseline}.tw-justify-between{justify-content:space-between}.tw-overflow-hidden{overflow:hidden}.tw-overflow-y-hidden{overflow-y:hidden}.tw-whitespace-nowrap{white-space:nowrap}.tw-text-nowrap{text-wrap:nowrap}.tw-rounded{border-radius:.25rem}.tw-rounded-lg{border-radius:.5rem}.tw-rounded-md{border-radius:.375rem}.tw-rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.tw-rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.tw-rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.tw-border{border-width:1px}.tw-border-2{border-width:2px}.tw-border-b{border-bottom-width:1px}.tw-border-b-0{border-bottom-width:0}.tw-border-b-0\.5{border-bottom-width:.5px}.tw-border-b-2{border-bottom-width:2px}.tw-border-l-8{border-left-width:8px}.tw-border-r-0{border-right-width:0}.tw-border-r-0\.5{border-right-width:.5px}.tw-border-t-0{border-top-width:0}.tw-border-t-0\.5{border-top-width:.5px}.tw-border-solid{border-style:solid}.tw-border-blue-600{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity))}.tw-border-current{border-color:currentColor}.tw-border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.tw-border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.tw-border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.tw-border-green-600{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity))}.tw-border-red-600{--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity))}.tw-border-transparent{border-color:transparent}.tw-border-yellow-600{--tw-border-opacity:1;border-color:rgb(202 138 4/var(--tw-border-opacity))}.tw-bg-current{background-color:currentColor}.tw-bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.tw-bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.\!tw-p-0{padding:0!important}.tw-p-1{padding:.25rem}.tw-p-10{padding:2.5rem}.tw-p-2{padding:.5rem}.tw-p-2\.5{padding:.625rem}.tw-p-3{padding:.75rem}.tw-px-2{padding-left:.5rem;padding-right:.5rem}.tw-px-3{padding-left:.75rem;padding-right:.75rem}.tw-px-4{padding-left:1rem;padding-right:1rem}.tw-py-2{padding-bottom:.5rem;padding-top:.5rem}.tw-py-4{padding-bottom:1rem;padding-top:1rem}.\!tw-pb-0{padding-bottom:0!important}.tw-pl-2{padding-left:.5rem}.tw-pl-20{padding-left:5rem}.tw-pr-1{padding-right:.25rem}.tw-pr-2{padding-right:.5rem}.tw-text-left{text-align:left}.tw-text-center{text-align:center}.tw-text-2xl{font-size:1.5rem;line-height:2rem}.tw-text-3xl{font-size:1.875rem;line-height:2.25rem}.tw-text-base{font-size:1rem;line-height:1.5rem}.tw-text-sm{font-size:.875rem;line-height:1.25rem}.tw-text-xl{font-size:1.25rem;line-height:1.75rem}.tw-font-bold{font-weight:700}.tw-font-medium{font-weight:500}.tw-font-normal{font-weight:400}.tw-font-semibold{font-weight:600}.tw-capitalize{text-transform:capitalize}.tw-leading-5{line-height:1.25rem}.tw-leading-6{line-height:1.5rem}.tw-leading-7{line-height:1.75rem}.tw-leading-normal{line-height:1.5}.tw-text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity))}.tw-text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.tw-text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity))}.tw-text-blue-900{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity))}.tw-text-emerald-600{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity))}.tw-text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.tw-text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.tw-text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.tw-text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.tw-text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.tw-text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.tw-text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.tw-text-yellow-600{--tw-text-opacity:1;color:rgb(202 138 4/var(--tw-text-opacity))}.tw-no-underline{text-decoration-line:none}.tw-opacity-0{opacity:0}.tw-opacity-100{opacity:1}.tw-opacity-80{opacity:.8}.tw-opacity-90{opacity:.9}.tw-shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.tw-shadow-lg,.tw-shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.tw-shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.tw-ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.tw-transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.tw-duration-150,.tw-transition{transition-duration:.15s}.tw-duration-700{transition-duration:.7s}.tw-ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.tw-ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.even\:tw-bg-gray-50:nth-child(2n){--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.visited\:tw-text-blue-900:visited{color:#1e3a8a}.visited\:tw-text-gray-400:visited{color:#9ca3af}.visited\:tw-text-red-600:visited{color:#dc2626}.hover\:tw-border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.hover\:tw-bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:tw-bg-gray-500:hover{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.hover\:tw-text-gray-100:hover{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.hover\:tw-text-gray-400:hover{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.hover\:tw-text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:tw-text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.hover\:tw-opacity-100:hover{opacity:1}.hover\:tw-shadow-xl:hover{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:tw-z-10:focus{z-index:10}.focus\:tw-border-blue-300:focus{--tw-border-opacity:1;border-color:rgb(147 197 253/var(--tw-border-opacity))}.focus\:tw-border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity))}.focus\:tw-outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:tw-ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:tw-ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity))}.active\:tw-bg-gray-100:active{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.active\:tw-text-gray-500:active{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.active\:tw-text-gray-700:active{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.dark\:tw-border-blue-500:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity))}.dark\:tw-border-dark-gray-200:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(62 68 76/var(--tw-border-opacity))}.dark\:tw-border-gray-600:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.dark\:tw-bg-dark-gray-300:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(53 58 65/var(--tw-bg-opacity))}.dark\:tw-bg-gray-700:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark\:tw-bg-gray-800:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark\:tw-text-blue-500:is(.tw-dark *){--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.dark\:tw-text-dark-white-100:is(.tw-dark *){--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}.dark\:tw-text-gray-300:is(.tw-dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark\:tw-text-gray-400:is(.tw-dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark\:tw-text-gray-600:is(.tw-dark *){--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.dark\:tw-text-white:is(.tw-dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.dark\:tw-placeholder-gray-400:is(.tw-dark *)::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}.dark\:tw-placeholder-gray-400:is(.tw-dark *)::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}.dark\:even\:tw-bg-zinc-900:nth-child(2n):is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity))}.dark\:visited\:tw-text-dark-white-100:visited:is(.tw-dark *){color:#f9fafb}.dark\:hover\:tw-bg-gray-600:hover:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.dark\:hover\:tw-text-gray-300:hover:is(.tw-dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark\:focus\:tw-border-blue-500:focus:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity))}.dark\:focus\:tw-border-blue-700:focus:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(29 78 216/var(--tw-border-opacity))}.dark\:focus\:tw-border-blue-800:focus:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(30 64 175/var(--tw-border-opacity))}.dark\:active\:tw-bg-gray-700:active:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark\:active\:tw-text-gray-300:active:is(.tw-dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}@media (min-width:576px){.sm\:tw-inline{display:inline}.sm\:tw-flex{display:flex}.sm\:tw-hidden{display:none}.sm\:tw-w-1\/2{width:50%}.sm\:tw-flex-1{flex:1 1 0%}.sm\:tw-items-center{align-items:center}.sm\:tw-justify-between{justify-content:space-between}.sm\:tw-px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:992px){.lg\:tw-w-1\/4{width:25%}}.rtl\:tw-flex-row-reverse:where([dir=rtl],[dir=rtl] *){flex-direction:row-reverse} +/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.tw-absolute{position:absolute}.tw-relative{position:relative}.tw-bottom-0{bottom:0}.tw-left-0{left:0}.tw-z-0{z-index:0}.tw-z-50{z-index:50}.tw-float-right{float:right}.tw-m-3{margin:.75rem}.tw-mx-10{margin-left:2.5rem;margin-right:2.5rem}.tw-mx-4{margin-left:1rem;margin-right:1rem}.tw-mx-auto{margin-left:auto;margin-right:auto}.\!tw-mb-0{margin-bottom:0!important}.-tw-mb-px{margin-bottom:-1px}.tw--ml-px{margin-left:-1px}.tw-mb-0{margin-bottom:0}.tw-mb-1{margin-bottom:.25rem}.tw-mb-2{margin-bottom:.5rem}.tw-me-2{margin-inline-end:.5rem}.tw-ml-2{margin-left:.5rem}.tw-ml-3{margin-left:.75rem}.tw-ml-4{margin-left:1rem}.tw-ml-auto{margin-left:auto}.tw-mr-0{margin-right:0}.tw-mr-0\.5{margin-right:.125rem}.tw-mr-1{margin-right:.25rem}.tw-mr-2{margin-right:.5rem}.tw-mr-3{margin-right:.75rem}.tw-mt-1{margin-top:.25rem}.tw-mt-10{margin-top:2.5rem}.tw-mt-2{margin-top:.5rem}.tw-mt-5{margin-top:1.25rem}.tw-block{display:block}.tw-inline-block{display:inline-block}.tw-flex{display:flex}.tw-inline-flex{display:inline-flex}.tw-grid{display:grid}.tw-hidden{display:none}.tw-h-1{height:.25rem}.tw-h-24{height:6rem}.tw-h-5{height:1.25rem}.\!tw-w-auto{width:auto!important}.tw-w-48{width:12rem}.tw-w-5{width:1.25rem}.tw-w-full{width:100%}.tw-max-w-screen-lg{max-width:992px}.tw-flex-1{flex:1 1 0%}.tw-flex-grow{flex-grow:1}.tw-cursor-crosshair{cursor:crosshair}.tw-cursor-default{cursor:default}.tw-cursor-pointer{cursor:pointer}.tw-list-none{list-style-type:none}.tw-flex-row-reverse{flex-direction:row-reverse}.tw-flex-col{flex-direction:column}.tw-flex-wrap{flex-wrap:wrap}.tw-place-items-center{place-items:center}.tw-items-center{align-items:center}.tw-items-baseline{align-items:baseline}.tw-justify-between{justify-content:space-between}.tw-overflow-y-hidden{overflow-y:hidden}.tw-whitespace-nowrap{white-space:nowrap}.tw-text-nowrap{text-wrap:nowrap}.tw-rounded{border-radius:.25rem}.tw-rounded-lg{border-radius:.5rem}.tw-rounded-md{border-radius:.375rem}.tw-rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.tw-rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.tw-rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.tw-border{border-width:1px}.tw-border-2{border-width:2px}.tw-border-b{border-bottom-width:1px}.tw-border-b-0{border-bottom-width:0}.tw-border-b-0\.5{border-bottom-width:.5px}.tw-border-b-2{border-bottom-width:2px}.tw-border-l-8{border-left-width:8px}.tw-border-r-0{border-right-width:0}.tw-border-r-0\.5{border-right-width:.5px}.tw-border-t-0{border-top-width:0}.tw-border-t-0\.5{border-top-width:.5px}.tw-border-solid{border-style:solid}.tw-border-blue-600{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity))}.tw-border-current{border-color:currentColor}.tw-border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.tw-border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.tw-border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.tw-border-green-600{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity))}.tw-border-red-600{--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity))}.tw-border-transparent{border-color:transparent}.tw-border-yellow-600{--tw-border-opacity:1;border-color:rgb(202 138 4/var(--tw-border-opacity))}.tw-bg-current{background-color:currentColor}.tw-bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.tw-bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.\!tw-p-0{padding:0!important}.tw-p-1{padding:.25rem}.tw-p-10{padding:2.5rem}.tw-p-2{padding:.5rem}.tw-p-2\.5{padding:.625rem}.tw-p-3{padding:.75rem}.tw-px-2{padding-left:.5rem;padding-right:.5rem}.tw-px-3{padding-left:.75rem;padding-right:.75rem}.tw-px-4{padding-left:1rem;padding-right:1rem}.tw-py-2{padding-bottom:.5rem;padding-top:.5rem}.tw-py-4{padding-bottom:1rem;padding-top:1rem}.\!tw-pb-0{padding-bottom:0!important}.tw-pl-2{padding-left:.5rem}.tw-pl-20{padding-left:5rem}.tw-pr-1{padding-right:.25rem}.tw-pr-2{padding-right:.5rem}.tw-text-left{text-align:left}.tw-text-center{text-align:center}.tw-text-2xl{font-size:1.5rem;line-height:2rem}.tw-text-3xl{font-size:1.875rem;line-height:2.25rem}.tw-text-base{font-size:1rem;line-height:1.5rem}.tw-text-sm{font-size:.875rem;line-height:1.25rem}.tw-text-xl{font-size:1.25rem;line-height:1.75rem}.tw-font-bold{font-weight:700}.tw-font-medium{font-weight:500}.tw-font-normal{font-weight:400}.tw-font-semibold{font-weight:600}.tw-capitalize{text-transform:capitalize}.tw-leading-5{line-height:1.25rem}.tw-leading-6{line-height:1.5rem}.tw-leading-7{line-height:1.75rem}.tw-leading-normal{line-height:1.5}.tw-text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.tw-text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity))}.tw-text-blue-900{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity))}.tw-text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.tw-text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.tw-text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.tw-text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.tw-text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.tw-text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.tw-text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.tw-text-yellow-600{--tw-text-opacity:1;color:rgb(202 138 4/var(--tw-text-opacity))}.tw-no-underline{text-decoration-line:none}.tw-opacity-0{opacity:0}.tw-opacity-100{opacity:1}.tw-opacity-80{opacity:.8}.tw-opacity-90{opacity:.9}.tw-shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.tw-shadow-lg,.tw-shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.tw-shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.tw-ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.tw-transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.tw-duration-150,.tw-transition{transition-duration:.15s}.tw-duration-700{transition-duration:.7s}.tw-ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.tw-ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.even\:tw-bg-gray-50:nth-child(2n){--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.visited\:tw-text-blue-900:visited{color:#1e3a8a}.visited\:tw-text-gray-400:visited{color:#9ca3af}.visited\:tw-text-red-600:visited{color:#dc2626}.hover\:tw-border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.hover\:tw-bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:tw-bg-gray-500:hover{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.hover\:tw-text-gray-100:hover{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.hover\:tw-text-gray-400:hover{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.hover\:tw-text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:tw-text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.hover\:tw-opacity-100:hover{opacity:1}.hover\:tw-shadow-xl:hover{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:tw-z-10:focus{z-index:10}.focus\:tw-border-blue-300:focus{--tw-border-opacity:1;border-color:rgb(147 197 253/var(--tw-border-opacity))}.focus\:tw-border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity))}.focus\:tw-outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:tw-ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:tw-ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity))}.active\:tw-bg-gray-100:active{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.active\:tw-text-gray-500:active{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.active\:tw-text-gray-700:active{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.dark\:tw-border-blue-500:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity))}.dark\:tw-border-dark-gray-200:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(62 68 76/var(--tw-border-opacity))}.dark\:tw-border-gray-600:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.dark\:tw-bg-dark-gray-300:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(53 58 65/var(--tw-bg-opacity))}.dark\:tw-bg-gray-700:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark\:tw-bg-gray-800:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark\:tw-text-blue-500:is(.tw-dark *){--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.dark\:tw-text-dark-white-100:is(.tw-dark *){--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}.dark\:tw-text-gray-300:is(.tw-dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark\:tw-text-gray-400:is(.tw-dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark\:tw-text-gray-600:is(.tw-dark *){--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.dark\:tw-text-white:is(.tw-dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.dark\:tw-placeholder-gray-400:is(.tw-dark *)::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}.dark\:tw-placeholder-gray-400:is(.tw-dark *)::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}.dark\:even\:tw-bg-zinc-900:nth-child(2n):is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity))}.dark\:visited\:tw-text-dark-white-100:visited:is(.tw-dark *){color:#f9fafb}.dark\:hover\:tw-bg-gray-600:hover:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.dark\:hover\:tw-text-gray-300:hover:is(.tw-dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark\:focus\:tw-border-blue-500:focus:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity))}.dark\:focus\:tw-border-blue-700:focus:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(29 78 216/var(--tw-border-opacity))}.dark\:focus\:tw-border-blue-800:focus:is(.tw-dark *){--tw-border-opacity:1;border-color:rgb(30 64 175/var(--tw-border-opacity))}.dark\:active\:tw-bg-gray-700:active:is(.tw-dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark\:active\:tw-text-gray-300:active:is(.tw-dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}@media (min-width:576px){.sm\:tw-inline{display:inline}.sm\:tw-flex{display:flex}.sm\:tw-table-cell{display:table-cell}.sm\:tw-hidden{display:none}.sm\:tw-w-1\/2{width:50%}.sm\:tw-flex-1{flex:1 1 0%}.sm\:tw-items-center{align-items:center}.sm\:tw-justify-between{justify-content:space-between}.sm\:tw-px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:768px){.md\:tw-table-cell{display:table-cell}}@media (min-width:992px){.lg\:tw-w-1\/4{width:25%}}.rtl\:tw-flex-row-reverse:where([dir=rtl],[dir=rtl] *){flex-direction:row-reverse} diff --git a/html/css/styles.css b/html/css/styles.css index ba719b1889..6a16a585f5 100644 --- a/html/css/styles.css +++ b/html/css/styles.css @@ -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; } diff --git a/html/mix-manifest.json b/html/mix-manifest.json index e893fa4901..b7a9869db9 100644 --- a/html/mix-manifest.json +++ b/html/mix-manifest.json @@ -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", diff --git a/includes/functions.php b/includes/functions.php index 0b880ea44e..0aeb9265d6 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -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); } /** diff --git a/includes/html/pages/device/loadbalancer/ace_rservers.inc.php b/includes/html/pages/device/loadbalancer/ace_rservers.inc.php index afa16fc782..e7bd6e3927 100644 --- a/includes/html/pages/device/loadbalancer/ace_rservers.inc.php +++ b/includes/html/pages/device/loadbalancer/ace_rservers.inc.php @@ -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 ' '; diff --git a/includes/html/pages/device/neighbours.inc.php b/includes/html/pages/device/neighbours.inc.php index d460ce2ffa..c6609d98fe 100644 --- a/includes/html/pages/device/neighbours.inc.php +++ b/includes/html/pages/device/neighbours.inc.php @@ -18,7 +18,7 @@ echo "Neighbours » "; $selection = basename($vars['selection'] ?? 'list'); -unset($sep); +$sep = ''; foreach ($datas as $type) { echo $sep; diff --git a/includes/html/pages/device/neighbours/list.inc.php b/includes/html/pages/device/neighbours/list.inc.php deleted file mode 120000 index 75a3e1cd6f..0000000000 --- a/includes/html/pages/device/neighbours/list.inc.php +++ /dev/null @@ -1 +0,0 @@ -../ports/neighbours.inc.php \ No newline at end of file diff --git a/includes/html/pages/device/neighbours/list.inc.php b/includes/html/pages/device/neighbours/list.inc.php new file mode 100644 index 0000000000..baed5bca6e --- /dev/null +++ b/includes/html/pages/device/neighbours/list.inc.php @@ -0,0 +1,8 @@ +load(['links.port', 'links.remoteDevice', 'links.remotePort']); +echo view('device.tabs.ports.links', [ + 'data' => [ + 'links' => DeviceCache::get($device['device_id'])->links, + ], +]); diff --git a/includes/html/pages/device/port.inc.php b/includes/html/pages/device/port.inc.php index 995092ea1f..00654a935d 100644 --- a/includes/html/pages/device/port.inc.php +++ b/includes/html/pages/device/port.inc.php @@ -45,9 +45,9 @@ $bg = '#ffffff'; $show_all = 1; -echo "
"; +echo "
"; -require 'includes/html/print-interface.inc.php'; +echo view('device.tabs.includes.port_row', ['port' => $port, 'collapsing' => false]); echo '
'; diff --git a/includes/html/pages/device/ports.inc.php b/includes/html/pages/device/ports.inc.php deleted file mode 100644 index 5e91f2cafa..0000000000 --- a/includes/html/pages/device/ports.inc.php +++ /dev/null @@ -1,170 +0,0 @@ - '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 ""; - } - - echo generate_link($text, $link_array, ['view' => $option]); - if ($vars['view'] == $option) { - echo ''; - } - - $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 ""; - } - - echo generate_link($descr, $link_array, ['view' => 'graphs', 'graph' => $type]); - if ($vars['graph'] == $type && $vars['view'] == 'graphs') { - echo ''; - } - - echo ' ('; - if ($vars['graph'] == $type && $vars['view'] == 'minigraphs') { - echo ""; - } - - echo generate_link('Mini', $link_array, ['view' => 'minigraphs', 'graph' => $type]); - if ($vars['graph'] == $type && $vars['view'] == 'minigraphs') { - echo ''; - } - - echo ')'; - $type_sep = ' | '; -}//end foreach - -print_optionbar_end(); - -if ($vars['view'] == 'minigraphs') { - $timeperiods = [ - '-1day', - '-1week', - '-1month', - '-1year', - ]; - $from = '-1day'; - echo "
"; - unset($seperator); - - foreach (Port::where('device_id', $device['device_id'])->where('disabled', 0)->orderBy('ifIndex')->get() as $port) { - echo '
' - . Url::portLink($port, - '
' . $port->getShortLabel() . '
' . - Url::graphTag([ - 'type' => $graph_type, - 'id' => $port['port_id'], - 'from' => $from, - 'width' => 180, - 'height' => 55, - 'legend' => 'no', - ])) - . '
'; - } - - echo '
'; -} 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; - } ?> -
- - - - - - - - - - - $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 '
PortPort GroupTrafficSpeedMediaMac Address
'; -}//end if - -$pagetitle[] = 'Ports'; diff --git a/includes/html/pages/device/ports/neighbours.inc.php b/includes/html/pages/device/ports/neighbours.inc.php deleted file mode 100644 index 07a363709b..0000000000 --- a/includes/html/pages/device/ports/neighbours.inc.php +++ /dev/null @@ -1,27 +0,0 @@ - - - - Local Port - Remote Device - Remote Port - Protocol - - '; - -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 '' . generate_port_link($neighbour) . '
' . htmlspecialchars($neighbour['ifAlias']) . ''; - 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 '' . generate_device_link($remote_device) . '
' . htmlspecialchars($remote_device['hardware']) . ' - ' . generate_port_link($remote_port) . '
' . htmlspecialchars($remote_port['ifAlias']) . ''; - } else { - echo '' . htmlspecialchars($neighbour['remote_hostname']) . '
' . htmlspecialchars($neighbour['remote_platform']) . ' - ' . htmlspecialchars($neighbour['remote_port']) . ''; - } - echo '' . strtoupper(htmlspecialchars($neighbour['protocol'])) . ''; -} -echo ''; diff --git a/includes/html/pages/device/ports/xdsl.inc.php b/includes/html/pages/device/ports/xdsl.inc.php deleted file mode 100644 index ad1ad232aa..0000000000 --- a/includes/html/pages/device/ports/xdsl.inc.php +++ /dev/null @@ -1,28 +0,0 @@ -"; - -echo ''; -$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 '
PortTrafficSync SpeedAttainable SpeedAttenuationSNR MarginOutput Powers
'; -echo "
"; diff --git a/includes/html/print-interface-adsl.inc.php b/includes/html/print-interface-adsl.inc.php deleted file mode 100644 index 5cdb55fa04..0000000000 --- a/includes/html/print-interface-adsl.inc.php +++ /dev/null @@ -1,115 +0,0 @@ - 0 || $port['ifOutErrors_delta'] > 0) { - $error_img = generate_port_link($port, "", 'port_errors'); -} else { - $error_img = ''; -} - -echo " - "; -echo ' - ' . generate_port_link($port, $port['ifIndex'] . '. ' . $port['label']) . ' -
' . \LibreNMS\Util\Clean::html($port['ifAlias'], []) . ''; - -if ($port['ifAlias']) { - echo '
'; -} - -$break = ''; -if ($port_details) { - foreach (dbFetchRows('SELECT * FROM `ipv4_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip) { - echo "$break " . $ip['ipv4_address'] . '/' . $ip['ipv4_prefixlen'] . ''; - $break = ','; - } - - foreach (dbFetchRows('SELECT * FROM `ipv6_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip6) { - echo "$break " . IP::parse($ip6['ipv6_address'], true) . '/' . $ip6['ipv6_prefixlen'] . ''; - $break = ','; - } -} - -echo ''; - -$width = '120'; -$height = '40'; -$from = Config::get('time.day'); - -echo ''; -echo \LibreNMS\Util\Number::formatSi($port['ifInOctets_rate'] * 8, 2, 3, 'bps') . " " . \LibreNMS\Util\Number::formatSi($port['ifOutOctets_rate'] * 8, 2, 3, 'bps'); -echo '
'; -$port['graph_type'] = 'port_bits'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -echo '' . \LibreNMS\Util\Number::formatSi($port['adslAtucChanCurrTxRate'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['adslAturChanCurrTxRate'], 2, 3, 'bps'); -echo '
'; -$port['graph_type'] = 'port_adsl_speed'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -echo '' . \LibreNMS\Util\Number::formatSi($port['adslAturCurrAttainableRate'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['adslAtucCurrAttainableRate'], 2, 3, 'bps'); -echo '
'; -$port['graph_type'] = 'port_adsl_attainable'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -echo '' . $port['adslAturCurrAtn'] . 'dB/' . $port['adslAtucCurrAtn'] . 'dB'; -echo '
'; -$port['graph_type'] = 'port_adsl_attenuation'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -echo '' . $port['adslAturCurrSnrMgn'] . 'dB/' . $port['adslAtucCurrSnrMgn'] . 'dB'; -echo '
'; -$port['graph_type'] = 'port_adsl_snr'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -echo '' . $port['adslAturCurrOutputPwr'] . 'dBm/' . $port['adslAtucCurrOutputPwr'] . 'dBm'; -echo '
'; -$port['graph_type'] = 'port_adsl_power'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; diff --git a/includes/html/print-interface-vdsl.inc.php b/includes/html/print-interface-vdsl.inc.php deleted file mode 100644 index b6f0bbef32..0000000000 --- a/includes/html/print-interface-vdsl.inc.php +++ /dev/null @@ -1,117 +0,0 @@ - 0 || $port['ifOutErrors_delta'] > 0) { - $error_img = generate_port_link($port, "", 'port_errors'); -} else { - $error_img = ''; -} - -echo " - "; -echo ' - ' . generate_port_link($port, $port['ifIndex'] . '. ' . $port['label']) . ' -
' . \LibreNMS\Util\Clean::html($port['ifAlias'], []) . ''; - -if ($port['ifAlias']) { - echo '
'; -} - -$break = ''; -if ($port_details) { - foreach (Ipv4Address::where('port_id', (string) $port['port_id']) as $ip) { - echo "$break " . $ip['ipv4_address'] . '/' . $ip['ipv4_prefixlen'] . ''; - $break = ','; - } - - foreach (Ipv6Address::where('port_id', (string) $port['port_id']) as $ip6) { - echo "$break " . IP::parse($ip6['ipv6_address'], true) . '/' . $ip6['ipv6_prefixlen'] . ''; - $break = ','; - } -} - -echo ''; - -$width = '120'; -$height = '40'; -$from = Config::get('time.day'); - -echo ''; -echo \LibreNMS\Util\Number::formatSi($port['ifInOctets_rate'] * 8, 2, 3, 'bps') . " " . \LibreNMS\Util\Number::formatSi($port['ifOutOctets_rate'] * 8, 2, 3, 'bps'); -echo '
'; -$port['graph_type'] = 'port_bits'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -echo '' . \LibreNMS\Util\Number::formatSi($port['xdsl2ChStatusActDataRateXtur'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['xdsl2ChStatusActDataRateXtuc'], 2, 3, 'bps'); -echo '
'; -$port['graph_type'] = 'port_vdsl_speed'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -echo '' . \LibreNMS\Util\Number::formatSi($port['xdsl2LineStatusAttainableRateDs'], 2, 3, 'bps') . '/' . \LibreNMS\Util\Number::formatSi($port['xdsl2LineStatusAttainableRateUs'], 2, 3, 'bps'); -echo '
'; -$port['graph_type'] = 'port_vdsl_attainable'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; -//echo '' . $port['adslAturCurrAtn'] . 'dB/' . $port['adslAtucCurrAtn'] . 'dB'; -//echo '
'; -//$port['graph_type'] = 'port_adsl_attenuation'; -//echo generate_port_link( -// $port, -// "", -// $port['graph_type'] -//); - -echo ''; -//echo '' . $port['adslAturCurrSnrMgn'] . 'dB/' . $port['adslAtucCurrSnrMgn'] . 'dB'; -//echo '
'; -//$port['graph_type'] = 'port_adsl_snr'; -//echo generate_port_link( -// $port, -// "", -// $port['graph_type'] -//); - -echo ''; -echo '' . $port['xdsl2LineStatusActAtpDs'] . 'dBm/' . $port['xdsl2LineStatusActAtpUs'] . 'dBm'; -echo '
'; -$port['graph_type'] = 'port_vdsl_power'; -echo generate_port_link( - $port, - "", - $port['graph_type'] -); - -echo ''; diff --git a/includes/html/print-interface.inc.php b/includes/html/print-interface.inc.php deleted file mode 100644 index 040a9400d8..0000000000 --- a/includes/html/print-interface.inc.php +++ /dev/null @@ -1,370 +0,0 @@ - - 0 || $port['ifOutErrors_delta'] > 0) { - $error_img = generate_port_link($port, "", 'port_errors'); -} else { - $error_img = ''; -} - -if (dbFetchCell('SELECT COUNT(*) FROM `mac_accounting` WHERE `port_id` = ?', [$port['port_id']])) { - $mac = " 'macaccounting']) . "'>"; -} else { - $mac = ''; -} - -echo " - "; - -if (Auth::user()->hasGlobalRead()) { - $port_data = array_to_htmljson($port); - echo ''; -} - - echo ' - ' . generate_port_link($port, $port['label']) . " $error_img $mac -
" . $port['ifAlias'] . ''; - -if ($port['ifAlias']) { - echo '
'; -} - -$break = ''; - -if (! empty($port_details)) { - foreach (dbFetchRows('SELECT * FROM `ipv4_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip) { - echo "$break " . $ip['ipv4_address'] . '/' . $ip['ipv4_prefixlen'] . ''; - $break = '
'; - } - - foreach (dbFetchRows('SELECT * FROM `ipv6_addresses` WHERE `port_id` = ?', [$port['port_id']]) as $ip6) { - echo "$break " . IP::parse($ip6['ipv6_address'], true) . '/' . $ip6['ipv6_prefixlen'] . ''; - $break = '
'; - } -} - -echo ''; - -$port_group_name_list = Port::find($port['port_id'])->groups->pluck('name')->toArray() ?: ['Default']; - -echo ''; -echo implode('
', $port_group_name_list); -echo ""; - -if (! empty($port_details)) { - $port['graph_type'] = 'port_bits'; - echo generate_port_link($port, ""); - $port['graph_type'] = 'port_upkts'; - echo generate_port_link($port, ""); - $port['graph_type'] = 'port_errors'; - echo generate_port_link($port, ""); -} - -echo ""; - -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 " " . Number::formatSi($port['in_rate'], 2, 3, 'bps') . "
- " . Number::formatSi($port['out_rate'], 2, 3, 'bps') . "
- " . Number::formatBi($port['ifInUcastPkts_rate'], 2, 3, 'pps') . "

- " . Number::formatBi($port['ifOutUcastPkts_rate'], 2, 3, 'pps') . '
'; -} - -echo ""; -if ($port['ifSpeed']) { - echo '' . \LibreNMS\Util\Number::formatSi($port['ifSpeed'], 2, 3, 'bps') . ''; -} - -echo '
'; - -if ($port['ifDuplex'] != 'unknown') { - echo '' . $port['ifDuplex'] . ''; -} 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 '

VLANs: '; - echo $vlan_count; - echo '

'; -} elseif ($vlan_count == 1 || $port['ifVlan']) { - echo '

VLAN: '; - echo $vlans[0] ?: $port['ifVlan']; - echo '

'; -} elseif ($port['ifVrf']) { - $vrf = dbFetchRow('SELECT * FROM vrfs WHERE vrf_id = ?', [$port['ifVrf']]); - echo "

" . $vrf['vrf_name'] . '

'; -}//end if - -if (! empty($port_adsl->adslLineCoding)) { - echo ""; - echo $port_adsl->adslLineCoding . '/' . rewrite_adslLineType($port_adsl->adslLineType); - echo '
'; - // 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 '
'; - // 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 ""; - echo 'Atten:' . $port_adsl->adslAturCurrAtn . 'dB/' . $port_adsl->adslAtucCurrAtn . 'dB'; - echo '
'; - echo 'SNR:' . $port_adsl->adslAturCurrSnrMgn . 'dB/' . $port_adsl->adslAtucCurrSnrMgn . 'dB'; -} elseif (! empty($port_vdsl->xdsl2LineStatusAttainableRateDs)) { - echo ""; - echo '
'; - // 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 '
'; - echo 'Max:' . Number::formatSi($port_vdsl->xdsl2LineStatusAttainableRateDs, 2, 3, 'bps') . '/' . Number::formatSi($port_vdsl->xdsl2LineStatusAttainableRateUs, 2, 3, 'bps'); -} else { - echo ""; - if ($port['ifType'] && $port['ifType'] != '') { - echo '' . \LibreNMS\Util\Rewrite::normalizeIfType($port['ifType']) . ''; - } else { - echo '-'; - } - - echo '
'; - if (! empty($ifHardType)) { - echo '' . $ifHardType . ''; - } else { - echo '-'; - } - - echo ""; - if ($port['ifPhysAddress'] && $port['ifPhysAddress'] != '') { - echo '' . $port->ifPhysAddress . ''; - } else { - echo '-'; - } - - echo '
'; - if ($port['ifMtu'] && $port['ifMtu'] != '') { - echo 'MTU ' . $port['ifMtu'] . ''; - } else { - echo '-'; - } -}//end if - -echo ''; -echo ''; - -$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 '
- '; - } - - 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 '
[...]
'; - echo '
'; - echo '
'; -} -echo ''; - -// 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 ""; - - include 'includes/html/print-interface-graphs.inc.php'; - - echo ''; - } -} diff --git a/includes/rewrites.php b/includes/rewrites.php index 21f0974e7e..e6e2136682 100644 --- a/includes/rewrites.php +++ b/includes/rewrites.php @@ -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) diff --git a/lang/en/port.php b/lang/en/port.php index edfaaf0b6c..6e2d883be1 100644 --- a/lang/en/port.php +++ b/lang/en/port.php @@ -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', + ], ]; diff --git a/misc/config_definitions.json b/misc/config_definitions.json index db8b4f1008..022c6dd234 100644 --- a/misc/config_definitions.json +++ b/misc/config_definitions.json @@ -1719,10 +1719,6 @@ "default": false, "type": "boolean" }, - "enable_port_relationship": { - "default": true, - "type": "boolean" - }, "enable_ports_adsl": { "default": true, "type": "boolean" diff --git a/phpstan-baseline-deprecated.neon b/phpstan-baseline-deprecated.neon index de3dd09363..f797ed5a2e 100644 --- a/phpstan-baseline-deprecated.neon +++ b/phpstan-baseline-deprecated.neon @@ -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\\(\\)\\: diff --git a/resources/views/components/expandable.blade.php b/resources/views/components/expandable.blade.php new file mode 100644 index 0000000000..02743d889d --- /dev/null +++ b/resources/views/components/expandable.blade.php @@ -0,0 +1,15 @@ +
+
{{ $slot }}
+
... +
+
diff --git a/resources/views/components/port-link.blade.php b/resources/views/components/port-link.blade.php index ac79d3bf73..bc0d4b0cdd 100644 --- a/resources/views/components/port-link.blade.php +++ b/resources/views/components/port-link.blade.php @@ -1,9 +1,5 @@ - - {{ $slot->isNotEmpty() ? $slot : $label }} - + @include('components.port-link_basic')
{{ $port->device->displayName() }} - {{ $label }}
{{ $description }}
diff --git a/resources/views/components/port-link_basic.blade.php b/resources/views/components/port-link_basic.blade.php new file mode 100644 index 0000000000..48537f8ebd --- /dev/null +++ b/resources/views/components/port-link_basic.blade.php @@ -0,0 +1,5 @@ + + {{ $slot->isNotEmpty() ? $slot : $label }} + diff --git a/resources/views/components/select.blade.php b/resources/views/components/select.blade.php new file mode 100644 index 0000000000..07f2416421 --- /dev/null +++ b/resources/views/components/select.blade.php @@ -0,0 +1,22 @@ +@props(['label', 'name', 'options', 'selected', 'hint']) + +
+ @isset($label) + + @endif + +
diff --git a/resources/views/components/submenu.blade.php b/resources/views/components/submenu.blade.php index efd7ce86c3..beee08ddac 100644 --- a/resources/views/components/submenu.blade.php +++ b/resources/views/components/submenu.blade.php @@ -2,15 +2,19 @@
@foreach ($menu as $header => $m) @if($loop->first) - {{ $title }} - » + {{ $title }} » @else - | {{ $header }}: + {{ $header }}: @endif @foreach($m as $sm) - @if($isSelected($sm['url']))@endif - {{ $sm['name'] }}@if($isSelected($sm['url']))@endif + + {{ $sm['name'] }} + + + @isset($sm['sub_name']) + ({{ $sm['sub_name'] }}) + @endisset @if(!$loop->last) | diff --git a/resources/views/device/index.blade.php b/resources/views/device/index.blade.php index 09781ced77..c4ea4fd245 100644 --- a/resources/views/device/index.blade.php +++ b/resources/views/device/index.blade.php @@ -36,6 +36,15 @@ @if($link['external'])target="_blank" rel="noopener" @endif > {{ $link['title'] }} @endforeach + @if($page_links) + + @foreach($page_links as $link) +
  • {{ $link['title'] }}
  • + @endforeach + @endif
    diff --git a/resources/views/device/tabs/ports.blade.php b/resources/views/device/tabs/ports.blade.php new file mode 100644 index 0000000000..c22c48bb24 --- /dev/null +++ b/resources/views/device/tabs/ports.blade.php @@ -0,0 +1,5 @@ +@extends('device.submenu') + +@section('tabcontent') + @includeFirst(['device.tabs.ports.' . $data['tab'], 'device.tabs.ports.detail']) +@endsection diff --git a/includes/html/pages/device/ports/arp.inc.php b/resources/views/device/tabs/ports/arp.blade.php similarity index 52% rename from includes/html/pages/device/ports/arp.inc.php rename to resources/views/device/tabs/ports/arp.blade.php index 7f28aa871e..963b883351 100644 --- a/includes/html/pages/device/ports/arp.inc.php +++ b/resources/views/device/tabs/ports/arp.blade.php @@ -1,37 +1,33 @@ - - - + +
    + -Vendor'; -} -?> + @config('mac_oui.enabled') + + @endconfig - -
    Port MAC addressVendorIPv4 address Remote device Remote interface
    + + + - diff --git a/resources/views/device/tabs/ports/basic.blade.php b/resources/views/device/tabs/ports/basic.blade.php new file mode 100644 index 0000000000..37c381e76d --- /dev/null +++ b/resources/views/device/tabs/ports/basic.blade.php @@ -0,0 +1 @@ +@include('device.tabs.ports.detail') diff --git a/resources/views/device/tabs/ports/detail.blade.php b/resources/views/device/tabs/ports/detail.blade.php new file mode 100644 index 0000000000..fda948cd3f --- /dev/null +++ b/resources/views/device/tabs/ports/detail.blade.php @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + @foreach($data['ports'] as $port) + @include('device.tabs.ports.includes.port_row', ['collapsing' => true]) + @endforeach +
    {{ __('Port') }}{{ __('Port Groups') }}{{ __('Graphs') }}{{ __('Traffic') }}{{ __('Speed') }}{{ __('Media') }}{{ __('MAC Address') }}
    +
    + {{ $data['ports']->links('pagination::tailwind', ['perPage' => $data['perPage']]) }} + @isset($data['perPage']) + + @endisset +
    +
    diff --git a/includes/html/pages/device/ports/fdb.inc.php b/resources/views/device/tabs/ports/fdb.blade.php similarity index 62% rename from includes/html/pages/device/ports/fdb.inc.php rename to resources/views/device/tabs/ports/fdb.blade.php index 55dec42481..9500a10723 100644 --- a/includes/html/pages/device/ports/fdb.inc.php +++ b/resources/views/device/tabs/ports/fdb.blade.php @@ -1,11 +1,9 @@ - - - + +
    + - + @@ -14,22 +12,22 @@ $no_refresh = true; - -
    MAC AddressVendorVendor IPv4 Address Port DescriptionFirst seen Last seen
    + + + - diff --git a/resources/views/device/tabs/ports/graphs.blade.php b/resources/views/device/tabs/ports/graphs.blade.php new file mode 100644 index 0000000000..a43453c380 --- /dev/null +++ b/resources/views/device/tabs/ports/graphs.blade.php @@ -0,0 +1,19 @@ +@foreach($data['ports'] as $port) + + +
    + {{-- div to allow color to override boostrap title link color --}} + + + + {{ $port->getLabel() }} + @if($port->getLabel() !== $port->getDescription()) + {{ $port->getDescription() }} + @endif + + +
    +
    + +
    +@endforeach diff --git a/resources/views/device/tabs/ports/includes/port_row.blade.php b/resources/views/device/tabs/ports/includes/port_row.blade.php new file mode 100644 index 0000000000..24dc9a8b26 --- /dev/null +++ b/resources/views/device/tabs/ports/includes/port_row.blade.php @@ -0,0 +1,138 @@ + + + + {{ $port->getLabel() }} + +
    + @if($port->ifInErrors_delta > 0 || $port->ifOutErrors_delta > 0) + + @endif + @if($port->getLabel() !== $port->getDescription()) + {{ $port->getDescription() }} + @endif +
    + @if($data['tab'] != 'basic') + @foreach($port->ipv4 as $ipv4) +
    {{ $ipv4->ipv4_address }}/{{ $ipv4->ipv4_prefixlen }}
    + @endforeach + @foreach($port->ipv6 as $ipv6) +
    {{ $ipv6->ipv6_compressed }}/{{ $ipv6->ipv6_prefixlen }}
    + @endforeach + @endif + + + @forelse($port->groups as $group) +
    {{ $group->name }}
    + @empty +
    {{ __('Default') }}
    + @endforelse + + +
    + + + + + + + + + +
    + + +
    + + {{ \LibreNMS\Util\Number::formatSi($port->ifInOctets_rate * 8, 2, 3, 'bps') }} +
    +
    + + {{ \LibreNMS\Util\Number::formatSi($port->ifOutOctets_rate * 8, 2, 3, 'bps') }} +
    +
    + + {{ \LibreNMS\Util\Number::formatBi($port->ifInUcastPkts_rate, 2, 3, 'pps') }} +
    +
    + + {{ \LibreNMS\Util\Number::formatBi($port->ifOutUcastPkts_rate, 2, 3, 'pps') }} +
    + + + @if($port->ifSpeed) +
    {{ \LibreNMS\Util\Number::formatSi($port->ifSpeed, 2, 3, 'bps') }}
    + @endif + @if($port->ifDuplex != 'unknown') +
    {{ $port->ifDuplex }}
    + @endif + @if($port->vlans->isNotEmpty()) + + @endif + + + @if($port->adsl) +
    {{ $port->adsl->adslLineCoding }}/{{ \LibreNMS\Util\Rewrite::dslLineType($port->adsl->adslLineType) }}
    +
    {{ __('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')]) }}
    +
    {{ __('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')]) }}
    +
    {{ __('port.xdsl.attenuation_stat', ['down' => $port->adsl->adslAtucCurrAtn . 'dB', 'up' => $port->adsl->adslAturCurrAtn . 'dB']) }}
    +
    {{ __('port.xdsl.snr_stat', ['down' => $port->adsl->adslAtucCurrSnrMgn . 'dB','up' => $port->adsl->adslAturCurrSnrMgn . 'dB']) }}
    + @elseif($port->vdsl) +
    {{ __('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')]) }}
    +
    {{ __('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')]) }}
    + @else +
    {{ \LibreNMS\Util\Rewrite::normalizeIfType($port->ifType) }}
    + @endif + + + +
    {{ $port->ifPhysAddress }}
    +
    {{ $port->ifMtu ? __('port.mtu_label', ['mtu' => $port->ifMtu]) : '' }}
    + + + + @foreach($data['neighbors'][$port->port_id] as $port_id => $neighbor) +
    + @php + $np = $data['neighbor_ports']->get($neighbor['port_id']); + @endphp + @if($np) + @if(isset($neighbor['link'])) + + @elseif(isset($neighbor['pseudowire'])) + + @elseif(isset($neighbor['stack_low'])) + + @elseif(isset($neighbor['stack_high'])) + + @elseif(isset($neighbor['pagp'])) + + @else + + @endif + + + on + + + @isset($neighbor['ipv6_network']) + v6 + @endisset + @isset($neighbor['ipv4_network']) + v4 + @endisset + @endif +
    + @endforeach +
    + + diff --git a/resources/views/device/tabs/ports/includes/xdsl_base_columns.blade.php b/resources/views/device/tabs/ports/includes/xdsl_base_columns.blade.php new file mode 100644 index 0000000000..aec47eb150 --- /dev/null +++ b/resources/views/device/tabs/ports/includes/xdsl_base_columns.blade.php @@ -0,0 +1,16 @@ + + {{ $dslPort->port->ifIndex }}. {{ $dslPort->port->getLabel() }} + @if($dslPort->port->ifInErrors_delta > 0 || $dslPort->port->ifOutErrors_delta > 0) + + @endif + @if($dslPort->port->getLabel() !== $dslPort->port->getDescription()) +
    {{ $dslPort->port->getDescription() }} + @endif + + + {{ \LibreNMS\Util\Number::formatSi($dslPort->port->ifInOctets_rate * 8, 2, 3, 'bps') }} + + {{ \LibreNMS\Util\Number::formatSi($dslPort->port->ifOutOctets_rate * 8, 2, 3, 'bps') }} +
    + + diff --git a/resources/views/device/tabs/ports/links.blade.php b/resources/views/device/tabs/ports/links.blade.php new file mode 100644 index 0000000000..d97a8f5179 --- /dev/null +++ b/resources/views/device/tabs/ports/links.blade.php @@ -0,0 +1,41 @@ + + + + + + + + + + + @foreach($data['links'] as $link) + + + + + + + @endforeach +
    Local PortRemote DeviceRemote PortProtocol
    + @if($link->port->getLabel() !== $link->port->getDescription() ) +
    {{ $link->port->getDescription() }} + @endif +
    + @if($link->remoteDevice) + + @else + {{ $link->remote_hostname }} + @endif +
    + {{ $link->remote_platform }} +
    + @if($link->remotePort) + + @if($link->remotePort->getLabel() !== $link->remotePort->getDescription() ) +
    {{ $link->remotePort->getDescription() }} + @endif + @else + {{ $link->remote_port }} + @endif +
    {{ strtoupper($link->protocol) }}
    +
    diff --git a/resources/views/device/tabs/ports/mini_graphs.blade.php b/resources/views/device/tabs/ports/mini_graphs.blade.php new file mode 100644 index 0000000000..49c240f90f --- /dev/null +++ b/resources/views/device/tabs/ports/mini_graphs.blade.php @@ -0,0 +1,8 @@ +@foreach($data['ports'] as $port) +
    + +
    {{ $port->getShortLabel() }}
    + +
    +
    +@endforeach diff --git a/resources/views/device/tabs/ports/xdsl.blade.php b/resources/views/device/tabs/ports/xdsl.blade.php new file mode 100644 index 0000000000..b78ed1bedd --- /dev/null +++ b/resources/views/device/tabs/ports/xdsl.blade.php @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + @foreach($data['adsl'] as $dslPort) + + @include('device.tabs.ports.includes.xdsl_base_columns') + + + + + + + @endforeach + + @foreach($data['vdsl'] as $dslPort) + @include('device.tabs.ports.includes.xdsl_base_columns') + + + + + + @endforeach +
    {{ __('Port') }}{{ __('Traffic') }}{{ __('port.xdsl.sync') }}{{ __('port.xdsl.attainable') }}{{ __('port.xdsl.attenuation') }}{{ __('port.xdsl.snr') }}{{ __('port.xdsl.power') }}
    + {{ \LibreNMS\Util\Number::formatSi($dslPort->adslAtucChanCurrTxRate, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->adslAturChanCurrTxRate, 2, 3, 'bps') }} +
    + +
    + {{ \LibreNMS\Util\Number::formatSi($dslPort->adslAtucCurrAttainableRate, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->adslAturCurrAttainableRate, 2, 3, 'bps') }} +
    + +
    + {{ $dslPort->adslAtucCurrAtn }}dB/{{ $dslPort->adslAturCurrAtn }}dB +
    + +
    + {{ $dslPort->adslAtucCurrSnrMgn }}dB/{{ $dslPort->adslAturCurrSnrMgn }}dB +
    + +
    + {{ $dslPort->adslAturCurrOutputPwr }}dBm/{{ $dslPort->adslAtucCurrOutputPwr }}dBm +
    + +
    + {{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2ChStatusActDataRateXtuc, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2ChStatusActDataRateXtur, 2, 3, 'bps') }} +
    + +
    + {{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2LineStatusAttainableRateDs, 2, 3, 'bps') }}/{{ \LibreNMS\Util\Number::formatSi($dslPort->xdsl2LineStatusAttainableRateUs, 2, 3, 'bps') }} +
    + +
    + {{ $dslPort->xdsl2LineStatusActAtpDs }}dBm/{{ $dslPort->xdsl2LineStatusActAtpUs }}dBm +
    + +
    +
    diff --git a/resources/views/layouts/librenmsv1.blade.php b/resources/views/layouts/librenmsv1.blade.php index a63e68c4d0..ed9e20aef2 100644 --- a/resources/views/layouts/librenmsv1.blade.php +++ b/resources/views/layouts/librenmsv1.blade.php @@ -42,7 +42,7 @@ - + @foreach(LibreNMS\Config::get('webui.custom_css', []) as $custom_css) diff --git a/resources/views/vendor/pagination/simple-tailwind.blade.php b/resources/views/vendor/pagination/simple-tailwind.blade.php new file mode 100644 index 0000000000..49eec2bb65 --- /dev/null +++ b/resources/views/vendor/pagination/simple-tailwind.blade.php @@ -0,0 +1,25 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/tailwind.blade.php b/resources/views/vendor/pagination/tailwind.blade.php new file mode 100644 index 0000000000..75497facbf --- /dev/null +++ b/resources/views/vendor/pagination/tailwind.blade.php @@ -0,0 +1,102 @@ +@if ($paginator->hasPages()) + +@endif