luci-app-bmx7: update to v0.1-alpha

consolidated commits from master branch:
 fce1287 luci-app-bmx7: show mDNS menu if available
 3e259f8 luci-app-bmx7: fix bmx7-info script's "$info" call
 a7d7f4b luci-app-bmx7: fix bmx7-info script's indentation
 9345df9 luci-app-bmx7: update version, dependencies and maintainer
 a1e1020 luci-app-bmx7: refactory, multiple fixes and add topology graph

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle 2019-03-02 22:30:16 +01:00
parent 0e3d70176c
commit 71f9aae5e4
12 changed files with 1030 additions and 216 deletions

View File

@ -21,7 +21,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-bmx7
PKG_RELEASE:=0.0-alpha
PKG_RELEASE:=0.1-alpha
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
PKG_LICENSE:=GPL-2.0+
@ -33,8 +33,8 @@ define Package/luci-app-bmx7
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:= LuCI support for BMX7
DEPENDS:=+luci-lib-json +luci-mod-admin-full +luci-lib-httpclient +bmx7
MAINTAINER:= Roger Pueyo Centelles <roger.pueyo@guifi.net>
DEPENDS:=+luci-lib-json +luci-mod-admin-full +bmx7 +bmx7-json
MAINTAINER:= Roger Pueyo <roger.pueyo@guifi.net> and Pau Escrich <p4u@dabax.net>
endef
define Package/luci-app-bmx7/description

View File

@ -59,10 +59,28 @@ function index()
entry(place,call("action_status_j"),"Status",0)
table.remove(place)
-- Nodes list
table.insert(place,"Nodes")
entry(place,call("action_nodes_j"),"Nodes",1)
-- Topology
table.insert(place,"Topology")
entry(place,call("topology"),"Topology",1)
table.remove(place)
-- Nodes
table.insert(place,"Nodes")
entry(place,call("action_nodes_j"),"Nodes",2)
table.remove(place)
-- Tunnels
table.insert(place,"Gateways")
entry(place,call("action_tunnels_j"),"Gateways",3)
table.remove(place)
-- Integrate bmx7-mdns if present
if nixio.fs.stat("/usr/lib/lua/luci/model/cbi/bmx7-mdns.lua","type") ~= nil then
table.insert(place,"mDNS")
entry(place, cbi("bmx7-mdns"), "mesh DNS", 1).dependent=false
table.remove(place)
end
end
@ -70,8 +88,14 @@ function action_status_j()
luci.template.render("bmx7/status_j", {})
end
function action_nodes_j()
local http = require "luci.http"
local link_non_js = "/cgi-bin/luci" .. http.getenv("PATH_INFO") .. '/nodes_nojs'
luci.template.render("bmx7/nodes_j", {link_non_js=link_non_js})
function action_tunnels_j()
luci.template.render("bmx7/tunnels_j", {})
end
function topology()
luci.template.render("bmx7/topology", {})
end
function action_nodes_j()
luci.template.render("bmx7/nodes_j", {})
end

View File

@ -0,0 +1,40 @@
<div class="cbi-map">
<div class="cbi-section">
<legend><%:Bmx7 mesh nodes%></legend>
<div class="cbi-section-node">
<div class="table" id="nodes_div">
<div class="tr table-titles">
<div class="th"><%:Name%></div>
<div class="th"><%:Short ID%></div>
<div class="th"><%:S/s/T/t%></div>
<div class="th"><%:Primary IPv6%></div>
<div class="th"><%:Via Neighbour%></div>
<div class="th"><%:Device%></div>
<div class="th"><%:Metric%></div>
<div class="th"><%:Last Ref%></div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="<%=resource%>/bmx7/js/polling.js"></script>
<script type="text/javascript">//<![CDATA[
new TablePooler(10,"/cgi-bin/bmx7-info", {'$originators':''}, "nodes_div", function(st){
var originators = st.originators;
var res = Array();
originators.forEach(function(originator,i){
var name = originator.name;
var shortId = originator.shortId;
var SsTt = originator.S+'/'+originator.s+'/'+originator.T+'/'+originator.t;
var primaryIp = originator.primaryIp;
var nbName = originator.nbName;
var dev = originator.dev;
var metric = originator.metric;
var lastRef = originator.lastRef;
res.push([name, shortId, SsTt, primaryIp,
nbName, dev, metric, lastRef]);
});
return res;
});
//]]></script>

View File

