blob: b4073a040fa8613a3e83242d45544b02dcea1034 [file] [log] [blame]
Simon Hunt737c89f2015-01-28 12:23:19 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Simon Hunt737c89f2015-01-28 12:23:19 -08003 *
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/*
Simon Hunt3a6eec02015-02-09 21:16:43 -080018 ONOS GUI -- Topology Force Module.
19 Visualization of the topology in an SVG layer, using a D3 Force Layout.
Simon Hunt737c89f2015-01-28 12:23:19 -080020 */
21
22(function () {
23 'use strict';
24
25 // injected refs
Simon Hunt8d22c4b2015-08-06 16:24:43 -070026 var $log, $timeout, fs, sus, ts, flash, wss, tov,
Andrea Campanella732ea832017-02-06 09:25:59 -080027 tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg, tpis;
Simon Huntac4c6f72015-02-03 19:50:53 -080028
Simon Hunte2d9dc72017-08-10 15:21:04 -070029 // function to be replaced by the localization bundle function
30 var topoLion = function (x) {
31 return '#tfs#' + x + '#';
32 };
33
Simon Huntac4c6f72015-02-03 19:50:53 -080034 // configuration
Simon Hunt1894d792015-02-04 17:09:20 -080035 var linkConfig = {
36 light: {
Simon Huntf44d7262016-06-14 14:46:56 -070037 baseColor: '#939598',
Simon Hunt1894d792015-02-04 17:09:20 -080038 inColor: '#66f',
Steven Burrows1c2a9682017-07-14 16:52:46 +010039 outColor: '#f00',
Simon Hunt1894d792015-02-04 17:09:20 -080040 },
41 dark: {
Simon Huntf44d7262016-06-14 14:46:56 -070042 // TODO : theme
43 baseColor: '#939598',
Simon Hunt1894d792015-02-04 17:09:20 -080044 inColor: '#66f',
Steven Burrows1c2a9682017-07-14 16:52:46 +010045 outColor: '#f00',
Simon Hunt1894d792015-02-04 17:09:20 -080046 },
47 inWidth: 12,
Steven Burrows1c2a9682017-07-14 16:52:46 +010048 outWidth: 10,
Simon Hunt1894d792015-02-04 17:09:20 -080049 };
50
Simon Hunt737c89f2015-01-28 12:23:19 -080051 // internal state
Steven Burrows1c2a9682017-07-14 16:52:46 +010052 var settings, // merged default settings and options
53 force, // force layout object
54 drag, // drag behavior handler
Simon Hunt737c89f2015-01-28 12:23:19 -080055 network = {
56 nodes: [],
57 links: [],
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -070058 linksByDevice: {},
Simon Hunt737c89f2015-01-28 12:23:19 -080059 lookup: {},
Steven Burrows1c2a9682017-07-14 16:52:46 +010060 revLinkToKey: {},
Simon Huntac4c6f72015-02-03 19:50:53 -080061 },
Steven Burrows1c2a9682017-07-14 16:52:46 +010062 lu, // shorthand for lookup
63 rlk, // shorthand for revLinktoKey
64 showHosts = false, // whether hosts are displayed
65 showOffline = true, // whether offline devices are displayed
66 nodeLock = false, // whether nodes can be dragged or not (locked)
67 fTimer, // timer for delayed force layout
68 fNodesTimer, // timer for delayed nodes update
69 fLinksTimer, // timer for delayed links update
70 dim, // the dimensions of the force layout [w,h]
71 linkNums = [], // array of link number labels
72 devIconDim = 36, // node target dimension
73 devIconDimMin = 20, // node minimum dimension when zoomed out
74 devIconDimMax = 40, // node maximum dimension when zoomed in
Steven Burrowsf17f0ab2017-04-11 11:03:58 -070075 portLabelDim = 30;
Simon Hunt737c89f2015-01-28 12:23:19 -080076
77 // SVG elements;
Bri Prebilic Cole80401762015-07-16 11:36:18 -070078 var linkG, linkLabelG, numLinkLblsG, portLabelG, nodeG;
Simon Hunt737c89f2015-01-28 12:23:19 -080079
80 // D3 selections;
81 var link, linkLabel, node;
82
83 // default settings for force layout
84 var defaultSettings = {
85 gravity: 0.4,
86 friction: 0.7,
87 charge: {
88 // note: key is node.class
89 device: -8000,
90 host: -5000,
Steven Burrows1c2a9682017-07-14 16:52:46 +010091 _def_: -12000,
Simon Hunt737c89f2015-01-28 12:23:19 -080092 },
93 linkDistance: {
94 // note: key is link.type
95 direct: 100,
96 optical: 120,
97 hostLink: 3,
Steven Burrows1c2a9682017-07-14 16:52:46 +010098 _def_: 50,
Simon Hunt737c89f2015-01-28 12:23:19 -080099 },
100 linkStrength: {
101 // note: key is link.type
102 // range: {0.0 ... 1.0}
Steven Burrows1c2a9682017-07-14 16:52:46 +0100103 // direct: 1.0,
104 // optical: 1.0,
105 // hostLink: 1.0,
106 _def_: 1.0,
107 },
Simon Hunt737c89f2015-01-28 12:23:19 -0800108 };
109
110
Simon Huntac4c6f72015-02-03 19:50:53 -0800111 // ==========================
112 // === EVENT HANDLERS
113
114 function addDevice(data) {
115 var id = data.id,
116 d;
117
Simon Hunt1894d792015-02-04 17:09:20 -0800118 uplink.showNoDevs(false);
Simon Huntac4c6f72015-02-03 19:50:53 -0800119
120 // although this is an add device event, if we already have the
121 // device, treat it as an update instead..
Simon Hunt1894d792015-02-04 17:09:20 -0800122 if (lu[id]) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800123 updateDevice(data);
124 return;
125 }
126
Simon Hunt3a6eec02015-02-09 21:16:43 -0800127 d = tms.createDeviceNode(data);
Simon Huntac4c6f72015-02-03 19:50:53 -0800128 network.nodes.push(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800129 lu[id] = d;
Simon Huntac4c6f72015-02-03 19:50:53 -0800130 updateNodes();
Simon Hunta17fa672015-08-19 18:42:22 -0700131 fStart();
Simon Huntac4c6f72015-02-03 19:50:53 -0800132 }
133
134 function updateDevice(data) {
135 var id = data.id,
Simon Hunt1894d792015-02-04 17:09:20 -0800136 d = lu[id],
Simon Huntac4c6f72015-02-03 19:50:53 -0800137 wasOnline;
138
139 if (d) {
140 wasOnline = d.online;
141 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800142 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800143 sendUpdateMeta(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800144 }
145 updateNodes();
146 if (wasOnline !== d.online) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800147 tms.findAttachedLinks(d.id).forEach(restyleLinkElement);
Simon Hunt5724fb42015-02-05 16:59:40 -0800148 updateOfflineVisibility(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800149 }
Simon Huntac4c6f72015-02-03 19:50:53 -0800150 }
151 }
152
Simon Hunt1894d792015-02-04 17:09:20 -0800153 function removeDevice(data) {
154 var id = data.id,
155 d = lu[id];
156 if (d) {
157 removeDeviceElement(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800158 }
159 }
160
161 function addHost(data) {
162 var id = data.id,
Simon Hunt12c79ed2017-09-12 11:58:44 -0700163 d;
Simon Hunt1894d792015-02-04 17:09:20 -0800164
165 // although this is an add host event, if we already have the
166 // host, treat it as an update instead..
167 if (lu[id]) {
168 updateHost(data);
169 return;
170 }
171
Simon Hunt3a6eec02015-02-09 21:16:43 -0800172 d = tms.createHostNode(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800173 network.nodes.push(d);
174 lu[id] = d;
Simon Hunt1894d792015-02-04 17:09:20 -0800175 updateNodes();
176
Simon Hunt12c79ed2017-09-12 11:58:44 -0700177 function mkLinkKey(devId, devPort) {
178 return id + '/0-' + devId + '/' + devPort;
179 }
180
181 // need to handle possible multiple links (multi-homed host)
182 d.links = [];
183 data.allCps.forEach(function (cp) {
184 var linkData = {
185 key: mkLinkKey(cp.device, cp.port),
186 dst: cp.device,
187 dstPort: cp.port,
188 };
189 d.links.push(linkData);
190
191 var lnk = tms.createHostLink(id, cp.device, cp.port);
192 if (lnk) {
193 network.links.push(lnk);
194 lu[linkData.key] = lnk;
195 }
196 });
197
198 if (d.links.length) {
Simon Hunt1894d792015-02-04 17:09:20 -0800199 updateLinks();
200 }
Simon Hunta17fa672015-08-19 18:42:22 -0700201 fStart();
Simon Hunt1894d792015-02-04 17:09:20 -0800202 }
203
204 function updateHost(data) {
205 var id = data.id,
206 d = lu[id];
207 if (d) {
208 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800209 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800210 sendUpdateMeta(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800211 }
212 updateNodes();
Simon Hunt1894d792015-02-04 17:09:20 -0800213 }
214 }
215
Simon Hunt95d56fd2015-11-12 11:06:44 -0800216 function moveHost(data) {
217 var id = data.id,
218 d = lu[id],
219 lnk;
Simon Hunt12c79ed2017-09-12 11:58:44 -0700220
Simon Hunt95d56fd2015-11-12 11:06:44 -0800221 if (d) {
222 // first remove the old host link
Simon Hunt12c79ed2017-09-12 11:58:44 -0700223 // FIXME: what if the host has multiple links??????
Simon Hunt95d56fd2015-11-12 11:06:44 -0800224 removeLinkElement(d.linkData);
225
226 // merge new data
227 angular.extend(d, data);
228 if (tms.positionNode(d, true)) {
229 sendUpdateMeta(d);
230 }
231
232 // now create a new host link
Simon Hunt12c79ed2017-09-12 11:58:44 -0700233 // TODO: verify this is the APPROPRIATE host link
234 lnk = tms.createHostLink(id, data.cp.device, data.cp.port);
Simon Hunt95d56fd2015-11-12 11:06:44 -0800235 if (lnk) {
Simon Hunt95d56fd2015-11-12 11:06:44 -0800236 network.links.push(lnk);
Simon Hunt12c79ed2017-09-12 11:58:44 -0700237 lu[lnk.key] = lnk;
238
239 d.links.push({
240 key: id + '/0-' + cp.device + '/' + cp.port,
241 dst: data.cp.device,
242 dstPort: data.cp.port,
243 });
Simon Hunt95d56fd2015-11-12 11:06:44 -0800244 }
245
246 updateNodes();
247 updateLinks();
248 fResume();
249 }
250 }
251
Simon Hunt1894d792015-02-04 17:09:20 -0800252 function removeHost(data) {
253 var id = data.id,
254 d = lu[id];
255 if (d) {
256 removeHostElement(d, true);
Simon Hunt1894d792015-02-04 17:09:20 -0800257 }
258 }
259
260 function addLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800261 var result = tms.findLink(data, 'add'),
Simon Hunt1894d792015-02-04 17:09:20 -0800262 bad = result.badLogic,
263 d = result.ldata;
264
265 if (bad) {
Simon Hunteb18f522016-01-28 19:22:23 -0800266 $log.debug(bad + ': ' + link.id);
Simon Hunt1894d792015-02-04 17:09:20 -0800267 return;
268 }
269
270 if (d) {
271 // we already have a backing store link for src/dst nodes
272 addLinkUpdate(d, data);
273 return;
274 }
275
276 // no backing store link yet
Simon Hunt3a6eec02015-02-09 21:16:43 -0800277 d = tms.createLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800278 if (d) {
279 network.links.push(d);
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700280 aggregateLink(d, data);
Simon Hunt1894d792015-02-04 17:09:20 -0800281 lu[d.key] = d;
282 updateLinks();
Simon Hunta17fa672015-08-19 18:42:22 -0700283 fStart();
Simon Hunt1894d792015-02-04 17:09:20 -0800284 }
285 }
286
287 function updateLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800288 var result = tms.findLink(data, 'update'),
Simon Hunt1894d792015-02-04 17:09:20 -0800289 bad = result.badLogic;
290 if (bad) {
Simon Hunteb18f522016-01-28 19:22:23 -0800291 $log.debug(bad + ': ' + link.id);
Simon Hunt1894d792015-02-04 17:09:20 -0800292 return;
293 }
Simon Hunteb18f522016-01-28 19:22:23 -0800294 result.updateWith(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800295 }
296
297 function removeLink(data) {
Simon Hunta4242de2015-02-24 17:11:55 -0800298 var result = tms.findLink(data, 'remove');
299
300 if (!result.badLogic) {
301 result.removeRawLink();
Simon Hunt1894d792015-02-04 17:09:20 -0800302 }
Simon Hunt1894d792015-02-04 17:09:20 -0800303 }
304
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700305 function topoStartDone(data) {
306 // called when the initial barrage of data has been sent from server
307 uplink.topoStartDone();
308 }
309
Simon Hunt1894d792015-02-04 17:09:20 -0800310 // ========================
311
Simon Hunt94f7dae2015-08-26 17:40:59 -0700312 function nodeById(id) {
313 return lu[id];
314 }
315
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700316 function makeNodeKey(node1, node2) {
317 return node1 + '-' + node2;
318 }
319
320 function findNodePair(key, keyRev) {
321 if (network.linksByDevice[key]) {
322 return key;
323 } else if (network.linksByDevice[keyRev]) {
324 return keyRev;
325 } else {
326 return false;
327 }
328 }
329
330 function aggregateLink(ldata, link) {
331 var key = makeNodeKey(link.src, link.dst),
332 keyRev = makeNodeKey(link.dst, link.src),
333 found = findNodePair(key, keyRev);
334
335 if (found) {
336 network.linksByDevice[found].push(ldata);
337 ldata.devicePair = found;
338 } else {
Steven Burrows1c2a9682017-07-14 16:52:46 +0100339 network.linksByDevice[key] = [ldata];
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700340 ldata.devicePair = key;
341 }
342 }
343
Simon Hunt1894d792015-02-04 17:09:20 -0800344 function addLinkUpdate(ldata, link) {
345 // add link event, but we already have the reverse link installed
346 ldata.fromTarget = link;
Simon Huntdc6adea2015-02-09 22:29:36 -0800347 rlk[link.id] = ldata.key;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700348 // possible solution to el being undefined in restyleLinkElement:
Steven Burrows1c2a9682017-07-14 16:52:46 +0100349 // _updateLinks();
Simon Hunt1894d792015-02-04 17:09:20 -0800350 restyleLinkElement(ldata);
351 }
352
Simon Hunt1894d792015-02-04 17:09:20 -0800353
354 var widthRatio = 1.4,
355 linkScale = d3.scale.linear()
356 .domain([1, 12])
357 .range([widthRatio, 12 * widthRatio])
Simon Hunt5724fb42015-02-05 16:59:40 -0800358 .clamp(true),
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800359 allLinkTypes = 'direct indirect optical tunnel',
360 allLinkSubTypes = 'inactive not-permitted';
Simon Hunt1894d792015-02-04 17:09:20 -0800361
Simon Hunta142dd22015-02-12 22:07:51 -0800362 function restyleLinkElement(ldata, immediate) {
Simon Hunt1894d792015-02-04 17:09:20 -0800363 // this fn's job is to look at raw links and decide what svg classes
364 // need to be applied to the line element in the DOM
365 var th = ts.theme(),
366 el = ldata.el,
367 type = ldata.type(),
368 lw = ldata.linkWidth(),
Simon Hunta142dd22015-02-12 22:07:51 -0800369 online = ldata.online(),
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800370 modeCls = ldata.expected() ? 'inactive' : 'not-permitted',
Simon Hunta142dd22015-02-12 22:07:51 -0800371 delay = immediate ? 0 : 1000;
Simon Hunt1894d792015-02-04 17:09:20 -0800372
Simon Huntf44d7262016-06-14 14:46:56 -0700373 // NOTE: understand why el is sometimes undefined on addLink events...
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700374 // Investigated:
375 // el is undefined when it's a reverse link that is being added.
376 // updateLinks (which sets ldata.el) isn't called before this is called.
377 // Calling _updateLinks in addLinkUpdate fixes it, but there might be
378 // a more efficient way to fix it.
379 if (el && !el.empty()) {
Thomas Vachuskacb5016f2015-05-18 14:11:43 -0700380 el.classed('link', true);
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800381 el.classed(allLinkSubTypes, false);
382 el.classed(modeCls, !online);
Thomas Vachuskacb5016f2015-05-18 14:11:43 -0700383 el.classed(allLinkTypes, false);
384 if (type) {
385 el.classed(type, true);
386 }
387 el.transition()
388 .duration(delay)
389 .attr('stroke-width', linkScale(lw))
390 .attr('stroke', linkConfig[th].baseColor);
Simon Hunt1894d792015-02-04 17:09:20 -0800391 }
Simon Hunt1894d792015-02-04 17:09:20 -0800392 }
393
Simon Hunt1894d792015-02-04 17:09:20 -0800394 function removeLinkElement(d) {
395 var idx = fs.find(d.key, network.links, 'key'),
396 removed;
397 if (idx >=0) {
398 // remove from links array
399 removed = network.links.splice(idx, 1);
400 // remove from lookup cache
401 delete lu[removed[0].key];
402 updateLinks();
Simon Hunta17fa672015-08-19 18:42:22 -0700403 fResume();
Simon Hunt1894d792015-02-04 17:09:20 -0800404 }
405 }
406
407 function removeHostElement(d, upd) {
408 // first, remove associated hostLink...
409 removeLinkElement(d.linkData);
410
411 // remove hostLink bindings
412 delete lu[d.ingress];
413 delete lu[d.egress];
414
415 // remove from lookup cache
416 delete lu[d.id];
417 // remove from nodes array
418 var idx = fs.find(d.id, network.nodes);
419 network.nodes.splice(idx, 1);
420
421 // remove from SVG
422 // NOTE: upd is false if we were called from removeDeviceElement()
423 if (upd) {
424 updateNodes();
Simon Hunta17fa672015-08-19 18:42:22 -0700425 fResume();
Simon Hunt1894d792015-02-04 17:09:20 -0800426 }
427 }
428
429 function removeDeviceElement(d) {
Bri Prebilic Cole8d003782015-07-31 15:33:06 -0700430 var id = d.id,
431 idx;
Simon Hunt1894d792015-02-04 17:09:20 -0800432 // first, remove associated hosts and links..
Simon Huntdc6adea2015-02-09 22:29:36 -0800433 tms.findAttachedHosts(id).forEach(removeHostElement);
434 tms.findAttachedLinks(id).forEach(removeLinkElement);
Simon Hunt1894d792015-02-04 17:09:20 -0800435
436 // remove from lookup cache
437 delete lu[id];
438 // remove from nodes array
Bri Prebilic Cole8d003782015-07-31 15:33:06 -0700439 idx = fs.find(id, network.nodes);
440 if (idx > -1) {
441 network.nodes.splice(idx, 1);
442 }
Simon Hunt1894d792015-02-04 17:09:20 -0800443
444 if (!network.nodes.length) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800445 uplink.showNoDevs(true);
Simon Hunt1894d792015-02-04 17:09:20 -0800446 }
447
448 // remove from SVG
449 updateNodes();
Simon Hunta17fa672015-08-19 18:42:22 -0700450 fResume();
Simon Hunt1894d792015-02-04 17:09:20 -0800451 }
452
Simon Hunt5724fb42015-02-05 16:59:40 -0800453 function updateHostVisibility() {
Simon Hunt18bf9822015-02-12 17:35:45 -0800454 sus.visible(nodeG.selectAll('.host'), showHosts);
455 sus.visible(linkG.selectAll('.hostLink'), showHosts);
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800456 sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts);
Simon Hunt5724fb42015-02-05 16:59:40 -0800457 }
458
459 function updateOfflineVisibility(dev) {
460 function updDev(d, show) {
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800461 var b;
Simon Hunt18bf9822015-02-12 17:35:45 -0800462 sus.visible(d.el, show);
Simon Hunt5724fb42015-02-05 16:59:40 -0800463
Simon Huntdc6adea2015-02-09 22:29:36 -0800464 tms.findAttachedLinks(d.id).forEach(function (link) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800465 b = show && ((link.type() !== 'hostLink') || showHosts);
Simon Hunt18bf9822015-02-12 17:35:45 -0800466 sus.visible(link.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800467 });
Simon Huntdc6adea2015-02-09 22:29:36 -0800468 tms.findAttachedHosts(d.id).forEach(function (host) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800469 b = show && showHosts;
Simon Hunt18bf9822015-02-12 17:35:45 -0800470 sus.visible(host.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800471 });
472 }
473
474 if (dev) {
475 // updating a specific device that just toggled off/on-line
476 updDev(dev, dev.online || showOffline);
477 } else {
478 // updating all offline devices
Simon Huntdc6adea2015-02-09 22:29:36 -0800479 tms.findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800480 updDev(d, showOffline);
481 });
482 }
483 }
484
Simon Hunt1894d792015-02-04 17:09:20 -0800485
Simon Hunt445e8152015-02-06 13:00:12 -0800486 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800487 var metaUi = {},
488 ll;
489
Simon Hunt445e8152015-02-06 13:00:12 -0800490 // if we are not clearing the position data (unpinning),
Simon Huntfd7106c2016-02-09 15:05:26 -0800491 // attach the x, y, (and equivalent longitude, latitude)...
Simon Hunt445e8152015-02-06 13:00:12 -0800492 if (!clearPos) {
Simon Hunt3a6eec02015-02-09 21:16:43 -0800493 ll = tms.lngLatFromCoord([d.x, d.y]);
Simon Huntfd7106c2016-02-09 15:05:26 -0800494 metaUi = {
495 x: d.x,
496 y: d.y,
497 equivLoc: {
498 lng: ll[0],
Steven Burrows1c2a9682017-07-14 16:52:46 +0100499 lat: ll[1],
500 },
Simon Huntfd7106c2016-02-09 15:05:26 -0800501 };
Simon Hunt1894d792015-02-04 17:09:20 -0800502 }
503 d.metaUi = metaUi;
Simon Hunt237676b52015-03-10 19:04:26 -0700504 wss.sendEvent('updateMeta', {
Simon Hunt1894d792015-02-04 17:09:20 -0800505 id: d.id,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700506 class: d.class,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100507 memento: metaUi,
Simon Hunt1894d792015-02-04 17:09:20 -0800508 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800509 }
510
Simon Hunt1894d792015-02-04 17:09:20 -0800511
Simon Huntac4c6f72015-02-03 19:50:53 -0800512 function mkSvgClass(d) {
513 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
514 }
515
Simon Hunt5724fb42015-02-05 16:59:40 -0800516 function vis(b) {
Simon Hunt1603c692017-08-10 19:53:35 -0700517 return topoLion(b ? 'visible' : 'hidden');
Simon Hunt5724fb42015-02-05 16:59:40 -0800518 }
519
Simon Huntfcbde892015-04-16 12:05:28 -0700520 function toggleHosts(x) {
521 var kev = (x === 'keyev'),
522 on = kev ? !showHosts : !!x;
523
524 showHosts = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800525 updateHostVisibility();
Simon Hunte2d9dc72017-08-10 15:21:04 -0700526 flash.flash(topoLion('hosts') + ' ' + vis(on));
Simon Huntfcbde892015-04-16 12:05:28 -0700527 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800528 }
529
Simon Huntfcbde892015-04-16 12:05:28 -0700530 function toggleOffline(x) {
531 var kev = (x === 'keyev'),
532 on = kev ? !showOffline : !!x;
533
534 showOffline = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800535 updateOfflineVisibility();
Simon Hunte2d9dc72017-08-10 15:21:04 -0700536 flash.flash(topoLion('fl_offline_devices') + ' ' + vis(on));
Simon Huntfcbde892015-04-16 12:05:28 -0700537 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800538 }
539
540 function cycleDeviceLabels() {
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700541 flash.flash(td3.incDevLabIndex());
Simon Huntdc6adea2015-02-09 22:29:36 -0800542 tms.findDevices().forEach(function (d) {
Simon Hunta4242de2015-02-24 17:11:55 -0800543 td3.updateDeviceLabel(d);
Simon Hunt1c367112015-02-05 18:02:46 -0800544 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800545 }
546
Simon Hunt10618f62017-06-15 19:30:52 -0700547 function cycleHostLabels() {
548 flash.flash(td3.incHostLabIndex());
549 tms.findHosts().forEach(function (d) {
550 td3.updateHostLabel(d);
551 });
552 }
553
Simon Hunt445e8152015-02-06 13:00:12 -0800554 function unpin() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800555 var hov = tss.hovered();
556 if (hov) {
557 sendUpdateMeta(hov, true);
558 hov.fixed = false;
559 hov.el.classed('fixed', false);
Simon Hunt445e8152015-02-06 13:00:12 -0800560 fResume();
561 }
562 }
563
Simon Hunta142dd22015-02-12 22:07:51 -0800564 function showMastership(masterId) {
565 if (!masterId) {
566 restoreLayerState();
567 } else {
568 showMastershipFor(masterId);
569 }
570 }
571
572 function restoreLayerState() {
573 // NOTE: this level of indirection required, for when we have
574 // the layer filter functionality re-implemented
575 suppressLayers(false);
576 }
577
578 function showMastershipFor(id) {
579 suppressLayers(true);
580 node.each(function (n) {
581 if (n.master === id) {
Simon Hunt743a8492015-08-25 16:18:19 -0700582 n.el.classed('suppressedmax', false);
Simon Hunta142dd22015-02-12 22:07:51 -0800583 }
584 });
585 }
586
Simon Hunt743a8492015-08-25 16:18:19 -0700587 function supAmt(less) {
Steven Burrows1c2a9682017-07-14 16:52:46 +0100588 return less ? 'suppressed' : 'suppressedmax';
Simon Hunt743a8492015-08-25 16:18:19 -0700589 }
590
591 function suppressLayers(b, less) {
592 var cls = supAmt(less);
593 node.classed(cls, b);
594 link.classed(cls, b);
595 }
596
597 function unsuppressNode(id, less) {
598 var cls = supAmt(less);
599 node.each(function (n) {
600 if (n.id === id) {
601 n.el.classed(cls, false);
602 }
603 });
604 }
605
Simon Hunt94f7dae2015-08-26 17:40:59 -0700606 function unsuppressLink(key, less) {
Simon Hunt743a8492015-08-25 16:18:19 -0700607 var cls = supAmt(less);
608 link.each(function (n) {
Simon Hunt94f7dae2015-08-26 17:40:59 -0700609 if (n.key === key) {
Simon Hunt743a8492015-08-25 16:18:19 -0700610 n.el.classed(cls, false);
611 }
612 });
Simon Hunta142dd22015-02-12 22:07:51 -0800613 }
Simon Hunt445e8152015-02-06 13:00:12 -0800614
Simon Hunt86b7c882015-04-02 23:06:08 -0700615 function showBadLinks() {
616 var badLinks = tms.findBadLinks();
Simon Hunte2d9dc72017-08-10 15:21:04 -0700617 flash.flash(topoLion('fl_bad_links') + ': ' + badLinks.length);
Simon Hunt86b7c882015-04-02 23:06:08 -0700618 $log.debug('Bad Link List (' + badLinks.length + '):');
619 badLinks.forEach(function (d) {
620 $log.debug('bad link: (' + d.bad + ') ' + d.key, d);
621 if (d.el) {
622 d.el.attr('stroke-width', linkScale(2.8))
623 .attr('stroke', 'red');
624 }
625 });
626 // back to normal after 2 seconds...
627 $timeout(updateLinks, 2000);
628 }
629
Steven Burrowsf17f0ab2017-04-11 11:03:58 -0700630 function deviceScale() {
631 var scale = uplink.zoomer().scale(),
632 dim = devIconDim,
633 multiplier = 1;
634
635 if (dim * scale < devIconDimMin) {
636 multiplier = devIconDimMin / (dim * scale);
637 } else if (dim * scale > devIconDimMax) {
638 multiplier = devIconDimMax / (dim * scale);
639 }
640
641 return multiplier;
642 }
643
644 function linkWidthScale(scale) {
645 var scale = uplink.zoomer().scale();
646 return linkScale(widthRatio) / scale;
647 }
648
649 function portLabelScale(scale) {
650 var scale = uplink.zoomer().scale();
651 return portLabelDim / (portLabelDim * scale);
652 }
653
654 function setNodeScale(scale) {
655 // Scale the network nodes
656 _.each(network.nodes, function (node) {
657 if (node.class === 'host') {
658 node.el.selectAll('g').style('transform', 'scale(' + deviceScale(scale) + ')');
659 node.el.selectAll('text').style('transform', 'scale(' + deviceScale(scale) + ')');
660 return;
661 }
662 node.el.selectAll('*')
663 .style('transform', 'scale(' + deviceScale(scale) + ')');
664 });
665
666 // Scale the network links
667 _.each(network.links, function (link) {
668 link.el.style('stroke-width', linkWidthScale(scale) + 'px');
669 });
670
671 d3.select('#topo-portLabels')
672 .selectAll('.portLabel')
673 .selectAll('*')
674 .style('transform', 'scale(' + portLabelScale(scale) + ')');
675 }
676
Simon Huntfd7106c2016-02-09 15:05:26 -0800677 function resetAllLocations() {
678 tms.resetAllLocations();
679 updateNodes();
680 tick(); // force nodes to be redrawn in their new locations
Simon Hunte2d9dc72017-08-10 15:21:04 -0700681 flash.flash(topoLion('fl_reset_node_locations'));
Simon Huntfd7106c2016-02-09 15:05:26 -0800682 }
683
Simon Hunt5724fb42015-02-05 16:59:40 -0800684 // ==========================================
685
Simon Huntac4c6f72015-02-03 19:50:53 -0800686 function updateNodes() {
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700687 if (fNodesTimer) {
688 $timeout.cancel(fNodesTimer);
689 }
690 fNodesTimer = $timeout(_updateNodes, 150);
691 }
692
Simon Hunta17fa672015-08-19 18:42:22 -0700693 // IMPLEMENTATION NOTE: _updateNodes() should NOT stop, start, or resume
694 // the force layout; that needs to be determined and implemented elsewhere
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700695 function _updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800696 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800697 node = nodeG.selectAll('.node')
698 .data(network.nodes, function (d) { return d.id; });
699
Simon Hunt1894d792015-02-04 17:09:20 -0800700 // operate on existing nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800701 node.filter('.device').each(td3.deviceExisting);
702 node.filter('.host').each(td3.hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800703
704 // operate on entering nodes:
705 var entering = node.enter()
706 .append('g')
707 .attr({
708 id: function (d) { return sus.safeId(d.id); },
709 class: mkSvgClass,
Simon Hunta17fa672015-08-19 18:42:22 -0700710 transform: function (d) {
711 // Need to guard against NaN here ??
712 return sus.translate(d.x, d.y);
713 },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100714 opacity: 0,
Simon Huntac4c6f72015-02-03 19:50:53 -0800715 })
716 .call(drag)
Simon Hunt08f841d02015-02-10 14:39:20 -0800717 .on('mouseover', tss.nodeMouseOver)
718 .on('mouseout', tss.nodeMouseOut)
Simon Huntac4c6f72015-02-03 19:50:53 -0800719 .transition()
720 .attr('opacity', 1);
721
Simon Hunt1894d792015-02-04 17:09:20 -0800722 // augment entering nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800723 entering.filter('.device').each(td3.deviceEnter);
724 entering.filter('.host').each(td3.hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -0800725
Simon Hunt51056592015-02-03 21:48:07 -0800726 // operate on both existing and new nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800727 td3.updateDeviceColors();
Simon Huntac4c6f72015-02-03 19:50:53 -0800728
729 // operate on exiting nodes:
730 // Note that the node is removed after 2 seconds.
731 // Sub element animations should be shorter than 2 seconds.
732 var exiting = node.exit()
733 .transition()
734 .duration(2000)
735 .style('opacity', 0)
736 .remove();
737
Simon Hunt1894d792015-02-04 17:09:20 -0800738 // exiting node specifics:
Simon Hunta4242de2015-02-24 17:11:55 -0800739 exiting.filter('.host').each(td3.hostExit);
740 exiting.filter('.device').each(td3.deviceExit);
Simon Huntac4c6f72015-02-03 19:50:53 -0800741 }
742
Simon Hunt51056592015-02-03 21:48:07 -0800743 // ==========================
Simon Hunt1894d792015-02-04 17:09:20 -0800744
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700745 function getDefaultPos(link) {
746 return {
747 x1: link.source.x,
748 y1: link.source.y,
749 x2: link.target.x,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100750 y2: link.target.y,
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700751 };
752 }
753
754 // returns amount of adjustment along the normal for given link
755 function amt(numLinks, linkIdx) {
756 var gap = 6;
757 return (linkIdx - ((numLinks - 1) / 2)) * gap;
758 }
759
760 function calcMovement(d, amt, flipped) {
761 var pos = getDefaultPos(d),
762 mult = flipped ? -amt : amt,
763 dx = pos.x2 - pos.x1,
764 dy = pos.y2 - pos.y1,
765 length = Math.sqrt((dx * dx) + (dy * dy));
766
767 return {
768 x1: pos.x1 + (mult * dy / length),
769 y1: pos.y1 + (mult * -dx / length),
770 x2: pos.x2 + (mult * dy / length),
Steven Burrows1c2a9682017-07-14 16:52:46 +0100771 y2: pos.y2 + (mult * -dx / length),
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700772 };
773 }
774
775 function calcPosition() {
776 var lines = this,
777 linkSrcId;
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700778 linkNums = [];
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700779 lines.each(function (d) {
780 if (d.type() === 'hostLink') {
781 d.position = getDefaultPos(d);
782 }
783 });
784
785 function normalizeLinkSrc(link) {
786 // ensure source device is consistent across set of links
787 // temporary measure until link modeling is refactored
788 if (!linkSrcId) {
789 linkSrcId = link.source.id;
790 return false;
791 }
792
793 return link.source.id !== linkSrcId;
794 }
795
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700796 angular.forEach(network.linksByDevice, function (linkArr, key) {
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700797 var numLinks = linkArr.length,
798 link;
799
800 if (numLinks === 1) {
801 link = linkArr[0];
802 link.position = getDefaultPos(link);
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700803 link.position.multiLink = false;
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700804 } else if (numLinks >= 5) {
805 // this code is inefficient, in the future the way links
806 // are modeled will be changed
807 angular.forEach(linkArr, function (link) {
808 link.position = getDefaultPos(link);
809 link.position.multiLink = true;
810 });
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700811 linkNums.push({
812 id: key,
813 num: numLinks,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100814 linkCoords: linkArr[0].position,
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700815 });
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700816 } else {
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700817 linkSrcId = null;
818 angular.forEach(linkArr, function (link, index) {
819 var offsetAmt = amt(numLinks, index),
820 needToFlip = normalizeLinkSrc(link);
821 link.position = calcMovement(link, offsetAmt, needToFlip);
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700822 link.position.multiLink = false;
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700823 });
824 }
825 });
826 }
827
Simon Hunt1894d792015-02-04 17:09:20 -0800828 function updateLinks() {
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700829 if (fLinksTimer) {
830 $timeout.cancel(fLinksTimer);
831 }
832 fLinksTimer = $timeout(_updateLinks, 150);
833 }
834
Simon Hunta17fa672015-08-19 18:42:22 -0700835 // IMPLEMENTATION NOTE: _updateLinks() should NOT stop, start, or resume
836 // the force layout; that needs to be determined and implemented elsewhere
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700837 function _updateLinks() {
Simon Hunt1894d792015-02-04 17:09:20 -0800838 var th = ts.theme();
839
840 link = linkG.selectAll('.link')
841 .data(network.links, function (d) { return d.key; });
842
843 // operate on existing links:
Simon Huntd5264122015-02-25 10:17:43 -0800844 link.each(function (d) {
845 // this is supposed to be an existing link, but we have observed
846 // occasions (where links are deleted and added rapidly?) where
847 // the DOM element has not been defined. So protect against that...
848 if (d.el) {
849 restyleLinkElement(d, true);
850 }
851 });
Simon Hunt1894d792015-02-04 17:09:20 -0800852
853 // operate on entering links:
854 var entering = link.enter()
855 .append('line')
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700856 .call(calcPosition)
Simon Hunt1894d792015-02-04 17:09:20 -0800857 .attr({
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700858 x1: function (d) { return d.position.x1; },
859 y1: function (d) { return d.position.y1; },
860 x2: function (d) { return d.position.x2; },
861 y2: function (d) { return d.position.y2; },
Simon Hunt1894d792015-02-04 17:09:20 -0800862 stroke: linkConfig[th].inColor,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100863 'stroke-width': linkConfig.inWidth,
Simon Hunt1894d792015-02-04 17:09:20 -0800864 });
865
866 // augment links
Simon Hunta4242de2015-02-24 17:11:55 -0800867 entering.each(td3.linkEntering);
Simon Hunt1894d792015-02-04 17:09:20 -0800868
869 // operate on both existing and new links:
Steven Burrows1c2a9682017-07-14 16:52:46 +0100870 // link.each(...)
Simon Hunt1894d792015-02-04 17:09:20 -0800871
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700872 // add labels for how many links are in a thick line
873 td3.applyNumLinkLabels(linkNums, numLinkLblsG);
874
Simon Hunt1894d792015-02-04 17:09:20 -0800875 // apply or remove labels
Simon Hunta4242de2015-02-24 17:11:55 -0800876 td3.applyLinkLabels();
Simon Hunt1894d792015-02-04 17:09:20 -0800877
878 // operate on exiting links:
879 link.exit()
880 .attr('stroke-dasharray', '3 3')
Simon Hunt5724fb42015-02-05 16:59:40 -0800881 .attr('stroke', linkConfig[th].outColor)
Simon Hunt1894d792015-02-04 17:09:20 -0800882 .style('opacity', 0.5)
883 .transition()
884 .duration(1500)
885 .attr({
886 'stroke-dasharray': '3 12',
Steven Burrows1c2a9682017-07-14 16:52:46 +0100887 'stroke-width': linkConfig.outWidth,
Simon Hunt1894d792015-02-04 17:09:20 -0800888 })
889 .style('opacity', 0.0)
890 .remove();
Simon Hunt1894d792015-02-04 17:09:20 -0800891 }
892
Simon Huntac4c6f72015-02-03 19:50:53 -0800893
894 // ==========================
Simon Hunt737c89f2015-01-28 12:23:19 -0800895 // force layout tick function
Simon Hunt737c89f2015-01-28 12:23:19 -0800896
Simon Hunt5724fb42015-02-05 16:59:40 -0800897 function fResume() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800898 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800899 force.resume();
900 }
901 }
902
903 function fStart() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800904 if (!tos.isOblique()) {
Simon Hunta17fa672015-08-19 18:42:22 -0700905 if (fTimer) {
906 $timeout.cancel(fTimer);
907 }
908 fTimer = $timeout(function () {
Steven Burrows1c2a9682017-07-14 16:52:46 +0100909 $log.debug('Starting force-layout');
Simon Hunta17fa672015-08-19 18:42:22 -0700910 force.start();
911 }, 200);
Simon Hunt5724fb42015-02-05 16:59:40 -0800912 }
913 }
914
915 var tickStuff = {
916 nodeAttr: {
Simon Hunta17fa672015-08-19 18:42:22 -0700917 transform: function (d) {
918 var dx = isNaN(d.x) ? 0 : d.x,
919 dy = isNaN(d.y) ? 0 : d.y;
920 return sus.translate(dx, dy);
Steven Burrows1c2a9682017-07-14 16:52:46 +0100921 },
Simon Hunt5724fb42015-02-05 16:59:40 -0800922 },
923 linkAttr: {
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700924 x1: function (d) { return d.position.x1; },
925 y1: function (d) { return d.position.y1; },
926 x2: function (d) { return d.position.x2; },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100927 y2: function (d) { return d.position.y2; },
Simon Hunt5724fb42015-02-05 16:59:40 -0800928 },
929 linkLabelAttr: {
930 transform: function (d) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800931 var lnk = tms.findLinkById(d.key);
Simon Hunt5724fb42015-02-05 16:59:40 -0800932 if (lnk) {
Carmelo Casconed01eda62016-08-02 10:19:15 -0700933 return td3.transformLabel(lnk.position, d.key);
Simon Hunt5724fb42015-02-05 16:59:40 -0800934 }
Steven Burrows1c2a9682017-07-14 16:52:46 +0100935 },
936 },
Simon Hunt5724fb42015-02-05 16:59:40 -0800937 };
938
939 function tick() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800940 // guard against null (which can happen when our view pages out)...
Bri Prebilic Cole8d003782015-07-31 15:33:06 -0700941 if (node && node.size()) {
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700942 node.attr(tickStuff.nodeAttr);
943 }
Bri Prebilic Cole8d003782015-07-31 15:33:06 -0700944 if (link && link.size()) {
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700945 link.call(calcPosition)
946 .attr(tickStuff.linkAttr);
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700947 td3.applyNumLinkLabels(linkNums, numLinkLblsG);
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700948 }
Bri Prebilic Cole8d003782015-07-31 15:33:06 -0700949 if (linkLabel && linkLabel.size()) {
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700950 linkLabel.attr(tickStuff.linkLabelAttr);
951 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800952 }
953
954
Simon Huntac4c6f72015-02-03 19:50:53 -0800955 // ==========================
956 // === MOUSE GESTURE HANDLERS
957
Simon Hunt205099e2015-02-07 13:12:01 -0800958 function zoomingOrPanning(ev) {
959 return ev.metaKey || ev.altKey;
Simon Hunt445e8152015-02-06 13:00:12 -0800960 }
961
962 function atDragEnd(d) {
963 // once we've finished moving, pin the node in position
964 d.fixed = true;
965 d3.select(this).classed('fixed', true);
966 sendUpdateMeta(d);
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700967 tss.clickConsumed(true);
Simon Hunt445e8152015-02-06 13:00:12 -0800968 }
969
970 // predicate that indicates when dragging is active
971 function dragEnabled() {
972 var ev = d3.event.sourceEvent;
973 // nodeLock means we aren't allowing nodes to be dragged...
Simon Hunt205099e2015-02-07 13:12:01 -0800974 return !nodeLock && !zoomingOrPanning(ev);
Simon Hunt445e8152015-02-06 13:00:12 -0800975 }
976
977 // predicate that indicates when clicking is active
978 function clickEnabled() {
979 return true;
980 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800981
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700982 // =============================================
983 // function entry points for overlay module
Simon Huntf542d842015-02-11 16:20:33 -0800984
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700985 // TODO: find an automatic way of tracking via the "showHighlights" events
Simon Hunte50829c2015-06-09 08:39:28 -0700986 var allTrafficClasses = 'primary secondary optical animated ' +
Simon Hunt21281fd2017-03-30 22:28:28 -0700987 'port-traffic-green port-traffic-yellow port-traffic-orange ' +
988 'port-traffic-red';
Simon Huntf542d842015-02-11 16:20:33 -0800989
990 function clearLinkTrafficStyle() {
991 link.style('stroke-width', null)
992 .classed(allTrafficClasses, false);
993 }
994
995 function removeLinkLabels() {
996 network.links.forEach(function (d) {
997 d.label = '';
998 });
999 }
Simon Hunt737c89f2015-01-28 12:23:19 -08001000
Simon Hunte9343f32015-10-21 18:07:46 -07001001 function clearNodeDeco() {
1002 node.selectAll('g.badge').remove();
1003 }
1004
1005 function removeNodeBadges() {
1006 network.nodes.forEach(function (d) {
1007 d.badge = null;
1008 });
1009 }
1010
Simon Hunta4242de2015-02-24 17:11:55 -08001011 function updateLinkLabelModel() {
1012 // create the backing data for showing labels..
1013 var data = [];
1014 link.each(function (d) {
1015 if (d.label) {
1016 data.push({
1017 id: 'lab-' + d.key,
1018 key: d.key,
1019 label: d.label,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001020 ldata: d,
Simon Hunta4242de2015-02-24 17:11:55 -08001021 });
1022 }
1023 });
1024
1025 linkLabel = linkLabelG.selectAll('.linkLabel')
1026 .data(data, function (d) { return d.id; });
1027 }
1028
Simon Hunt737c89f2015-01-28 12:23:19 -08001029 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -08001030 // Module definition
Simon Hunt737c89f2015-01-28 12:23:19 -08001031
Simon Huntdc6adea2015-02-09 22:29:36 -08001032 function mkModelApi(uplink) {
1033 return {
1034 projection: uplink.projection,
1035 network: network,
1036 restyleLinkElement: restyleLinkElement,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001037 removeLinkElement: removeLinkElement,
Simon Huntdc6adea2015-02-09 22:29:36 -08001038 };
1039 }
1040
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001041 function mkD3Api() {
Simon Hunta4242de2015-02-24 17:11:55 -08001042 return {
1043 node: function () { return node; },
1044 link: function () { return link; },
1045 linkLabel: function () { return linkLabel; },
1046 instVisible: function () { return tis.isVisible(); },
1047 posNode: tms.positionNode,
1048 showHosts: function () { return showHosts; },
1049 restyleLinkElement: restyleLinkElement,
Bri Prebilic Cole80401762015-07-16 11:36:18 -07001050 updateLinkLabelModel: updateLinkLabelModel,
Steven Burrowsf17f0ab2017-04-11 11:03:58 -07001051 linkConfig: function () { return linkConfig; },
1052 deviceScale: deviceScale,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001053 linkWidthScale: linkWidthScale,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -07001054 };
Simon Hunta4242de2015-02-24 17:11:55 -08001055 }
1056
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001057 function mkSelectApi() {
Simon Hunt08f841d02015-02-10 14:39:20 -08001058 return {
1059 node: function () { return node; },
1060 zoomingOrPanning: zoomingOrPanning,
Simon Hunt0c6b2d32015-03-26 17:46:29 -07001061 updateDeviceColors: td3.updateDeviceColors,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001062 deselectAllLinks: tls.deselectAllLinks,
Simon Hunt08f841d02015-02-10 14:39:20 -08001063 };
1064 }
1065
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001066 function mkTrafficApi() {
1067 return {
1068 hovered: tss.hovered,
1069 somethingSelected: tss.somethingSelected,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001070 selectOrder: tss.selectOrder,
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001071 };
1072 }
1073
1074 function mkOverlayApi() {
Simon Huntf542d842015-02-11 16:20:33 -08001075 return {
Simon Hunte9343f32015-10-21 18:07:46 -07001076 clearNodeDeco: clearNodeDeco,
1077 removeNodeBadges: removeNodeBadges,
Simon Huntf542d842015-02-11 16:20:33 -08001078 clearLinkTrafficStyle: clearLinkTrafficStyle,
1079 removeLinkLabels: removeLinkLabels,
Simon Hunt743a8492015-08-25 16:18:19 -07001080 findLinkById: tms.findLinkById,
Simon Hunt94f7dae2015-08-26 17:40:59 -07001081 findNodeById: nodeById,
Simon Huntf542d842015-02-11 16:20:33 -08001082 updateLinks: updateLinks,
Simon Hunt743a8492015-08-25 16:18:19 -07001083 updateNodes: updateNodes,
1084 supLayers: suppressLayers,
1085 unsupNode: unsuppressNode,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001086 unsupLink: unsuppressLink,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -07001087 };
Simon Huntf542d842015-02-11 16:20:33 -08001088 }
1089
Simon Huntc3c5b672015-02-20 11:32:13 -08001090 function mkObliqueApi(uplink, fltr) {
Simon Hunt96f88c62015-02-19 17:57:25 -08001091 return {
Steven Burrows1c2a9682017-07-14 16:52:46 +01001092 force: function () { return force; },
Simon Huntc3c5b672015-02-20 11:32:13 -08001093 zoomLayer: uplink.zoomLayer,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001094 nodeGBBox: function () { return nodeG.node().getBBox(); },
Simon Hunt96f88c62015-02-19 17:57:25 -08001095 node: function () { return node; },
Simon Huntc3c5b672015-02-20 11:32:13 -08001096 link: function () { return link; },
1097 linkLabel: function () { return linkLabel; },
1098 nodes: function () { return network.nodes; },
1099 tickStuff: tickStuff,
1100 nodeLock: function (b) {
1101 var old = nodeLock;
1102 nodeLock = b;
1103 return old;
1104 },
1105 opacifyMap: uplink.opacifyMap,
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -07001106 inLayer: fltr.inLayer,
Bri Prebilic Cole80401762015-07-16 11:36:18 -07001107 calcLinkPos: calcPosition,
1108 applyNumLinkLabels: function () {
1109 td3.applyNumLinkLabels(linkNums, numLinkLblsG);
Steven Burrows1c2a9682017-07-14 16:52:46 +01001110 },
Simon Hunt96f88c62015-02-19 17:57:25 -08001111 };
1112 }
1113
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001114 function mkFilterApi() {
Simon Hunteb0fa052015-02-17 19:20:28 -08001115 return {
1116 node: function () { return node; },
Steven Burrows1c2a9682017-07-14 16:52:46 +01001117 link: function () { return link; },
Simon Hunteb0fa052015-02-17 19:20:28 -08001118 };
1119 }
1120
Simon Hunt9e2104c2015-02-26 10:48:59 -08001121 function mkLinkApi(svg, uplink) {
Simon Huntfb8ea1f2015-02-24 21:38:09 -08001122 return {
1123 svg: svg,
Simon Huntfb8ea1f2015-02-24 21:38:09 -08001124 zoomer: uplink.zoomer(),
1125 network: network,
Simon Hunt1a5301e2015-02-25 15:31:25 -08001126 portLabelG: function () { return portLabelG; },
Steven Burrows1c2a9682017-07-14 16:52:46 +01001127 showHosts: function () { return showHosts; },
Simon Huntfb8ea1f2015-02-24 21:38:09 -08001128 };
1129 }
1130
Simon Huntf51bf462016-06-29 16:22:57 -07001131 function updateLinksAndNodes() {
1132 updateLinks();
1133 updateNodes();
1134 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +01001135
Simon Hunte2d9dc72017-08-10 15:21:04 -07001136 // invoked after the localization bundle has been received from the server
1137 function setLionBundle(bundle) {
1138 topoLion = bundle;
1139 td3.setLionBundle(bundle);
1140 fltr.setLionBundle(bundle);
1141 tls.setLionBundle(bundle);
Simon Hunt1603c692017-08-10 19:53:35 -07001142 tos.setLionBundle(bundle);
1143 tov.setLionBundle(bundle);
Simon Huntcaed0412017-08-12 13:49:17 -07001144 tss.setLionBundle(bundle);
Simon Hunte2d9dc72017-08-10 15:21:04 -07001145 }
1146
Simon Hunt737c89f2015-01-28 12:23:19 -08001147 angular.module('ovTopo')
1148 .factory('TopoForceService',
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -07001149 ['$log', '$timeout', 'FnService', 'SvgUtilService',
Simon Hunt86b7c882015-04-02 23:06:08 -07001150 'ThemeService', 'FlashService', 'WebSocketService',
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001151 'TopoOverlayService', 'TopoInstService', 'TopoModelService',
Simon Hunta4242de2015-02-24 17:11:55 -08001152 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
Simon Huntfb8ea1f2015-02-24 21:38:09 -08001153 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
Andrea Campanella732ea832017-02-06 09:25:59 -08001154 'TopoProtectedIntentsService',
Simon Hunt737c89f2015-01-28 12:23:19 -08001155
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001156 function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_, _tov_,
Andrea Campanella732ea832017-02-06 09:25:59 -08001157 _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_, _tpis_) {
Simon Hunt737c89f2015-01-28 12:23:19 -08001158 $log = _$log_;
Simon Hunt86b7c882015-04-02 23:06:08 -07001159 $timeout = _$timeout_;
Simon Hunt1894d792015-02-04 17:09:20 -08001160 fs = _fs_;
Simon Hunt737c89f2015-01-28 12:23:19 -08001161 sus = _sus_;
Simon Huntac4c6f72015-02-03 19:50:53 -08001162 ts = _ts_;
Simon Hunt5724fb42015-02-05 16:59:40 -08001163 flash = _flash_;
Simon Hunt237676b52015-03-10 19:04:26 -07001164 wss = _wss_;
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001165 tov = _tov_;
Simon Huntac4c6f72015-02-03 19:50:53 -08001166 tis = _tis_;
Simon Hunt3a6eec02015-02-09 21:16:43 -08001167 tms = _tms_;
Simon Hunta4242de2015-02-24 17:11:55 -08001168 td3 = _td3_;
Simon Hunt08f841d02015-02-10 14:39:20 -08001169 tss = _tss_;
Simon Huntf542d842015-02-11 16:20:33 -08001170 tts = _tts_;
Simon Hunt96f88c62015-02-19 17:57:25 -08001171 tos = _tos_;
Simon Hunteb0fa052015-02-17 19:20:28 -08001172 fltr = _fltr_;
Simon Huntfb8ea1f2015-02-24 21:38:09 -08001173 tls = _tls_;
Andrea Campanella732ea832017-02-06 09:25:59 -08001174 tpis = _tpis_;
Simon Hunt737c89f2015-01-28 12:23:19 -08001175
Simon Huntf51bf462016-06-29 16:22:57 -07001176 ts.addListener(updateLinksAndNodes);
Simon Hunta142dd22015-02-12 22:07:51 -08001177
Simon Hunt737c89f2015-01-28 12:23:19 -08001178 // forceG is the SVG group to display the force layout in
Simon Huntdc6adea2015-02-09 22:29:36 -08001179 // uplink is the api from the main topo source file
Simon Hunt3a6eec02015-02-09 21:16:43 -08001180 // dim is the initial dimensions of the SVG as [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -08001181 // opts are, well, optional :)
Simon Hunt3ab20282015-02-26 20:32:19 -08001182 function initForce(_svg_, forceG, _uplink_, _dim_, opts) {
Simon Hunt1894d792015-02-04 17:09:20 -08001183 uplink = _uplink_;
Simon Hunt3a6eec02015-02-09 21:16:43 -08001184 dim = _dim_;
Simon Hunt3ab20282015-02-26 20:32:19 -08001185 svg = _svg_;
1186
1187 lu = network.lookup;
1188 rlk = network.revLinkToKey;
Simon Hunt3a6eec02015-02-09 21:16:43 -08001189
1190 $log.debug('initForce().. dim = ' + dim);
1191
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001192 tov.setApi(mkOverlayApi(), tss);
Simon Huntdc6adea2015-02-09 22:29:36 -08001193 tms.initModel(mkModelApi(uplink), dim);
Steven Burrowsf17f0ab2017-04-11 11:03:58 -07001194 td3.initD3(mkD3Api(), uplink.zoomer());
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001195 tss.initSelect(mkSelectApi());
1196 tts.initTraffic(mkTrafficApi());
Andrea Campanella732ea832017-02-06 09:25:59 -08001197 tpis.initProtectedIntents(mkTrafficApi());
Simon Huntc3c5b672015-02-20 11:32:13 -08001198 tos.initOblique(mkObliqueApi(uplink, fltr));
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001199 fltr.initFilter(mkFilterApi());
Simon Hunt9e2104c2015-02-26 10:48:59 -08001200 tls.initLink(mkLinkApi(svg, uplink), td3);
Simon Hunta11b4eb2015-01-28 16:20:50 -08001201
Simon Hunt737c89f2015-01-28 12:23:19 -08001202 settings = angular.extend({}, defaultSettings, opts);
1203
1204 linkG = forceG.append('g').attr('id', 'topo-links');
1205 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
Bri Prebilic Cole80401762015-07-16 11:36:18 -07001206 numLinkLblsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
Simon Hunt737c89f2015-01-28 12:23:19 -08001207 nodeG = forceG.append('g').attr('id', 'topo-nodes');
Simon Hunt1a5301e2015-02-25 15:31:25 -08001208 portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
Simon Hunt737c89f2015-01-28 12:23:19 -08001209
1210 link = linkG.selectAll('.link');
1211 linkLabel = linkLabelG.selectAll('.linkLabel');
1212 node = nodeG.selectAll('.node');
1213
1214 force = d3.layout.force()
Simon Hunt3a6eec02015-02-09 21:16:43 -08001215 .size(dim)
Simon Hunt737c89f2015-01-28 12:23:19 -08001216 .nodes(network.nodes)
1217 .links(network.links)
1218 .gravity(settings.gravity)
1219 .friction(settings.friction)
1220 .charge(settings.charge._def_)
1221 .linkDistance(settings.linkDistance._def_)
1222 .linkStrength(settings.linkStrength._def_)
1223 .on('tick', tick);
1224
1225 drag = sus.createDragBehavior(force,
Simon Hunt08f841d02015-02-10 14:39:20 -08001226 tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt737c89f2015-01-28 12:23:19 -08001227 }
1228
Simon Hunt3a6eec02015-02-09 21:16:43 -08001229 function newDim(_dim_) {
1230 dim = _dim_;
1231 force.size(dim);
1232 tms.newDim(dim);
Simon Hunt737c89f2015-01-28 12:23:19 -08001233 }
1234
Simon Hunt3a6eec02015-02-09 21:16:43 -08001235 function destroyForce() {
Simon Hunt3ab20282015-02-26 20:32:19 -08001236 force.stop();
1237
Simon Huntfb8ea1f2015-02-24 21:38:09 -08001238 tls.destroyLink();
Simon Hunt96f88c62015-02-19 17:57:25 -08001239 tos.destroyOblique();
Simon Huntf542d842015-02-11 16:20:33 -08001240 tts.destroyTraffic();
Andrea Campanella732ea832017-02-06 09:25:59 -08001241 tpis.destroyProtectedIntents();
Simon Huntf542d842015-02-11 16:20:33 -08001242 tss.destroySelect();
Simon Hunta4242de2015-02-24 17:11:55 -08001243 td3.destroyD3();
Simon Huntf542d842015-02-11 16:20:33 -08001244 tms.destroyModel();
Simon Hunt8d22c4b2015-08-06 16:24:43 -07001245 // note: no need to destroy overlay service
Simon Huntf51bf462016-06-29 16:22:57 -07001246 ts.removeListener(updateLinksAndNodes);
Simon Hunt3ab20282015-02-26 20:32:19 -08001247
1248 // clean up the DOM
1249 svg.selectAll('g').remove();
1250 svg.selectAll('defs').remove();
1251
1252 // clean up internal state
1253 network.nodes = [];
1254 network.links = [];
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -07001255 network.linksByDevice = {};
Simon Hunt3ab20282015-02-26 20:32:19 -08001256 network.lookup = {};
1257 network.revLinkToKey = {};
1258
Bri Prebilic Cole80401762015-07-16 11:36:18 -07001259 linkNums = [];
1260
1261 linkG = linkLabelG = numLinkLblsG = nodeG = portLabelG = null;
Simon Hunt3ab20282015-02-26 20:32:19 -08001262 link = linkLabel = node = null;
1263 force = drag = null;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -07001264
1265 // clean up $timeout promises
Simon Hunta17fa672015-08-19 18:42:22 -07001266 if (fTimer) {
1267 $timeout.cancel(fTimer);
1268 }
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -07001269 if (fNodesTimer) {
1270 $timeout.cancel(fNodesTimer);
1271 }
1272 if (fLinksTimer) {
1273 $timeout.cancel(fLinksTimer);
1274 }
Simon Hunt3a6eec02015-02-09 21:16:43 -08001275 }
1276
Simon Hunt737c89f2015-01-28 12:23:19 -08001277 return {
1278 initForce: initForce,
Simon Hunt3a6eec02015-02-09 21:16:43 -08001279 newDim: newDim,
1280 destroyForce: destroyForce,
Simon Huntac4c6f72015-02-03 19:50:53 -08001281
Simon Hunta4242de2015-02-24 17:11:55 -08001282 updateDeviceColors: td3.updateDeviceColors,
Simon Hunt5724fb42015-02-05 16:59:40 -08001283 toggleHosts: toggleHosts,
Simon Hunt9e2104c2015-02-26 10:48:59 -08001284 togglePorts: tls.togglePorts,
Simon Hunt5724fb42015-02-05 16:59:40 -08001285 toggleOffline: toggleOffline,
1286 cycleDeviceLabels: cycleDeviceLabels,
Simon Hunt10618f62017-06-15 19:30:52 -07001287 cycleHostLabels: cycleHostLabels,
Simon Hunt445e8152015-02-06 13:00:12 -08001288 unpin: unpin,
Simon Hunta142dd22015-02-12 22:07:51 -08001289 showMastership: showMastership,
Simon Hunt86b7c882015-04-02 23:06:08 -07001290 showBadLinks: showBadLinks,
Steven Burrowsf17f0ab2017-04-11 11:03:58 -07001291 setNodeScale: setNodeScale,
Simon Huntac4c6f72015-02-03 19:50:53 -08001292
Simon Huntfd7106c2016-02-09 15:05:26 -08001293 resetAllLocations: resetAllLocations,
Simon Huntac4c6f72015-02-03 19:50:53 -08001294 addDevice: addDevice,
Simon Hunt1894d792015-02-04 17:09:20 -08001295 updateDevice: updateDevice,
1296 removeDevice: removeDevice,
1297 addHost: addHost,
1298 updateHost: updateHost,
Simon Hunt95d56fd2015-11-12 11:06:44 -08001299 moveHost: moveHost,
Simon Hunt1894d792015-02-04 17:09:20 -08001300 removeHost: removeHost,
1301 addLink: addLink,
1302 updateLink: updateLink,
Simon Hunt4a6b54b2015-10-27 22:08:25 -07001303 removeLink: removeLink,
Steven Burrows1c2a9682017-07-14 16:52:46 +01001304 topoStartDone: topoStartDone,
Simon Hunte2d9dc72017-08-10 15:21:04 -07001305
1306 setLionBundle: setLionBundle,
Simon Hunt737c89f2015-01-28 12:23:19 -08001307 };
1308 }]);
1309}());