net/frr - reuse opnsense-treeview.js from core to ease maintanance and offer the same functionality.

This commit is contained in:
Ad Schellevis
2023-12-10 16:07:20 +01:00
parent d714e8fc24
commit 25940b512c
3 changed files with 54 additions and 190 deletions
@@ -27,6 +27,25 @@ namespace OPNsense\Quagga;
class DiagnosticsController extends \OPNsense\Base\IndexController
{
/**
* {@inheritdoc}
*/
protected function templateJSIncludes()
{
return array_merge(parent::templateJSIncludes(), [
'/ui/js/tree.jquery.min.js',
'/ui/js/opnsense-treeview.js'
]);
}
/**
* {@inheritdoc}
*/
protected function templateCSSIncludes()
{
return array_merge(parent::templateCSSIncludes(), ['/css/jqtree.css']);
}
public function bgpAction()
{
$this->view->tabs = [
@@ -29,8 +29,6 @@ POSSIBILITY OF SUCH DAMAGE.
#}
<script src="{{ cache_safe('/ui/js/frr/diagnostics_utils.js') }}"></script>
<style>
.searchbox {
margin: 8px;
@@ -46,9 +44,24 @@ POSSIBILITY OF SUCH DAMAGE.
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>
.bootstrap-dialog-body {
overflow-x: auto;
}
.modal-dialog,
.modal-content {
height: 80%;
}
.modal-body {
height: calc(100% - 120px);
overflow-y: scroll;
}
@media (min-width: 768px) {
.modal-dialog {
width: 90%;
}
}
</style>
<script>
'use strict';
@@ -60,26 +73,26 @@ POSSIBILITY OF SUCH DAMAGE.
* 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");
$(".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);
$(window).on('resize', function(){
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');
});
let all_grids = [];
{% for tab in tabs %}
/**
* register refresh event handler for {{ tab['tabhead'] }}
*/
$("#refresh-{{ tab['name'] }}").click(function () {
$("#{{ tab['name'] }}_tab").click(function () {
{% switch tab['type'] %}
{% case 'generalroutetable' %}
{% case 'bgproutetable' %}
@@ -203,46 +216,8 @@ POSSIBILITY OF SUCH DAMAGE.
{% 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('&gt;','\>').replace('&lt;','\<'));
if (node.value !== undefined) {
$li.find('.jqtree-element').append(
'&nbsp; <strong>:</strong> &nbsp;' + 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);
}
if (status == "success" && data.response) {
update_tree(data.response, "#tree-{{ tab['name'] }}");
}
});
$(window).trigger('resize');
@@ -257,14 +232,10 @@ POSSIBILITY OF SUCH DAMAGE.
{% endswitch %}
});
/**
* perform data fetch via event handler
*/
$("a[id='{{ tab['name'] }}_tab']").on("shown.bs.tab", function (event) {
$("#refresh-{{ tab['name'] }}").click();
});
{% endfor %}
$(".tree_search").keyup(tree_delayed_live_search);
/**
* add "sub title" heading
*/
@@ -282,7 +253,7 @@ POSSIBILITY OF SUCH DAMAGE.
{% 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>
{{ tab['tabhead'] }} <span class="fa tab-icon fa-refresh" style="cursor: pointer"></span>
</a>
</li>
{% endfor %}
@@ -435,6 +406,7 @@ POSSIBILITY OF SUCH DAMAGE.
</div>
{% break %}
{% case 'tree' %}
<section class="col-xs-12">
<div class="searchbox">
<input
id="search-{{ tab['name'] }}"
@@ -444,7 +416,8 @@ POSSIBILITY OF SUCH DAMAGE.
placeholder="{{ lang._('search') }}"
></input>
</div>
<div class="treewidget" style="padding: 8px; overflow-y: scroll; height:400px;" id="tree-{{ tab['name'] }}"></div>
<div class="treewidget" style="padding: 8px; overflow-y: scroll; height:400px;" id="tree-{{tab['name']}}"></div>
</section>
{% break %}
{% case 'text' %}
<pre id="text-{{ tab['name'] }}"></pre>
@@ -1,128 +0,0 @@
/*
* Copyright (C) 2015-2023 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';
/**
* 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;
}