From 1bc988c09ceb2aa0338270d5e5939d0904022e42 Mon Sep 17 00:00:00 2001 From: Nicolas Pace Date: Thu, 23 Mar 2017 16:18:34 -0300 Subject: [PATCH] Polishes bmx6 graph. Adds colors by network and gives color to links based on link quality. --- .../usr/lib/lua/luci/view/bmx6/graph.htm | 71 +----- .../resources/bmx6/js/bmx6-graph.js | 234 ++++++++++++++++++ 2 files changed, 239 insertions(+), 66 deletions(-) create mode 100644 luci-app-bmx6/files/www/luci-static/resources/bmx6/js/bmx6-graph.js diff --git a/luci-app-bmx6/files/usr/lib/lua/luci/view/bmx6/graph.htm b/luci-app-bmx6/files/usr/lib/lua/luci/view/bmx6/graph.htm index 401e932..580732e 100644 --- a/luci-app-bmx6/files/usr/lib/lua/luci/view/bmx6/graph.htm +++ b/luci-app-bmx6/files/usr/lib/lua/luci/view/bmx6/graph.htm @@ -29,11 +29,6 @@ the file called "COPYING". <%+header%> - - - - -
@@ -45,66 +40,10 @@ the file called "COPYING".
- - + + + + + <%+footer%> diff --git a/luci-app-bmx6/files/www/luci-static/resources/bmx6/js/bmx6-graph.js b/luci-app-bmx6/files/www/luci-static/resources/bmx6/js/bmx6-graph.js new file mode 100644 index 0000000..80233ff --- /dev/null +++ b/luci-app-bmx6/files/www/luci-static/resources/bmx6/js/bmx6-graph.js @@ -0,0 +1,234 @@ +var graph, canvas, layouter, renderer, divwait, nodes, announcements, nodesIndex, palette, localInfo; +document.addEventListener( "DOMContentLoaded", init, false); + +/** + * Returns an index of nodes by name + */ +function createNodeIndex(nodes) { + var inode, index = {}; + + for (inode in nodes) + index[nodes[inode].name] = nodes[inode]; + + return index; +} + +/** + * Updates to have announcements in nodes list + */ +function processNodeAnnouncements(nodes, announcements) { + var iannouncement, remoteNode, announcement; + nodesIndex = createNodeIndex(nodes); + + for(iannouncement in announcements) { + announcement = announcements[iannouncement]; + if (announcement.remoteName == '---' ) continue; + if (!( announcement.remoteName in nodesIndex )) { + newNode = { + name: announcement.remoteName, + links: [] + }; + nodes.push(newNode); + nodesIndex[newNode.name] = newNode; + }; + + remoteNode = nodesIndex[announcement.remoteName]; + if (!( 'announcements' in remoteNode )) remoteNode.announcements = []; + remoteNode.announcements.push(announcement); + }; +} + +function init() { + palette = generatePalette(200); + + graph = new Graph(); + canvas = document.getElementById('canvas'); + layouter = new Graph.Layout.Spring(graph); + renderer = new Graph.Renderer.Raphael(canvas.id, graph, canvas.offsetWidth, canvas.offsetHeight); + + divwait = document.getElementById("wait"); + + XHR.get('/cgi-bin/luci/status/bmx6/topology', null, function(nodesRequest, nodesData) { + nodes = nodesData; + + XHR.get('/cgi-bin/bmx6-info?$myself&', null, function(myselfRequest, myselfData) { + if (myselfData) + localAnnouncements = [ + {remoteName: myselfData.myself.hostname, advNet: myselfData.myself.net4}, + {remoteName: myselfData.myself.hostname, advNet: myselfData.myself.net6} + ]; + + XHR.get('/cgi-bin/bmx6-info?$tunnels=&', null, function(tunnelsRequest, tunnelsData) { + var iAnnouncement; + + announcements = tunnelsData.tunnels; + for(iAnnouncement in localAnnouncements) { + announcements.push(localAnnouncements[iAnnouncement]) + }; + + processNodeAnnouncements(nodes, announcements); + + divwait.parentNode.removeChild(divwait); + draw(nodes); + }); + }); + }); +} + +function hashCode(str) { + var hash = 0; + if (str.length == 0) return hash; + for (i = 0; i < str.length; i++) { + char = str.charCodeAt(i); + hash = ((hash<<5)-hash)+char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} + +function generatePalette(size) { + var i, arr = []; + Raphael.getColor(); // just to remove the grey one + for(i = 0; i < size; i++) { + arr.push(Raphael.getColor()) + } + + return arr; +} + +function getFillFromHash(hash) { + return palette[Math.abs(hash % palette.length)]; +} + +function hashAnnouncementsNames(announcementsNames) { + return hashCode(announcementsNames.sort().join('-')); +} + +function getNodeAnnouncements(networkNode) { + return networkNode.announcements; +} + +function nodeRenderer(raphael, node) { + var nodeFill, renderedNode, options; + options = { + 'fill': 'announcements' in node.networkNode ? getFillFromHash( + hashAnnouncementsNames( + getNodeAnnouncements(node.networkNode).map(function(ann) {return ann.advNet;}) + ) + ) : '#bfbfbf', + 'stroke-width': 1, + + }; + + renderedNode = raphael.set(); + + renderedNode.push(raphael.ellipse(node.point[0], node.point[1], 30, 20).attr({"fill": options['fill'], "stroke-width": options['stroke-width']})); + renderedNode.push(raphael.text(node.point[0], node.point[1] + 30, node.networkNode.name).attr({})); + + renderedNode.items.forEach(function(el) { + var announcements, tooltip = raphael.set(); + tooltip.push(raphael.rect(-60, -60, 120, 60).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})); + + announcements = getNodeAnnouncements(node.networkNode); + if (announcements) { + + announcements = announcements.map(function(ann) {return ann.advNet}); + tooltip.push(raphael.text(0, -40, 'announcements\n' + announcements.join('\n')).attr({})); + }; + + el.tooltip(tooltip); + }); + + return renderedNode; +} + +function genericNodeRenderer(raphael, node) { + var renderedNode; + + renderedNode = raphael.set(); + + renderedNode.push(raphael.ellipse(node.point[0], node.point[1], 30, 20).attr({"fill": '#bfbfbf', "stroke-width": 1})); + renderedNode.push(raphael.text(node.point[0], node.point[1] + 30, node.networkNode.name).attr({})); + + return renderedNode; +} + +function redraw() { + layouter.layout(); + renderer.draw(); +} + +function interpolateColor(minColor,maxColor,maxDepth,depth){ + + function d2h(d) {return d.toString(16);} + function h2d(h) {return parseInt(h,16);} + + if(depth == 0){ + return minColor; + } + if(depth == maxDepth){ + return maxColor; + } + + var color = "#"; + + for(var i=1; i <= 6; i+=2){ + var minVal = new Number(h2d(minColor.substr(i,2))); + var maxVal = new Number(h2d(maxColor.substr(i,2))); + var nVal = minVal + (maxVal-minVal) * (depth/maxDepth); + var val = d2h(Math.floor(nVal)); + while(val.length < 2){ + val = "0"+val; + } + color += val; + } + return color; +} +function draw(nodes) { + var node, neighbourNode, seenKey, rxRate, txRate, seen, i, j, currentName, linkQuality; + + seen = { }; + + for (i = 0; i < (nodes.length); i++) { + node = nodes[i]; + graph.addNode(node.name, { + networkNode: node, + render: nodeRenderer + }); + }; + + for (i = 0; i < (nodes.length); i++) { + node = nodes[i]; + + if (! node.name) continue; + + currentName = node.name; + + for (j = 0; j < (node.links.length); j++) { + neighbourNode = node.links[j]; + + graph.addNode(neighbourNode.name, {render: genericNodeRenderer, networkNode: neighbourNode}); + + seenKey = (node.name < neighbourNode.name) ? node.name + '|' + neighbourNode.name : neighbourNode.name + '|' + node.name; + + rxRate = neighbourNode.rxRate; + txRate = neighbourNode.txRate; + + if (!seen[seenKey] && rxRate > 0 && txRate > 0) { + linkQuality = ( rxRate + txRate ) / 2; + + graph.addEdge(node.name, neighbourNode.name, { + 'label': rxRate + '/' + txRate, + 'directed': false, + 'stroke': interpolateColor('FF0000','00FF00', 5, 5 * ( linkQuality - 1 )/100), + 'fill': interpolateColor('FF0000','00FF00', 5, 5 * ( linkQuality - 1 )/100), + 'label-style': { 'font-size': 8 } + }); + + seen[seenKey] = true; + } + } + } + + redraw(); +} -- 2.30.2