From a1e102093f7bf589d085e5315e6abd7f337d9ea6 Mon Sep 17 00:00:00 2001 From: p4u Date: Thu, 6 Dec 2018 12:17:49 +0100 Subject: [PATCH 1/4] luci-app-bmx7: refactory, multiple fixes and add topology graph Signed-off-by: p4u --- .../usr/lib/lua/luci/controller/bmx7.lua | 28 +- .../view/admin_status/index/bmx7_nodes.htm | 40 ++ .../usr/lib/lua/luci/view/bmx7/nodes_j.htm | 76 +-- .../usr/lib/lua/luci/view/bmx7/status_j.htm | 198 +++--- .../usr/lib/lua/luci/view/bmx7/topology.htm | 54 ++ .../usr/lib/lua/luci/view/bmx7/tunnels_j.htm | 76 +++ .../resources/bmx7/css/netjsongraph-theme.css | 59 ++ .../resources/bmx7/css/netjsongraph.css | 62 ++ .../resources/bmx7/js/netjsongraph.js | 568 ++++++++++++++++++ .../luci-static/resources/bmx7/js/polling.js | 40 +- 10 files changed, 1005 insertions(+), 196 deletions(-) create mode 100644 luci-app-bmx7/files/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm create mode 100644 luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/topology.htm create mode 100644 luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/tunnels_j.htm create mode 100644 luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph-theme.css create mode 100644 luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph.css create mode 100644 luci-app-bmx7/files/www/luci-static/resources/bmx7/js/netjsongraph.js diff --git a/luci-app-bmx7/files/usr/lib/lua/luci/controller/bmx7.lua b/luci-app-bmx7/files/usr/lib/lua/luci/controller/bmx7.lua index ed62628..a511dd9 100644 --- a/luci-app-bmx7/files/usr/lib/lua/luci/controller/bmx7.lua +++ b/luci-app-bmx7/files/usr/lib/lua/luci/controller/bmx7.lua @@ -59,9 +59,19 @@ function index() entry(place,call("action_status_j"),"Status",0) table.remove(place) - -- Nodes list + -- 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",1) + 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) end @@ -70,8 +80,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 diff --git a/luci-app-bmx7/files/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm b/luci-app-bmx7/files/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm new file mode 100644 index 0000000..8a6aefa --- /dev/null +++ b/luci-app-bmx7/files/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm @@ -0,0 +1,40 @@ +
+
+ <%:Bmx7 mesh nodes%> +
+
+
+
<%:Name%>
+
<%:Short ID%>
+
<%:S/s/T/t%>
+
<%:Primary IPv6%>
+
<%:Via Neighbour%>
+
<%:Device%>
+
<%:Metric%>
+
<%:Last Ref%>
+
+
+
+
+
+ + + diff --git a/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/nodes_j.htm b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/nodes_j.htm index 347af42..a631c93 100644 --- a/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/nodes_j.htm +++ b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/nodes_j.htm @@ -27,19 +27,16 @@ +

Mesh nodes

@@ -70,40 +64,38 @@ Tip: click the icon to see individual node information.
-
+ + +
<%:Originators%> - - - - - - - - - - - - - - - - -
<%:Name%><%:Short ID%><%:S/s/T/t%><%:Primary IPv6 address%><%:Via neighbour%><%:Metric%><%:Last desc.%><%:Last ref.%><%: %>

<%:Collecting data...%>
-
+
+
+
+
+
<%:Name%>
+
<%:Short ID%>
+
<%:S/s/T/t%>
+
<%:Primary IPv6%>
+
<%:Via Neighbour%>
+
<%:Metric%>
+
<%:Last Desc%>
+
<%:Last Ref%>
+
<%: %>
+
+
+
+ <%+footer%> - diff --git a/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/status_j.htm b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/status_j.htm index c6920f9..b7609d7 100644 --- a/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/status_j.htm +++ b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/status_j.htm @@ -2,30 +2,6 @@ - -
@@ -38,145 +14,117 @@
-
+
<%:Node configuration%> - - - - - - - - - - - -
<%:Short ID%><%:Node name%><%:Primary IPv6 address%><%:Node key%><%:BMX7 revision%>

