implement interactions for link up/down flow up/down
setup controller interface. link up/down not currently working (or I'm not using them correctly)
diff --git a/web/ons-demo/css/layout.default.css b/web/ons-demo/css/layout.default.css
index 7f80276..e46f165 100644
--- a/web/ons-demo/css/layout.default.css
+++ b/web/ons-demo/css/layout.default.css
@@ -5,6 +5,7 @@
 body {
 	display: -webkit-box;
 	-webkit-box-orient: vertical;
+	-webkit-user-select: none;
 }
 
 #columns {
@@ -45,6 +46,7 @@
 
 .selectedFlow {
 	display: -webkit-box;
+	-webkit-user-select: auto;
 }
 
 #selectedFlowsHeader {
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 482cfbc..753fff3 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -92,6 +92,15 @@
 	background-color:#AAA;
 }
 
+circle.highlight {
+	stroke: rgba(255, 255, 255, .5);
+	stroke-width: 2px;
+}
+
+path {
+	pointer-events: none;
+}
+
 path.flow {
 	fill: none;
 	stroke-width: 3px;
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index 234f1fb..e89f409 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -7,6 +7,7 @@
 	<script src="js/async.js"></script>
 	<script src="js/utils.js"></script>
 	<script src="js/model.js"></script>
+	<script src="js/controller.js"></script>
 </head>
 
 <body>
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index d4cb18f..8cb50ef 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -201,22 +201,28 @@
   return angle * (Math.PI / 180);
 }
 
+var widths = {
+	edge: 6,
+	aggregation: 12,
+	core: 18
+}
+
 function createRingsFromModel(model) {
 	var rings = [{
 		radius: 3,
-		width: 6,
+		width: widths.edge,
 		switches: model.edgeSwitches,
 		className: 'edge',
 		angles: []
 	}, {
 		radius: 2.25,
-		width: 12,
+		width: widths.aggregation,
 		switches: model.aggregationSwitches,
 		className: 'aggregation',
 		angles: []
 	}, {
 		radius: 0.75,
-		width: 18,
+		width: widths.core,
 		switches: model.coreSwitches,
 		className: 'core',
 		angles: []
@@ -298,11 +304,88 @@
 	return testRings;
 }
 
+function createLinkMap(model) {
+	var linkMap = {};
+	model.links.forEach(function (link) {
+		var srcDPID = link['src-switch'];
+		var dstDPID = link['dst-switch'];
+
+		var srcMap = linkMap[srcDPID] || {};
+		var dstMap = linkMap[dstDPID] || {};
+
+		srcMap[dstDPID] = true;
+		dstMap[srcDPID] = true;
+
+		linkMap[srcDPID]  = srcMap;
+		linkMap[dstDPID]  = dstMap;
+	});
+	return linkMap;
+}
+
 updateTopology = function(svg, model) {
 
 	// DRAW THE SWITCHES
 	var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
 
+	var linkMap = createLinkMap(model);
+//	var flowMap = createFlowMap(model);
+
+	function mouseOver(data) {
+		if (data.highlighted) {
+			return;
+		}
+
+		// only highlight valid link or flow destination by checking for class of existing highlighted circle
+		var highlighted = svg.selectAll('circle.highlight')[0];
+		if (highlighted.length == 1) {
+			var s = d3.select(highlighted[0]);
+			// only allow links
+			// 	edge->edge (flow)
+			//  aggregation->core
+			//	core->core
+			if (data.className == 'edge' && !s.classed('edge') ||
+				data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
+				data.className == 'aggregation' && !s.classed('core')) {
+				return;
+			}
+
+			// don't highlight if there's already a link or flow
+			// var map = linkMap[data.dpid];
+			// console.log(map);
+			// console.log(s.data()[0].dpid);
+			// console.log(map[s.data()[0].dpid]);
+			// if (map && map[s.data()[0].dpid]) {
+			// 	return;
+			// }
+
+			// the second highlighted switch is the target for a link or flow
+			data.target = true;
+		}
+
+
+		d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
+		var node = d3.select(document.getElementById(data.dpid));
+		node.select('circle').classed('highlight', true).transition().duration(100).attr("r", widths.core);
+		data.highlighted = true;
+		node.moveToFront();
+	}
+
+	function mouseOut(data) {
+		if (data.mouseDown)
+			return;
+
+		d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
+		var node = d3.select(document.getElementById(data.dpid));
+		node.select('circle').classed('highlight', false).transition().duration(100).attr("r", widths[data.className]);
+		data.highlighted = false;
+		data.target = false;
+	}
+
+	function mouseDown(data) {
+		mouseOver(data);
+		data.mouseDown = true;
+	}
+
 	function ringEnter(data, i) {
 		if (!data.length) {
 			return;
@@ -314,7 +397,6 @@
 				return data.dpid;
 			})
 			.enter().append("svg:g")
-			.classed('nolabel', true)
 			.attr("id", function (data, i) {
 				return data.dpid;
 			})
@@ -342,16 +424,9 @@
 			});
 
 		// setup the mouseover behaviors
-		function showLabel(data, index) {
-			d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
-		}
-
-		function hideLabel(data, index) {
-			d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
-		}
-
-		nodes.on('mouseover', showLabel);
-		nodes.on('mouseout', hideLabel);
+		nodes.on('mouseover', mouseOver);
+		nodes.on('mouseout', mouseOut);
+		nodes.on('mousedown', mouseDown)
 	}
 
 	// append switches
