more refactoring of topology code
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index 9798add..005bbed 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -1,22 +1,8 @@
<html>
-
<head>
<link rel="stylesheet" href="css/layout.default.css" type="text/css"/>
<link rel="stylesheet" href="css/skin.default.css" type="text/css"/>
- <script src="js/d3.v3.js" charset="utf-8"></script>
- <script src="js/async.js"></script>
- <script src="js/debug.js"></script>
- <script src="js/constants.js"></script>
- <script src="js/globals.js"></script>
- <script src="js/utils.js"></script>
- <script src="js/model.js"></script>
- <script src="js/controller.js"></script>
- <script src="js/controllers.js"></script>
- <script src="js/topology.js"></script>
- <script src="js/flows.js"></script>
- <script src="js/init.js"></script>
</head>
-
<body>
<div id='background'>
<img id='background-image' src='assets/logo.svg'/>
@@ -51,8 +37,21 @@
<div id='flowChooser'></div>
+ <script src="js/d3.v3.js" charset="utf-8"></script>
+ <script src="js/async.js"></script>
+ <script src="js/debug.js"></script>
+ <script src="js/constants.js"></script>
+ <script src="js/globals.js"></script>
+ <script src="js/utils.js"></script>
+ <script src="js/model.js"></script>
+ <script src="js/controller.js"></script>
+ <script src="js/controllers.js"></script>
+ <script src="js/rings.js"></script>
+ <script src="js/topologyactions.js"></script>
+ <script src="js/topology.js"></script>
+ <script src="js/flows.js"></script>
+ <script src="js/init.js"></script>
<script src="js/app.js"></script>
-
</div>
</body>
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index f7eb5b4..34ff2da 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -21,6 +21,8 @@
if (modelChanged) {
updateControllers();
updateSelectedFlows();
+ reconcilePendingLinks(model);
+ updateLinkMap(links);
updateTopology();
}
diff --git a/web/ons-demo/js/globals.js b/web/ons-demo/js/globals.js
index 30f42f2..c7cf5bb 100644
--- a/web/ons-demo/js/globals.js
+++ b/web/ons-demo/js/globals.js
@@ -26,6 +26,16 @@
var pendingLinks = {};
/***************************************************************************************************
+current links including pending
+***************************************************************************************************/
+var links;
+
+/***************************************************************************************************
+a map from srcDPID => map of dstDPID=>link
+***************************************************************************************************/
+var linkMap;
+
+/***************************************************************************************************
the flows that are displayed in the selected flow table
this may include pending flows which have not appeared in the flows API response yet
***************************************************************************************************/
diff --git a/web/ons-demo/js/map.js b/web/ons-demo/js/map.js
new file mode 100644
index 0000000..7cbaaaf
--- /dev/null
+++ b/web/ons-demo/js/map.js
@@ -0,0 +1,11 @@
+// d3.xml("assets/map.svg", "image/svg+xml", function(xml) {
+// var importedNode = document.importNode(xml.documentElement, true);
+// var paths = importedNode.querySelectorAll('path');
+// var i;
+// for (i=0; i < paths.length; i+=1) {
+// svg.append('svg:path')
+// .attr('class', 'state')
+// .attr('d', d3.select(paths.item(i)).attr('d'))
+// .attr('transform', 'translate(-500 -500)scale(1 1.7)')
+// }
+// });
\ No newline at end of file
diff --git a/web/ons-demo/js/rings.js b/web/ons-demo/js/rings.js
new file mode 100644
index 0000000..5141195
--- /dev/null
+++ b/web/ons-demo/js/rings.js
@@ -0,0 +1,126 @@
+function createTopologyView() {
+
+ window.addEventListener('resize', function () {
+ // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
+ // either that or is it possible to position the paths so they get the automatic transform as well?
+// updateTopology();
+ });
+
+ var svg = d3.select('#svg-container').append('svg:svg');
+
+ svg.append("svg:defs").append("svg:marker")
+ .attr("id", "arrow")
+ .attr("viewBox", "0 -5 10 10")
+ .attr("refX", -1)
+ .attr("markerWidth", 5)
+ .attr("markerHeight", 5)
+ .attr("orient", "auto")
+ .append("svg:path")
+ .attr("d", "M0,-3L10,0L0,3");
+
+ return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
+ attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
+}
+
+var widths = {
+ edge: 6,
+ aggregation: 12,
+ core: 18
+}
+
+function createTopologyModel(model) {
+ var rings = [{
+ radius: 3,
+ width: widths.edge,
+ switches: model.edgeSwitches,
+ className: 'edge',
+ angles: []
+ }, {
+ radius: 2.25,
+ width: widths.aggregation,
+ switches: model.aggregationSwitches,
+ className: 'aggregation',
+ angles: []
+ }, {
+ radius: 0.75,
+ width: widths.core,
+ switches: model.coreSwitches,
+ className: 'core',
+ angles: []
+ }];
+
+
+ var aggRanges = {};
+
+ // arrange edge switches at equal increments
+ var k = 360 / rings[0].switches.length;
+ rings[0].switches.forEach(function (s, i) {
+ var angle = k * i;
+
+ rings[0].angles[i] = angle;
+
+ // record the angle for the agg switch layout
+ var dpid = s.dpid.split(':');
+ dpid[7] = '01'; // the last component of the agg switch is always '01'
+ var aggdpid = dpid.join(':');
+ var aggRange = aggRanges[aggdpid];
+ if (!aggRange) {
+ aggRange = aggRanges[aggdpid] = {};
+ aggRange.min = aggRange.max = angle;
+ } else {
+ aggRange.max = angle;
+ }
+ });
+
+ // arrange aggregation switches to "fan out" to edge switches
+ k = 360 / rings[1].switches.length;
+ rings[1].switches.forEach(function (s, i) {
+// rings[1].angles[i] = k * i;
+ var range = aggRanges[s.dpid];
+
+ rings[1].angles[i] = (range.min + range.max)/2;
+ });
+
+ // find the association between core switches and aggregation switches
+ var aggregationSwitchMap = {};
+ model.aggregationSwitches.forEach(function (s, i) {
+ aggregationSwitchMap[s.dpid] = i;
+ });
+
+ // put core switches next to linked aggregation switches
+ k = 360 / rings[2].switches.length;
+ rings[2].switches.forEach(function (s, i) {
+// rings[2].angles[i] = k * i;
+ var associatedAggregationSwitches = model.configuration.association[s.dpid];
+ // TODO: go between if there are multiple
+ var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
+
+ rings[2].angles[i] = rings[1].angles[index];
+ });
+
+ // TODO: construct this form initially rather than converting. it works better because
+ // it allows binding by dpid
+ var testRings = [];
+ rings.forEach(function (ring) {
+ var testRing = [];
+ ring.switches.forEach(function (s, i) {
+ var testSwitch = {
+ dpid: s.dpid,
+ state: s.state,
+ radius: ring.radius,
+ width: ring.width,
+ className: ring.className,
+ angle: ring.angles[i],
+ controller: s.controller
+ };
+ testRing.push(testSwitch);
+ });
+
+
+ testRings.push(testRing);
+ });
+
+
+// return rings;
+ return testRings;
+}
\ No newline at end of file
diff --git a/web/ons-demo/js/topology.js b/web/ons-demo/js/topology.js
index b7a9112..e934972 100644
--- a/web/ons-demo/js/topology.js
+++ b/web/ons-demo/js/topology.js
@@ -4,298 +4,12 @@
flow related topology is in flows.js
***************************************************************************************************/
-function createLinkMap(links) {
- var linkMap = {};
- links.forEach(function (link) {
- var srcDPID = link['src-switch'];
- var dstDPID = link['dst-switch'];
-
- var srcMap = linkMap[srcDPID] || {};
-
- srcMap[dstDPID] = link;
-
- linkMap[srcDPID] = srcMap;
- });
- return linkMap;
-}
-
-
-// removes links from the pending list that are now in the model
-function reconcilePendingLinks(model) {
- var links = [];
- model.links.forEach(function (link) {
- links.push(link);
- delete pendingLinks[makeLinkKey(link)]
- })
- var linkId;
- for (linkId in pendingLinks) {
- links.push(pendingLinks[linkId]);
- }
- return links
-}
-
-
-function createTopologyView() {
-
- window.addEventListener('resize', function () {
- // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
- // either that or is it possible to position the paths so they get the automatic transform as well?
-// updateTopology();
- });
-
- var svg = d3.select('#svg-container').append('svg:svg');
-
- svg.append("svg:defs").append("svg:marker")
- .attr("id", "arrow")
- .attr("viewBox", "0 -5 10 10")
- .attr("refX", -1)
- .attr("markerWidth", 5)
- .attr("markerHeight", 5)
- .attr("orient", "auto")
- .append("svg:path")
- .attr("d", "M0,-3L10,0L0,3");
-
- return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
- attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
-}
-
-// d3.xml("assets/map.svg", "image/svg+xml", function(xml) {
-// var importedNode = document.importNode(xml.documentElement, true);
-// var paths = importedNode.querySelectorAll('path');
-// var i;
-// for (i=0; i < paths.length; i+=1) {
-// svg.append('svg:path')
-// .attr('class', 'state')
-// .attr('d', d3.select(paths.item(i)).attr('d'))
-// .attr('transform', 'translate(-500 -500)scale(1 1.7)')
-// }
-// });
-
-
-var widths = {
- edge: 6,
- aggregation: 12,
- core: 18
-}
-
-function createRingsFromModel(model) {
- var rings = [{
- radius: 3,
- width: widths.edge,
- switches: model.edgeSwitches,
- className: 'edge',
- angles: []
- }, {
- radius: 2.25,
- width: widths.aggregation,
- switches: model.aggregationSwitches,
- className: 'aggregation',
- angles: []
- }, {
- radius: 0.75,
- width: widths.core,
- switches: model.coreSwitches,
- className: 'core',
- angles: []
- }];
-
-
- var aggRanges = {};
-
- // arrange edge switches at equal increments
- var k = 360 / rings[0].switches.length;
- rings[0].switches.forEach(function (s, i) {
- var angle = k * i;
-
- rings[0].angles[i] = angle;
-
- // record the angle for the agg switch layout
- var dpid = s.dpid.split(':');
- dpid[7] = '01'; // the last component of the agg switch is always '01'
- var aggdpid = dpid.join(':');
- var aggRange = aggRanges[aggdpid];
- if (!aggRange) {
- aggRange = aggRanges[aggdpid] = {};
- aggRange.min = aggRange.max = angle;
- } else {
- aggRange.max = angle;
- }
- });
-
- // arrange aggregation switches to "fan out" to edge switches
- k = 360 / rings[1].switches.length;
- rings[1].switches.forEach(function (s, i) {
-// rings[1].angles[i] = k * i;
- var range = aggRanges[s.dpid];
-
- rings[1].angles[i] = (range.min + range.max)/2;
- });
-
- // find the association between core switches and aggregation switches
- var aggregationSwitchMap = {};
- model.aggregationSwitches.forEach(function (s, i) {
- aggregationSwitchMap[s.dpid] = i;
- });
-
- // put core switches next to linked aggregation switches
- k = 360 / rings[2].switches.length;
- rings[2].switches.forEach(function (s, i) {
-// rings[2].angles[i] = k * i;
- var associatedAggregationSwitches = model.configuration.association[s.dpid];
- // TODO: go between if there are multiple
- var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
-
- rings[2].angles[i] = rings[1].angles[index];
- });
-
- // TODO: construct this form initially rather than converting. it works better because
- // it allows binding by dpid
- var testRings = [];
- rings.forEach(function (ring) {
- var testRing = [];
- ring.switches.forEach(function (s, i) {
- var testSwitch = {
- dpid: s.dpid,
- state: s.state,
- radius: ring.radius,
- width: ring.width,
- className: ring.className,
- angle: ring.angles[i],
- controller: s.controller
- };
- testRing.push(testSwitch);
- });
-
-
- testRings.push(testRing);
- });
-
-
-// return rings;
- return testRings;
-}
+(function () {
updateTopology = function() {
// DRAW THE SWITCHES
- var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
-
- var links = reconcilePendingLinks(model);
- var linkMap = createLinkMap(links);
-
- function mouseOverSwitch(data) {
-
- d3.event.preventDefault();
-
- d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
-
- if (data.highlighted) {
- return;
- }
-
- // only highlight valid link or flow destination by checking for class of existing highlighted circle
- var highlighted = svg.selectAll('.highlight')[0];
- if (highlighted.length == 1) {
- var s = d3.select(highlighted[0]).select('circle');
- // 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;
- }
-
- var node = d3.select(document.getElementById(data.dpid));
- node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
- data.highlighted = true;
- node.moveToFront();
- }
-
- function mouseOutSwitch(data) {
- d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
-
- if (data.mouseDown)
- return;
-
- var node = d3.select(document.getElementById(data.dpid));
- node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
- data.highlighted = false;
- data.target = false;
- }
-
- function mouseDownSwitch(data) {
- mouseOverSwitch(data);
- data.mouseDown = true;
- d3.select('#topology').classed('linking', true);
-
- d3.select('svg')
- .append('svg:path')
- .attr('id', 'linkVector')
- .attr('d', function () {
- var s = d3.select(document.getElementById(data.dpid));
-
- var pt = document.querySelector('svg').createSVGPoint();
- pt.x = s.attr('x');
- pt.y = s.attr('y');
- pt = pt.matrixTransform(s[0][0].getCTM());
-
- return line([pt, pt]);
- });
-
-
- if (data.className === 'core') {
- d3.selectAll('.edge').classed('nodrop', true);
- }
- if (data.className === 'edge') {
- d3.selectAll('.core').classed('nodrop', true);
- d3.selectAll('.aggregation').classed('nodrop', true);
- }
- if (data.className === 'aggregation') {
- d3.selectAll('.edge').classed('nodrop', true);
- d3.selectAll('.aggregation').classed('nodrop', true);
- }
- }
-
- function mouseUpSwitch(data) {
- if (data.mouseDown) {
- data.mouseDown = false;
- d3.select('#topology').classed('linking', false);
- d3.event.stopPropagation();
- d3.selectAll('.nodrop').classed('nodrop', false);
- }
- }
-
- function doubleClickSwitch(data) {
- var circle = d3.select(document.getElementById(data.dpid)).select('circle');
- if (data.state == 'ACTIVE') {
- var prompt = 'Deactivate ' + data.dpid + '?';
- if (confirm(prompt)) {
- switchDown(data);
- setPending(circle);
- }
- } else {
- var prompt = 'Activate ' + data.dpid + '?';
- if (confirm(prompt)) {
- switchUp(data);
- setPending(circle);
- }
- }
- }
+ var rings = svg.selectAll('.ring').data(createTopologyModel(model));
function ringEnter(data, i) {
if (!data.length) {
@@ -382,7 +96,7 @@
// Now setup the labels
// This is done separately because SVG draws in node order and we want the labels
// always on top
- var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
+ var labelRings = svg.selectAll('.labelRing').data(createTopologyModel(model));
d3.select(document.body).on('mousemove', function () {
if (!d3.select('#topology').classed('linking')) {
@@ -426,132 +140,6 @@
}
});
- d3.select(document.body).on('mouseup', function () {
- function clearHighlight() {
- svg.selectAll('circle').each(function (data) {
- data.mouseDown = false;
- d3.select('#topology').classed('linking', false);
- mouseOutSwitch(data);
- });
- d3.select('#linkVector').remove();
- };
-
- d3.selectAll('.nodrop').classed('nodrop', false);
-
- function removeLink(link) {
- var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
- var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
-
- if (path1) {
- setPending(d3.select(path1));
- }
- if (path2) {
- setPending(d3.select(path2));
- }
-
- linkDown(link);
- }
-
-
- var highlighted = svg.selectAll('.highlight')[0];
- if (highlighted.length == 2) {
- var s1Data = highlighted[0].__data__;
- var s2Data = highlighted[1].__data__;
-
- 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)) {
- addFlow(srcData, dstData);
-
- var flow = {
- dataPath: {
- srcPort: {
- dpid: {
- value: srcData.dpid
- }
- },
- dstPort: {
- dpid: {
- value: dstData.dpid
- }
- }
- },
- srcDpid: srcData.dpid,
- dstDpid: dstData.dpid,
- createPending: true
- };
-
- selectFlow(flow);
-
- setTimeout(function () {
- deselectFlowIfCreatePending(flow);
- }, pendingTimeout);
- }
- } else {
- var map = linkMap[srcData.dpid];
- if (map && map[dstData.dpid]) {
- var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
- if (confirm(prompt)) {
- removeLink(map[dstData.dpid]);
- }
- } else {
- map = linkMap[dstData.dpid];
- if (map && map[srcData.dpid]) {
- var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
- if (confirm(prompt)) {
- removeLink(map[srcData.dpid]);
- }
- } else {
- var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
- if (confirm(prompt)) {
- var link1 = {
- 'src-switch': srcData.dpid,
- 'src-port': 1,
- 'dst-switch': dstData.dpid,
- 'dst-port': 1,
- pending: true
- };
- pendingLinks[makeLinkKey(link1)] = link1;
- var link2 = {
- 'src-switch': dstData.dpid,
- 'src-port': 1,
- 'dst-switch': srcData.dpid,
- 'dst-port': 1,
- pending: true
- };
- pendingLinks[makeLinkKey(link2)] = link2;
- updateTopology();
-
- linkUp(link1);
-
- // remove the pending links after 10s
- setTimeout(function () {
- delete pendingLinks[makeLinkKey(link1)];
- delete pendingLinks[makeLinkKey(link2)];
-
- updateTopology();
- }, pendingTimeout);
- }
- }
- }
- }
-
- clearHighlight();
- } else {
- clearHighlight();
- }
-
- });
-
function labelRingEnter(data) {
if (!data.length) {
return;
@@ -633,15 +221,15 @@
// DRAW THE LINKS
// key on link dpids since these will come/go during demo
- var links = d3.select('svg').selectAll('.link').data(links, function (d) {
+ var linkLines = d3.select('svg').selectAll('.link').data(links, function (d) {
return d['src-switch']+'->'+d['dst-switch'];
});
// add new links
- links.enter().append("svg:path")
+ linkLines.enter().append("svg:path")
.attr("class", "link");
- links.attr('id', function (d) {
+ linkLines.attr('id', function (d) {
return makeLinkKey(d);
})
.attr("d", function (d) {
@@ -671,5 +259,7 @@
// remove old links
- links.exit().remove();
-}
\ No newline at end of file
+ linkLines.exit().remove();
+}
+
+})();
diff --git a/web/ons-demo/js/topologyactions.js b/web/ons-demo/js/topologyactions.js
new file mode 100644
index 0000000..a7ceaaa
--- /dev/null
+++ b/web/ons-demo/js/topologyactions.js
@@ -0,0 +1,229 @@
+function mouseOverSwitch(data) {
+
+ d3.event.preventDefault();
+
+ d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
+
+ if (data.highlighted) {
+ return;
+ }
+
+ // only highlight valid link or flow destination by checking for class of existing highlighted circle
+ var highlighted = svg.selectAll('.highlight')[0];
+ if (highlighted.length == 1) {
+ var s = d3.select(highlighted[0]).select('circle');
+ // 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;
+ }
+
+ // the second highlighted switch is the target for a link or flow
+ data.target = true;
+ }
+
+ var node = d3.select(document.getElementById(data.dpid));
+ node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
+ data.highlighted = true;
+ node.moveToFront();
+}
+
+function mouseOutSwitch(data) {
+ d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
+
+ if (data.mouseDown)
+ return;
+
+ var node = d3.select(document.getElementById(data.dpid));
+ node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
+ data.highlighted = false;
+ data.target = false;
+}
+
+function mouseDownSwitch(data) {
+ mouseOverSwitch(data);
+ data.mouseDown = true;
+ d3.select('#topology').classed('linking', true);
+
+ d3.select('svg')
+ .append('svg:path')
+ .attr('id', 'linkVector')
+ .attr('d', function () {
+ var s = d3.select(document.getElementById(data.dpid));
+
+ var pt = document.querySelector('svg').createSVGPoint();
+ pt.x = s.attr('x');
+ pt.y = s.attr('y');
+ pt = pt.matrixTransform(s[0][0].getCTM());
+
+ return line([pt, pt]);
+ });
+
+
+ if (data.className === 'core') {
+ d3.selectAll('.edge').classed('nodrop', true);
+ }
+ if (data.className === 'edge') {
+ d3.selectAll('.core').classed('nodrop', true);
+ d3.selectAll('.aggregation').classed('nodrop', true);
+ }
+ if (data.className === 'aggregation') {
+ d3.selectAll('.edge').classed('nodrop', true);
+ d3.selectAll('.aggregation').classed('nodrop', true);
+ }
+}
+
+function mouseUpSwitch(data) {
+ if (data.mouseDown) {
+ data.mouseDown = false;
+ d3.select('#topology').classed('linking', false);
+ d3.event.stopPropagation();
+ d3.selectAll('.nodrop').classed('nodrop', false);
+ }
+}
+
+function doubleClickSwitch(data) {
+ var circle = d3.select(document.getElementById(data.dpid)).select('circle');
+ if (data.state == 'ACTIVE') {
+ var prompt = 'Deactivate ' + data.dpid + '?';
+ if (confirm(prompt)) {
+ switchDown(data);
+ setPending(circle);
+ }
+ } else {
+ var prompt = 'Activate ' + data.dpid + '?';
+ if (confirm(prompt)) {
+ switchUp(data);
+ setPending(circle);
+ }
+ }
+}
+
+d3.select(document.body).on('mouseup', function () {
+ function clearHighlight() {
+ svg.selectAll('circle').each(function (data) {
+ data.mouseDown = false;
+ d3.select('#topology').classed('linking', false);
+ mouseOutSwitch(data);
+ });
+ d3.select('#linkVector').remove();
+ };
+
+ d3.selectAll('.nodrop').classed('nodrop', false);
+
+ function removeLink(link) {
+ var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
+ var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
+
+ if (path1) {
+ setPending(d3.select(path1));
+ }
+ if (path2) {
+ setPending(d3.select(path2));
+ }
+
+ linkDown(link);
+ }
+
+
+ var highlighted = svg.selectAll('.highlight')[0];
+ if (highlighted.length == 2) {
+ var s1Data = highlighted[0].__data__;
+ var s2Data = highlighted[1].__data__;
+
+ 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)) {
+ addFlow(srcData, dstData);
+
+ var flow = {
+ dataPath: {
+ srcPort: {
+ dpid: {
+ value: srcData.dpid
+ }
+ },
+ dstPort: {
+ dpid: {
+ value: dstData.dpid
+ }
+ }
+ },
+ srcDpid: srcData.dpid,
+ dstDpid: dstData.dpid,
+ createPending: true
+ };
+
+ selectFlow(flow);
+
+ setTimeout(function () {
+ deselectFlowIfCreatePending(flow);
+ }, pendingTimeout);
+ }
+ } else {
+ var map = linkMap[srcData.dpid];
+ if (map && map[dstData.dpid]) {
+ var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+ if (confirm(prompt)) {
+ removeLink(map[dstData.dpid]);
+ }
+ } else {
+ map = linkMap[dstData.dpid];
+ if (map && map[srcData.dpid]) {
+ var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
+ if (confirm(prompt)) {
+ removeLink(map[srcData.dpid]);
+ }
+ } else {
+ var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+ if (confirm(prompt)) {
+ var link1 = {
+ 'src-switch': srcData.dpid,
+ 'src-port': 1,
+ 'dst-switch': dstData.dpid,
+ 'dst-port': 1,
+ pending: true
+ };
+ pendingLinks[makeLinkKey(link1)] = link1;
+ var link2 = {
+ 'src-switch': dstData.dpid,
+ 'src-port': 1,
+ 'dst-switch': srcData.dpid,
+ 'dst-port': 1,
+ pending: true
+ };
+ pendingLinks[makeLinkKey(link2)] = link2;
+ updateTopology();
+
+ linkUp(link1);
+
+ // remove the pending links after 10s
+ setTimeout(function () {
+ delete pendingLinks[makeLinkKey(link1)];
+ delete pendingLinks[makeLinkKey(link2)];
+
+ updateTopology();
+ }, pendingTimeout);
+ }
+ }
+ }
+ }
+
+ clearHighlight();
+ } else {
+ clearHighlight();
+ }
+});
\ No newline at end of file
diff --git a/web/ons-demo/js/utils.js b/web/ons-demo/js/utils.js
index d597507..e85eeae 100644
--- a/web/ons-demo/js/utils.js
+++ b/web/ons-demo/js/utils.js
@@ -84,4 +84,36 @@
d3.select('#activeFlows').text(model.flows.length);
}
+/***************************************************************************************************
+update the global linkmap
+***************************************************************************************************/
+function updateLinkMap(links) {
+ linkMap = {};
+ links.forEach(function (link) {
+ var srcDPID = link['src-switch'];
+ var dstDPID = link['dst-switch'];
+
+ var srcMap = linkMap[srcDPID] || {};
+
+ srcMap[dstDPID] = link;
+
+ linkMap[srcDPID] = srcMap;
+ });
+}
+
+/***************************************************************************************************
+// removes links from the pending list that are now in the model
+***************************************************************************************************/
+function reconcilePendingLinks(model) {
+ links = [];
+ model.links.forEach(function (link) {
+ links.push(link);
+ delete pendingLinks[makeLinkKey(link)]
+ })
+ var linkId;
+ for (linkId in pendingLinks) {
+ links.push(pendingLinks[linkId]);
+ }
+}
+