@ -27,19 +27,16 @@
<style>
div.hideme{
display: none;
}
div.info{
background: #FFF;
border: solid 0px;
height: 90px;
height: 190px;
display: block;
overflow: auto;
}
div.inforow{
text-align:left;
display:inline-block;
@ -48,18 +45,15 @@
float: left;
white-space:nowrap;
}
div.inforow.newline{
clear: both;
}
u {
text-decoration: underline;
}
}
#extra-info ul { list-style: none outside none; margin-left: 0em; }
</style>
<div class="cbi-map">
<h2>Mesh nodes</h2>
@ -70,40 +64,38 @@
Tip: click the <img src="<%=resource%>/bmx7/world.png" /> icon to see individual node information.
</center>
</div>
<fieldset class="cbi-section">
<div class="cbi-section">
<legend><%:Originators%></legend>
<table class="cbi-section-table" id="descriptions_table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"></th>
<th class="cbi-section-table-cell"><%:Name%></th>
<th class="cbi-section-table-cell"><%:Short ID%></th>
<th class="cbi-section-table-cell"><%:S/s/T/t%></th>
<th class="cbi-section-table-cell"><%:Primary IPv6 address%></th>
<th class="cbi-section-table-cell"><%:Via neighbour%></th>
<th class="cbi-section-table-cell"><%:Metric%></th>
<th class="cbi-section-table-cell"><%:Last desc.%></th>
<th class="cbi-section-table-cell"><%:Last ref.%></th>
<th class="cbi-section-table-cell"><%: %></th>
</tr>
<tr class="cbi-section-table-row">
<td colspan="11"><br /><center><em><%:Collecting data...%></em></center></td>
</tr>
</table>
</fieldset>
<div class="cbi-section-node">
<div class="table" id="nodes_div">
<div class="tr table-titles">
<div class="th"></div>
<div class="th"><%:Name%></div>
<div class="th"><%:Short ID%></div>
<div class="th"><%:S/s/T/t%></div>
<div class="th"><%:Primary IPv6%></div>
<div class="th"><%:Via Neighbour%></div>
<div class="th"><%:Metric%></div>
<div class="th"><%:Last Desc%></div>
<div class="th"><%:Last Ref%></div>
<div class="th"><%: %></div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">//<![CDATA[
var displayExtraInfo = function ( id ) {
console.log('aaa'+id)
document.getElementById('extra-info').innerHTML = document.getElementById(id).innerHTML;
}
new TablePooler(5,"/cgi-bin/bmx7-info", {'$originators':''}, "descriptions_table", function(st){
new TablePooler(5,"/cgi-bin/bmx7-info", {'$originators':''}, "nodes_div", function(st){
var infoicon = "<%=resource%>/bmx7/world_small.png";
var originators = st.originators;
var res = Array();
originators.forEach(function(originator,i){
var name = originator.name;
var shortId = originator.shortId;
@ -119,57 +111,45 @@
var metric = originator.metric;
var lastDesc = originator.lastDesc;
var lastRef = originator.lastRef;
var extrainfo = '<a onclick="displayExtraInfo(\'ip-' + i + '\')"><img src="' + infoicon + '" / ></a>';
var extrainfo_link = '<a onclick="displayExtraInfo(\'ip-' + i + '\')">' + '<img src="' + infoicon + '" />' + '</a>';
extrainfo = '<div id="ip-'+ i +'" class="hideme">'
extrainfo = '<div id="ip-'+ i +'" class="hideme">'
+ "<div class='inforow'>"
+ "<h4><u>" + name + '</u></h4>\n'
+ 'Node ID: ' + nodeId + "</div>"
+ 'Node ID: ' + shortId + "</div>"
+ "<div class='inforow'>"
+ "<h5>Primary IPv6 address</h5>\n"
+ primaryIp + "</div>\n"
+ "<div class='inforow'>"
+ "<h5>Support & Trust</h5>\n"
+ SsTt + "</div>\n"
+ "<div class='inforow'>"
+ "<h5>Node key</h5>\n"
+ nodeKey + "</div>\n"
+ "<div class='inforow newline'>"
+ "<h5>Via neighbour</h5>\n"
+ nbName + "</div>\n"
+ "<div class='inforow'>"
+ "<h5>Via device</h5>\n"
+ dev + "</div>\n"
+ "<div class='inforow'>"
+ "<h5>Via remote link-local IPv6 address</h5>\n"
+ "<h5>Via link-local IPv6</h5>\n"
+ nbLocalIp + "</div>\n"
+ "<div class='inforow'>"
+ "<h5>Route metric</h5>\n"
+ metric + "</div>\n"
+ "<div class='inforow'>"
+ "<h5>Desc. size</h5>\n"
+ descSize + "</div>\n"
+ "\n</div>";
res.push([extrainfo_link, name, shortId, SsTt, primaryIp,
nbName, metric, lastDesc, lastRef, extrainfo]);
});
return res;
});
//]]></script>
<%+footer%>

View File

@ -2,30 +2,6 @@
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<script type="text/javascript" src="<%=resource%>/bmx7/js/polling.js"></script>
<style>
div.hideme{
display: none;
}
div.info{
background: #FFF;
border: solid 1px;
height: 80px;
display: block;
overflow: auto;
}
div.inforow{
text-align:left;
display:inline-block;
width:20%;
margin:5px;
vertical-align:top;
}
#extra-info ul { list-style: none outside none; margin-left: 0em; }
</style>
<div class="cbi-map">
<center>
<img src="<%=resource%>/bmx7/bmx7logo.png" />
@ -38,145 +14,117 @@
<div class="cbi-map-descr"></div>
<fieldset class="cbi-section">
<div class="cbi-section">
<legend><%:Node configuration%></legend>
<table class="cbi-section-table" id="config_table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:Short ID%></th>
<th class="cbi-section-table-cell"><%:Node name%></th>
<th class="cbi-section-table-cell"><%:Primary IPv6 address%></th>
<th class="cbi-section-table-cell"><%:Node key%></th>
<th class="cbi-section-table-cell"><%:BMX7 revision%></th>
</tr>
<tr class="cbi-section-table-row">
<td colspan="5"><em><br /><%:Collecting data...%></em></td>
</tr>
</table>
</fieldset>
<div class="cbi-section-node">
<div class="table" id="config_div">
<div class="tr table-titles">
<div class="th"><%:Short ID%></div>
<div class="th"><%:Node name%></div>
<div class="th"><%:Primary IPv6 address%></div>
<div class="th"><%:Node key%></div>
<div class="th"><%:Short DHash%></div>
<div class="th"><%:BMX7 revision%></div>
</div>
</div>
</div>
</div>
<fieldset class="cbi-section">
<div class="cbi-section">
<legend><%:Node status%></legend>
<table class="cbi-section-table" id="status_table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:Nodes seen%></th>
<th class="cbi-section-table-cell"><%:Neighbours%></th>
<th class="cbi-section-table-cell"><%:Tunnelled IPv6 address%></th>
<th class="cbi-section-table-cell"><%:Tunnelled IPv4 address%></th>
<th class="cbi-section-table-cell"><%:Uptime%></th>
<th class="cbi-section-table-cell"><%:CPU usage%></th>
<th class="cbi-section-table-cell"><%:Memory usage%></th>
<th class="cbi-section-table-cell"><%:Tx queue%></th>
<div class="cbi-section-node">
<div class="table" id="status_div">
<div class="tr table-titles">
<div class="th"><%:Nodes seen%></div>
<div class="th"><%:Neighbours%></div>
<div class="th"><%:Tunnelled IPv6 address%></div>
<div class="th"><%:Tunnelled IPv4 address%></div>
<div class="th"><%:Uptime%></div>
<div class="th"><%:CPU usage%></div>
<div class="th"><%:Memory usage%></div>
<div class="th"><%:Tx queue%></div>
</div>
</div>
</div>
</div>
</tr>
<tr class="cbi-section-table-row">
<td colspan="8"><em><br /><%:Collecting data...%></em></td>
</tr>
</table>
</fieldset>
<div class="cbi-section">
<legend><%:Network interfaces%></legend>
<div class="cbi-section-node">
<div class="table" id="ifaces_div">
<div class="tr table-titles">
<div class="th"><%:Interface%></div>
<div class="th"><%:State%></div>
<div class="th"><%:Type%></div>
<div class="th"><%:Max rate%></div>
<div class="th"><%:LinkLocal Ipv6%></div>
<div class="th"><%:RX BpP%></div>
<div class="th"><%:TX BpP%></div>
</div>
</div>
</div>
</div>
<fieldset class="cbi-section">
<legend><%:Interfaces%></legend>
<table class="cbi-section-table" id="ifaces_table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:Interface%></th>
<th class="cbi-section-table-cell"><%:State%></th>
<th class="cbi-section-table-cell"><%:Type%></th>
<th class="cbi-section-table-cell"><%:Max. rate%></th>
<th class="cbi-section-table-cell"><%:Link-local IPv6 address%></th>
<th class="cbi-section-table-cell"><%:Rx BpP%></th>
<th class="cbi-section-table-cell"><%:Tx BpP%></th>
</tr>
<tr class="cbi-section-table-row">
<td colspan="7"><em><br /><%:Collecting data...%></em></td>
</tr>
</table>
</fieldset>
<fieldset class="cbi-section">
<div class="cbi-section">
<legend><%:Links%></legend>
<table class="cbi-section-table" id="links_table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:Short ID%></th>
<th class="cbi-section-table-cell"><%:Name%></th>
<th class="cbi-section-table-cell"><%:Link key%></th>
<th class="cbi-section-table-cell"><%:Remote link-local IPv6 address%></th>
<th class="cbi-section-table-cell"><%:Device%></th>
<th class="cbi-section-table-cell"><%:Rx rate%></th>
<th class="cbi-section-table-cell"><%:Tx rate%></th>
<th class="cbi-section-table-cell"><%:Routes%></th>
</tr>
<tr class="cbi-section-table-row">
<td colspan="8"><em><br /><%:Collecting data...%></em></td>
</tr>
</table>
</fieldset>
<div class="cbi-section-node">
<div class="table" id="links_div">
<div class="tr table-titles">
<div class="th"><%:Short ID%></div>
<div class="th"><%:Name%></div>
<div class="th"><%:Link key%></div>
<div class="th"><%:Remote linklocal IPv6%></div>
<div class="th"><%:Device%></div>
<div class="th"><%:RX rate%></div>
<div class="th"><%:TX rate%></div>
<div class="th"><%:Routes%></div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">//<![CDATA[
new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "config_table", function(st){
new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "config_div", function(st){
var res = Array();
var sta = st.info[0].status;
var ifaces = st.info[1].interfaces;
res.push([sta.shortId, sta.name, sta.primaryIp, sta.nodeKey, sta.revision]);
res.push(['','','','',''])
res.push(['','','','',''])
res.push([sta.shortId, sta.name, sta.primaryIp, sta.nodeKey, sta.shortDhash, sta.revision]);
return res;
});
new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "status_table", function(st){
new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "status_div", function(st){
var res = Array();
var sta = st.info[0].status;
var mem = st.info[3].memory;
var mem = st.info[3].memory.bmx7;
var txQ = sta.txQ.split('/');
console.log(txQ)
var ptxQ = '<p style="color:rgb('+parseInt(255*txQ[0]/txQ[1])+','+parseInt(128*(txQ[1]-txQ[0])/txQ[1])+',0)")>'+sta.txQ+'</p>';
console.log(ptxQ)
res.push([sta.nodes, sta.nbs, sta.tun6Address, sta.tun4Address, sta.uptime, sta.cpu, mem.bmx7, ptxQ]);
res.push(['','','','','','','',''])
res.push(['','','','','','','',''])
res.push([sta.nodes, sta.nbs, sta.tun6Address, sta.tun4Address, sta.uptime, sta.cpu, mem, ptxQ]);
return res;
});
new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "ifaces_table", function(st){
new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "ifaces_div", function(st){
var res = Array();
var sta = st.info[0].status;
var ifaces = st.info[1].interfaces;
ifaces.forEach(function(iface){
res.push([iface.dev, iface.state, iface.type, iface.rateMax, iface.localIp, iface.rxBpP, iface.txBpP]);
});
res.push(['','','','','','',''])
if (ifaces.length % 2 == 0)
res.push('')
res.push(['','','','','','',''])
return res;
});
new TablePooler(1,"/cgi-bin/bmx7-info", {'links':''}, "links_table", function(st){
new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "links_div", function(st){
var res = Array();
links = st.links;
links = st.info[2].links;
links.forEach(function(link){
res.push([link.shortId, link.name, link.linkKey, link.nbLocalIp, link.dev, link.rxRate, link.txRate, link.routes]);
res.push([link.shortId, link.name, link.linkKey, link.nbLocalIp, link.dev, link.rxRate, link.txRate, link.rts]);
});
res.push(['','','','','','','',''])
if (links.length % 2 == 0)
res.push([])
res.push(['','','','','','','',''])
return res;
});
//]]></script>
<%+footer%>

View File

@ -0,0 +1,54 @@
<%+header%>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js"></script>
<script type="text/javascript" src="<%=resource%>/bmx7/js/netjsongraph.js"></script>
<link href="<%=resource%>/bmx7/css/netjsongraph.css" rel="stylesheet">
<style type="text/css">
body {
font-family: Arial, sans-serif;
font-size: 13px;
}
.njg-overlay{
width: auto;
height: auto;
min-width: 200px;
max-width: 400px;
border: 1px solid #000;
border-radius: 2px;
background: rgba(0, 0, 0, 0.7);
top: 10px;
right: 10px;
padding: 0 15px;
font-family: Arial, sans-serif;
font-size: 14px;
color: #fff
}
.njg-node {
fill: #008000;
fill-opacity: 0.8;
stroke: #008000;
stroke-width: 1px;
cursor: pointer;
}
.njg-node:hover,
.njg-node.njg-open{
fill-opacity: 1;
}
.njg-link {
stroke: #00ff00;
stroke-width: 2;
stroke-opacity: .5;
cursor: pointer;
}
.njg-link:hover,
.njg-link.njg-open{
stroke-width: 3;
stroke-opacity: 1
}
</style>
<script>d3.netJsonGraph("/cgi-bin/bmx7-info?netjson/network-graph.json", { defaultStyle: false });</script>
<%+footer%>

View File

@ -0,0 +1,76 @@
<%#
Copyright (C) 2011 Pau Escrich <pau@dabax.net>
Contributors Lluis Esquerda <eskerda@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
The full GNU General Public License is included in this distribution in
the file called "COPYING".
-%>
<%+header%>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<script type="text/javascript" src="<%=resource%>/bmx7/js/polling.js"></script>
<div class="cbi-map">
<h2>Gateway announcements</h2>
<div class="cbi-map-descr">Networks announced by mesh nodes</div>
<div class="cbi-section">
<legend><%:Announcements%></legend>
<div class="cbi-section-node">
<div class="table" id="tunnels_div">
<div class="tr table-titles">
<div class="th"><%:Status%></div>
<div class="th"><%:Name%></div>
<div class="th"><%:Node%></div>
<div class="th"><%:Network%></div>
<div class="th"><%:Bandwith%></div>
<div class="th"><%:Local net%></div>
<div class="th"><%:Path Metric%></div>
<div class="th"><%:Tun Metric%></div>
<div class="th"><%:Rating%></div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">//<![CDATA[
new TablePooler(5,"/cgi-bin/bmx7-info", {'$tunnels':''}, "tunnels_div", function(st){
var tunicon = "<%=resource%>/icons/tunnel.png";
var tunicon_dis = "<%=resource%>/icons/tunnel_disabled.png";
var applyicon = "<%=resource%>/cbi/apply.gif";
var res = Array();
for ( var k in st.tunnels ) {
var tunnel = st.tunnels[k];
var nodename = tunnel.remoteName;
var advnet = tunnel.advNet;
var status = '<img src="'+tunicon_dis+'"/>';
if ( tunnel.tunName != "---" ) status = '<img src="'+tunicon+'"/>';
if ( advnet == "0.0.0.0/0" ) advnet = "<b>Internet IPv4</b>";
if ( advnet == "::/0" ) advnet = "<b>Internet IPv6</b>";
if (nodename != "---") {
res.push([status, tunnel.tunName, nodename, advnet, tunnel.advBw, tunnel.net,
tunnel.pathMtc, tunnel.tunMtc, tunnel.rating]);
}
}
return res;
});
//]]></script>
<%+footer%>

View File

@ -1,7 +1,7 @@
#!/bin/sh
# Copyright © 2011 Pau Escrich
# Contributors Jo-Philipp Wich <xm@subsignal.org>
# Roger Pueyo Centelles <roger.pueyo@guifi.net>
# Roger Pueyo Centelles <roger.pueyo@guifi.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -34,13 +34,12 @@ else
QUERY="${QUERY_STRING%%=*}"
echo "Content-type: application/json"
echo ""
fi
check_path() {
[ -d "$1" ] && path=$(cd $1; pwd)
[ -f "$1" ] && path=$(cd $1/..; pwd)
[ $(echo "$path" | grep -c "^$BMX7_DIR") -ne 1 ] && exit 1
[ -d "$1" ] && path=$(cd $1; pwd)
[ -f "$1" ] && path=$(cd $1/..; pwd)
[ $(echo "$path" | grep -c "^$BMX7_DIR") -ne 1 ] && exit 1
}
print_mem() {
@ -52,19 +51,19 @@ print_mem() {
print_query() {
# If the query is a directory
[ -d "$BMX7_DIR/$1" ] &&
{
{
# If /all has not been specified
[ -z "$QALL" ] &&
{
total=$(ls $BMX7_DIR/$1 | wc -w)
i=1
echo -n "{ \"$1\": [ "
for f in $(ls $BMX7_DIR/$1); do
echo -n "{ \"$1\": [ "
for f in $(ls $BMX7_DIR/$1); do
echo -n "{ \"name\": \"$f\" }"
[ $i -lt $total ] && echo -n ','
i=$(( $i + 1 ))
done
echo -n " ] }"
done
echo -n " ] }"
# If /all has been specified, printing all the files together
} || {
@ -80,10 +79,10 @@ print_query() {
done
echo -n " ]"
}
}
}
# If the query is a file, just printing the file
[ -f "$BMX7_DIR/$1" ] && cat "$BMX7_DIR/$1";
[ -f "$BMX7_DIR/$1" ] && [ -s "$BMX7_DIR/$1" ] && cat "$BMX7_DIR/$1" && return 0 || return 1
}
if [ "${QUERY##*/}" == "all" ]; then
@ -95,10 +94,8 @@ if [ "$QUERY" == '$info' ]; then
echo '{ "info": [ '
print_query status
echo -n ","
print_query interfaces
echo -n ","
print_query links
echo -n ","
print_query interfaces && echo -n "," || echo -n '{ "interfaces": "" },'
print_query links && echo -n "," || echo -n '{ "links": "" },'
print_mem
echo "] }"
fi

View File

@ -0,0 +1,59 @@
.njg-overlay{
background: #fbfbfb;
border-radius: 2px;
border: 1px solid #ccc;
color: #6d6357;
font-family: Arial, sans-serif;
font-family: sans-serif;
font-size: 14px;
line-height: 20px;
height: auto;
max-width: 400px;
min-width: 200px;
padding: 0 15px;
right: 10px;
top: 10px;
width: auto;
}
.njg-metadata{
background: #fbfbfb;
border-radius: 2px;
border: 1px solid #ccc;
color: #6d6357;
display: none;
font-family: Arial, sans-serif;
font-family: sans-serif;
font-size: 14px;
height: auto;
left: 10px;
max-width: 500px;
min-width: 200px;
padding: 0 15px;
top: 10px;
width: auto;
}
.njg-node{
stroke-opacity: 0.5;
stroke-width: 7px;
stroke: #fff;
}
.njg-node:hover,
.njg-node.njg-open {
stroke: rgba(0, 0, 0, 0.2);
}
.njg-link{
cursor: pointer;
stroke: #999;
stroke-width: 2;
stroke-opacity: 0.25;
}
.njg-link:hover,
.njg-link.njg-open{
stroke-width: 4 !important;
stroke-opacity: 0.5;
}

View File

@ -0,0 +1,62 @@
.njg-hidden {
display: none !important;
visibility: hidden !important;
}
.njg-tooltip{
font-family: sans-serif;
font-size: 10px;
fill: #000;
opacity: 0.5;
text-anchor: middle;
}
.njg-overlay{
display: none;
position: absolute;
z-index: 11;
}
.njg-close{
cursor: pointer;
position: absolute;
right: 10px;
top: 10px;
}
.njg-close:before { content: "\2716"; }
.njg-metadata{
display: none;
position: absolute;
z-index: 12;
}
.njg-node{ cursor: pointer }
.njg-link{ cursor: pointer }
#njg-select-group {
text-align: center;
box-shadow: 0 0 10px #ccc;
position: fixed;
left: 50%;
top: 50%;
width: 50%;
margin-top: -7.5em;
margin-left: -25%;
padding: 5em 2em;
}
#njg-select-group select {
font-size: 2em;
padding: 10px 15px;
width: 50%;
cursor: pointer;
}
#njg-select-group option {
padding: 0.5em;
}
#njg-select-group option[value=""] {
color: #aaa;
}

View File

@ -0,0 +1,568 @@
// version 0.1
(function () {
/**
* vanilla JS implementation of jQuery.extend()
*/
d3._extend = function(defaults, options) {
var extended = {},
prop;
for(prop in defaults) {
if(Object.prototype.hasOwnProperty.call(defaults, prop)) {
extended[prop] = defaults[prop];
}
}
for(prop in options) {
if(Object.prototype.hasOwnProperty.call(options, prop)) {
extended[prop] = options[prop];
}
}
return extended;
};
/**
* @function
* @name d3._pxToNumber
* Convert strings like "10px" to 10
*
* @param {string} val The value to convert
* @return {int} The converted integer
*/
d3._pxToNumber = function(val) {
return parseFloat(val.replace('px'));
};
/**
* @function
* @name d3._windowHeight
*
* Get window height
*
* @return {int} The window innerHeight
*/
d3._windowHeight = function() {
return window.innerHeight || document.documentElement.clientHeight || 600;
};
/**
* @function
* @name d3._getPosition
*
* Get the position of `element` relative to `container`
*
* @param {object} element
* @param {object} container
*/
d3._getPosition = function(element, container) {
var n = element.node(),
nPos = n.getBoundingClientRect();
cPos = container.node().getBoundingClientRect();
return {
top: nPos.top - cPos.top,
left: nPos.left - cPos.left,
width: nPos.width,
bottom: nPos.bottom - cPos.top,
height: nPos.height,
right: nPos.right - cPos.left
};
};
/**
* netjsongraph.js main function
*
* @constructor
* @param {string} url The NetJSON file url
* @param {object} opts The object with parameters to override {@link d3.netJsonGraph.opts}
*/
d3.netJsonGraph = function(url, opts) {
/**
* Default options
*
* @param {string} el "body" The container element el: "body" [description]
* @param {bool} metadata true Display NetJSON metadata at startup?
* @param {bool} defaultStyle true Use css style?
* @param {bool} animationAtStart false Animate nodes or not on load
* @param {array} scaleExtent [0.25, 5] The zoom scale's allowed range. @see {@link https://github.com/mbostock/d3/wiki/Zoom-Behavior#scaleExtent}
* @param {int} charge -130 The charge strength to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#charge}
* @param {int} linkDistance 50 The target distance between linked nodes to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#linkDistance}
* @param {float} linkStrength 0.2 The strength (rigidity) of links to the specified value in the range. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#linkStrength}
* @param {float} friction 0.9 The friction coefficient to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#friction}
* @param {string} chargeDistance Infinity The maximum distance over which charge forces are applied. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#chargeDistance}
* @param {float} theta 0.8 The BarnesHut approximation criterion to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#theta}
* @param {float} gravity 0.1 The gravitational strength to the specified numerical value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#gravity}
* @param {int} circleRadius 8 The radius of circles (nodes) in pixel
* @param {string} labelDx "0" SVG dx (distance on x axis) attribute of node labels in graph
* @param {string} labelDy "-1.3em" SVG dy (distance on y axis) attribute of node labels in graph
* @param {function} onInit Callback function executed on initialization
* @param {function} onLoad Callback function executed after data has been loaded
* @param {function} onEnd Callback function executed when initial animation is complete
* @param {function} linkDistanceFunc By default high density areas have longer links
* @param {function} redraw Called when panning and zooming
* @param {function} prepareData Used to convert NetJSON NetworkGraph to the javascript data
* @param {function} onClickNode Called when a node is clicked
* @param {function} onClickLink Called when a link is clicked
*/
opts = d3._extend({
el: "body",
metadata: true,
defaultStyle: true,
animationAtStart: true,
scaleExtent: [0.25, 5],
charge: -130,
linkDistance: 50,
linkStrength: 0.2,
friction: 0.9, // d3 default
chargeDistance: Infinity, // d3 default
theta: 0.8, // d3 default
gravity: 0.1,
circleRadius: 8,
labelDx: "0",
labelDy: "-1.3em",
nodeClassProperty: null,
linkClassProperty: null,
/**
* @function
* @name onInit
*
* Callback function executed on initialization
* @param {string|object} url The netJson remote url or object
* @param {object} opts The object of passed arguments
* @return {function}
*/
onInit: function(url, opts) {},
/**
* @function
* @name onLoad
*
* Callback function executed after data has been loaded
* @param {string|object} url The netJson remote url or object
* @param {object} opts The object of passed arguments
* @return {function}
*/
onLoad: function(url, opts) {},
/**
* @function
* @name onEnd
*
* Callback function executed when initial animation is complete
* @param {string|object} url The netJson remote url or object
* @param {object} opts The object of passed arguments
* @return {function}
*/
onEnd: function(url, opts) {},
/**
* @function
* @name linkDistanceFunc
*
* By default, high density areas have longer links
*/
linkDistanceFunc: function(d){
var val = opts.linkDistance;
if(d.source.linkCount >= 4 && d.target.linkCount >= 4) {
return val * 2;
}
return val;
},
/**
* @function
* @name redraw
*
* Called on zoom and pan
*/
redraw: function() {
panner.attr("transform",
"translate(" + d3.event.translate + ") " +
"scale(" + d3.event.scale + ")"
);
},
/**
* @function
* @name prepareData
*
* Convert NetJSON NetworkGraph to the data structure consumed by d3
*
* @param graph {object}
*/
prepareData: function(graph) {
var nodesMap = {},
nodes = graph.nodes.slice(), // copy
links = graph.links.slice(), // copy
nodes_length = graph.nodes.length,
links_length = graph.links.length;
for(var i = 0; i < nodes_length; i++) {
// count how many links every node has
nodes[i].linkCount = 0;
nodesMap[nodes[i].id] = i;
}
for(var c = 0; c < links_length; c++) {
var sourceIndex = nodesMap[links[c].source],
targetIndex = nodesMap[links[c].target];
// ensure source and target exist
if(!nodes[sourceIndex]) { throw("source '" + links[c].source + "' not found"); }
if(!nodes[targetIndex]) { throw("target '" + links[c].target + "' not found"); }
links[c].source = nodesMap[links[c].source];
links[c].target = nodesMap[links[c].target];
// add link count to both ends
nodes[sourceIndex].linkCount++;
nodes[targetIndex].linkCount++;
}
return { "nodes": nodes, "links": links };
},
/**
* @function
* @name onClickNode
*
* Called when a node is clicked
*/
onClickNode: function(n) {
var overlay = d3.select(".njg-overlay"),
overlayInner = d3.select(".njg-overlay > .njg-inner"),
html = "<p><b>id</b>: " + n.id + "</p>";
if(n.label) { html += "<p><b>label</b>: " + n.label + "</p>"; }
if(n.properties) {
for(var key in n.properties) {
if(!n.properties.hasOwnProperty(key)) { continue; }
html += "<p><b>"+key.replace(/_/g, " ")+"</b>: " + n.properties[key] + "</p>";
}
}
if(n.linkCount) { html += "<p><b>links</b>: " + n.linkCount + "</p>"; }
if(n.local_addresses) {
html += "<p><b>local addresses</b>:<br>" + n.local_addresses.join('<br>') + "</p>";
}
overlayInner.html(html);
overlay.classed("njg-hidden", false);
overlay.style("display", "block");
// set "open" class to current node
removeOpenClass();
d3.select(this).classed("njg-open", true);
},
/**
* @function
* @name onClickLink
*
* Called when a node is clicked
*/
onClickLink: function(l) {
var overlay = d3.select(".njg-overlay"),
overlayInner = d3.select(".njg-overlay > .njg-inner"),
html = "<p><b>source</b>: " + (l.source.label || l.source.id) + "</p>";
html += "<p><b>target</b>: " + (l.target.label || l.target.id) + "</p>";
html += "<p><b>cost</b>: " + l.cost + "</p>";
if(l.properties) {
for(var key in l.properties) {
if(!l.properties.hasOwnProperty(key)) { continue; }
html += "<p><b>"+ key.replace(/_/g, " ") +"</b>: " + l.properties[key] + "</p>";
}
}
overlayInner.html(html);
overlay.classed("njg-hidden", false);
overlay.style("display", "block");
// set "open" class to current link
removeOpenClass();
d3.select(this).classed("njg-open", true);
}
}, opts);
// init callback
opts.onInit(url, opts);
if(!opts.animationAtStart) {
opts.linkStrength = 2;
opts.friction = 0.3;
opts.gravity = 0;
}
if(opts.el == "body") {
var body = d3.select(opts.el),
rect = body.node().getBoundingClientRect();
if (d3._pxToNumber(d3.select("body").style("height")) < 60) {
body.style("height", d3._windowHeight() - rect.top - rect.bottom + "px");
}
}
var el = d3.select(opts.el).style("position", "relative"),
width = d3._pxToNumber(el.style('width')),
height = d3._pxToNumber(el.style('height')),
force = d3.layout.force()
.charge(opts.charge)
.linkStrength(opts.linkStrength)
.linkDistance(opts.linkDistanceFunc)
.friction(opts.friction)
.chargeDistance(opts.chargeDistance)
.theta(opts.theta)
.gravity(opts.gravity)
// width is easy to get, if height is 0 take the height of the body
.size([width, height]),
zoom = d3.behavior.zoom().scaleExtent(opts.scaleExtent),
// panner is the element that allows zooming and panning
panner = el.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom.on("zoom", opts.redraw))
.append("g")
.style("position", "absolute"),
svg = d3.select(opts.el + " svg"),
drag = force.drag(),
overlay = d3.select(opts.el).append("div").attr("class", "njg-overlay"),
closeOverlay = overlay.append("a").attr("class", "njg-close"),
overlayInner = overlay.append("div").attr("class", "njg-inner"),
metadata = d3.select(opts.el).append("div").attr("class", "njg-metadata"),
metadataInner = metadata.append("div").attr("class", "njg-inner"),
closeMetadata = metadata.append("a").attr("class", "njg-close"),
// container of ungrouped networks
str = [],
selected = [],
/**
* @function
* @name removeOpenClass
*
* Remove open classes from nodes and links
*/
removeOpenClass = function () {
d3.selectAll("svg .njg-open").classed("njg-open", false);
};
processJson = function(graph) {
/**
* Init netJsonGraph
*/
init = function(url, opts) {
d3.netJsonGraph(url, opts);
};
/**
* Remove all instances
*/
destroy = function() {
force.stop();
d3.select("#selectGroup").remove();
d3.select(".njg-overlay").remove();
d3.select(".njg-metadata").remove();
overlay.remove();
overlayInner.remove();
metadata.remove();
svg.remove();
node.remove();
link.remove();
nodes = [];
links = [];
};
/**
* Destroy and e-init all instances
* @return {[type]} [description]
*/
reInit = function() {
destroy();
init(url, opts);
};
var data = opts.prepareData(graph),
links = data.links,
nodes = data.nodes;
// disable some transitions while dragging
drag.on('dragstart', function(n){
d3.event.sourceEvent.stopPropagation();
zoom.on('zoom', null);
})
// re-enable transitions when dragging stops
.on('dragend', function(n){
zoom.on('zoom', opts.redraw);
})
.on("drag", function(d) {
// avoid pan & drag conflict
d3.select(this).attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
});
force.nodes(nodes).links(links).start();
var link = panner.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", function (link) {
var baseClass = "njg-link",
addClass = null;
value = link.properties && link.properties[opts.linkClassProperty];
if (opts.linkClassProperty && value) {
// if value is stirng use that as class
if (typeof(value) === "string") {
addClass = value;
}
else if (typeof(value) === "number") {
addClass = opts.linkClassProperty + value;
}
else if (value === true) {
addClass = opts.linkClassProperty;
}
return baseClass + " " + addClass;
}
return baseClass;
})
.on("click", opts.onClickLink),
groups = panner.selectAll(".node")
.data(nodes)
.enter()
.append("g");
node = groups.append("circle")
.attr("class", function (node) {
var baseClass = "njg-node",
addClass = null;
value = node.properties && node.properties[opts.nodeClassProperty];
if (opts.nodeClassProperty && value) {
// if value is stirng use that as class
if (typeof(value) === "string") {
addClass = value;
}
else if (typeof(value) === "number") {
addClass = opts.nodeClassProperty + value;
}
else if (value === true) {
addClass = opts.nodeClassProperty;
}
return baseClass + " " + addClass;
}
return baseClass;
})
.attr("r", opts.circleRadius)
.on("click", opts.onClickNode)
.call(drag);
var labels = groups.append('text')
.text(function(n){ return n.label || n.id })
.attr('dx', opts.labelDx)
.attr('dy', opts.labelDy)
.attr('class', 'njg-tooltip');
// Close overlay
closeOverlay.on("click", function() {
removeOpenClass();
overlay.classed("njg-hidden", true);
});
// Close Metadata panel
closeMetadata.on("click", function() {
// Reinitialize the page
if(graph.type === "NetworkCollection") {
reInit();
}
else {
removeOpenClass();
metadata.classed("njg-hidden", true);
}
});
// default style
// TODO: probably change defaultStyle
// into something else
if(opts.defaultStyle) {
var colors = d3.scale.category20c();
node.style({
"fill": function(d){ return colors(d.linkCount); },
"cursor": "pointer"
});
}
// Metadata style
if(opts.metadata) {
metadata.attr("class", "njg-metadata").style("display", "block");
}
var attrs = ["protocol",
"version",
"revision",
"metric",
"router_id",
"topology_id"],
html = "";
if(graph.label) {
html += "<h3>" + graph.label + "</h3>";
}
for(var i in attrs) {
var attr = attrs[i];
if(graph[attr]) {
html += "<p><b>" + attr + "</b>: <span>" + graph[attr] + "</span></p>";
}
}
// Add nodes and links count
html += "<p><b>nodes</b>: <span>" + graph.nodes.length + "</span></p>";
html += "<p><b>links</b>: <span>" + graph.links.length + "</span></p>";
metadataInner.html(html);
metadata.classed("njg-hidden", false);
// onLoad callback
opts.onLoad(url, opts);
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
labels.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
})
.on("end", function(){
force.stop();
// onEnd callback
opts.onEnd(url, opts);
});
return force;
};
if(typeof(url) === "object") {
processJson(url);
}
else {
/**
* Parse the provided json file
* and call processJson() function
*
* @param {string} url The provided json file
* @param {function} error
*/
d3.json(url, function(error, graph) {
if(error) { throw error; }
/**
* Check if the json contains a NetworkCollection
*/
if(graph.type === "NetworkCollection") {
var selectGroup = body.append("div").attr("id", "njg-select-group"),
select = selectGroup.append("select")
.attr("id", "select");
str = graph;
select.append("option")
.attr({
"value": "",
"selected": "selected",
"name": "default",
"disabled": "disabled"
})
.html("Choose the network to display");
graph.collection.forEach(function(structure) {
select.append("option").attr("value", structure.type).html(structure.type);
// Collect each network json structure
selected[structure.type] = structure;
});
select.on("change", function() {
selectGroup.attr("class", "njg-hidden");
// Call selected json structure
processJson(selected[this.options[this.selectedIndex].value]);
});
}
else {
processJson(graph);
}
});
}
};
})();

