Simon Hunt

GUI -- Re-instated Packet / Optical Layer filters.

- Adding ONOS instance fly-in pane. Note: still WIP.
- refactored onos.ui.addFloatingPanel to allow TL vs. TR.
- added instance pane to topo view.
- implemented addInstance() event.
- refactored event tracing.
- added instances test scenario.

Change-Id: I58d9769afa8aee9079ec778496cbc47bef329608
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0003/4-of:0000ffffffffff03/1",
"type": "pktopt",
"linkWidth": 2,
"src": "of:0000ffffffff0003",
"srcPort": "4",
"dst": "of:0000ffffffffff03",
"dstPort": "1",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0003/9-of:0000ffffffff0007/2",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0003",
"srcPort": "9",
"dst": "of:0000ffffffff0007",
"dstPort": "2",
"props" : {
"BW": "120 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0008/2-of:0000ffffffff0003/1",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0008",
"srcPort": "2",
"dst": "of:0000ffffffff0003",
"dstPort": "1",
"props" : {
"BW": "70 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0008/4-of:0000ffffffff0007/1",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0008",
"srcPort": "4",
"dst": "of:0000ffffffff0007",
"dstPort": "1",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff08/4-of:0000ffffffffff03/1",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff08",
"srcPort": "4",
"dst": "of:0000ffffffffff03",
"dstPort": "1",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addHost",
"payload": {
"id": "0E:2A:69:30:13:aa/-1",
"ingress": "0E:2A:69:30:13:aa/-1/0-of:0000ffffffff0008/101",
"egress": "of:0000ffffffff0008/101-0E:2A:69:30:13:aa/-1/0",
"cp": {
"device": "of:0000ffffffff0008",
"port": 101
},
"labels": [
"12.13.14.15",
"0E:2A:69:30:13:aa"
],
"props": {}
}
}
{
"event": "addHost",
"payload": {
"id": "0E:2A:69:30:13:88/-1",
"ingress": "0E:2A:69:30:13:88/-1/0-of:0000ffffffff0007/101",
"egress": "of:0000ffffffff0007/101-0E:2A:69:30:13:86/-1/0",
"cp": {
"device": "of:0000ffffffff0007",
"port": 101
},
"labels": [
"4.5.7.6",
"0E:2A:69:30:13:88"
],
"props": {}
}
}
{
"event": "addHost",
"payload": {
"id": "0E:2A:69:30:13:86/-1",
"ingress": "0E:2A:69:30:13:86/-1/0-of:0000ffffffff0003/101",
"egress": "of:0000ffffffff0003/101-0E:2A:69:30:13:86/-1/0",
"cp": {
"device": "of:0000ffffffff0003",
"port": 101
},
"labels": [
"1.2.3.4",
"0E:2A:69:30:13:86"
],
"props": {}
}
}
{
"event": "addInstance",
"payload": {
"id": "local",
"online": true,
"labels": [
"local",
"127.0.0.1"
]
}
}
{
"event": "addInstance",
"payload": {
"id": "onos-2",
"online": true,
"labels": [
"onos-2",
"192.168.2.2"
]
}
}
{
"event": "addInstance",
"payload": {
"id": "onos-3",
"online": false,
"labels": [
"onos-3",
"192.168.3.3"
]
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0008",
"type": "switch",
"online": true,
"master": "onos-3",
"labels": [
"0000ffffffff0008",
"FF:FF:FF:FF:00:08",
"sw-8"
],
"metaUi": {
"x": 734,
"y": 477
}
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0003",
"type": "switch",
"online": true,
"master": "local",
"labels": [
"0000ffffffff0003",
"FF:FF:FF:FF:00:03",
"sw-3"
],
"metaUi": {
"x": 282,
"y": 503
}
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0007",
"type": "switch",
"online": true,
"master": "onos-2",
"labels": [
"0000ffffffff0007",
"FF:FF:FF:FF:00:07",
"sw-7"
],
"metaUi": {
"x": 530,
"y": 330
}
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffffff08",
"type": "roadm",
"online": true,
"master": "onos-3",
"labels": [
"0000ffffffffff08",
"FF:FF:FF:FF:FF:08",
"opt-8"
],
"metaUi": {
"x": 734,
"y": 577
}
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffffff03",
"type": "roadm",
"online": true,
"master": "local",
"labels": [
"0000ffffffffff03",
"FF:FF:FF:FF:FF:03",
"opt-3"
],
"metaUi": {
"x": 282,
"y": 603
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0008/4-of:0000ffffffffff08/1",
"type": "pktopt",
"linkWidth": 2,
"src": "of:0000ffffffff0008",
"srcPort": "4",
"dst": "of:0000ffffffffff08",
"dstPort": "1",
"props" : {
"BW": "90 Gb"
}
}
}
{
"comments": [
"Developing ONOS instance visualization"
],
"title": "ONOS Instance Scenario",
"params": {
"lastAuto": 17
},
"description": [
"Visualizing ONOS instances.",
"",
"Press 'S' to load initial events.",
"",
"Press spacebar to complete the scenario..."
]
}
......@@ -699,6 +699,16 @@
// ..........................................................
// UI API
var fpConfig = {
TR: {
side: 'right'
},
TL: {
side: 'left'
}
};
uiApi = {
addLib: function (libName, api) {
// TODO: validation of args
......@@ -713,6 +723,7 @@
*/
addFloatingPanel: function (id, position) {
var pos = position || 'TR',
cfg = fpConfig[pos],
el,
fp;
......@@ -723,22 +734,37 @@
el = $floatPanels.append('div')
.attr('id', id)
.attr('class', 'fpanel');
.attr('class', 'fpanel')
.style('opacity', 0);
// has to be called after el is set.
el.style(cfg.side, pxHide());
function pxShow() {
return '20px';
}
function pxHide() {
return (-20 - widthVal()) + 'px';
}
function widthVal() {
return el.style('width').replace(/px$/, '');
}
fp = {
id: id,
el: el,
pos: pos,
show: function () {
console.log('show pane: ' + id);
el.transition().duration(750)
.style('right', '20px')
.style(cfg.side, pxShow())
.style('opacity', 1);
},
hide: function () {
console.log('hide pane: ' + id);
el.transition().duration(750)
.style('right', '-320px')
.style(cfg.side, pxHide())
.style('opacity', 0);
},
empty: function () {
......@@ -746,6 +772,12 @@
},
append: function (what) {
return el.append(what);
},
width: function (w) {
if (w === undefined) {
return widthVal();
}
el.style('width', w);
}
};
fpanels[id] = fp;
......
......@@ -197,6 +197,39 @@
border: 0;
}
/* ONOS instance stuff */
#topo-oibox {
width: 80px;
}
#topo-oibox .onosInst {
margin: 6px 0;
padding: 3px;
width: 80%;
left: 10%;
height: 40px;
cursor: pointer;
/* theme-related */
color: #444;
background-color: #ccc;
border: 2px dashed #aaa;
}
#topo-oibox .onosInst.online {
/* theme-related */
color: #113;
background-color: #bbf;
border: 2px solid #555;
}
#topo svg .suppressed,
#topo-oibox .suppressed {
opacity: 0.2;
}
/* Web Socket Closed Mask (starts hidden) */
#topo-mask {
......
......@@ -155,11 +155,14 @@
sid = 0,
deviceLabelIndex = 0,
hostLabelIndex = 0,
detailPane,
selectOrder = [],
selections = {},
selectOrder = [],
hovered = null,
detailPane,
antTimer = null,
onosInstances = {},
onosOrder = [],
oiBox,
viewMode = 'showAll',
portLabelsOn = false;
......@@ -198,11 +201,19 @@
}
}
function evTrace(data) {
fnTrace(data.event, data.payload.id);
}
// ==============================
// Key Callbacks
function testMe(view) {
view.alert('test');
//view.alert('test');
detailPane.show();
setTimeout(detailPane.hide, 2000);
oiBox.show();
setTimeout(oiBox.hide, 2000);
}
function abortIfLive() {
......@@ -303,25 +314,61 @@
// ==============================
// Radio Button Callbacks
var layerLookup = {
host: {
endstation: 'pkt', // default, if host event does not define type
bgpSpeaker: 'pkt'
},
device: {
switch: 'pkt',
roadm: 'opt'
},
link: {
hostLink: 'pkt',
direct: 'pkt',
optical: 'opt'
}
};
function inLayer(d, layer) {
var look = layerLookup[d.class],
lyr = look && look[d.type];
return lyr === layer;
}
function unsuppressLayer(which) {
node.each(function (d) {
var node = d.el;
if (inLayer(d, which)) {
node.classed('suppressed', false);
}
});
link.each(function (d) {
var link = d.el;
if (inLayer(d, which)) {
link.classed('suppressed', false);
}
});
}
function showAllLayers() {
// network.node.classed('inactive', false);
// network.link.classed('inactive', false);
node.classed('suppressed', false);
link.classed('suppressed', false);
// d3.selectAll('svg .port').classed('inactive', false);
// d3.selectAll('svg .portText').classed('inactive', false);
// TODO ...
network.view.alert('showAllLayers() callback');
}
function showPacketLayer() {
showAllLayers();
// TODO ...
network.view.alert('showPacketLayer() callback');
node.classed('suppressed', true);
link.classed('suppressed', true);
unsuppressLayer('pkt');
}
function showOpticalLayer() {
showAllLayers();
// TODO ...
network.view.alert('showOpticalLayer() callback');
node.classed('suppressed', true);
link.classed('suppressed', true);
unsuppressLayer('opt');
}
// ==============================
......@@ -351,7 +398,7 @@
}
var eventDispatch = {
addInstance: stillToImplement,
addInstance: addInstance,
addDevice: addDevice,
addLink: addLink,
addHost: addHost,
......@@ -371,8 +418,21 @@
showTraffic: showTraffic
};
function addInstance(data) {
evTrace(data);
var inst = data.payload,
id = inst.id;
if (onosInstances[id]) {
logicError('ONOS instance already added: ' + id);
return;
}
onosInstances[id] = inst;
onosOrder.push(inst);
updateInstances();
}
function addDevice(data) {
fnTrace('addDevice', data.payload.id);
evTrace(data);
var device = data.payload,
nodeData = createDeviceNode(device);
network.nodes.push(nodeData);
......@@ -382,7 +442,7 @@
}
function addLink(data) {
fnTrace('addLink', data.payload.id);
evTrace(data);
var link = data.payload,
lnk = createLink(link);
if (lnk) {
......@@ -394,7 +454,7 @@
}
function addHost(data) {
fnTrace('addHost', data.payload.id);
evTrace(data);
var host = data.payload,
node = createHostNode(host),
lnk;
......@@ -415,7 +475,7 @@
// TODO: fold updateX(...) methods into one base method; remove duplication
function updateDevice(data) {
fnTrace('updateDevice', data.payload.id);
evTrace(data);
var device = data.payload,
id = device.id,
nodeData = network.lookup[id];
......@@ -428,7 +488,7 @@
}
function updateLink(data) {
fnTrace('updateLink', data.payload.id);
evTrace(data);
var link = data.payload,
id = link.id,
linkData = network.lookup[id];
......@@ -441,7 +501,7 @@
}
function updateHost(data) {
fnTrace('updateHost', data.payload.id);
evTrace(data);
var host = data.payload,
id = host.id,
hostData = network.lookup[id];
......@@ -455,7 +515,7 @@
// TODO: fold removeX(...) methods into base method - remove dup code
function removeLink(data) {
fnTrace('removeLink', data.payload.id);
evTrace(data);
var link = data.payload,
id = link.id,
linkData = network.lookup[id];
......@@ -467,7 +527,7 @@
}
function removeHost(data) {
fnTrace('removeHost', data.payload.id);
evTrace(data);
var host = data.payload,
id = host.id,
hostData = network.lookup[id];
......@@ -479,14 +539,14 @@
}
function showDetails(data) {
fnTrace('showDetails', data.payload.id);
evTrace(data);
populateDetails(data.payload);
detailPane.show();
}
function showPath(data) {
// TODO: review - making sure we are handling the payload correctly.
fnTrace('showPath', data.payload.id);
evTrace(data);
var links = data.payload.links,
s = [ data.event + "\n" + links.length ];
links.forEach(function (d, i) {
......@@ -503,7 +563,7 @@
}
function showTraffic(data) {
fnTrace('showTraffic', data.payload.id);
evTrace(data);
var paths = data.payload.paths;
// Revert any links hilighted previously.
......@@ -602,6 +662,30 @@
// ==============================
// onos instance panel functions
function updateInstances() {
var onoses = oiBox.el.selectAll('.onosInst')
.data(onosOrder, function (d) { return d.id; });
// operate on existing onoses if necessary
var entering = onoses.enter()
.append('div')
.attr('class', 'onosInst')
.classed('online', function (d) { return d.online; })
.text(function (d) { return d.id; });
// operate on existing + new onoses here
// the departed...
var exiting = onoses.exit()
.transition()
.style('opacity', 0)
.remove();
}
// ==============================
// force layout modification functions
function translate(x, y) {
......@@ -760,11 +844,9 @@
// Augment as needed...
node.class = 'host';
if (!node.type) {
// TODO: perhaps type would be: {phone, tablet, laptop, endstation} ?
node.type = 'endstation';
}
node.svgClass = 'node host';
// TODO: consider placing near its switch, if [x,y] not defined
positionNode(node);
// cache label array length
......@@ -1352,21 +1434,21 @@
function addSingleSelectActions() {
detailPane.append('hr');
// always want to allow 'show traffic'
addAction('Show Traffic', showTrafficAction);
addAction(detailPane, 'Show Traffic', showTrafficAction);
}
function addMultiSelectActions() {
detailPane.append('hr');
// always want to allow 'show traffic'
addAction('Show Traffic', showTrafficAction);
addAction(detailPane, 'Show Traffic', showTrafficAction);
// if exactly two hosts are selected, also want 'add host intent'
if (nSel() === 2 && allSelectionsClass('host')) {
addAction('Add Host Intent', addIntentAction);
addAction(detailPane, 'Add Host Intent', addIntentAction);
}
}
function addAction(text, cb) {
detailPane.append('div')
function addAction(panel, text, cb) {
panel.append('div')
.classed('actionBtn', true)
.text(text)
.on('click', cb);
......@@ -1441,40 +1523,53 @@
// TODO: toggle button (and other widgets in the masthead) should be provided
// by the framework; not generated by the view.
var showTrafficOnHover,
doPanZoom;
var showInstances,
doPanZoom,
showTrafficOnHover;
function addButtonBar(view) {
var bb = d3.select('#mast')
.append('span').classed('right', true).attr('id', 'bb');
doPanZoom = bb.append('span')
.classed('btn', true)
.text('Pan/Zoom')
.on('click', togglePanZoom);
function mkTogBtn(text, cb) {
return bb.append('span')
.classed('btn', true)
.text(text)
.on('click', cb);
}
showTrafficOnHover = bb.append('span')
.classed('btn', true)
.text('Show traffic on hover')
.on('click', toggleTrafficHover);
showInstances = mkTogBtn('Show Instances', toggleInst);
doPanZoom = mkTogBtn('Pan/Zoom', togglePanZoom);
showTrafficOnHover = mkTogBtn('Show traffic on hover', toggleTrafficHover);
}
function toggleTrafficHover() {
showTrafficOnHover.classed('active', !trafficHover());
function instShown() {
return showInstances.classed('active');
}
function trafficHover() {
return showTrafficOnHover.classed('active');
function toggleInst() {
showInstances.classed('active', !instShown());
if (instShown()) {
oiBox.show();
} else {
oiBox.hide();
}
}
function panZoom() {
return doPanZoom.classed('active');
}
function togglePanZoom() {
doPanZoom.classed('active', !panZoom());
}
function panZoom() {
return doPanZoom.classed('active');
function trafficHover() {
return showTrafficOnHover.classed('active');
}
function toggleTrafficHover() {
showTrafficOnHover.classed('active', !trafficHover());
}
// ==============================
// View life-cycle callbacks
......@@ -1485,16 +1580,12 @@
fpad = fcfg.pad,
forceDim = [w - 2*fpad, h - 2*fpad];
// TODO: set trace api
//trace = onos.exported.webSockTrace;
// NOTE: view.$div is a D3 selection of the view's div
var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
svg = view.$div.append('svg').attr('viewBox', viewBox);
setSize(svg, view);
zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
setupZoomPan();
// add blue glow filter to svg layer
......@@ -1566,6 +1657,7 @@
selectCb, atDragEnd, panZoom);
// create mask layer for when we lose connection to server.
// TODO: this should be part of the framework
mask = view.$div.append('div').attr('id','topo-mask');
para(mask, 'Oops!');
para(mask, 'Web-socket connection to server closed...');
......@@ -1707,5 +1799,6 @@
});
detailPane = onos.ui.addFloatingPanel('topo-detail');
oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
}(ONOS));
......