Showing
5 changed files
with
368 additions
and
8 deletions
... | @@ -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> | ... | ... |
-
Please register or login to post a comment