blob: 9c551fd5fbc7f80a26f65badb6b69765595c41a0 [file] [log] [blame]
tom8bb16062014-09-12 14:47:46 -07001<!DOCTYPE html>
2<html>
3<head>
tom4009d2c2014-09-12 15:53:42 -07004 <title>Topology Viewer</title>
5 <script src="libs/d3.v3.min.js"></script>
6 <script src="libs/jquery-1.11.1.min.js"></script>
7 <style>
8 .link {
9 }
10
11 .node {
12 stroke-width: 3px;
13 }
14
15 .textClass {
16 stroke: #323232;
17 font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
18 font-weight: normal;
19 stroke-width: .5;
20 font-size: 14px;
21 }
22 </style>
tom8bb16062014-09-12 14:47:46 -070023</head>
24<body>
tom4009d2c2014-09-12 15:53:42 -070025 <script>
26 // Adapted from sample code @ ericcoopey’s block #6c602d7cb14b25c179a4
27 // by Thomas Vachuska
28
29 var graph,
30 topo = {vertexes:{}, edges:{}},
31 paths = [],
32 currentPath = 0;
33
34 // Set up the canvas
35 var w = 2048,
36 h = 2048;
37
38 // Allocate colors predictably
39 var color = d3.scale.category10(),
40 deviceColor = color(2),
41 sourceColor = color(0),
42 targetColor = color(1),
43 dummy1Color = color(9),
44 dummy2Color = color(8),
45 dummy3Color = color(7),
46 dummy4Color = color(6),
47 offlineColor = color(5),
48 dummy5Color = color(4),
49 hostColor = color(3);
50
51 var selectedNode,
52 sourceNode,
53 targetNode,
54 pathRequested;
55
56
57 function fillColor(d) {
58 return ((targetNode && targetNode.id == d.id) ? targetColor :
59 ((sourceNode && sourceNode.id == d.id) ? sourceColor :
60 d.online ? color(d.group) : offlineColor));
61 }
62
63 function strokeColor(d) {
64 return selectedNode && d.id == selectedNode.id ? "#f00" : "#aaa";
65 }
66
67 function linkColor(d) {
68 if (!paths || paths.length == 0) {
69 return "#666";
70 }
71
72 var path = paths[currentPath];
73 if (path) {
74 for (var i = 0, n = path.length; i < n; i++) {
75 var link = path[i];
76 if ((link.src == d.source.id || link.dst == d.source.id) &&
77 (link.dst == d.target.id || link.src == d.target.id)) {
78 return "#f00";
79 }
80 }
81 }
82 return "#666";
83 }
84
85 function linkKey(link) {
86 return link.source.id + "-" + link.target.id;
87
88 };
89
90 function toggleNode(node) {
91 pathRequested = false;
92 return selectedNode && selectedNode != node ? selectedNode : null;
93
94 };
95
96 function refreshPaths() {
97 d3.selectAll("line").attr("stroke", linkColor);
98 }
99
100 function fetchPaths() {
101 if (!pathRequested && sourceNode && targetNode) {
102 pathRequested = true;
103 d3.json("rs/topology/paths/" + sourceNode.id + "/" + targetNode.id, function(error, data) {
104 currentPath = 0;
105 paths = data.paths;
106 refreshPaths();
107 });
108 }
109 }
110
111 function resetSelections() {
112 selectedNode = null;
113 sourceNode = null;
114 targetNode = null;
115 paths = [];
116 currentPath = 0;
117 refreshPaths();
118 d3.selectAll(".nodeStrokeClass").attr("stroke", strokeColor);
119 }
120
121 function nextPath() {
122 currentPath = paths && currentPath < paths.length - 1 ? currentPath + 1 : 0
123 console.log("Showing path: " + currentPath);
124 refreshPaths();
125 }
126
127 function dblclick(d) {
128 d3.select(this).classed("fixed", d.fixed = false);
129 }
130
131 function dragstart(d) {
tom3897c032014-09-16 00:05:15 -0700132 // d3.select(this).classed("fixed", d.fixed = true);
tom4009d2c2014-09-12 15:53:42 -0700133 }
134
135
136 function topoGraph() {
137 // Add and remove elements on the graph object
138 this.addNode = function (vertex, stamp) {
139 var node = topo.vertexes[vertex.name];
140 if (node) {
141 var oldState = node.online;
142 node.online = vertex.online;
143 node.stamp = stamp;
144 if (oldState != node.online) {
145 update();
146 return true;
147 }
148 return false;
149 }
tom4d0c6632014-09-15 23:27:01 -0700150 node = {"id": vertex.name, "label": vertex.label,
151 "group": vertex.group, "online": vertex.online, "stamp": stamp};
tom4009d2c2014-09-12 15:53:42 -0700152 nodes.push(node);
153 topo.vertexes[vertex.name] = node;
154 update();
155 return true;
156 };
157
158 this.addLink = function (edge, stamp) {
159 var key = edge.source + "-" + edge.target;
160 var link = topo.edges[key];
161 if (link) {
162 var oldValue = link.value;
163 link.value = edge.value;
164 link.stamp = stamp;
165 if (oldValue != link.value) {
166 update();
167 return true;
168 }
169 return false;
170 }
171 link = {"source": findNode(edge.source), "target": findNode(edge.target),
172 "value": edge.value, "stamp": stamp};
173 links.push(link);
174 topo.edges[key] = link;
175 update();
176 return true;
177 };
178
179 this.prune = function (stamp) {
180 var linksChanged = pruneArray(links, stamp, topo.edges, linkKey);
181 var nodesChanged = pruneArray(nodes, stamp, topo.vertexes,
182 function(node) { return node.id; });
183 if (linksChanged || nodesChanged) {
184 update();
185 return true;
186 }
187 return false;
188 };
189
190 var pruneArray = function(array, stamp, map, key) {
191 var changed = false;
192 for (var i = 0; i < array.length; i++) {
193 if (array[i].stamp < stamp) {
194 changed = true;
195 map[key(array[i])] = null;
196 array.splice(i, 1);
197 i--;
198 }
199 }
200 return changed;
201 };
202
203 var findNode = function (id) {
204 for (var i in nodes) {
205 if (nodes[i]["id"] === id) return nodes[i];
206 }
207 };
208
209 var force = d3.layout.force();
210
211 var drag = force.drag()
212 .on("dragstart", dragstart);
213
214 var nodes = force.nodes(),
215 links = force.links();
216
217 var vis = d3.select("body")
218 .append("svg:svg")
219 .attr("width", w)
220 .attr("height", h)
221 .attr("id", "svg")
222 .attr("pointer-events", "all")
223 .attr("viewBox", "0 0 " + w + " " + h)
224 .attr("perserveAspectRatio", "xMinYMid")
225 .append('svg:g');
226
227 d3.select("body")
228 .on("keydown", function(d) {
229 console.log(d3.event.keyCode);
230 if (d3.event.keyCode == 27) {
231 resetSelections();
232 } else if (d3.event.keyCode == 83) {
233 sourceNode = toggleNode(sourceNode);
234 } else if (d3.event.keyCode == 68) {
235 targetNode = toggleNode(targetNode);
236 } else if (d3.event.keyCode == 65) {
237 var aux = sourceNode;
238 sourceNode = targetNode;
239 targetNode = aux;
240 } else if (d3.event.keyCode == 70) {
241 nextPath();
tom3897c032014-09-16 00:05:15 -0700242 } else if (d3.event.keyCode == 67 && selectedNode) {
243 selectedNode.fixed = !selectedNode.fixed;
tom4009d2c2014-09-12 15:53:42 -0700244 }
245
246 d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
247 fetchPaths();
248 });
249
250
251 var update = function () {
252 var link = vis.selectAll("line")
253 .data(links, linkKey);
254
255 link.enter().append("line")
256 .attr("id", linkKey)
257 .attr("class", "link")
258 .attr("stroke-width", function (d) { return d.value; })
259 .attr("stroke", linkColor);
260 link.append("title").text(function (d) { return d.id; });
261 link.exit().remove();
262
263 var node = vis.selectAll("g.node")
264 .data(nodes, function (d) { return d.id; })
265 .on("click", function(d) {
266 selectedNode = d;
267 d3.selectAll(".nodeStrokeClass").attr("stroke", strokeColor);
268 });
269
270 var nodeEnter = node.enter().append("g")
271 .attr("class", "node")
272 .on("dblclick", dblclick)
273 .call(force.drag);
274
275 nodeEnter.append("svg:circle")
276 .attr("r", function(d) { return 28 / 2; })
277 .attr("id", function (d) { return "n-" + d.id.replace(/[.:]/g, ""); })
278 .attr("class", "nodeStrokeClass")
279 .attr("fill", fillColor)
280 .attr("stroke", strokeColor);
281
282 nodeEnter.append("image")
283 .attr("xlink:href", function(d) { return d.group == 2 ? "images/switch.png" : "images/server.png"; })
284 .attr("x", -12)
285 .attr("y", -12)
286 .attr("width", 24)
287 .attr("height", 24);
288
289 nodeEnter.append("svg:text")
290 .attr("class", "textClass")
291 .attr("x", 20)
292 .attr("y", ".31em")
tom4d0c6632014-09-15 23:27:01 -0700293 .text(function (d) { return d.label; });
tom4009d2c2014-09-12 15:53:42 -0700294
295 node.exit().remove();
296
297 d3.selectAll("nodeStrokeClass").attr("stroke", strokeColor);
298
299 force.on("tick", function () {
300 node.attr("transform", function (d) {
301 return "translate(" + d.x + "," + d.y + ")";
302 });
303
304 link.attr("x1", function (d) { return d.source.x; })
305 .attr("y1", function (d) { return d.source.y; })
306 .attr("x2", function (d) { return d.target.x; })
307 .attr("y2", function (d) { return d.target.y; });
308 });
309
310 // Restart the force layout.
311 force
312 .gravity(0.3)
313 .charge(-15000)
314 .friction(0.1)
315 .linkDistance(function(d) { return d.value * 30; })
316 .linkStrength(function(d) { return d.value * 0.6; })
317 .size([w, h])
318 .start();
319 };
320
321 // Make it all go
322 update();
323 }
324
325 function drawGraph() {
326 graph = new topoGraph("#svgdiv");
327 bringNodesToFront();
328 }
329
330 function bringNodesToFront() {
331 $(".nodeStrokeClass").each(function( index ) {
332 var gnode = this.parentNode;
333 gnode.parentNode.appendChild(gnode);
334 });
335 }
336
337 function addNodes() {
338 d3.select("svg")
339 .remove();
340 drawGraph();
341 }
342
343 function fetchData() {
344 var stamp = new Date().getTime();
345 d3.json("rs/topology/graph", function(error, data) {
346 var changed = false;
347 data.vertexes.forEach(function(vertex) { changed = graph.addNode(vertex, stamp) || changed; });
348 data.edges.forEach(function(edge) { changed = graph.addLink(edge, stamp) || changed; });
349
350 changed = graph.prune(stamp) || changed;
351 if (changed) {
352 bringNodesToFront();
353 // Update node and links styles
354 d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
355 d3.selectAll(".line").attr("stroke-width", function (d) { return d.value; })
356 }
357
358 setTimeout(fetchData, 1000);
359 });
360 };
361
362 drawGraph();
363 setTimeout(fetchData, 500);
364
365 </script>
tom8bb16062014-09-12 14:47:46 -0700366</body>
367</html>