@@ -383,6 +458,58 @@
 	// always on top
 	var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
 
+	d3.select(document.body).on('mouseup', function () {
+		function clearHighlight() {
+			svg.selectAll('circle').each(function (data) {
+				data.mouseDown = false;
+				mouseOut(data);
+			})
+		};
+
+
+		var highlighted = svg.selectAll('circle.highlight')[0];
+		if (highlighted.length == 2) {
+			var s1Data = d3.select(highlighted[0]).data()[0];
+			var s2Data = d3.select(highlighted[1]).data()[0];
+
+			var srcData, dstData;
+			if (s1Data.target) {
+				dstData = s1Data;
+				srcData = s2Data;
+			} else {
+				dstData = s2Data;
+				srcData = s1Data;
+			}
+
+			if (s1Data.className == 'edge' && s2Data.className == 'edge') {
+				var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
+				if (confirm(prompt)) {
+					alert('do create flow');
+				} else {
+					alert('do not create flow');
+				}
+			} else {
+				var map = linkMap[srcData.dpid];
+				if (map && map[dstData.dpid]) {
+					var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+					if (confirm(prompt)) {
+						linkDown(srcData, dstData);
+					}
+				} else {
+					var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+					if (confirm(prompt)) {
+						linkUp(srcData, dstData);
+					}
+				}
+			}
+
+			clearHighlight();
+		} else {
+			clearHighlight();
+		}
+
+	});
+
 	function labelRingEnter(data) {
 		if (!data.length) {
 			return;
@@ -400,7 +527,7 @@
 			})
 			.attr("transform", function(data, i) {
 				return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
-			});
+			})
 
 		// add the text nodes which show on mouse over
 		nodes.append("svg:text")
@@ -457,29 +584,10 @@
 		.attr("class", "textRing")
 		.each(labelRingEnter);
 
-
 	// switches should not change during operation of the ui so no
 	// rings.exit()
 
 
-	// do mouseover zoom on edge nodes
-	function zoom(data, index) {
-		var g = d3.select(document.getElementById(data.dpid)).select('circle');
-			g.transition().duration(100).attr("r", data.width*3);
-			// TODO: this doesn't work because the data binding is by index
-			d3.select(this.parentNode).moveToFront();
-	}
-
-	svg.selectAll('.edge').on('mouseover', zoom);
-	svg.selectAll('.edge').on('mousedown', zoom);
-
-	function unzoom(data, index) {
-		var g = d3.select(document.getElementById(data.dpid)).select('circle');
-			g.transition().duration(100).attr("r", data.width);
-	}
-	svg.selectAll('.edge').on('mouseout', unzoom);
-
-
 	// DRAW THE LINKS
 
 	// key on link dpids since these will come/go during demo
