mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
net/frr: Diagnostics overhaul (#3484)
* net/frr: Diagnostics overhaul [2] - https://github.com/opnsense/plugins/pull/3263 o new extensible template for tabbed views o get rid of diagnostics form - was unused anyway o get rid of lodash dependency o the API output is HTML-encoded, this fixes resulting display issues o move search functionality to controller. o move bgp additional attributes inside the record set so people can show them on request (the tree functions might better to move into a core Javascript file to reduce duplication, but for now this is fine.) o add formatters in bootgrid to mimic the previous behavior o add interface names (descriptions) for easier reading in route endpoints o cleanups, move bgp routerid and localAS to subtitle and offer a "generic" hook for endpoints. remove unused elements from the volt template. --------- Co-authored-by: Marc Leuser <github@mleuser.de>
This commit is contained in:
co-authored by
Marc Leuser
parent
25a1684452
commit
39bbffb928
+167
-3
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<form>
|
||||
<field>
|
||||
<id>diagnostics.bgpneighbor</id>
|
||||
<label>BGP Neighbor</label>
|
||||
<type>text</type>
|
||||
<hint>One of the neighbor IPs</hint>
|
||||
</field>
|
||||
</form>
|
||||
@@ -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 <m.muenz@gmail.com>
|
||||
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.
|
||||
|
||||
#}
|
||||
|
||||
<script src="{{ cache_safe('/ui/js/quagga/diagnostics_utils.js') }}"></script>
|
||||
|
||||
<style>
|
||||
.searchbox {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.node-selected {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#page_frr_subtitle {
|
||||
margin-left: -2px;
|
||||
> span {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="{{ cache_safe(theme_file_or_default('/css/jqtree.css', ui_theme|default('opnsense'))) }}">
|
||||
<script src="{{ cache_safe('/ui/js/tree.jquery.min.js') }}"></script>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
$( document ).ready(function() {
|
||||
updateServiceControlUI('quagga');
|
||||
|
||||
/**
|
||||
* only display the refresh button on the currently active tab
|
||||
*/
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
$(".tab-icon").removeClass("fa-refresh");
|
||||
$("#"+e.target.id).find(".tab-icon").addClass("fa-refresh");
|
||||
});
|
||||
|
||||
/**
|
||||
* resize tree widgets on window resize
|
||||
*/
|
||||
$(window).on('resize', resizeTreeWidget);
|
||||
|
||||
/**
|
||||
* delayed search for tree widgets
|
||||
*/
|
||||
$(".tree_search").keyup(treeSearchKeyUp);
|
||||
|
||||
let all_grids = [];
|
||||
|
||||
{% for tab in tabs %}
|
||||
/**
|
||||
* register refresh event handler for {{ tab['tabhead'] }}
|
||||
*/
|
||||
$("#refresh-{{ tab['name'] }}").click(function () {
|
||||
{% switch tab['type'] %}
|
||||
{% case 'generalroutetable' %}
|
||||
{% case 'bgproutetable' %}
|
||||
{% case 'ospfroutetable' %}
|
||||
{% case 'ospfneighbors' %}
|
||||
if (all_grids["{{ tab['name'] }}"] === undefined) {
|
||||
/**
|
||||
* initialize bootgrid table for {{ tab['tabhead'] }}
|
||||
*/
|
||||
gridopt = {
|
||||
search: "{{ tab['endpoint'] }}",
|
||||
options:{
|
||||
formatters : {
|
||||
general_route_code: function(column, row){
|
||||
let protocols = {
|
||||
'kernel' : {short: 'K', long: '{{ lang._('Kernel') }}'},
|
||||
'connected': {short: 'C', long: '{{ lang._('Connected') }}'},
|
||||
'bgp': {short: 'B', long: '{{ lang._('BGP') }}'},
|
||||
'ospf': {short: 'O', long: '{{ lang._('OSPF') }}'},
|
||||
'ospf6': {short: 'O', long: '{{ lang._('OSPFv3') }}'}
|
||||
};
|
||||
let field = $("<div/>");
|
||||
if (protocols[row.protocol] !== undefined) {
|
||||
let tmp = protocols[row.protocol];
|
||||
field.append($("<abbr>").text(tmp.short).attr('title', tmp.long));
|
||||
}
|
||||
if(row.selected) {
|
||||
field.append($("<abbr>").html(">").attr('title', "{{ lang._('Selected') }}"));
|
||||
}
|
||||
if(row.installed) {
|
||||
field.append($("<abbr>").html("*").attr('title', "{{ lang._('FIB') }}"));
|
||||
}
|
||||
return field.html();
|
||||
},
|
||||
boolean: function (column, row) {
|
||||
if (row[column.id]) {
|
||||
return "<span class=\"fa fa-check\" data-value=\"1\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
} else {
|
||||
return "<span class=\"fa fa-times\" data-value=\"0\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
}
|
||||
},
|
||||
origin: function(column, row) {
|
||||
return (row[column.id] === 'incomplete' ? '<abbr title="{{ lang._('Incomplete') }}">?</abbr>' : row[column.id]);
|
||||
}
|
||||
},
|
||||
responseHandler: function (data) {
|
||||
if (data.subtitle !== undefined) {
|
||||
$("#page_frr_subtitle").empty();
|
||||
$("#page_frr_subtitle").append('<i class="fa fa-fw fa-chevron-right" aria-hidden="true"></i>');
|
||||
$("#page_frr_subtitle").append($("<span/>").text(data.subtitle));
|
||||
}
|
||||
console.log(data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
};
|
||||
all_grids["{{ tab['name'] }}"] = $("#grid-{{ tab['name'] }}").UIBootgrid(gridopt);
|
||||
all_grids["{{ tab['name'] }}"].on("loaded.rs.jquery.bootgrid", function (e) {
|
||||
$("abbr").tooltip();
|
||||
});
|
||||
} else {
|
||||
all_grids["{{ tab['name'] }}"].bootgrid('reload');
|
||||
}
|
||||
{% break %}
|
||||
{% case 'tree' %}
|
||||
ajaxGet("{{ tab['endpoint'] }}", {}, function (data, status) {
|
||||
if (status == "success") {
|
||||
let $tree = $("#tree-{{ tab['name'] }}");
|
||||
if ($("#tree-{{ tab['name'] }} > ul").length == 0) {
|
||||
$tree.tree({
|
||||
data: dict_to_tree(data['response']),
|
||||
autoOpen: false,
|
||||
dragAndDrop: false,
|
||||
selectable: false,
|
||||
closedIcon: $('<i class="fa fa-plus-square-o"></i>'),
|
||||
openedIcon: $('<i class="fa fa-minus-square-o"></i>'),
|
||||
onCreateLi: function(node, $li) {
|
||||
let n_title = $li.find('.jqtree-title');
|
||||
n_title.text(n_title.text().replace('>','\>').replace('<','\<'));
|
||||
if (node.value !== undefined) {
|
||||
$li.find('.jqtree-element').append(
|
||||
' <strong>:</strong> ' + node.value
|
||||
);
|
||||
}
|
||||
if (node.selected) {
|
||||
$li.addClass("node-selected");
|
||||
} else {
|
||||
$li.removeClass("node-selected");
|
||||
}
|
||||
}
|
||||
});
|
||||
// initial view, collapse first level if there's only one node
|
||||
if (Object.keys(data['response']).length == 1) {
|
||||
for (let key in data['response']) {
|
||||
$tree.tree('openNode', $tree.tree('getNodeById', key));
|
||||
}
|
||||
}
|
||||
//open node on label click
|
||||
$tree.bind('tree.click', function(e) {
|
||||
$tree.tree('toggle', e.node);
|
||||
});
|
||||
} else {
|
||||
let curent_state = $tree.tree('getState');
|
||||
$tree.tree('loadData', dict_to_tree(data['response']));
|
||||
$tree.tree('setState', curent_state);
|
||||
}
|
||||
}
|
||||
});
|
||||
$(window).trigger('resize');
|
||||
{% break %}
|
||||
{% case 'text' %}
|
||||
ajaxGet("{{ tab['endpoint'] }}", {}, function(data, status) {
|
||||
if (status == "success") {
|
||||
$('#text-{{ tab['name'] }}').html(data['response']);
|
||||
}
|
||||
});
|
||||
{% break %}
|
||||
{% endswitch %}
|
||||
});
|
||||
|
||||
/**
|
||||
* perform data fetch via event handler
|
||||
*/
|
||||
$("a[id='{{ tab['name'] }}_tab']").on("shown.bs.tab", function (event) {
|
||||
$("#refresh-{{ tab['name'] }}").click();
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
/**
|
||||
* add "sub title" heading
|
||||
*/
|
||||
$("header.page-content-head > div:eq(0) > ul:eq(0) > li:eq(0)").append($("<span id='page_frr_subtitle'/>"));
|
||||
|
||||
/**
|
||||
* activate the default tab
|
||||
*/
|
||||
$("a[id='{{ default_tab }}_tab']").click();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Navigation bar -->
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
{% for tab in tabs %}
|
||||
<li>
|
||||
<a data-toggle="tab" href="#{{ tab['name'] }}" id="{{tab['name']}}_tab">
|
||||
{{ tab['tabhead'] }} <span id="refresh-{{ tab['name'] }}" class="fa tab-icon fa-refresh" style="cursor: pointer"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="content-box tab-content" style="padding-bottom: 1.5em;">
|
||||
{% for tab in tabs %}
|
||||
<div id="{{ tab['name'] }}" class="tab-pane fade">
|
||||
{% switch tab['type'] %}
|
||||
{% case 'generalroutetable' %}
|
||||
<div class="col-sm-12">
|
||||
<table id="grid-{{ tab['name'] }}" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="protocol" data-searchable="false" data-formatter="general_route_code" data-sortable="false">{{ lang._('Code') }}</th>
|
||||
<th data-column-id="selected" data-searchable="false" data-visible="false" data-sortable="false">{{ lang._('Selected') }}</th>
|
||||
<th data-column-id="installed" data-searchable="false" data-visible="false" data-sortable="false">{{ lang._('Installed') }}</th>
|
||||
<th data-column-id="prefix">{{ lang._('Network') }}</th>
|
||||
<th data-column-id="distance" data-searchable="false">{{ lang._('Administrative Distance') }}</th>
|
||||
<th data-column-id="metric" data-searchable="false">{{ lang._('Metric') }}</th>
|
||||
<th data-column-id="interfaceName">{{ lang._('Interface') }}</th>
|
||||
<th data-column-id="interfaceDescr">{{ lang._('Interface name') }}</th>
|
||||
<th data-column-id="via">{{ lang._('Via') }}</th>
|
||||
<th data-column-id="uptime" data-searchable="false">{{ lang._('Time') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% break %}
|
||||
{% case 'bgproutetable' %}
|
||||
<div class="col-sm-12">
|
||||
<table id="grid-{{ tab['name'] }}" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="valid" data-searchable="false" data-formatter="boolean" data-sortable="false">{{ lang._('Valid') }}</th>
|
||||
<th data-column-id="bestpath" data-searchable="false" data-formatter="boolean" data-sortable="false">{{ lang._('Best') }}</th>
|
||||
<th data-column-id="internal" data-searchable="false" data-formatter="boolean" data-sortable="false">{{ lang._('Internal') }}</th>
|
||||
<th data-column-id="network" data-width="15%">{{ lang._('Network') }}</th>
|
||||
<th data-column-id="ip" data-width="25%">{{ lang._('Next Hop') }}</th>
|
||||
<th data-column-id="metric" data-searchable="false">{{ lang._('Metric') }}</th>
|
||||
<th data-column-id="locPrf" data-searchable="false">{{ lang._('LocPrf') }}</th>
|
||||
<th data-column-id="weight" data-searchable="false">{{ lang._('Weight') }}</th>
|
||||
<th data-column-id="path" data-width="21%">{{ lang._('Path') }}</th>
|
||||
<th data-column-id="origin" data-width="10%" data-formatter="origin">{{ lang._('Origin') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% break %}
|
||||
{% case 'ospfroutetable' %}
|
||||
<div class="col-sm-12">
|
||||
<table id="grid-{{ tab['name'] }}" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="type" data-searchable="false" data-formatter="ospf_route_type" data-sortable="false">{{ lang._('Type') }}</th>
|
||||
<th data-column-id="network">{{ lang._('Network') }}</th>
|
||||
<th data-column-id="cost" data-searchable="false">{{ lang._('Cost') }}</th>
|
||||
<th data-column-id="area">{{ lang._('Area') }}</th>
|
||||
<th data-column-id="via">{{ lang._('Via') }}</th>
|
||||
<th data-column-id="viainterface">{{ lang._('Via interface') }}</th>
|
||||
<th data-column-id="viainterfaceDescr">{{ lang._('Via interface name') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% break %}
|
||||
{% case 'ospfneighbors' %}
|
||||
<div class="col-sm-12">
|
||||
<table id="grid-{{ tab['name'] }}" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="neighborid">{{ lang._('Neighbor ID') }}</th>
|
||||
<th data-column-id="priority" data-searchable="false">{{ lang._('Priority') }}</th>
|
||||
<th data-column-id="state">{{ lang._('State') }}</th>
|
||||
<th data-column-id="deadTimeMsecs" data-searchable="false">{{ lang._('Dead Time') }} [ms]</th>
|
||||
<th data-column-id="address">{{ lang._('Address') }}</th>
|
||||
<th data-column-id="ifaceName">{{ lang._('Interface') }}</th>
|
||||
<th data-column-id="retransmitCounter" data-searchable="false">{{ lang._('Retransmit Counter') }}</th>
|
||||
<th data-column-id="requestCounter" data-searchable="false">{{ lang._('Request Counter') }}</th>
|
||||
<th data-column-id="dbSummaryCounter" data-searchable="false">{{ lang._('DB Summary Counter') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% break %}
|
||||
{% case 'tree' %}
|
||||
<div class="searchbox">
|
||||
<input
|
||||
id="search-{{ tab['name'] }}"
|
||||
type="text"
|
||||
for="tree-{{tab['name']}}"
|
||||
class="tree_search"
|
||||
placeholder="{{ lang._('search') }}"
|
||||
></input>
|
||||
</div>
|
||||
<div class="treewidget" style="padding: 8px; overflow-y: scroll; height:400px;" id="tree-{{ tab['name'] }}"></div>
|
||||
{% break %}
|
||||
{% case 'text' %}
|
||||
<pre id="text-{{ tab['name'] }}"></pre>
|
||||
{% break %}
|
||||
{% endswitch %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -1,143 +0,0 @@
|
||||
{#
|
||||
|
||||
OPNsense® is Copyright © 2014 – 2017 by Deciso B.V.
|
||||
Copyright (C) 2017 Fabian Franz
|
||||
Copyright (C) 2017 Michael Muenz <m.muenz@gmail.com>
|
||||
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'])}}
|
||||
#}
|
||||
|
||||
<script type="text/x-template" id="routestpl">
|
||||
<h2>{{ lang._('Table Version') }}: <%= tableVersion %></h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="valid" data-type="boolean" data-formatter="boolean" data-width="5%">{{ lang._('Valid') }}</th>
|
||||
<th data-column-id="best" data-type="boolean" data-formatter="boolean" data-width="5%">{{ lang._('Best') }}</th>
|
||||
<th data-column-id="internal" data-type="boolean" data-formatter="boolean" data-width="6%">{{ lang._('Internal') }}</th>
|
||||
<th data-column-id="network" data-type="string" data-width="21%">{{ lang._('Network') }}</th>
|
||||
<th data-column-id="nexthop" data-type="string" data-width="21%">{{ lang._('Next Hop') }}</th>
|
||||
<th data-column-id="metric" data-type="numeric" data-width="5%">{{ lang._('Metric') }}</th>
|
||||
<th data-column-id="locprf" data-type="numeric" data-width="5%">{{ lang._('LocPrf') }}</th>
|
||||
<th data-column-id="weight" data-type="numeric" data-width="6%">{{ lang._('Weight') }}</th>
|
||||
<th data-column-id="path" data-type="string" data-width="16%">{{ lang._('Path') }}</th>
|
||||
<th data-column-id="origin" data-type="string" data-formatter="origin" data-width="10%">{{ lang._('Origin') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% _.forEach(routes, function(route_array, network) { %>
|
||||
<% _.forEach(route_array, function(route) { %>
|
||||
<% _.forEach(route['nexthops'], function(nexthop) { %>
|
||||
<tr>
|
||||
<td><%= (typeof(route['valid']) != "undefined" && route['valid']) %></td>
|
||||
<td><%= (typeof(route['bestpath']) != "undefined" && route['bestpath']) %></td>
|
||||
<td><%= (typeof(route['pathFrom']) != "undefined" && route['pathFrom'] == 'internal') %></td>
|
||||
<td><%= network %></td>
|
||||
<td><%= nexthop['ip'] %></td>
|
||||
<td><%= route['metric'] %></td>
|
||||
<td><%= route['locPrf'] %></td>
|
||||
<td><%= route['weight'] %></td>
|
||||
<td><%= (route['path'] == "" ? "{{ lang._('Internal') }}" : route['path']) %></td>
|
||||
<td><%= route['origin'] %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="/ui/js/quagga/lodash.js"></script>
|
||||
<script type="text/javascript">
|
||||
let dataconverters = {
|
||||
boolean: {
|
||||
from: function (value) { return (value == 'true') || (value == true); },
|
||||
to: function (value) { return value; }
|
||||
}
|
||||
}
|
||||
|
||||
let dataformatters = {
|
||||
boolean: function (column, row) {
|
||||
if (row[column.id]) {
|
||||
return "<span class=\"fa fa-check\" data-value=\"1\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
} else {
|
||||
return "<span class=\"fa fa-times\" data-value=\"0\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
}
|
||||
},
|
||||
origin: function(column, row) {
|
||||
return (row[column.id] === 'incomplete' ? '<abbr title="{{ lang._('Incomplete') }}">?</abbr>' : row[column.id]);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
updateServiceControlUI('quagga');
|
||||
|
||||
ajaxCall(url="/api/quagga/diagnostics/bgproute4", sendData={}, callback=function(data, status) {
|
||||
content = _.template($('#routestpl').html())(data['response']);
|
||||
$('#routing').html(content);
|
||||
$('#routing table').bootgrid({
|
||||
formatters: dataformatters
|
||||
});
|
||||
});
|
||||
|
||||
ajaxCall(url="/api/quagga/diagnostics/bgproute6", sendData={}, callback=function(data, status) {
|
||||
content = _.template($('#routestpl').html())(data['response']);
|
||||
$('#routing6').html(content);
|
||||
$('#routing6 table').bootgrid({
|
||||
formatters: dataformatters
|
||||
});
|
||||
});
|
||||
|
||||
ajaxCall(url="/api/quagga/diagnostics/bgpneighbors/plain", sendData={}, callback=function(data, status) {
|
||||
$('#neighborscontent').text(data['response']);
|
||||
});
|
||||
|
||||
ajaxCall(url="/api/quagga/diagnostics/bgpsummary/plain", sendData={}, callback=function(data, status) {
|
||||
$("#summarycontent").text(data['response']);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Navigation bar -->
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" href="#routing">IPv4 {{ lang._('Routing Table') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#routing6">IPv6 {{ lang._('Routing Table') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#neighbors">{{ lang._('Neighbors') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#summary">{{ lang._('Summary') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box tab-content">
|
||||
<div id="routing" class="tab-pane fade in active"></div>
|
||||
<div id="routing6" class="tab-pane fade in"></div>
|
||||
<div id="neighbors" class="tab-pane fade in">
|
||||
<pre id="neighborscontent"></pre>
|
||||
</div>
|
||||
<div id="summary" class="tab-pane fade in">
|
||||
<pre id="summarycontent"></pre>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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.
|
||||
|
||||
#}
|
||||
<script src="/ui/js/quagga/lodash.js"></script>
|
||||
<script type="text/x-template" id="routestpl">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="code" data-type="string" data-formatter="code">{{ lang._('Code') }}</th>
|
||||
<th data-column-id="selected" data-type="boolean" data-visible="false">{{ lang._('Selected') }}</th>
|
||||
<th data-column-id="installed" data-type="boolean" data-visible="false">{{ lang._('Installed') }}</th>
|
||||
<th data-column-id="network" data-type="string">{{ lang._('Network') }}</th>
|
||||
<th data-column-id="ad" data-type="numeric">{{ lang._('Administrative Distance') }}</th>
|
||||
<th data-column-id="metric" data-type="numeric">{{ lang._('Metric') }}</th>
|
||||
<th data-column-id="interface" data-type="string">{{ lang._('Interface') }}</th>
|
||||
<th data-column-id="via" data-type="string">{{ lang._('Via') }}</th>
|
||||
<th data-column-id="time" data-type="string">{{ lang._('Time') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% _.forEach(routes, function(route_array, network) { %>
|
||||
<% _.forEach(route_array, function(route) { %>
|
||||
<% _.forEach(route['nexthops'], function(nexthop) { %>
|
||||
<tr>
|
||||
<td><%= route['protocol'] %></td>
|
||||
<td><%= (typeof(route['selected']) != "undefined" && route['selected']) %></td>
|
||||
<td><%= (typeof(route['installed']) != "undefined" && route['installed']) %></td>
|
||||
<td><%= network %></td>
|
||||
<td><%= route['distance'] %></td>
|
||||
<td><%= route['metric'] %></td>
|
||||
<td><%= nexthop['interfaceName'] %></td>
|
||||
<td><%= (typeof(nexthop['ip']) != "undefined" ? nexthop['ip'] : '{{ lang._('Directly Attached') }}') %></td>
|
||||
<td><%= route['uptime'] %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function translateProtocol(data) {
|
||||
let tr = [];
|
||||
|
||||
// routing table tab
|
||||
tr['kernel'] = {short: 'K', long: '{{ lang._('Kernel') }}'};
|
||||
tr['connected'] = {short: 'C', long: '{{ lang._('Connected') }}'};
|
||||
tr['bgp'] = {short: 'B', long: '{{ lang._('BGP') }}'};
|
||||
tr['ospf'] = {short: 'O', long: '{{ lang._('OSPF') }}'};
|
||||
tr['ospf6'] = {short: 'O', long: '{{ lang._('OSPFv3') }}'};
|
||||
|
||||
return _.has(tr,data) ? tr[data] : data;
|
||||
}
|
||||
|
||||
let dataconverters = {
|
||||
boolean: {
|
||||
from: function (value) { return (value == 'true') || (value == true); },
|
||||
to: function (value) { return value; }
|
||||
}
|
||||
}
|
||||
|
||||
let dataformatters = {
|
||||
code: function(column, row) {
|
||||
let result = row.code;
|
||||
let protocol = translateProtocol(row.code);
|
||||
|
||||
if(typeof(protocol) != "string") result = '<abbr title="' + protocol['long'] + '">' + protocol['short'] + '</abbr> ';
|
||||
if(row.selected) result += '<abbr title="{{ lang._('Selected') }}">></abbr> ';
|
||||
if(row.installed) result += '<abbr title="{{ lang._('FIB') }}">*</abbr>';
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
updateServiceControlUI('quagga');
|
||||
|
||||
ajaxCall(url="/api/quagga/diagnostics/generalroute", sendData={}, callback=function(data, status) {
|
||||
let content = _.template($('#routestpl').html())({
|
||||
routes: data['response']['ipv4']
|
||||
});
|
||||
$('#routing').html(content);
|
||||
$('#routing table').bootgrid({
|
||||
converters: dataconverters,
|
||||
formatters: dataformatters
|
||||
});
|
||||
content = _.template($('#routestpl').html())({
|
||||
routes: data['response']['ipv6']
|
||||
});
|
||||
$('#routing6').html(content);
|
||||
$('#routing6 table').bootgrid({
|
||||
converters: dataconverters,
|
||||
formatters: dataformatters
|
||||
});
|
||||
});
|
||||
|
||||
ajaxCall(url="/api/quagga/diagnostics/generalrunningconfig", sendData={}, callback=function(data, status) {
|
||||
$("#runningconfig").text(data['response']);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Navigation bar -->
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" href="#routing">{{ lang._('IPv4 Routes') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#routing6">{{ lang._('IPv6 Routes') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#showrun">{{ lang._('Running Configuration') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box tab-content">
|
||||
<div id="routing" class="tab-pane fade in active"></div>
|
||||
<div id="routing6" class="tab-pane fade in"></div>
|
||||
<div id="showrun" class="tab-pane fade in">
|
||||
<pre id="runningconfig"></pre>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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.
|
||||
|
||||
#}
|
||||
|
||||
<script type="text/x-template" id="overviewtpl">
|
||||
<h2>{{ lang._('General') }}</h2>
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ lang._('RFC2328 Conform') }}</td>
|
||||
<td><%= checkmark(rfc2328Conform) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('ASBR') }}</td>
|
||||
<td><%= checkmark(typeof asbrRouter != 'undefined' && asbrRouter == "injectingExternalRoutingInformation") %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Router ID') }}</td>
|
||||
<td><%= routerId %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('RFC1583 Compatibility') }}</td>
|
||||
<td><%= checkmark(typeof rfc1583Compatibility != "undefined" && rfc1583Compatibility) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Opaque Capability') }}</td>
|
||||
<td><%= checkmark(typeof opaqueCapable != "undefined" && opaqueCapable) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Initial SPF Scheduling Delay') }}</td>
|
||||
<td><%= spfScheduleDelayMsecs %> {{ lang._('Milliseconds') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Minimum Hold Time') }}</td>
|
||||
<td><%= holdtimeMinMsecs %> {{ lang._('Milliseconds') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Maximum Hold Time') }}</td>
|
||||
<td><%= holdtimeMaxMsecs %> {{ lang._('Milliseconds') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Current Hold Time Multipier') }}</td>
|
||||
<td><%= holdtimeMultplier %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Refresh Timer') }}</td>
|
||||
<td><%= refreshTimerMsecs %> {{ lang._('Milliseconds') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Areas Attached Count') }}</td>
|
||||
<td><%= attachedAreaCounter %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>{{ lang._('Link State Area') }}</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ lang._('Count') }}</th>
|
||||
<th>{{ lang._('Checksum') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ lang._('External LSA') }}</td>
|
||||
<td><%= lsaExternalCounter %></td>
|
||||
<td><%= lsaExternalChecksum %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Opaque AS LSA') }}</td>
|
||||
<td><%= lsaAsopaqueCounter %></td>
|
||||
<td><%= lsaAsOpaqueChecksum %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<% if (areas) { %>
|
||||
<h2>{{ lang._('Areas') }}</h2>
|
||||
<% _.forEach(areas, function(area, areaname) { %>
|
||||
<br />
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= areaname %></th>
|
||||
<th>{{ lang._('Count') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ lang._('Interfaces: Total') }}</td>
|
||||
<td><%= area['areaIfTotalCounter'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Interfaces: Active') }}</td>
|
||||
<td><%= area['areaIfActiveCounter'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Fully Adjacent Neighbor Count') }}</td>
|
||||
<td><%= area['nbrFullAdjacentCounter'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('SPF Execution Count') }}</td>
|
||||
<td><%= area['spfExecutedCounter'] %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ lang._('LSA Type') }}</th>
|
||||
<th>{{ lang._('Count') }}</th>
|
||||
<th>{{ lang._('Checksum') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ lang._('Router') }}</td>
|
||||
<td><%= area['lsaRouterNumber'] %></td>
|
||||
<td><%= area['lsaRouterChecksum'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Network') }}</td>
|
||||
<td><%= area['lsaNetworkNumber'] %></td>
|
||||
<td><%= area['lsaNetworkChecksum'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Summary') }}</td>
|
||||
<td><%= area['lsaSummaryNumber'] %></td>
|
||||
<td><%= area['lsaSummaryChecksum'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('ASBR Summary') }}</td>
|
||||
<td><%= area['lsaAsbrNumber'] %></td>
|
||||
<td><%= area['lsaAsbrChecksum'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('NSSA') }}</td>
|
||||
<td><%= area['lsaNssaNumber'] %></td>
|
||||
<td><%= area['lsaNssaChecksum'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Opaque Link') }}</td>
|
||||
<td><%= area['lsaOpaqueLinkNumber'] %></td>
|
||||
<td><%= area['lsaOpaqueLinkChecksum'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ lang._('Opaque Area') }}</td>
|
||||
<td><%= area['lsaOpaqueAreaNumber'] %></td>
|
||||
<td><%= area['lsaOpaqueAreaNumber'] %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
</script>
|
||||
<script type="text/x-template" id="databasetpl">
|
||||
<% _.each(_.keys(ospf_database), function(router_id) { %>
|
||||
<h1>{{ lang._('Router ID:')}} <%= router_id %></h1>
|
||||
<hr />
|
||||
<h2>{{ lang._('Router Link State Area') }}</h2>
|
||||
<% _.each(_.keys(ospf_database[router_id]['router_link_state_area']), function(area) { %>
|
||||
<h3>Area <%= area %></h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="linkid" data-type="string">{{ lang._('Link ID') }}</th>
|
||||
<th data-column-id="advrouter" data-type="string">{{ lang._('ADV Router') }}</th>
|
||||
<th data-column-id="age" data-type="numeric">{{ lang._('Age') }}</th>
|
||||
<th data-column-id="seqnr" data-type="string">{{ lang._('Sequence Number') }}</th>
|
||||
<th data-column-id="cksum" data-type="string">{{ lang._('Checksum') }}</th>
|
||||
<th data-column-id="linkcnt" data-type="numeric">{{ lang._('Link Count') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% _.each(ospf_database[router_id]['router_link_state_area'][area], function(entry) { %>
|
||||
<tr>
|
||||
<td><%= entry["Link ID"] %></td>
|
||||
<td><%= entry["ADV Router"] %></td>
|
||||
<td><%= entry["Age"] %></td>
|
||||
<td><%= entry["Seq#"] %></td>
|
||||
<td><%= entry["CkSum"] %></td>
|
||||
<td><%= entry["Link count"] %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% }); %>
|
||||
<h2>{{ lang._('Net Link State Area') }}</h2>
|
||||
<% _.each(_.keys(ospf_database[router_id]['net_link_state_area']), function(area) { %>
|
||||
<h3>{{ lang._('Area:') }} <%= area %></h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="linkid" data-type="string">{{ lang._('Link ID') }}</th>
|
||||
<th data-column-id="advrouter" data-type="string">{{ lang._('ADV Router') }}</th>
|
||||
<th data-column-id="age" data-type="numeric">{{ lang._('Age') }}</th>
|
||||
<th data-column-id="seqnr" data-type="string">{{ lang._('Sequence Number') }}</th>
|
||||
<th data-column-id="cksum" data-type="string">{{ lang._('Checksum') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% _.each(ospf_database[router_id]['net_link_state_area'][area], function(entry) { %>
|
||||
<tr>
|
||||
<td><%= entry["Link ID"] %></td>
|
||||
<td><%= entry["ADV Router"] %></td>
|
||||
<td><%= entry["Age"] %></td>
|
||||
<td><%= entry["Seq#"] %></td>
|
||||
<td><%= entry["CkSum"] %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% }); %>
|
||||
<h2>{{ lang._('External States') }}</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="linkid" data-type="string">{{ lang._('Link ID') }}</th>
|
||||
<th data-column-id="advrouter" data-type="string">{{ lang._('ADV Router') }}</th>
|
||||
<th data-column-id="age" data-type="numeric">{{ lang._('Age') }}</th>
|
||||
<th data-column-id="seqnr" data-type="string">{{ lang._('Sequence Number') }}</th>
|
||||
<th data-column-id="chsum" data-type="string">{{ lang._('Checksum') }}</th>
|
||||
<th data-column-id="route" data-type="string">{{ lang._('Route') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% _.each(ospf_database[router_id]['external_states'], function(entry) { %>
|
||||
<tr>
|
||||
<td><%= entry["Link ID"] %></td>
|
||||
<td><%= entry["ADV Router"] %></td>
|
||||
<td><%= entry["Age"] %></td>
|
||||
<td><%= entry["Seq#"] %></td>
|
||||
<td><%= entry["CkSum"] %></td>
|
||||
<td><%= entry["Route"] %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% }); %>
|
||||
</script>
|
||||
<script type="text/x-template" id="routestpl">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="type" data-type="string" data-formatter="route_type">{{ lang._('Type') }}</th>
|
||||
<th data-column-id="network" data-type="string">{{ lang._('Network') }}</th>
|
||||
<th data-column-id="cost" data-type="numeric">{{ lang._('Cost') }}</th>
|
||||
<th data-column-id="area" data-type="string">{{ lang._('Area') }}</th>
|
||||
<th data-column-id="via" data-type="string">{{ lang._('Via') }}</th>
|
||||
<th data-column-id="viainterface" data-type="string">{{ lang._('Via interface') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% _.forEach(routes, function(route, network) { %>
|
||||
<tr>
|
||||
<td><%= route['routeType'] %></td>
|
||||
<td><%= network %></td>
|
||||
<td><%= route['cost'] %></td>
|
||||
<td><%= route['area'] %></td>
|
||||
<td><%= (typeof route['nexthops'][0]['via'] != "undefined" ? route['nexthops'][0]['ip'] : translate('directly attached')) %></td>
|
||||
<td><%= (typeof route['nexthops'][0]['via'] != "undefined" ? route['nexthops'][0]['via'] : route['nexthops'][0]['directly attached to']) %></td>
|
||||
</tr>
|
||||
<% route['nexthops'].shift(); %>
|
||||
<% _.forEach(route['nexthops'], function(nexthop) { %>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><%= (typeof nexthop['via'] != "undefined" ? nexthop['ip'] : translate('directly attached')) %></td>
|
||||
<td><%= (typeof nexthop['via'] != "undefined" ? nexthop['via'] : nexthop['directly attached to']) %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
<script type="text/x-template" id="neighbortpl">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="neighborid" data-type="string">{{ lang._('Neighbor ID') }}</th>
|
||||
<th data-column-id="priority" data-type="numeric">{{ lang._('Priority') }}</th>
|
||||
<th data-column-id="state" data-type="string">{{ lang._('State') }}</th>
|
||||
<th data-column-id="deadtime" data-type="string">{{ lang._('Dead Time') }} [ms]</th>
|
||||
<th data-column-id="address" data-type="string">{{ lang._('Address') }}</th>
|
||||
<th data-column-id="interface" data-type="string">{{ lang._('Interface') }}</th>
|
||||
<th data-column-id="rxmtl" data-type="numeric">{{ lang._('Retransmit Counter') }}</th>
|
||||
<th data-column-id="rqstl" data-type="numeric">{{ lang._('Request Counter') }}</th>
|
||||
<th data-column-id="dbsml" data-type="numeric">{{ lang._('DB Summary Counter') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% _.forEach(neighbors, function(connections, neighborId) { %>
|
||||
<% _.forEach(connections, function(connection) { %>
|
||||
<tr>
|
||||
<td><%= neighborId %></td>
|
||||
<td><%= connection['priority'] %></td>
|
||||
<td><%= translate(connection['state']) %></td>
|
||||
<td><%= connection['deadTimeMsecs'] %></td>
|
||||
<td><%= connection['address'] %></td>
|
||||
<td><%= connection['ifaceName'] %></td>
|
||||
<td><%= connection['retransmitCounter'] %></td>
|
||||
<td><%= connection['requestCounter'] %></td>
|
||||
<td><%= connection['dbSummaryCounter'] %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
<script type="text/x-template" id="interfacetpl">
|
||||
<% _.forEach(interfaces, function(interface, interfacename) { %>
|
||||
<h2><%= interfacename %></h2>
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<% _.forEach(interface, function(propertyvalue, propertyname) { %>
|
||||
<tr>
|
||||
<td><%= translate(propertyname) %></td>
|
||||
<td>
|
||||
<% if (typeof(propertyvalue) == "boolean") { %>
|
||||
<%= checkmark(propertyvalue) %>
|
||||
<% } else { %>
|
||||
<%= translate(propertyvalue) %>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% }); %>
|
||||
</script>
|
||||
<script type="text/javascript" src="/ui/js/quagga/lodash.js"></script>
|
||||
<script type="text/javascript">
|
||||
function translate(data) {
|
||||
let tr = [];
|
||||
// routing table tab
|
||||
tr['N'] = '{{ lang._('Network') }}';
|
||||
tr['R'] = '{{ lang._('Router') }}';
|
||||
tr['IA'] = '{{ lang._('OSPF inter area') }}';
|
||||
tr['N1'] = '{{ lang._('OSPF NSSA external type 1') }}';
|
||||
tr['N2'] = '{{ lang._('OSPF NSSA external type 2') }}';
|
||||
tr['E1'] = '{{ lang._('OSPF external type 1') }}';
|
||||
tr['E2'] = '{{ lang._('OSPF external type 2') }}';
|
||||
tr['directly attached'] = '{{ lang._('Directly Attached') }}';
|
||||
|
||||
// neighbor tab
|
||||
tr['Full/DR'] = '{{ lang._('Full (Designated Router)') }}';
|
||||
|
||||
// interfaces tab
|
||||
tr['ifUp'] = '{{ lang._('Up') }}';
|
||||
tr['ifIndex'] = '{{ lang._('Index') }}';
|
||||
tr['mtuBytes'] = '{{ lang._('MTU') }} [{{ lang._('Bytes') }}]';
|
||||
tr['bandwidthMbit'] = '{{ lang._('Bandwidth') }} [Mbit/s]';
|
||||
tr['ifFlags'] = '{{ lang._('Flags') }}';
|
||||
tr['ospfEnabled'] = '{{ lang._('OSPF Enabled') }}';
|
||||
tr['ipAddress'] = '{{ lang._('Address') }}';
|
||||
tr['ipAddressPrefixlen'] = '{{ lang._('Prefix Length') }}';
|
||||
tr['ospfIfType'] = '{{ lang._('Type') }}';
|
||||
tr['localIfUsed'] = '{{ lang._('Local Interface') }}';
|
||||
tr['area'] = '{{ lang._('Area') }}';
|
||||
tr['routerId'] = '{{ lang._('Router ID') }}';
|
||||
tr['networkType'] = '{{ lang._('Network Type') }}';
|
||||
tr['cost'] = '{{ lang._('Cost') }}';
|
||||
tr['transmitDelaySecs'] = '{{ lang._('Transmit Delay') }} [s]';
|
||||
tr['state'] = '{{ lang._('State') }}';
|
||||
tr['priority'] = '{{ lang._('Priority') }}';
|
||||
tr['mcastMemberOspfAllRouters'] = '{{ lang._('Multicast') }} {{ lang._('Group') }} {{ lang._('Member') }} OSPFAllRouters';
|
||||
tr['timerMsecs'] = '{{ lang._('Hello Timer') }} [ms]';
|
||||
tr['timerDeadSecs'] = '{{ lang._('Dead Timer') }} [s]';
|
||||
tr['timerWaitSecs'] = '{{ lang._('Wait Timer') }} [s]';
|
||||
tr['timerRetransmitSecs'] = '{{ lang._('Retransmit Timer') }} [s]';
|
||||
tr['timerPassiveIface'] = '{{ lang._('Passive Interface') }}';
|
||||
tr['timerHelloInMsecs'] = '{{ lang._('Hello Due In') }} [ms]';
|
||||
tr['nbrCount'] = '{{ lang._('Neighbor Count') }}';
|
||||
tr['nbrAdjacentCount'] = '{{ lang._('Adjacent Neighbor Count') }}';
|
||||
|
||||
return _.has(tr,data) ? tr[data] : data;
|
||||
}
|
||||
|
||||
function checkmark(bin) {
|
||||
return "<i class=\"fa " + (bin ? "fa-check-square" : "fa-square") + " text-muted\"></i>";
|
||||
}
|
||||
|
||||
dataformatters = {
|
||||
route_type: function(column, row) {
|
||||
let result = ''
|
||||
|
||||
_.forEach(row.type.split(' '), function(routeType) {
|
||||
if(translate(routeType) == routeType) result += routeType;
|
||||
else result += '<abbr title="' + translate(routeType) + '">' + routeType + '</abbr>';
|
||||
result += ' ';
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
updateServiceControlUI('quagga');
|
||||
|
||||
ajaxCall(url="/api/quagga/diagnostics/ospfoverview", sendData={}, callback=function(data, status) {
|
||||
let content = _.template($('#overviewtpl').html())(data['response']);
|
||||
$('#overview').html(content);
|
||||
});
|
||||
ajaxCall(url="/api/quagga/diagnostics/ospfdatabase", sendData={}, callback=function(data, status) {
|
||||
let content = _.template($('#databasetpl').html())(data['response']);
|
||||
$('#database').html(content);
|
||||
});
|
||||
ajaxCall(url="/api/quagga/diagnostics/ospfroute", sendData={}, callback=function(data, status) {
|
||||
let content = _.template($('#routestpl').html())({
|
||||
routes: data['response']
|
||||
});
|
||||
$('#routing').html(content);
|
||||
$('#routing table').bootgrid({
|
||||
formatters: dataformatters
|
||||
});
|
||||
});
|
||||
ajaxCall(url="/api/quagga/diagnostics/ospfneighbor", sendData={}, callback=function(data, status) {
|
||||
let content = _.template($('#neighbortpl').html())(data['response']);
|
||||
$('#neighbor').html(content);
|
||||
$('#neighbor table').bootgrid({
|
||||
formatters: dataformatters
|
||||
});
|
||||
});
|
||||
ajaxCall(url="/api/quagga/diagnostics/ospfinterface", sendData={}, callback=function(data, status) {
|
||||
let content = _.template($('#interfacetpl').html())(data['response']);
|
||||
$('#interface').html(content);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Navigation bar -->
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" href="#overview">{{ lang._('Overview') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#routing">{{ lang._('Routing Table') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#database">{{ lang._('Database') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#neighbor">{{ lang._('Neighbor') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#interface">{{ lang._('Interface') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box tab-content">
|
||||
<div id="overview" class="tab-pane fade in active"></div>
|
||||
<div id="routing" class="tab-pane fade in"></div>
|
||||
<div id="database" class="tab-pane fade in"></div>
|
||||
<div id="neighbor" class="tab-pane fade in"></div>
|
||||
<div id="interface" class="tab-pane fade in"></div>
|
||||
</div>
|
||||
@@ -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:
|
||||
|
||||
@@ -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 '<span class="fa fa-fw fa-check" data-value="1" data-row-id="' + row.uuid + '"></span>';
|
||||
} 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 += '<abbr title="' + translatedRouteType + '">' + routeType + '</abbr>';
|
||||
}
|
||||
|
||||
result += ' ';
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
general_route_code: function(column, row) {
|
||||
let result = row.code;
|
||||
let protocol = translateZebraCode(row.code);
|
||||
|
||||
if(typeof(protocol) !== 'string') result = '<abbr title="' + protocol['long'] + '">' + protocol['short'] + '</abbr>';
|
||||
if(row.selected) result += ' <abbr title="Selected">></abbr>';
|
||||
if(row.installed) result += ' <abbr title="FIB">*</abbr>';
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user