Polishes bmx6 graph. Adds colors by network and gives color to links based on link...
[feed/routing.git] / luci-app-bmx6 / files / www / luci-static / resources / bmx6 / js / bmx6-graph.js
1 var graph, canvas, layouter, renderer, divwait, nodes, announcements, nodesIndex, palette, localInfo;
2 document.addEventListener( "DOMContentLoaded", init, false);
3
4 /**
5 * Returns an index of nodes by name
6 */
7 function createNodeIndex(nodes) {
8 var inode, index = {};
9
10 for (inode in nodes)
11 index[nodes[inode].name] = nodes[inode];
12
13 return index;
14 }
15
16 /**
17 * Updates to have announcements in nodes list
18 */
19 function processNodeAnnouncements(nodes, announcements) {
20 var iannouncement, remoteNode, announcement;
21 nodesIndex = createNodeIndex(nodes);
22
23 for(iannouncement in announcements) {
24 announcement = announcements[iannouncement];
25 if (announcement.remoteName == '---' ) continue;
26 if (!( announcement.remoteName in nodesIndex )) {
27 newNode = {
28 name: announcement.remoteName,
29 links: []
30 };
31 nodes.push(newNode);
32 nodesIndex[newNode.name] = newNode;
33 };
34
35 remoteNode = nodesIndex[announcement.remoteName];
36 if (!( 'announcements' in remoteNode )) remoteNode.announcements = [];
37 remoteNode.announcements.push(announcement);
38 };
39 }
40
41 function init() {
42 palette = generatePalette(200);
43
44 graph = new Graph();
45 canvas = document.getElementById('canvas');
46 layouter = new Graph.Layout.Spring(graph);
47 renderer = new Graph.Renderer.Raphael(canvas.id, graph, canvas.offsetWidth, canvas.offsetHeight);
48
49 divwait = document.getElementById("wait");
50
51 XHR.get('/cgi-bin/luci/status/bmx6/topology', null, function(nodesRequest, nodesData) {
52 nodes = nodesData;
53
54 XHR.get('/cgi-bin/bmx6-info?$myself&', null, function(myselfRequest, myselfData) {
55 if (myselfData)
56 localAnnouncements = [
57 {remoteName: myselfData.myself.hostname, advNet: myselfData.myself.net4},
58 {remoteName: myselfData.myself.hostname, advNet: myselfData.myself.net6}
59 ];
60
61 XHR.get('/cgi-bin/bmx6-info?$tunnels=&', null, function(tunnelsRequest, tunnelsData) {
62 var iAnnouncement;
63
64 announcements = tunnelsData.tunnels;
65 for(iAnnouncement in localAnnouncements) {
66 announcements.push(localAnnouncements[iAnnouncement])
67 };
68
69 processNodeAnnouncements(nodes, announcements);
70
71 divwait.parentNode.removeChild(divwait);
72 draw(nodes);
73 });
74 });
75 });
76 }
77
78 function hashCode(str) {
79 var hash = 0;
80 if (str.length == 0) return hash;
81 for (i = 0; i < str.length; i++) {
82 char = str.charCodeAt(i);
83 hash = ((hash<<5)-hash)+char;
84 hash = hash & hash; // Convert to 32bit integer
85 }
86 return hash;
87 }
88
89 function generatePalette(size) {
90 var i, arr = [];
91 Raphael.getColor(); // just to remove the grey one
92 for(i = 0; i < size; i++) {
93 arr.push(Raphael.getColor())
94 }
95
96 return arr;
97 }
98
99 function getFillFromHash(hash) {
100 return palette[Math.abs(hash % palette.length)];
101 }
102
103 function hashAnnouncementsNames(announcementsNames) {
104 return hashCode(announcementsNames.sort().join('-'));
105 }
106
107 function getNodeAnnouncements(networkNode) {
108 return networkNode.announcements;
109 }
110
111 function nodeRenderer(raphael, node) {
112 var nodeFill, renderedNode, options;
113 options = {
114 'fill': 'announcements' in node.networkNode ? getFillFromHash(
115 hashAnnouncementsNames(
116 getNodeAnnouncements(node.networkNode).map(function(ann) {return ann.advNet;})
117 )
118 ) : '#bfbfbf',
119 'stroke-width': 1,
120
121 };
122
123 renderedNode = raphael.set();
124
125 renderedNode.push(raphael.ellipse(node.point[0], node.point[1], 30, 20).attr({"fill": options['fill'], "stroke-width": options['stroke-width']}));
126 renderedNode.push(raphael.text(node.point[0], node.point[1] + 30, node.networkNode.name).attr({}));
127
128 renderedNode.items.forEach(function(el) {
129 var announcements, tooltip = raphael.set();
130 tooltip.push(raphael.rect(-60, -60, 120, 60).attr({"fill": "#fec", "stroke-width": 1, r : "9px"}));
131
132 announcements = getNodeAnnouncements(node.networkNode);
133 if (announcements) {
134
135 announcements = announcements.map(function(ann) {return ann.advNet});
136 tooltip.push(raphael.text(0, -40, 'announcements\n' + announcements.join('\n')).attr({}));
137 };
138
139 el.tooltip(tooltip);
140 });
141
142 return renderedNode;
143 }
144
145 function genericNodeRenderer(raphael, node) {
146 var renderedNode;
147
148 renderedNode = raphael.set();
149
150 renderedNode.push(raphael.ellipse(node.point[0], node.point[1], 30, 20).attr({"fill": '#bfbfbf', "stroke-width": 1}));
151 renderedNode.push(raphael.text(node.point[0], node.point[1] + 30, node.networkNode.name).attr({}));
152
153 return renderedNode;
154 }
155
156 function redraw() {
157 layouter.layout();
158 renderer.draw();
159 }
160
161 function interpolateColor(minColor,maxColor,maxDepth,depth){
162
163 function d2h(d) {return d.toString(16);}
164 function h2d(h) {return parseInt(h,16);}
165
166 if(depth == 0){
167 return minColor;
168 }
169 if(depth == maxDepth){
170 return maxColor;
171 }
172
173 var color = "#";
174
175 for(var i=1; i <= 6; i+=2){
176 var minVal = new Number(h2d(minColor.substr(i,2)));
177 var maxVal = new Number(h2d(maxColor.substr(i,2)));
178 var nVal = minVal + (maxVal-minVal) * (depth/maxDepth);
179 var val = d2h(Math.floor(nVal));
180 while(val.length < 2){
181 val = "0"+val;
182 }
183 color += val;
184 }
185 return color;
186 }
187 function draw(nodes) {
188 var node, neighbourNode, seenKey, rxRate, txRate, seen, i, j, currentName, linkQuality;
189
190 seen = { };
191
192 for (i = 0; i < (nodes.length); i++) {
193 node = nodes[i];
194 graph.addNode(node.name, {
195 networkNode: node,
196 render: nodeRenderer
197 });
198 };
199
200 for (i = 0; i < (nodes.length); i++) {
201 node = nodes[i];
202
203 if (! node.name) continue;
204
205 currentName = node.name;
206
207 for (j = 0; j < (node.links.length); j++) {
208 neighbourNode = node.links[j];
209
210 graph.addNode(neighbourNode.name, {render: genericNodeRenderer, networkNode: neighbourNode});
211
212 seenKey = (node.name < neighbourNode.name) ? node.name + '|' + neighbourNode.name : neighbourNode.name + '|' + node.name;
213
214 rxRate = neighbourNode.rxRate;
215 txRate = neighbourNode.txRate;
216
217 if (!seen[seenKey] && rxRate > 0 && txRate > 0) {
218 linkQuality = ( rxRate + txRate ) / 2;
219
220 graph.addEdge(node.name, neighbourNode.name, {
221 'label': rxRate + '/' + txRate,
222 'directed': false,
223 'stroke': interpolateColor('FF0000','00FF00', 5, 5 * ( linkQuality - 1 )/100),
224 'fill': interpolateColor('FF0000','00FF00', 5, 5 * ( linkQuality - 1 )/100),
225 'label-style': { 'font-size': 8 }
226 });
227
228 seen[seenKey] = true;
229 }
230 }
231 }
232
233 redraw();
234 }