blob: cf390198d795d6945a3c69c7a223616a60288500 [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) {
132 d3.select(this).classed("fixed", d.fixed = true);
133 }
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();
242 }
243
244 d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
245 fetchPaths();
246 });
247
248
249 var update = function () {
250 var link = vis.selectAll("line")
251 .data(links, linkKey);
252
253 link.enter().append("line")
254 .attr("id", linkKey)
255 .attr("class", "link")
256 .attr("stroke-width", function (d) { return d.value; })
257 .attr("stroke", linkColor);
258 link.append("title").text(function (d) { return d.id; });
259 link.exit().remove();
260
261 var node = vis.selectAll("g.node")
262 .data(nodes, function (d) { return d.id; })
263 .on("click", function(d) {
264 selectedNode = d;
265 d3.selectAll(".nodeStrokeClass").attr("stroke", strokeColor);
266 });
267
268 var nodeEnter = node.enter().append("g")
269 .attr("class", "node")
270 .on("dblclick", dblclick)
271 .call(force.drag);
272
273 nodeEnter.append("svg:circle")
274 .attr("r", function(d) { return 28 / 2; })
275 .attr("id", function (d) { return "n-" + d.id.replace(/[.:]/g, ""); })
276 .attr("class", "nodeStrokeClass")
277 .attr("fill", fillColor)
278 .attr("stroke", strokeColor);
279
280 nodeEnter.append("image")
281 .attr("xlink:href", function(d) { return d.group == 2 ? "images/switch.png" : "images/server.png"; })
282 .attr("x", -12)
283 .attr("y", -12)
284 .attr("width", 24)
285 .attr("height", 24);
286
287 nodeEnter.append("svg:text")
288 .attr("class", "textClass")
289 .attr("x", 20)
290 .attr("y", ".31em")
tom4d0c6632014-09-15 23:27:01 -0700291 .text(function (d) { return d.label; });
tom4009d2c2014-09-12 15:53:42 -0700292
293 node.exit().remove();
294
295 d3.selectAll("nodeStrokeClass").attr("stroke", strokeColor);
296
297 force.on("tick", function () {
298 node.attr("transform", function (d) {
299 return "translate(" + d.x + "," + d.y + ")";
300 });
301
302 link.attr("x1", function (d) { return d.source.x; })
303 .attr("y1", function (d) { return d.source.y; })
304 .attr("x2", function (d) { return d.target.x; })
305 .attr("y2", function (d) { return d.target.y; });
306 });
307
308 // Restart the force layout.
309 force
310 .gravity(0.3)
311 .charge(-15000)
312 .friction(0.1)
313 .linkDistance(function(d) { return d.value * 30; })
314 .linkStrength(function(d) { return d.value * 0.6; })
315 .size([w, h])
316 .start();
317 };
318
319 // Make it all go
320 update();
321 }
322
323 function drawGraph() {
324 graph = new topoGraph("#svgdiv");
325 bringNodesToFront();
326 }
327
328 function bringNodesToFront() {
329 $(".nodeStrokeClass").each(function( index ) {
330 var gnode = this.parentNode;
331 gnode.parentNode.appendChild(gnode);
332 });
333 }
334
335 function addNodes() {
336 d3.select("svg")
337 .remove();
338 drawGraph();
339 }
340
341 function fetchData() {
342 var stamp = new Date().getTime();
343 d3.json("rs/topology/graph", function(error, data) {
344 var changed = false;
345 data.vertexes.forEach(function(vertex) { changed = graph.addNode(vertex, stamp) || changed; });
346 data.edges.forEach(function(edge) { changed = graph.addLink(edge, stamp) || changed; });
347
348 changed = graph.prune(stamp) || changed;
349 if (changed) {
350 bringNodesToFront();
351 // Update node and links styles
352 d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
353 d3.selectAll(".line").attr("stroke-width", function (d) { return d.value; })
354 }
355
356 setTimeout(fetchData, 1000);
357 });
358 };
359
360 drawGraph();
361 setTimeout(fetchData, 500);
362
363 </script>
tom8bb16062014-09-12 14:47:46 -0700364</body>
365</html>