<%:Collecting data...%>
-
+
+
+
+
<%:Short ID%>
+
<%:Node name%>
+
<%:Primary IPv6 address%>
+
<%:Node key%>
+
<%:Short DHash%>
+
<%:BMX7 revision%>
+
+
+
+
-
+ +
<%:Node status%> - - - - - - - - - - +
+
+
+
<%:Nodes seen%>
+
<%:Neighbours%>
+
<%:Tunnelled IPv6 address%>
+
<%:Tunnelled IPv4 address%>
+
<%:Uptime%>
+
<%:CPU usage%>
+
<%:Memory usage%>
+
<%:Tx queue%>
+
+
+
+ - - - - -
<%:Nodes seen%><%:Neighbours%><%:Tunnelled IPv6 address%><%:Tunnelled IPv4 address%><%:Uptime%><%:CPU usage%><%:Memory usage%><%:Tx queue%>

<%:Collecting data...%>
-
+
+ <%:Network interfaces%> +
+
+
+
<%:Interface%>
+
<%:State%>
+
<%:Type%>
+
<%:Max rate%>
+
<%:LinkLocal Ipv6%>
+
<%:RX BpP%>
+
<%:TX BpP%>
+
+
+
+
-
- <%:Interfaces%> - - - - - - - - - - - - - -
<%:Interface%><%:State%><%:Type%><%:Max. rate%><%:Link-local IPv6 address%><%:Rx BpP%><%:Tx BpP%>

<%:Collecting data...%>
-
- -
+
<%:Links%> - - - - - - - - - - - - - - - - -
- +
+ +
+ <%+footer%> diff --git a/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/topology.htm b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/topology.htm new file mode 100644 index 0000000..58ce9fd --- /dev/null +++ b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/topology.htm @@ -0,0 +1,54 @@ +<%+header%> + + + + + + +<%+footer%> + diff --git a/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/tunnels_j.htm b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/tunnels_j.htm new file mode 100644 index 0000000..aaa79a8 --- /dev/null +++ b/luci-app-bmx7/files/usr/lib/lua/luci/view/bmx7/tunnels_j.htm @@ -0,0 +1,76 @@ +<%# + Copyright (C) 2011 Pau Escrich + Contributors Lluis Esquerda + + 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%> + + + +
+

Gateway announcements

+
Networks announced by mesh nodes
+ +
+ <%:Announcements%> +
+
+
+
<%:Status%>
+
<%:Name%>
+
<%:Node%>
+
<%:Network%>
+
<%:Bandwith%>
+
<%:Local net%>
+
<%:Path Metric%>
+
<%:Tun Metric%>
+
<%:Rating%>
+
+
+
+
+ +
+ + + +<%+footer%> diff --git a/luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph-theme.css b/luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph-theme.css new file mode 100644 index 0000000..276d362 --- /dev/null +++ b/luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph-theme.css @@ -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/luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph.css b/luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph.css new file mode 100644 index 0000000..556c520 --- /dev/null +++ b/luci-app-bmx7/files/www/luci-static/resources/bmx7/css/netjsongraph.css @@ -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/luci-app-bmx7/files/www/luci-static/resources/bmx7/js/netjsongraph.js b/luci-app-bmx7/files/www/luci-static/resources/bmx7/js/netjsongraph.js new file mode 100644 index 0000000..66d0a5f --- /dev/null +++ b/luci-app-bmx7/files/www/luci-static/resources/bmx7/js/netjsongraph.js @@ -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 = "

id: " + n.id + "

"; + if(n.label) { html += "

label: " + n.label + "

"; } + if(n.properties) { + for(var key in n.properties) { + if(!n.properties.hasOwnProperty(key)) { continue; } + html += "

"+key.replace(/_/g, " ")+": " + n.properties[key] + "

"; + } + } + if(n.linkCount) { html += "

links: " + n.linkCount + "

"; } + if(n.local_addresses) { + html += "

local addresses:
" + n.local_addresses.join('
') + "

"; + } + 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 = "

source: " + (l.source.label || l.source.id) + "

"; + html += "

