GUI -- Link selection showing link details implemented.
- note: basic link data shown for now. will need enhancing.
Change-Id: I067edec6f336b5ea5c83c610622346d5fcedce38
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css
index 9c0ebe0..870d4f8 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -432,9 +432,15 @@
opacity: .9;
}
+#ov-topo svg .link.selected,
+#ov-topo svg .link.enhanced {
+ stroke-width: 4.5px;
+}
+.light #ov-topo svg .link.selected,
.light #ov-topo svg .link.enhanced {
filter: url(#blue-glow);
}
+.dark #ov-topo svg .link.selected,
.dark #ov-topo svg .link.enhanced {
filter: url(#yellow-glow);
}
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 82f1173..50e465d 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -29,7 +29,7 @@
// references to injected services etc.
var $log, fs, ks, zs, gs, ms, sus, flash, wss,
- tes, tfs, tps, tis, tss, tts, tos, ttbs;
+ tes, tfs, tps, tis, tss, tls, tts, tos, ttbs;
// DOM elements
var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer;
@@ -45,7 +45,7 @@
actionMap = {
I: [toggleInstances, 'Toggle ONOS instances pane'],
O: [tps.toggleSummary, 'Toggle ONOS summary pane'],
- D: [tss.toggleDetails, 'Disable / enable details pane'],
+ D: [tps.toggleDetails, 'Disable / enable details pane'],
H: [tfs.toggleHosts, 'Toggle host visibility'],
M: [tfs.toggleOffline, 'Toggle offline visibility'],
@@ -117,9 +117,13 @@
// if an instance is selected, cancel the affinity mapping
tis.cancelAffinity()
- } else if (tss.haveDetails()) {
+ } else if (tss.deselectAll()) {
// else if we have node selections, deselect them all
- tss.deselectAll();
+ // (work already done)
+
+ } else if (tls.deselectLink()) {
+ // else if we have a link selected, deselect it
+ // (work already done)
} else if (tis.isVisible()) {
// else if the Instance Panel is visible, hide it
@@ -238,12 +242,12 @@
'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
'WebSocketService',
'TopoEventService', 'TopoForceService', 'TopoPanelService',
- 'TopoInstService', 'TopoSelectService', 'TopoTrafficService',
- 'TopoObliqueService', 'TopoToolbarService',
+ 'TopoInstService', 'TopoSelectService', 'TopoLinkService',
+ 'TopoTrafficService', 'TopoObliqueService', 'TopoToolbarService',
- function ($scope, _$log_, $loc, $timeout, _fs_, mast,
- _ks_, _zs_, _gs_, _ms_, _sus_, _flash_, _wss_,
- _tes_, _tfs_, _tps_, _tis_, _tss_, _tts_, _tos_, _ttbs_) {
+ function ($scope, _$log_, $loc, $timeout, _fs_, mast, _ks_, _zs_,
+ _gs_, _ms_, _sus_, _flash_, _wss_, _tes_, _tfs_, _tps_,
+ _tis_, _tss_, _tls_, _tts_, _tos_, _ttbs_) {
var self = this,
projection,
dim,
@@ -273,6 +277,7 @@
tps = _tps_;
tis = _tis_;
tss = _tss_;
+ tls = _tls_;
tts = _tts_;
tos = _tos_;
ttbs = _ttbs_;
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 4a40782..11f4934 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -615,6 +615,7 @@
d.fixed = true;
d3.select(this).classed('fixed', true);
sendUpdateMeta(d);
+ tss.clickConsumed(true);
}
// predicate that indicates when dragging is active
@@ -692,7 +693,8 @@
return {
node: function () { return node; },
zoomingOrPanning: zoomingOrPanning,
- updateDeviceColors: td3.updateDeviceColors
+ updateDeviceColors: td3.updateDeviceColors,
+ deselectLink: tls.deselectLink
};
}
diff --git a/web/gui/src/main/webapp/app/view/topo/topoLink.js b/web/gui/src/main/webapp/app/view/topo/topoLink.js
index 3686bc5..8c8fd82 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoLink.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoLink.js
@@ -23,34 +23,35 @@
'use strict';
// injected refs
- var $log, fs, sus, ts, flash;
+ var $log, fs, sus, ts, flash, tss, tps;
+ // internal state
var api,
td3,
network,
- enhancedLink = null; // the link which the mouse is hovering over
+ showPorts = true, // enable port highlighting by default
+ enhancedLink = null, // the link over which the mouse is hovering
+ selectedLink = null; // the link which is currently selected
// SVG elements;
var svg;
- // internal state
- var showPorts = true; // enable port highlighting by default
-
// ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ========
- function mouseMoveHandler() {
- var m = d3.mouse(this),
+ function getLogicalMousePosition(container) {
+ var m = d3.mouse(container),
sc = api.zoomer.scale(),
tr = api.zoomer.translate(),
mx = (m[0] - tr[0]) / sc,
my = (m[1] - tr[1]) / sc;
- computeNearestLink({x: mx, y: my});
+ return {x: mx, y: my};
}
function computeNearestLink(mouse) {
var proximity = 30 / api.zoomer.scale(),
- nearest, minDist;
+ nearest = null,
+ minDist;
function sq(x) { return x * x; }
@@ -91,7 +92,6 @@
}
if (network.links.length) {
- nearest = null;
minDist = proximity * 2;
network.links.forEach(function (d) {
@@ -112,13 +112,11 @@
}
}
});
-
- enhanceNearestLink(nearest);
}
+ return nearest;
}
-
- function enhanceNearestLink(ldata) {
+ function enhanceLink(ldata) {
// if the new link is same as old link, do nothing
if (enhancedLink && ldata && enhancedLink.key === ldata.key) return;
@@ -148,7 +146,6 @@
if (!d.el) return;
d.el.classed('enhanced', true);
- $log.debug('[' + (d.srcPort || 'H') + '] ---> [' + d.tgtPort + ']', d.key);
// Define port label data objects.
// NOTE: src port is absent in the case of host-links.
@@ -188,6 +185,62 @@
return {x: k * dx + ln.x, y: k * dy + ln.y};
}
+
+ function selectLink(ldata) {
+ // if the new link is same as old link, do nothing
+ if (selectedLink && ldata && selectedLink.key === ldata.key) return;
+
+ // make sure no nodes are selected
+ tss.deselectAll();
+
+ // first, unenhance the currently enhanced link
+ if (selectedLink) {
+ unselLink(selectedLink);
+ }
+ selectedLink = ldata;
+ if (selectedLink) {
+ selLink(selectedLink);
+ }
+ }
+
+ function unselLink(d) {
+ // guard against link element not set
+ if (d.el) {
+ d.el.classed('selected', false);
+ }
+ }
+
+ function selLink(d) {
+ // guard against link element not set
+ if (!d.el) return;
+
+ d.el.classed('selected', true);
+
+ tps.displayLink(d);
+ tps.displaySomething();
+ }
+
+ // ====== MOUSE EVENT HANDLERS ======
+
+ function mouseMoveHandler() {
+ var mp = getLogicalMousePosition(this),
+ link = computeNearestLink(mp);
+ enhanceLink(link);
+ }
+
+ function mouseClickHandler() {
+ var mp, link;
+
+ if (!tss.clickConsumed()) {
+ mp = getLogicalMousePosition(this);
+ link = computeNearestLink(mp);
+ selectLink(link);
+ }
+ }
+
+
+ // ======================
+
function togglePorts() {
showPorts = !showPorts;
@@ -195,25 +248,37 @@
handler = showPorts ? mouseMoveHandler : null;
if (!showPorts) {
- enhanceNearestLink(null);
+ enhanceLink(null);
}
svg.on('mousemove', handler);
flash.flash(what + ' port highlighting');
}
+ function deselectLink() {
+ if (selectedLink) {
+ unselLink(selectedLink);
+ selectedLink = null;
+ return true;
+ }
+ return false;
+ }
+
// ==========================
// Module definition
angular.module('ovTopo')
.factory('TopoLinkService',
['$log', 'FnService', 'SvgUtilService', 'ThemeService', 'FlashService',
+ 'TopoSelectService', 'TopoPanelService',
- function (_$log_, _fs_, _sus_, _ts_, _flash_) {
+ function (_$log_, _fs_, _sus_, _ts_, _flash_, _tss_, _tps_) {
$log = _$log_;
fs = _fs_;
sus = _sus_;
ts = _ts_;
flash = _flash_;
+ tss = _tss_;
+ tps = _tps_;
function initLink(_api_, _td3_) {
api = _api_;
@@ -223,17 +288,20 @@
if (showPorts) {
svg.on('mousemove', mouseMoveHandler);
}
+ svg.on('click', mouseClickHandler);
}
function destroyLink() {
- // unconditionally remove any mousemove event handler
+ // unconditionally remove any event handlers
svg.on('mousemove', null);
+ svg.on('click', null);
}
return {
initLink: initLink,
destroyLink: destroyLink,
- togglePorts: togglePorts
+ togglePorts: togglePorts,
+ deselectLink: deselectLink
};
}]);
}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoPanel.js b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
index 485bfc8..e5a4eef 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
@@ -23,7 +23,7 @@
'use strict';
// injected refs
- var $log, fs, ps, gs, wss;
+ var $log, fs, ps, gs, flash, wss;
// constants
var pCls = 'topo-p',
@@ -37,6 +37,9 @@
var summaryPanel,
detailPanel;
+ // internal state
+ var useDetails = true, // should we show details if we have 'em?
+ haveDetails = false; // do we have details that we could show?
// === -----------------------------------------------------
// Utility functions
@@ -129,6 +132,42 @@
.on('click', cb);
}
+ function displayLink(data) {
+ detailPanel.empty();
+
+ var svg = dpa('svg'),
+ title = dpa('h2'),
+ table = dpa('table'),
+ tbody = table.append('tbody');
+
+ gs.addGlyph(svg, 'ports', 40);
+ title.text('Link');
+ listProps(tbody, {
+ propOrder: [
+ 'type', '-', 'src', 'srcPort', '-', 'tgt', 'tgtPort'
+ ],
+ props: {
+ type: data.type(),
+ src: data.source.id,
+ srcPort: data.srcPort,
+ tgt: data.target.id,
+ tgtPort: data.tgtPort
+ }
+ });
+ }
+
+ function displayNothing() {
+ haveDetails = false;
+ hideDetailPanel();
+ }
+
+ function displaySomething() {
+ haveDetails = true;
+ if (useDetails) {
+ showDetailPanel();
+ }
+ }
+
// === -----------------------------------------------------
// Event Handlers
@@ -201,6 +240,19 @@
dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
}
+ function toggleDetails() {
+ useDetails = !useDetails;
+ if (useDetails) {
+ flash.flash('Enable details panel');
+ if (haveDetails) {
+ showDetailPanel();
+ }
+ } else {
+ flash.flash('Disable details panel');
+ hideDetailPanel();
+ }
+ }
+
// ==========================
function initPanels() {
@@ -223,13 +275,15 @@
angular.module('ovTopo')
.factory('TopoPanelService',
- ['$log', 'FnService', 'PanelService', 'GlyphService', 'WebSocketService',
+ ['$log', 'FnService', 'PanelService', 'GlyphService',
+ 'FlashService', 'WebSocketService',
- function (_$log_, _fs_, _ps_, _gs_, _wss_) {
+ function (_$log_, _fs_, _ps_, _gs_, _flash_, _wss_) {
$log = _$log_;
fs = _fs_;
ps = _ps_;
gs = _gs_;
+ flash = _flash_;
wss = _wss_;
return {
@@ -239,13 +293,15 @@
showSummary: showSummary,
toggleSummary: toggleSummary,
+ toggleDetails: toggleDetails,
displaySingle: displaySingle,
displayMulti: displayMulti,
addAction: addAction,
+ displayLink: displayLink,
+ displayNothing: displayNothing,
+ displaySomething: displaySomething,
hideSummaryPanel: hideSummaryPanel,
- showDetailPanel: showDetailPanel,
- hideDetailPanel: hideDetailPanel,
detailVisible: function () { return detailPanel.isVisible(); },
summaryVisible: function () { return summaryPanel.isVisible(); }
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
index af0e041..5d456aa 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -23,7 +23,7 @@
'use strict';
// injected refs
- var $log, fs, flash, wss, tps, tts;
+ var $log, fs, wss, tps, tts;
// api to topoForce
var api;
@@ -31,14 +31,14 @@
node() // get ref to D3 selection of nodes
zoomingOrPanning( ev )
updateDeviceColors( [dev] )
+ deselectLink()
*/
// internal state
var hovered, // the node over which the mouse is hovering
selections = {}, // currently selected nodes (by id)
selectOrder = [], // the order in which we made selections
- haveDetails = false, // do we have details of one or more nodes?
- useDetails = true; // should we show details if we have 'em?
+ consumeClick = false; // used to coordinate with SVG click handler
// ==========================
@@ -101,6 +101,9 @@
}
if (!n) return;
+ consumeClick = true;
+ api.deselectLink();
+
if (ev.shiftKey && n.classed('selected')) {
deselectObject(obj.id);
updateDetail();
@@ -130,12 +133,17 @@
}
function deselectAll() {
+ var something = (selectOrder.length > 0);
+
// deselect all nodes in the network...
api.node().classed('selected', false);
selections = {};
selectOrder = [];
api.updateDeviceColors();
updateDetail();
+
+ // return true if something was selected
+ return something;
}
// === -----------------------------------------------------
@@ -162,9 +170,8 @@
}
function emptySelect() {
- haveDetails = false;
- tps.hideDetailPanel();
tts.cancelTraffic();
+ tps.displayNothing();
}
function singleSelect() {
@@ -175,8 +182,6 @@
}
function multiSelect() {
- haveDetails = true;
-
// display the selected nodes in the detail panel
tps.displayMulti(selectOrder);
@@ -192,6 +197,7 @@
tts.cancelTraffic();
tts.requestTrafficForMode();
+ tps.displaySomething();
}
@@ -199,8 +205,6 @@
// Event Handlers
function showDetails(data) {
- haveDetails = true;
-
// display the data for the single selected node
tps.displaySingle(data);
@@ -212,23 +216,7 @@
tps.addAction('Show Device Flows', tts.showDeviceLinkFlowsAction);
}
- // only show the details panel if the user hasn't "hidden" it
- if (useDetails) {
- tps.showDetailPanel();
- }
- }
-
- function toggleDetails() {
- useDetails = !useDetails;
- if (useDetails) {
- flash.flash('Enable details panel');
- if (haveDetails) {
- tps.showDetailPanel();
- }
- } else {
- flash.flash('Disable details panel');
- tps.hideDetailPanel();
- }
+ tps.displaySomething();
}
function validateSelectionContext() {
@@ -239,18 +227,23 @@
return true;
}
+ function clickConsumed(x) {
+ var cc = consumeClick;
+ consumeClick = !!x;
+ return cc;
+ }
+
// === -----------------------------------------------------
// === MODULE DEFINITION ===
angular.module('ovTopo')
.factory('TopoSelectService',
- ['$log', 'FnService', 'FlashService', 'WebSocketService',
+ ['$log', 'FnService', 'WebSocketService',
'TopoPanelService', 'TopoTrafficService',
- function (_$log_, _fs_, _flash_, _wss_, _tps_, _tts_) {
+ function (_$log_, _fs_, _wss_, _tps_, _tts_) {
$log = _$log_;
fs = _fs_;
- flash = _flash_;
wss = _wss_;
tps = _tps_;
tts = _tts_;
@@ -266,7 +259,6 @@
destroySelect: destroySelect,
showDetails: showDetails,
- toggleDetails: toggleDetails,
nodeMouseOver: nodeMouseOver,
nodeMouseOut: nodeMouseOut,
@@ -275,9 +267,10 @@
deselectAll: deselectAll,
hovered: function () { return hovered; },
- haveDetails: function () { return haveDetails; },
selectOrder: function () { return selectOrder; },
- validateSelectionContext: validateSelectionContext
+ validateSelectionContext: validateSelectionContext,
+
+ clickConsumed: clickConsumed
};
}]);
}());