summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Donald2026-02-23 21:03:39 +0000
committerPaul Donald2026-02-24 00:23:31 +0000
commitf578786152c9e792202a8a9d08312a72849bf6d5 (patch)
treecfddb76c10c2348b84230a4f1a07ef5c8cdbe48a
parentd2b850b69c8835428828fc8b5c801556763a8d44 (diff)
downloadluci-f578786152c9e792202a8a9d08312a72849bf6d5.tar.gz
luci-app-bmx7: convert to JS
migrate away from the old luci-app lua control system. See: https://github.com/openwrt/luci/issues/7310 The lua app was relatively broken prior to conversion. Strangely, bmx7-uci-config package is what installs the init.d service and uci config. Signed-off-by: Paul Donald <newtwen+github@gmail.com>
-rw-r--r--applications/luci-app-bmx7/Makefile2
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/bmx7logo.png (renamed from applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png)bin3318 -> 3318 bytes
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/css/netjsongraph-theme.css59
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/css/netjsongraph.css58
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/js/netjsongraph.js568
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/js/polling.js (renamed from applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js)50
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/world.png (renamed from applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png)bin1777 -> 1777 bytes
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/world_small.png (renamed from applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png)bin820 -> 820 bytes
-rw-r--r--applications/luci-app-bmx7/htdocs/luci-static/resources/view/bmx7/config.js67
-rwxr-xr-xapplications/luci-app-bmx7/root/etc/config/luci-bmx77
-rw-r--r--applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua101
-rw-r--r--applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm40
-rw-r--r--applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm130
-rw-r--r--applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm54
-rw-r--r--applications/luci-app-bmx7/root/usr/share/luci/menu.d/luci-app-bmx7.json73
-rw-r--r--applications/luci-app-bmx7/root/usr/share/rpcd/acl.d/luci-app-bmx7.json18
-rwxr-xr-xapplications/luci-app-bmx7/root/www/cgi-bin/bmx7-info151
-rw-r--r--applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css59
-rw-r--r--applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css62
-rw-r--r--applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js568
-rw-r--r--applications/luci-app-bmx7/ucode/template/bmx7/bmxnodes.ut (renamed from applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm)80
-rw-r--r--applications/luci-app-bmx7/ucode/template/bmx7/bmxstatus.ut132
-rw-r--r--applications/luci-app-bmx7/ucode/template/bmx7/bmxtopology.ut53
-rw-r--r--applications/luci-app-bmx7/ucode/template/bmx7/bmxtunnels.ut (renamed from applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm)58
24 files changed, 1199 insertions, 1191 deletions
diff --git a/applications/luci-app-bmx7/Makefile b/applications/luci-app-bmx7/Makefile
index 48e6fc023a..242f6a1dfa 100644
--- a/applications/luci-app-bmx7/Makefile
+++ b/applications/luci-app-bmx7/Makefile
@@ -4,7 +4,7 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for BMX7
-LUCI_DEPENDS:=+luci-compat +luci-lib-json +luci-base +bmx7 +bmx7-json
+LUCI_DEPENDS:=+luci-base +bmx7 +bmx7-uci-config +bmx7-json +bmx7-tun +bmx7-iwinfo
PKG_MAINTAINER:= Roger Pueyo <roger.pueyo@guifi.net> \
Pau Escrich <p4u@dabax.net>
PKG_LICENSE:=GPL-2.0-or-later
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/bmx7logo.png
index c247be9e2d..c247be9e2d 100644
--- a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png
+++ b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/bmx7logo.png
Binary files differ
diff --git a/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/css/netjsongraph-theme.css b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/css/netjsongraph-theme.css
new file mode 100644
index 0000000000..39b77f1ae9
--- /dev/null
+++ b/applications/luci-app-bmx7/htdocs/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/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/css/netjsongraph.css b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/css/netjsongraph.css
new file mode 100644
index 0000000000..255ec01960
--- /dev/null
+++ b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/css/netjsongraph.css
@@ -0,0 +1,58 @@
+.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{
+ z-index: 0;
+}
+
+.njg-close{
+ cursor: pointer;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+}
+.njg-close:before { content: "\2716"; }
+
+.njg-metadata{
+ z-index: 0;
+}
+
+.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/htdocs/luci-static/resources/bmx7/js/netjsongraph.js b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/js/netjsongraph.js
new file mode 100644
index 0000000000..7225d8b5d6
--- /dev/null
+++ b/applications/luci-app-bmx7/htdocs/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) {
+ let 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) {
+ let n = element.node(),
+ nPos = n.getBoundingClientRect();
+ let 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){
+ let 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",
+ "trans"+"late(" + 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) {
+ let nodesMap = {},
+ nodes = graph.nodes.slice(), // copy
+ links = graph.links.slice(), // copy
+ nodes_length = graph.nodes.length,
+ links_length = graph.links.length;
+
+ for(let i = 0; i < nodes_length; i++) {
+ // count how many links every node has
+ nodes[i].linkCount = 0;
+ nodesMap[nodes[i].id] = i;
+ }
+ for(let c = 0; c < links_length; c++) {
+ let 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) {
+ let 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(let 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) {
+ let 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(let 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") {
+ let 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");
+ }
+ }
+ let 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);
+ };
+
+ let 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();
+
+ let link = panner.selectAll(".link")
+ .data(links)
+ .enter().append("line")
+ .attr("class", function (link) {
+ let 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) {
+ let 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);
+
+ let 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) {
+ let 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");
+ }
+
+ let attrs = ["protocol",
+ "version",
+ "revision",
+ "metric",
+ "router_id",
+ "topology_id"],
+ html = "";
+ if(graph.label) {
+ html += "<h3>" + graph.label + "</h3>";
+ }
+ for(let i in attrs) {
+ let 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 "trans"+"late(" + 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") {
+ let 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/htdocs/luci-static/resources/bmx7/js/polling.js
index 234391a975..3518627c4a 100644
--- a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js
+++ b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/js/polling.js
@@ -1,23 +1,23 @@
/*
- Copyright © 2011 Pau Escrich <pau@dabax.net>
- Contributors Lluis Esquerda <eskerda@gmail.com>
+ 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 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.
+ 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.
+ 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".
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
*/
@@ -25,20 +25,20 @@
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()
+ let 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
+ polling_time: time between polls
+ 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
+ In the code st is the data obtained from the JSON call
*/
function TablePooler (time, jsonurl, getparams, div_id, callback) {
@@ -51,9 +51,9 @@ function TablePooler (time, jsonurl, getparams, div_id, callback) {
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++){
+ let data = this.callback(st);
+ let content, rowDiv, rowId, cellDiv, cellId;
+ for (let i = 0; i < data.length; i++){
rowId = "trDiv_" + this.div_id + i;
rowDiv = document.getElementById(rowId);
if (rowDiv === null) {
@@ -62,7 +62,7 @@ function TablePooler (time, jsonurl, getparams, div_id, callback) {
rowDiv.className = "tr";
this.div.appendChild(rowDiv);
}
- for (var j = 0; j < data[i].length; j++){
+ for (let j = 0; j < data[i].length; j++){
cellId = "tdDiv_" + this.div_id + i + j;
cellDiv = document.getElementById(cellId);
if (cellDiv === null) {
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/world.png
index be7ff1c09f..be7ff1c09f 100644
--- a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png
+++ b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/world.png
Binary files differ
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/world_small.png
index f4a30eaa32..f4a30eaa32 100644
--- a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png
+++ b/applications/luci-app-bmx7/htdocs/luci-static/resources/bmx7/world_small.png
Binary files differ
diff --git a/applications/luci-app-bmx7/htdocs/luci-static/resources/view/bmx7/config.js b/applications/luci-app-bmx7/htdocs/luci-static/resources/view/bmx7/config.js
new file mode 100644
index 0000000000..74fff5817a
--- /dev/null
+++ b/applications/luci-app-bmx7/htdocs/luci-static/resources/view/bmx7/config.js
@@ -0,0 +1,67 @@
+'use strict';
+'require view';
+'require form';
+'require tools.widgets as widgets';
+
+return view.extend({
+ render() {
+ let m, s, o;
+
+ m = new form.Map('bmx7', _('BMX7'));
+ s = m.section(form.NamedSection, 'general');
+ s.anonymous = true;
+
+ o = s.option(form.Value, 'runtimeDir', _('runtimeDir'));
+ o = s.option(form.Value, 'trustedNodesDir', _('trustedNodesDir'));
+
+ s = m.section(form.TypedSection, 'plugin', _('Plugins'));
+ s.addremove = true;
+ s.anonymous = true;
+
+ o = s.option(form.Value, 'plugin', _('Plugin'));
+
+ s = m.section(form.TypedSection, 'dev', _('Devices'));
+ s.addremove = true;
+ s.anonymous = false;
+
+ o = s.option(widgets.DeviceSelect, 'dev', _('Dev'));
+
+ s = m.section(form.TypedSection, 'tunDev', _('Tunnel Devices'));
+ s.addremove = true;
+ s.anonymous = false;
+
+ o = s.option(form.Value, 'tunDev', _('Dev'));
+ o = s.option(form.Value, 'tun6Address', _('tun6Address'));
+ o = s.option(form.Value, 'tun4Address', _('tun4Address'));
+
+ s = m.section(form.TypedSection, 'tunOut', _('Gateway Devices'));
+ s.addremove = true;
+ s.anonymous = true;
+
+ o = s.option(form.Value, 'tunOut', _('tunOut'));
+ o.value('ip4');
+ o.value('ip6');
+
+ o = s.option(form.Value, 'network', _('Network'));
+ o.datatype = 'ipaddr';
+
+ o = s.option(form.Value, 'exportDistance', _('exportDistance'));
+ o.datatype = 'uinteger';
+ o = s.option(form.Value, 'minPrefixLen', _('minPrefixLen'));
+ o.datatype = 'uinteger';
+
+ s = m.section(form.NamedSection, 'luci', _('luci'));
+ s.uciconfig = 'bmx7-luci';
+ s.anonymous = true;
+
+ o = s.option(form.Flag, 'ignore', _('Ignore'));
+ o.default = '0';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'json', _('JSON source'));
+ o.rmempty = false;
+
+ return m.render();
+ },
+});
+
diff --git a/applications/luci-app-bmx7/root/etc/config/luci-bmx7 b/applications/luci-app-bmx7/root/etc/config/luci-bmx7
deleted file mode 100755
index 46a77272f1..0000000000
--- a/applications/luci-app-bmx7/root/etc/config/luci-bmx7
+++ /dev/null
@@ -1,7 +0,0 @@
-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
deleted file mode 100644
index 482fb5db51..0000000000
--- a/applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua
+++ /dev/null
@@ -1,101 +0,0 @@
---[[
- 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
deleted file mode 100644
index c8ddb2d8e8..0000000000
--- a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm
+++ /dev/null
@@ -1,40 +0,0 @@
-<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 src="<%=resource%>/bmx7/js/polling.js"></script>
-<script>
- 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/status_j.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm
deleted file mode 100644
index bfabf1ea50..0000000000
--- a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm
+++ /dev/null
@@ -1,130 +0,0 @@
-<%+header%>
-<script src="<%=resource%>/cbi.js"></script>
-<script 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"><%:Link-local 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 link-local 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>
- new TablePooler(10,"/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(10,"/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(10,"/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(10,"/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
deleted file mode 100644
index 1f09cc4ac1..0000000000
--- a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm
+++ /dev/null
@@ -1,54 +0,0 @@
-<%+header%>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js" integrity="sha512-uy3foVtL4u0+5430l7zZt4PHjVtICfrbu3mtzdanR425sKD7kS5264djeZAzNIV0l4vc1QkFpW2+G5i5KoJIFQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-<script src="<%=resource%>/bmx7/js/netjsongraph.js"></script>
-
-<link href="<%=resource%>/bmx7/css/netjsongraph.css" rel="stylesheet">
-<style>
- 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/share/luci/menu.d/luci-app-bmx7.json b/applications/luci-app-bmx7/root/usr/share/luci/menu.d/luci-app-bmx7.json
new file mode 100644
index 0000000000..a5f75c6c2d
--- /dev/null
+++ b/applications/luci-app-bmx7/root/usr/share/luci/menu.d/luci-app-bmx7.json
@@ -0,0 +1,73 @@
+{
+ "admin/network/bmx7": {
+ "title": "BMX7",
+ "order": 50,
+ "action": {
+ "type": "view",
+ "path": "bmx7/config"
+ },
+ "depends": {
+ "acl": [ "luci-app-bmx7" ]
+ }
+ },
+
+ "admin/network/bmx7/config": {
+ "title": "Config",
+ "order": 1,
+ "action": {
+ "type": "view",
+ "path": "bmx7/config"
+ },
+ "depends": {
+ "acl": [ "luci-app-bmx7" ]
+ }
+ },
+
+ "admin/network/bmx7/status": {
+ "title": "Status",
+ "order": 3,
+ "action": {
+ "type": "template",
+ "path": "bmx7/bmxstatus"
+ },
+ "depends": {
+ "acl": [ "luci-app-bmx7" ]
+ }
+ },
+
+ "admin/network/bmx7/topology": {
+ "title": "Topology",
+ "order": 4,
+ "action": {
+ "type": "template",
+ "path": "bmx7/bmxtopology"
+ },
+ "depends": {
+ "acl": [ "luci-app-bmx7" ]
+ }
+ },
+
+ "admin/network/bmx7/nodes": {
+ "title": "Nodes",
+ "order": 5,
+ "action": {
+ "type": "template",
+ "path": "bmx7/bmxnodes"
+ },
+ "depends": {
+ "acl": [ "luci-app-bmx7" ]
+ }
+ },
+
+ "admin/network/bmx7/tunnels": {
+ "title": "Tunnels",
+ "order": 6,
+ "action": {
+ "type": "template",
+ "path": "bmx7/bmxtunnels"
+ },
+ "depends": {
+ "acl": [ "luci-app-bmx7" ]
+ }
+ }
+}
diff --git a/applications/luci-app-bmx7/root/usr/share/rpcd/acl.d/luci-app-bmx7.json b/applications/luci-app-bmx7/root/usr/share/rpcd/acl.d/luci-app-bmx7.json
new file mode 100644
index 0000000000..174b0ddc24
--- /dev/null
+++ b/applications/luci-app-bmx7/root/usr/share/rpcd/acl.d/luci-app-bmx7.json
@@ -0,0 +1,18 @@
+{
+ "luci-app-bmx7": {
+ "description": "Grant UCI access for luci-app-bmx7",
+ "read": {
+ "file": {
+ "/www/cgi-bin/bmx7-info *": "exec"
+ },
+ "uci": [
+ "bmx7"
+ ]
+ },
+ "write": {
+ "uci": [
+ "bmx7"
+ ]
+ }
+ }
+}
diff --git a/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info b/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info
index 327e3f0f16..c65aae048f 100755
--- a/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info
+++ b/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info
@@ -21,31 +21,38 @@
# 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
+# Can be executed from a Linux shell: ./bmx7-info -s links
+# Or from web interface (with cgi enabled): http://host/cgi-bin/bmx7-info?links
+# If you ask for a directory you will get the directory contents in JSON format
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
+case "${1:-}" in
+ -s)
+ QUERY="$2"
+ ;;
+ *)
+ QUERY="${QUERY_STRING%%&*}"
+ QUERY="${QUERY%%=*}"
+ printf 'Content-type: application/json\n\n'
+ ;;
+esac
check_path() {
- [ -d "$1" ] && path=$(cd $1; pwd)
- [ -f "$1" ] && path=$(cd $1/..; pwd)
- [ $(echo "$path" | grep -c "^$BMX7_DIR") -ne 1 ] && exit 1
-}
+ target="$1"
+
+ # Resolve real absolute path safely
+ resolved="$(cd "$(dirname -- "$target")" 2>/dev/null && pwd -P)/$(basename -- "$target")"
+ [ -e "$resolved" ] || return
+}
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 '"}}'
+ pid="$(pidof bmx7 2>/dev/null)" || return
+ [ -r "/proc/$pid/status" ] || return
+
+ vm=$(awk '/VmSize:/ {print $2" "$3}' "/proc/$pid/status")
+
+ printf '{ "memory": { "bmx7": "%s" }}' "$vm"
}
print_query() {
@@ -53,81 +60,75 @@ print_query() {
[ -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 ))
+ if [ -z "$QALL" ]; then
+ first=1
+ printf '{ "%s": [ ' "$1"
+ for f in "$BMX7_DIR"/$1; do
+ [ -e "$f" ] || continue
+ printf '{ "name": "%s" }' "$(tr -d '\n' < "$f")"
+ [ $first -eq 0 ] && printf ','
+ first=0
done
- echo -n " ] }"
+ printf " ] }"
- # If /all has been specified, printing all the files together
- } || {
- comma=""
- echo -n "[ "
- for entry in "$BMX7_DIR/$1/"*; do
+ # If /all has been specified, print all the files together
+ else
+ first=1
+ printf "[ "
+ for entry in "$BMX7_DIR"/$1; do
[ -f "$entry" ] &&
{
- ${comma:+echo "$comma"}
- tr -d '\n' < "$entry"
- comma=","
+ [ $first -eq 0 ] && printf ','
+ printf "%s" "$(tr -d '\n' < "$entry")"
+ first=0
}
done
- echo -n " ]"
- }
+ printf " ]"
+ fi
}
- # If the query is a file, just printing the file
+ # If the query is a file, just print the file
[ -f "$BMX7_DIR/$1" ] && [ -s "$BMX7_DIR/$1" ] && cat "$BMX7_DIR/$1" && return 0 || return 1
}
-if [ "${QUERY##*/}" == "all" ]; then
+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
+case "$QUERY" in
+ neighbours)
+ QALL=1
+ printf '{ "neighbours": [ '
+ printf '{ "originators": '
+ print_query originators
+ printf '}, '
+ printf '{ "descriptions": '
+ print_query descriptions
+ printf "} ] }"
+ exit 0
+ ;;
+ tunnels)
+ bmx7 -c --jshow tunnels /r=0
+ exit 0
+ ;;
+ originators)
bmx7 -c --jshow originators /r=0
exit 0
-
- else
+ ;;
+ info)
+ printf '{ "info": [ '
+ print_query status && printf "," || printf '{ "status": "" },'
+ print_query interfaces && printf "," || printf '{ "interfaces": "" },'
+ print_query links && printf "," || printf '{ "links": "" },'
+ print_mem
+ printf "] }"
+ ;;
+ *)
check_path "$BMX7_DIR/$QUERY"
- print_query $QUERY
- exit 0
- fi
- fi
-fi
-fi
+ print_query "$QUERY"
+ ;;
+esac
-ls -1F "$BMX7_DIR"
exit 0
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
deleted file mode 100644
index 276d362b13..0000000000
--- a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css
+++ /dev/null
@@ -1,59 +0,0 @@
-.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
deleted file mode 100644
index 556c520767..0000000000
--- a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css
+++ /dev/null
@@ -1,62 +0,0 @@
-.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
deleted file mode 100644
index 1df0db4e33..0000000000
--- a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js
+++ /dev/null
@@ -1,568 +0,0 @@
-// 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",
- "trans"+"late(" + 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 "trans"+"late(" + 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/usr/lib/lua/luci/view/bmx7/nodes_j.htm b/applications/luci-app-bmx7/ucode/template/bmx7/bmxnodes.ut
index bbbeb52c0e..6718e33182 100644
--- a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm
+++ b/applications/luci-app-bmx7/ucode/template/bmx7/bmxnodes.ut
@@ -1,4 +1,4 @@
-<%#
+{#
Copyright © 2011 Pau Escrich <pau@dabax.net>
Contributors Lluis Esquerda <eskerda@gmail.com>
Roger Pueyo Centelles <roger.pueyo@guifi.net>
@@ -19,11 +19,11 @@
The full GNU General Public License is included in this distribution in
the file called "COPYING".
--%>
+-#}
-<%+header%>
-<script src="<%=resource%>/cbi.js"></script>
-<script src="<%=resource%>/bmx7/js/polling.js"></script>
+{% include('header', { }) %}
+
+<script src="{{ resource }}/bmx7/js/polling.js"></script>
<style>
@@ -61,26 +61,26 @@
<div id="extra-info" class="info">
<br />
<center>
- Tip: click the <img src="<%=resource%>/bmx7/world.png" /> icon to see individual node information.
+ Tip: click the <img src="{{ resource }}/bmx7/world.png" /> icon to see individual node information.
</center>
</div>
<div class="cbi-section">
- <legend><%:Originators%></legend>
+ <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 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>
@@ -89,31 +89,31 @@
</div>
<script>
- var displayExtraInfo = function ( id ) {
+ let 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>';
+ new TablePooler(5,"/cgi-bin/bmx7-info", {'originators':'1'}, "nodes_div", function(st){
+ let infoicon = "{{ resource }}/bmx7/world_small.png";
+ let originators = st?.originators;
+ let res = Array();
+ originators.forEach(function(originator, i){
+ let name = originator?.name;
+ let shortId = originator?.shortId;
+ let nodeId = originator?.nodeId;
+ let extensions = originator?.name;
+ let SsTt = originator?.S+'/'+originator?.s+'/'+originator?.T+'/'+originator?.t;
+ let nodeKey = originator?.nodeKey;
+ let descSize = originator?.descSize;
+ let primaryIp = originator?.primaryIp;
+ let nbName = originator?.nbName;
+ let dev = originator?.dev;
+ let nbLocalIp = originator?.nbLocalIp;
+ let metric = originator?.metric;
+ let lastDesc = originator?.lastDesc;
+ let lastRef = originator?.lastRef;
+
+ let extrainfo = '<a onclick="displayExtraInfo(\'ip-' + i + '\')"><img src="' + infoicon + '" / ></a>';
+ let extrainfo_link = '<a onclick="displayExtraInfo(\'ip-' + i + '\')">' + '<img src="' + infoicon + '" />' + '</a>';
extrainfo = '<div id="ip-'+ i +'" class="hideme">'
+ "<div class='inforow'>"
@@ -152,4 +152,4 @@
});
</script>
-<%+footer%>
+{% include('footer', { }) %}
diff --git a/applications/luci-app-bmx7/ucode/template/bmx7/bmxstatus.ut b/applications/luci-app-bmx7/ucode/template/bmx7/bmxstatus.ut
new file mode 100644
index 0000000000..8063868cf9
--- /dev/null
+++ b/applications/luci-app-bmx7/ucode/template/bmx7/bmxstatus.ut
@@ -0,0 +1,132 @@
+{% include('header', { }) %}
+
+
+<script 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">{{ _('Link-local 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 link-local 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>
+ new TablePooler(10,"/cgi-bin/bmx7-info", {'info':'1'}, "config_div", function(st){
+ let res = Array();
+ let sta = st?.info[0]?.status;
+ res.push([sta?.shortId, sta?.name, sta?.primaryIp, sta?.nodeKey, sta?.shortDhash, sta?.revision]);
+ return res;
+ });
+
+ new TablePooler(10,"/cgi-bin/bmx7-info", {'info':'1'}, "status_div", function(st){
+ let res = Array();
+ let sta = st?.info[0]?.status;
+ let mem = st?.info[3]?.memory?.bmx7;
+ let txQ = sta?.txQ?.split('/');
+ let 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(10,"/cgi-bin/bmx7-info", {'info':'1'}, "ifaces_div", function(st){
+ let res = Array();
+ let ifaces = st?.info[1]?.interfaces;
+
+ if (ifaces)
+ ifaces?.forEach(function(iface) {
+ res.push([iface?.dev, iface?.state, iface?.type, iface?.rateMax, iface?.localIp, iface?.rxBpP, iface?.txBpP]);
+ });
+ return res;
+ });
+
+ new TablePooler(10,"/cgi-bin/bmx7-info", {'info':'1'}, "links_div", function(st){
+ let res = Array();
+ links = st?.info[2]?.links;
+ if (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>
+
+{% include('footer', { }) %}
diff --git a/applications/luci-app-bmx7/ucode/template/bmx7/bmxtopology.ut b/applications/luci-app-bmx7/ucode/template/bmx7/bmxtopology.ut
new file mode 100644
index 0000000000..8af2a885e0
--- /dev/null
+++ b/applications/luci-app-bmx7/ucode/template/bmx7/bmxtopology.ut
@@ -0,0 +1,53 @@
+{% include('header', { }) %}
+<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js" integrity="sha512-uy3foVtL4u0+5430l7zZt4PHjVtICfrbu3mtzdanR425sKD7kS5264djeZAzNIV0l4vc1QkFpW2+G5i5KoJIFQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+<script src="{{ resource }}/bmx7/js/netjsongraph.js"></script>
+
+<link href="{{ resource }}/bmx7/css/netjsongraph.css" rel="stylesheet">
+<style>
+ 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>
+{% include('footer', { }) %}
diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm b/applications/luci-app-bmx7/ucode/template/bmx7/bmxtunnels.ut
index 1c8d3a9cd8..9477aa3cab 100644
--- a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm
+++ b/applications/luci-app-bmx7/ucode/template/bmx7/bmxtunnels.ut
@@ -1,4 +1,4 @@
-<%#
+{#
Copyright (C) 2011 Pau Escrich <pau@dabax.net>
Contributors Lluis Esquerda <eskerda@gmail.com>
@@ -18,31 +18,31 @@
The full GNU General Public License is included in this distribution in
the file called "COPYING".
--%>
+-#}
-<%+header%>
-<script src="<%=resource%>/cbi.js"></script>
-<script src="<%=resource%>/bmx7/js/polling.js"></script>
+{% include('header', { }) %}
+
+<script 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>
+ <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"><%:Bandwidth%></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 class="th">{{ _('Status') }}</div>
+ <div class="th">{{ _('Name') }}</div>
+ <div class="th">{{ _('Node') }}</div>
+ <div class="th">{{ _('Network') }}</div>
+ <div class="th">{{ _('Bandwidth') }}</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>
@@ -51,26 +51,26 @@
</div>
<script>
- new TablePooler(5,"/cgi-bin/bmx7-info", {'tunnels':''}, "tunnels_div", function(st){
- var tunicon = "<%=resource%>/icons/tunnel.svg";
- var tunicon_dis = "<%=resource%>/icons/tunnel_disabled.svg";
- 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+'"/>';
+ new TablePooler(5,"/cgi-bin/bmx7-info", {'tunnels':'1'}, "tunnels_div", function(st){
+ let tunicon = "{{ resource }}/icons/tunnel.svg";
+ let tunicon_dis = "{{ resource }}/icons/tunnel_disabled.svg";
+ let applyicon = "{{ resource }}/cbi/apply.gif";
+ let res = Array();
+ for ( let k in st?.tunnels ) {
+ let tunnel = st?.tunnels[k];
+ let nodename = tunnel?.remoteName;
+ let advnet = tunnel?.advNet;
+ let 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]);
+ res.push([status, tunnel?.tunName, nodename, advnet, tunnel?.advBw, tunnel?.net,
+ tunnel?.pathMtc, tunnel?.tunMtc, tunnel?.rating]);
}
}
return res;
});
</script>
-<%+footer%>
+{% include('footer', { }) %}