ONOS-1479 -- GUI - augmenting topology view for extensibility: WIP.
Change-Id: I11820a9ff8f446c0d10a0311cee5ce448c15f402
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 d1d4b4c..f26f478 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -305,6 +305,21 @@
filter: url("data:image/svg+xml;utf8, <svg xmlns = \'http://www.w3.org/2000/svg\'><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" id=\"yellow-glow\"><feColorMatrix type=\"matrix\" values=\"0 0 0 0 1.0 0 0 0 0 1.0 0 0 0 0 0.3 0 0 0 1 0 \"></feColorMatrix><feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\"></feGaussianBlur><feMerge><feMergeNode in=\"coloredBlur\"></feMergeNode><feMergeNode in=\"SourceGraphic\"></feMergeNode></feMerge></filter></svg>#yellow-glow");
}
+
+/* --- Toolbar --- */
+
+#toolbar-topo-tbar .tbar-row.right {
+ width: 100%;
+}
+
+#toolbar-topo-tbar .tbar-row-text {
+ height: 21px;
+ text-align: right;
+ padding: 8px 60px 0 0;
+ font-style: italic;
+}
+
+
/* --- Topo Nodes --- */
#ov-topo svg .suppressed {
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 f46645e..313673f 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -40,7 +40,7 @@
// --- Short Cut Keys ------------------------------------------------
- function setUpKeys() {
+ function setUpKeys(overlayKeys) {
// key bindings need to be made after the services have been injected
// thus, deferred to here...
actionMap = {
@@ -63,14 +63,6 @@
R: [resetZoom, 'Reset pan / zoom'],
dot: [ttbs.toggleToolbar, 'Toggle Toolbar'],
- V: [tts.showRelatedIntentsAction, 'Show all related intents'],
- rightArrow: [tts.showNextIntentAction, 'Show next related intent'],
- leftArrow: [tts.showPrevIntentAction, 'Show previous related intent'],
- W: [tts.showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
- A: [tts.showAllFlowTrafficAction, 'Monitor all traffic using flow stats'],
- Q: [tts.showAllPortTrafficAction, 'Monitor all traffic using port stats'],
- F: [tts.showDeviceLinkFlowsAction, 'Show device link flows'],
-
E: [equalizeMasters, 'Equalize mastership roles'],
esc: handleEscape,
@@ -78,12 +70,16 @@
_keyListener: ttbs.keyListener,
_helpFormat: [
- ['I', 'O', 'D', '-', 'H', 'M', 'P', 'dash', 'B' ],
- ['X', 'Z', 'N', 'L', 'U', 'R', '-', 'dot'],
- ['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
+ ['I', 'O', 'D', 'H', 'M', 'P', 'dash', 'B', 'S' ],
+ ['X', 'Z', 'N', 'L', 'U', 'R', '-', 'E', '-', 'dot'],
+ [] // this column reserved for overlay actions
]
};
+ if (fs.isO(overlayKeys)) {
+ mergeKeys(overlayKeys);
+ }
+
ks.keyBindings(actionMap);
ks.gestureNotes([
@@ -95,6 +91,22 @@
]);
}
+ // when a topology overlay is activated, we need to bind their keystrokes
+ // and include them in the quick-help panel
+ function mergeKeys(extra) {
+ var _hf = actionMap._helpFormat[2];
+ extra._keyOrder.forEach(function (k) {
+ var d = extra[k],
+ cb = d && d.cb,
+ tt = d && d.tt;
+ // NOTE: ignore keys that are already defined
+ if (d && !actionMap[k]) {
+ actionMap[k] = [cb, tt];
+ _hf.push(k);
+ }
+ });
+ }
+
// --- Keystroke functions -------------------------------------------
function toggleInstances(x) {
@@ -153,6 +165,10 @@
// if an instance is selected, cancel the affinity mapping
tis.cancelAffinity()
+ } else if (tov.hooks.escape()) {
+ // else if the overlay consumed the ESC event...
+ // (work already done)
+
} else if (tss.deselectAll()) {
// else if we have node selections, deselect them all
// (work already done)
@@ -169,19 +185,15 @@
} else if (tps.summaryVisible()) {
// else if the Summary Panel is visible, hide it
tps.hideSummaryPanel();
-
- } else {
- // TODO: set hover mode to hoverModeNone
- // talk to Thomas about this: shouldn't it be done
- // when we deselect the node (if tss.haveDetails()...)
}
}
// --- Toolbar Functions ---------------------------------------------
function notValid(what) {
- $log.warn('Topo.js getActionEntry(): Not a valid ' + what);
+ $log.warn('topo.js getActionEntry(): Not a valid ' + what);
}
+
function getActionEntry(key) {
var entry;
@@ -201,7 +213,8 @@
function setUpToolbar() {
ttbs.init({
- getActionEntry: getActionEntry
+ getActionEntry: getActionEntry,
+ setUpKeys: setUpKeys
});
ttbs.createToolbar();
}
@@ -503,7 +516,6 @@
restoreConfigFromPrefs();
$log.debug('registered overlays...', tov.list());
-
$log.log('OvTopoCtrl has been created');
}]);
}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoEvent.js b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
index e6c943d..5fd38bf 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoEvent.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
@@ -27,14 +27,14 @@
'use strict';
// injected refs
- var $log, $interval, wss, tps, tis, tfs, tss, tts, tspr;
+ var $log, $interval, wss, tps, tis, tfs, tss, tov, tspr;
// internal state
var handlerMap,
openListener,
heartbeatTimer;
- var heartbeatPeriod = 5000; // 5 seconds
+ var heartbeatPeriod = 9000; // 9 seconds
// ==========================
@@ -44,7 +44,7 @@
showDetails: tss,
- showTraffic: tts,
+ showHighlights: tov,
addInstance: tis,
updateInstance: tis,
@@ -90,10 +90,10 @@
.factory('TopoEventService',
['$log', '$interval', 'WebSocketService',
'TopoPanelService', 'TopoInstService', 'TopoForceService',
- 'TopoSelectService', 'TopoTrafficService', 'TopoSpriteService',
+ 'TopoSelectService', 'TopoOverlayService', 'TopoSpriteService',
function (_$log_, _$interval_, _wss_,
- _tps_, _tis_, _tfs_, _tss_, _tts_, _tspr_) {
+ _tps_, _tis_, _tfs_, _tss_, _tov_, _tspr_) {
$log = _$log_;
$interval = _$interval_;
wss = _wss_;
@@ -101,7 +101,7 @@
tis = _tis_;
tfs = _tfs_;
tss = _tss_;
- tts = _tts_;
+ tov = _tov_;
tspr = _tspr_;
createHandlerMap();
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 963a370..0595393 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -23,7 +23,7 @@
'use strict';
// injected refs
- var $log, $timeout, fs, sus, is, ts, flash, wss,
+ var $log, $timeout, fs, sus, ts, flash, wss, tov,
tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg;
// configuration
@@ -797,9 +797,10 @@
return true;
}
- // ==========================
- // function entry points for traffic module
+ // =============================================
+ // function entry points for overlay module
+ // TODO: find an automatic way of tracking via the "showHighlights" events
var allTrafficClasses = 'primary secondary optical animated ' +
'port-traffic-Kbps port-traffic-Mbps port-traffic-Gbps ' +
'port-traffic-Gbps-choked';
@@ -845,7 +846,7 @@
};
}
- function mkD3Api(uplink) {
+ function mkD3Api() {
return {
node: function () { return node; },
link: function () { return link; },
@@ -859,7 +860,7 @@
};
}
- function mkSelectApi(uplink) {
+ function mkSelectApi() {
return {
node: function () { return node; },
zoomingOrPanning: zoomingOrPanning,
@@ -868,15 +869,20 @@
};
}
- function mkTrafficApi(uplink) {
+ function mkTrafficApi() {
+ return {
+ hovered: tss.hovered,
+ somethingSelected: tss.somethingSelected,
+ selectOrder: tss.selectOrder
+ };
+ }
+
+ function mkOverlayApi() {
return {
clearLinkTrafficStyle: clearLinkTrafficStyle,
removeLinkLabels: removeLinkLabels,
updateLinks: updateLinks,
- findLinkById: tms.findLinkById,
- hovered: tss.hovered,
- validateSelectionContext: tss.validateSelectionContext,
- selectOrder: tss.selectOrder
+ findLinkById: tms.findLinkById
};
}
@@ -904,7 +910,7 @@
};
}
- function mkFilterApi(uplink) {
+ function mkFilterApi() {
return {
node: function () { return node; },
link: function () { return link; }
@@ -925,11 +931,11 @@
.factory('TopoForceService',
['$log', '$timeout', 'FnService', 'SvgUtilService',
'ThemeService', 'FlashService', 'WebSocketService',
- 'TopoInstService', 'TopoModelService',
+ 'TopoOverlayService', 'TopoInstService', 'TopoModelService',
'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
- function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_,
+ function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_, _tov_,
_tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
$log = _$log_;
$timeout = _$timeout_;
@@ -938,6 +944,7 @@
ts = _ts_;
flash = _flash_;
wss = _wss_;
+ tov = _tov_;
tis = _tis_;
tms = _tms_;
td3 = _td3_;
@@ -966,12 +973,13 @@
$log.debug('initForce().. dim = ' + dim);
+ tov.setApi(mkOverlayApi(), tss);
tms.initModel(mkModelApi(uplink), dim);
- td3.initD3(mkD3Api(uplink));
- tss.initSelect(mkSelectApi(uplink));
- tts.initTraffic(mkTrafficApi(uplink));
+ td3.initD3(mkD3Api());
+ tss.initSelect(mkSelectApi());
+ tts.initTraffic(mkTrafficApi());
tos.initOblique(mkObliqueApi(uplink, fltr));
- fltr.initFilter(mkFilterApi(uplink));
+ fltr.initFilter(mkFilterApi());
tls.initLink(mkLinkApi(svg, uplink), td3);
settings = angular.extend({}, defaultSettings, opts);
@@ -1016,6 +1024,7 @@
tss.destroySelect();
td3.destroyD3();
tms.destroyModel();
+ // note: no need to destroy overlay service
ts.removeListener(themeListener);
themeListener = null;
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index f2b81f5..41c8e1e 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -30,7 +30,7 @@
var tos = 'TopoOverlayService: ';
// injected refs
- var $log, fs, gs, wss, ns;
+ var $log, fs, gs, wss, ns, tss, tps, api;
// internal state
var overlays = {},
@@ -80,6 +80,7 @@
function register(overlay) {
var r = 'register',
over = fs.isO(overlay),
+ kb = over ? fs.isO(overlay.keyBindings) : null,
id = over ? over.overlayId : '';
if (!id) {
@@ -90,11 +91,26 @@
}
overlays[id] = overlay;
handleGlyphs(overlay);
+
+ if (kb) {
+ if (!fs.isA(kb._keyOrder)) {
+ warn(r, 'no _keyOrder array defined on keyBindings');
+ } else {
+ kb._keyOrder.forEach(function (k) {
+ if (k !== '-' && !kb[k]) {
+ warn(r, 'no "' + k + '" property defined on keyBindings');
+ }
+ });
+ }
+ }
+
$log.debug(tos + 'registered overlay: ' + id, overlay);
}
+ // TODO: remove this redundant code.......
// NOTE: unregister needs to be called if an app is ever
// deactivated/uninstalled via the applications view
+/*
function unregister(overlay) {
var u = 'unregister',
over = fs.isO(overlay),
@@ -108,21 +124,33 @@
}
delete overlays[id];
$log.debug(tos + 'unregistered overlay: ' + id);
- // TODO: rebuild the toolbar overlay radio button set
}
+*/
+
+ // returns the list of overlay identifiers
function list() {
return d3.map(overlays).keys();
}
- function overlay(id) {
- return overlays[id];
+ // add a radio button for each registered overlay
+ function augmentRbset(rset, switchFn) {
+ angular.forEach(overlays, function (ov) {
+ rset.push({
+ gid: ov._glyphId,
+ tooltip: (ov.tooltip || '(no tooltip)'),
+ cb: function () {
+ tbSelection(ov.overlayId, switchFn);
+ }
+ });
+ });
}
// an overlay was selected via toolbar radio button press from user
- function tbSelection(id) {
+ function tbSelection(id, switchFn) {
var same = current && current.overlayId === id,
- payload = {};
+ payload = {},
+ actions;
function doop(op) {
var oid = current.overlayId;
@@ -133,70 +161,211 @@
if (!same) {
current && doop('deactivate');
- current = overlay(id);
+ current = overlays[id];
current && doop('activate');
+ actions = current && fs.isO(current.keyBindings);
+ switchFn(id, actions);
+
wss.sendEvent('topoSelectOverlay', payload);
- // TODO: refactor to emit "flush on overlay change" messages
+ // Ensure summary and details panels are updated immediately..
wss.sendEvent('requestSummary');
+ tss.updateDetail();
}
}
- var coreButtonPath = {
- showDeviceView: 'device',
- showFlowView: 'flow',
- showPortView: 'port',
- showGroupView: 'group'
+ var coreButtons = {
+ showDeviceView: {
+ gid: 'switch',
+ tt: 'Show Device View',
+ path: 'device'
+ },
+ showFlowView: {
+ gid: 'flowTable',
+ tt: 'Show Flow View for this Device',
+ path: 'flow'
+ },
+ showPortView: {
+ gid: 'portTable',
+ tt: 'Show Port View for this Device',
+ path: 'port'
+ },
+ showGroupView: {
+ gid: 'groupTable',
+ tt: 'Show Group View for this Device',
+ path: 'group'
+ }
};
+ // retrieves a button definition from the current overlay and generates
+ // a button descriptor to be added to the panel, with the data baked in
+ function _getButtonDef(id, data) {
+ var btns = current && current.buttons,
+ b = btns && btns[id],
+ cb = fs.isF(b.cb),
+ f = cb ? function () { cb(data); } : function () {};
+
+ return b ? {
+ id: current.mkId(id),
+ gid: current.mkGid(b.gid),
+ tt: b.tt,
+ cb: f
+ } : null;
+ }
+
// install core buttons, and include any additional from the current overlay
- function installButtons(buttons, addFn, data, devId) {
+ function installButtons(buttons, data, devId) {
+ buttons.forEach(function (id) {
+ var btn = coreButtons[id],
+ gid = btn && btn.gid,
+ tt = btn && btn.tt,
+ path = btn && btn.path;
- angular.forEach(buttons, function (btn) {
- var path = coreButtonPath[btn.id],
- _id,
- _gid,
- _cb,
- action;
-
- if (path) {
- // core callback function
- _id = btn.id;
- _gid = btn.gid;
- action = function () {
- ns.navTo(path, { devId: devId });
- };
- } else if (current) {
- _id = current.mkId(btn.id);
- _gid = current.mkGid(btn.gid);
- action = current.buttonActions[btn.id] || function () {};
+ if (btn) {
+ tps.addAction({
+ id: 'core-' + id,
+ gid: gid,
+ tt: tt,
+ cb: function () { ns.navTo(path, {devId: devId }); }
+ });
+ } else if (btn = _getButtonDef(id, data)) {
+ tps.addAction(btn);
}
+ });
+ }
- _cb = function () { action(data); };
+ function addDetailButton(id) {
+ var b = _getButtonDef(id);
+ if (b) {
+ tps.addAction({
+ id: current.mkId(id),
+ gid: current.mkGid(b.gid),
+ cb: b.cb,
+ tt: b.tt
+ });
+ }
+ }
- addFn({ id: _id, gid: _gid, cb: _cb, tt: btn.tt});
+
+ // === -----------------------------------------------------
+ // Hooks for overlays
+
+ function _hook(x) {
+ var h = current && current.hooks;
+ return h && fs.isF(h[x]);
+ }
+
+ function escapeHook() {
+ var eh = _hook('escape');
+ return eh ? eh() : false;
+ }
+
+ function emptySelectHook() {
+ var cb = _hook('empty');
+ cb && cb();
+ }
+
+ function singleSelectHook(data) {
+ var cb = _hook('single');
+ cb && cb(data);
+ }
+
+ function multiSelectHook(selectOrder) {
+ var cb = _hook('multi');
+ cb && cb(selectOrder);
+ }
+
+ // === -----------------------------------------------------
+ // Event (from server) Handlers
+
+ function setApi(_api_, _tss_) {
+ api = _api_;
+ tss = _tss_;
+ }
+
+ // TODO: refactor this (currently using showTraffic data structure)
+ function showHighlights(data) {
+ /*
+ API to topoForce
+ clearLinkTrafficStyle()
+ removeLinkLabels()
+ updateLinks()
+ findLinkById( id )
+ */
+
+ var paths = data.paths;
+
+ api.clearLinkTrafficStyle();
+ api.removeLinkLabels();
+
+ // Now highlight all links in the paths payload, and attach
+ // labels to them, if they are defined.
+ paths.forEach(function (p) {
+ var n = p.links.length,
+ i, ldata, lab, units, magnitude, portcls;
+
+ for (i=0; i<n; i++) {
+ ldata = api.findLinkById(p.links[i]);
+ lab = p.labels[i];
+
+ if (ldata && !ldata.el.empty()) {
+ ldata.el.classed(p.class, true);
+ ldata.label = lab;
+
+ if (fs.endsWith(lab, 'bps')) {
+ // inject additional styling for port-based traffic
+ units = lab.substring(lab.length-4);
+ portcls = 'port-traffic-' + units;
+
+ // for GBps
+ if (units.substring(0,1) === 'G') {
+ magnitude = fs.parseBitRate(lab);
+ if (magnitude >= 9) {
+ portcls += '-choked'
+ }
+ }
+ ldata.el.classed(portcls, true);
+ }
+ }
+ }
});
+ api.updateLinks();
}
+ // ========================================================================
+
angular.module('ovTopo')
.factory('TopoOverlayService',
['$log', 'FnService', 'GlyphService', 'WebSocketService', 'NavService',
+ 'TopoPanelService',
- function (_$log_, _fs_, _gs_, _wss_, _ns_) {
+ function (_$log_, _fs_, _gs_, _wss_, _ns_, _tps_) {
$log = _$log_;
fs = _fs_;
gs = _gs_;
wss = _wss_;
ns = _ns_;
+ tps = _tps_;
return {
register: register,
- unregister: unregister,
+ //unregister: unregister,
+ setApi: setApi,
list: list,
- overlay: overlay,
+ augmentRbset: augmentRbset,
+ mkGlyphId: mkGlyphId,
tbSelection: tbSelection,
- installButtons: installButtons
+ installButtons: installButtons,
+ addDetailButton: addDetailButton,
+ hooks: {
+ escape: escapeHook,
+ emptySelect: emptySelectHook,
+ singleSelect: singleSelectHook,
+ multiSelect: multiSelectHook
+ },
+
+ showHighlights: showHighlights
}
}]);
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 fe2f7a1..2e73ea2 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -40,12 +40,6 @@
selectOrder = [], // the order in which we made selections
consumeClick = false; // used to coordinate with SVG click handler
- // constants
- var devPath = 'device',
- flowPath = 'flow',
- portPath ='port',
- groupPath = 'group';
-
// ==========================
function nSel() {
@@ -157,8 +151,7 @@
// === -----------------------------------------------------
- function requestDetails() {
- var data = getSel(0).obj;
+ function requestDetails(data) {
wss.sendEvent('requestDetails', {
id: data.id,
class: data.class
@@ -179,91 +172,62 @@
}
function emptySelect() {
- tts.cancelTraffic();
+ tov.hooks.emptySelect();
tps.displayNothing();
}
function singleSelect() {
- // NOTE: detail is shown from 'showDetails' event callback
- requestDetails();
- tts.cancelTraffic();
- tts.requestTrafficForMode();
+ var data = getSel(0).obj;
+ requestDetails(data);
+ // NOTE: detail panel is shown as a response to receiving
+ // a 'showDetails' event from the server. See 'showDetails'
+ // callback function below...
}
function multiSelect() {
// display the selected nodes in the detail panel
tps.displayMulti(selectOrder);
-
- // always add the 'show traffic' action
- tps.addAction({
- id: '-mult-rel-traf-btn',
- gid: 'allTraffic',
- cb: tts.showRelatedIntentsAction,
- tt: 'Show Related Traffic'
- });
-
- // add other actions, based on what is selected...
- if (nSel() === 2 && allSelectionsClass('host')) {
- tps.addAction({
- id: 'host-flow-btn',
- gid: 'endstation',
- cb: tts.addHostIntentAction,
- tt: 'Create Host-to-Host Flow'
- });
- } else if (nSel() >= 2 && allSelectionsClass('host')) {
- tps.addAction({
- id: 'mult-src-flow-btn',
- gid: 'flows',
- cb: tts.addMultiSourceIntentAction,
- tt: 'Create Multi-Source Flow'
- });
- }
-
- tts.cancelTraffic();
- tts.requestTrafficForMode();
+ addHostSelectionActions();
+ tov.hooks.multiSelect(selectOrder);
tps.displaySomething();
}
+ function addHostSelectionActions() {
+ if (allSelectionsClass('host')) {
+ if (nSel() === 2) {
+ tps.addAction({
+ id: 'host-flow-btn',
+ gid: 'endstation',
+ cb: tts.addHostIntent,
+ tt: 'Create Host-to-Host Flow'
+ });
+ } else if (nSel() >= 2) {
+ tps.addAction({
+ id: 'mult-src-flow-btn',
+ gid: 'flows',
+ cb: tts.addMultiSourceIntent,
+ tt: 'Create Multi-Source Flow'
+ });
+ }
+ }
+ }
+
// === -----------------------------------------------------
// Event Handlers
+ // display the data for the single selected node
function showDetails(data) {
var buttons = fs.isA(data.buttons) || [];
-
- // display the data for the single selected node
tps.displaySingle(data);
-
- tov.installButtons(buttons, tps.addAction, data, data.props['URI']);
-
- // TODO: MOVE traffic buttons to the traffic overlay
- // always add the 'show traffic' action
- tps.addAction({
- id: '-sin-rel-traf-btn',
- gid: 'intentTraffic',
- cb: tts.showRelatedIntentsAction,
- tt: 'Show Related Traffic'
- });
-
- // add other actions, based on what is selected...
- if (data.type === 'switch') {
- tps.addAction({
- id: 'sin-dev-flows-btn',
- gid: 'flows',
- cb: tts.showDeviceLinkFlowsAction,
- tt: 'Show Device Flows'
- });
- }
-
+ tov.installButtons(buttons, data, data.props['URI']);
+ tov.hooks.singleSelect(data);
tps.displaySomething();
}
- function validateSelectionContext() {
- if (!hovered && !nSel()) {
- tts.cancelTraffic();
- return false;
- }
- return true;
+ // returns true if we are hovering over a node, or any nodes are selected
+ function somethingSelected() {
+ return hovered || nSel();
}
function clickConsumed(x) {
@@ -306,10 +270,11 @@
selectObject: selectObject,
deselectObject: deselectObject,
deselectAll: deselectAll,
+ updateDetail: updateDetail,
hovered: function () { return hovered; },
selectOrder: function () { return selectOrder; },
- validateSelectionContext: validateSelectionContext,
+ somethingSelected: somethingSelected,
clickConsumed: clickConsumed
};
diff --git a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
index cbf443a..84de261 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -25,19 +25,25 @@
// injected references
var $log, fs, tbs, ps, tov, api;
+ // API:
+ // getActionEntry
+ // setUpKeys
+
// internal state
- var toolbar, keyData, cachedState;
+ var toolbar, keyData, cachedState, thirdRow;
// constants
var name = 'topo-tbar',
- cooktag = 'topo_prefs';
+ cooktag = 'topo_prefs',
+ soa = 'switchOverlayActions: ',
+ selOver = 'Select overlay here ⇧';
+
// key to button mapping data
var k2b = {
O: { id: 'summary-tog', gid: 'summary', isel: true},
I: { id: 'instance-tog', gid: 'uiAttached', isel: true },
D: { id: 'details-tog', gid: 'details', isel: true },
-
H: { id: 'hosts-tog', gid: 'endstation', isel: false },
M: { id: 'offline-tog', gid: 'switch', isel: true },
P: { id: 'ports-tog', gid: 'ports', isel: true },
@@ -50,16 +56,16 @@
L: { id: 'cycleLabels-btn', gid: 'cycleLabels' },
R: { id: 'resetZoom-btn', gid: 'resetZoom' },
- E: { id: 'eqMaster-btn', gid: 'eqMaster' },
-
- V: { id: 'relatedIntents-btn', gid: 'relatedIntents' },
- leftArrow: { id: 'prevIntent-btn', gid: 'prevIntent' },
- rightArrow: { id: 'nextIntent-btn', gid: 'nextIntent' },
- W: { id: 'intentTraffic-btn', gid: 'intentTraffic' },
- A: { id: 'allTraffic-btn', gid: 'allTraffic' },
- F: { id: 'flows-btn', gid: 'flows' }
+ E: { id: 'eqMaster-btn', gid: 'eqMaster' }
};
+ var prohibited = [
+ 'T', 'backSlash', 'slash',
+ 'X' // needed until we re-instate X above.
+ ];
+ prohibited = prohibited.concat(d3.map(k2b).keys());
+
+
// initial toggle state: default settings and tag to key mapping
var defaultPrefsState = {
summary: 1,
@@ -112,6 +118,7 @@
}
function initKeyData() {
+ // TODO: use angular forEach instead of d3.map
keyData = d3.map(k2b);
keyData.forEach(function(key, value) {
var data = api.getActionEntry(key);
@@ -124,6 +131,7 @@
var v = keyData.get(key);
v.btn = toolbar.addButton(v.id, v.gid, v.cb, v.tt);
}
+
function addToggle(key, suppressIfMobile) {
var v = keyData.get(key);
if (suppressIfMobile && fs.isMobile()) { return; }
@@ -158,36 +166,60 @@
// generate radio button set for overlays; start with 'none'
var rset = [{
- gid: 'unknown',
+ gid: 'topo',
tooltip: 'No Overlay',
cb: function () {
- tov.tbSelection(null);
+ tov.tbSelection(null, switchOverlayActions);
}
}];
-
- tov.list().forEach(function (key) {
- var ov = tov.overlay(key);
- rset.push({
- gid: ov._glyphId,
- tooltip: (ov.tooltip || '(no tooltip)'),
- cb: function () {
- tov.tbSelection(ov.overlayId);
- }
- });
- });
-
+ tov.augmentRbset(rset, switchOverlayActions);
toolbar.addRadioSet('topo-overlays', rset);
}
- // TODO: 3rd row needs to be swapped in/out based on selected overlay
- // NOTE: This particular row of buttons is for the traffic overlay
- function addThirdRow() {
- addButton('V');
- addButton('leftArrow');
- addButton('rightArrow');
- addButton('W');
- addButton('A');
- addButton('F');
+ // invoked by overlay service to switch out old buttons and switch in new
+ function switchOverlayActions(oid, keyBindings) {
+ var prohibits = [],
+ kb = fs.isO(keyBindings) || {},
+ order = fs.isA(kb._keyOrder) || [];
+
+ if (keyBindings && !keyBindings._keyOrder) {
+ $log.warn(soa + 'no _keyOrder property defined');
+ } else {
+ // sanity removal of reserved property names
+ ['esc', '_keyListener', '_helpFormat'].forEach(function (k) {
+ fs.removeFromArray(k, order);
+ });
+ }
+
+ thirdRow.clear();
+
+ if (!order.length) {
+ thirdRow.setText(selOver);
+ thirdRow.classed('right', true);
+ api.setUpKeys(); // clear previous overlay key bindings
+
+ } else {
+ thirdRow.classed('right', false);
+ angular.forEach(order, function (key) {
+ var value, bid, gid, tt;
+
+ if (prohibited.indexOf(key) > -1) {
+ prohibits.push(key);
+
+ } else {
+ value = keyBindings[key];
+ bid = oid + '-' + key;
+ gid = tov.mkGlyphId(oid, value.gid);
+ tt = value.tt + ' (' + key + ')';
+ thirdRow.addButton(bid, gid, value.cb, tt);
+ }
+ });
+ api.setUpKeys(keyBindings); // add overlay key bindings
+ }
+
+ if (prohibits.length) {
+ $log.warn(soa + 'Prohibited key bindings ignored:', prohibits);
+ }
}
function createToolbar() {
@@ -197,8 +229,9 @@
toolbar.addRow();
addSecondRow();
addOverlays();
- toolbar.addRow();
- addThirdRow();
+ thirdRow = toolbar.addRow();
+ thirdRow.setText(selOver);
+ thirdRow.classed('right', true);
if (cachedState.toolbar) {
toolbar.show();
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
index 7332ad0..27ec979 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
@@ -23,85 +23,44 @@
'use strict';
// injected refs
- var $log, fs, flash, wss;
+ var $log, fs, flash, wss, api;
- // api to topoForce
- var api;
/*
- clearLinkTrafficStyle()
- removeLinkLabels()
- updateLinks()
- findLinkById( id )
- hovered()
- validateSelectionContext()
+ API to topoForce
+ hovered()
+ somethingSelected()
+ selectOrder()
*/
- // constants
- var hoverModeNone = 0,
- hoverModeAll = 1,
- hoverModeFlows = 2,
- hoverModeIntents = 3;
-
// internal state
- var hoverMode = hoverModeNone;
+ var trafficMode = null,
+ hoverMode = null;
// === -----------------------------------------------------
- // Event Handlers
-
- function showTraffic(data) {
- var paths = data.paths;
-
- api.clearLinkTrafficStyle();
- api.removeLinkLabels();
-
- // Now highlight all links in the paths payload, and attach
- // labels to them, if they are defined.
- paths.forEach(function (p) {
- var n = p.links.length,
- i, ldata, lab, units, magnitude, portcls;
-
- for (i=0; i<n; i++) {
- ldata = api.findLinkById(p.links[i]);
- lab = p.labels[i];
-
- if (ldata && !ldata.el.empty()) {
- ldata.el.classed(p.class, true);
- ldata.label = lab;
-
- if (fs.endsWith(lab, 'bps')) {
- // inject additional styling for port-based traffic
- units = lab.substring(lab.length-4);
- portcls = 'port-traffic-' + units;
-
- // for GBps
- if (units.substring(0,1) === 'G') {
- magnitude = fs.parseBitRate(lab);
- if (magnitude >= 9) {
- portcls += '-choked'
- }
- }
- ldata.el.classed(portcls, true);
- }
- }
- }
- });
-
- api.updateLinks();
- }
-
- // === -----------------------------------------------------
// Helper functions
+ // invoked in response to change in selection and/or mouseover/out:
+ function requestTrafficForMode() {
+ if (hoverMode === 'flows') {
+ requestDeviceLinkFlows();
+ } else if (hoverMode === 'intents') {
+ requestRelatedIntents();
+ } else {
+ cancelTraffic();
+ }
+ }
+
function requestDeviceLinkFlows() {
+ // generates payload based on current hover-state
var hov = api.hovered();
function hoverValid() {
- return hoverMode === hoverModeFlows &&
+ return hoverMode === 'flows' &&
hov && (hov.class === 'device');
}
- if (api.validateSelectionContext()) {
+ if (api.somethingSelected()) {
wss.sendEvent('requestDeviceLinkFlows', {
ids: api.selectOrder(),
hover: hoverValid() ? hov.id : ''
@@ -110,14 +69,15 @@
}
function requestRelatedIntents() {
+ // generates payload based on current hover-state
var hov = api.hovered();
function hoverValid() {
- return hoverMode === hoverModeIntents &&
+ return hoverMode === 'intents' &&
hov && (hov.class === 'host' || hov.class === 'device');
}
- if (api.validateSelectionContext()) {
+ if (api.somethingSelected()) {
wss.sendEvent('requestRelatedIntents', {
ids: api.selectOrder(),
hover: hoverValid() ? hov.id : ''
@@ -126,71 +86,75 @@
}
- // === -----------------------------------------------------
- // Traffic requests
+ // === -------------------------------------------------------------
+ // Traffic requests invoked from keystrokes or toolbar buttons...
function cancelTraffic() {
- wss.sendEvent('cancelTraffic');
- }
-
- // invoked in response to change in selection and/or mouseover/out:
- function requestTrafficForMode() {
- if (hoverMode === hoverModeFlows) {
- requestDeviceLinkFlows();
- } else if (hoverMode === hoverModeIntents) {
- requestRelatedIntents();
+ if (!trafficMode) {
+ return false;
}
+
+ trafficMode = hoverMode = null;
+ wss.sendEvent('cancelTraffic');
+ flash.flash('Traffic monitoring canceled');
+ return true;
}
- // === -----------------------------
- // keystroke commands
-
- // keystroke-right-arrow (see topo.js)
- function showNextIntentAction() {
- hoverMode = hoverModeNone;
- wss.sendEvent('requestNextRelatedIntent');
- flash.flash('Next related intent');
- }
-
- // keystroke-left-arrow (see topo.js)
- function showPrevIntentAction() {
- hoverMode = hoverModeNone;
- wss.sendEvent('requestPrevRelatedIntent');
- flash.flash('Previous related intent');
- }
-
- // keystroke-W (see topo.js)
- function showSelectedIntentTrafficAction() {
- hoverMode = hoverModeNone;
- wss.sendEvent('requestSelectedIntentTraffic');
- flash.flash('Traffic on Selected Path');
- }
-
- // keystroke-A (see topo.js)
- function showAllFlowTrafficAction() {
- hoverMode = hoverModeAll;
+ function showAllFlowTraffic() {
+ trafficMode = 'allFlow';
+ hoverMode = 'all';
wss.sendEvent('requestAllFlowTraffic');
flash.flash('All Flow Traffic');
}
- // keystroke-A (see topo.js)
- function showAllPortTrafficAction() {
- hoverMode = hoverModeAll;
+ function showAllPortTraffic() {
+ trafficMode = 'allPort';
+ hoverMode = 'all';
wss.sendEvent('requestAllPortTraffic');
flash.flash('All Port Traffic');
}
- // === -----------------------------
- // action buttons on detail panel
+ function showDeviceLinkFlows () {
+ trafficMode = hoverMode = 'flows';
+ requestDeviceLinkFlows();
+ flash.flash('Device Flows');
+ }
- // also, keystroke-V (see topo.js)
- function showRelatedIntentsAction () {
- hoverMode = hoverModeIntents;
+ function showRelatedIntents () {
+ trafficMode = hoverMode = 'intents';
requestRelatedIntents();
flash.flash('Related Paths');
}
- function addHostIntentAction () {
+ function showPrevIntent() {
+ if (trafficMode === 'intents') {
+ hoverMode = null;
+ wss.sendEvent('requestPrevRelatedIntent');
+ flash.flash('Previous related intent');
+ }
+ }
+
+ function showNextIntent() {
+ if (trafficMode === 'intents') {
+ hoverMode = null;
+ wss.sendEvent('requestNextRelatedIntent');
+ flash.flash('Next related intent');
+ }
+ }
+
+ function showSelectedIntentTraffic() {
+ if (trafficMode === 'intents') {
+ hoverMode = null;
+ wss.sendEvent('requestSelectedIntentTraffic');
+ flash.flash('Traffic on Selected Path');
+ }
+ }
+
+
+ // === ------------------------------------------------------
+ // action buttons on detail panel (multiple selection)
+
+ function addHostIntent () {
var so = api.selectOrder();
wss.sendEvent('addHostIntent', {
one: so[0],
@@ -200,7 +164,7 @@
flash.flash('Host-to-Host flow added');
}
- function addMultiSourceIntentAction () {
+ function addMultiSourceIntent () {
var so = api.selectOrder();
wss.sendEvent('addMultiSourceIntent', {
src: so.slice(0, so.length - 1),
@@ -210,12 +174,6 @@
flash.flash('Multi-Source flow added');
}
- // also, keystroke-F (see topo.js)
- function showDeviceLinkFlowsAction () {
- hoverMode = hoverModeFlows;
- requestDeviceLinkFlows();
- flash.flash('Device Flows');
- }
// === -----------------------------------------------------
@@ -231,29 +189,26 @@
flash = _flash_;
wss = _wss_;
- function initTraffic(_api_) {
- api = _api_;
- }
-
- function destroyTraffic() { }
-
return {
- initTraffic: initTraffic,
- destroyTraffic: destroyTraffic,
+ initTraffic: function (_api_) { api = _api_; },
+ destroyTraffic: function () { },
- showTraffic: showTraffic,
-
+ // invoked from toolbar overlay buttons or keystrokes
cancelTraffic: cancelTraffic,
+ showAllFlowTraffic: showAllFlowTraffic,
+ showAllPortTraffic: showAllPortTraffic,
+ showDeviceLinkFlows: showDeviceLinkFlows,
+ showRelatedIntents: showRelatedIntents,
+ showPrevIntent: showPrevIntent,
+ showNextIntent: showNextIntent,
+ showSelectedIntentTraffic: showSelectedIntentTraffic,
+
+ // invoked from mouseover/mouseout and selection change
requestTrafficForMode: requestTrafficForMode,
- showRelatedIntentsAction: showRelatedIntentsAction,
- addHostIntentAction: addHostIntentAction,
- addMultiSourceIntentAction: addMultiSourceIntentAction,
- showDeviceLinkFlowsAction: showDeviceLinkFlowsAction,
- showNextIntentAction: showNextIntentAction,
- showPrevIntentAction: showPrevIntentAction,
- showSelectedIntentTrafficAction: showSelectedIntentTrafficAction,
- showAllFlowTrafficAction: showAllFlowTrafficAction,
- showAllPortTrafficAction: showAllPortTrafficAction
+
+ // invoked from buttons on detail (multi-select) panel
+ addHostIntent: addHostIntent,
+ addMultiSourceIntent: addMultiSourceIntent
};
}]);
}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
index 71cb94c..be91f0c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
@@ -16,7 +16,7 @@
*/
/*
- ONOS GUI -- Topology Traffic Module.
+ ONOS GUI -- Topology Traffic Overlay Module.
Defines behavior for viewing different traffic modes.
Installed as a Topology Overlay.
*/
@@ -24,7 +24,13 @@
'use strict';
// injected refs
- var $log;
+ var $log, tov, tts;
+
+ // NOTE: no internal state here -- see TopoTrafficService for that
+
+ // NOTE: providing button disabling requires too big a refactoring of
+ // the button factory etc. Will have to be done another time.
+
// traffic overlay definition
var overlay = {
@@ -32,26 +38,112 @@
glyphId: 'allTraffic',
tooltip: 'Traffic Overlay',
- activate: activateTraffic,
- deactivate: deactivateTraffic
+ // NOTE: Traffic glyphs already installed as part of the base ONOS set.
+
+ activate: function () {
+ $log.debug("Traffic overlay ACTIVATED");
+ },
+
+ deactivate: function () {
+ tts.cancelTraffic();
+ $log.debug("Traffic overlay DEACTIVATED");
+ },
+
+ // detail panel button definitions
+ // (keys match button identifiers, also defined in TrafficOverlay.java)
+ buttons: {
+ showDeviceFlows: {
+ gid: 'flows',
+ tt: 'Show Device Flows',
+ cb: function (data) { tts.showDeviceLinkFlows(); }
+ },
+
+ showRelatedTraffic: {
+ gid: 'relatedIntents',
+ tt: 'Show Related Traffic',
+ cb: function (data) { tts.showRelatedIntents(); }
+ }
+ },
+
+ // key bindings for traffic overlay toolbar buttons
+ // NOTE: fully qual. button ID is derived from overlay-id and key-name
+ keyBindings: {
+ 0: {
+ cb: function () { tts.cancelTraffic(); },
+ tt: 'Cancel traffic monitoring',
+ gid: 'xMark'
+ },
+
+ A: {
+ cb: function () { tts.showAllFlowTraffic(); },
+ tt: 'Monitor all traffic using flow stats',
+ gid: 'allTraffic'
+ },
+ Q: {
+ cb: function () { tts.showAllPortTraffic(); },
+ tt: 'Monitor all traffic using port stats',
+ gid: 'allTraffic'
+ },
+ F: {
+ cb: function () { tts.showDeviceLinkFlows(); },
+ tt: 'Show device link flows',
+ gid: 'flows'
+ },
+ V: {
+ cb: function () { tts.showRelatedIntents(); },
+ tt: 'Show all related intents',
+ gid: 'relatedIntents'
+ },
+ leftArrow: {
+ cb: function () { tts.showPrevIntent(); },
+ tt: 'Show previous related intent',
+ gid: 'prevIntent'
+ },
+ rightArrow: {
+ cb: function () { tts.showNextIntent(); },
+ tt: 'Show next related intent',
+ gid: 'nextIntent'
+ },
+ W: {
+ cb: function () { tts.showSelectedIntentTraffic(); },
+ tt: 'Monitor traffic of selected intent',
+ gid: 'intentTraffic'
+ },
+
+ _keyOrder: [
+ '0', 'A', 'Q', 'F', 'V', 'leftArrow', 'rightArrow', 'W'
+ ]
+ },
+
+ hooks: {
+ // hook for handling escape key
+ escape: function () {
+ // Must return true to consume ESC, false otherwise.
+ return tts.cancelTraffic();
+ },
+
+ // hooks for when the selection changes...
+ empty: function () {
+ tts.cancelTraffic();
+ },
+ single: function (data) {
+ tts.requestTrafficForMode();
+ },
+ multi: function (selectOrder) {
+ tts.requestTrafficForMode();
+ tov.addDetailButton('showRelatedTraffic');
+ }
+ }
};
- // === implementation of overlay API (essentially callbacks)
- function activateTraffic() {
- $log.debug("Topology traffic overlay ACTIVATED");
- }
-
- function deactivateTraffic() {
- $log.debug("Topology traffic overlay DEACTIVATED");
- }
-
-
// invoke code to register with the overlay service
angular.module('ovTopo')
- .run(['$log', 'TopoOverlayService',
+ .run(['$log', 'TopoOverlayService', 'TopoTrafficService',
- function (_$log_, tov) {
+ function (_$log_, _tov_, _tts_) {
$log = _$log_;
+ tov = _tov_;
+ tts = _tts_;
tov.register(overlay);
}]);