diff options
| author | Paul Donald | 2026-02-23 21:03:39 +0000 |
|---|---|---|
| committer | Paul Donald | 2026-02-24 00:23:31 +0000 |
| commit | f578786152c9e792202a8a9d08312a72849bf6d5 (patch) | |
| tree | cfddb76c10c2348b84230a4f1a07ef5c8cdbe48a | |
| parent | d2b850b69c8835428828fc8b5c801556763a8d44 (diff) | |
| download | luci-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>
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 Binary files differindex 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 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 Binary files differindex 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 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 Binary files differindex 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 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', { }) %} |