Merge pull request #366 from pgreyson/master
Display flow counts through core/core links. New confirm dialog.
diff --git a/web/ons-demo/RELEASE_NOTES.txt b/web/ons-demo/RELEASE_NOTES.txt
index bfe6059..369e9c4 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,3 +1,7 @@
+** April 9, 2013 **
+- display number of flows for each core<->core link
+- graphics tweaks
+
** April 8, 2013 **
- map view
- onos nodes at top
diff --git a/web/ons-demo/css/layout.default.css b/web/ons-demo/css/layout.default.css
index 297e31b..f3daf04 100644
--- a/web/ons-demo/css/layout.default.css
+++ b/web/ons-demo/css/layout.default.css
@@ -27,6 +27,38 @@
-webkit-box-flex: 1.0;
}
+#confirm {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 100%;
+ display: -webkit-box;
+ -webkit-box-align: center;
+ -webkit-box-pack: center;
+}
+
+#confirm-background {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 100%;
+}
+
+#confirm-panel {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-box-pack: justify;
+}
+
+#confirm-buttons {
+ display: -webkit-box;
+ -webkit-box-pack: center;
+ -webkit-box-flex: 1.0;
+ -webkit-box-align: center;
+}
+
.header {
width: 100%;
display: -webkit-box;
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index f6086a3..9f1ecf1 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -106,6 +106,34 @@
border-bottom: 1px solid #AAA;;
}
+#onos {
+ background-color: #333;
+}
+
+#cluster-label {
+ font-size: 22px;
+ display: -webkit-box;
+ -webkit-box-align: center;
+ padding-left: .5em;
+ padding-right: .5em;
+ color: #EEE;
+}
+
+#actions {
+ padding-right: .25em;
+ padding-left: .25em;
+ border-left: 1px solid white;
+ display: -webkit-box;
+ -webkit-box-align: center;
+}
+
+#controllers {
+ padding: .25em;
+ background-color: black;
+ margin: .25em;
+ border-radius: 8px;
+}
+
#flowChooser .selectedFlow {
background-color: rgba(255, 255, 255, .75);
color: black;
@@ -208,6 +236,7 @@
border: 1px solid #444;
color: white;
position: relative;
+ border-radius: 8px;
}
.controller:hover {
@@ -395,19 +424,21 @@
.action {
margin: .25em;
- padding: .25em;
- padding-left: 1em;
- padding-right: 1em;
- border: 1px solid #AAA;
+ border: 2px solid #AAA;
+ height: 2em;
+ width: 2em;
background-color: #444;
display: -webkit-box;
-webkit-box-pack: center;
-webkit-box-align: center;
- color: white;
+ color: #AAA;
+ border-radius: 50%;
+ -webkit-box-sizing: border-box;
}
.action:hover {
- border: 1px solid #FFF;
+ border: 2px solid #FFF;
+ color: white;
}
.action:active {
@@ -437,3 +468,63 @@
stroke: black;
}
+.flowCount {
+ font-size: 20px;
+ fill: rgba(255, 255, 255, .75);
+}
+
+#confirm {
+ display: none;
+ -webkit-transition: opacity .25s;
+ font-size: 20px;
+}
+
+#confirm-background {
+ background-color: black;
+ opacity: .5;
+}
+
+#confirm-prompt {
+ display: -webkit-box;
+ -webkit-box-pack: center;
+ -webkit-box-align: center;
+ -webkit-box-flex: 1.0;
+ text-align: center;
+}
+
+#confirm-buttons {
+ padding: 1em;
+}
+
+#confirm-prompt {
+ margin-top:1em;
+ line-height: 1.5em;
+}
+
+#confirm-panel {
+ position: relative;
+ background-color: #222;
+ border: 2px solid #aaa;
+ border-radius: 12px;
+ width: 20em;
+ padding: 1em;
+}
+
+.confirm-button {
+ padding: .5em;
+ border: 2px solid #aaa;
+ color: #aaa;
+ border-radius: 6px;
+ margin-left: .5em;
+ margin-right: .5em;
+}
+
+.confirm-button:hover{
+ border: 2px solid white;
+ color: white;
+}
+
+.confirm-button:active{
+ background-color: black;
+}
+
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index ca930ec..1b88b4a 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -20,11 +20,13 @@
</div>
<div id='onos'>
+ <div id='cluster-label'>ONOS Node Cluster</div>
<div id='controllers'></div>
<div id='actions'>
- <div id='action-all' class='action'>ALL</div>
- <div id='action-local' class='action'>LOCAL</div>
- <div id='action-scale' class='action'>SCALE</div>
+ <div id='action-all' class='action'>A</div>
+ <div id='action-local' class='action'>1</div>
+ <div id='action-scale' class='action'>S</div>
+ <div id='action-reset' class='action'>R</div>
</div>
</div>
@@ -43,28 +45,38 @@
<div id='selectedFlows'></div>
<div id='flowChooser'></div>
-
- <script src="d3/d3.v3.js" charset="utf-8"></script>
- <script src="d3/topojson.v0.min.js"></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>
-
- <!-- choose ring or map layout -->
- <!--script src="js/rings.js"></script-->
- <script src="js/map.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>
+<div id='confirm'>
+ <div id='confirm-background'></div>
+ <div id='confirm-panel'>
+ <div id='confirm-prompt'>A PROMPT</div>
+ <div id='confirm-buttons'>
+ <div id='confirm-ok' class='confirm-button'>OK</div>
+ <div id='confirm-cancel' class='confirm-button'>CANCEL</div>
+ </div>
+ </div>
+</div>
+
+<script src="d3/d3.v3.js" charset="utf-8"></script>
+<script src="d3/topojson.v0.min.js"></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>
+
+<!-- choose ring or map layout -->
+<!--script src="js/rings.js"></script-->
+<script src="js/map.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>
</body>
</html>
\ No newline at end of file
diff --git a/web/ons-demo/js/controllers.js b/web/ons-demo/js/controllers.js
index abe966b..501ac1d 100644
--- a/web/ons-demo/js/controllers.js
+++ b/web/ons-demo/js/controllers.js
@@ -26,16 +26,22 @@
controllers.on('dblclick', function (c) {
if (model.activeControllers.indexOf(c) != -1) {
var prompt = 'Dectivate ' + c + '?';
- if (confirm(prompt)) {
- controllerDown(c);
- setPending(d3.select(this));
- };
+ var that = this;
+ doConfirm(prompt, function (result) {
+ if (result) {
+ controllerDown(c);
+ setPending(d3.select(that));
+ };
+ })
} else {
var prompt = 'Activate ' + c + '?';
- if (confirm(prompt)) {
- controllerUp(c);
- setPending(d3.select(this));
- };
+ var that = this;
+ doConfirm(prompt, function (result) {
+ if (result) {
+ controllerUp(c);
+ setPending(d3.select(that));
+ };
+ });
}
});
diff --git a/web/ons-demo/js/debug.js b/web/ons-demo/js/debug.js
index 7b66912..3d3b302 100644
--- a/web/ons-demo/js/debug.js
+++ b/web/ons-demo/js/debug.js
@@ -9,4 +9,23 @@
}
});
return links;
+}
+
+function debug_findswitch(model, dpid) {
+ var sw;
+
+ model.edgeSwitches.forEach(function (s) {
+ if (s.dpid == dpid)
+ sw = s;
+ });
+ model.aggregationSwitches.forEach(function (s) {
+ if (s.dpid == dpid)
+ sw = s;
+ });
+ model.coreSwitches.forEach(function (s) {
+ if (s.dpid == dpid)
+ sw = s;
+ });
+
+ return sw;
}
\ No newline at end of file
diff --git a/web/ons-demo/js/flows.js b/web/ons-demo/js/flows.js
index fa0ccf2..bc30d11 100644
--- a/web/ons-demo/js/flows.js
+++ b/web/ons-demo/js/flows.js
@@ -142,16 +142,18 @@
row.on('dblclick', function () {
if (d) {
var prompt = 'Delete flow ' + d.flowId + '?';
- if (confirm(prompt)) {
- deleteFlow(d);
- d.deletePending = true;
- updateSelectedFlows();
-
- setTimeout(function () {
- d.deletePending = false;
+ doConfirm(prompt, function (result) {
+ if (result) {
+ deleteFlow(d);
+ d.deletePending = true;
updateSelectedFlows();
- }, pendingTimeout)
- };
+
+ setTimeout(function () {
+ d.deletePending = false;
+ updateSelectedFlows();
+ }, pendingTimeout)
+ };
+ });
}
});
diff --git a/web/ons-demo/js/init.js b/web/ons-demo/js/init.js
index dd11bf8..f7bd55b 100644
--- a/web/ons-demo/js/init.js
+++ b/web/ons-demo/js/init.js
@@ -9,21 +9,29 @@
d3.select('#action-all').on('click', function () {
var prompt = "Switch controllers to all?"
- if (confirm(prompt)) {
- switchAll();
- }
+ doConfirm(prompt, function (result) {
+ if (result) {
+ switchAll();
+ }
+ });
});
d3.select('#action-local').on('click', function () {
var prompt = "Switch controllers to local?"
- if (confirm(prompt)) {
- switchLocal();
- }
+ doConfirm(prompt, function (result) {
+ if (result) {
+ switchLocal();
+ }
+ });
});
d3.select('#action-scale').on('click', function () {
alert('scale')
});
+ d3.select('#action-reset').on('click', function () {
+ alert('reset')
+ });
+
createTopologyView(cb);
}
diff --git a/web/ons-demo/js/map.js b/web/ons-demo/js/map.js
index 7662aaf..ad590cb 100644
--- a/web/ons-demo/js/map.js
+++ b/web/ons-demo/js/map.js
@@ -120,6 +120,64 @@
});
}
+function drawCoreFlowCounts() {
+ var links = {};
+ model.links.forEach(function (l) {
+ links[makeLinkKey(l)] = l;
+ });
+
+ var flowCounts = [];
+ countCoreLinkFlows().forEach(function (count) {
+ var l = links[count.key];
+ if (l) {
+ var src = d3.select(document.getElementById(l['src-switch']));
+ var dst = d3.select(document.getElementById(l['dst-switch']));
+
+ if (!src.empty() && !dst.empty()) {
+ var x1 = parseFloat(src.attr('x'));
+ var x2 = parseFloat(dst.attr('x'));
+ var y1 = parseFloat(src.attr('y'));
+ var y2 = parseFloat(dst.attr('y'));
+
+ var slope = (y2 - y1)/(x2 - x1);
+
+ var offset = 15;
+ var xOffset = offset;
+ var yOffset = slope*offset;
+
+ var d = Math.sqrt(xOffset*xOffset + yOffset*yOffset);
+ var scaler = offset/d;
+
+ count.pt = {
+ x: x1 + (x2 - x1)/2 + xOffset*scaler,
+ y: y1 + (y2 - y1)/2 + yOffset*scaler
+ }
+ }
+ flowCounts.push(count);
+ }
+ });
+
+
+ var counts = linksLayer.selectAll('.flowCount').data(flowCounts, function (d) {
+ return d.key;
+ });
+
+ counts.enter().append('svg:text')
+ .attr('class', 'flowCount')
+ .attr('x', function (d) {
+ return d.pt.x;
+ })
+ .attr('y', function (d) {
+ return d.pt.y;
+ });
+
+ counts.text(function (d) {
+ return d.value;
+ });
+
+ counts.exit().remove();
+}
+
function drawLinkLines() {
// key on link dpids since these will come/go during demo
@@ -163,6 +221,7 @@
linkLines.exit().remove();
}
+
var fanOutAngles = {
aggregation: 100,
edge: 5
@@ -252,9 +311,13 @@
g.append('svg:text')
.classed('label', true)
.text(d.label)
- .attr("text-anchor", "end")
- .attr('x', d.x - width)
- .attr('y', d.y - width);
+ .attr("text-anchor", function (d) {
+ return d.x > 500 ? "end" : "start";
+ })
+ .attr('x', function (d) {
+ return d.x > 500 ? d.x - width*.8 : d.x + width*.8;
+ })
+ .attr('y', d.y - width*.8);
}
}
@@ -281,7 +344,7 @@
}
function switchesEnter(switches) {
- return switchLayer.selectAll('g').data(switches, function (d) {
+ switchLayer.selectAll('g').data(switches, function (d) {
return d.dpid;
})
.enter()
@@ -300,7 +363,9 @@
function switchesUpdate(switches) {
- switches.each(function (d) {
+ switchLayer.selectAll('g').data(switches, function (d) {
+ return d.dpid;
+ }).each(function (d) {
// if there's a pending state changed and then the state changes, clear the pending class
var circle = d3.select(this);
if (d.state === 'ACTIVE' && circle.classed('inactive') ||
@@ -329,10 +394,13 @@
var switches = coreSwitches.concat(aggregationSwitches).concat(edgeSwitches);
- switchesUpdate(switchesEnter(switches));
+ switchesEnter(switches)
+ switchesUpdate(switches);
drawLinkLines();
+ drawCoreFlowCounts();
+
labelsEnter(switches);
}
diff --git a/web/ons-demo/js/model.js b/web/ons-demo/js/model.js
index f251c87..df4a751 100644
--- a/web/ons-demo/js/model.js
+++ b/web/ons-demo/js/model.js
@@ -5,13 +5,20 @@
edgeSwitches: [],
aggregationSwitches: [],
coreSwitches: [],
- flows: results.flows,
+ flows: [],
controllers: results.controllers,
activeControllers: results.activeControllers,
links: results.links,
configuration: results.configuration
};
+ // remove bad flows;
+ results.flows.forEach(function (f) {
+ if (f.dataPath && f.dataPath.flowEntries && f.dataPath.flowEntries.length > 1) {
+ model.flows.push(f);
+ }
+ })
+
// sort the switches
results.switches.sort(function (a, b) {
var aA = a.dpid.split(':');
diff --git a/web/ons-demo/js/topologyactions.js b/web/ons-demo/js/topologyactions.js
index 6501e5b..28d418b 100644
--- a/web/ons-demo/js/topologyactions.js
+++ b/web/ons-demo/js/topologyactions.js
@@ -101,16 +101,20 @@
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);
- }
+ doConfirm(prompt, function(result) {
+ if (result) {
+ switchDown(data);
+ setPending(circle);
+ }
+ });
} else {
var prompt = 'Activate ' + data.dpid + '?';
- if (confirm(prompt)) {
- switchUp(data);
- setPending(circle);
- }
+ doConfirm(prompt, function (result) {
+ if (result) {
+ switchUp(data);
+ setPending(circle);
+ }
+ });
}
}
@@ -148,78 +152,87 @@
if (s1Data.className == 'edge' && s2Data.className == 'edge') {
var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
- if (confirm(prompt)) {
- addFlow(srcData, dstData);
+ doConfirm(prompt, function (result) {
+ if (result) {
+ addFlow(srcData, dstData);
- var flow = {
- dataPath: {
- srcPort: {
- dpid: {
- value: srcData.dpid
+ var flow = {
+ dataPath: {
+ srcPort: {
+ dpid: {
+ value: srcData.dpid
+ }
+ },
+ dstPort: {
+ dpid: {
+ value: dstData.dpid
+ }
}
},
- dstPort: {
- dpid: {
- value: dstData.dpid
- }
- }
- },
- srcDpid: srcData.dpid,
- dstDpid: dstData.dpid,
- createPending: true
- };
+ srcDpid: srcData.dpid,
+ dstDpid: dstData.dpid,
+ createPending: true
+ };
- selectFlow(flow);
+ selectFlow(flow);
- setTimeout(function () {
- deselectFlowIfCreatePending(flow);
- }, pendingTimeout);
- }
+ 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]);
- }
+ doConfirm(prompt, function (result) {
+ if (result) {
+ 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]);
- }
+ doConfirm(prompt, function (result) {
+ if (result) {
+ 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)];
-
+ doConfirm(prompt, function (result) {
+ if (result) {
+ 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();
- }, pendingTimeout);
- }
+
+ linkUp(link1);
+
+ // remove the pending links after 10s
+ setTimeout(function () {
+ delete pendingLinks[makeLinkKey(link1)];
+ delete pendingLinks[makeLinkKey(link2)];
+
+ updateTopology();
+ }, pendingTimeout);
+ }
+ });
}
}
}
diff --git a/web/ons-demo/js/utils.js b/web/ons-demo/js/utils.js
index be7cf53..265c661 100644
--- a/web/ons-demo/js/utils.js
+++ b/web/ons-demo/js/utils.js
@@ -81,24 +81,27 @@
function updateHeader() {
d3.select('#lastUpdate').text(new Date());
- var count = 0;
+ var activeSwitchCount = 0;
model.edgeSwitches.forEach(function (s) {
if (s.state === 'ACTIVE') {
- count += 1;
+ activeSwitchCount += 1;
}
});
model.aggregationSwitches.forEach(function (s) {
if (s.state === 'ACTIVE') {
- count += 1;
+ activeSwitchCount += 1;
}
});
model.coreSwitches.forEach(function (s) {
if (s.state === 'ACTIVE') {
- count += 1;
+ activeSwitchCount += 1;
}
});
- d3.select('#activeSwitches').text(count);
+ d3.select('#activeSwitches').text(activeSwitchCount);
+
+
+
d3.select('#activeFlows').text(model.flows.length);
}
@@ -153,4 +156,90 @@
return svg;
}
+/***************************************************************************************************
+counts the number of flows which pass through each core<->core link
+***************************************************************************************************/
+function countCoreLinkFlows() {
+ var allCounts = {};
+ model.flows.forEach(function (f) {
+ if (f.dataPath && f.dataPath.flowEntries && f.dataPath.flowEntries.length > 1) {
+ var flowEntries = f.dataPath.flowEntries;
+ var i;
+
+ for (i = 0; i < flowEntries.length - 1; i += 1) {
+ var linkKey = flowEntries[i].dpid.value + '=>' + flowEntries[i+1].dpid.value;
+ if (!allCounts[linkKey]) {
+ allCounts[linkKey] = 1;
+ } else {
+ allCounts[linkKey] += 1;
+ }
+ }
+ }
+ });
+
+ var coreCounts = {};
+ var i, j;
+ for (i = 0; i < model.coreSwitches.length - 1; i += 1) {
+ for (j = i + 1; j < model.coreSwitches.length; j += 1) {
+ var si = model.coreSwitches[i];
+ var sj = model.coreSwitches[j];
+ var key1 = si.dpid + '=>' + sj.dpid;
+ var key2 = sj.dpid + '=>' + si.dpid;
+ var linkCount = 0;
+ if (allCounts[key1]) {
+ linkCount += allCounts[key1];
+ }
+ if (allCounts[key2]) {
+ linkCount += allCounts[key2];
+ }
+
+ coreCounts[key1] = linkCount;
+ }
+ }
+
+ return d3.entries(coreCounts);
+}
+
+
+/***************************************************************************************************
+
+***************************************************************************************************/
+function doConfirm(prompt, cb) {
+ var confirm = d3.select('#confirm');
+ confirm.select('#confirm-prompt').text(prompt);
+
+ function show() {
+ confirm.style('display', '-webkit-box');
+ confirm.style('opacity', 0);
+ setTimeout(function () {
+ confirm.style('opacity', 1);
+ }, 0);
+ }
+
+ function dismiss() {
+ confirm.style('opacity', 0);
+ confirm.on('webkitTransitionEnd', function () {
+ confirm.style('display', 'none');
+ confirm.on('webkitTransitionEnd', null);
+ });
+ }
+
+ confirm.select('#confirm-ok').on('click', function () {
+ d3.select(this).on('click', null);
+ dismiss();
+ cb(true);
+ });
+
+ confirm.select('#confirm-cancel').on('click', function () {
+ d3.select(this).on('click', null);
+ dismiss();
+ cb(false);
+ });
+
+ show();
+}
+
+
+
+