blob: dbb1cc309ef0f9392b48e3a2cfd16e9a2e579f78 [file] [log] [blame]
Simon Hunt72e44bf2015-07-21 21:34:20 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Simon Hunt72e44bf2015-07-21 21:34:20 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18/*
19 ONOS GUI -- Topology Overlay Module.
20
21 Provides overlay capabilities, allowing ONOS apps to provide additional
22 custom data/behavior for the topology view.
23
24 */
25
26(function () {
27 'use strict';
28
29 // constants
30 var tos = 'TopoOverlayService: ';
31
32 // injected refs
Andrea Campanella2dc91dc2015-12-07 12:17:02 -080033 var $log, $timeout, fs, gs, wss, ns, tss, tps, api;
Simon Hunt72e44bf2015-07-21 21:34:20 -070034
35 // internal state
Simon Hunte05cae42015-07-23 17:35:24 -070036 var overlays = {},
Simon Hunt4a6b54b2015-10-27 22:08:25 -070037 current = null,
38 reset = true;
Simon Hunt72e44bf2015-07-21 21:34:20 -070039
40 function error(fn, msg) {
41 $log.error(tos + fn + '(): ' + msg);
42 }
43
44 function warn(fn, msg) {
45 $log.warn(tos + fn + '(): ' + msg);
46 }
47
Simon Huntfb940112015-07-29 18:36:35 -070048 function mkGlyphId(oid, gid) {
49 return (gid[0] === '*') ? oid + '-' + gid.slice(1) : gid;
50 }
Simon Hunt72e44bf2015-07-21 21:34:20 -070051
Simon Huntfb940112015-07-29 18:36:35 -070052 function handleGlyphs(o) {
53 var gdata = fs.isO(o.glyphs),
54 oid = o.overlayId,
55 gid = o.glyphId || 'unknown',
56 data = {},
57 note = [];
58
59 o._glyphId = mkGlyphId(oid, gid);
60
61 o.mkGid = function (g) {
62 return mkGlyphId(oid, g);
63 };
64 o.mkId = function (s) {
65 return oid + '-' + s;
66 };
67
68 // process glyphs if defined
69 if (gdata) {
70 angular.forEach(gdata, function (value, key) {
71 var fullkey = oid + '-' + key;
72 data['_' + fullkey] = value.vb;
73 data[fullkey] = value.d;
74 note.push('*' + key);
75 });
76 gs.registerGlyphs(data);
77 $log.debug('registered overlay glyphs:', oid, note);
Simon Hunt72e44bf2015-07-21 21:34:20 -070078 }
79 }
80
81 function register(overlay) {
82 var r = 'register',
83 over = fs.isO(overlay),
Simon Hunt8d22c4b2015-08-06 16:24:43 -070084 kb = over ? fs.isO(overlay.keyBindings) : null,
Simon Hunt72e44bf2015-07-21 21:34:20 -070085 id = over ? over.overlayId : '';
86
87 if (!id) {
88 return error(r, 'not a recognized overlay');
89 }
90 if (overlays[id]) {
91 return warn(r, 'already registered: "' + id + '"');
92 }
93 overlays[id] = overlay;
Simon Huntfb940112015-07-29 18:36:35 -070094 handleGlyphs(overlay);
Simon Hunt8d22c4b2015-08-06 16:24:43 -070095
96 if (kb) {
97 if (!fs.isA(kb._keyOrder)) {
98 warn(r, 'no _keyOrder array defined on keyBindings');
99 } else {
100 kb._keyOrder.forEach(function (k) {
101 if (k !== '-' && !kb[k]) {
102 warn(r, 'no "' + k + '" property defined on keyBindings');
103 }
104 });
105 }
106 }
107
Simon Hunt72e44bf2015-07-21 21:34:20 -0700108 $log.debug(tos + 'registered overlay: ' + id, overlay);
109 }
110
Simon Huntc5489c92017-01-30 17:50:48 -0800111 // Returns the list of overlay identifiers. If a truthy argument is supplied,
112 // returns an augmented list of overlay tokens, providing overlay ID,
113 // glyph ID and overlay Tooltip text.
114 function list(x) {
115 var oids = d3.map(overlays).keys(),
116 info = [];
117
118 if (!x) {
119 return oids;
120 }
121
122 oids.forEach(function (oid) {
123 var o = overlays[oid],
124 ot = o.tooltip || '%' + o.overlayId + '%',
125 og = o._glyphId;
126
127 info.push({ id: oid, tt: ot, gid: og });
128 });
129 return info;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700130 }
131
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700132 // add a radio button for each registered overlay
Simon Hunta5b53af2015-10-12 15:56:40 -0700133 // return an overlay id to index map
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700134 function augmentRbset(rset, switchFn) {
Simon Hunta5b53af2015-10-12 15:56:40 -0700135 var map = {},
136 idx = 1;
137
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700138 angular.forEach(overlays, function (ov) {
139 rset.push({
140 gid: ov._glyphId,
141 tooltip: (ov.tooltip || '(no tooltip)'),
142 cb: function () {
143 tbSelection(ov.overlayId, switchFn);
144 }
145 });
Simon Hunta5b53af2015-10-12 15:56:40 -0700146 map[ov.overlayId] = idx++;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700147 });
Simon Hunta5b53af2015-10-12 15:56:40 -0700148 return map;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700149 }
150
Simon Hunte05cae42015-07-23 17:35:24 -0700151 // an overlay was selected via toolbar radio button press from user
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700152 function tbSelection(id, switchFn) {
Simon Hunte05cae42015-07-23 17:35:24 -0700153 var same = current && current.overlayId === id,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700154 payload = {},
155 actions;
Simon Hunte05cae42015-07-23 17:35:24 -0700156
157 function doop(op) {
158 var oid = current.overlayId;
159 $log.debug('Overlay:', op, oid);
160 current[op]();
161 payload[op] = oid;
162 }
163
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700164 if (reset || !same) {
165 reset = false;
Simon Hunte05cae42015-07-23 17:35:24 -0700166 current && doop('deactivate');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700167 current = overlays[id];
Simon Hunte05cae42015-07-23 17:35:24 -0700168 current && doop('activate');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700169 actions = current && fs.isO(current.keyBindings);
170 switchFn(id, actions);
171
Simon Hunte05cae42015-07-23 17:35:24 -0700172 wss.sendEvent('topoSelectOverlay', payload);
Simon Hunt0af1ec32015-07-24 12:17:55 -0700173
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700174 // Ensure summary and details panels are updated immediately..
Simon Hunt0af1ec32015-07-24 12:17:55 -0700175 wss.sendEvent('requestSummary');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700176 tss.updateDetail();
Simon Hunte05cae42015-07-23 17:35:24 -0700177 }
178 }
179
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700180 var coreButtons = {
181 showDeviceView: {
182 gid: 'switch',
183 tt: 'Show Device View',
184 path: 'device'
185 },
186 showFlowView: {
187 gid: 'flowTable',
188 tt: 'Show Flow View for this Device',
189 path: 'flow'
190 },
191 showPortView: {
192 gid: 'portTable',
193 tt: 'Show Port View for this Device',
194 path: 'port'
195 },
196 showGroupView: {
197 gid: 'groupTable',
198 tt: 'Show Group View for this Device',
199 path: 'group'
Jian Li79f67322016-01-06 18:22:37 -0800200 },
201 showMeterView: {
202 gid: 'meterTable',
203 tt: 'Show Meter View for this Device',
204 path: 'meter'
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700205 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700206 };
Simon Huntfb940112015-07-29 18:36:35 -0700207
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700208 // retrieves a button definition from the current overlay and generates
209 // a button descriptor to be added to the panel, with the data baked in
210 function _getButtonDef(id, data) {
211 var btns = current && current.buttons,
212 b = btns && btns[id],
213 cb = fs.isF(b.cb),
214 f = cb ? function () { cb(data); } : function () {};
215
216 return b ? {
217 id: current.mkId(id),
218 gid: current.mkGid(b.gid),
219 tt: b.tt,
220 cb: f
221 } : null;
222 }
223
Simon Hunt3a0598f2015-08-04 19:59:04 -0700224 // install core buttons, and include any additional from the current overlay
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700225 function installButtons(buttons, data, devId) {
226 buttons.forEach(function (id) {
227 var btn = coreButtons[id],
228 gid = btn && btn.gid,
229 tt = btn && btn.tt,
230 path = btn && btn.path;
Simon Hunt3a0598f2015-08-04 19:59:04 -0700231
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700232 if (btn) {
233 tps.addAction({
234 id: 'core-' + id,
235 gid: gid,
236 tt: tt,
237 cb: function () { ns.navTo(path, {devId: devId }); }
238 });
239 } else if (btn = _getButtonDef(id, data)) {
240 tps.addAction(btn);
Simon Hunt3a0598f2015-08-04 19:59:04 -0700241 }
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700242 });
243 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700244
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700245 function addDetailButton(id) {
246 var b = _getButtonDef(id);
247 if (b) {
248 tps.addAction({
249 id: current.mkId(id),
250 gid: current.mkGid(b.gid),
251 cb: b.cb,
252 tt: b.tt
253 });
254 }
255 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700256
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700257
258 // === -----------------------------------------------------
259 // Hooks for overlays
260
261 function _hook(x) {
262 var h = current && current.hooks;
263 return h && fs.isF(h[x]);
264 }
265
266 function escapeHook() {
267 var eh = _hook('escape');
268 return eh ? eh() : false;
269 }
270
271 function emptySelectHook() {
272 var cb = _hook('empty');
273 cb && cb();
274 }
275
276 function singleSelectHook(data) {
277 var cb = _hook('single');
278 cb && cb(data);
279 }
280
281 function multiSelectHook(selectOrder) {
282 var cb = _hook('multi');
283 cb && cb(selectOrder);
284 }
285
Simon Hunt584e92d2015-08-24 11:27:22 -0700286 function mouseOverHook(what) {
287 var cb = _hook('mouseover');
288 cb && cb(what);
289 }
290
291 function mouseOutHook() {
292 var cb = _hook('mouseout');
293 cb && cb();
294 }
295
Simon Hunt5c1a9382016-06-01 19:35:35 -0700296 // Temporary function to allow overlays to modify link detail data
297 // in the client. (In the near future, this will be done on the server).
298 function modifyLinkDataHook(data, extra) {
299 var cb = _hook('modifylinkdata');
300 return cb && extra ? cb(data, extra) : data;
301 }
302
Simon Hunt8419efd2017-01-12 12:36:28 -0800303 // Request from Intent View to visualize an intent on the topo view
304 function showIntentHook(intentData) {
305 $log.debug('^^ topoOverlay.showIntentHook(...) ^^');
306 var cb = _hook('showintent');
307 return cb && cb(intentData);
308 }
309
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700310 // === -----------------------------------------------------
311 // Event (from server) Handlers
312
313 function setApi(_api_, _tss_) {
314 api = _api_;
315 tss = _tss_;
316 }
317
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800318 //process highlight event with optional delay
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700319 function showHighlights(data) {
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800320 function doHighlight() {
321 _showHighlights(data);
322 }
323
324 if (data.delay) {
325 $timeout(doHighlight, data.delay);
326 } else {
327 doHighlight();
328 }
329 }
330
331 function _showHighlights(data) {
Simon Hunt743a8492015-08-25 16:18:19 -0700332 var less;
333
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700334 /*
335 API to topoForce
336 clearLinkTrafficStyle()
337 removeLinkLabels()
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700338 findLinkById( id )
Simon Hunt94f7dae2015-08-26 17:40:59 -0700339 findNodeById( id )
Simon Hunt743a8492015-08-25 16:18:19 -0700340 updateLinks()
341 updateNodes()
342 supLayers( bool, [less] )
343 unsupNode( id, [less] )
Simon Hunt94f7dae2015-08-26 17:40:59 -0700344 unsupLink( key, [less] )
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700345 */
346
Simon Hunte9343f32015-10-21 18:07:46 -0700347 api.clearNodeDeco();
348 api.removeNodeBadges();
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700349 api.clearLinkTrafficStyle();
350 api.removeLinkLabels();
351
Simon Hunt743a8492015-08-25 16:18:19 -0700352 // handle element suppression
353 if (data.subdue) {
354 less = data.subdue === 'min';
355 api.supLayers(true, less);
356
357 } else {
358 api.supLayers(false);
359 api.supLayers(false, true);
360 }
361
Simon Hunt94f7dae2015-08-26 17:40:59 -0700362 data.hosts.forEach(function (host) {
Andrea Campanella52125412015-12-03 14:50:40 -0800363 var hdata = api.findNodeById(host.id),
364 badgeData = host.badge || null;
365
Simon Hunta1f1c022016-03-03 15:54:57 -0800366 if (hdata && hdata.el && !hdata.el.empty()) {
Andrea Campanella52125412015-12-03 14:50:40 -0800367 hdata.badge = badgeData;
Simon Hunt5b3ff902015-08-27 09:46:27 -0700368 if (!host.subdue) {
369 api.unsupNode(hdata.id, less);
370 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700371 // TODO: further highlighting?
Simon Huntb3442482016-03-03 17:30:07 -0800372 } else {
373 $log.warn('HILITE: no host element:', host.id);
Simon Hunt94f7dae2015-08-26 17:40:59 -0700374 }
375 });
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700376
Simon Hunt94f7dae2015-08-26 17:40:59 -0700377 data.devices.forEach(function (device) {
Simon Hunte9343f32015-10-21 18:07:46 -0700378 var ddata = api.findNodeById(device.id),
379 badgeData = device.badge || null;
380
Simon Hunta1f1c022016-03-03 15:54:57 -0800381 if (ddata && ddata.el && !ddata.el.empty()) {
Simon Hunte9343f32015-10-21 18:07:46 -0700382 ddata.badge = badgeData;
Simon Hunt5b3ff902015-08-27 09:46:27 -0700383 if (!device.subdue) {
384 api.unsupNode(ddata.id, less);
385 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700386 // TODO: further highlighting?
Simon Huntb3442482016-03-03 17:30:07 -0800387 } else {
388 $log.warn('HILITE: no device element:', device.id);
Simon Hunt94f7dae2015-08-26 17:40:59 -0700389 }
390 });
391
392 data.links.forEach(function (link) {
393 var ldata = api.findLinkById(link.id),
394 lab = link.label,
Simon Huntd3ceffa2015-08-25 12:44:35 -0700395 units, portcls, magnitude;
Simon Hunta1f1c022016-03-03 15:54:57 -0800396
397 if (ldata && ldata.el && !ldata.el.empty()) {
Simon Hunt5b3ff902015-08-27 09:46:27 -0700398 if (!link.subdue) {
399 api.unsupLink(ldata.key, less);
400 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700401 ldata.el.classed(link.css, true);
Simon Huntd3ceffa2015-08-25 12:44:35 -0700402 ldata.label = lab;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700403
Simon Hunt5b3ff902015-08-27 09:46:27 -0700404 // TODO: this needs to be pulled out into traffic overlay
Simon Huntd3ceffa2015-08-25 12:44:35 -0700405 // inject additional styling for port-based traffic
406 if (fs.endsWith(lab, 'bps')) {
407 units = lab.substring(lab.length-4);
408 portcls = 'port-traffic-' + units;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700409
Simon Huntd3ceffa2015-08-25 12:44:35 -0700410 // for GBps
411 if (units.substring(0,1) === 'G') {
412 magnitude = fs.parseBitRate(lab);
413 if (magnitude >= 9) {
414 portcls += '-choked'
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700415 }
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700416 }
Simon Huntd3ceffa2015-08-25 12:44:35 -0700417 ldata.el.classed(portcls, true);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700418 }
Simon Huntb3442482016-03-03 17:30:07 -0800419 } else {
420 $log.warn('HILITE: no link element:', link.id);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700421 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700422 });
Simon Huntfb940112015-07-29 18:36:35 -0700423
Simon Hunt743a8492015-08-25 16:18:19 -0700424 api.updateNodes();
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700425 api.updateLinks();
Simon Huntfb940112015-07-29 18:36:35 -0700426 }
427
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700428 // ========================================================================
429
Simon Hunt72e44bf2015-07-21 21:34:20 -0700430 angular.module('ovTopo')
431 .factory('TopoOverlayService',
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800432 ['$log', '$timeout', 'FnService', 'GlyphService', 'WebSocketService',
433 'NavService', 'TopoPanelService',
Simon Hunt72e44bf2015-07-21 21:34:20 -0700434
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800435 function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _ns_, _tps_) {
Simon Hunt72e44bf2015-07-21 21:34:20 -0700436 $log = _$log_;
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800437 $timeout = _$timeout_;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700438 fs = _fs_;
439 gs = _gs_;
Simon Hunte05cae42015-07-23 17:35:24 -0700440 wss = _wss_;
Simon Hunt3a0598f2015-08-04 19:59:04 -0700441 ns = _ns_;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700442 tps = _tps_;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700443
444 return {
445 register: register,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700446 setApi: setApi,
Simon Hunt72e44bf2015-07-21 21:34:20 -0700447 list: list,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700448 augmentRbset: augmentRbset,
449 mkGlyphId: mkGlyphId,
Simon Huntfb940112015-07-29 18:36:35 -0700450 tbSelection: tbSelection,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700451 installButtons: installButtons,
452 addDetailButton: addDetailButton,
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700453 resetOnToolbarDestroy: function () { reset = true; },
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700454 hooks: {
455 escape: escapeHook,
456 emptySelect: emptySelectHook,
457 singleSelect: singleSelectHook,
Simon Hunt584e92d2015-08-24 11:27:22 -0700458 multiSelect: multiSelectHook,
459 mouseOver: mouseOverHook,
Simon Hunt5c1a9382016-06-01 19:35:35 -0700460 mouseOut: mouseOutHook,
Simon Hunt8419efd2017-01-12 12:36:28 -0800461 modifyLinkData: modifyLinkDataHook,
462 showIntent: showIntentHook
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700463 },
464
465 showHighlights: showHighlights
Simon Hunt72e44bf2015-07-21 21:34:20 -0700466 }
467 }]);
468
469}());