Simon Hunt

GUI -- Reworked selection logic to populate fly-in detail pane with action items.

Change-Id: I1a7ba3e04d608b7ec7bc635c21658d2d55b2d5c9
...@@ -146,6 +146,26 @@ ...@@ -146,6 +146,26 @@
146 } 146 }
147 147
148 148
149 +#topo-detail .actionBtn {
150 + margin: 6px 12px;
151 + padding: 2px 6px;
152 + font-size: 9pt;
153 + cursor: pointer;
154 + width: 50%;
155 + text-align: center;
156 +
157 + /* theme specific... */
158 + border: 1px solid #ddf;
159 + color: #99f;
160 +}
161 +
162 +#topo-detail .actionBtn:hover {
163 + /* theme specific... */
164 + border: 1px solid #ddf;
165 + background: #eef;
166 + color: #77d;
167 +}
168 +
149 169
150 #topo-detail hr { 170 #topo-detail hr {
151 height: 1px; 171 height: 1px;
......
...@@ -129,11 +129,7 @@ ...@@ -129,11 +129,7 @@
129 P: togglePorts, 129 P: togglePorts,
130 U: unpin, 130 U: unpin,
131 R: resetZoomPan, 131 R: resetZoomPan,
132 - esc: deselectAll, 132 + esc: deselectAll
133 -
134 - W: requestTraffic, // bag of selections
135 - X: cancelTraffic,
136 - Z: requestPath // host-to-host intent (and monitor)
137 }; 133 };
138 134
139 // state variables 135 // state variables
...@@ -355,14 +351,17 @@ ...@@ -355,14 +351,17 @@
355 } 351 }
356 352
357 var eventDispatch = { 353 var eventDispatch = {
354 + addInstance: stillToImplement,
358 addDevice: addDevice, 355 addDevice: addDevice,
359 addLink: addLink, 356 addLink: addLink,
360 addHost: addHost, 357 addHost: addHost,
361 358
359 + updateInstance: stillToImplement,
362 updateDevice: updateDevice, 360 updateDevice: updateDevice,
363 updateLink: updateLink, 361 updateLink: updateLink,
364 updateHost: updateHost, 362 updateHost: updateHost,
365 363
364 + removeInstance: stillToImplement,
366 removeDevice: stillToImplement, 365 removeDevice: stillToImplement,
367 removeLink: removeLink, 366 removeLink: removeLink,
368 removeHost: removeHost, 367 removeHost: removeHost,
...@@ -482,11 +481,11 @@ ...@@ -482,11 +481,11 @@
482 function showDetails(data) { 481 function showDetails(data) {
483 fnTrace('showDetails', data.payload.id); 482 fnTrace('showDetails', data.payload.id);
484 populateDetails(data.payload); 483 populateDetails(data.payload);
485 - // TODO: Add single-select actions ...
486 detailPane.show(); 484 detailPane.show();
487 } 485 }
488 486
489 function showPath(data) { 487 function showPath(data) {
488 + // TODO: review - making sure we are handling the payload correctly.
490 fnTrace('showPath', data.payload.id); 489 fnTrace('showPath', data.payload.id);
491 var links = data.payload.links, 490 var links = data.payload.links,
492 s = [ data.event + "\n" + links.length ]; 491 s = [ data.event + "\n" + links.length ];
...@@ -501,11 +500,11 @@ ...@@ -501,11 +500,11 @@
501 link.el.classed('showPath', true); 500 link.el.classed('showPath', true);
502 } 501 }
503 }); 502 });
504 -
505 - // TODO: add selection-highlite lines to links
506 } 503 }
507 504
508 function showTraffic(data) { 505 function showTraffic(data) {
506 + // TODO: review - making sure we are handling the payload correctly.
507 + // TODO: handle 'class' of link: primary, secondary, animated...
509 fnTrace('showTraffic', data.payload.id); 508 fnTrace('showTraffic', data.payload.id);
510 var paths = data.payload.paths; 509 var paths = data.payload.paths;
511 510
...@@ -524,7 +523,6 @@ ...@@ -524,7 +523,6 @@
524 } 523 }
525 }); 524 });
526 }); 525 });
527 - //network.view.alert("showTraffic() -- TODO")
528 } 526 }
529 527
530 // ............................... 528 // ...............................
...@@ -571,42 +569,8 @@ ...@@ -571,42 +569,8 @@
571 return true; 569 return true;
572 } 570 }
573 571
574 - function requestTraffic(hoverNode) {
575 - if (nSel() > 0 || hoverNode) {
576 - var nodes = hoverNode ? selectOrder.concat(hoverNode.id) : selectOrder;
577 - sendMessage('requestTraffic', {
578 - ids: nodes
579 - });
580 - } else {
581 - userFeedback('Request-Traffic requires one or\n' +
582 - 'more items to be selected.');
583 - }
584 - }
585 -
586 - function requestPath() {
587 - if (nSel() === 2 && allSelectionsClass('host')) {
588 - sendMessage('requestPath', {
589 - one: getSelId(0),
590 - two: getSelId(1)
591 - });
592 - } else {
593 - userFeedback('Request-Path requires two\n' +
594 - 'hosts to be selected.');
595 - }
596 - }
597 -
598 - function cancelTraffic(hoverNode) {
599 - if (hoverNode && selectOrder.length) {
600 - requestTraffic();
601 - } else {
602 - // FIXME: from where do we get the intent id(s) to send to the server?
603 - sendMessage('cancelTraffic', {
604 - ids: ["need_the_intent_id"]
605 - });
606 - }
607 - }
608 -
609 // request details for the selected element 572 // request details for the selected element
573 + // invoked from selection of a single node.
610 function requestDetails() { 574 function requestDetails() {
611 var data = getSel(0).obj, 575 var data = getSel(0).obj,
612 payload = { 576 payload = {
...@@ -616,6 +580,31 @@ ...@@ -616,6 +580,31 @@
616 sendMessage('requestDetails', payload); 580 sendMessage('requestDetails', payload);
617 } 581 }
618 582
583 + function addIntentAction() {
584 + sendMessage('addHostIntent', {
585 + one: getSelId(0),
586 + two: getSelId(1)
587 + });
588 + }
589 +
590 + function showTrafficAction() {
591 + // if nothing is hovered over, and nothing selected, send cancel request
592 + if (!hovered && nSel() === 0) {
593 + sendMessage('cancelTraffic', {});
594 + return;
595 + }
596 +
597 + // NOTE: hover is only populated if "show traffic on hover" is
598 + // toggled on, and the item hovered is a host...
599 + var hoverId = (trafficHover() && hovered && hovered.class === 'host')
600 + ? hovered.id : '';
601 + sendMessage('requestTraffic', {
602 + ids: selectOrder,
603 + hover: hoverId
604 + });
605 + }
606 +
607 +
619 // ============================== 608 // ==============================
620 // force layout modification functions 609 // force layout modification functions
621 610
...@@ -942,18 +931,16 @@ ...@@ -942,18 +931,16 @@
942 } 931 }
943 932
944 function nodeMouseOver(d) { 933 function nodeMouseOver(d) {
945 - console.log("Hover:", d);
946 hovered = d; 934 hovered = d;
947 - if (d.class === 'host') { 935 + if (trafficHover() && d.class === 'host') {
948 - //requestTraffic(d); 936 + showTrafficAction();
949 } 937 }
950 } 938 }
951 939
952 function nodeMouseOut(d) { 940 function nodeMouseOut(d) {
953 - console.log("Unhover:", d);
954 hovered = null; 941 hovered = null;
955 - if (d.class === 'host') { 942 + if (trafficHover() && d.class === 'host') {
956 - //cancelTraffic(d); 943 + showTrafficAction();
957 } 944 }
958 } 945 }
959 946
...@@ -1293,6 +1280,7 @@ ...@@ -1293,6 +1280,7 @@
1293 var nSel = selectOrder.length; 1280 var nSel = selectOrder.length;
1294 if (!nSel) { 1281 if (!nSel) {
1295 detailPane.hide(); 1282 detailPane.hide();
1283 + showTrafficAction(); // sends cancelTraffic event
1296 } else if (nSel === 1) { 1284 } else if (nSel === 1) {
1297 singleSelect(); 1285 singleSelect();
1298 } else { 1286 } else {
...@@ -1307,7 +1295,6 @@ ...@@ -1307,7 +1295,6 @@
1307 1295
1308 function multiSelect() { 1296 function multiSelect() {
1309 populateMultiSelect(); 1297 populateMultiSelect();
1310 - // TODO: Add multi-select actions ...
1311 } 1298 }
1312 1299
1313 function addSep(tbody) { 1300 function addSep(tbody) {
...@@ -1339,6 +1326,8 @@ ...@@ -1339,6 +1326,8 @@
1339 selectOrder.forEach(function (d, i) { 1326 selectOrder.forEach(function (d, i) {
1340 addProp(tbody, i+1, d); 1327 addProp(tbody, i+1, d);
1341 }); 1328 });
1329 +
1330 + addMultiSelectActions();
1342 } 1331 }
1343 1332
1344 function populateDetails(data) { 1333 function populateDetails(data) {
...@@ -1358,8 +1347,34 @@ ...@@ -1358,8 +1347,34 @@
1358 addProp(tbody, p, data.props[p]); 1347 addProp(tbody, p, data.props[p]);
1359 } 1348 }
1360 }); 1349 });
1350 +
1351 + addSingleSelectActions();
1361 } 1352 }
1362 1353
1354 + function addSingleSelectActions() {
1355 + detailPane.append('hr');
1356 + // always want to allow 'show traffic'
1357 + addAction('Show Traffic', showTrafficAction);
1358 + }
1359 +
1360 + function addMultiSelectActions() {
1361 + detailPane.append('hr');
1362 + // always want to allow 'show traffic'
1363 + addAction('Show Traffic', showTrafficAction);
1364 + // if exactly two hosts are selected, also want 'add host intent'
1365 + if (nSel() === 2 && allSelectionsClass('host')) {
1366 + addAction('Add Host Intent', addIntentAction);
1367 + }
1368 + }
1369 +
1370 + function addAction(text, cb) {
1371 + detailPane.append('div')
1372 + .classed('actionBtn', true)
1373 + .text(text)
1374 + .on('click', cb);
1375 + }
1376 +
1377 +
1363 function zoomPan(scale, translate) { 1378 function zoomPan(scale, translate) {
1364 zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")"); 1379 zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
1365 // keep the map lines constant width while zooming 1380 // keep the map lines constant width while zooming
......