diff options
author | Paul Spooren <mail@aparcar.org> | 2019-07-06 01:25:07 +0200 |
---|---|---|
committer | Paul Spooren <mail@aparcar.org> | 2019-07-10 17:44:13 +0200 |
commit | 9aa507790e8a75ab8f52e267e29059a080d55122 (patch) | |
tree | 7c6f6fc8b4a077b3e107aab387697af4147d6d56 /applications/luci-app-bmx7/root | |
parent | 69e5488c139f249fcf785d4ba170a90ed0e7f33c (diff) |
luci-app-bmx7: transfer from routing
The Makefile is minified as the LuCi build system does most of the job.
Signed-off-by: Paul Spooren <mail@aparcar.org>
Diffstat (limited to 'applications/luci-app-bmx7/root')
15 files changed, 1471 insertions, 0 deletions
diff --git a/applications/luci-app-bmx7/root/etc/config/luci-bmx7 b/applications/luci-app-bmx7/root/etc/config/luci-bmx7 new file mode 100755 index 0000000000..46a77272f1 --- /dev/null +++ b/applications/luci-app-bmx7/root/etc/config/luci-bmx7 @@ -0,0 +1,7 @@ +config 'bmx7' 'luci' + option ignore '0' + option place 'admin network BMX7' + #option place 'qmp Mesh' + option position '3' + #option json 'http://127.0.0.1/cgi-bin/bmx7-info?' + option json 'exec:/www/cgi-bin/bmx7-info -s' diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua b/applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua new file mode 100644 index 0000000000..482fb5db51 --- /dev/null +++ b/applications/luci-app-bmx7/root/usr/lib/lua/luci/controller/bmx7.lua @@ -0,0 +1,101 @@ +--[[ + Copyright (C) 2011 Pau Escrich <pau@dabax.net> + Contributors Jo-Philipp Wich <xm@subsignal.org> + Roger Pueyo Centelles <roger.pueyo@guifi.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + The full GNU General Public License is included in this distribution in + the file called "COPYING". +--]] + +module("luci.controller.bmx7", package.seeall) + +function index() + local place = {} + local ucim = require "luci.model.uci" + local uci = ucim.cursor() + + -- checking if ignore is on + if uci:get("luci-bmx7","luci","ignore") == "1" then + return nil + end + + -- getting value from uci database + local uci_place = uci:get("luci-bmx7","luci","place") + + -- default values + if uci_place == nil then + place = {"bmx7"} + else + local util = require "luci.util" + place = util.split(uci_place," ") + end + + -- getting position of menu + local uci_position = uci:get("luci-bmx7","luci","position") + + + --------------------------- + -- Placing the pages in the menu + --------------------------- + + -- Status (default) + entry(place,call("action_status_j"),place[#place],tonumber(uci_position)) + + table.insert(place,"Status") + entry(place,call("action_status_j"),"Status",0) + table.remove(place) + + -- Topology + table.insert(place,"Topology") + entry(place,call("topology"),"Topology",1) + table.remove(place) + + -- Nodes + table.insert(place,"Nodes") + entry(place,call("action_nodes_j"),"Nodes",2) + table.remove(place) + + -- Tunnels + table.insert(place,"Gateways") + entry(place,call("action_tunnels_j"),"Gateways",3) + table.remove(place) + + -- Integrate bmx7-mdns if present + if nixio.fs.stat("/usr/lib/lua/luci/model/cbi/bmx7-mdns.lua","type") ~= nil then + table.insert(place,"mDNS") + entry(place, cbi("bmx7-mdns"), "mesh DNS", 1).dependent=false + table.remove(place) + end + +end + + +function action_status_j() + luci.template.render("bmx7/status_j", {}) +end + +function action_tunnels_j() + luci.template.render("bmx7/tunnels_j", {}) +end + +function topology() + luci.template.render("bmx7/topology", {}) +end + +function action_nodes_j() + luci.template.render("bmx7/nodes_j", {}) +end diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm new file mode 100644 index 0000000000..8a6aefaa20 --- /dev/null +++ b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/admin_status/index/bmx7_nodes.htm @@ -0,0 +1,40 @@ +<div class="cbi-map"> +<div class="cbi-section"> + <legend><%:Bmx7 mesh nodes%></legend> + <div class="cbi-section-node"> + <div class="table" id="nodes_div"> + <div class="tr table-titles"> + <div class="th"><%:Name%></div> + <div class="th"><%:Short ID%></div> + <div class="th"><%:S/s/T/t%></div> + <div class="th"><%:Primary IPv6%></div> + <div class="th"><%:Via Neighbour%></div> + <div class="th"><%:Device%></div> + <div class="th"><%:Metric%></div> + <div class="th"><%:Last Ref%></div> + </div> + </div> + </div> +</div> +</div> + +<script type="text/javascript" src="<%=resource%>/bmx7/js/polling.js"></script> +<script type="text/javascript">//<![CDATA[ + new TablePooler(10,"/cgi-bin/bmx7-info", {'$originators':''}, "nodes_div", function(st){ + var originators = st.originators; + var res = Array(); + originators.forEach(function(originator,i){ + var name = originator.name; + var shortId = originator.shortId; + var SsTt = originator.S+'/'+originator.s+'/'+originator.T+'/'+originator.t; + var primaryIp = originator.primaryIp; + var nbName = originator.nbName; + var dev = originator.dev; + var metric = originator.metric; + var lastRef = originator.lastRef; + res.push([name, shortId, SsTt, primaryIp, + nbName, dev, metric, lastRef]); + }); + return res; + }); +//]]></script> diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm new file mode 100644 index 0000000000..a631c93e89 --- /dev/null +++ b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/nodes_j.htm @@ -0,0 +1,155 @@ +<%# + Copyright © 2011 Pau Escrich <pau@dabax.net> + Contributors Lluis Esquerda <eskerda@gmail.com> + Roger Pueyo Centelles <roger.pueyo@guifi.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + The full GNU General Public License is included in this distribution in + the file called "COPYING". +-%> + +<%+header%> +<script type="text/javascript" src="<%=resource%>/cbi.js"></script> +<script type="text/javascript" src="<%=resource%>/bmx7/js/polling.js"></script> + + +<style> + div.hideme{ + display: none; + } + div.info{ + background: #FFF; + border: solid 0px; + height: 190px; + display: block; + overflow: auto; + } + div.inforow{ + text-align:left; + display:inline-block; + margin:10px; + vertical-align:top; + float: left; + white-space:nowrap; + } + div.inforow.newline{ + clear: both; + } + u { + text-decoration: underline; + } +#extra-info ul { list-style: none outside none; margin-left: 0em; } +</style> + +<div class="cbi-map"> + +<h2>Mesh nodes</h2> +<div class="cbi-map-descr"></div> +<div id="extra-info" class="info"> + <br /> + <center> + Tip: click the <img src="<%=resource%>/bmx7/world.png" /> icon to see individual node information. + </center> +</div> + + +<div class="cbi-section"> + <legend><%:Originators%></legend> + <div class="cbi-section-node"> + <div class="table" id="nodes_div"> + <div class="tr table-titles"> + <div class="th"></div> + <div class="th"><%:Name%></div> + <div class="th"><%:Short ID%></div> + <div class="th"><%:S/s/T/t%></div> + <div class="th"><%:Primary IPv6%></div> + <div class="th"><%:Via Neighbour%></div> + <div class="th"><%:Metric%></div> + <div class="th"><%:Last Desc%></div> + <div class="th"><%:Last Ref%></div> + <div class="th"><%: %></div> + </div> + </div> + </div> +</div> + +</div> + +<script type="text/javascript">//<![CDATA[ + var displayExtraInfo = function ( id ) { + document.getElementById('extra-info').innerHTML = document.getElementById(id).innerHTML; + } + new TablePooler(5,"/cgi-bin/bmx7-info", {'$originators':''}, "nodes_div", function(st){ + var infoicon = "<%=resource%>/bmx7/world_small.png"; + var originators = st.originators; + var res = Array(); + originators.forEach(function(originator,i){ + var name = originator.name; + var shortId = originator.shortId; + var nodeId = originator.nodeId; + var extensions = originator.name; + var SsTt = originator.S+'/'+originator.s+'/'+originator.T+'/'+originator.t; + var nodeKey = originator.nodeKey; + var descSize = originator.descSize; + var primaryIp = originator.primaryIp; + var nbName = originator.nbName; + var dev = originator.dev; + var nbLocalIp = originator.nbLocalIp; + var metric = originator.metric; + var lastDesc = originator.lastDesc; + var lastRef = originator.lastRef; + + var extrainfo = '<a onclick="displayExtraInfo(\'ip-' + i + '\')"><img src="' + infoicon + '" / ></a>'; + var extrainfo_link = '<a onclick="displayExtraInfo(\'ip-' + i + '\')">' + '<img src="' + infoicon + '" />' + '</a>'; + + extrainfo = '<div id="ip-'+ i +'" class="hideme">' + + "<div class='inforow'>" + + "<h4><u>" + name + '</u></h4>\n' + + 'Node ID: ' + shortId + "</div>" + + "<div class='inforow'>" + + "<h5>Primary IPv6 address</h5>\n" + + primaryIp + "</div>\n" + + "<div class='inforow'>" + + "<h5>Support & Trust</h5>\n" + + SsTt + "</div>\n" + + "<div class='inforow'>" + + "<h5>Node key</h5>\n" + + nodeKey + "</div>\n" + + "<div class='inforow newline'>" + + "<h5>Via neighbour</h5>\n" + + nbName + "</div>\n" + + "<div class='inforow'>" + + "<h5>Via device</h5>\n" + + dev + "</div>\n" + + "<div class='inforow'>" + + "<h5>Via link-local IPv6</h5>\n" + + nbLocalIp + "</div>\n" + + "<div class='inforow'>" + + "<h5>Route metric</h5>\n" + + metric + "</div>\n" + + "<div class='inforow'>" + + "<h5>Desc. size</h5>\n" + + descSize + "</div>\n" + + "\n</div>"; + + res.push([extrainfo_link, name, shortId, SsTt, primaryIp, + nbName, metric, lastDesc, lastRef, extrainfo]); + }); + return res; + }); +//]]></script> + +<%+footer%> diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm new file mode 100644 index 0000000000..b7609d7a52 --- /dev/null +++ b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/status_j.htm @@ -0,0 +1,130 @@ +<%+header%> +<script type="text/javascript" src="<%=resource%>/cbi.js"></script> +<script type="text/javascript" src="<%=resource%>/bmx7/js/polling.js"></script> + +<div class="cbi-map"> + <center> + <img src="<%=resource%>/bmx7/bmx7logo.png" /> + <br /> + <br /> + A mesh routing protocol for Linux devices.<br /> + Visit <a href="http://bmx6.net">bmx6.net</a> for more information.<br /> + <br /> + </center> + +<div class="cbi-map-descr"></div> + +<div class="cbi-section"> + <legend><%:Node configuration%></legend> + <div class="cbi-section-node"> + <div class="table" id="config_div"> + <div class="tr table-titles"> + <div class="th"><%:Short ID%></div> + <div class="th"><%:Node name%></div> + <div class="th"><%:Primary IPv6 address%></div> + <div class="th"><%:Node key%></div> + <div class="th"><%:Short DHash%></div> + <div class="th"><%:BMX7 revision%></div> + </div> + </div> + </div> +</div> + + +<div class="cbi-section"> + <legend><%:Node status%></legend> + <div class="cbi-section-node"> + <div class="table" id="status_div"> + <div class="tr table-titles"> + <div class="th"><%:Nodes seen%></div> + <div class="th"><%:Neighbours%></div> + <div class="th"><%:Tunnelled IPv6 address%></div> + <div class="th"><%:Tunnelled IPv4 address%></div> + <div class="th"><%:Uptime%></div> + <div class="th"><%:CPU usage%></div> + <div class="th"><%:Memory usage%></div> + <div class="th"><%:Tx queue%></div> + </div> + </div> + </div> +</div> + +<div class="cbi-section"> + <legend><%:Network interfaces%></legend> + <div class="cbi-section-node"> + <div class="table" id="ifaces_div"> + <div class="tr table-titles"> + <div class="th"><%:Interface%></div> + <div class="th"><%:State%></div> + <div class="th"><%:Type%></div> + <div class="th"><%:Max rate%></div> + <div class="th"><%:LinkLocal Ipv6%></div> + <div class="th"><%:RX BpP%></div> + <div class="th"><%:TX BpP%></div> + </div> + </div> + </div> +</div> + + +<div class="cbi-section"> + <legend><%:Links%></legend> + <div class="cbi-section-node"> + <div class="table" id="links_div"> + <div class="tr table-titles"> + <div class="th"><%:Short ID%></div> + <div class="th"><%:Name%></div> + <div class="th"><%:Link key%></div> + <div class="th"><%:Remote linklocal IPv6%></div> + <div class="th"><%:Device%></div> + <div class="th"><%:RX rate%></div> + <div class="th"><%:TX rate%></div> + <div class="th"><%:Routes%></div> + </div> + </div> + </div> +</div> + +</div> + +<script type="text/javascript">//<![CDATA[ + new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "config_div", function(st){ + var res = Array(); + var sta = st.info[0].status; + res.push([sta.shortId, sta.name, sta.primaryIp, sta.nodeKey, sta.shortDhash, sta.revision]); + return res; + }); + + new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "status_div", function(st){ + var res = Array(); + var sta = st.info[0].status; + var mem = st.info[3].memory.bmx7; + var txQ = sta.txQ.split('/'); + var ptxQ = '<p style="color:rgb('+parseInt(255*txQ[0]/txQ[1])+','+parseInt(128*(txQ[1]-txQ[0])/txQ[1])+',0)")>'+sta.txQ+'</p>'; + res.push([sta.nodes, sta.nbs, sta.tun6Address, sta.tun4Address, sta.uptime, sta.cpu, mem, ptxQ]); + return res; + }); + + new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "ifaces_div", function(st){ + var res = Array(); + var ifaces = st.info[1].interfaces; + + ifaces.forEach(function(iface){ + res.push([iface.dev, iface.state, iface.type, iface.rateMax, iface.localIp, iface.rxBpP, iface.txBpP]); + }); + return res; + }); + + new TablePooler(1,"/cgi-bin/bmx7-info", {'$info':''}, "links_div", function(st){ + var res = Array(); + links = st.info[2].links; + + links.forEach(function(link){ + res.push([link.shortId, link.name, link.linkKey, link.nbLocalIp, link.dev, link.rxRate, link.txRate, link.rts]); + }); + return res; + }); + +//]]></script> + +<%+footer%> diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm new file mode 100644 index 0000000000..58ce9fdbf0 --- /dev/null +++ b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/topology.htm @@ -0,0 +1,54 @@ +<%+header%> +<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js"></script> +<script type="text/javascript" src="<%=resource%>/bmx7/js/netjsongraph.js"></script> + +<link href="<%=resource%>/bmx7/css/netjsongraph.css" rel="stylesheet"> + <style type="text/css"> + body { + font-family: Arial, sans-serif; + font-size: 13px; + } + + .njg-overlay{ + width: auto; + height: auto; + min-width: 200px; + max-width: 400px; + border: 1px solid #000; + border-radius: 2px; + background: rgba(0, 0, 0, 0.7); + top: 10px; + right: 10px; + padding: 0 15px; + font-family: Arial, sans-serif; + font-size: 14px; + color: #fff + } + + .njg-node { + fill: #008000; + fill-opacity: 0.8; + stroke: #008000; + stroke-width: 1px; + cursor: pointer; + } + .njg-node:hover, + .njg-node.njg-open{ + fill-opacity: 1; + } + + .njg-link { + stroke: #00ff00; + stroke-width: 2; + stroke-opacity: .5; + cursor: pointer; + } + .njg-link:hover, + .njg-link.njg-open{ + stroke-width: 3; + stroke-opacity: 1 + } +</style> +<script>d3.netJsonGraph("/cgi-bin/bmx7-info?netjson/network-graph.json", { defaultStyle: false });</script> +<%+footer%> + diff --git a/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm new file mode 100644 index 0000000000..aaa79a8f4e --- /dev/null +++ b/applications/luci-app-bmx7/root/usr/lib/lua/luci/view/bmx7/tunnels_j.htm @@ -0,0 +1,76 @@ +<%# + Copyright (C) 2011 Pau Escrich <pau@dabax.net> + Contributors Lluis Esquerda <eskerda@gmail.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + The full GNU General Public License is included in this distribution in + the file called "COPYING". +-%> + + +<%+header%> +<script type="text/javascript" src="<%=resource%>/cbi.js"></script> +<script type="text/javascript" src="<%=resource%>/bmx7/js/polling.js"></script> + +<div class="cbi-map"> +<h2>Gateway announcements</h2> +<div class="cbi-map-descr">Networks announced by mesh nodes</div> + +<div class="cbi-section"> + <legend><%:Announcements%></legend> + <div class="cbi-section-node"> + <div class="table" id="tunnels_div"> + <div class="tr table-titles"> + <div class="th"><%:Status%></div> + <div class="th"><%:Name%></div> + <div class="th"><%:Node%></div> + <div class="th"><%:Network%></div> + <div class="th"><%:Bandwith%></div> + <div class="th"><%:Local net%></div> + <div class="th"><%:Path Metric%></div> + <div class="th"><%:Tun Metric%></div> + <div class="th"><%:Rating%></div> + </div> + </div> + </div> +</div> + +</div> + +<script type="text/javascript">//<![CDATA[ + new TablePooler(5,"/cgi-bin/bmx7-info", {'$tunnels':''}, "tunnels_div", function(st){ + var tunicon = "<%=resource%>/icons/tunnel.png"; + var tunicon_dis = "<%=resource%>/icons/tunnel_disabled.png"; + var applyicon = "<%=resource%>/cbi/apply.gif"; + var res = Array(); + for ( var k in st.tunnels ) { + var tunnel = st.tunnels[k]; + var nodename = tunnel.remoteName; + var advnet = tunnel.advNet; + var status = '<img src="'+tunicon_dis+'"/>'; + if ( tunnel.tunName != "---" ) status = '<img src="'+tunicon+'"/>'; + if ( advnet == "0.0.0.0/0" ) advnet = "<b>Internet IPv4</b>"; + if ( advnet == "::/0" ) advnet = "<b>Internet IPv6</b>"; + if (nodename != "---") { + res.push([status, tunnel.tunName, nodename, advnet, tunnel.advBw, tunnel.net, + tunnel.pathMtc, tunnel.tunMtc, tunnel.rating]); + } + } + return res; + }); +//]]></script> + +<%+footer%> diff --git a/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info b/applications/luci-app-bmx7/root/www/cgi-bin/bmx7-info new file mode 100755 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 Binary files differnew file mode 100644 index 0000000000..c7d9ceafae --- /dev/null +++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/bmx7logo.png 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 Binary files differnew file mode 100644 index 0000000000..29b53c957e --- /dev/null +++ b/applications/luci-app-bmx7/root/www/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/root/www/luci-static/resources/bmx7/world_small.png Binary files differnew file mode 100644 index 0000000000..f5f31056c6 --- /dev/null +++ b/applications/luci-app-bmx7/root/www/luci-static/resources/bmx7/world_small.png |