diff --git a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/DiagnosticsController.php b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/DiagnosticsController.php
index a92a3d8e0..76f0cf6a2 100644
--- a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/DiagnosticsController.php
+++ b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/DiagnosticsController.php
@@ -41,11 +41,27 @@ use OPNsense\Core\Config;
*/
class DiagnosticsController extends ApiControllerBase
{
+ private $allifnames = [];
+
+ public function initialize()
+ {
+ parent::initialize();
+ foreach (Config::getInstance()->object()->interfaces->children() as $key => $node) {
+ $this->allifnames[(string)$node->if] = !empty((string)$node->descr) ? (string)$node->descr : $key;
+ }
+ }
+
+ public function getIfDesc($ifname)
+ {
+ return !empty($this->allifnames[$ifname]) ? $this->allifnames[$ifname] : '';
+ }
+
private function getInformation(string $daemon, string $name, string $format): array
{
- $backend = new Backend();
- $response = $backend->configdRun("quagga diagnostics " . $daemon . "_" . $name . ($format === "json" ? "_json" : ""));
- return array("response" => ($format === "json" ? json_decode($response) : $response));
+ $response = (new Backend())->configdRun(
+ "quagga diagnostics " . $daemon . "_" . $name . ($format === "json" ? "_json" : "")
+ );
+ return ["response" => ($format === "json" ? json_decode($response ?? '', true) : $response)];
}
public function generalrunningconfigAction(): array
@@ -53,6 +69,9 @@ class DiagnosticsController extends ApiControllerBase
return $this->getInformation("general", "running-config", "plain");
}
+ /**
+ * XXX: unused, deprecate in next major?
+ */
public function generalrouteAction($format = "json"): array
{
$routes4 = $this->getInformation("general", "route4", $format)['response'];
@@ -69,11 +88,45 @@ class DiagnosticsController extends ApiControllerBase
return $this->getInformation("general", "route4", $format);
}
+ public function searchGeneralroute4Action(): array
+ {
+ $records = [];
+ foreach($this->getInformation("general", "route4", "json")['response'] as $routes) {
+ foreach ($routes as $route) {
+ foreach ($route['nexthops'] as $nexthop) {
+ $nexthop = array_merge($route, $nexthop);
+ unset($nexthop['nexthops']);
+ $nexthop['via'] = !empty($nexthop['ip']) ? $nexthop['ip'] : 'Directly Attached';
+ $nexthop['interfaceDescr'] = $this->getIfDesc($nexthop['interfaceName'] ?? '');
+ $records[] = $nexthop;
+ }
+ }
+ }
+ return $this->searchRecordsetBase($records);
+ }
+
public function generalroute6Action($format = "json"): array
{
return $this->getInformation("general", "route6", $format);
}
+ public function searchGeneralroute6Action(): array
+ {
+ $records = [];
+ foreach($this->getInformation("general", "route6", "json")['response'] as $routes) {
+ foreach ($routes as $route) {
+ foreach ($route['nexthops'] as $nexthop) {
+ $nexthop = array_merge($route, $nexthop);
+ unset($nexthop['nexthops']);
+ $nexthop['via'] = !empty($nexthop['ip']) ? $nexthop['ip'] : 'Directly Attached';
+ $nexthop['interfaceDescr'] = $this->getIfDesc($nexthop['interfaceName'] ?? '');
+ $records[] = $nexthop;
+ }
+ }
+ }
+ return $this->searchRecordsetBase($records);
+ }
+
public function bgprouteAction($format = "json"): array
{
return $this->getInformation("bgp", "route", $format);
@@ -84,11 +137,83 @@ class DiagnosticsController extends ApiControllerBase
return $this->getInformation("bgp", "route4", $format);
}
+ public function searchBgproute4Action(): array
+ {
+ $records = [];
+ $payload = $this->getInformation("bgp", "route4", "json")['response'];
+ $baserecord = [];
+ foreach ($payload as $key => $value) {
+ if (!is_array($value)) {
+ $baserecord[$key] = $value;
+ }
+ }
+ if (!empty($payload['routes'])) {
+ foreach ($payload['routes'] as $routes) {
+ foreach ($routes as $route) {
+ foreach ($route['nexthops'] as $nexthop) {
+ $nexthop = array_merge($route, $nexthop);
+ unset($nexthop['nexthops']);
+ $nexthop['internal'] = !empty($nexthop['pathFrom']) && $nexthop['pathFrom'] == 'internal';
+ $nexthop['path'] = !empty($nexthop['path']) ? $nexthop['path'] : 'Internal';
+ $records[] = array_merge($baserecord, $nexthop);
+ }
+ }
+ }
+ }
+ $result = $this->searchRecordsetBase($records);
+ if (!empty($baserecord)) {
+ $result['subtitle'] = sprintf(
+ '%s : %s , %s : %s',
+ gettext('routerId'),
+ $baserecord['routerId'],
+ gettext('localAS'),
+ $baserecord['localAS']
+ );
+ }
+ return $result;
+ }
+
public function bgproute6Action($format = "json"): array
{
return $this->getInformation("bgp", "route6", $format);
}
+ public function searchBgproute6Action(): array
+ {
+ $records = [];
+ $payload = $this->getInformation("bgp", "route6", "json")['response'];
+ $baserecord = [];
+ foreach ($payload as $key => $value) {
+ if (!is_array($value)) {
+ $baserecord[$key] = $value;
+ }
+ }
+ if (!empty($payload['routes'])) {
+ foreach ($payload['routes'] as $routes) {
+ foreach ($routes as $route) {
+ foreach ($route['nexthops'] as $nexthop) {
+ $nexthop = array_merge($route, $nexthop);
+ unset($nexthop['nexthops']);
+ $nexthop['internal'] = !empty($nexthop['pathFrom']) && $nexthop['pathFrom'] == 'internal';
+ $nexthop['path'] = !empty($nexthop['path']) ? $nexthop['path'] : 'Internal';
+ $records[] = array_merge($baserecord, $nexthop);
+ }
+ }
+ }
+ }
+ $result = $this->searchRecordsetBase($records);
+ if (!empty($baserecord)) {
+ $result['subtitle'] = sprintf(
+ '%s : %s , %s : %s',
+ gettext('routerId'),
+ $baserecord['routerId'],
+ gettext('localAS'),
+ $baserecord['localAS']
+ );
+ }
+ return $result;
+ }
+
public function bgpsummaryAction($format = "json"): array
{
return $this->getInformation("bgp", "summary", $format);
@@ -109,11 +234,50 @@ class DiagnosticsController extends ApiControllerBase
return $this->getInformation("ospf", "neighbor", $format);
}
+ public function searchOspfneighborAction(): array
+ {
+ $records = [];
+ $payload = $this->getInformation("ospf", "neighbor", "json")['response'];
+ if (!empty($payload['neighbors'])) {
+ foreach ($payload['neighbors'] as $neighborid => $neighbor) {
+ foreach ($neighbor as $item) {
+ $item['neighborid'] = $neighborid;
+ $records[] = $item;
+ }
+ }
+ }
+ return $this->searchRecordsetBase($records);
+ }
+
public function ospfrouteAction($format = "json"): array
{
return $this->getInformation("ospf", "route", $format);
}
+ public function searchOspfrouteAction(): array
+ {
+ $records = [];
+ $payload = $this->getInformation("ospf", "route", "json")['response'];
+ foreach($payload as $net => $network) {
+ if (empty($network['nexthops'])) {
+ continue;
+ }
+ foreach ($network['nexthops'] as $nexthop) {
+ $records[] = [
+ 'type' => $network['routeType'],
+ 'network' => $net,
+ 'cost' => $network['cost'],
+ 'area' => $network['area'] ?? '',
+ 'via' => !empty($nexthop['via']) ? $nexthop['ip'] : 'Directly Attached',
+ 'viainterface' => !empty($nexthop['via']) ? $nexthop['via'] : $nexthop['directly attached to'],
+ 'viainterfaceDescr' => $this->getIfDesc($nexthop['via'] ?? $nexthop['directly attached to']),
+ ];
+ }
+ }
+
+ return $this->searchRecordsetBase($records);
+ }
+
public function ospfdatabaseAction($format = "json"): array
{
return $this->getInformation("ospf", "database", $format);
diff --git a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/DiagnosticsController.php b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/DiagnosticsController.php
index 4b7431184..923e0d3ed 100644
--- a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/DiagnosticsController.php
+++ b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/DiagnosticsController.php
@@ -29,12 +29,71 @@ class DiagnosticsController extends \OPNsense\Base\IndexController
{
public function bgpAction()
{
- $this->view->diagnosticsForm = $this->getForm("diagnostics");
- $this->view->pick('OPNsense/Quagga/diagnosticsbgp');
+ $this->view->tabs = [
+ [
+ 'name' => 'routing4',
+ 'endpoint' => '/api/quagga/diagnostics/search_bgproute4',
+ 'tabhead' => "IPv4 " . gettext('Routing Table'),
+ 'type' => 'bgproutetable'
+ ],
+ [
+ 'name' => 'routing6',
+ 'endpoint' => '/api/quagga/diagnostics/search_bgproute6',
+ 'tabhead' => "IPv6 " . gettext('Routing Table'),
+ 'type' => 'bgproutetable'
+ ],
+ [
+ 'name' => 'neighbors',
+ 'endpoint' => '/api/quagga/diagnostics/bgpneighbors',
+ 'tabhead' => gettext('Neighbors'),
+ 'type' => 'tree'
+ ],
+ [
+ 'name' => 'summary',
+ 'endpoint' => '/api/quagga/diagnostics/bgpsummary',
+ 'tabhead' => gettext('Summary'),
+ 'type' => 'tree'
+ ]
+ ];
+ $this->view->default_tab = 'routing4';
+ $this->view->pick('OPNsense/Quagga/diagnostics');
}
public function ospfAction()
{
- $this->view->pick('OPNsense/Quagga/diagnosticsospf');
+ $this->view->tabs = [
+ [
+ 'name' => 'overview',
+ 'endpoint' => '/api/quagga/diagnostics/ospfoverview',
+ 'tabhead' => gettext('Overview'),
+ 'type' => 'tree'
+ ],
+ [
+ 'name' => 'routing',
+ 'endpoint' => '/api/quagga/diagnostics/search_ospfroute',
+ 'tabhead' => gettext('Routing Table'),
+ 'type' => 'ospfroutetable'
+ ],
+ [
+ 'name' => 'database',
+ 'endpoint' => '/api/quagga/diagnostics/ospfdatabase/plain',
+ 'tabhead' => gettext('Database'),
+ 'type' => 'text'
+ ],
+ [
+ 'name' => 'neighbors',
+ 'endpoint' => '/api/quagga/diagnostics/search_ospfneighbor',
+ 'tabhead' => gettext('Neighbors'),
+ 'type' => 'ospfneighbors'
+ ],
+ [
+ 'name' => 'interfaces',
+ 'endpoint' => '/api/quagga/diagnostics/ospfinterface',
+ 'tabhead' => gettext('Interfaces'),
+ 'type' => 'tree'
+ ]
+ ];
+ $this->view->default_tab = 'routing';
+ $this->view->pick('OPNsense/Quagga/diagnostics');
}
public function ospfv3Action()
{
@@ -42,6 +101,27 @@ class DiagnosticsController extends \OPNsense\Base\IndexController
}
public function generalAction()
{
- $this->view->pick('OPNsense/Quagga/diagnosticsgeneral');
+ $this->view->tabs = [
+ [
+ 'name' => 'routing4',
+ 'endpoint' => '/api/quagga/diagnostics/search_generalroute4',
+ 'tabhead' => gettext('IPv4 Routes'),
+ 'type' => 'generalroutetable'
+ ],
+ [
+ 'name' => 'routing6',
+ 'endpoint' => '/api/quagga/diagnostics/search_generalroute6',
+ 'tabhead' => gettext('IPv6 Routes'),
+ 'type' => 'generalroutetable'
+ ],
+ [
+ 'name' => 'runningconfig',
+ 'endpoint' => '/api/quagga/diagnostics/generalrunningconfig/plain',
+ 'tabhead' => gettext('Running Configuration'),
+ 'type' => 'text'
+ ]
+ ];
+ $this->view->default_tab = 'routing4';
+ $this->view->pick('OPNsense/Quagga/diagnostics');
}
}
diff --git a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/diagnostics.xml b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/diagnostics.xml
deleted file mode 100644
index 0c0fff293..000000000
--- a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/diagnostics.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnostics.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnostics.volt
new file mode 100644
index 000000000..247317359
--- /dev/null
+++ b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnostics.volt
@@ -0,0 +1,341 @@
+{#
+
+OPNsense® is Copyright © 2014 – 2023 by Deciso B.V.
+Copyright (C) 2023 Marc Bartelt
+Copyright (C) 2017 Fabian Franz
+Copyright (C) 2017 Michael Muenz
+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.
+
+#}
+
+
+
+
+
+
+
+
+
+
+
+ {% for tab in tabs %}
+ -
+
+ {{ tab['tabhead'] }}
+
+
+ {% endfor %}
+
+
+
+ {% for tab in tabs %}
+
+ {% switch tab['type'] %}
+ {% case 'generalroutetable' %}
+
+
+
+
+ | {{ lang._('Code') }} |
+ {{ lang._('Selected') }} |
+ {{ lang._('Installed') }} |
+ {{ lang._('Network') }} |
+ {{ lang._('Administrative Distance') }} |
+ {{ lang._('Metric') }} |
+ {{ lang._('Interface') }} |
+ {{ lang._('Interface name') }} |
+ {{ lang._('Via') }} |
+ {{ lang._('Time') }} |
+
+
+
+
+
+
+ {% break %}
+ {% case 'bgproutetable' %}
+
+
+
+
+ | {{ lang._('Valid') }} |
+ {{ lang._('Best') }} |
+ {{ lang._('Internal') }} |
+ {{ lang._('Network') }} |
+ {{ lang._('Next Hop') }} |
+ {{ lang._('Metric') }} |
+ {{ lang._('LocPrf') }} |
+ {{ lang._('Weight') }} |
+ {{ lang._('Path') }} |
+ {{ lang._('Origin') }} |
+
+
+
+
+
+
+ {% break %}
+ {% case 'ospfroutetable' %}
+
+
+
+
+ | {{ lang._('Type') }} |
+ {{ lang._('Network') }} |
+ {{ lang._('Cost') }} |
+ {{ lang._('Area') }} |
+ {{ lang._('Via') }} |
+ {{ lang._('Via interface') }} |
+ {{ lang._('Via interface name') }} |
+
+
+
+
+
+
+ {% break %}
+ {% case 'ospfneighbors' %}
+
+
+
+
+ | {{ lang._('Neighbor ID') }} |
+ {{ lang._('Priority') }} |
+ {{ lang._('State') }} |
+ {{ lang._('Dead Time') }} [ms] |
+ {{ lang._('Address') }} |
+ {{ lang._('Interface') }} |
+ {{ lang._('Retransmit Counter') }} |
+ {{ lang._('Request Counter') }} |
+ {{ lang._('DB Summary Counter') }} |
+
+
+
+
+
+
+ {% break %}
+ {% case 'tree' %}
+
+
+
+
+ {% break %}
+ {% case 'text' %}
+
+ {% break %}
+ {% endswitch %}
+
+ {% endfor %}
+
diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt
deleted file mode 100644
index b0e196ce0..000000000
--- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt
+++ /dev/null
@@ -1,143 +0,0 @@
-{#
-
-OPNsense® is Copyright © 2014 – 2017 by Deciso B.V.
-Copyright (C) 2017 Fabian Franz
-Copyright (C) 2017 Michael Muenz
-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.
-
-#}
-
-{#
-{{ partial("layout_partials/base_form",['fields':diagnosticsForm,'id':'frm_diagnostics_settings'])}}
-#}
-
-
-
-
-
-
-
-
-
-
diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsgeneral.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsgeneral.volt
deleted file mode 100644
index 16c147019..000000000
--- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsgeneral.volt
+++ /dev/null
@@ -1,142 +0,0 @@
-{#
-
-OPNsense® is Copyright © 2014 – 2017 by Deciso B.V.
-Copyright (C) 2017 Fabian Franz
-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.
-
-#}
-
-
-
-
-
-
-
-
-
diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospf.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospf.volt
deleted file mode 100644
index 1aa5e0857..000000000
--- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospf.volt
+++ /dev/null
@@ -1,475 +0,0 @@
-{#
-
-OPNsense® is Copyright © 2014 – 2017 by Deciso B.V.
-Copyright (C) 2017 Fabian Franz
-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.
-
-#}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/net/frr/src/opnsense/scripts/frr/legacy-diagnostics.py b/net/frr/src/opnsense/scripts/frr/legacy-diagnostics.py
index 73cb70d5e..1567ace98 100755
--- a/net/frr/src/opnsense/scripts/frr/legacy-diagnostics.py
+++ b/net/frr/src/opnsense/scripts/frr/legacy-diagnostics.py
@@ -404,7 +404,8 @@ class OSPFv3(Daemon):
self.myre.search(r'SPF algorithm last executed (.*)', line) or \
self.myre.search(r'Last SPF duration (.*)', line) or \
self.myre.search(r'SPF last executed (.*)', line) or \
- self.myre.search(r'Number of Area scoped LSAs is (.*)', line):
+ self.myre.search(r'Number of Area scoped LSAs is (.*)', line) or \
+ self.myre.search(r'Adjacency changes are logged', line):
# skip these lines
pass
else:
diff --git a/net/frr/src/opnsense/www/js/quagga/diagnostics_utils.js b/net/frr/src/opnsense/www/js/quagga/diagnostics_utils.js
new file mode 100644
index 000000000..3d69d2cd9
--- /dev/null
+++ b/net/frr/src/opnsense/www/js/quagga/diagnostics_utils.js
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2015-2022 Deciso B.V.
+ * Copyright (C) 2023 Marc Bartelt
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * shared options for bootgrid tables
+ */
+let gridopt = {
+ formatters: {
+ boolean: function(column, row) {
+ if (row[column.id]) {
+ return '';
+ } else {
+ return '';
+ }
+ },
+ ospf_route_type: function(column, row) {
+ let result = '';
+
+ row[column.id].split(' ').forEach(function(routeType) {
+ let translatedRouteType = translateOSPFTerm(routeType)
+
+ if (translatedRouteType === routeType) {
+ result += routeType;
+ } else {
+ result += '' + routeType + '';
+ }
+
+ result += ' ';
+ });
+
+ return result;
+ },
+ general_route_code: function(column, row) {
+ let result = row.code;
+ let protocol = translateZebraCode(row.code);
+
+ if(typeof(protocol) !== 'string') result = '' + protocol['short'] + '';
+ if(row.selected) result += ' >';
+ if(row.installed) result += ' *';
+
+ return result;
+ }
+ }
+};
+
+/**
+ * zebra route codes - translation table
+ */
+function translateZebraCode(data) {
+ let tr = [];
+
+ // routing table tab
+ tr['kernel'] = {short: 'K', long: 'Kernel'};
+ tr['connected'] = {short: 'C', long: 'Connected'};
+ tr['bgp'] = {short: 'B', long: 'BGP'};
+ tr['ospf'] = {short: 'O', long: 'OSPF'};
+ tr['ospf6'] = {short: 'O', long: 'OSPFv3'};
+
+ return ((data in tr) ? tr[data] : data);
+}
+
+
+
+/**
+ * OSPF terms and abbreviations - translation table
+ **/
+function translateOSPFTerm(data) {
+ let tr = [];
+
+ // routing table tab
+ tr['N'] = 'Network';
+ tr['R'] = 'Router';
+ tr['IA'] = 'OSPF inter area';
+ tr['N1'] = 'OSPF NSSA external type 1';
+ tr['N2'] = 'OSPF NSSA external type 2';
+ tr['E1'] = 'OSPF external type 1';
+ tr['E2'] = 'OSPF external type 2';
+
+ return ((data in tr) ? tr[data] : data);
+}
+
+
+/**
+ * tree view: resize widget on window resize
+ */
+function resizeTreeWidget() {
+ let new_height = $(".page-foot").offset().top -
+ ($(".page-content-head").offset().top + $(".page-content-head").height()) - 160;
+ $(".treewidget").height(new_height);
+ $(".treewidget").css('max-height', new_height + 'px');
+}
+
+/**
+ * tree view: delayed live-search
+ */
+let apply_tree_search_timer = null;
+function treeSearchKeyUp() {
+ let sender = $(this);
+
+ clearTimeout(apply_tree_search_timer);
+ apply_tree_search_timer = setTimeout(function() {
+ let searchTerm = sender.val().toLowerCase();
+ let target = $("#"+sender.attr('for'));
+ let tree = target.tree("getTree");
+ let selected = [];
+ if (tree !== null) {
+ tree.iterate((node) => {
+ let matched = false;
+ if (searchTerm !== "") {
+ matched = node.name.toLowerCase().includes(searchTerm);
+ if (!matched && typeof node.value === 'string') {
+ matched = node.value.toLowerCase().includes(searchTerm);
+ }
+ }
+ node["selected"] = matched;
+
+ if (matched) {
+ selected.push(node);
+ if (node.isFolder()) {
+ node.is_open = true;
+ }
+ let parent = node.parent;
+ while (parent) {
+ parent.is_open = true;
+ parent = parent.parent;
+ }
+ } else if (node.isFolder()) {
+ node.is_open = false;
+ }
+
+ return true;
+ });
+ target.tree("refresh");
+ if (selected.length > 0) {
+ target.tree('scrollToNode', selected[0]);
+ }
+ }
+ }, 500);
+}
+
+/**
+ * jqtree expects a list + dict type structure, transform key value store into expected output
+ * https://mbraak.github.io/jqTree/#general
+ */
+ function dict_to_tree(node, path) {
+ // some entries are lists, try use a name for the nodes in that case
+ let node_name_keys = ['name', 'interface-name'];
+ let result = [];
+ if ( path === undefined) {
+ path = "";
+ } else {
+ path = path + ".";
+ }
+ for (let key in node) {
+ if (typeof node[key] === "function") {
+ continue;
+ }
+ let item_path = path + key;
+ if (node[key] instanceof Object) {
+ let node_name = key;
+ for (let idx=0; idx < node_name_keys.length; ++idx) {
+ if (/^(0|[1-9]\d*)$/.test(node_name) && node[key][node_name_keys[idx]] !== undefined) {
+ node_name = node[key][node_name_keys[idx]];
+ break;
+ }
+ }
+ result.push({
+ name: node_name,
+ id: item_path,
+ children: dict_to_tree(node[key], item_path)
+ });
+ } else {
+ result.push({
+ name: key,
+ value: node[key],
+ id: item_path
+ });
+ }
+ }
+ return result;
+}