tom

Adding a sample GUI page to visualize topology.

...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
11 <relativePath>../pom.xml</relativePath> 11 <relativePath>../pom.xml</relativePath>
12 </parent> 12 </parent>
13 13
14 - <artifactId>onos-tvue</artifactId> 14 + <artifactId>onos-app-tvue</artifactId>
15 <packaging>bundle</packaging> 15 <packaging>bundle</packaging>
16 16
17 <description>ONOS simple topology viewer</description> 17 <description>ONOS simple topology viewer</description>
......
...@@ -100,7 +100,7 @@ public class TopologyResource extends BaseResource { ...@@ -100,7 +100,7 @@ public class TopologyResource extends BaseResource {
100 private ObjectNode json(ObjectMapper mapper, ElementId id, int group, 100 private ObjectNode json(ObjectMapper mapper, ElementId id, int group,
101 boolean isOnline) { 101 boolean isOnline) {
102 return mapper.createObjectNode() 102 return mapper.createObjectNode()
103 - .put("name", id.uri().toString()) 103 + .put("name", id.uri().getSchemeSpecificPart())
104 .put("group", group) 104 .put("group", group)
105 .put("online", isOnline); 105 .put("online", isOnline);
106 } 106 }
...@@ -147,7 +147,7 @@ public class TopologyResource extends BaseResource { ...@@ -147,7 +147,7 @@ public class TopologyResource extends BaseResource {
147 // Returns a formatted string for the element associated with the given 147 // Returns a formatted string for the element associated with the given
148 // connection point. 148 // connection point.
149 private static String id(ConnectPoint cp) { 149 private static String id(ConnectPoint cp) {
150 - return cp.elementId().uri().toString(); 150 + return cp.elementId().uri().getSchemeSpecificPart();
151 } 151 }
152 152
153 } 153 }
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
14 <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> 14 <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
15 <init-param> 15 <init-param>
16 <param-name>com.sun.jersey.config.property.packages</param-name> 16 <param-name>com.sun.jersey.config.property.packages</param-name>
17 - <param-value>org.onlab.onos.gui</param-value> 17 + <param-value>org.onlab.onos.tvue</param-value>
18 </init-param> 18 </init-param>
19 <load-on-startup>1</load-on-startup> 19 <load-on-startup>1</load-on-startup>
20 </servlet> 20 </servlet>
......
1 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <html> 2 <html>
3 <head> 3 <head>
4 - <title>ONOS GUI</title> 4 + <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>
5 </head> 23 </head>
6 <body> 24 <body>
7 - <h1>ONOS GUI</h1> 25 + <script>
8 - Sort of... 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 + }
150 + node = {"id": vertex.name, "group": vertex.group,
151 + "online": vertex.online, "stamp": stamp};
152 + 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")
291 + .text(function (d) { return d.id; });
292 +
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>
9 </body> 364 </body>
10 </html> 365 </html>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -62,7 +62,6 @@ ...@@ -62,7 +62,6 @@
62 <feature>onos-core</feature> 62 <feature>onos-core</feature>
63 <bundle>mvn:io.netty/netty/3.9.2.Final</bundle> 63 <bundle>mvn:io.netty/netty/3.9.2.Final</bundle>
64 64
65 - <!--bundle>mvn:org.projectfloodlight/openflowj/0.3.8-SNAPSHOT</bundle-->
66 <bundle>mvn:org.onlab.onos/onos-of-api/1.0.0-SNAPSHOT</bundle> 65 <bundle>mvn:org.onlab.onos/onos-of-api/1.0.0-SNAPSHOT</bundle>
67 <bundle>mvn:org.onlab.onos/onos-of-ctl/1.0.0-SNAPSHOT</bundle> 66 <bundle>mvn:org.onlab.onos/onos-of-ctl/1.0.0-SNAPSHOT</bundle>
68 67
...@@ -71,4 +70,10 @@ ...@@ -71,4 +70,10 @@
71 <bundle>mvn:org.onlab.onos/onos-of-provider-host/1.0.0-SNAPSHOT</bundle> 70 <bundle>mvn:org.onlab.onos/onos-of-provider-host/1.0.0-SNAPSHOT</bundle>
72 </feature> 71 </feature>
73 72
73 + <feature name="onos-app-tvue" version="1.0.0"
74 + description="ONOS sample topology viewer application">
75 + <feature>onos-core</feature>
76 + <bundle>mvn:org.onlab.onos/onos-app-tvue/1.0.0-SNAPSHOT</bundle>
77 + </feature>
78 +
74 </features> 79 </features>
......