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},