luci-app-bmx7: transfer from routing
authorPaul Spooren <mail@aparcar.org>
Fri, 5 Jul 2019 23:25:07 +0000 (01:25 +0200)
committerPaul Spooren <mail@aparcar.org>
Wed, 10 Jul 2019 15:44:13 +0000 (17:44 +0200)
The Makefile is minified as the LuCi build system does most of the job.

Signed-off-by: Paul Spooren <mail@aparcar.org>
16 files changed:
applications/luci-app-bmx7/Makefile [new file with mode: 0644]
applications/luci-app-bmx7/root/etc/config/luci-bmx7 [new file with mode: 0755]
applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua [new file with mode: 0644]
applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm [new file with mode: 0644]
applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm [new file with mode: 0644]
applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm [new file with mode: 0644]
applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm [new file with mode: 0644]
applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm [new file with mode: 0644]
applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info [new file with mode: 0755]
applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png [new file with mode: 0644]
applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css [new file with mode: 0644]
applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css [new file with mode: 0644]
applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js [new file with mode: 0644]
applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js [new file with mode: 0644]
applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png [new file with mode: 0644]
applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png [new file with mode: 0644]

diff --git a/applications/luci-app-bmx7/Makefile b/applications/luci-app-bmx7/Makefile
new file mode 100644 (file)
index 0000000..d614327
--- /dev/null
@@ -0,0 +1,14 @@
+# See /LICENSE for more information.
+# This is free software, licensed under the GNU General Public License v2.
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for BMX7
+LUCI_DEPENDS:=+luci-lib-json +luci-mod-admin-full +bmx7 +bmx7-json
+PKG_MAINTAINER:= Roger Pueyo <roger.pueyo@guifi.net> \
+       Pau Escrich <p4u@dabax.net>
+PKG_LICENSE:=GPL-2.0-or-later
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/applications/luci-app-bmx7/root/etc/config/luci-bmx7 b/applications/luci-app-bmx7/root/etc/config/luci-bmx7
new file mode 100755 (executable)
index 0000000..46a7727
--- /dev/null
@@ -0,0 +1,7 @@
+config 'bmx7' 'luci'
+       option ignore '0'
+       option place 'admin network BMX7'
+       #option place 'qmp Mesh'
+       option position '3'
+       #option json 'http://127.0.0.1/cgi-bin/bmx7-info?'
+       option json 'exec:/www/cgi-bin/bmx7-info -s'
diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua b/applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua
new file mode 100644 (file)
index 0000000..482fb5d
--- /dev/null
@@ -0,0 +1,101 @@
+--[[
+    Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+    Contributors Jo-Philipp Wich <xm@subsignal.org>
+                 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
+    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".
+--]]
+
+module("luci.controller.bmx7", package.seeall)
+
+function index()
+       local place = {}
+       local ucim = require "luci.model.uci"
+       local uci = ucim.cursor()
+
+       -- checking if ignore is on
+       if uci:get("luci-bmx7","luci","ignore") == "1" then
+               return nil
+       end
+
+       -- getting value from uci database
+       local uci_place = uci:get("luci-bmx7","luci","place")
+
+       -- default values
+       if uci_place == nil then
+               place = {"bmx7"}
+       else
+               local util = require "luci.util"
+               place = util.split(uci_place," ")
+       end
+
+       -- getting position of menu
+       local uci_position = uci:get("luci-bmx7","luci","position")
+
+
+       ---------------------------
+       -- Placing the pages in the menu
+       ---------------------------
+
+       -- Status (default)
+       entry(place,call("action_status_j"),place[#place],tonumber(uci_position))
+
+       table.insert(place,"Status")
+       entry(place,call("action_status_j"),"Status",0)
+       table.remove(place)
+
+       -- 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
+
+
+function action_status_j()
+       luci.template.render("bmx7/status_j", {})
+end
+
+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
diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm
new file mode 100644 (file)
index 0000000..8a6aefa
--- /dev/null
@@ -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>
diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm
new file mode 100644 (file)
index 0000000..a631c93
--- /dev/null
@@ -0,0 +1,155 @@
+<%#
+   Copyright © 2011 Pau Escrich <pau@dabax.net>
+   Contributors Lluis Esquerda <eskerda@gmail.com>
+                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
+   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>
+
+
+<style>
+       div.hideme{
+               display: none;
+       }
+       div.info{
+               background: #FFF;
+               border: solid 0px;
+               height: 190px;
+               display: block;
+               overflow: auto;
+       }
+       div.inforow{
+               text-align:left;
+               display:inline-block;
+               margin:10px;
+               vertical-align:top;
+               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>
+<div class="cbi-map-descr"></div>
+<div id="extra-info" class="info">
+       <br />
+       <center>
+       Tip: click the <img src="<%=resource%>/bmx7/world.png" /> icon to see individual node information.
+       </center>
+</div>
+
+
+<div class="cbi-section">
+       <legend><%:Originators%></legend>
+       <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 ) {
+                       document.getElementById('extra-info').innerHTML = document.getElementById(id).innerHTML;
+               }
+               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;
+                               var nodeId = originator.nodeId;
+                               var extensions = originator.name;
+                               var SsTt = originator.S+'/'+originator.s+'/'+originator.T+'/'+originator.t;
+                               var nodeKey = originator.nodeKey;
+                               var descSize = originator.descSize;
+                               var primaryIp = originator.primaryIp;
+                               var nbName = originator.nbName;
+                               var dev = originator.dev;
+                               var nbLocalIp = originator.nbLocalIp;
+                               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">'
+                       + "<div class='inforow'>"
+                       + "<h4><u>" + name  + '</u></h4>\n'
+                       + '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 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%>
diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm
new file mode 100644 (file)
index 0000000..b7609d7
--- /dev/null
@@ -0,0 +1,130 @@
+<%+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">
+       <center>
+               <img src="<%=resource%>/bmx7/bmx7logo.png" />
+               <br />
+               <br />
+               A mesh routing protocol for Linux devices.<br />
+               Visit <a href="http://bmx6.net">bmx6.net</a> for more information.<br />
+               <br />
+       </center>
+
+<div class="cbi-map-descr"></div>
+
+<div class="cbi-section">
+       <legend><%:Node configuration%></legend>
+       <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>
+
+
+<div class="cbi-section">
+       <legend><%:Node status%></legend>
+       <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>
+
+<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>
+
+
+<div class="cbi-section">
+       <legend><%:Links%></legend>
+       <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_div", function(st){
+               var res = Array();
+               var sta = st.info[0].status;
+               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_div", function(st){
+               var res = Array();
+               var sta = st.info[0].status;
+               var mem = st.info[3].memory.bmx7;
+               var txQ = sta.txQ.split('/');
+               var ptxQ = '<p style="color:rgb('+parseInt(255*txQ[0]/txQ[1])+','+parseInt(128*(txQ[1]-txQ[0])/txQ[1])+',0)")>'+sta.txQ+'</p>';
+               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_div", function(st){
+               var res = Array();
+               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]);
+               });
+               return res;
+       });
+
+       new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "links_div", function(st){
+               var res = Array();
+               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.rts]);
+               });
+               return res;
+       });
+
+//]]></script>
+
+<%+footer%>
diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm
new file mode 100644 (file)
index 0000000..58ce9fd
--- /dev/null
@@ -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%>                                                                        
+
diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm
new file mode 100644 (file)
index 0000000..aaa79a8
--- /dev/null
@@ -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%>
diff --git a/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info b/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info
new file mode 100755 (executable)
index 0000000..7388ed1
--- /dev/null
@@ -0,0 +1,133 @@
+#!/bin/sh
+#    Copyright © 2011 Pau Escrich
+#    Contributors Jo-Philipp Wich <xm@subsignal.org>
+#               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
+#    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".
+#
+#    This script gives information about bmx7
+#    Can be executed from a linux shell: ./bmx7-info -s links
+#    Or from web interfae (with cgi enabled): http://host/cgi-bin/bmx7-info?links
+#    If you ask for a directory you wil get the directory contents in JSON forman
+
+BMX7_DIR="$(uci get bmx7.general.runtimeDir 2>/dev/null)" || BMX7_DIR="/var/run/bmx7/json"
+
+#Checking if shell mode or cgi-bin mode
+if [ "$1" == "-s" ]; then
+       QUERY="$2"
+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
+}
+
+print_mem() {
+       echo -n '{ "memory": { "bmx7": "'
+       cat /proc/$(cat /var/run/bmx7/pid)/status |grep -i VmSize | tr -s " " | cut -d " " -f 2,3 | tr -d "\n"
+       echo '"}}'
+}
+
+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 "{ \"name\": \"$f\" }"
+                       [ $i -lt $total ]  && echo -n ','
+                       i=$(( $i + 1 ))
+               done
+               echo -n " ] }"
+
+       # If /all has been specified, printing all the files together
+               } || {
+               comma=""
+               echo -n "[ "
+               for entry in "$BMX7_DIR/$1/"*; do
+                       [ -f "$entry" ] &&
+                       {
+                               ${comma:+echo "$comma"}
+                               tr -d '\n' < "$entry"
+                               comma=","
+                       }
+               done
+               echo -n " ]"
+               }
+       }
+
+       # If the query is a file, just printing the file
+       [ -f "$BMX7_DIR/$1" ] && [ -s "$BMX7_DIR/$1" ] && cat "$BMX7_DIR/$1" && return 0 || return 1
+}
+
+if [ "${QUERY##*/}" == "all" ]; then
+       QUERY="${QUERY%/all}"
+       QALL=1
+fi
+
+if [ "$QUERY" == '$info' ]; then
+       echo '{ "info": [ '
+       print_query status
+       echo -n ","
+       print_query interfaces && echo -n "," || echo -n '{ "interfaces": "" },'
+       print_query links && echo -n "," || echo -n '{ "links": "" },'
+       print_mem
+       echo "] }"
+fi
+
+if [ "$QUERY" == '$neighbours' ]; then
+       QALL=1
+       echo '{ "neighbours": [ '
+       echo '{ "originators": '
+       print_query originators
+       echo '}, '
+       echo '{ "descriptions": '
+       print_query descriptions
+       echo "} ] }"
+       exit 0
+
+else if [ "$QUERY" == '$tunnels' ]; then
+       bmx7 -c --jshow tunnels /r=0
+       exit 0
+
+       else if [ "$QUERY" == '$originators' ]; then
+               bmx7 -c --jshow originators /r=0
+               exit 0
+
+               else
+               check_path "$BMX7_DIR/$QUERY"
+               print_query $QUERY
+               exit 0
+               fi
+       fi
+fi
+fi
+
+ls -1F "$BMX7_DIR"
+exit 0
+
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png
new file mode 100644 (file)
index 0000000..c7d9cea
Binary files /dev/null and b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png differ
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css
new file mode 100644 (file)
index 0000000..276d362
--- /dev/null
@@ -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;
+}
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css
new file mode 100644 (file)
index 0000000..556c520
--- /dev/null
@@ -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;
+}
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js
new file mode 100644 (file)
index 0000000..66d0a5f
--- /dev/null
@@ -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 Barnes–Hut 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);
+                }
+            });
+        }
+     };
+})();
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js
new file mode 100644 (file)
index 0000000..234391a
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+    Copyright © 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".
+*/
+
+
+/*
+       Table pooler is a function to easy call XHR poller. 
+
+       new TablePooler(5,"/cgi-bin/bmx7-info", {'status':''}, "status_table", function(st){
+               var table = Array()
+               table.push(st.first,st.second)
+               return table
+       }
+
+       The parameters are: 
+               polling_time: time between pollings
+               json_url: the json url to fetch the data
+               json_call: the json call
+               output_table_id: the table where javascript will put the data
+               callback_function: the function that will be executed each polling_time
+       
+       The callback_function must return an array of arrays (matrix).
+       In the code st is the data obtained from the json call
+*/
+
+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;
+
+       this.start = function(){
+               XHR.poll(this.time, this.jsonurl, this.getparams, function(x, st){
+                       var data = this.callback(st);
+                       var content;
+                       for (var i = 0; i < data.length; i++){
+                               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++){
+                                       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];
+                                       cellDiv.innerHTML = content;
+                               }
+                       }
+               }.bind(this));
+       }
+
+
+       this.start();
+}
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png
new file mode 100644 (file)
index 0000000..29b53c9
Binary files /dev/null and b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png differ
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png
new file mode 100644 (file)
index 0000000..f5f3105
Binary files /dev/null and b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png differ