blob: d51d8fcc036ef79cfcc7253016feb20ef145f222 [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 Huntfc5c5842017-02-01 23:32:18 -0800111 // Returns the list of overlay identifiers.
112 function list() {
113 return d3.map(overlays).keys();
114 }
Simon Huntc5489c92017-01-30 17:50:48 -0800115
Simon Hunt441c9ae2017-02-03 18:22:31 -0800116 // Returns an array containing overlays that implement the showIntent and
117 // acceptIntent callbacks, and that accept the given intent type
118 function overlaysAcceptingIntents(intentType) {
Simon Huntfc5c5842017-02-01 23:32:18 -0800119 var result = [];
120 angular.forEach(overlays, function (ov) {
Simon Hunt441c9ae2017-02-03 18:22:31 -0800121 var ovid = ov.overlayId,
122 hooks = fs.isO(ov.hooks) || {},
123 aicb = fs.isF(hooks.acceptIntent),
124 sicb = fs.isF(hooks.showIntent);
Simon Huntc5489c92017-01-30 17:50:48 -0800125
Simon Hunt441c9ae2017-02-03 18:22:31 -0800126 if (sicb && aicb && aicb(intentType)) {
Simon Huntfc5c5842017-02-01 23:32:18 -0800127 result.push({
Simon Hunt441c9ae2017-02-03 18:22:31 -0800128 id: ovid,
129 tt: ov.tooltip || '%' + ovid + '%'
Simon Huntfc5c5842017-02-01 23:32:18 -0800130 });
131 }
Simon Huntc5489c92017-01-30 17:50:48 -0800132 });
Simon Huntfc5c5842017-02-01 23:32:18 -0800133 return result;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700134 }
135
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700136 // add a radio button for each registered overlay
Simon Hunta5b53af2015-10-12 15:56:40 -0700137 // return an overlay id to index map
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700138 function augmentRbset(rset, switchFn) {
Simon Hunta5b53af2015-10-12 15:56:40 -0700139 var map = {},
140 idx = 1;
141
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700142 angular.forEach(overlays, function (ov) {
143 rset.push({
144 gid: ov._glyphId,
145 tooltip: (ov.tooltip || '(no tooltip)'),
146 cb: function () {
147 tbSelection(ov.overlayId, switchFn);
148 }
149 });
Simon Hunta5b53af2015-10-12 15:56:40 -0700150 map[ov.overlayId] = idx++;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700151 });
Simon Hunta5b53af2015-10-12 15:56:40 -0700152 return map;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700153 }
154
Simon Hunte05cae42015-07-23 17:35:24 -0700155 // an overlay was selected via toolbar radio button press from user
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700156 function tbSelection(id, switchFn) {
Simon Hunte05cae42015-07-23 17:35:24 -0700157 var same = current && current.overlayId === id,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700158 payload = {},
159 actions;
Simon Hunte05cae42015-07-23 17:35:24 -0700160
161 function doop(op) {
162 var oid = current.overlayId;
163 $log.debug('Overlay:', op, oid);
164 current[op]();
165 payload[op] = oid;
166 }
167
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700168 if (reset || !same) {
169 reset = false;
Simon Hunte05cae42015-07-23 17:35:24 -0700170 current && doop('deactivate');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700171 current = overlays[id];
Simon Hunte05cae42015-07-23 17:35:24 -0700172 current && doop('activate');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700173 actions = current && fs.isO(current.keyBindings);
174 switchFn(id, actions);
175
Simon Hunte05cae42015-07-23 17:35:24 -0700176 wss.sendEvent('topoSelectOverlay', payload);
Simon Hunt0af1ec32015-07-24 12:17:55 -0700177
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700178 // Ensure summary and details panels are updated immediately..
Simon Hunt0af1ec32015-07-24 12:17:55 -0700179 wss.sendEvent('requestSummary');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700180 tss.updateDetail();
Simon Hunte05cae42015-07-23 17:35:24 -0700181 }
182 }
183
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700184 var coreButtons = {
185 showDeviceView: {
186 gid: 'switch',
187 tt: 'Show Device View',
188 path: 'device'
189 },
190 showFlowView: {
191 gid: 'flowTable',
192 tt: 'Show Flow View for this Device',
193 path: 'flow'
194 },
195 showPortView: {
196 gid: 'portTable',
197 tt: 'Show Port View for this Device',
198 path: 'port'
199 },
200 showGroupView: {
201 gid: 'groupTable',
202 tt: 'Show Group View for this Device',
203 path: 'group'
Jian Li79f67322016-01-06 18:22:37 -0800204 },
205 showMeterView: {
206 gid: 'meterTable',
207 tt: 'Show Meter View for this Device',
208 path: 'meter'
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700209 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700210 };
Simon Huntfb940112015-07-29 18:36:35 -0700211
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700212 // retrieves a button definition from the current overlay and generates
213 // a button descriptor to be added to the panel, with the data baked in
214 function _getButtonDef(id, data) {
215 var btns = current && current.buttons,
216 b = btns && btns[id],
217 cb = fs.isF(b.cb),
218 f = cb ? function () { cb(data); } : function () {};
219
220 return b ? {
221 id: current.mkId(id),
222 gid: current.mkGid(b.gid),
223 tt: b.tt,
224 cb: f
225 } : null;
226 }
227
Simon Hunt3a0598f2015-08-04 19:59:04 -0700228 // install core buttons, and include any additional from the current overlay
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700229 function installButtons(buttons, data, devId) {
230 buttons.forEach(function (id) {
231 var btn = coreButtons[id],
232 gid = btn && btn.gid,
233 tt = btn && btn.tt,
234 path = btn && btn.path;
Simon Hunt3a0598f2015-08-04 19:59:04 -0700235
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700236 if (btn) {
237 tps.addAction({
238 id: 'core-' + id,
239 gid: gid,
240 tt: tt,
241 cb: function () { ns.navTo(path, {devId: devId }); }
242 });
243 } else if (btn = _getButtonDef(id, data)) {
244 tps.addAction(btn);
Simon Hunt3a0598f2015-08-04 19:59:04 -0700245 }
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700246 });
247 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700248
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700249 function addDetailButton(id) {
250 var b = _getButtonDef(id);
251 if (b) {
252 tps.addAction({
253 id: current.mkId(id),
254 gid: current.mkGid(b.gid),
255 cb: b.cb,
256 tt: b.tt
257 });
258 }
259 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700260
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700261
262 // === -----------------------------------------------------
263 // Hooks for overlays
264
265 function _hook(x) {
266 var h = current && current.hooks;
267 return h && fs.isF(h[x]);
268 }
269
270 function escapeHook() {
271 var eh = _hook('escape');
272 return eh ? eh() : false;
273 }
274
275 function emptySelectHook() {
276 var cb = _hook('empty');
277 cb && cb();
278 }
279
280 function singleSelectHook(data) {
281 var cb = _hook('single');
282 cb && cb(data);
283 }
284
285 function multiSelectHook(selectOrder) {
286 var cb = _hook('multi');
287 cb && cb(selectOrder);
288 }
289
Simon Hunt584e92d2015-08-24 11:27:22 -0700290 function mouseOverHook(what) {
291 var cb = _hook('mouseover');
292 cb && cb(what);
293 }
294
295 function mouseOutHook() {
296 var cb = _hook('mouseout');
297 cb && cb();
298 }
299
Simon Hunt5c1a9382016-06-01 19:35:35 -0700300 // Temporary function to allow overlays to modify link detail data
301 // in the client. (In the near future, this will be done on the server).
302 function modifyLinkDataHook(data, extra) {
303 var cb = _hook('modifylinkdata');
304 return cb && extra ? cb(data, extra) : data;
305 }
306
Simon Hunt8419efd2017-01-12 12:36:28 -0800307 // Request from Intent View to visualize an intent on the topo view
308 function showIntentHook(intentData) {
Simon Hunt441c9ae2017-02-03 18:22:31 -0800309 var cb = _hook('showIntent');
Simon Hunt8419efd2017-01-12 12:36:28 -0800310 return cb && cb(intentData);
311 }
312
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700313 // === -----------------------------------------------------
314 // Event (from server) Handlers
315
316 function setApi(_api_, _tss_) {
317 api = _api_;
318 tss = _tss_;
319 }
320
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800321 //process highlight event with optional delay
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700322 function showHighlights(data) {
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800323 function doHighlight() {
324 _showHighlights(data);
325 }
326
327 if (data.delay) {
328 $timeout(doHighlight, data.delay);
329 } else {
330 doHighlight();
331 }
332 }
333
334 function _showHighlights(data) {
Simon Hunt743a8492015-08-25 16:18:19 -0700335 var less;
336
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700337 /*
338 API to topoForce
339 clearLinkTrafficStyle()
340 removeLinkLabels()
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700341 findLinkById( id )
Simon Hunt94f7dae2015-08-26 17:40:59 -0700342 findNodeById( id )
Simon Hunt743a8492015-08-25 16:18:19 -0700343 updateLinks()
344 updateNodes()
345 supLayers( bool, [less] )
346 unsupNode( id, [less] )
Simon Hunt94f7dae2015-08-26 17:40:59 -0700347 unsupLink( key, [less] )
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700348 */
349
Simon Hunte9343f32015-10-21 18:07:46 -0700350 api.clearNodeDeco();
351 api.removeNodeBadges();
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700352 api.clearLinkTrafficStyle();
353 api.removeLinkLabels();
354
Simon Hunt743a8492015-08-25 16:18:19 -0700355 // handle element suppression
356 if (data.subdue) {
357 less = data.subdue === 'min';
358 api.supLayers(true, less);
359
360 } else {
361 api.supLayers(false);
362 api.supLayers(false, true);
363 }
364
Simon Hunt94f7dae2015-08-26 17:40:59 -0700365 data.hosts.forEach(function (host) {
Andrea Campanella52125412015-12-03 14:50:40 -0800366 var hdata = api.findNodeById(host.id),
367 badgeData = host.badge || null;
368
Simon Hunta1f1c022016-03-03 15:54:57 -0800369 if (hdata && hdata.el && !hdata.el.empty()) {
Andrea Campanella52125412015-12-03 14:50:40 -0800370 hdata.badge = badgeData;
Simon Hunt5b3ff902015-08-27 09:46:27 -0700371 if (!host.subdue) {
372 api.unsupNode(hdata.id, less);
373 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700374 // TODO: further highlighting?
Simon Huntb3442482016-03-03 17:30:07 -0800375 } else {
376 $log.warn('HILITE: no host element:', host.id);
Simon Hunt94f7dae2015-08-26 17:40:59 -0700377 }
378 });
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700379
Simon Hunt94f7dae2015-08-26 17:40:59 -0700380 data.devices.forEach(function (device) {
Simon Hunte9343f32015-10-21 18:07:46 -0700381 var ddata = api.findNodeById(device.id),
382 badgeData = device.badge || null;
383
Simon Hunta1f1c022016-03-03 15:54:57 -0800384 if (ddata && ddata.el && !ddata.el.empty()) {
Simon Hunte9343f32015-10-21 18:07:46 -0700385 ddata.badge = badgeData;
Simon Hunt5b3ff902015-08-27 09:46:27 -0700386 if (!device.subdue) {
387 api.unsupNode(ddata.id, less);
388 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700389 // TODO: further highlighting?
Simon Huntb3442482016-03-03 17:30:07 -0800390 } else {
391 $log.warn('HILITE: no device element:', device.id);
Simon Hunt94f7dae2015-08-26 17:40:59 -0700392 }
393 });
394
395 data.links.forEach(function (link) {
396 var ldata = api.findLinkById(link.id),
397 lab = link.label,
Simon Huntd3ceffa2015-08-25 12:44:35 -0700398 units, portcls, magnitude;
Simon Hunta1f1c022016-03-03 15:54:57 -0800399
400 if (ldata && ldata.el && !ldata.el.empty()) {
Simon Hunt5b3ff902015-08-27 09:46:27 -0700401 if (!link.subdue) {
402 api.unsupLink(ldata.key, less);
403 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700404 ldata.el.classed(link.css, true);
Simon Huntd3ceffa2015-08-25 12:44:35 -0700405 ldata.label = lab;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700406
Simon Hunt5b3ff902015-08-27 09:46:27 -0700407 // TODO: this needs to be pulled out into traffic overlay
Simon Huntd3ceffa2015-08-25 12:44:35 -0700408 // inject additional styling for port-based traffic
409 if (fs.endsWith(lab, 'bps')) {
410 units = lab.substring(lab.length-4);
411 portcls = 'port-traffic-' + units;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700412
Simon Huntd3ceffa2015-08-25 12:44:35 -0700413 // for GBps
414 if (units.substring(0,1) === 'G') {
415 magnitude = fs.parseBitRate(lab);
416 if (magnitude >= 9) {
417 portcls += '-choked'
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700418 }
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700419 }
Simon Huntd3ceffa2015-08-25 12:44:35 -0700420 ldata.el.classed(portcls, true);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700421 }
Simon Huntb3442482016-03-03 17:30:07 -0800422 } else {
423 $log.warn('HILITE: no link element:', link.id);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700424 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700425 });
Simon Huntfb940112015-07-29 18:36:35 -0700426
Simon Hunt743a8492015-08-25 16:18:19 -0700427 api.updateNodes();
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700428 api.updateLinks();
Simon Huntfb940112015-07-29 18:36:35 -0700429 }
430
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700431 // ========================================================================
432
Simon Hunt72e44bf2015-07-21 21:34:20 -0700433 angular.module('ovTopo')
434 .factory('TopoOverlayService',
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800435 ['$log', '$timeout', 'FnService', 'GlyphService', 'WebSocketService',
436 'NavService', 'TopoPanelService',
Simon Hunt72e44bf2015-07-21 21:34:20 -0700437
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800438 function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _ns_, _tps_) {
Simon Hunt72e44bf2015-07-21 21:34:20 -0700439 $log = _$log_;
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800440 $timeout = _$timeout_;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700441 fs = _fs_;
442 gs = _gs_;
Simon Hunte05cae42015-07-23 17:35:24 -0700443 wss = _wss_;
Simon Hunt3a0598f2015-08-04 19:59:04 -0700444 ns = _ns_;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700445 tps = _tps_;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700446
447 return {
448 register: register,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700449 setApi: setApi,
Simon Hunt72e44bf2015-07-21 21:34:20 -0700450 list: list,
Simon Hunt441c9ae2017-02-03 18:22:31 -0800451 overlaysAcceptingIntents: overlaysAcceptingIntents,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700452 augmentRbset: augmentRbset,
453 mkGlyphId: mkGlyphId,
Simon Huntfb940112015-07-29 18:36:35 -0700454 tbSelection: tbSelection,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700455 installButtons: installButtons,
456 addDetailButton: addDetailButton,
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700457 resetOnToolbarDestroy: function () { reset = true; },
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700458 hooks: {
459 escape: escapeHook,
460 emptySelect: emptySelectHook,
461 singleSelect: singleSelectHook,
Simon Hunt584e92d2015-08-24 11:27:22 -0700462 multiSelect: multiSelectHook,
463 mouseOver: mouseOverHook,
Simon Hunt5c1a9382016-06-01 19:35:35 -0700464 mouseOut: mouseOutHook,
Simon Hunt8419efd2017-01-12 12:36:28 -0800465 modifyLinkData: modifyLinkDataHook,
466 showIntent: showIntentHook
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700467 },
468
469 showHighlights: showHighlights
Simon Hunt72e44bf2015-07-21 21:34:20 -0700470 }
471 }]);
472
473}());