blob: bffa191f06bb6512d177a297673a17c4de1becaa [file] [log] [blame]
Simon Hunt195cb382014-11-03 17:50:51 -08001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
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 Hunt142d0032014-11-04 20:13:09 -080018 ONOS network topology viewer - version 1.1
Simon Hunt195cb382014-11-03 17:50:51 -080019
20 @author Simon Hunt
21 */
22
23(function (onos) {
24 'use strict';
25
Simon Hunt1a9eff92014-11-07 11:06:34 -080026 // shorter names for library APIs
Simon Huntbb282f52014-11-10 11:08:19 -080027 var d3u = onos.lib.d3util,
Simon Hunt12ce12e2014-11-15 21:13:19 -080028 gly = onos.lib.glyphs,
Simon Huntbb282f52014-11-10 11:08:19 -080029 trace;
Simon Hunt1a9eff92014-11-07 11:06:34 -080030
Simon Hunt195cb382014-11-03 17:50:51 -080031 // configuration data
32 var config = {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080033 useLiveData: true,
Simon Huntfc274c92014-11-11 11:05:46 -080034 fnTrace: true,
Simon Hunt195cb382014-11-03 17:50:51 -080035 debugOn: false,
36 debug: {
Simon Hunt99c13842014-11-06 18:23:12 -080037 showNodeXY: true,
38 showKeyHandler: false
Simon Hunt195cb382014-11-03 17:50:51 -080039 },
Simon Hunt12ce12e2014-11-15 21:13:19 -080040 birdDim: 400,
Simon Hunt195cb382014-11-03 17:50:51 -080041 options: {
42 layering: true,
43 collisionPrevention: true,
Simon Hunt142d0032014-11-04 20:13:09 -080044 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080045 },
46 backgroundUrl: 'img/us-map.png',
Thomas Vachuska7d638d32014-11-07 10:24:43 -080047 webSockUrl: 'ws/topology',
Simon Hunt195cb382014-11-03 17:50:51 -080048 data: {
49 live: {
50 jsonUrl: 'rs/topology/graph',
51 detailPrefix: 'rs/topology/graph/',
52 detailSuffix: ''
53 },
54 fake: {
55 jsonUrl: 'json/network2.json',
56 detailPrefix: 'json/',
57 detailSuffix: '.json'
58 }
59 },
Simon Hunt99c13842014-11-06 18:23:12 -080060 labels: {
61 imgPad: 16,
62 padLR: 4,
63 padTB: 3,
64 marginLR: 3,
65 marginTB: 2,
66 port: {
67 gap: 3,
68 width: 18,
69 height: 14
70 }
71 },
Simon Hunt1a9eff92014-11-07 11:06:34 -080072 topo: {
Thomas Vachuska89543292014-11-19 11:28:33 -080073 linkBaseColor: '#666',
Simon Hunt1a9eff92014-11-07 11:06:34 -080074 linkInColor: '#66f',
Paul Greyson29cd58f2014-11-18 13:14:57 -080075 linkInWidth: 14,
Thomas Vachuska89543292014-11-19 11:28:33 -080076 linkOutColor: '#f00',
Simon Hunt13bf9c82014-11-18 07:26:44 -080077 linkOutWidth: 30
Simon Hunt1a9eff92014-11-07 11:06:34 -080078 },
Paul Greyson29cd58f2014-11-18 13:14:57 -080079 icons: {
Thomas Vachuska89543292014-11-19 11:28:33 -080080 w: 24,
81 h: 24,
82 xoff: -10,
83 yoff: -6
84 },
85 iconUrl: {
86 device: 'img/device.png',
87 host: 'img/host.png',
88 pkt: 'img/pkt.png',
89 opt: 'img/opt.png'
Simon Hunt195cb382014-11-03 17:50:51 -080090 },
Simon Hunt195cb382014-11-03 17:50:51 -080091 force: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080092 note_for_links: 'link.type is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080093 linkDistance: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080094 direct: 100,
95 optical: 120,
Thomas Vachuska3266abf2014-11-13 09:28:46 -080096 hostLink: 3
Simon Huntc7ee0662014-11-05 16:44:37 -080097 },
98 linkStrength: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080099 direct: 1.0,
100 optical: 1.0,
101 hostLink: 1.0
Simon Huntc7ee0662014-11-05 16:44:37 -0800102 },
Simon Hunt7cd48f32014-11-09 23:42:50 -0800103 note_for_nodes: 'node.class is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -0800104 charge: {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800105 device: -8000,
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800106 host: -5000
Simon Huntc7ee0662014-11-05 16:44:37 -0800107 },
108 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -0800109 translate: function() {
110 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -0800111 config.force.pad + ',' +
112 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -0800113 }
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800114 },
115 // see below in creation of viewBox on main svg
116 logicalSize: 1000
Simon Hunt195cb382014-11-03 17:50:51 -0800117 };
118
Simon Hunt142d0032014-11-04 20:13:09 -0800119 // radio buttons
Simon Hunt9462e8c2014-11-14 17:28:09 -0800120 var layerButtons = [
121 { text: 'All Layers', id: 'all', cb: showAllLayers },
122 { text: 'Packet Only', id: 'pkt', cb: showPacketLayer },
123 { text: 'Optical Only', id: 'opt', cb: showOpticalLayer }
124 ],
125 layerBtnSet,
126 layerBtnDispatch = {
127 all: showAllLayers,
128 pkt: showPacketLayer,
129 opt: showOpticalLayer
130 };
Simon Hunt934c3ce2014-11-05 11:45:07 -0800131
132 // key bindings
133 var keyDispatch = {
Simon Hunta255a2c2014-11-13 22:29:35 -0800134 M: testMe, // TODO: remove (testing only)
135 S: injectStartupEvents, // TODO: remove (testing only)
136 space: injectTestEvent, // TODO: remove (testing only)
Simon Hunt99c13842014-11-06 18:23:12 -0800137
Simon Hunt01095ff2014-11-13 16:37:29 -0800138 B: toggleBg,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800139 L: cycleLabels,
140 P: togglePorts,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800141 U: unpin,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800142 R: resetZoomPan,
Simon Hunt9462e8c2014-11-14 17:28:09 -0800143 esc: handleEscape
Simon Hunt934c3ce2014-11-05 11:45:07 -0800144 };
Simon Hunt142d0032014-11-04 20:13:09 -0800145
Simon Hunt195cb382014-11-03 17:50:51 -0800146 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800147 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800148 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800149 nodes: [],
150 links: [],
Simon Hunt269670f2014-11-17 16:17:43 -0800151 lookup: {},
152 revLinkToKey: {}
Simon Hunt99c13842014-11-06 18:23:12 -0800153 },
Simon Hunt56d51852014-11-09 13:03:35 -0800154 scenario = {
155 evDir: 'json/ev/',
156 evScenario: '/scenario.json',
157 evPrefix: '/ev_',
158 evOnos: '_onos.json',
159 evUi: '_ui.json',
160 ctx: null,
161 params: {},
162 evNumber: 0,
163 view: null,
164 debug: false
165 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800166 webSock,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800167 sid = 0,
Simon Hunt56d51852014-11-09 13:03:35 -0800168 deviceLabelIndex = 0,
169 hostLabelIndex = 0,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800170 selections = {},
Simon Hunta5e89142014-11-14 07:00:33 -0800171 selectOrder = [],
Simon Hunt6ac93f32014-11-13 12:17:27 -0800172 hovered = null,
Simon Hunta5e89142014-11-14 07:00:33 -0800173 detailPane,
Simon Hunta255a2c2014-11-13 22:29:35 -0800174 antTimer = null,
Simon Hunta5e89142014-11-14 07:00:33 -0800175 onosInstances = {},
176 onosOrder = [],
177 oiBox,
Simon Hunt9462e8c2014-11-14 17:28:09 -0800178 oiShowMaster = false,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800179
Simon Hunt195cb382014-11-03 17:50:51 -0800180 portLabelsOn = false;
181
Simon Hunt934c3ce2014-11-05 11:45:07 -0800182 // D3 selections
183 var svg,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800184 zoomPanContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800185 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800186 topoG,
187 nodeG,
188 linkG,
Simon Hunte2575b62014-11-18 15:25:53 -0800189 linkLabelG,
Simon Huntc7ee0662014-11-05 16:44:37 -0800190 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800191 link,
Simon Hunte2575b62014-11-18 15:25:53 -0800192 linkLabel,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800193 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800194
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800195 // the projection for the map background
196 var geoMapProjection;
197
Paul Greysonfcba0e82014-11-13 10:21:16 -0800198 // the zoom function
199 var zoom;
200
Simon Hunt142d0032014-11-04 20:13:09 -0800201 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800202 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800203
Simon Hunt99c13842014-11-06 18:23:12 -0800204 function note(label, msg) {
205 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800206 }
207
Simon Hunt99c13842014-11-06 18:23:12 -0800208 function debug(what) {
209 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800210 }
211
Simon Huntfc274c92014-11-11 11:05:46 -0800212 function fnTrace(msg, id) {
213 if (config.fnTrace) {
214 console.log('FN: ' + msg + ' [' + id + ']');
215 }
216 }
Simon Hunt99c13842014-11-06 18:23:12 -0800217
Simon Hunta5e89142014-11-14 07:00:33 -0800218 function evTrace(data) {
219 fnTrace(data.event, data.payload.id);
220 }
221
Simon Hunt934c3ce2014-11-05 11:45:07 -0800222 // ==============================
223 // Key Callbacks
224
Simon Hunt99c13842014-11-06 18:23:12 -0800225 function testMe(view) {
Simon Hunt625dc402014-11-18 10:57:18 -0800226 view.alert('Theme is ' + view.theme());
Simon Hunt99c13842014-11-06 18:23:12 -0800227 }
228
Simon Hunt56d51852014-11-09 13:03:35 -0800229 function abortIfLive() {
Simon Hunt50128c02014-11-08 13:36:15 -0800230 if (config.useLiveData) {
Simon Huntb53e0682014-11-12 13:32:01 -0800231 network.view.alert("Sorry, currently using live data..");
Simon Hunt56d51852014-11-09 13:03:35 -0800232 return true;
Simon Hunt50128c02014-11-08 13:36:15 -0800233 }
Simon Hunt56d51852014-11-09 13:03:35 -0800234 return false;
235 }
Simon Hunt50128c02014-11-08 13:36:15 -0800236
Simon Hunt56d51852014-11-09 13:03:35 -0800237 function testDebug(msg) {
238 if (scenario.debug) {
239 scenario.view.alert(msg);
240 }
241 }
Simon Hunt99c13842014-11-06 18:23:12 -0800242
Simon Hunt56d51852014-11-09 13:03:35 -0800243 function injectTestEvent(view) {
244 if (abortIfLive()) { return; }
245 var sc = scenario,
246 evn = ++sc.evNumber,
247 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
248 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800249 uiUrl = pfx + sc.evUi,
250 stack = [
251 { url: onosUrl, cb: handleServerEvent },
252 { url: uiUrl, cb: handleUiEvent }
253 ];
254 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800255 }
256
Simon Hunt7cd48f32014-11-09 23:42:50 -0800257 function recurseFetchEvent(stack, evn) {
258 var v = scenario.view,
259 frame;
260 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800261 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800262 return;
263 }
264 frame = stack.shift();
265
266 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800267 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800268 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800269 // if we didn't find the data, try the next stack frame
270 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800271 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800272 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800273 }
Simon Hunt99c13842014-11-06 18:23:12 -0800274 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800275 testDebug('loaded: ' + frame.url);
Simon Hunt1712ed82014-11-17 12:56:00 -0800276 wsTrace('test', JSON.stringify(data));
Simon Hunt7cd48f32014-11-09 23:42:50 -0800277 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800278 }
279 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800280
Simon Hunt56d51852014-11-09 13:03:35 -0800281 }
Simon Hunt50128c02014-11-08 13:36:15 -0800282
Simon Hunt56d51852014-11-09 13:03:35 -0800283 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800284 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
285 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800286 }
287
288 function injectStartupEvents(view) {
289 var last = scenario.params.lastAuto || 0;
290 if (abortIfLive()) { return; }
291
292 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800293 injectTestEvent(view);
294 }
295 }
296
Simon Hunt934c3ce2014-11-05 11:45:07 -0800297 function toggleBg() {
298 var vis = bgImg.style('visibility');
299 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
300 }
301
Simon Hunt99c13842014-11-06 18:23:12 -0800302 function cycleLabels() {
Simon Huntbb282f52014-11-10 11:08:19 -0800303 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1)
304 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800305
Simon Hunt99c13842014-11-06 18:23:12 -0800306 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800307 if (d.class === 'device') {
308 updateDeviceLabel(d);
309 }
Simon Hunt99c13842014-11-06 18:23:12 -0800310 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800311 }
312
313 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800314 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800315 }
316
Simon Hunt6ac93f32014-11-13 12:17:27 -0800317 function unpin() {
318 if (hovered) {
319 hovered.fixed = false;
320 hovered.el.classed('fixed', false);
321 network.force.resume();
322 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800323 }
324
Simon Hunt9462e8c2014-11-14 17:28:09 -0800325 function handleEscape(view) {
326 if (oiShowMaster) {
327 cancelAffinity();
328 } else {
329 deselectAll();
330 }
331 }
332
Simon Hunt934c3ce2014-11-05 11:45:07 -0800333 // ==============================
334 // Radio Button Callbacks
335
Simon Hunta5e89142014-11-14 07:00:33 -0800336 var layerLookup = {
337 host: {
338 endstation: 'pkt', // default, if host event does not define type
Thomas Vachuska89543292014-11-19 11:28:33 -0800339 router: 'pkt',
Simon Hunta5e89142014-11-14 07:00:33 -0800340 bgpSpeaker: 'pkt'
341 },
342 device: {
343 switch: 'pkt',
344 roadm: 'opt'
345 },
346 link: {
347 hostLink: 'pkt',
348 direct: 'pkt',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800349 indirect: '',
350 tunnel: '',
Simon Hunta5e89142014-11-14 07:00:33 -0800351 optical: 'opt'
352 }
353 };
354
355 function inLayer(d, layer) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800356 var type = d.class === 'link' ? d.type() : d.type,
357 look = layerLookup[d.class],
358 lyr = look && look[type];
Simon Hunta5e89142014-11-14 07:00:33 -0800359 return lyr === layer;
360 }
361
362 function unsuppressLayer(which) {
363 node.each(function (d) {
364 var node = d.el;
365 if (inLayer(d, which)) {
366 node.classed('suppressed', false);
367 }
368 });
369
370 link.each(function (d) {
371 var link = d.el;
372 if (inLayer(d, which)) {
373 link.classed('suppressed', false);
374 }
375 });
376 }
377
Simon Hunt9462e8c2014-11-14 17:28:09 -0800378 function suppressLayers(b) {
379 node.classed('suppressed', b);
380 link.classed('suppressed', b);
Simon Hunt142d0032014-11-04 20:13:09 -0800381// d3.selectAll('svg .port').classed('inactive', false);
382// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt195cb382014-11-03 17:50:51 -0800383 }
384
Simon Hunt9462e8c2014-11-14 17:28:09 -0800385 function showAllLayers() {
386 suppressLayers(false);
387 }
388
Simon Hunt195cb382014-11-03 17:50:51 -0800389 function showPacketLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800390 node.classed('suppressed', true);
391 link.classed('suppressed', true);
392 unsuppressLayer('pkt');
Simon Hunt195cb382014-11-03 17:50:51 -0800393 }
394
395 function showOpticalLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800396 node.classed('suppressed', true);
397 link.classed('suppressed', true);
398 unsuppressLayer('opt');
Simon Hunt195cb382014-11-03 17:50:51 -0800399 }
400
Simon Hunt9462e8c2014-11-14 17:28:09 -0800401 function restoreLayerState() {
402 layerBtnDispatch[layerBtnSet.selected()]();
403 }
404
Simon Hunt142d0032014-11-04 20:13:09 -0800405 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800406 // Private functions
407
Simon Hunt99c13842014-11-06 18:23:12 -0800408 function safeId(s) {
409 return s.replace(/[^a-z0-9]/gi, '-');
410 }
411
Simon Huntc7ee0662014-11-05 16:44:37 -0800412 // set the size of the given element to that of the view (reduced if padded)
413 function setSize(el, view, pad) {
414 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800415 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800416 width: view.width() - padding,
417 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800418 });
419 }
420
Simon Hunt8257f4c2014-11-16 19:34:54 -0800421 function makeNodeKey(d, what) {
422 var port = what + 'Port';
423 return d[what] + '/' + d[port];
424 }
425
426 function makeLinkKey(d, flipped) {
427 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
428 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
429 return one + '-' + two;
430 }
431
Simon Hunt269670f2014-11-17 16:17:43 -0800432 function findLinkById(id) {
433 // check to see if this is a reverse lookup, else default to given id
434 var key = network.revLinkToKey[id] || id;
435 return key && network.lookup[key];
436 }
437
Simon Hunt8257f4c2014-11-16 19:34:54 -0800438 function findLink(linkData, op) {
439 var key = makeLinkKey(linkData),
440 keyrev = makeLinkKey(linkData, 1),
441 link = network.lookup[key],
442 linkRev = network.lookup[keyrev],
443 result = {},
444 ldata = link || linkRev,
445 rawLink;
446
447 if (op === 'add') {
448 if (link) {
449 // trying to add a link that we already know about
450 result.ldata = link;
451 result.badLogic = 'addLink: link already added';
452
453 } else if (linkRev) {
454 // we found the reverse of the link to be added
455 result.ldata = linkRev;
456 if (linkRev.fromTarget) {
457 result.badLogic = 'addLink: link already added';
458 }
459 }
460 } else if (op === 'update') {
461 if (!ldata) {
462 result.badLogic = 'updateLink: link not found';
463 } else {
464 rawLink = link ? ldata.fromSource : ldata.fromTarget;
465 result.updateWith = function (data) {
466 $.extend(rawLink, data);
467 restyleLinkElement(ldata);
468 }
469 }
470 } else if (op === 'remove') {
471 if (!ldata) {
472 result.badLogic = 'removeLink: link not found';
473 } else {
474 rawLink = link ? ldata.fromSource : ldata.fromTarget;
475
476 if (!rawLink) {
477 result.badLogic = 'removeLink: link not found';
478
479 } else {
480 result.removeRawLink = function () {
481 if (link) {
482 // remove fromSource
483 ldata.fromSource = null;
484 if (ldata.fromTarget) {
485 // promote target into source position
486 ldata.fromSource = ldata.fromTarget;
487 ldata.fromTarget = null;
488 ldata.key = keyrev;
489 delete network.lookup[key];
490 network.lookup[keyrev] = ldata;
Simon Hunt269670f2014-11-17 16:17:43 -0800491 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800492 }
493 } else {
494 // remove fromTarget
495 ldata.fromTarget = null;
Simon Hunt269670f2014-11-17 16:17:43 -0800496 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800497 }
498 if (ldata.fromSource) {
499 restyleLinkElement(ldata);
500 } else {
501 removeLinkElement(ldata);
502 }
503 }
504 }
505 }
506 }
507 return result;
508 }
509
510 function addLinkUpdate(ldata, link) {
511 // add link event, but we already have the reverse link installed
512 ldata.fromTarget = link;
Simon Hunt269670f2014-11-17 16:17:43 -0800513 network.revLinkToKey[link.id] = ldata.key;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800514 restyleLinkElement(ldata);
515 }
516
517 var allLinkTypes = 'direct indirect optical tunnel',
518 defaultLinkType = 'direct';
519
520 function restyleLinkElement(ldata) {
521 // this fn's job is to look at raw links and decide what svg classes
522 // need to be applied to the line element in the DOM
523 var el = ldata.el,
524 type = ldata.type(),
525 lw = ldata.linkWidth(),
526 online = ldata.online();
527
528 el.classed('link', true);
529 el.classed('inactive', !online);
530 el.classed(allLinkTypes, false);
531 if (type) {
532 el.classed(type, true);
533 }
534 el.transition()
535 .duration(1000)
Thomas Vachuska89543292014-11-19 11:28:33 -0800536 .attr('stroke-width', linkScale(lw))
537 .attr('stroke', config.topo.linkBaseColor);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800538 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800539
Simon Hunt99c13842014-11-06 18:23:12 -0800540 // ==============================
541 // Event handlers for server-pushed events
542
Simon Huntbb282f52014-11-10 11:08:19 -0800543 function logicError(msg) {
544 // TODO, report logic error to server, via websock, so it can be logged
Simon Huntcb56cff2014-11-17 11:42:26 -0800545 //network.view.alert('Logic Error:\n\n' + msg);
Simon Huntfc274c92014-11-11 11:05:46 -0800546 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800547 }
548
Simon Hunt99c13842014-11-06 18:23:12 -0800549 var eventDispatch = {
Simon Hunta5e89142014-11-14 07:00:33 -0800550 addInstance: addInstance,
Simon Hunt99c13842014-11-06 18:23:12 -0800551 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800552 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800553 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800554
Simon Huntd72bc702014-11-13 18:38:04 -0800555 updateInstance: stillToImplement,
Simon Huntbb282f52014-11-10 11:08:19 -0800556 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800557 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800558 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800559
Simon Huntd72bc702014-11-13 18:38:04 -0800560 removeInstance: stillToImplement,
Simon Huntbb282f52014-11-10 11:08:19 -0800561 removeDevice: stillToImplement,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800562 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800563 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800564
Simon Hunt61d04042014-11-11 17:27:16 -0800565 showDetails: showDetails,
Simon Huntb53e0682014-11-12 13:32:01 -0800566 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800567 };
568
Simon Hunta5e89142014-11-14 07:00:33 -0800569 function addInstance(data) {
570 evTrace(data);
571 var inst = data.payload,
572 id = inst.id;
573 if (onosInstances[id]) {
574 logicError('ONOS instance already added: ' + id);
575 return;
576 }
577 onosInstances[id] = inst;
578 onosOrder.push(inst);
579 updateInstances();
580 }
581
Simon Hunt99c13842014-11-06 18:23:12 -0800582 function addDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800583 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800584 var device = data.payload,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800585 nodeData = createDeviceNode(device);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800586 network.nodes.push(nodeData);
587 network.lookup[nodeData.id] = nodeData;
Simon Hunt99c13842014-11-06 18:23:12 -0800588 updateNodes();
589 network.force.start();
590 }
591
Simon Hunt99c13842014-11-06 18:23:12 -0800592 function addLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800593 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800594 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800595 result = findLink(link, 'add'),
596 bad = result.badLogic,
597 ldata = result.ldata;
598
599 if (bad) {
600 logicError(bad + ': ' + link.id);
601 return;
602 }
603
604 if (ldata) {
605 // we already have a backing store link for src/dst nodes
606 addLinkUpdate(ldata, link);
607 return;
608 }
609
610 // no backing store link yet
611 ldata = createLink(link);
612 if (ldata) {
613 network.links.push(ldata);
614 network.lookup[ldata.key] = ldata;
Simon Hunt99c13842014-11-06 18:23:12 -0800615 updateLinks();
616 network.force.start();
617 }
618 }
619
Simon Hunt56d51852014-11-09 13:03:35 -0800620 function addHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800621 evTrace(data);
Simon Hunt56d51852014-11-09 13:03:35 -0800622 var host = data.payload,
623 node = createHostNode(host),
624 lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800625 network.nodes.push(node);
626 network.lookup[host.id] = node;
627 updateNodes();
628
629 lnk = createHostLink(host);
630 if (lnk) {
Simon Hunt44031102014-11-11 13:20:36 -0800631 node.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800632 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800633 network.lookup[host.ingress] = lnk;
634 network.lookup[host.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800635 updateLinks();
636 }
637 network.force.start();
638 }
639
Simon Hunt44031102014-11-11 13:20:36 -0800640 // TODO: fold updateX(...) methods into one base method; remove duplication
Simon Huntbb282f52014-11-10 11:08:19 -0800641 function updateDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800642 evTrace(data);
Simon Huntbb282f52014-11-10 11:08:19 -0800643 var device = data.payload,
644 id = device.id,
645 nodeData = network.lookup[id];
646 if (nodeData) {
647 $.extend(nodeData, device);
648 updateDeviceState(nodeData);
649 } else {
650 logicError('updateDevice lookup fail. ID = "' + id + '"');
651 }
652 }
653
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800654 function updateLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800655 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800656 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800657 result = findLink(link, 'update'),
658 bad = result.badLogic;
659 if (bad) {
660 logicError(bad + ': ' + link.id);
661 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800662 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800663 result.updateWith(link);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800664 }
665
Simon Hunt7cd48f32014-11-09 23:42:50 -0800666 function updateHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800667 evTrace(data);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800668 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800669 id = host.id,
670 hostData = network.lookup[id];
671 if (hostData) {
672 $.extend(hostData, host);
673 updateHostState(hostData);
674 } else {
675 logicError('updateHost lookup fail. ID = "' + id + '"');
676 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800677 }
678
Simon Hunt44031102014-11-11 13:20:36 -0800679 // TODO: fold removeX(...) methods into base method - remove dup code
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800680 function removeLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800681 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800682 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800683 result = findLink(link, 'remove'),
684 bad = result.badLogic;
685 if (bad) {
686 logicError(bad + ': ' + link.id);
687 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800688 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800689 result.removeRawLink();
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800690 }
691
Simon Hunt44031102014-11-11 13:20:36 -0800692 function removeHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800693 evTrace(data);
Simon Hunt44031102014-11-11 13:20:36 -0800694 var host = data.payload,
695 id = host.id,
696 hostData = network.lookup[id];
697 if (hostData) {
698 removeHostElement(hostData);
699 } else {
700 logicError('removeHost lookup fail. ID = "' + id + '"');
701 }
702 }
703
Simon Hunt61d04042014-11-11 17:27:16 -0800704 function showDetails(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800705 evTrace(data);
Simon Hunt61d04042014-11-11 17:27:16 -0800706 populateDetails(data.payload);
707 detailPane.show();
708 }
709
Simon Huntb53e0682014-11-12 13:32:01 -0800710 function showTraffic(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800711 evTrace(data);
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800712 var paths = data.payload.paths;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800713
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800714 // Revert any links hilighted previously.
Simon Hunta255a2c2014-11-13 22:29:35 -0800715 link.classed('primary secondary animated optical', false);
Simon Hunte2575b62014-11-18 15:25:53 -0800716 // Remove all previous labels.
717 removeLinkLabels();
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800718
Simon Hunte2575b62014-11-18 15:25:53 -0800719 // Now hilight all links in the paths payload, and attach
720 // labels to them, if they are defined.
Simon Hunta255a2c2014-11-13 22:29:35 -0800721 paths.forEach(function (p) {
Simon Hunte2575b62014-11-18 15:25:53 -0800722 var n = p.links.length,
723 i,
724 ldata;
725
726 for (i=0; i<n; i++) {
727 ldata = findLinkById(p.links[i]);
728 if (ldata) {
729 ldata.el.classed(p.class, true);
730 ldata.label = p.labels[i];
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800731 }
Simon Hunte2575b62014-11-18 15:25:53 -0800732 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800733 });
Simon Hunte2575b62014-11-18 15:25:53 -0800734 updateLinks();
Simon Huntb53e0682014-11-12 13:32:01 -0800735 }
736
Simon Hunt56d51852014-11-09 13:03:35 -0800737 // ...............................
738
739 function stillToImplement(data) {
740 var p = data.payload;
741 note(data.event, p.id);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800742 network.view.alert('Not yet implemented: "' + data.event + '"');
Simon Hunt56d51852014-11-09 13:03:35 -0800743 }
Simon Hunt99c13842014-11-06 18:23:12 -0800744
745 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800746 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800747 }
748
749 function handleServerEvent(data) {
750 var fn = eventDispatch[data.event] || unknownEvent;
751 fn(data);
752 }
753
754 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -0800755 // Out-going messages...
756
Simon Huntb53e0682014-11-12 13:32:01 -0800757 function userFeedback(msg) {
758 // for now, use the alert pane as is. Maybe different alert style in
759 // the future (centered on view; dismiss button?)
760 network.view.alert(msg);
761 }
762
763 function nSel() {
764 return selectOrder.length;
765 }
Simon Hunt61d04042014-11-11 17:27:16 -0800766 function getSel(idx) {
767 return selections[selectOrder[idx]];
768 }
Simon Huntb53e0682014-11-12 13:32:01 -0800769 function getSelId(idx) {
770 return getSel(idx).obj.id;
771 }
772 function allSelectionsClass(cls) {
773 for (var i=0, n=nSel(); i<n; i++) {
774 if (getSel(i).obj.class !== cls) {
775 return false;
776 }
777 }
778 return true;
779 }
Simon Hunt61d04042014-11-11 17:27:16 -0800780
Simon Hunt61d04042014-11-11 17:27:16 -0800781 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -0800782 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -0800783 function requestDetails() {
784 var data = getSel(0).obj,
785 payload = {
786 id: data.id,
787 class: data.class
788 };
789 sendMessage('requestDetails', payload);
790 }
791
Simon Huntd72bc702014-11-13 18:38:04 -0800792 function addIntentAction() {
793 sendMessage('addHostIntent', {
794 one: getSelId(0),
Thomas Vachuska82f2c622014-11-17 12:23:18 -0800795 two: getSelId(1),
796 ids: [ getSelId(0), getSelId(1) ]
Simon Huntd72bc702014-11-13 18:38:04 -0800797 });
798 }
799
800 function showTrafficAction() {
801 // if nothing is hovered over, and nothing selected, send cancel request
802 if (!hovered && nSel() === 0) {
803 sendMessage('cancelTraffic', {});
804 return;
805 }
806
807 // NOTE: hover is only populated if "show traffic on hover" is
808 // toggled on, and the item hovered is a host...
809 var hoverId = (trafficHover() && hovered && hovered.class === 'host')
810 ? hovered.id : '';
811 sendMessage('requestTraffic', {
812 ids: selectOrder,
813 hover: hoverId
814 });
815 }
816
817
Simon Hunt61d04042014-11-11 17:27:16 -0800818 // ==============================
Simon Hunta5e89142014-11-14 07:00:33 -0800819 // onos instance panel functions
820
821 function updateInstances() {
822 var onoses = oiBox.el.selectAll('.onosInst')
823 .data(onosOrder, function (d) { return d.id; });
824
825 // operate on existing onoses if necessary
826
827 var entering = onoses.enter()
828 .append('div')
829 .attr('class', 'onosInst')
830 .classed('online', function (d) { return d.online; })
Simon Hunt9c15eca2014-11-15 18:37:59 -0800831 .on('click', clickInst);
832
833 entering.each(function (d, i) {
834 var el = d3.select(this),
835 img;
836
837 $('<img src="img/host.png">').appendTo(el);
838 img = el.select('img')
839 .attr({
840 width: 40,
841 top: -10,
842 left: -10
843 })
844 .style({
845 });
846
847 $('<div>').attr('class', 'onosTitle').text(d.id).appendTo(el);
848
849 // is the UI attached to this instance?
850 // TODO: need uiAttached boolean in instance data
851 //if (d.uiAttached) {
852 if (i === 0) {
853 $('<img src="img/ui.png">').attr('class','ui').appendTo(el);
854 }
855 });
Simon Hunta5e89142014-11-14 07:00:33 -0800856
857 // operate on existing + new onoses here
858
859 // the departed...
860 var exiting = onoses.exit()
861 .transition()
862 .style('opacity', 0)
863 .remove();
864 }
865
Simon Hunt9462e8c2014-11-14 17:28:09 -0800866 function clickInst(d) {
867 var el = d3.select(this),
868 aff = el.classed('affinity');
869 if (!aff) {
870 setAffinity(el, d);
871 } else {
872 cancelAffinity();
873 }
874 }
875
876 function setAffinity(el, d) {
877 d3.selectAll('.onosInst')
878 .classed('mastership', true)
879 .classed('affinity', false);
880 el.classed('affinity', true);
881
882 suppressLayers(true);
883 node.each(function (n) {
884 if (n.master === d.id) {
885 n.el.classed('suppressed', false);
886 }
887 });
888 oiShowMaster = true;
889 }
890
891 function cancelAffinity() {
892 d3.selectAll('.onosInst')
893 .classed('mastership affinity', false);
894 restoreLayerState();
895 oiShowMaster = false;
896 }
897
Simon Hunta5e89142014-11-14 07:00:33 -0800898 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -0800899 // force layout modification functions
900
901 function translate(x, y) {
902 return 'translate(' + x + ',' + y + ')';
903 }
904
Simon Hunte2575b62014-11-18 15:25:53 -0800905 function rotate(deg) {
906 return 'rotate(' + deg + ')';
907 }
908
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800909 function missMsg(what, id) {
910 return '\n[' + what + '] "' + id + '" missing ';
911 }
912
913 function linkEndPoints(srcId, dstId) {
914 var srcNode = network.lookup[srcId],
915 dstNode = network.lookup[dstId],
916 sMiss = !srcNode ? missMsg('src', srcId) : '',
917 dMiss = !dstNode ? missMsg('dst', dstId) : '';
918
919 if (sMiss || dMiss) {
920 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
921 return null;
922 }
923 return {
924 source: srcNode,
925 target: dstNode,
926 x1: srcNode.x,
927 y1: srcNode.y,
928 x2: dstNode.x,
929 y2: dstNode.y
930 };
931 }
932
Simon Hunt56d51852014-11-09 13:03:35 -0800933 function createHostLink(host) {
934 var src = host.id,
935 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800936 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800937 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -0800938
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800939 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -0800940 return null;
941 }
942
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800943 // Synthesize link ...
944 $.extend(lnk, {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800945 key: id,
Simon Hunt56d51852014-11-09 13:03:35 -0800946 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800947
948 type: function () { return 'hostLink'; },
949 // TODO: ideally, we should see if our edge switch is online...
950 online: function () { return true; },
951 linkWidth: function () { return 1; }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800952 });
Simon Hunt99c13842014-11-06 18:23:12 -0800953 return lnk;
954 }
955
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800956 function createLink(link) {
957 var lnk = linkEndPoints(link.src, link.dst),
958 type = link.type;
959
960 if (!lnk) {
961 return null;
962 }
963
Simon Hunt8257f4c2014-11-16 19:34:54 -0800964 $.extend(lnk, {
965 key: link.id,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800966 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800967 fromSource: link,
968
969 // functions to aggregate dual link state
970 type: function () {
971 var s = lnk.fromSource,
972 t = lnk.fromTarget;
973 return (s && s.type) || (t && t.type) || defaultLinkType;
974 },
975 online: function () {
976 var s = lnk.fromSource,
977 t = lnk.fromTarget;
978 return (s && s.online) || (t && t.online);
979 },
980 linkWidth: function () {
981 var s = lnk.fromSource,
982 t = lnk.fromTarget,
983 ws = (s && s.linkWidth) || 0,
984 wt = (t && t.linkWidth) || 0;
985 return Math.max(ws, wt);
986 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800987 });
988 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -0800989 }
990
Simon Hunte2575b62014-11-18 15:25:53 -0800991 function removeLinkLabels() {
992 network.links.forEach(function (d) {
993 d.label = '';
994 });
995 }
996
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800997 var widthRatio = 1.4,
998 linkScale = d3.scale.linear()
999 .domain([1, 12])
1000 .range([widthRatio, 12 * widthRatio])
1001 .clamp(true);
1002
Simon Hunt99c13842014-11-06 18:23:12 -08001003 function updateLinks() {
1004 link = linkG.selectAll('.link')
Simon Hunt8257f4c2014-11-16 19:34:54 -08001005 .data(network.links, function (d) { return d.key; });
Simon Hunt99c13842014-11-06 18:23:12 -08001006
1007 // operate on existing links, if necessary
1008 // link .foo() .bar() ...
1009
1010 // operate on entering links:
1011 var entering = link.enter()
1012 .append('line')
1013 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001014 x1: function (d) { return d.x1; },
1015 y1: function (d) { return d.y1; },
1016 x2: function (d) { return d.x2; },
1017 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001018 stroke: config.topo.linkInColor,
1019 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -08001020 });
1021
1022 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -08001023 entering.each(function (d) {
1024 var link = d3.select(this);
1025 // provide ref to element selection from backing data....
1026 d.el = link;
Simon Hunt8257f4c2014-11-16 19:34:54 -08001027 restyleLinkElement(d);
Simon Hunt7cd48f32014-11-09 23:42:50 -08001028 });
Thomas Vachuska4830d392014-11-09 17:09:56 -08001029
1030 // operate on both existing and new links, if necessary
1031 //link .foo() .bar() ...
1032
Simon Hunte2575b62014-11-18 15:25:53 -08001033 // apply or remove labels
1034 var labelData = getLabelData();
1035 applyLinkLabels(labelData);
1036
Thomas Vachuska4830d392014-11-09 17:09:56 -08001037 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -08001038 link.exit()
Simon Hunt13bf9c82014-11-18 07:26:44 -08001039 .attr('stroke-dasharray', '3, 3')
1040 .style('opacity', 0.5)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001041 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -08001042 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001043 .attr({
Simon Hunt13bf9c82014-11-18 07:26:44 -08001044 'stroke-dasharray': '3, 12',
1045 stroke: config.topo.linkOutColor,
1046 'stroke-width': config.topo.linkOutWidth
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001047 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001048 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -08001049 .remove();
Simon Hunte2575b62014-11-18 15:25:53 -08001050
1051 // NOTE: invoke a single tick to force the labels to position
1052 // onto their links.
1053 tick();
1054 }
1055
1056 function getLabelData() {
1057 // create the backing data for showing labels..
1058 var data = [];
1059 link.each(function (d) {
1060 if (d.label) {
1061 data.push({
1062 id: 'lab-' + d.key,
1063 key: d.key,
1064 label: d.label,
1065 ldata: d
1066 });
1067 }
1068 });
1069 return data;
1070 }
1071
1072 var linkLabelOffset = '0.3em';
1073
1074 function applyLinkLabels(data) {
1075 var entering;
1076
1077 linkLabel = linkLabelG.selectAll('.linkLabel')
1078 .data(data, function (d) { return d.id; });
1079
1080 entering = linkLabel.enter().append('g')
1081 .classed('linkLabel', true)
1082 .attr('id', function (d) { return d.id; });
1083
1084 entering.each(function (d) {
1085 var el = d3.select(this),
1086 rect,
1087 text,
1088 parms = {
1089 x1: d.ldata.x1,
1090 y1: d.ldata.y1,
1091 x2: d.ldata.x2,
1092 y2: d.ldata.y2
1093 };
1094
1095 d.el = el;
1096 rect = el.append('rect');
1097 text = el.append('text').text(d.label);
1098 rect.attr(rectAroundText(el));
1099 text.attr('dy', linkLabelOffset);
1100
1101 el.attr('transform', transformLabel(parms));
1102 });
1103
1104 // Remove any links that are no longer required.
1105 linkLabel.exit().remove();
1106 }
1107
1108 function rectAroundText(el) {
1109 var text = el.select('text'),
1110 box = text.node().getBBox();
1111
1112 // translate the bbox so that it is centered on [x,y]
1113 box.x = -box.width / 2;
1114 box.y = -box.height / 2;
1115
1116 // add padding
1117 box.x -= 1;
1118 box.width += 2;
1119 return box;
1120 }
1121
1122 function transformLabel(p) {
1123 var dx = p.x2 - p.x1,
1124 dy = p.y2 - p.y1,
1125 xMid = dx/2 + p.x1,
1126 yMid = dy/2 + p.y1;
1127 //length = Math.sqrt(dx*dx + dy*dy),
1128 //rads = Math.asin(dy/length),
1129 //degs = rads / (Math.PI*2) * 360;
1130
1131 return translate(xMid, yMid);
1132
1133 // TODO: consider making label parallel to line
1134 //return [
1135 // translate(xMid, yMid),
1136 // rotate(degs),
1137 // translate(0, 8)
1138 //].join('');
Simon Hunt99c13842014-11-06 18:23:12 -08001139 }
1140
1141 function createDeviceNode(device) {
1142 // start with the object as is
1143 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -08001144 type = device.type,
1145 svgCls = type ? 'node device ' + type : 'node device';
Simon Hunt99c13842014-11-06 18:23:12 -08001146
1147 // Augment as needed...
1148 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -08001149 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -08001150 positionNode(node);
1151
1152 // cache label array length
1153 network.deviceLabelCount = device.labels.length;
Simon Hunt99c13842014-11-06 18:23:12 -08001154 return node;
1155 }
1156
Simon Hunt56d51852014-11-09 13:03:35 -08001157 function createHostNode(host) {
1158 // start with the object as is
1159 var node = host;
1160
1161 // Augment as needed...
1162 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001163 if (!node.type) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001164 node.type = 'endstation';
1165 }
Simon Hunt7fa116d2014-11-17 14:16:55 -08001166 node.svgClass = 'node host ' + node.type;
Simon Hunt56d51852014-11-09 13:03:35 -08001167 positionNode(node);
1168
1169 // cache label array length
1170 network.hostLabelCount = host.labels.length;
Simon Hunt56d51852014-11-09 13:03:35 -08001171 return node;
1172 }
1173
Simon Hunt99c13842014-11-06 18:23:12 -08001174 function positionNode(node) {
1175 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -08001176 x = meta && meta.x,
1177 y = meta && meta.y,
1178 xy;
Simon Hunt99c13842014-11-06 18:23:12 -08001179
Simon Huntac9e24f2014-11-12 10:12:21 -08001180 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -08001181 if (x && y) {
1182 node.fixed = true;
Simon Huntac9e24f2014-11-12 10:12:21 -08001183 node.x = x;
1184 node.y = y;
1185 return;
Simon Hunt99c13842014-11-06 18:23:12 -08001186 }
Simon Huntac9e24f2014-11-12 10:12:21 -08001187
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001188 var location = node.location;
1189 if (location && location.type === 'latlng') {
1190 var coord = geoMapProjection([location.lng, location.lat]);
1191 node.fixed = true;
1192 node.x = coord[0];
1193 node.y = coord[1];
1194 return;
1195 }
1196
Simon Huntac9e24f2014-11-12 10:12:21 -08001197 // Note: Placing incoming unpinned nodes at exactly the same point
1198 // (center of the view) causes them to explode outwards when
1199 // the force layout kicks in. So, we spread them out a bit
1200 // initially, to provide a more serene layout convergence.
1201 // Additionally, if the node is a host, we place it near
1202 // the device it is connected to.
1203
1204 function spread(s) {
1205 return Math.floor((Math.random() * s) - s/2);
1206 }
1207
1208 function randDim(dim) {
1209 return dim / 2 + spread(dim * 0.7071);
1210 }
1211
1212 function rand() {
1213 return {
1214 x: randDim(network.view.width()),
1215 y: randDim(network.view.height())
1216 };
1217 }
1218
1219 function near(node) {
1220 var min = 12,
1221 dx = spread(12),
1222 dy = spread(12);
1223 return {
1224 x: node.x + min + dx,
1225 y: node.y + min + dy
1226 };
1227 }
1228
1229 function getDevice(cp) {
1230 var d = network.lookup[cp.device];
1231 return d || rand();
1232 }
1233
1234 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1235 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -08001236 }
1237
Thomas Vachuska89543292014-11-19 11:28:33 -08001238 function iconUrl(d) {
1239 return 'img/' + d.type + '.png';
Simon Hunt99c13842014-11-06 18:23:12 -08001240 }
1241
1242 // returns the newly computed bounding box of the rectangle
1243 function adjustRectToFitText(n) {
1244 var text = n.select('text'),
1245 box = text.node().getBBox(),
1246 lab = config.labels;
1247
1248 text.attr('text-anchor', 'middle')
1249 .attr('y', '-0.8em')
1250 .attr('x', lab.imgPad/2);
1251
1252 // translate the bbox so that it is centered on [x,y]
1253 box.x = -box.width / 2;
1254 box.y = -box.height / 2;
1255
1256 // add padding
1257 box.x -= (lab.padLR + lab.imgPad/2);
1258 box.width += lab.padLR * 2 + lab.imgPad;
1259 box.y -= lab.padTB;
1260 box.height += lab.padTB * 2;
1261
1262 return box;
1263 }
1264
Simon Hunt1a9eff92014-11-07 11:06:34 -08001265 function mkSvgClass(d) {
1266 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1267 }
1268
Simon Hunt7cd48f32014-11-09 23:42:50 -08001269 function hostLabel(d) {
1270 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1271 return d.labels[idx];
1272 }
1273 function deviceLabel(d) {
1274 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1275 return d.labels[idx];
1276 }
1277 function niceLabel(label) {
1278 return (label && label.trim()) ? label : '.';
1279 }
1280
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001281 function updateDeviceLabel(d) {
1282 var label = niceLabel(deviceLabel(d)),
1283 node = d.el,
1284 box;
1285
1286 node.select('text')
1287 .text(label)
1288 .style('opacity', 0)
1289 .transition()
1290 .style('opacity', 1);
1291
1292 box = adjustRectToFitText(node);
1293
1294 node.select('rect')
1295 .transition()
1296 .attr(box);
1297
Thomas Vachuska89543292014-11-19 11:28:33 -08001298 node.select('image')
1299 .transition()
1300 .attr('x', box.x + config.icons.xoff)
1301 .attr('y', box.y + config.icons.yoff);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001302 }
1303
1304 function updateHostLabel(d) {
1305 var label = hostLabel(d),
1306 host = d.el;
1307
1308 host.select('text').text(label);
1309 }
1310
Simon Huntbb282f52014-11-10 11:08:19 -08001311 function updateDeviceState(nodeData) {
1312 nodeData.el.classed('online', nodeData.online);
1313 updateDeviceLabel(nodeData);
1314 // TODO: review what else might need to be updated
1315 }
1316
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001317 function updateLinkState(linkData) {
1318 updateLinkWidth(linkData);
Thomas Vachuskabadb93f2014-11-15 23:51:17 -08001319 linkData.el.classed('inactive', !linkData.online);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001320 // TODO: review what else might need to be updated
1321 // update label, if showing
1322 }
1323
Simon Huntbb282f52014-11-10 11:08:19 -08001324 function updateHostState(hostData) {
1325 updateHostLabel(hostData);
1326 // TODO: review what else might need to be updated
1327 }
1328
Simon Hunt6ac93f32014-11-13 12:17:27 -08001329 function nodeMouseOver(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001330 hovered = d;
Simon Huntd72bc702014-11-13 18:38:04 -08001331 if (trafficHover() && d.class === 'host') {
1332 showTrafficAction();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001333 }
1334 }
1335
1336 function nodeMouseOut(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001337 hovered = null;
Simon Huntd72bc702014-11-13 18:38:04 -08001338 if (trafficHover() && d.class === 'host') {
1339 showTrafficAction();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001340 }
1341 }
Simon Huntbb282f52014-11-10 11:08:19 -08001342
Thomas Vachuska89543292014-11-19 11:28:33 -08001343 function addHostIcon(node, radius, iconId) {
1344 var dim = radius * 1.5,
1345 xlate = -dim / 2;
1346
1347 node.append('svg:image')
1348 .attr('transform', translate(xlate,xlate))
1349 .attr('xlink:href', 'img/' + iconId + '.png')
1350 .attr('width', dim)
1351 .attr('height', dim);
1352 }
1353
Simon Hunt99c13842014-11-06 18:23:12 -08001354 function updateNodes() {
1355 node = nodeG.selectAll('.node')
1356 .data(network.nodes, function (d) { return d.id; });
1357
1358 // operate on existing nodes, if necessary
Simon Hunt7cd48f32014-11-09 23:42:50 -08001359 // update host labels
Simon Hunt99c13842014-11-06 18:23:12 -08001360 //node .foo() .bar() ...
1361
1362 // operate on entering nodes:
1363 var entering = node.enter()
1364 .append('g')
1365 .attr({
1366 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001367 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -08001368 transform: function (d) { return translate(d.x, d.y); },
1369 opacity: 0
1370 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08001371 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -08001372 .on('mouseover', nodeMouseOver)
1373 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -08001374 .transition()
1375 .attr('opacity', 1);
1376
1377 // augment device nodes...
1378 entering.filter('.device').each(function (d) {
1379 var node = d3.select(this),
Thomas Vachuska89543292014-11-19 11:28:33 -08001380 icon = iconUrl(d),
Simon Hunt7cd48f32014-11-09 23:42:50 -08001381 label = niceLabel(deviceLabel(d)),
Simon Hunt99c13842014-11-06 18:23:12 -08001382 box;
1383
Simon Hunt7cd48f32014-11-09 23:42:50 -08001384 // provide ref to element from backing data....
1385 d.el = node;
1386
Simon Hunt99c13842014-11-06 18:23:12 -08001387 node.append('rect')
1388 .attr({
1389 'rx': 5,
1390 'ry': 5
1391 });
1392
1393 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001394 .text(label)
Simon Hunt99c13842014-11-06 18:23:12 -08001395 .attr('dy', '1.1em');
1396
1397 box = adjustRectToFitText(node);
1398
1399 node.select('rect')
1400 .attr(box);
1401
Thomas Vachuska89543292014-11-19 11:28:33 -08001402 if (icon) {
1403 var cfg = config.icons;
1404 node.append('rect')
Simon Hunt99c13842014-11-06 18:23:12 -08001405 .attr({
Thomas Vachuska89543292014-11-19 11:28:33 -08001406 x: box.x + config.icons.xoff,
1407 y: box.y + config.icons.yoff,
1408 width: cfg.w,
1409 height: cfg.h,
1410 rx: 4
1411 }).style({
1412 stroke: '#000',
1413 fill: '#ddd'
1414 });
1415 node.append('svg:image')
1416 .attr({
1417 x: box.x + config.icons.xoff + 2,
1418 y: box.y + config.icons.yoff + 2,
1419 width: cfg.w - 4,
1420 height: cfg.h - 4,
1421 'xlink:href': icon
Simon Hunt99c13842014-11-06 18:23:12 -08001422 });
1423 }
1424
1425 // debug function to show the modelled x,y coordinates of nodes...
1426 if (debug('showNodeXY')) {
1427 node.select('rect').attr('fill-opacity', 0.5);
1428 node.append('circle')
1429 .attr({
1430 class: 'debug',
1431 cx: 0,
1432 cy: 0,
1433 r: '3px'
1434 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001435 }
1436 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08001437
Thomas Vachuska89543292014-11-19 11:28:33 -08001438 // TODO: better place for this configuration state
1439 var defaultHostRadius = 9,
1440 hostRadius = {
1441 bgpSpeaker: 14,
1442 router: 14,
1443 host: 14
1444 },
1445 hostIcon = {
1446 bgpSpeaker: 'bgpSpeaker',
1447 router: 'router',
1448 host: 'host'
1449 };
1450
1451
Simon Hunt56d51852014-11-09 13:03:35 -08001452 // augment host nodes...
1453 entering.filter('.host').each(function (d) {
1454 var node = d3.select(this),
Thomas Vachuska89543292014-11-19 11:28:33 -08001455 r = hostRadius[d.type] || defaultHostRadius,
1456 textDy = r + 10,
1457 icon = hostIcon[d.type];
Simon Hunt56d51852014-11-09 13:03:35 -08001458
Simon Hunt7cd48f32014-11-09 23:42:50 -08001459 // provide ref to element from backing data....
1460 d.el = node;
1461
Thomas Vachuska89543292014-11-19 11:28:33 -08001462 node.append('circle')
1463 .attr('r', r);
Simon Hunt7fa116d2014-11-17 14:16:55 -08001464
Thomas Vachuska89543292014-11-19 11:28:33 -08001465 if (icon) {
1466 addHostIcon(node, r, icon);
Simon Hunt7fa116d2014-11-17 14:16:55 -08001467 }
Simon Hunt56d51852014-11-09 13:03:35 -08001468
Paul Greyson29cd58f2014-11-18 13:14:57 -08001469
Simon Hunt56d51852014-11-09 13:03:35 -08001470 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001471 .text(hostLabel)
Thomas Vachuska89543292014-11-19 11:28:33 -08001472 .attr('dy', textDy)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001473 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08001474
1475 // debug function to show the modelled x,y coordinates of nodes...
1476 if (debug('showNodeXY')) {
1477 node.select('circle').attr('fill-opacity', 0.5);
1478 node.append('circle')
1479 .attr({
1480 class: 'debug',
1481 cx: 0,
1482 cy: 0,
1483 r: '3px'
1484 });
1485 }
1486 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001487
Simon Hunt99c13842014-11-06 18:23:12 -08001488 // operate on both existing and new nodes, if necessary
1489 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -08001490
Simon Hunt99c13842014-11-06 18:23:12 -08001491 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08001492 // Note that the node is removed after 2 seconds.
1493 // Sub element animations should be shorter than 2 seconds.
1494 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08001495 .transition()
1496 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08001497 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08001498 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08001499
1500 // host node exits....
1501 exiting.filter('.host').each(function (d) {
1502 var node = d3.select(this);
1503
1504 node.select('text')
1505 .style('opacity', 0.5)
1506 .transition()
1507 .duration(1000)
1508 .style('opacity', 0);
1509 // note, leave <g>.remove to remove this element
1510
Thomas Vachuska89543292014-11-19 11:28:33 -08001511 node.select('circle')
1512 .style('stroke-fill', '#555')
1513 .style('fill', '#888')
Simon Huntea80eb42014-11-11 13:46:57 -08001514 .style('opacity', 0.5)
1515 .transition()
1516 .duration(1500)
1517 .attr('r', 0);
1518 // note, leave <g>.remove to remove this element
1519
1520 });
1521
1522 // TODO: device node exits
Simon Huntc7ee0662014-11-05 16:44:37 -08001523 }
1524
Simon Hunt8257f4c2014-11-16 19:34:54 -08001525 function find(key, array) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001526 for (var idx = 0, n = array.length; idx < n; idx++) {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001527 if (array[idx].key === key) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001528 return idx;
1529 }
1530 }
1531 return -1;
1532 }
1533
1534 function removeLinkElement(linkData) {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001535 var idx = find(linkData.key, network.links),
1536 removed;
1537 if (idx >=0) {
1538 // remove from links array
1539 removed = network.links.splice(idx, 1);
1540 // remove from lookup cache
1541 delete network.lookup[removed[0].key];
1542 updateLinks();
1543 network.force.resume();
1544 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001545 }
Simon Huntc7ee0662014-11-05 16:44:37 -08001546
Simon Hunt44031102014-11-11 13:20:36 -08001547 function removeHostElement(hostData) {
1548 // first, remove associated hostLink...
1549 removeLinkElement(hostData.linkData);
1550
1551 // remove from lookup cache
1552 delete network.lookup[hostData.id];
1553 // remove from nodes array
1554 var idx = find(hostData.id, network.nodes);
1555 network.nodes.splice(idx, 1);
1556 // remove from SVG
1557 updateNodes();
1558 network.force.resume();
1559 }
1560
1561
Simon Huntc7ee0662014-11-05 16:44:37 -08001562 function tick() {
1563 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001564 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -08001565 });
1566
1567 link.attr({
1568 x1: function (d) { return d.source.x; },
1569 y1: function (d) { return d.source.y; },
1570 x2: function (d) { return d.target.x; },
1571 y2: function (d) { return d.target.y; }
1572 });
Simon Hunte2575b62014-11-18 15:25:53 -08001573
1574 linkLabel.each(function (d) {
1575 var el = d3.select(this);
1576 var lnk = findLinkById(d.key),
1577 parms = {
1578 x1: lnk.source.x,
1579 y1: lnk.source.y,
1580 x2: lnk.target.x,
1581 y2: lnk.target.y
1582 };
1583 el.attr('transform', transformLabel(parms));
1584 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001585 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08001586
1587 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001588 // Web-Socket for live data
1589
1590 function webSockUrl() {
1591 return document.location.toString()
1592 .replace(/\#.*/, '')
1593 .replace('http://', 'ws://')
1594 .replace('https://', 'wss://')
1595 .replace('index2.html', config.webSockUrl);
1596 }
1597
1598 webSock = {
1599 ws : null,
1600
1601 connect : function() {
1602 webSock.ws = new WebSocket(webSockUrl());
1603
1604 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08001605 noWebSock(false);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001606 };
1607
1608 webSock.ws.onmessage = function(m) {
1609 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08001610 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08001611 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001612 }
1613 };
1614
1615 webSock.ws.onclose = function(m) {
1616 webSock.ws = null;
Simon Hunt0c6d4192014-11-12 12:07:10 -08001617 noWebSock(true);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001618 };
1619 },
1620
1621 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001622 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001623 webSock._send(text);
1624 }
1625 },
1626
1627 _send : function(message) {
1628 if (webSock.ws) {
1629 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08001630 } else if (config.useLiveData) {
Simon Hunt56d51852014-11-09 13:03:35 -08001631 network.view.alert('no web socket open\n\n' + message);
Simon Hunta255a2c2014-11-13 22:29:35 -08001632 } else {
1633 console.log('WS Send: ' + JSON.stringify(message));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001634 }
1635 }
1636
1637 };
1638
Simon Hunt0c6d4192014-11-12 12:07:10 -08001639 function noWebSock(b) {
1640 mask.style('display',b ? 'block' : 'none');
1641 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001642
1643 function sendMessage(evType, payload) {
1644 var toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08001645 event: evType,
1646 sid: ++sid,
1647 payload: payload
1648 },
1649 asText = JSON.stringify(toSend);
1650 wsTraceTx(asText);
1651 webSock.send(asText);
Simon Huntc76ae892014-11-18 17:31:51 -08001652
1653 // Temporary measure for debugging UI behavior ...
1654 if (!config.useLiveData) {
1655 handleTestSend(toSend);
1656 }
Simon Huntbb282f52014-11-10 11:08:19 -08001657 }
1658
1659 function wsTraceTx(msg) {
1660 wsTrace('tx', msg);
1661 }
1662 function wsTraceRx(msg) {
1663 wsTrace('rx', msg);
1664 }
1665 function wsTrace(rxtx, msg) {
Simon Huntbb282f52014-11-10 11:08:19 -08001666 console.log('[' + rxtx + '] ' + msg);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001667 }
1668
Simon Huntc76ae892014-11-18 17:31:51 -08001669 // NOTE: Temporary hardcoded example for showing detail pane
1670 // while we fine-
1671 // Probably should not merge this change...
1672 function handleTestSend(msg) {
1673 if (msg.event === 'requestDetails') {
1674 showDetails({
1675 event: 'showDetails',
1676 sid: 1001,
1677 payload: {
1678 "id": "of:0000ffffffffff09",
1679 "type": "roadm",
1680 "propOrder": [
1681 "Name",
1682 "Vendor",
1683 "H/W Version",
1684 "S/W Version",
1685 "-",
1686 "Latitude",
1687 "Longitude",
1688 "Ports"
1689 ],
1690 "props": {
1691 "Name": null,
1692 "Vendor": "Linc",
1693 "H/W Version": "OE",
1694 "S/W Version": "?",
1695 "-": "",
1696 "Latitude": "40.8",
1697 "Longitude": "73.1",
1698 "Ports": "2"
1699 }
1700 }
1701 });
1702 }
1703 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001704
1705 // ==============================
1706 // Selection stuff
1707
1708 function selectObject(obj, el) {
1709 var n,
Simon Hunt01095ff2014-11-13 16:37:29 -08001710 srcEv = d3.event.sourceEvent,
1711 meta = srcEv.metaKey,
1712 shift = srcEv.shiftKey;
1713
Simon Huntdeab4322014-11-13 18:49:07 -08001714 if ((panZoom() && !meta) || (!panZoom() && meta)) {
Simon Hunt01095ff2014-11-13 16:37:29 -08001715 return;
1716 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001717
1718 if (el) {
1719 n = d3.select(el);
1720 } else {
1721 node.each(function(d) {
1722 if (d == obj) {
1723 n = d3.select(el = this);
1724 }
1725 });
1726 }
1727 if (!n) return;
1728
Simon Hunt01095ff2014-11-13 16:37:29 -08001729 if (shift && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001730 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08001731 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001732 return;
1733 }
1734
Simon Hunt01095ff2014-11-13 16:37:29 -08001735 if (!shift) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001736 deselectAll();
1737 }
1738
Simon Huntc31d5692014-11-12 13:27:18 -08001739 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001740 selectOrder.push(obj.id);
1741
1742 n.classed('selected', true);
Simon Hunt61d04042014-11-11 17:27:16 -08001743 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001744 }
1745
1746 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08001747 var obj = selections[id],
1748 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001749 if (obj) {
1750 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08001751 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08001752 idx = $.inArray(id, selectOrder);
1753 if (idx >= 0) {
1754 selectOrder.splice(idx, 1);
1755 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001756 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001757 }
1758
1759 function deselectAll() {
1760 // deselect all nodes in the network...
1761 node.classed('selected', false);
1762 selections = {};
1763 selectOrder = [];
Simon Hunt61d04042014-11-11 17:27:16 -08001764 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001765 }
1766
Simon Hunt61d04042014-11-11 17:27:16 -08001767 // update the state of the detail pane, based on current selections
1768 function updateDetailPane() {
1769 var nSel = selectOrder.length;
1770 if (!nSel) {
1771 detailPane.hide();
Simon Huntd72bc702014-11-13 18:38:04 -08001772 showTrafficAction(); // sends cancelTraffic event
Simon Hunt61d04042014-11-11 17:27:16 -08001773 } else if (nSel === 1) {
1774 singleSelect();
1775 } else {
1776 multiSelect();
1777 }
1778 }
1779
1780 function singleSelect() {
1781 requestDetails();
Simon Huntb53e0682014-11-12 13:32:01 -08001782 // NOTE: detail pane will be shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08001783 }
1784
1785 function multiSelect() {
Simon Huntb53e0682014-11-12 13:32:01 -08001786 populateMultiSelect();
Simon Huntb53e0682014-11-12 13:32:01 -08001787 }
1788
1789 function addSep(tbody) {
1790 var tr = tbody.append('tr');
1791 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
1792 }
1793
1794 function addProp(tbody, label, value) {
1795 var tr = tbody.append('tr');
1796
1797 tr.append('td')
1798 .attr('class', 'label')
1799 .text(label + ' :');
1800
1801 tr.append('td')
1802 .attr('class', 'value')
1803 .text(value);
1804 }
1805
1806 function populateMultiSelect() {
1807 detailPane.empty();
1808
1809 var title = detailPane.append("h2"),
1810 table = detailPane.append("table"),
1811 tbody = table.append("tbody");
1812
1813 title.text('Multi-Select...');
1814
1815 selectOrder.forEach(function (d, i) {
1816 addProp(tbody, i+1, d);
1817 });
Simon Huntd72bc702014-11-13 18:38:04 -08001818
1819 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08001820 }
1821
1822 function populateDetails(data) {
1823 detailPane.empty();
1824
1825 var title = detailPane.append("h2"),
1826 table = detailPane.append("table"),
1827 tbody = table.append("tbody");
1828
1829 $('<img src="img/' + data.type + '.png">').appendTo(title);
1830 $('<span>').attr('class', 'icon').text(data.id).appendTo(title);
1831
1832 data.propOrder.forEach(function(p) {
1833 if (p === '-') {
1834 addSep(tbody);
1835 } else {
1836 addProp(tbody, p, data.props[p]);
1837 }
1838 });
Simon Huntd72bc702014-11-13 18:38:04 -08001839
1840 addSingleSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08001841 }
1842
Simon Huntd72bc702014-11-13 18:38:04 -08001843 function addSingleSelectActions() {
1844 detailPane.append('hr');
1845 // always want to allow 'show traffic'
Simon Hunta5e89142014-11-14 07:00:33 -08001846 addAction(detailPane, 'Show Traffic', showTrafficAction);
Simon Huntd72bc702014-11-13 18:38:04 -08001847 }
1848
1849 function addMultiSelectActions() {
1850 detailPane.append('hr');
1851 // always want to allow 'show traffic'
Simon Hunta5e89142014-11-14 07:00:33 -08001852 addAction(detailPane, 'Show Traffic', showTrafficAction);
Simon Huntd72bc702014-11-13 18:38:04 -08001853 // if exactly two hosts are selected, also want 'add host intent'
1854 if (nSel() === 2 && allSelectionsClass('host')) {
Simon Hunta5e89142014-11-14 07:00:33 -08001855 addAction(detailPane, 'Add Host Intent', addIntentAction);
Simon Huntd72bc702014-11-13 18:38:04 -08001856 }
1857 }
1858
Simon Hunta5e89142014-11-14 07:00:33 -08001859 function addAction(panel, text, cb) {
1860 panel.append('div')
Simon Huntd72bc702014-11-13 18:38:04 -08001861 .classed('actionBtn', true)
1862 .text(text)
1863 .on('click', cb);
1864 }
1865
1866
Paul Greysonfcba0e82014-11-13 10:21:16 -08001867 function zoomPan(scale, translate) {
1868 zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
1869 // keep the map lines constant width while zooming
Thomas Vachuska89543292014-11-19 11:28:33 -08001870 bgImg.style("stroke-width", 2.0 / scale + "px");
Paul Greysonfcba0e82014-11-13 10:21:16 -08001871 }
1872
1873 function resetZoomPan() {
1874 zoomPan(1, [0,0]);
1875 zoom.scale(1).translate([0,0]);
1876 }
1877
1878 function setupZoomPan() {
1879 function zoomed() {
Simon Huntdeab4322014-11-13 18:49:07 -08001880 if (!panZoom() ^ !d3.event.sourceEvent.metaKey) {
Paul Greysonfcba0e82014-11-13 10:21:16 -08001881 zoomPan(d3.event.scale, d3.event.translate);
1882 }
1883 }
1884
1885 zoom = d3.behavior.zoom()
1886 .translate([0, 0])
1887 .scale(1)
1888 .scaleExtent([1, 8])
1889 .on("zoom", zoomed);
1890
1891 svg.call(zoom);
1892 }
1893
Simon Hunt61d04042014-11-11 17:27:16 -08001894 // ==============================
1895 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08001896
1897 function prepareScenario(view, ctx, dbg) {
1898 var sc = scenario,
1899 urlSc = sc.evDir + ctx + sc.evScenario;
1900
1901 if (!ctx) {
1902 view.alert("No scenario specified (null ctx)");
1903 return;
1904 }
1905
1906 sc.view = view;
1907 sc.ctx = ctx;
1908 sc.debug = dbg;
1909 sc.evNumber = 0;
1910
1911 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08001912 var p = data && data.params || {},
1913 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08001914 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08001915
Simon Hunt56d51852014-11-09 13:03:35 -08001916 if (err) {
1917 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
1918 } else {
1919 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08001920 if (desc) {
1921 intro += '\n\n ' + desc.join('\n ');
1922 }
1923 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08001924 }
1925 });
1926
1927 }
1928
Simon Hunt01095ff2014-11-13 16:37:29 -08001929 // ==============================
1930 // Toggle Buttons in masthead
Simon Hunt0c6d4192014-11-12 12:07:10 -08001931
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001932 // TODO: toggle button (and other widgets in the masthead) should be provided
1933 // by the framework; not generated by the view.
1934
Simon Hunta5e89142014-11-14 07:00:33 -08001935 var showInstances,
1936 doPanZoom,
1937 showTrafficOnHover;
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001938
1939 function addButtonBar(view) {
1940 var bb = d3.select('#mast')
1941 .append('span').classed('right', true).attr('id', 'bb');
1942
Simon Hunta5e89142014-11-14 07:00:33 -08001943 function mkTogBtn(text, cb) {
1944 return bb.append('span')
1945 .classed('btn', true)
1946 .text(text)
1947 .on('click', cb);
1948 }
Simon Hunt01095ff2014-11-13 16:37:29 -08001949
Simon Hunta5e89142014-11-14 07:00:33 -08001950 showInstances = mkTogBtn('Show Instances', toggleInst);
Simon Hunte5b71752014-11-18 20:06:07 -08001951 //doPanZoom = mkTogBtn('Pan/Zoom', togglePanZoom);
1952 //showTrafficOnHover = mkTogBtn('Show traffic on hover', toggleTrafficHover);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001953 }
1954
Simon Hunta5e89142014-11-14 07:00:33 -08001955 function instShown() {
1956 return showInstances.classed('active');
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001957 }
Simon Hunta5e89142014-11-14 07:00:33 -08001958 function toggleInst() {
1959 showInstances.classed('active', !instShown());
1960 if (instShown()) {
1961 oiBox.show();
1962 } else {
1963 oiBox.hide();
1964 }
Simon Hunt01095ff2014-11-13 16:37:29 -08001965 }
1966
Simon Huntdeab4322014-11-13 18:49:07 -08001967 function panZoom() {
Simon Hunte5b71752014-11-18 20:06:07 -08001968 return false;
1969 //return doPanZoom.classed('active');
Simon Hunt01095ff2014-11-13 16:37:29 -08001970 }
Simon Hunta5e89142014-11-14 07:00:33 -08001971 function togglePanZoom() {
1972 doPanZoom.classed('active', !panZoom());
1973 }
1974
1975 function trafficHover() {
Simon Hunte5b71752014-11-18 20:06:07 -08001976 return false;
1977 //return showTrafficOnHover.classed('active');
Simon Hunta5e89142014-11-14 07:00:33 -08001978 }
1979 function toggleTrafficHover() {
1980 showTrafficOnHover.classed('active', !trafficHover());
1981 }
1982
Simon Hunt7fa116d2014-11-17 14:16:55 -08001983 function loadGlyphs(svg) {
1984 var defs = svg.append('defs');
1985 gly.defBird(defs);
1986 gly.defBullhorn(defs);
1987 }
Simon Hunt01095ff2014-11-13 16:37:29 -08001988
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001989 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08001990 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08001991
Simon Huntf67722a2014-11-10 09:32:06 -08001992 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08001993 var w = view.width(),
1994 h = view.height(),
Simon Huntc7ee0662014-11-05 16:44:37 -08001995 fcfg = config.force,
1996 fpad = fcfg.pad,
1997 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08001998
Simon Hunt142d0032014-11-04 20:13:09 -08001999 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002000 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
2001 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002002 setSize(svg, view);
2003
Simon Hunt7fa116d2014-11-17 14:16:55 -08002004 loadGlyphs(svg);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002005
Paul Greysonfcba0e82014-11-13 10:21:16 -08002006 zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002007 setupZoomPan();
2008
Simon Hunt1a9eff92014-11-07 11:06:34 -08002009 // add blue glow filter to svg layer
Paul Greysonfcba0e82014-11-13 10:21:16 -08002010 d3u.appendGlow(zoomPanContainer);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002011
Simon Huntc7ee0662014-11-05 16:44:37 -08002012 // group for the topology
Paul Greysonfcba0e82014-11-13 10:21:16 -08002013 topoG = zoomPanContainer.append('g')
Simon Huntd3b7d512014-11-12 15:48:41 -08002014 .attr('id', 'topo-G')
Simon Huntc7ee0662014-11-05 16:44:37 -08002015 .attr('transform', fcfg.translate());
2016
Simon Hunte2575b62014-11-18 15:25:53 -08002017 // subgroups for links, link labels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002018 linkG = topoG.append('g').attr('id', 'links');
Simon Hunte2575b62014-11-18 15:25:53 -08002019 linkLabelG = topoG.append('g').attr('id', 'linkLabels');
Simon Huntc7ee0662014-11-05 16:44:37 -08002020 nodeG = topoG.append('g').attr('id', 'nodes');
2021
Simon Hunte2575b62014-11-18 15:25:53 -08002022 // selection of links, linkLabels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002023 link = linkG.selectAll('.link');
Simon Hunte2575b62014-11-18 15:25:53 -08002024 linkLabel = linkLabelG.selectAll('.linkLabel');
Simon Huntc7ee0662014-11-05 16:44:37 -08002025 node = nodeG.selectAll('.node');
2026
Simon Hunt7cd48f32014-11-09 23:42:50 -08002027 function chrg(d) {
2028 return fcfg.charge[d.class] || -12000;
2029 }
Simon Hunt99c13842014-11-06 18:23:12 -08002030 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002031 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08002032 }
2033 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002034 // 0.0 - 1.0
2035 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08002036 }
2037
Simon Hunt1a9eff92014-11-07 11:06:34 -08002038 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002039 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002040 }
2041
2042 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08002043 // once we've finished moving, pin the node in position
2044 d.fixed = true;
2045 d3.select(self).classed('fixed', true);
2046 if (config.useLiveData) {
Simon Hunt902c9922014-11-11 11:59:31 -08002047 sendUpdateMeta(d);
Simon Hunta255a2c2014-11-13 22:29:35 -08002048 } else {
2049 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08002050 }
2051 }
2052
Simon Hunt902c9922014-11-11 11:59:31 -08002053 function sendUpdateMeta(d) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002054 sendMessage('updateMeta', {
2055 id: d.id,
2056 'class': d.class,
Simon Hunt902c9922014-11-11 11:59:31 -08002057 'memento': {
Simon Hunt01095ff2014-11-13 16:37:29 -08002058 x: d.x,
2059 y: d.y
Simon Hunt902c9922014-11-11 11:59:31 -08002060 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002061 });
2062 }
2063
Simon Huntc7ee0662014-11-05 16:44:37 -08002064 // set up the force layout
2065 network.force = d3.layout.force()
2066 .size(forceDim)
2067 .nodes(network.nodes)
2068 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002069 .gravity(0.4)
2070 .friction(0.7)
2071 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08002072 .linkDistance(ldist)
2073 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08002074 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08002075
Simon Hunt01095ff2014-11-13 16:37:29 -08002076 network.drag = d3u.createDragBehavior(network.force,
Simon Huntdeab4322014-11-13 18:49:07 -08002077 selectCb, atDragEnd, panZoom);
Simon Hunt0c6d4192014-11-12 12:07:10 -08002078
2079 // create mask layer for when we lose connection to server.
Simon Hunta5e89142014-11-14 07:00:33 -08002080 // TODO: this should be part of the framework
Simon Hunt0c6d4192014-11-12 12:07:10 -08002081 mask = view.$div.append('div').attr('id','topo-mask');
2082 para(mask, 'Oops!');
2083 para(mask, 'Web-socket connection to server closed...');
2084 para(mask, 'Try refreshing the page.');
Simon Hunt12ce12e2014-11-15 21:13:19 -08002085
2086 mask.append('svg')
2087 .attr({
2088 id: 'mask-bird',
2089 width: w,
2090 height: h
2091 })
2092 .append('g')
2093 .attr('transform', birdTranslate(w, h))
2094 .style('opacity', 0.3)
2095 .append('use')
2096 .attr({
2097 'xlink:href': '#bird',
2098 width: config.birdDim,
2099 height: config.birdDim,
2100 fill: '#111'
Thomas Vachuska89543292014-11-19 11:28:33 -08002101 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002102 }
Simon Hunt195cb382014-11-03 17:50:51 -08002103
Simon Hunt01095ff2014-11-13 16:37:29 -08002104 function para(sel, text) {
2105 sel.append('p').text(text);
2106 }
2107
2108
Simon Hunt56d51852014-11-09 13:03:35 -08002109 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08002110 // resize, in case the window was resized while we were not loaded
2111 resize(view, ctx, flags);
2112
Simon Hunt99c13842014-11-06 18:23:12 -08002113 // cache the view token, so network topo functions can access it
2114 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08002115 config.useLiveData = !flags.local;
2116
2117 if (!config.useLiveData) {
2118 prepareScenario(view, ctx, flags.debug);
2119 }
Simon Hunt99c13842014-11-06 18:23:12 -08002120
2121 // set our radio buttons and key bindings
Simon Hunt9462e8c2014-11-14 17:28:09 -08002122 layerBtnSet = view.setRadio(layerButtons);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002123 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -08002124
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002125 // patch in our "button bar" for now
2126 // TODO: implement a more official frameworky way of doing this..
2127 addButtonBar(view);
2128
Simon Huntd3b7d512014-11-12 15:48:41 -08002129 // Load map data asynchronously; complete startup after that..
2130 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08002131
2132 // start the and timer
2133 var dashIdx = 0;
2134 antTimer = setInterval(function () {
2135 // TODO: figure out how to choose Src-->Dst and Dst-->Src, per link
2136 dashIdx = dashIdx === 0 ? 14 : dashIdx - 2;
2137 d3.selectAll('.animated').style('stroke-dashoffset', dashIdx);
2138 }, 35);
2139 }
2140
2141 function unload(view, ctx, flags) {
2142 if (antTimer) {
2143 clearInterval(antTimer);
2144 antTimer = null;
2145 }
Simon Huntd3b7d512014-11-12 15:48:41 -08002146 }
2147
2148 // TODO: move these to config/state portion of script
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002149 var geoJsonUrl = 'json/map/continental_us.json', // TODO: Paul
Simon Huntd3b7d512014-11-12 15:48:41 -08002150 geoJson;
2151
2152 function loadGeoJsonData() {
2153 d3.json(geoJsonUrl, function (err, data) {
2154 if (err) {
2155 // fall back to USA map background
2156 loadStaticMap();
2157 } else {
2158 geoJson = data;
2159 loadGeoMap();
2160 }
2161
2162 // finally, connect to the server...
2163 if (config.useLiveData) {
2164 webSock.connect();
2165 }
2166 });
2167 }
2168
2169 function showBg() {
2170 return config.options.showBackground ? 'visible' : 'hidden';
2171 }
2172
2173 function loadStaticMap() {
2174 fnTrace('loadStaticMap', config.backgroundUrl);
2175 var w = network.view.width(),
2176 h = network.view.height();
2177
2178 // load the background image
2179 bgImg = svg.insert('svg:image', '#topo-G')
2180 .attr({
2181 id: 'topo-bg',
2182 width: w,
2183 height: h,
2184 'xlink:href': config.backgroundUrl
2185 })
2186 .style({
2187 visibility: showBg()
2188 });
2189 }
2190
2191 function loadGeoMap() {
2192 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08002193
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002194 // extracts the topojson data into geocoordinate-based geometry
2195 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08002196
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002197 // see: http://bl.ocks.org/mbostock/4707858
2198 geoMapProjection = d3.geo.mercator();
2199 var path = d3.geo.path().projection(geoMapProjection);
Simon Huntd3b7d512014-11-12 15:48:41 -08002200
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002201 geoMapProjection
2202 .scale(1)
2203 .translate([0, 0]);
Simon Huntd3b7d512014-11-12 15:48:41 -08002204
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002205 // [[x1,y1],[x2,y2]]
2206 var b = path.bounds(topoData);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002207 // size map to 95% of minimum dimension to fill space
2208 var s = .95 / Math.min((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002209 var t = [(config.logicalSize - s * (b[1][0] + b[0][0])) / 2, (config.logicalSize - s * (b[1][1] + b[0][1])) / 2];
Simon Huntd3b7d512014-11-12 15:48:41 -08002210
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002211 geoMapProjection
2212 .scale(s)
2213 .translate(t);
2214
Paul Greysonfcba0e82014-11-13 10:21:16 -08002215 bgImg = zoomPanContainer.insert("g", '#topo-G');
Thomas Vachuska89543292014-11-19 11:28:33 -08002216 bgImg.attr('id', 'map').selectAll('path')
2217 .data(topoData.features)
2218 .enter()
2219 .append('path')
2220 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08002221 }
2222
Simon Huntf67722a2014-11-10 09:32:06 -08002223 function resize(view, ctx, flags) {
Simon Hunt12ce12e2014-11-15 21:13:19 -08002224 var w = view.width(),
2225 h = view.height();
2226
Simon Hunt934c3ce2014-11-05 11:45:07 -08002227 setSize(svg, view);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002228
2229 d3.select('#mask-bird').attr({ width: w, height: h})
2230 .select('g').attr('transform', birdTranslate(w, h));
Simon Hunt142d0032014-11-04 20:13:09 -08002231 }
2232
Simon Hunt12ce12e2014-11-15 21:13:19 -08002233 function birdTranslate(w, h) {
2234 var bdim = config.birdDim;
2235 return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
2236 }
Simon Hunt142d0032014-11-04 20:13:09 -08002237
2238 // ==============================
2239 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08002240
Simon Hunt25248912014-11-04 11:25:48 -08002241 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08002242 preload: preload,
2243 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08002244 unload: unload,
Simon Hunt142d0032014-11-04 20:13:09 -08002245 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -08002246 });
2247
Simon Hunt61d04042014-11-11 17:27:16 -08002248 detailPane = onos.ui.addFloatingPanel('topo-detail');
Simon Hunta5e89142014-11-14 07:00:33 -08002249 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
Simon Hunt61d04042014-11-11 17:27:16 -08002250
Simon Hunt195cb382014-11-03 17:50:51 -08002251}(ONOS));