Adding a sample GUI page to visualize topology.
diff --git a/apps/tvue/src/main/webapp/index.html b/apps/tvue/src/main/webapp/index.html
index f959f93..04abfa4 100644
--- a/apps/tvue/src/main/webapp/index.html
+++ b/apps/tvue/src/main/webapp/index.html
@@ -1,10 +1,365 @@
<!DOCTYPE html>
<html>
<head>
- <title>ONOS GUI</title>
+ <title>Topology Viewer</title>
+ <script src="libs/d3.v3.min.js"></script>
+ <script src="libs/jquery-1.11.1.min.js"></script>
+ <style>
+ .link {
+ }
+
+ .node {
+ stroke-width: 3px;
+ }
+
+ .textClass {
+ stroke: #323232;
+ font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
+ font-weight: normal;
+ stroke-width: .5;
+ font-size: 14px;
+ }
+ </style>
</head>
<body>
- <h1>ONOS GUI</h1>
- Sort of...
+ <script>
+ // Adapted from sample code @ ericcoopey’s block #6c602d7cb14b25c179a4
+ // by Thomas Vachuska
+
+ var graph,
+ topo = {vertexes:{}, edges:{}},
+ paths = [],
+ currentPath = 0;
+
+ // Set up the canvas
+ var w = 2048,
+ h = 2048;
+
+ // Allocate colors predictably
+ var color = d3.scale.category10(),
+ deviceColor = color(2),
+ sourceColor = color(0),
+ targetColor = color(1),
+ dummy1Color = color(9),
+ dummy2Color = color(8),
+ dummy3Color = color(7),
+ dummy4Color = color(6),
+ offlineColor = color(5),
+ dummy5Color = color(4),
+ hostColor = color(3);
+
+ var selectedNode,
+ sourceNode,
+ targetNode,
+ pathRequested;
+
+
+ function fillColor(d) {
+ return ((targetNode && targetNode.id == d.id) ? targetColor :
+ ((sourceNode && sourceNode.id == d.id) ? sourceColor :
+ d.online ? color(d.group) : offlineColor));
+ }
+
+ function strokeColor(d) {
+ return selectedNode && d.id == selectedNode.id ? "#f00" : "#aaa";
+ }
+
+ function linkColor(d) {
+ if (!paths || paths.length == 0) {
+ return "#666";
+ }
+
+ var path = paths[currentPath];
+ if (path) {
+ for (var i = 0, n = path.length; i < n; i++) {
+ var link = path[i];
+ if ((link.src == d.source.id || link.dst == d.source.id) &&
+ (link.dst == d.target.id || link.src == d.target.id)) {
+ return "#f00";
+ }
+ }
+ }
+ return "#666";
+ }
+
+ function linkKey(link) {
+ return link.source.id + "-" + link.target.id;
+
+ };
+
+ function toggleNode(node) {
+ pathRequested = false;
+ return selectedNode && selectedNode != node ? selectedNode : null;
+
+ };
+
+ function refreshPaths() {
+ d3.selectAll("line").attr("stroke", linkColor);
+ }
+
+ function fetchPaths() {
+ if (!pathRequested && sourceNode && targetNode) {
+ pathRequested = true;
+ d3.json("rs/topology/paths/" + sourceNode.id + "/" + targetNode.id, function(error, data) {
+ currentPath = 0;
+ paths = data.paths;
+ refreshPaths();
+ });
+ }
+ }
+
+ function resetSelections() {
+ selectedNode = null;
+ sourceNode = null;
+ targetNode = null;
+ paths = [];
+ currentPath = 0;
+ refreshPaths();
+ d3.selectAll(".nodeStrokeClass").attr("stroke", strokeColor);
+ }
+
+ function nextPath() {
+ currentPath = paths && currentPath < paths.length - 1 ? currentPath + 1 : 0
+ console.log("Showing path: " + currentPath);
+ refreshPaths();
+ }
+
+ function dblclick(d) {
+ d3.select(this).classed("fixed", d.fixed = false);
+ }
+
+ function dragstart(d) {
+ d3.select(this).classed("fixed", d.fixed = true);
+ }
+
+
+ function topoGraph() {
+ // Add and remove elements on the graph object
+ this.addNode = function (vertex, stamp) {
+ var node = topo.vertexes[vertex.name];
+ if (node) {
+ var oldState = node.online;
+ node.online = vertex.online;
+ node.stamp = stamp;
+ if (oldState != node.online) {
+ update();
+ return true;
+ }
+ return false;
+ }
+ node = {"id": vertex.name, "group": vertex.group,
+ "online": vertex.online, "stamp": stamp};
+ nodes.push(node);
+ topo.vertexes[vertex.name] = node;
+ update();
+ return true;
+ };
+
+ this.addLink = function (edge, stamp) {
+ var key = edge.source + "-" + edge.target;
+ var link = topo.edges[key];
+ if (link) {
+ var oldValue = link.value;
+ link.value = edge.value;
+ link.stamp = stamp;
+ if (oldValue != link.value) {
+ update();
+ return true;
+ }
+ return false;
+ }
+ link = {"source": findNode(edge.source), "target": findNode(edge.target),
+ "value": edge.value, "stamp": stamp};
+ links.push(link);
+ topo.edges[key] = link;
+ update();
+ return true;
+ };
+
+ this.prune = function (stamp) {
+ var linksChanged = pruneArray(links, stamp, topo.edges, linkKey);
+ var nodesChanged = pruneArray(nodes, stamp, topo.vertexes,
+ function(node) { return node.id; });
+ if (linksChanged || nodesChanged) {
+ update();
+ return true;
+ }
+ return false;
+ };
+
+ var pruneArray = function(array, stamp, map, key) {
+ var changed = false;
+ for (var i = 0; i < array.length; i++) {
+ if (array[i].stamp < stamp) {
+ changed = true;
+ map[key(array[i])] = null;
+ array.splice(i, 1);
+ i--;
+ }
+ }
+ return changed;
+ };
+
+ var findNode = function (id) {
+ for (var i in nodes) {
+ if (nodes[i]["id"] === id) return nodes[i];
+ }
+ };
+
+ var force = d3.layout.force();
+
+ var drag = force.drag()
+ .on("dragstart", dragstart);
+
+ var nodes = force.nodes(),
+ links = force.links();
+
+ var vis = d3.select("body")
+ .append("svg:svg")
+ .attr("width", w)
+ .attr("height", h)
+ .attr("id", "svg")
+ .attr("pointer-events", "all")
+ .attr("viewBox", "0 0 " + w + " " + h)
+ .attr("perserveAspectRatio", "xMinYMid")
+ .append('svg:g');
+
+ d3.select("body")
+ .on("keydown", function(d) {
+ console.log(d3.event.keyCode);
+ if (d3.event.keyCode == 27) {
+ resetSelections();
+ } else if (d3.event.keyCode == 83) {
+ sourceNode = toggleNode(sourceNode);
+ } else if (d3.event.keyCode == 68) {
+ targetNode = toggleNode(targetNode);
+ } else if (d3.event.keyCode == 65) {
+ var aux = sourceNode;
+ sourceNode = targetNode;
+ targetNode = aux;
+ } else if (d3.event.keyCode == 70) {
+ nextPath();
+ }
+
+ d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
+ fetchPaths();
+ });
+
+
+ var update = function () {
+ var link = vis.selectAll("line")
+ .data(links, linkKey);
+
+ link.enter().append("line")
+ .attr("id", linkKey)
+ .attr("class", "link")
+ .attr("stroke-width", function (d) { return d.value; })
+ .attr("stroke", linkColor);
+ link.append("title").text(function (d) { return d.id; });
+ link.exit().remove();
+
+ var node = vis.selectAll("g.node")
+ .data(nodes, function (d) { return d.id; })
+ .on("click", function(d) {
+ selectedNode = d;
+ d3.selectAll(".nodeStrokeClass").attr("stroke", strokeColor);
+ });
+
+ var nodeEnter = node.enter().append("g")
+ .attr("class", "node")
+ .on("dblclick", dblclick)
+ .call(force.drag);
+
+ nodeEnter.append("svg:circle")
+ .attr("r", function(d) { return 28 / 2; })
+ .attr("id", function (d) { return "n-" + d.id.replace(/[.:]/g, ""); })
+ .attr("class", "nodeStrokeClass")
+ .attr("fill", fillColor)
+ .attr("stroke", strokeColor);
+
+ nodeEnter.append("image")
+ .attr("xlink:href", function(d) { return d.group == 2 ? "images/switch.png" : "images/server.png"; })
+ .attr("x", -12)
+ .attr("y", -12)
+ .attr("width", 24)
+ .attr("height", 24);
+
+ nodeEnter.append("svg:text")
+ .attr("class", "textClass")
+ .attr("x", 20)
+ .attr("y", ".31em")
+ .text(function (d) { return d.id; });
+
+ node.exit().remove();
+
+ d3.selectAll("nodeStrokeClass").attr("stroke", strokeColor);
+
+ force.on("tick", function () {
+ node.attr("transform", function (d) {
+ return "translate(" + d.x + "," + d.y + ")";
+ });
+
+ link.attr("x1", function (d) { return d.source.x; })
+ .attr("y1", function (d) { return d.source.y; })
+ .attr("x2", function (d) { return d.target.x; })
+ .attr("y2", function (d) { return d.target.y; });
+ });
+
+ // Restart the force layout.
+ force
+ .gravity(0.3)
+ .charge(-15000)
+ .friction(0.1)
+ .linkDistance(function(d) { return d.value * 30; })
+ .linkStrength(function(d) { return d.value * 0.6; })
+ .size([w, h])
+ .start();
+ };
+
+ // Make it all go
+ update();
+ }
+
+ function drawGraph() {
+ graph = new topoGraph("#svgdiv");
+ bringNodesToFront();
+ }
+
+ function bringNodesToFront() {
+ $(".nodeStrokeClass").each(function( index ) {
+ var gnode = this.parentNode;
+ gnode.parentNode.appendChild(gnode);
+ });
+ }
+
+ function addNodes() {
+ d3.select("svg")
+ .remove();
+ drawGraph();
+ }
+
+ function fetchData() {
+ var stamp = new Date().getTime();
+ d3.json("rs/topology/graph", function(error, data) {
+ var changed = false;
+ data.vertexes.forEach(function(vertex) { changed = graph.addNode(vertex, stamp) || changed; });
+ data.edges.forEach(function(edge) { changed = graph.addLink(edge, stamp) || changed; });
+
+ changed = graph.prune(stamp) || changed;
+ if (changed) {
+ bringNodesToFront();
+ // Update node and links styles
+ d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
+ d3.selectAll(".line").attr("stroke-width", function (d) { return d.value; })
+ }
+
+ setTimeout(fetchData, 1000);
+ });
+ };
+
+ drawGraph();
+ setTimeout(fetchData, 500);
+
+ </script>
</body>
</html>
\ No newline at end of file