target: " + (l.target.label || l.target.id) + "

"; + html += "

cost: " + l.cost + "

"; + if(l.properties) { + for(var key in l.properties) { + if(!l.properties.hasOwnProperty(key)) { continue; } + html += "

"+ key.replace(/_/g, " ") +": " + l.properties[key] + "

"; + } + } + 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 += "

" + graph.label + "

"; + } + for(var i in attrs) { + var attr = attrs[i]; + if(graph[attr]) { + html += "

" + attr + ": " + graph[attr] + "

"; + } + } + // Add nodes and links count + html += "

nodes: " + graph.nodes.length + "

"; + html += "

links: " + graph.links.length + "

"; + 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/luci-app-bmx7/files/www/luci-static/resources/bmx7/js/polling.js b/luci-app-bmx7/files/www/luci-static/resources/bmx7/js/polling.js index 4a382eb..234391a 100644 --- a/luci-app-bmx7/files/www/luci-static/resources/bmx7/js/polling.js +++ b/luci-app-bmx7/files/www/luci-static/resources/bmx7/js/polling.js @@ -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)); From 9345df949e52d9279ea070e7c650c822eb245aa0 Mon Sep 17 00:00:00 2001 From: p4u Date: Thu, 6 Dec 2018 12:25:51 +0100 Subject: [PATCH 2/4] luci-app-bmx7: update version, dependencies and maintainer Signed-off-by: p4u --- luci-app-bmx7/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/luci-app-bmx7/Makefile b/luci-app-bmx7/Makefile index a9e96f7..dbd71c8 100644 --- a/luci-app-bmx7/Makefile +++ b/luci-app-bmx7/Makefile @@ -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 + DEPENDS:=+luci-lib-json +luci-mod-admin-full +bmx7 +bmx7-json + MAINTAINER:= Roger Pueyo and Pau Escrich endef define Package/luci-app-bmx7/description From a7d7f4baa888bbccc17ac2020ff98994236c8af0 Mon Sep 17 00:00:00 2001 From: Roger Pueyo Centelles Date: Fri, 7 Dec 2018 17:47:22 +0100 Subject: [PATCH 3/4] luci-app-bmx7: fix bmx7-info script's indentation Signed-off-by: Roger Pueyo Centelles --- luci-app-bmx7/files/www/cgi-bin/bmx7-info | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/luci-app-bmx7/files/www/cgi-bin/bmx7-info b/luci-app-bmx7/files/www/cgi-bin/bmx7-info index 8c501c5..c3f4418 100755 --- a/luci-app-bmx7/files/www/cgi-bin/bmx7-info +++ b/luci-app-bmx7/files/www/cgi-bin/bmx7-info @@ -1,7 +1,7 @@ #!/bin/sh # Copyright © 2011 Pau Escrich # Contributors Jo-Philipp Wich -# Roger Pueyo Centelles +# Roger Pueyo Centelles # # 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,7 +79,7 @@ print_query() { done echo -n " ]" } - } + } # If the query is a file, just printing the file [ -f "$BMX7_DIR/$1" ] && cat "$BMX7_DIR/$1"; From 3e259f8ec58f982ce565533f14006102ec9f6565 Mon Sep 17 00:00:00 2001 From: Roger Pueyo Centelles Date: Fri, 7 Dec 2018 18:04:10 +0100 Subject: [PATCH 4/4] luci-app-bmx7: fix bmx7-info script's "$info" call This commit fixes a bug in bmx7-info script's "$info" call when no interfaces are being used by BMX7, or when no links have been established. In those cases, the generated JSON output struct contained extra commas, which made it invalid. Closes #430 Signed-off-by: Roger Pueyo Centelles --- luci-app-bmx7/files/www/cgi-bin/bmx7-info | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/luci-app-bmx7/files/www/cgi-bin/bmx7-info b/luci-app-bmx7/files/www/cgi-bin/bmx7-info index c3f4418..7388ed1 100755 --- a/luci-app-bmx7/files/www/cgi-bin/bmx7-info +++ b/luci-app-bmx7/files/www/cgi-bin/bmx7-info @@ -82,7 +82,7 @@ print_query() { } # 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 @@ -94,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