blob: b7cc07cd3b8a868173d8b99c38e35ea3b470e2a5 [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
Thomas Vachuskac616e172018-04-17 16:57:12 -0700114 function mergeNodeData(o, n) {
115 angular.extend(o, n);
116 if (!n.location) {
117 delete o.location;
118 }
119 }
120
Simon Huntac4c6f72015-02-03 19:50:53 -0800121 function addDevice(data) {
122 var id = data.id,
123 d;
124
Simon Hunt1894d792015-02-04 17:09:20 -0800125 uplink.showNoDevs(false);
Simon Huntac4c6f72015-02-03 19:50:53 -0800126
127 // although this is an add device event, if we already have the
128 // device, treat it as an update instead..
Simon Hunt1894d792015-02-04 17:09:20 -0800129 if (lu[id]) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800130 updateDevice(data);
131 return;
132 }
133
Simon Hunt3a6eec02015-02-09 21:16:43 -0800134 d = tms.createDeviceNode(data);
Simon Huntac4c6f72015-02-03 19:50:53 -0800135 network.nodes.push(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800136 lu[id] = d;
Simon Huntac4c6f72015-02-03 19:50:53 -0800137 updateNodes();
Simon Hunta17fa672015-08-19 18:42:22 -0700138 fStart();
Simon Huntac4c6f72015-02-03 19:50:53 -0800139 }
140
141 function updateDevice(data) {
142 var id = data.id,
Simon Hunt1894d792015-02-04 17:09:20 -0800143 d = lu[id],
Simon Huntac4c6f72015-02-03 19:50:53 -0800144 wasOnline;
145
146 if (d) {
147 wasOnline = d.online;
Thomas Vachuskac616e172018-04-17 16:57:12 -0700148 mergeNodeData(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800149 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800150 sendUpdateMeta(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800151 }
152 updateNodes();
Thomas Vachuskac616e172018-04-17 16:57:12 -0700153 tick();
Simon Huntac4c6f72015-02-03 19:50:53 -0800154 if (wasOnline !== d.online) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800155 tms.findAttachedLinks(d.id).forEach(restyleLinkElement);
Simon Hunt5724fb42015-02-05 16:59:40 -0800156 updateOfflineVisibility(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800157 }
Thomas Vachuskac616e172018-04-17 16:57:12 -0700158 fStart();
Simon Huntac4c6f72015-02-03 19:50:53 -0800159 }
160 }
161
Simon Hunt1894d792015-02-04 17:09:20 -0800162 function removeDevice(data) {
163 var id = data.id,
164 d = lu[id];
165 if (d) {
166 removeDeviceElement(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800167 }
168 }
169
170 function addHost(data) {
171 var id = data.id,
Simon Hunt12c79ed2017-09-12 11:58:44 -0700172 d;
Simon Hunt1894d792015-02-04 17:09:20 -0800173
174 // although this is an add host event, if we already have the
175 // host, treat it as an update instead..
176 if (lu[id]) {
177 updateHost(data);
178 return;
179 }
180
Simon Hunt3a6eec02015-02-09 21:16:43 -0800181 d = tms.createHostNode(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800182 network.nodes.push(d);
183 lu[id] = d;
Simon Hunt1894d792015-02-04 17:09:20 -0800184 updateNodes();
185
Simon Hunt12c79ed2017-09-12 11:58:44 -0700186 // need to handle possible multiple links (multi-homed host)
Simon Hunt7df764f2017-09-14 21:26:14 -0700187 createHostLinks(data.allCps, d);
Simon Hunt12c79ed2017-09-12 11:58:44 -0700188
189 if (d.links.length) {
Simon Hunt1894d792015-02-04 17:09:20 -0800190 updateLinks();
191 }
Simon Hunta17fa672015-08-19 18:42:22 -0700192 fStart();
Simon Hunt1894d792015-02-04 17:09:20 -0800193 }
194
195 function updateHost(data) {
196 var id = data.id,
197 d = lu[id];
198 if (d) {
Thomas Vachuskac616e172018-04-17 16:57:12 -0700199 mergeNodeData(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800200 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800201 sendUpdateMeta(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800202 }
203 updateNodes();
Thomas Vachuskac616e172018-04-17 16:57:12 -0700204 tick();
205 fStart();
Simon Hunt1894d792015-02-04 17:09:20 -0800206 }
207 }
208
Simon Hunt7df764f2017-09-14 21:26:14 -0700209 function createHostLinks(cps, model) {
210 model.links = [];
211 cps.forEach(function (cp) {
212 var linkData = {
213 key: model.id + '/0-' + cp.device + '/' + cp.port,
214 dst: cp.device,
215 dstPort: cp.port,
216 };
217 model.links.push(linkData);
218
219 var lnk = tms.createHostLink(model.id, cp.device, cp.port);
220 if (lnk) {
221 network.links.push(lnk);
222 lu[linkData.key] = lnk;
223 }
224 });
225 }
226
Simon Hunt95d56fd2015-11-12 11:06:44 -0800227 function moveHost(data) {
228 var id = data.id,
Simon Hunt7df764f2017-09-14 21:26:14 -0700229 d = lu[id];
Simon Hunt12c79ed2017-09-12 11:58:44 -0700230
Simon Hunt95d56fd2015-11-12 11:06:44 -0800231 if (d) {
Simon Hunt7df764f2017-09-14 21:26:14 -0700232 removeAllLinkElements(d.links);
Simon Hunt95d56fd2015-11-12 11:06:44 -0800233
234 // merge new data
235 angular.extend(d, data);
236 if (tms.positionNode(d, true)) {
237 sendUpdateMeta(d);
238 }
239
Simon Hunt7df764f2017-09-14 21:26:14 -0700240 // now create new host link(s)
241 createHostLinks(data.allCps, d);
Simon Hunt95d56fd2015-11-12 11:06:44 -0800242
243 updateNodes();
244 updateLinks();
245 fResume();
246 }
247 }
248
Simon Hunt1894d792015-02-04 17:09:20 -0800249 function removeHost(data) {
250 var id = data.id,
251 d = lu[id];
252 if (d) {
253 removeHostElement(d, true);
Simon Hunt1894d792015-02-04 17:09:20 -0800254 }
255 }
256
257 function addLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800258 var result = tms.findLink(data, 'add'),
Simon Hunt1894d792015-02-04 17:09:20 -0800259 bad = result.badLogic,
260 d = result.ldata;
261
262 if (bad) {
Simon Hunteb18f522016-01-28 19:22:23 -0800263 $log.debug(bad + ': ' + link.id);
Simon Hunt1894d792015-02-04 17:09:20 -0800264 return;
265 }
266
267 if (d) {
268 // we already have a backing store link for src/dst nodes
269 addLinkUpdate(d, data);
270 return;
271 }
272
273 // no backing store link yet
Simon Hunt3a6eec02015-02-09 21:16:43 -0800274 d = tms.createLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800275 if (d) {
276 network.links.push(d);
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700277 aggregateLink(d, data);
Simon Hunt1894d792015-02-04 17:09:20 -0800278 lu[d.key] = d;
279 updateLinks();
Simon Hunta17fa672015-08-19 18:42:22 -0700280 fStart();
Simon Hunt1894d792015-02-04 17:09:20 -0800281 }
282 }
283
284 function updateLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800285 var result = tms.findLink(data, 'update'),
Simon Hunt1894d792015-02-04 17:09:20 -0800286 bad = result.badLogic;
287 if (bad) {
Simon Hunteb18f522016-01-28 19:22:23 -0800288 $log.debug(bad + ': ' + link.id);
Simon Hunt1894d792015-02-04 17:09:20 -0800289 return;
290 }
Simon Hunteb18f522016-01-28 19:22:23 -0800291 result.updateWith(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800292 }
293
294 function removeLink(data) {
Simon Hunta4242de2015-02-24 17:11:55 -0800295 var result = tms.findLink(data, 'remove');
296
297 if (!result.badLogic) {
298 result.removeRawLink();
Simon Hunt1894d792015-02-04 17:09:20 -0800299 }
Simon Hunt1894d792015-02-04 17:09:20 -0800300 }
301
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700302 function topoStartDone(data) {
303 // called when the initial barrage of data has been sent from server
304 uplink.topoStartDone();
305 }
306
Simon Hunt1894d792015-02-04 17:09:20 -0800307 // ========================
308
Simon Hunt94f7dae2015-08-26 17:40:59 -0700309 function nodeById(id) {
310 return lu[id];
311 }
312
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700313 function makeNodeKey(node1, node2) {
314 return node1 + '-' + node2;
315 }
316
317 function findNodePair(key, keyRev) {
318 if (network.linksByDevice[key]) {
319 return key;
320 } else if (network.linksByDevice[keyRev]) {
321 return keyRev;
322 } else {
323 return false;
324 }
325 }
326
327 function aggregateLink(ldata, link) {
328 var key = makeNodeKey(link.src, link.dst),
329 keyRev = makeNodeKey(link.dst, link.src),
330 found = findNodePair(key, keyRev);
331
332 if (found) {
333 network.linksByDevice[found].push(ldata);
334 ldata.devicePair = found;
335 } else {
Steven Burrows1c2a9682017-07-14 16:52:46 +0100336 network.linksByDevice[key] = [ldata];
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700337 ldata.devicePair = key;
338 }
339 }
340
Simon Hunt1894d792015-02-04 17:09:20 -0800341 function addLinkUpdate(ldata, link) {
342 // add link event, but we already have the reverse link installed
343 ldata.fromTarget = link;
Simon Huntdc6adea2015-02-09 22:29:36 -0800344 rlk[link.id] = ldata.key;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700345 // possible solution to el being undefined in restyleLinkElement:
Steven Burrows1c2a9682017-07-14 16:52:46 +0100346 // _updateLinks();
Simon Hunt1894d792015-02-04 17:09:20 -0800347 restyleLinkElement(ldata);
348 }
349
Simon Hunt1894d792015-02-04 17:09:20 -0800350
351 var widthRatio = 1.4,
352 linkScale = d3.scale.linear()
353 .domain([1, 12])
354 .range([widthRatio, 12 * widthRatio])
Simon Hunt5724fb42015-02-05 16:59:40 -0800355 .clamp(true),
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800356 allLinkTypes = 'direct indirect optical tunnel',
357 allLinkSubTypes = 'inactive not-permitted';
Simon Hunt1894d792015-02-04 17:09:20 -0800358
Simon Hunta142dd22015-02-12 22:07:51 -0800359 function restyleLinkElement(ldata, immediate) {
Simon Hunt1894d792015-02-04 17:09:20 -0800360 // this fn's job is to look at raw links and decide what svg classes
361 // need to be applied to the line element in the DOM
362 var th = ts.theme(),
363 el = ldata.el,
364 type = ldata.type(),
365 lw = ldata.linkWidth(),
Simon Hunta142dd22015-02-12 22:07:51 -0800366 online = ldata.online(),
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800367 modeCls = ldata.expected() ? 'inactive' : 'not-permitted',
Simon Hunta142dd22015-02-12 22:07:51 -0800368 delay = immediate ? 0 : 1000;
Simon Hunt1894d792015-02-04 17:09:20 -0800369
Simon Huntf44d7262016-06-14 14:46:56 -0700370 // NOTE: understand why el is sometimes undefined on addLink events...
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700371 // Investigated:
372 // el is undefined when it's a reverse link that is being added.
373 // updateLinks (which sets ldata.el) isn't called before this is called.
374 // Calling _updateLinks in addLinkUpdate fixes it, but there might be
375 // a more efficient way to fix it.
376 if (el && !el.empty()) {
Thomas Vachuskacb5016f2015-05-18 14:11:43 -0700377 el.classed('link', true);
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800378 el.classed(allLinkSubTypes, false);
379 el.classed(modeCls, !online);
Thomas Vachuskacb5016f2015-05-18 14:11:43 -0700380 el.classed(allLinkTypes, false);
381 if (type) {
382 el.classed(type, true);
383 }
384 el.transition()
385 .duration(delay)
386 .attr('stroke-width', linkScale(lw))
387 .attr('stroke', linkConfig[th].baseColor);
Simon Hunt1894d792015-02-04 17:09:20 -0800388 }
Simon Hunt1894d792015-02-04 17:09:20 -0800389 }
390
Simon Hunt7df764f2017-09-14 21:26:14 -0700391 function removeAllLinkElements(links) {
392 links.forEach(function (lnk) {
393 removeLinkElement(lnk);
394 });
395 }
396
Simon Hunt1894d792015-02-04 17:09:20 -0800397 function removeLinkElement(d) {
398 var idx = fs.find(d.key, network.links, 'key'),
399 removed;
400 if (idx >=0) {
401 // remove from links array
402 removed = network.links.splice(idx, 1);
403 // remove from lookup cache
404 delete lu[removed[0].key];
405 updateLinks();
Simon Hunta17fa672015-08-19 18:42:22 -0700406 fResume();
Simon Hunt1894d792015-02-04 17:09:20 -0800407 }
408 }
409
410 function removeHostElement(d, upd) {
Simon Hunt1fb00552017-09-15 09:21:14 -0700411 // first, remove associated hostLink(s)...
412 removeAllLinkElements(d.links);
Simon Hunt1894d792015-02-04 17:09:20 -0800413
414 // remove from lookup cache
415 delete lu[d.id];
416 // remove from nodes array
417 var idx = fs.find(d.id, network.nodes);
418 network.nodes.splice(idx, 1);
419
420 // remove from SVG
421 // NOTE: upd is false if we were called from removeDeviceElement()
422 if (upd) {
423 updateNodes();
Simon Hunta17fa672015-08-19 18:42:22 -0700424 fResume();
Simon Hunt1894d792015-02-04 17:09:20 -0800425 }
426 }
427
428 function removeDeviceElement(d) {
Bri Prebilic Cole8d003782015-07-31 15:33:06 -0700429 var id = d.id,
430 idx;
Simon Hunt1894d792015-02-04 17:09:20 -0800431 // first, remove associated hosts and links..
Simon Huntdc6adea2015-02-09 22:29:36 -0800432 tms.findAttachedHosts(id).forEach(removeHostElement);
433 tms.findAttachedLinks(id).forEach(removeLinkElement);
Simon Hunt1894d792015-02-04 17:09:20 -0800434
435 // remove from lookup cache
436 delete lu[id];
437 // remove from nodes array
Bri Prebilic Cole8d003782015-07-31 15:33:06 -0700438 idx = fs.find(id, network.nodes);
439 if (idx > -1) {
440 network.nodes.splice(idx, 1);
441 }
Simon Hunt1894d792015-02-04 17:09:20 -0800442
443 if (!network.nodes.length) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800444 uplink.showNoDevs(true);
Simon Hunt1894d792015-02-04 17:09:20 -0800445 }
446
447 // remove from SVG
448 updateNodes();
Simon Hunta17fa672015-08-19 18:42:22 -0700449 fResume();
Simon Hunt1894d792015-02-04 17:09:20 -0800450 }
451
Simon Hunt5724fb42015-02-05 16:59:40 -0800452 function updateHostVisibility() {
Simon Hunt18bf9822015-02-12 17:35:45 -0800453 sus.visible(nodeG.selectAll('.host'), showHosts);
454 sus.visible(linkG.selectAll('.hostLink'), showHosts);
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800455 sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts);
Simon Hunt5724fb42015-02-05 16:59:40 -0800456 }
457
458 function updateOfflineVisibility(dev) {
459 function updDev(d, show) {
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800460 var b;
Simon Hunt18bf9822015-02-12 17:35:45 -0800461 sus.visible(d.el, show);
Simon Hunt5724fb42015-02-05 16:59:40 -0800462
Simon Huntdc6adea2015-02-09 22:29:36 -0800463 tms.findAttachedLinks(d.id).forEach(function (link) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800464 b = show && ((link.type() !== 'hostLink') || showHosts);
Simon Hunt18bf9822015-02-12 17:35:45 -0800465 sus.visible(link.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800466 });
Simon Huntdc6adea2015-02-09 22:29:36 -0800467 tms.findAttachedHosts(d.id).forEach(function (host) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800468 b = show && showHosts;
Simon Hunt18bf9822015-02-12 17:35:45 -0800469 sus.visible(host.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800470 });
471 }
472
473 if (dev) {
474 // updating a specific device that just toggled off/on-line
475 updDev(dev, dev.online || showOffline);
476 } else {
477 // updating all offline devices
Simon Huntdc6adea2015-02-09 22:29:36 -0800478 tms.findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800479 updDev(d, showOffline);
480 });
481 }
482 }
483
Simon Hunt1894d792015-02-04 17:09:20 -0800484
Simon Hunt445e8152015-02-06 13:00:12 -0800485 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800486 var metaUi = {},
487 ll;
488
Simon Hunt445e8152015-02-06 13:00:12 -0800489 // if we are not clearing the position data (unpinning),
Simon Huntfd7106c2016-02-09 15:05:26 -0800490 // attach the x, y, (and equivalent longitude, latitude)...
Simon Hunt445e8152015-02-06 13:00:12 -0800491 if (!clearPos) {
Simon Hunt3a6eec02015-02-09 21:16:43 -0800492 ll = tms.lngLatFromCoord([d.x, d.y]);
Simon Huntfd7106c2016-02-09 15:05:26 -0800493 metaUi = {
494 x: d.x,
495 y: d.y,
496 equivLoc: {
497 lng: ll[0],
Steven Burrows1c2a9682017-07-14 16:52:46 +0100498 lat: ll[1],
499 },
Simon Huntfd7106c2016-02-09 15:05:26 -0800500 };
Simon Hunt1894d792015-02-04 17:09:20 -0800501 }
502 d.metaUi = metaUi;
Simon Hunt237676b52015-03-10 19:04:26 -0700503 wss.sendEvent('updateMeta', {
Simon Hunt1894d792015-02-04 17:09:20 -0800504 id: d.id,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700505 class: d.class,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100506 memento: metaUi,
Simon Hunt1894d792015-02-04 17:09:20 -0800507 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800508 }
509
Simon Hunt1894d792015-02-04 17:09:20 -0800510
Simon Huntac4c6f72015-02-03 19:50:53 -0800511 function mkSvgClass(d) {
512 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
513 }
514
Simon Hunt5724fb42015-02-05 16:59:40 -0800515 function vis(b) {
Simon Hunt1603c692017-08-10 19:53:35 -0700516 return topoLion(b ? 'visible' : 'hidden');
Simon Hunt5724fb42015-02-05 16:59:40 -0800517 }
518
Simon Huntfcbde892015-04-16 12:05:28 -0700519 function toggleHosts(x) {
520 var kev = (x === 'keyev'),
521 on = kev ? !showHosts : !!x;
522
523 showHosts = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800524 updateHostVisibility();
Simon Hunte2d9dc72017-08-10 15:21:04 -0700525 flash.flash(topoLion('hosts') + ' ' + vis(on));
Simon Huntfcbde892015-04-16 12:05:28 -0700526 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800527 }
528
Simon Huntfcbde892015-04-16 12:05:28 -0700529 function toggleOffline(x) {
530 var kev = (x === 'keyev'),
531 on = kev ? !showOffline : !!x;
532
533 showOffline = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800534 updateOfflineVisibility();
Simon Hunte2d9dc72017-08-10 15:21:04 -0700535 flash.flash(topoLion('fl_offline_devices') + ' ' + vis(on));
Simon Huntfcbde892015-04-16 12:05:28 -0700536 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800537 }
538
539 function cycleDeviceLabels() {
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700540 flash.flash(td3.incDevLabIndex());
Simon Huntdc6adea2015-02-09 22:29:36 -0800541 tms.findDevices().forEach(function (d) {
Simon Hunta4242de2015-02-24 17:11:55 -0800542 td3.updateDeviceLabel(d);
Simon Hunt1c367112015-02-05 18:02:46 -0800543 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800544 }
545
Simon Hunt10618f62017-06-15 19:30:52 -0700546 function cycleHostLabels() {
547 flash.flash(td3.incHostLabIndex());
548 tms.findHosts().forEach(function (d) {
549 td3.updateHostLabel(d);
550 });
551 }
552
Simon Hunt445e8152015-02-06 13:00:12 -0800553 function unpin() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800554 var hov = tss.hovered();
555 if (hov) {
556 sendUpdateMeta(hov, true);
557 hov.fixed = false;
558 hov.el.classed('fixed', false);
Simon Hunt445e8152015-02-06 13:00:12 -0800559 fResume();
560 }
561 }
562
Simon Hunta142dd22015-02-12 22:07:51 -0800563 function showMastership(masterId) {
564 if (!masterId) {
565 restoreLayerState();
566 } else {
567 showMastershipFor(masterId);
568 }
569 }
570
571 function restoreLayerState() {
572 // NOTE: this level of indirection required, for when we have
573 // the layer filter functionality re-implemented
574 suppressLayers(false);
575 }
576
577 function showMastershipFor(id) {
578 suppressLayers(true);
579 node.each(function (n) {
580 if (n.master === id) {
Simon Hunt743a8492015-08-25 16:18:19 -0700581 n.el.classed('suppressedmax', false);
Simon Hunta142dd22015-02-12 22:07:51 -0800582 }
583 });
584 }
585
Simon Hunt743a8492015-08-25 16:18:19 -0700586 function supAmt(less) {
Steven Burrows1c2a9682017-07-14 16:52:46 +0100587 return less ? 'suppressed' : 'suppressedmax';
Simon Hunt743a8492015-08-25 16:18:19 -0700588 }
589
590 function suppressLayers(b, less) {
591 var cls = supAmt(less);
592 node.classed(cls, b);
593 link.classed(cls, b);
594 }
595
596 function unsuppressNode(id, less) {
597 var cls = supAmt(less);
598 node.each(function (n) {
599 if (n.id === id) {
600 n.el.classed(cls, false);
601 }
602 });
603 }
604
Simon Hunt94f7dae2015-08-26 17:40:59 -0700605 function unsuppressLink(key, less) {
Simon Hunt743a8492015-08-25 16:18:19 -0700606 var cls = supAmt(less);
607 link.each(function (n) {
Simon Hunt94f7dae2015-08-26 17:40:59 -0700608 if (n.key === key) {
Simon Hunt743a8492015-08-25 16:18:19 -0700609 n.el.classed(cls, false);
610 }
611 });
Simon Hunta142dd22015-02-12 22:07:51 -0800612 }
Simon Hunt445e8152015-02-06 13:00:12 -0800613
Simon Hunt86b7c882015-04-02 23:06:08 -0700614 function showBadLinks() {
615 var badLinks = tms.findBadLinks();
Simon Hunte2d9dc72017-08-10 15:21:04 -0700616 flash.flash(topoLion('fl_bad_links') + ': ' + badLinks.length);
Simon Hunt86b7c882015-04-02 23:06:08 -0700617 $log.debug('Bad Link List (' + badLinks.length + '):');
618 badLinks.forEach(function (d) {
619 $log.debug('bad link: (' + d.bad + ') ' + d.key, d);
620 if (d.el) {
621 d.el.attr('stroke-width', linkScale(2.8))
622 .attr('stroke', 'red');
623 }
624 });
625 // back to normal after 2 seconds...
626 $timeout(updateLinks, 2000);
627 }
628
Steven Burrowsf17f0ab2017-04-11 11:03:58 -0700629 function deviceScale() {
630 var scale = uplink.zoomer().scale(),
631 dim = devIconDim,
632 multiplier = 1;
633
634 if (dim * scale < devIconDimMin) {
635 multiplier = devIconDimMin / (dim * scale);
636 } else if (dim * scale > devIconDimMax) {
637 multiplier = devIconDimMax / (dim * scale);
638 }
639
640 return multiplier;
641 }
642
643 function linkWidthScale(scale) {
644 var scale = uplink.zoomer().scale();
645 return linkScale(widthRatio) / scale;
646 }
647
648 function portLabelScale(scale) {
649 var scale = uplink.zoomer().scale();
650 return portLabelDim / (portLabelDim * scale);
651 }
652
653 function setNodeScale(scale) {
654 // Scale the network nodes
655 _.each(network.nodes, function (node) {
656 if (node.class === 'host') {
657 node.el.selectAll('g').style('transform', 'scale(' + deviceScale(scale) + ')');
658 node.el.selectAll('text').style('transform', 'scale(' + deviceScale(scale) + ')');
659 return;
660 }
661 node.el.selectAll('*')
662 .style('transform', 'scale(' + deviceScale(scale) + ')');
663 });
664
665 // Scale the network links
666 _.each(network.links, function (link) {
667 link.el.style('stroke-width', linkWidthScale(scale) + 'px');
668 });
669
670 d3.select('#topo-portLabels')
671 .selectAll('.portLabel')
672 .selectAll('*')
673 .style('transform', 'scale(' + portLabelScale(scale) + ')');
674 }
675
Simon Huntfd7106c2016-02-09 15:05:26 -0800676 function resetAllLocations() {
677 tms.resetAllLocations();
678 updateNodes();
679 tick(); // force nodes to be redrawn in their new locations
Simon Hunte2d9dc72017-08-10 15:21:04 -0700680 flash.flash(topoLion('fl_reset_node_locations'));
Simon Huntfd7106c2016-02-09 15:05:26 -0800681 }
682
Simon Hunt5724fb42015-02-05 16:59:40 -0800683 // ==========================================
684
Simon Huntac4c6f72015-02-03 19:50:53 -0800685 function updateNodes() {
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700686 if (fNodesTimer) {
687 $timeout.cancel(fNodesTimer);
688 }
689 fNodesTimer = $timeout(_updateNodes, 150);
690 }
691
Simon Hunta17fa672015-08-19 18:42:22 -0700692 // IMPLEMENTATION NOTE: _updateNodes() should NOT stop, start, or resume
693 // the force layout; that needs to be determined and implemented elsewhere
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700694 function _updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800695 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800696 node = nodeG.selectAll('.node')
697 .data(network.nodes, function (d) { return d.id; });
698
Simon Hunt1894d792015-02-04 17:09:20 -0800699 // operate on existing nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800700 node.filter('.device').each(td3.deviceExisting);
701 node.filter('.host').each(td3.hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800702
703 // operate on entering nodes:
704 var entering = node.enter()
705 .append('g')
706 .attr({
707 id: function (d) { return sus.safeId(d.id); },
708 class: mkSvgClass,
Simon Hunta17fa672015-08-19 18:42:22 -0700709 transform: function (d) {
710 // Need to guard against NaN here ??
711 return sus.translate(d.x, d.y);
712 },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100713 opacity: 0,
Simon Huntac4c6f72015-02-03 19:50:53 -0800714 })
715 .call(drag)
Simon Hunt08f841d02015-02-10 14:39:20 -0800716 .on('mouseover', tss.nodeMouseOver)
717 .on('mouseout', tss.nodeMouseOut)
Simon Huntac4c6f72015-02-03 19:50:53 -0800718 .transition()
719 .attr('opacity', 1);
720
Simon Hunt1894d792015-02-04 17:09:20 -0800721 // augment entering nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800722 entering.filter('.device').each(td3.deviceEnter);
723 entering.filter('.host').each(td3.hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -0800724
Simon Hunt51056592015-02-03 21:48:07 -0800725 // operate on both existing and new nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800726 td3.updateDeviceColors();
Simon Huntac4c6f72015-02-03 19:50:53 -0800727
728 // operate on exiting nodes:
729 // Note that the node is removed after 2 seconds.
730 // Sub element animations should be shorter than 2 seconds.
731 var exiting = node.exit()
732 .transition()
733 .duration(2000)
734 .style('opacity', 0)
735 .remove();
736
Simon Hunt1894d792015-02-04 17:09:20 -0800737 // exiting node specifics:
Simon Hunta4242de2015-02-24 17:11:55 -0800738 exiting.filter('.host').each(td3.hostExit);
739 exiting.filter('.device').each(td3.deviceExit);
Thomas Vachuskac616e172018-04-17 16:57:12 -0700740 tick();
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}());