summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-bmx7/root/www
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-bmx7/root/www')
-rwxr-xr-xapplications/luci-app-bmx7/root/www/cgi-bin/bmx7-info133
-rw-r--r--applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.pngbin0 -> 5121 bytes
-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/root/www/luci-static/resources/bmx7/js/polling.js86
-rw-r--r--applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.pngbin0 -> 1885 bytes
-rw-r--r--applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.pngbin0 -> 923 bytes
8 files changed, 908 insertions, 0 deletions
diff --git a/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info b/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info
new file mode 100755
index 0000000000..7388ed12c3
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info
@@ -0,0 +1,133 @@
+#!/bin/sh
+# Copyright © 2011 Pau Escrich
+# Contributors Jo-Philipp Wich <xm@subsignal.org>
+# Roger Pueyo Centelles <roger.pueyo@guifi.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The full GNU General Public License is included in this distribution in
+# the file called "COPYING".
+#
+# This script gives information about bmx7
+# Can be executed from a linux shell: ./bmx7-info -s links
+# Or from web interfae (with cgi enabled): http://host/cgi-bin/bmx7-info?links
+# If you ask for a directory you wil get the directory contents in JSON forman
+
+BMX7_DIR="$(uci get bmx7.general.runtimeDir 2>/dev/null)" || BMX7_DIR="/var/run/bmx7/json"
+
+#Checking if shell mode or cgi-bin mode
+if [ "$1" == "-s" ]; then
+ QUERY="$2"
+else
+ QUERY="${QUERY_STRING%%=*}"
+ echo "Content-type: application/json"
+ echo ""
+fi
+
+check_path() {
+ [ -d "$1" ] && path=$(cd $1; pwd)
+ [ -f "$1" ] && path=$(cd $1/..; pwd)
+ [ $(echo "$path" | grep -c "^$BMX7_DIR") -ne 1 ] && exit 1
+}
+
+print_mem() {
+ echo -n '{ "memory": { "bmx7": "'
+ cat /proc/$(cat /var/run/bmx7/pid)/status |grep -i VmSize | tr -s " " | cut -d " " -f 2,3 | tr -d "\n"
+ echo '"}}'
+}
+
+print_query() {
+ # If the query is a directory
+ [ -d "$BMX7_DIR/$1" ] &&
+ {
+ # If /all has not been specified
+ [ -z "$QALL" ] &&
+ {
+ total=$(ls $BMX7_DIR/$1 | wc -w)
+ i=1
+ echo -n "{ \"$1\": [ "
+ for f in $(ls $BMX7_DIR/$1); do
+ echo -n "{ \"name\": \"$f\" }"
+ [ $i -lt $total ] && echo -n ','
+ i=$(( $i + 1 ))
+ done
+ echo -n " ] }"
+
+ # If /all has been specified, printing all the files together
+ } || {
+ comma=""
+ echo -n "[ "
+ for entry in "$BMX7_DIR/$1/"*; do
+ [ -f "$entry" ] &&
+ {
+ ${comma:+echo "$comma"}
+ tr -d '\n' < "$entry"
+ comma=","
+ }
+ done
+ echo -n " ]"
+ }
+ }
+
+ # If the query is a file, just printing the file
+ [ -f "$BMX7_DIR/$1" ] && [ -s "$BMX7_DIR/$1" ] && cat "$BMX7_DIR/$1" && return 0 || return 1
+}
+
+if [ "${QUERY##*/}" == "all" ]; then
+ QUERY="${QUERY%/all}"
+ QALL=1
+fi
+
+if [ "$QUERY" == '$info' ]; then
+ echo '{ "info": [ '
+ print_query status
+ echo -n ","
+ print_query interfaces && echo -n "," || echo -n '{ "interfaces": "" },'
+ print_query links && echo -n "," || echo -n '{ "links": "" },'
+ print_mem
+ echo "] }"
+fi
+
+if [ "$QUERY" == '$neighbours' ]; then
+ QALL=1
+ echo '{ "neighbours": [ '
+ echo '{ "originators": '
+ print_query originators
+ echo '}, '
+ echo '{ "descriptions": '
+ print_query descriptions
+ echo "} ] }"
+ exit 0
+
+else if [ "$QUERY" == '$tunnels' ]; then
+ bmx7 -c --jshow tunnels /r=0
+ exit 0
+
+ else if [ "$QUERY" == '$originators' ]; then
+ bmx7 -c --jshow originators /r=0
+ exit 0
+
+ else
+ check_path "$BMX7_DIR/$QUERY"
+ print_query $QUERY
+ exit 0
+ fi
+ fi
+fi
+fi
+
+ls -1F "$BMX7_DIR"
+exit 0
+
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png
new file mode 100644
index 0000000000..c7d9ceafae
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png
Binary files differ
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css
new file mode 100644
index 0000000000..276d362b13
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph-theme.css
@@ -0,0 +1,59 @@
+.njg-overlay{
+ background: #fbfbfb;
+ border-radius: 2px;
+ border: 1px solid #ccc;
+ color: #6d6357;
+ font-family: Arial, sans-serif;
+ font-family: sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ height: auto;
+ max-width: 400px;
+ min-width: 200px;
+ padding: 0 15px;
+ right: 10px;
+ top: 10px;
+ width: auto;
+}
+
+.njg-metadata{
+ background: #fbfbfb;
+ border-radius: 2px;
+ border: 1px solid #ccc;
+ color: #6d6357;
+ display: none;
+ font-family: Arial, sans-serif;
+ font-family: sans-serif;
+ font-size: 14px;
+ height: auto;
+ left: 10px;
+ max-width: 500px;
+ min-width: 200px;
+ padding: 0 15px;
+ top: 10px;
+ width: auto;
+}
+
+.njg-node{
+ stroke-opacity: 0.5;
+ stroke-width: 7px;
+ stroke: #fff;
+}
+
+.njg-node:hover,
+.njg-node.njg-open {
+ stroke: rgba(0, 0, 0, 0.2);
+}
+
+.njg-link{
+ cursor: pointer;
+ stroke: #999;
+ stroke-width: 2;
+ stroke-opacity: 0.25;
+}
+
+.njg-link:hover,
+.njg-link.njg-open{
+ stroke-width: 4 !important;
+ stroke-opacity: 0.5;
+}
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css
new file mode 100644
index 0000000000..556c520767
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/css/netjsongraph.css
@@ -0,0 +1,62 @@
+.njg-hidden {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+.njg-tooltip{
+ font-family: sans-serif;
+ font-size: 10px;
+ fill: #000;
+ opacity: 0.5;
+ text-anchor: middle;
+}
+
+.njg-overlay{
+ display: none;
+ position: absolute;
+ z-index: 11;
+}
+
+.njg-close{
+ cursor: pointer;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+}
+.njg-close:before { content: "\2716"; }
+
+.njg-metadata{
+ display: none;
+ position: absolute;
+ z-index: 12;
+}
+
+.njg-node{ cursor: pointer }
+.njg-link{ cursor: pointer }
+
+#njg-select-group {
+ text-align: center;
+ box-shadow: 0 0 10px #ccc;
+ position: fixed;
+ left: 50%;
+ top: 50%;
+ width: 50%;
+ margin-top: -7.5em;
+ margin-left: -25%;
+ padding: 5em 2em;
+}
+
+#njg-select-group select {
+ font-size: 2em;
+ padding: 10px 15px;
+ width: 50%;
+ cursor: pointer;
+}
+
+#njg-select-group option {
+ padding: 0.5em;
+}
+
+#njg-select-group option[value=""] {
+ color: #aaa;
+}
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js
new file mode 100644
index 0000000000..66d0a5f155
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/netjsongraph.js
@@ -0,0 +1,568 @@
+// version 0.1
+(function () {
+ /**
+ * vanilla JS implementation of jQuery.extend()
+ */
+ d3._extend = function(defaults, options) {
+ var extended = {},
+ prop;
+ for(prop in defaults) {
+ if(Object.prototype.hasOwnProperty.call(defaults, prop)) {
+ extended[prop] = defaults[prop];
+ }
+ }
+ for(prop in options) {
+ if(Object.prototype.hasOwnProperty.call(options, prop)) {
+ extended[prop] = options[prop];
+ }
+ }
+ return extended;
+ };
+
+ /**
+ * @function
+ * @name d3._pxToNumber
+ * Convert strings like "10px" to 10
+ *
+ * @param {string} val The value to convert
+ * @return {int} The converted integer
+ */
+ d3._pxToNumber = function(val) {
+ return parseFloat(val.replace('px'));
+ };
+
+ /**
+ * @function
+ * @name d3._windowHeight
+ *
+ * Get window height
+ *
+ * @return {int} The window innerHeight
+ */
+ d3._windowHeight = function() {
+ return window.innerHeight || document.documentElement.clientHeight || 600;
+ };
+
+ /**
+ * @function
+ * @name d3._getPosition
+ *
+ * Get the position of `element` relative to `container`
+ *
+ * @param {object} element
+ * @param {object} container
+ */
+ d3._getPosition = function(element, container) {
+ var n = element.node(),
+ nPos = n.getBoundingClientRect();
+ cPos = container.node().getBoundingClientRect();
+ return {
+ top: nPos.top - cPos.top,
+ left: nPos.left - cPos.left,
+ width: nPos.width,
+ bottom: nPos.bottom - cPos.top,
+ height: nPos.height,
+ right: nPos.right - cPos.left
+ };
+ };
+
+ /**
+ * netjsongraph.js main function
+ *
+ * @constructor
+ * @param {string} url The NetJSON file url
+ * @param {object} opts The object with parameters to override {@link d3.netJsonGraph.opts}
+ */
+ d3.netJsonGraph = function(url, opts) {
+ /**
+ * Default options
+ *
+ * @param {string} el "body" The container element el: "body" [description]
+ * @param {bool} metadata true Display NetJSON metadata at startup?
+ * @param {bool} defaultStyle true Use css style?
+ * @param {bool} animationAtStart false Animate nodes or not on load
+ * @param {array} scaleExtent [0.25, 5] The zoom scale's allowed range. @see {@link https://github.com/mbostock/d3/wiki/Zoom-Behavior#scaleExtent}
+ * @param {int} charge -130 The charge strength to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#charge}
+ * @param {int} linkDistance 50 The target distance between linked nodes to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#linkDistance}
+ * @param {float} linkStrength 0.2 The strength (rigidity) of links to the specified value in the range. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#linkStrength}
+ * @param {float} friction 0.9 The friction coefficient to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#friction}
+ * @param {string} chargeDistance Infinity The maximum distance over which charge forces are applied. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#chargeDistance}
+ * @param {float} theta 0.8 The Barnes–Hut approximation criterion to the specified value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#theta}
+ * @param {float} gravity 0.1 The gravitational strength to the specified numerical value. @see {@link https://github.com/mbostock/d3/wiki/Force-Layout#gravity}
+ * @param {int} circleRadius 8 The radius of circles (nodes) in pixel
+ * @param {string} labelDx "0" SVG dx (distance on x axis) attribute of node labels in graph
+ * @param {string} labelDy "-1.3em" SVG dy (distance on y axis) attribute of node labels in graph
+ * @param {function} onInit Callback function executed on initialization
+ * @param {function} onLoad Callback function executed after data has been loaded
+ * @param {function} onEnd Callback function executed when initial animation is complete
+ * @param {function} linkDistanceFunc By default high density areas have longer links
+ * @param {function} redraw Called when panning and zooming
+ * @param {function} prepareData Used to convert NetJSON NetworkGraph to the javascript data
+ * @param {function} onClickNode Called when a node is clicked
+ * @param {function} onClickLink Called when a link is clicked
+ */
+ opts = d3._extend({
+ el: "body",
+ metadata: true,
+ defaultStyle: true,
+ animationAtStart: true,
+ scaleExtent: [0.25, 5],
+ charge: -130,
+ linkDistance: 50,
+ linkStrength: 0.2,
+ friction: 0.9, // d3 default
+ chargeDistance: Infinity, // d3 default
+ theta: 0.8, // d3 default
+ gravity: 0.1,
+ circleRadius: 8,
+ labelDx: "0",
+ labelDy: "-1.3em",
+ nodeClassProperty: null,
+ linkClassProperty: null,
+ /**
+ * @function
+ * @name onInit
+ *
+ * Callback function executed on initialization
+ * @param {string|object} url The netJson remote url or object
+ * @param {object} opts The object of passed arguments
+ * @return {function}
+ */
+ onInit: function(url, opts) {},
+ /**
+ * @function
+ * @name onLoad
+ *
+ * Callback function executed after data has been loaded
+ * @param {string|object} url The netJson remote url or object
+ * @param {object} opts The object of passed arguments
+ * @return {function}
+ */
+ onLoad: function(url, opts) {},
+ /**
+ * @function
+ * @name onEnd
+ *
+ * Callback function executed when initial animation is complete
+ * @param {string|object} url The netJson remote url or object
+ * @param {object} opts The object of passed arguments
+ * @return {function}
+ */
+ onEnd: function(url, opts) {},
+ /**
+ * @function
+ * @name linkDistanceFunc
+ *
+ * By default, high density areas have longer links
+ */
+ linkDistanceFunc: function(d){
+ var val = opts.linkDistance;
+ if(d.source.linkCount >= 4 && d.target.linkCount >= 4) {
+ return val * 2;
+ }
+ return val;
+ },
+ /**
+ * @function
+ * @name redraw
+ *
+ * Called on zoom and pan
+ */
+ redraw: function() {
+ panner.attr("transform",
+ "translate(" + d3.event.translate + ") " +
+ "scale(" + d3.event.scale + ")"
+ );
+ },
+ /**
+ * @function
+ * @name prepareData
+ *
+ * Convert NetJSON NetworkGraph to the data structure consumed by d3
+ *
+ * @param graph {object}
+ */
+ prepareData: function(graph) {
+ var nodesMap = {},
+ nodes = graph.nodes.slice(), // copy
+ links = graph.links.slice(), // copy
+ nodes_length = graph.nodes.length,
+ links_length = graph.links.length;
+
+ for(var i = 0; i < nodes_length; i++) {
+ // count how many links every node has
+ nodes[i].linkCount = 0;
+ nodesMap[nodes[i].id] = i;
+ }
+ for(var c = 0; c < links_length; c++) {
+ var sourceIndex = nodesMap[links[c].source],
+ targetIndex = nodesMap[links[c].target];
+ // ensure source and target exist
+ if(!nodes[sourceIndex]) { throw("source '" + links[c].source + "' not found"); }
+ if(!nodes[targetIndex]) { throw("target '" + links[c].target + "' not found"); }
+ links[c].source = nodesMap[links[c].source];
+ links[c].target = nodesMap[links[c].target];
+ // add link count to both ends
+ nodes[sourceIndex].linkCount++;
+ nodes[targetIndex].linkCount++;
+ }
+ return { "nodes": nodes, "links": links };
+ },
+ /**
+ * @function
+ * @name onClickNode
+ *
+ * Called when a node is clicked
+ */
+ onClickNode: function(n) {
+ var overlay = d3.select(".njg-overlay"),
+ overlayInner = d3.select(".njg-overlay > .njg-inner"),
+ html = "<p><b>id</b>: " + n.id + "</p>";
+ if(n.label) { html += "<p><b>label</b>: " + n.label + "</p>"; }
+ if(n.properties) {
+ for(var key in n.properties) {
+ if(!n.properties.hasOwnProperty(key)) { continue; }
+ html += "<p><b>"+key.replace(/_/g, " ")+"</b>: " + n.properties[key] + "</p>";
+ }
+ }
+ if(n.linkCount) { html += "<p><b>links</b>: " + n.linkCount + "</p>"; }
+ if(n.local_addresses) {
+ html += "<p><b>local addresses</b>:<br>" + n.local_addresses.join('<br>') + "</p>";
+ }
+ overlayInner.html(html);
+ overlay.classed("njg-hidden", false);
+ overlay.style("display", "block");
+ // set "open" class to current node
+ removeOpenClass();
+ d3.select(this).classed("njg-open", true);
+ },
+ /**
+ * @function
+ * @name onClickLink
+ *
+ * Called when a node is clicked
+ */
+ onClickLink: function(l) {
+ var overlay = d3.select(".njg-overlay"),
+ overlayInner = d3.select(".njg-overlay > .njg-inner"),
+ html = "<p><b>source</b>: " + (l.source.label || l.source.id) + "</p>";
+ html += "<p><b>target</b>: " + (l.target.label || l.target.id) + "</p>";
+ html += "<p><b>cost</b>: " + l.cost + "</p>";
+ if(l.properties) {
+ for(var key in l.properties) {
+ if(!l.properties.hasOwnProperty(key)) { continue; }
+ html += "<p><b>"+ key.replace(/_/g, " ") +"</b>: " + l.properties[key] + "</p>";
+ }
+ }
+ overlayInner.html(html);
+ overlay.classed("njg-hidden", false);
+ overlay.style("display", "block");
+ // set "open" class to current link
+ removeOpenClass();
+ d3.select(this).classed("njg-open", true);
+ }
+ }, opts);
+
+ // init callback
+ opts.onInit(url, opts);
+
+ if(!opts.animationAtStart) {
+ opts.linkStrength = 2;
+ opts.friction = 0.3;
+ opts.gravity = 0;
+ }
+ if(opts.el == "body") {
+ var body = d3.select(opts.el),
+ rect = body.node().getBoundingClientRect();
+ if (d3._pxToNumber(d3.select("body").style("height")) < 60) {
+ body.style("height", d3._windowHeight() - rect.top - rect.bottom + "px");
+ }
+ }
+ var el = d3.select(opts.el).style("position", "relative"),
+ width = d3._pxToNumber(el.style('width')),
+ height = d3._pxToNumber(el.style('height')),
+ force = d3.layout.force()
+ .charge(opts.charge)
+ .linkStrength(opts.linkStrength)
+ .linkDistance(opts.linkDistanceFunc)
+ .friction(opts.friction)
+ .chargeDistance(opts.chargeDistance)
+ .theta(opts.theta)
+ .gravity(opts.gravity)
+ // width is easy to get, if height is 0 take the height of the body
+ .size([width, height]),
+ zoom = d3.behavior.zoom().scaleExtent(opts.scaleExtent),
+ // panner is the element that allows zooming and panning
+ panner = el.append("svg")
+ .attr("width", width)
+ .attr("height", height)
+ .call(zoom.on("zoom", opts.redraw))
+ .append("g")
+ .style("position", "absolute"),
+ svg = d3.select(opts.el + " svg"),
+ drag = force.drag(),
+ overlay = d3.select(opts.el).append("div").attr("class", "njg-overlay"),
+ closeOverlay = overlay.append("a").attr("class", "njg-close"),
+ overlayInner = overlay.append("div").attr("class", "njg-inner"),
+ metadata = d3.select(opts.el).append("div").attr("class", "njg-metadata"),
+ metadataInner = metadata.append("div").attr("class", "njg-inner"),
+ closeMetadata = metadata.append("a").attr("class", "njg-close"),
+ // container of ungrouped networks
+ str = [],
+ selected = [],
+ /**
+ * @function
+ * @name removeOpenClass
+ *
+ * Remove open classes from nodes and links
+ */
+ removeOpenClass = function () {
+ d3.selectAll("svg .njg-open").classed("njg-open", false);
+ };
+ processJson = function(graph) {
+ /**
+ * Init netJsonGraph
+ */
+ init = function(url, opts) {
+ d3.netJsonGraph(url, opts);
+ };
+ /**
+ * Remove all instances
+ */
+ destroy = function() {
+ force.stop();
+ d3.select("#selectGroup").remove();
+ d3.select(".njg-overlay").remove();
+ d3.select(".njg-metadata").remove();
+ overlay.remove();
+ overlayInner.remove();
+ metadata.remove();
+ svg.remove();
+ node.remove();
+ link.remove();
+ nodes = [];
+ links = [];
+ };
+ /**
+ * Destroy and e-init all instances
+ * @return {[type]} [description]
+ */
+ reInit = function() {
+ destroy();
+ init(url, opts);
+ };
+
+ var data = opts.prepareData(graph),
+ links = data.links,
+ nodes = data.nodes;
+
+ // disable some transitions while dragging
+ drag.on('dragstart', function(n){
+ d3.event.sourceEvent.stopPropagation();
+ zoom.on('zoom', null);
+ })
+ // re-enable transitions when dragging stops
+ .on('dragend', function(n){
+ zoom.on('zoom', opts.redraw);
+ })
+ .on("drag", function(d) {
+ // avoid pan & drag conflict
+ d3.select(this).attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
+ });
+
+ force.nodes(nodes).links(links).start();
+
+ var link = panner.selectAll(".link")
+ .data(links)
+ .enter().append("line")
+ .attr("class", function (link) {
+ var baseClass = "njg-link",
+ addClass = null;
+ value = link.properties && link.properties[opts.linkClassProperty];
+ if (opts.linkClassProperty && value) {
+ // if value is stirng use that as class
+ if (typeof(value) === "string") {
+ addClass = value;
+ }
+ else if (typeof(value) === "number") {
+ addClass = opts.linkClassProperty + value;
+ }
+ else if (value === true) {
+ addClass = opts.linkClassProperty;
+ }
+ return baseClass + " " + addClass;
+ }
+ return baseClass;
+ })
+ .on("click", opts.onClickLink),
+ groups = panner.selectAll(".node")
+ .data(nodes)
+ .enter()
+ .append("g");
+ node = groups.append("circle")
+ .attr("class", function (node) {
+ var baseClass = "njg-node",
+ addClass = null;
+ value = node.properties && node.properties[opts.nodeClassProperty];
+ if (opts.nodeClassProperty && value) {
+ // if value is stirng use that as class
+ if (typeof(value) === "string") {
+ addClass = value;
+ }
+ else if (typeof(value) === "number") {
+ addClass = opts.nodeClassProperty + value;
+ }
+ else if (value === true) {
+ addClass = opts.nodeClassProperty;
+ }
+ return baseClass + " " + addClass;
+ }
+ return baseClass;
+ })
+ .attr("r", opts.circleRadius)
+ .on("click", opts.onClickNode)
+ .call(drag);
+
+ var labels = groups.append('text')
+ .text(function(n){ return n.label || n.id })
+ .attr('dx', opts.labelDx)
+ .attr('dy', opts.labelDy)
+ .attr('class', 'njg-tooltip');
+
+ // Close overlay
+ closeOverlay.on("click", function() {
+ removeOpenClass();
+ overlay.classed("njg-hidden", true);
+ });
+ // Close Metadata panel
+ closeMetadata.on("click", function() {
+ // Reinitialize the page
+ if(graph.type === "NetworkCollection") {
+ reInit();
+ }
+ else {
+ removeOpenClass();
+ metadata.classed("njg-hidden", true);
+ }
+ });
+ // default style
+ // TODO: probably change defaultStyle
+ // into something else
+ if(opts.defaultStyle) {
+ var colors = d3.scale.category20c();
+ node.style({
+ "fill": function(d){ return colors(d.linkCount); },
+ "cursor": "pointer"
+ });
+ }
+ // Metadata style
+ if(opts.metadata) {
+ metadata.attr("class", "njg-metadata").style("display", "block");
+ }
+
+ var attrs = ["protocol",
+ "version",
+ "revision",
+ "metric",
+ "router_id",
+ "topology_id"],
+ html = "";
+ if(graph.label) {
+ html += "<h3>" + graph.label + "</h3>";
+ }
+ for(var i in attrs) {
+ var attr = attrs[i];
+ if(graph[attr]) {
+ html += "<p><b>" + attr + "</b>: <span>" + graph[attr] + "</span></p>";
+ }
+ }
+ // Add nodes and links count
+ html += "<p><b>nodes</b>: <span>" + graph.nodes.length + "</span></p>";
+ html += "<p><b>links</b>: <span>" + graph.links.length + "</span></p>";
+ metadataInner.html(html);
+ metadata.classed("njg-hidden", false);
+
+ // onLoad callback
+ opts.onLoad(url, opts);
+
+ force.on("tick", function() {
+ link.attr("x1", function(d) {
+ return d.source.x;
+ })
+ .attr("y1", function(d) {
+ return d.source.y;
+ })
+ .attr("x2", function(d) {
+ return d.target.x;
+ })
+ .attr("y2", function(d) {
+ return d.target.y;
+ });
+
+ node.attr("cx", function(d) {
+ return d.x;
+ })
+ .attr("cy", function(d) {
+ return d.y;
+ });
+
+ labels.attr("transform", function(d) {
+ return "translate(" + d.x + "," + d.y + ")";
+ });
+ })
+ .on("end", function(){
+ force.stop();
+ // onEnd callback
+ opts.onEnd(url, opts);
+ });
+
+ return force;
+ };
+
+ if(typeof(url) === "object") {
+ processJson(url);
+ }
+ else {
+ /**
+ * Parse the provided json file
+ * and call processJson() function
+ *
+ * @param {string} url The provided json file
+ * @param {function} error
+ */
+ d3.json(url, function(error, graph) {
+ if(error) { throw error; }
+ /**
+ * Check if the json contains a NetworkCollection
+ */
+ if(graph.type === "NetworkCollection") {
+ var selectGroup = body.append("div").attr("id", "njg-select-group"),
+ select = selectGroup.append("select")
+ .attr("id", "select");
+ str = graph;
+ select.append("option")
+ .attr({
+ "value": "",
+ "selected": "selected",
+ "name": "default",
+ "disabled": "disabled"
+ })
+ .html("Choose the network to display");
+ graph.collection.forEach(function(structure) {
+ select.append("option").attr("value", structure.type).html(structure.type);
+ // Collect each network json structure
+ selected[structure.type] = structure;
+ });
+ select.on("change", function() {
+ selectGroup.attr("class", "njg-hidden");
+ // Call selected json structure
+ processJson(selected[this.options[this.selectedIndex].value]);
+ });
+ }
+ else {
+ processJson(graph);
+ }
+ });
+ }
+ };
+})();
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js
new file mode 100644
index 0000000000..234391a975
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/js/polling.js
@@ -0,0 +1,86 @@
+/*
+ Copyright © 2011 Pau Escrich <pau@dabax.net>
+ Contributors Lluis Esquerda <eskerda@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+*/
+
+
+/*
+ Table pooler is a function to easy call XHR poller.
+
+ new TablePooler(5,"/cgi-bin/bmx7-info", {'status':''}, "status_table", function(st){
+ var table = Array()
+ table.push(st.first,st.second)
+ return table
+ }
+
+ The parameters are:
+ polling_time: time between pollings
+ json_url: the json url to fetch the data
+ json_call: the json call
+ output_table_id: the table where javascript will put the data
+ callback_function: the function that will be executed each polling_time
+
+ The callback_function must return an array of arrays (matrix).
+ In the code st is the data obtained from the json call
+*/
+
+function TablePooler (time, jsonurl, getparams, div_id, callback) {
+ this.div_id = div_id;
+ this.div = document.getElementById(div_id);
+ this.callback = callback;
+ this.jsonurl = jsonurl;
+ this.getparams = getparams;
+ this.time = time;
+
+ this.start = function(){
+ XHR.poll(this.time, this.jsonurl, this.getparams, function(x, st){
+ var data = this.callback(st);
+ var content;
+ for (var i = 0; i < data.length; i++){
+ rowId = "trDiv_" + this.div_id + i;
+ rowDiv = document.getElementById(rowId);
+ if (rowDiv === null) {
+ rowDiv = document.createElement("div");
+ rowDiv.id = rowId;
+ rowDiv.className = "tr";
+ this.div.appendChild(rowDiv);
+ }
+ for (var j = 0; j < data[i].length; j++){
+ cellId = "tdDiv_" + this.div_id + i + j;
+ cellDiv = document.getElementById(cellId);
+ if (cellDiv === null) {
+ cellDiv = document.createElement("div");
+ cellDiv.id = cellId;
+ cellDiv.className = "td";
+ rowDiv.appendChild(cellDiv);
+ }
+ if (typeof data[i][j] !== 'undefined' && data[i][j].length == 2) {
+ content = data[i][j][0] + "/" + data[i][j][1];
+ }
+ else content = data[i][j];
+ cellDiv.innerHTML = content;
+ }
+ }
+ }.bind(this));
+ }
+
+
+ this.start();
+}
diff --git a/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world.png
new file mode 100644
index 0000000000..29b53c957e
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/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/root/www/luci-static/resources/bmx7/world_small.png
new file mode 100644
index 0000000000..f5f31056c6
--- /dev/null
+++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png
Binary files differ