@@ -572,7 +680,7 @@
 function sync(svg, selectedFlowsView) {
 	var d = Date.now();
 	updateModel(function (newModel) {
-		console.log('Update time: ' + (Date.now() - d)/1000 + 's');
+//		console.log('Update time: ' + (Date.now() - d)/1000 + 's');
 
 		if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
 			updateControllers(newModel);
@@ -588,7 +696,7 @@
 			updateFlowView(newModel);
 			updateTopology(svg, newModel);
 		} else {
-			console.log('no change');
+//			console.log('no change');
 		}
 		updateHeader(newModel);
 
diff --git a/web/ons-demo/js/controller.js b/web/ons-demo/js/controller.js
new file mode 100644
index 0000000..86a7905
--- /dev/null
+++ b/web/ons-demo/js/controller.js
@@ -0,0 +1,32 @@
+var controllerFunctions = {
+	link: function (cmd, src, dst) {
+		var url = '/proxy/gui/link/' + [cmd, src.dpid, 1, dst.dpid, 1].join('/');
+		d3.json(url, function (error, result) {		
+			if (error) {
+				alert(url + ' : ' + error.status);
+			}
+		});
+	}
+}
+
+
+// if (parseURLParameters().mock) {
+// 	urls = mockURLs;
+// }
+
+
+function linkUp(src, dst) {
+	controllerFunctions.link('up', src, dst);
+}
+
+function linkDown(src, dst) {
+	controllerFunctions.link('down', src, dst);
+}
+
+function createFlow(src, dst) {
+
+}
+
+function deleteFlow(src, dst) {
+
+}
\ No newline at end of file
diff --git a/web/topology_rest.py b/web/topology_rest.py
index f038046..4409893 100755
--- a/web/topology_rest.py
+++ b/web/topology_rest.py
@@ -76,6 +76,21 @@
 ONOS_GUI3_HOST="http://gui3.onlab.us:8080"
 ONOS_LOCAL_HOST="http://localhost:8080" ;# for Amazon EC2
 
+@app.route("/proxy/gui/link/<cmd>/<src_dpid>/<src_port>/<dst_dpid>/<dst_port>")
+def proxy_link_change(cmd, src_dpid, src_port, dst_dpid, dst_port):
+  try:
+    command = "curl -s %s/gui/link/%s/%s/%s/%s/%s" % (ONOS_GUI3_HOST, cmd, src_dpid, src_port, dst_dpid, dst_port)
+    print command
+    result = os.popen(command).read()
+  except:
+    print "REST IF has issue"
+    exit
+
+  resp = Response(result, status=200, mimetype='application/json')
+  return resp
+
+
+
 @app.route("/wm/core/topology/switches/all/json")
 def switches():
   if request.args.get('proxy') == None:
@@ -85,7 +100,7 @@
 
   try:
     command = "curl -s %s/wm/core/topology/switches/all/json" % (host)
-    print command
+#    print command
     result = os.popen(command).read()
   except:
     print "REST IF has issue"
@@ -103,7 +118,7 @@
 
   try:
     command = "curl -s %s/wm/core/topology/links/json" % (host)
-    print command
+#    print command
     result = os.popen(command).read()
   except:
     print "REST IF has issue"
@@ -121,7 +136,7 @@
 
   try:
     command = "curl -s %s/wm/flow/getsummary/0/0/json" % (host)
-    print command
+#    print command
     result = os.popen(command).read()
   except:
     print "REST IF has issue"
@@ -139,7 +154,7 @@
 
   try:
     command = "curl -s %s/wm/registry/controllers/json" % (host)
-    print command
+#    print command
     result = os.popen(command).read()
   except:
     print "REST IF has issue"
@@ -157,7 +172,7 @@
 
   try:
     command = "curl -s %s/wm/registry/switches/json" % (host)
-    print command
+#    print command
     result = os.popen(command).read()
   except:
     print "REST IF has issue"
@@ -276,7 +291,7 @@
       if i < len(flowEntries) - 1:
         sdpid= flowEntries[i]['dpid']['value']
         ddpid = flowEntries[i+1]['dpid']['value']
-        path.append( (sdpid, ddpid))  
+        path.append( (sdpid, ddpid))
 
   try:
     command = "curl -s \'http://%s:%s/wm/core/topology/links/json\'" % (RestIP, RestPort)
@@ -359,12 +374,12 @@
     parsedResult = []
 #    exit(1)
 
-  path = [];    
+  path = [];
   for i, v in enumerate(parsedResult):
     if i < len(parsedResult) - 1:
       sdpid= parsedResult[i]['switch']
       ddpid = parsedResult[i+1]['switch']
-      path.append( (sdpid, ddpid))  
+      path.append( (sdpid, ddpid))
 
   try:
     command = "curl -s \'http://%s:%s/wm/core/topology/links/json\'" % (RestIP, RestPort)
@@ -488,7 +503,7 @@
         ret = {}
         ret[switchId]=aggr
     else:
-        ret = {} 
+        ret = {}
 
     js = json.dumps(ret)
     resp = Response(js, status=200, mimetype='application/json')
@@ -543,8 +558,8 @@
   resp = Response(js, status=200, mimetype='application/json')
   return resp
 
-topo_less = { 
-  "nodes" : [ 
+topo_less = {
+  "nodes" : [
     {"name" : "00:a0", "group" : 1},
     {"name" : "00:a1", "group" : 1},
     {"name" : "00:a2", "group" : 1},
@@ -559,8 +574,8 @@
     ]
 }
 
-topo_more = { 
-  "nodes" : [ 
+topo_more = {
+  "nodes" : [
     {"name" : "00:a3", "group" : 2},
     {"name" : "00:a0", "group" : 1},
     {"name" : "00:a1", "group" : 1},