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