Polishes bmx6 graph. Adds colors by network and gives color to links based on link...
authorNicolas Pace <nicopace@gmail.com>
Thu, 23 Mar 2017 19:18:34 +0000 (16:18 -0300)
committerPau Escrich <p4u@dabax.net>
Thu, 23 Mar 2017 22:29:38 +0000 (23:29 +0100)
luci-app-bmx6/files/usr/lib/lua/luci/view/bmx6/graph.htm
luci-app-bmx6/files/www/luci-static/resources/bmx6/js/bmx6-graph.js [new file with mode: 0644]

index 401e93297268322f4aac095d2b48034bbb3cf71e..580732efb08ab8042562abb81c15baa303997d6b 100644 (file)
@@ -29,11 +29,6 @@ the file called "COPYING".
 
 <%+header%>
 
-<script type="text/javascript" src="<%=resource%>/bmx6/js/raphael-min.js"></script>
-<script type="text/javascript" src="<%=resource%>/bmx6/js/dracula_graffle.js"></script>
-<script type="text/javascript" src="<%=resource%>/bmx6/js/jquery-1.4.2.min.js"></script>
-<script type="text/javascript" src="<%=resource%>/bmx6/js/dracula_graph.js"></script>
-
 <button id="redraw" onclick="redraw();">&nbsp redraw &nbsp</button>
 
 <div id="wait" style="text-align: center">
@@ -45,66 +40,10 @@ the file called "COPYING".
 
 <div id="canvas" style="min-width:800px; min-height:800px"></div>
 
-<script type="text/javascript">//<![CDATA[
-       var redraw;
-
-       XHR.get('<%=luci.dispatcher.build_url(unpack(location))%>', null,
-               function(x, data)
-               {
-                       var g = new Graph();
-                       var seen = { };
-
-                       for (var i = 0; i < (data.length); i++)
-                       {
-                               // node->node
-                               if (data[i].name)
-                                       {
-                                       for (var j = 0; j < (data[i].links.length); j++)
-                                                {
-                                                       var key = (data[i].name < data[i].links[j].name)
-                                                       ? data[i].name + '|' + data[i].links[j].name
-                                                       : data[i].links[j].name + '|' + data[i].name;
-
-                                                       var rxRate = data[i].links[j].rxRate;
-                                                       var txRate = data[i].links[j].txRate;
-
-                                                       if (!seen[key] && rxRate>0 && txRate>0)
-                                                       {
-                                                        g.addEdge(data[i].name, data[i].links[j].name,
-                                                               { label: rxRate + '/' + txRate,
-                                                               directed: false, stroke: '#aaaaaa', fill: '#ffffff',
-                                                               'label-style': { 'font-size': 8 }});
-                                                        seen[key] = true;
-                                                        }
-                                                }
-                                       }
-                                       //g.addEdge(data[i].router, data[i].neighbor,
-                                       //      { label: data[i].label, directed: true, stroke: '#aaaaaa' });
-                               // node->leaf
-                               //else if (data[i].router && data[i].gateway)
-                               //      g.addEdge(data[i].router, data[i].gateway,
-                               //              { label: 'leaf', stroke: '#cccccc' });
-                       }
-
-                       var canvas = document.getElementById('canvas');
-
-                       var layouter = new Graph.Layout.Spring(g);
-                       layouter.layout();
-                       
-                       var divwait = document.getElementById("wait");
-                       divwait.parentNode.removeChild(divwait);
-
-                       var renderer = new Graph.Renderer.Raphael(canvas.id, g, canvas.offsetWidth, canvas.offsetHeight);
-                       renderer.draw();
-
-                       redraw = function() {
-                               layouter.layout();
-                               renderer.draw();
-                       }
-
-               }
-       );
-//]]></script>
-
+<script type="text/javascript" src="<%=resource%>/bmx6/js/raphael-min.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/dracula_graffle.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/jquery-1.4.2.min.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/dracula_graph.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/bmx6-graph.js"></script>
 
 <%+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 (file)
index 0000000..80233ff
--- /dev/null
@@ -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();
+}