From 5da37a7facac0da279fa0d5e5154a43d13d75b3b Mon Sep 17 00:00:00 2001 From: Stephan de Wit Date: Mon, 27 Nov 2023 15:52:04 +0100 Subject: [PATCH] interfaces: revamp overview page (#7019) This commit omits some of the link-specific information such as ppp uptime, disconnect/release mechanism, as well as wireless and bridge information. Since there is more of this type of information available than was originally being handled by get_interfaces_info(), perhaps it makes more sense to extend the backend script with the relevant bits in time. --- plist | 4 +- .../Interfaces/Api/OverviewController.php | 278 +++++++++++++++ .../Interfaces/OverviewController.php | 37 ++ .../app/models/OPNsense/Core/Menu/Menu.xml | 1 + .../views/OPNsense/Interface/overview.volt | 318 ++++++++++++++++++ .../conf/actions.d/actions_interface.conf | 8 +- src/www/widgets/api/plugins/interfaces.inc | 67 ---- src/www/widgets/include/interface_list.inc | 2 +- .../widgets/include/interface_statistics.inc | 2 +- .../widgets/widgets/interface_list.widget.php | 153 +++++---- .../widgets/interface_statistics.widget.php | 56 +-- 11 files changed, 758 insertions(+), 168 deletions(-) create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/OverviewController.php create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Interfaces/OverviewController.php create mode 100644 src/opnsense/mvc/app/views/OPNsense/Interface/overview.volt delete mode 100644 src/www/widgets/api/plugins/interfaces.inc diff --git a/plist b/plist index ccccb5142..ac2ca05b4 100644 --- a/plist +++ b/plist @@ -387,12 +387,14 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/LaggSettingsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/LoopbackSettingsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/NeighborSettingsController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/OverviewController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VipSettingsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VlanSettingsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VxlanSettingsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/LaggController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/LoopbackController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/NeighborController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/OverviewController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/VipController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/VlanController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/VxlanController.php @@ -833,6 +835,7 @@ /usr/local/opnsense/mvc/app/views/OPNsense/Interface/lagg.volt /usr/local/opnsense/mvc/app/views/OPNsense/Interface/loopback.volt /usr/local/opnsense/mvc/app/views/OPNsense/Interface/neighbor.volt +/usr/local/opnsense/mvc/app/views/OPNsense/Interface/overview.volt /usr/local/opnsense/mvc/app/views/OPNsense/Interface/vip.volt /usr/local/opnsense/mvc/app/views/OPNsense/Interface/vlan.volt /usr/local/opnsense/mvc/app/views/OPNsense/Interface/vxlan.volt @@ -2152,7 +2155,6 @@ /usr/local/www/vpn_openvpn_client.php /usr/local/www/vpn_openvpn_server.php /usr/local/www/widgets/api/get.php -/usr/local/www/widgets/api/plugins/interfaces.inc /usr/local/www/widgets/api/plugins/system.inc /usr/local/www/widgets/api/plugins/temperature.inc /usr/local/www/widgets/include/carp_status.inc diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/OverviewController.php b/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/OverviewController.php new file mode 100644 index 000000000..4ebe15bc0 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/OverviewController.php @@ -0,0 +1,278 @@ + gettext('Flags'), + 'options' => gettext('Options'), + 'supported_media' => gettext('Supported Media'), + 'is_physical' => gettext('Physical'), + 'device' => gettext('Device'), + 'name' => gettext('Name'), + 'description' => gettext('Description'), + 'status' => gettext('Status'), + 'enabled' => gettext('Enabled'), + 'link_type' => gettext('Link Type'), + 'ipv4' => gettext('IPv4 Addresses'), + 'ipv6' => gettext('IPv6 Addresses'), + 'gateways' => gettext('Gateways'), + 'routes' => gettext('Routes'), + 'macaddr' => gettext('MAC Address'), + 'media_raw' => gettext('Media'), + 'mediaopt' => gettext('Media Options'), + 'capabilities' => gettext('Capabilities'), + 'identifier' => gettext('Identifier'), + 'ipaddr' => gettext('IP Address'), + 'subnetbits' => gettext('Subnet Bits'), + 'statistics' => gettext('Statistics'), + 'driver' => gettext('Driver'), + 'index' => gettext('Index'), + 'promiscuous listeners' => gettext('Promiscuous Listeners'), + 'send queue length' => gettext('Send Queue Length'), + 'send queue max length' => gettext('Send Queue Max Length'), + 'send queue drops' => gettext('Send Queue Drops'), + 'type' => gettext('Type'), + 'address length' => gettext('Address Length'), + 'header length' => gettext('Header Length'), + 'link state' => gettext('Link State'), + 'datalen' => gettext('Data Length'), + 'metric' => gettext('Metric'), + 'line rate' => gettext('Line Rate'), + 'packets received' => gettext('Packets Received'), + 'input errors' => gettext('Input Errors'), + 'packets transmitted' => gettext('Packets Transmitted'), + 'output errors' => gettext('Output Errors'), + 'collisions' => gettext('Collisions'), + 'bytes received' => gettext('Bytes Received'), + 'bytes transmitted' => gettext('Bytes Transmitted'), + 'multicasts received' => gettext('Multicasts Received'), + 'multicasts transmitted' => gettext('Multicasts Transmitted'), + 'input queue drops' => gettext('Input Queue Drops'), + 'packets for unknown protocol' => gettext('Packets for Unknown Protocol'), + 'HW offload capabilities' => gettext('Hardware Offload Capabilities'), + 'uptime at attach or stat reset' => gettext('Uptime at Attach or Statistics Reset'), + ]; + } + + private function parseIfInfo($interface = null, $detailed = false) + { + $backend = new Backend(); + $gateways = new Gateways(); + $cfg = Config::getInstance()->object(); + $result = []; + + /* quick information */ + $ifinfo = json_decode($backend->configdpRun('interface list ifconfig', [$interface]), true); + $routes = json_decode($backend->configdRun('interface routes list -n json'), true); + + /* detailed information */ + if ($detailed) { + $stats = json_decode($backend->configdpRun('interface list stats', [$interface]), true); + if (!$interface) { + foreach ($ifinfo as $if => $info) { + if (array_key_exists($if, $stats)) { + $ifinfo[$if]['statistics'] = $stats[$if]; + } + } + } else { + $ifinfo[$interface]['statistics'] = $stats; + } + } + + /* map routes to interfaces */ + foreach($routes as $route) { + if (!empty($route['netif']) && !empty($ifinfo[$route['netif']])) { + $ifinfo[$route['netif']]['routes'][] = $route['destination']; + } + } + + /* combine interfaces details with config */ + foreach ($cfg->interfaces->children() as $key => $node) { + if (!empty((string)$node->if) && !empty($ifinfo[(string)$node->if])) { + $props = []; + foreach ($node->children() as $property) { + $props[$property->getName()] = (string)$property; + } + $ifinfo[(string)$node->if]['config'] = $props; + $ifinfo[(string)$node->if]['config']['identifier'] = $key; + } + } + + /* format information */ + foreach ($ifinfo as $if => $details) { + $tmp = $details; + + if ($if == 'pfsync0') { + continue; + } + + $tmp['status'] = (!empty($details['flags']) && in_array('up', $details['flags'])) ? 'up' : 'down'; + if (!empty($details['status'])) { + if (!(in_array($details['status'] , ['active', 'running']))) { + /* reflect current ifconfig status, such as 'no carrier' */ + $tmp['status'] = $details['status']; + } + } + + if (empty($details['config'])) { + $tmp['identifier'] = ''; + $tmp['description'] = gettext('Unassigned Interface'); + $result[] = $tmp; + continue; + } + + $config = $details['config']; + + $tmp['identifier'] = $config['identifier']; + $tmp['description'] = !empty($config['descr']) ? $config['descr'] : strtoupper($config['identifier']); + $tmp['enabled'] = !empty($config['enable']); + $tmp['link_type'] = !empty($config['ipaddr']) ? $config['ipaddr'] : 'none'; + if (Util::isIpAddress($tmp['link_type'])) { + $tmp['link_type'] = 'static'; + } + + /* parse IP configuration */ + unset($tmp['ipv4'], $tmp['ipv6']); + foreach (['ipv4', 'ipv6'] as $ipproto) { + if (!empty($details[$ipproto])) { + foreach ($details[$ipproto] as $ip) { + if (!empty($ip['ipaddr'])) { + $entry = []; + $entry['ipaddr'] = $ip['ipaddr'] . '/' . $ip['subnetbits']; + + if (!empty($ip['vhid'])) { + $vhid = $ip['vhid']; + $entry['vhid'] = $vhid; + + if (!empty($details['carp'])) { + foreach ($details['carp'] as $carp) { + if ($carp['vhid'] == $vhid) { + $entry['status'] = $carp['status']; + $entry['advbase'] = $carp['advbase']; + $entry['advskew'] = $carp['advskew']; + } + } + } + } + + $tmp[$ipproto][] = $entry; + } + } + } + } + + /* gateway(s) */ + $gatewayv4 = $gateways->getInterfaceGateway($tmp['identifier'] , 'inet'); + $gatewayv6 = $gateways->getInterfaceGateway($tmp['identifier'], 'inet6'); + $tmp['gateways'] = array_filter([$gatewayv4, $gatewayv6]); + + $result[] = $tmp; + } + + return $result; + } + + public function interfacesInfoAction($details = false) + { + $this->sessionClose(); + $result = $this->parseIfInfo(null, $details); + return $this->searchRecordsetBase($result); + } + + public function getInterfaceAction($if = null) + { + $this->sessionClose(); + $result = ["message" => "failed"]; + if ($if != null) { + $ifinfo = $this->parseIfInfo($if, true)[0] ?? []; + if (!empty($ifinfo)) { + if (!empty($ifinfo['macaddr'])) { + $macs = json_decode((new Backend())->configdRun('interface list macdb json'), true); + $mac_hi = strtoupper(substr(str_replace(':', '', $ifinfo['macaddr']), 0, 6)); + if (array_key_exists($mac_hi, $macs)) { + $ifinfo['macaddr'] = $ifinfo['macaddr'] . ' - ' . $macs[$mac_hi]; + } + } + + /* move statistics one level up */ + if (isset($ifinfo['statistics'])) { + $stats = $ifinfo['statistics']; + unset($ifinfo['statistics']); + + $ifinfo = array_merge($ifinfo, $stats); + } + + unset($ifinfo['config']); + + /* apply translations */ + foreach ($ifinfo as $key => $value) { + $ifinfo[$key] = [ + 'value' => $value, + 'translation' => self::translations()[$key] ?? $key + ]; + } + + $result['message'] = $ifinfo; + } + } + + return json_encode($result); + } + + public function reloadInterfaceAction($identifier = null) + { + $this->sessionClose(); + $result = ["message" => "failed"]; + + if ($identifier != null) { + $backend = new Backend(); + $result['message'] = $backend->configdpRun('interface reconfigure', [$identifier]); + } + + return $result; + } + + public function exportAction() + { + $this->sessionClose(); + $this->response->setRawHeader('Content-Type: application/json'); + $this->response->setRawHeader('Content-Disposition: attachment; filename=ifconfig.json'); + echo (new Backend())->configdRun('interface list ifconfig'); + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/OverviewController.php b/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/OverviewController.php new file mode 100644 index 000000000..cc4fb04e9 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/OverviewController.php @@ -0,0 +1,37 @@ +view->pick('OPNsense/Interface/overview'); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml index ef5fdf8ad..752822aca 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml @@ -99,6 +99,7 @@ + diff --git a/src/opnsense/mvc/app/views/OPNsense/Interface/overview.volt b/src/opnsense/mvc/app/views/OPNsense/Interface/overview.volt new file mode 100644 index 000000000..23dab1e82 --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Interface/overview.volt @@ -0,0 +1,318 @@ +{# + # Copyright (c) 2023 Deciso B.V. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + + + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + +
{{ lang._('Status') }}{{ lang._('Interface') }}{{ lang._('Device') }}{{ lang._('Link Type') }}{{ lang._('IPv4') }}{{ lang._('IPv6') }}{{ lang._('Gateway') }}{{ lang._('Routes') }}{{ lang._('Commands') }}
+
diff --git a/src/opnsense/service/conf/actions.d/actions_interface.conf b/src/opnsense/service/conf/actions.d/actions_interface.conf index a926ee815..3271f5a19 100644 --- a/src/opnsense/service/conf/actions.d/actions_interface.conf +++ b/src/opnsense/service/conf/actions.d/actions_interface.conf @@ -91,10 +91,16 @@ message:request mac table [list.ifconfig] command:/usr/local/sbin/pluginctl -D -parameters: +parameters: %s type:script_output message:request ifconfig +[list.stats] +command:/usr/local/sbin/pluginctl -I +parameters: %s +type:script_output +message:request interface stats + [show.traffic] command:/usr/local/opnsense/scripts/interfaces/traffic_stats.php parameters: diff --git a/src/www/widgets/api/plugins/interfaces.inc b/src/www/widgets/api/plugins/interfaces.inc deleted file mode 100644 index 1634f481c..000000000 --- a/src/www/widgets/api/plugins/interfaces.inc +++ /dev/null @@ -1,67 +0,0 @@ - $ifname) { - $ifinfo = $ifsinfo[$ifdescr]; - $interfaceItem = array(); - $interfaceItem['inpkts'] = $ifinfo['inpkts']; - $interfaceItem['outpkts'] = $ifinfo['outpkts']; - $interfaceItem['inbytes'] = $ifinfo['inbytes']; - $interfaceItem['outbytes'] = $ifinfo['outbytes']; - $interfaceItem['inbytes_frmt'] = format_bytes($ifinfo['inbytes']); - $interfaceItem['outbytes_frmt'] = format_bytes($ifinfo['outbytes']); - $interfaceItem['inerrs'] = isset($ifinfo['inerrs']) ? $ifinfo['inerrs'] : "0"; - $interfaceItem['outerrs'] = isset($ifinfo['outerrs']) ? $ifinfo['outerrs'] : "0"; - $interfaceItem['collisions'] = isset($ifinfo['collisions']) ? $ifinfo['collisions'] : "0"; - $interfaceItem['descr'] = $ifdescr; - $interfaceItem['name'] = $ifname; - switch ($ifinfo['status']) { - case 'down': - case 'no carrier': - case 'up': - $interfaceItem['status'] = $ifinfo['status']; - break; - case 'associated': - $interfaceItem['status'] = 'up'; - break; - default: - $interfaceItem['status'] = ''; - break; - } - $interfaceItem['ipaddr'] = empty($ifinfo['ipaddr']) ? "" : $ifinfo['ipaddr']; - $interfaceItem['media'] = empty($ifinfo['media']) ? "" : $ifinfo['media']; - $result[] = $interfaceItem; - } - return $result; -} diff --git a/src/www/widgets/include/interface_list.inc b/src/www/widgets/include/interface_list.inc index 28e4836b0..323fddafe 100644 --- a/src/www/widgets/include/interface_list.inc +++ b/src/www/widgets/include/interface_list.inc @@ -1,4 +1,4 @@ - +
$ifname): - $listed = in_array($ifdescr, $pconfig['interfaceslistfilter']); + foreach ($interfaces as $ident => $ifname): + $listed = in_array($ident, $pconfig['interfaceslistfilter']); $listed = !empty($pconfig['interfaceslistinvert']) ? $listed : !$listed; if (!$listed) { continue; } - $ifinfo = $ifsinfo[$ifdescr]; ?> - - - - - + + list($primary4,, $bits4) = interfaces_primary_address($ident); + list($primary6,, $bits6) = interfaces_primary_address6($ident); +?> + + + + + diff --git a/src/www/widgets/widgets/interface_statistics.widget.php b/src/www/widgets/widgets/interface_statistics.widget.php index 181fbbed4..58cdf3320 100644 --- a/src/www/widgets/widgets/interface_statistics.widget.php +++ b/src/www/widgets/widgets/interface_statistics.widget.php @@ -71,25 +71,41 @@ $ifvalues = array( ?> -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - ' : '' ?> - + + ' : '' ?> +
+
$iflabel): ?>