View File

@ -41,35 +41,41 @@
In the code st is the data obtained from the json call
*/
function TablePooler (time, jsonurl, getparams, table_id, callback) {
this.table = document.getElementById(table_id);
function TablePooler (time, jsonurl, getparams, div_id, callback) {
this.div_id = div_id;
this.div = document.getElementById(div_id);
this.callback = callback;
this.jsonurl = jsonurl;
this.getparams = getparams;
this.time = time;
/* clear all rows */
this.clear = function(){
while( this.table.rows.length > 1 ) this.table.deleteRow(1);
}
this.start = function(){
XHR.poll(this.time, this.jsonurl, this.getparams, function(x, st){
var data = this.callback(st);
var content, tr, td;
this.clear();
var content;
for (var i = 0; i < data.length; i++){
tr = this.table.insertRow(-1);
tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
rowId = "trDiv_" + this.div_id + i;
rowDiv = document.getElementById(rowId);
if (rowDiv === null) {
rowDiv = document.createElement("div");
rowDiv.id = rowId;
rowDiv.className = "tr";
this.div.appendChild(rowDiv);
}
for (var j = 0; j < data[i].length; j++){
td = tr.insertCell(-1);
if (data[i][j].length == 2) {
td.colSpan = data[i][j][1];
content = data[i][j][0];
cellId = "tdDiv_" + this.div_id + i + j;
cellDiv = document.getElementById(cellId);
if (cellDiv === null) {
cellDiv = document.createElement("div");
cellDiv.id = cellId;
cellDiv.className = "td";
rowDiv.appendChild(cellDiv);
}
if (typeof data[i][j] !== 'undefined' && data[i][j].length == 2) {
content = data[i][j][0] + "/" + data[i][j][1];
}
else content = data[i][j];
td.innerHTML = content;
cellDiv.innerHTML = content;
}
}
}.bind(this));