Adding a sample GUI page to visualize topology.
diff --git a/apps/tvue/pom.xml b/apps/tvue/pom.xml
index d0380a3..0e0fc70 100644
--- a/apps/tvue/pom.xml
+++ b/apps/tvue/pom.xml
@@ -11,7 +11,7 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>onos-tvue</artifactId>
+    <artifactId>onos-app-tvue</artifactId>
     <packaging>bundle</packaging>
 
     <description>ONOS simple topology viewer</description>
diff --git a/apps/tvue/src/main/java/org/onlab/onos/tvue/TopologyResource.java b/apps/tvue/src/main/java/org/onlab/onos/tvue/TopologyResource.java
index 996a81f..283b8fb 100644
--- a/apps/tvue/src/main/java/org/onlab/onos/tvue/TopologyResource.java
+++ b/apps/tvue/src/main/java/org/onlab/onos/tvue/TopologyResource.java
@@ -100,7 +100,7 @@
     private ObjectNode json(ObjectMapper mapper, ElementId id, int group,
                             boolean isOnline) {
         return mapper.createObjectNode()
-                .put("name", id.uri().toString())
+                .put("name", id.uri().getSchemeSpecificPart())
                 .put("group", group)
                 .put("online", isOnline);
     }
@@ -147,7 +147,7 @@
     // Returns a formatted string for the element associated with the given
     // connection point.
     private static String id(ConnectPoint cp) {
-        return cp.elementId().uri().toString();
+        return cp.elementId().uri().getSchemeSpecificPart();
     }
 
 }
diff --git a/apps/tvue/src/main/webapp/WEB-INF/web.xml b/apps/tvue/src/main/webapp/WEB-INF/web.xml
index d958283..47b3150 100644
--- a/apps/tvue/src/main/webapp/WEB-INF/web.xml
+++ b/apps/tvue/src/main/webapp/WEB-INF/web.xml
@@ -14,7 +14,7 @@
         <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
         <init-param>
             <param-name>com.sun.jersey.config.property.packages</param-name>
-            <param-value>org.onlab.onos.gui</param-value>
+            <param-value>org.onlab.onos.tvue</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
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
diff --git a/features/features.xml b/features/features.xml
index 1d4da58..9f5f6c8 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -62,7 +62,6 @@
         <feature>onos-core</feature>
         <bundle>mvn:io.netty/netty/3.9.2.Final</bundle>
 
-        <!--bundle>mvn:org.projectfloodlight/openflowj/0.3.8-SNAPSHOT</bundle-->
         <bundle>mvn:org.onlab.onos/onos-of-api/1.0.0-SNAPSHOT</bundle>
         <bundle>mvn:org.onlab.onos/onos-of-ctl/1.0.0-SNAPSHOT</bundle>
 
@@ -71,4 +70,10 @@
         <bundle>mvn:org.onlab.onos/onos-of-provider-host/1.0.0-SNAPSHOT</bundle>
     </feature>
 
+    <feature name="onos-app-tvue" version="1.0.0"
+             description="ONOS sample topology viewer application">
+        <feature>onos-core</feature>
+        <bundle>mvn:org.onlab.onos/onos-app-tvue/1.0.0-SNAPSHOT</bundle>
+    </feature>
+
 </features>