blob: 1620048aede30fe121f2d9a1a4325b634ceaf416 [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
Simon Hunta6a9fe72014-11-20 11:17:12 -080021 @author Thomas Vachuska
Simon Hunt195cb382014-11-03 17:50:51 -080022 */
23
24(function (onos) {
25 'use strict';
26
Simon Hunt1a9eff92014-11-07 11:06:34 -080027 // shorter names for library APIs
Simon Huntbb282f52014-11-10 11:08:19 -080028 var d3u = onos.lib.d3util,
Simon Hunta6a9fe72014-11-20 11:17:12 -080029 gly = onos.lib.glyphs;
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',
Thomas Vachuska9edca302014-11-22 17:06:42 -080075 linkInWidth: 12,
Thomas Vachuska89543292014-11-19 11:28:33 -080076 linkOutColor: '#f00',
Thomas Vachuska9edca302014-11-22 17:06:42 -080077 linkOutWidth: 10
Simon Hunt1a9eff92014-11-07 11:06:34 -080078 },
Paul Greyson29cd58f2014-11-18 13:14:57 -080079 icons: {
Simon Huntc72967b2014-11-20 09:21:42 -080080 device: {
Simon Hunt395a70c2014-11-22 23:17:40 -080081 dim: 36,
82 rx: 4,
83 xoff: -20,
84 yoff: -18
85 },
86 host: {
87 defaultRadius: 9,
88 radius: {
89 endstation: 14,
90 bgpSpeaker: 14,
91 router: 14
92 }
Simon Huntc72967b2014-11-20 09:21:42 -080093 }
Thomas Vachuska89543292014-11-19 11:28:33 -080094 },
Simon Hunt195cb382014-11-03 17:50:51 -080095 force: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080096 note_for_links: 'link.type is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080097 linkDistance: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080098 direct: 100,
99 optical: 120,
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800100 hostLink: 3
Simon Huntc7ee0662014-11-05 16:44:37 -0800101 },
102 linkStrength: {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800103 direct: 1.0,
104 optical: 1.0,
105 hostLink: 1.0
Simon Huntc7ee0662014-11-05 16:44:37 -0800106 },
Simon Hunt7cd48f32014-11-09 23:42:50 -0800107 note_for_nodes: 'node.class is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -0800108 charge: {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800109 device: -8000,
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800110 host: -5000
Simon Huntc7ee0662014-11-05 16:44:37 -0800111 },
112 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -0800113 translate: function() {
114 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -0800115 config.force.pad + ',' +
116 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -0800117 }
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800118 },
119 // see below in creation of viewBox on main svg
120 logicalSize: 1000
Simon Hunt195cb382014-11-03 17:50:51 -0800121 };
122
Simon Hunt142d0032014-11-04 20:13:09 -0800123 // radio buttons
Simon Hunt9462e8c2014-11-14 17:28:09 -0800124 var layerButtons = [
125 { text: 'All Layers', id: 'all', cb: showAllLayers },
126 { text: 'Packet Only', id: 'pkt', cb: showPacketLayer },
127 { text: 'Optical Only', id: 'opt', cb: showOpticalLayer }
128 ],
129 layerBtnSet,
130 layerBtnDispatch = {
131 all: showAllLayers,
132 pkt: showPacketLayer,
133 opt: showOpticalLayer
134 };
Simon Hunt934c3ce2014-11-05 11:45:07 -0800135
136 // key bindings
137 var keyDispatch = {
Simon Hunt988c6fc2014-11-20 17:43:03 -0800138 // TODO: remove these "development only" bindings
Simon Hunt8f40cce2014-11-23 15:57:30 -0800139 0: testMe,
140 equals: injectStartupEvents,
141 dash: injectTestEvent,
Simon Hunt99c13842014-11-06 18:23:12 -0800142
Thomas Vachuska47635c62014-11-22 01:21:36 -0800143 O: [toggleSummary, 'Toggle ONOS summary pane'],
144 I: [toggleInstances, 'Toggle ONOS instances pane'],
Simon Hunt27d322d2014-11-28 10:45:43 -0800145 D: [toggleDetails, 'Disable / enable details pane'],
Simon Hunt988c6fc2014-11-20 17:43:03 -0800146 B: [toggleBg, 'Toggle background image'],
Simon Hunt434cf142014-11-24 11:10:28 -0800147 H: [toggleHosts, 'Toggle host visibility'],
Simon Hunt6d9bd032014-11-28 22:16:40 -0800148 M: [toggleOffline, 'Toggle offline visibility'],
Simon Hunt27d322d2014-11-28 10:45:43 -0800149 L: [cycleLabels, 'Cycle device labels'],
Simon Hunt934c3ce2014-11-05 11:45:07 -0800150 P: togglePorts,
Simon Hunt87514342014-11-24 16:41:27 -0800151 U: [unpin, 'Unpin node (hover mouse over)'],
152 R: [resetZoomPan, 'Reset zoom / pan'],
Thomas Vachuska9edca302014-11-22 17:06:42 -0800153 V: [showTrafficAction, 'Show related traffic'],
Simon Hunt56ef0fe2014-11-21 08:24:43 -0800154 A: [showAllTrafficAction, 'Show all traffic'],
155 F: [showDeviceLinkFlowsAction, 'Show device link flows'],
Simon Hunt9462e8c2014-11-14 17:28:09 -0800156 esc: handleEscape
Simon Hunt934c3ce2014-11-05 11:45:07 -0800157 };
Simon Hunt142d0032014-11-04 20:13:09 -0800158
Simon Hunt87514342014-11-24 16:41:27 -0800159 // mouse gestures
160 var gestures = [
161 ['click', 'Select the item and show details'],
162 ['shift-click', 'Toggle selection state'],
163 ['drag', 'Reposition (and pin) device / host'],
164 ['cmd-scroll', 'Zoom in / out'],
165 ['cmd-drag', 'Pan']
166 ];
167
Simon Hunt195cb382014-11-03 17:50:51 -0800168 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800169 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800170 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800171 nodes: [],
172 links: [],
Simon Hunt269670f2014-11-17 16:17:43 -0800173 lookup: {},
174 revLinkToKey: {}
Simon Hunt99c13842014-11-06 18:23:12 -0800175 },
Simon Hunt56d51852014-11-09 13:03:35 -0800176 scenario = {
177 evDir: 'json/ev/',
178 evScenario: '/scenario.json',
179 evPrefix: '/ev_',
180 evOnos: '_onos.json',
181 evUi: '_ui.json',
182 ctx: null,
183 params: {},
184 evNumber: 0,
Simon Hunt434cf142014-11-24 11:10:28 -0800185 view: null
Simon Hunt56d51852014-11-09 13:03:35 -0800186 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800187 webSock,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800188 sid = 0,
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800189 deviceLabelCount = 3,
Simon Hunt209155e2014-11-21 12:16:09 -0800190 hostLabelCount = 2,
Simon Hunt56d51852014-11-09 13:03:35 -0800191 deviceLabelIndex = 0,
192 hostLabelIndex = 0,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800193 selections = {},
Simon Hunta5e89142014-11-14 07:00:33 -0800194 selectOrder = [],
Simon Hunt6ac93f32014-11-13 12:17:27 -0800195 hovered = null,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800196 summaryPane,
Simon Hunta5e89142014-11-14 07:00:33 -0800197 detailPane,
Simon Hunta255a2c2014-11-13 22:29:35 -0800198 antTimer = null,
Simon Hunta5e89142014-11-14 07:00:33 -0800199 onosInstances = {},
200 onosOrder = [],
201 oiBox,
Simon Hunt9462e8c2014-11-14 17:28:09 -0800202 oiShowMaster = false,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800203 portLabelsOn = false,
Simon Huntb0ecfa52014-11-23 21:05:12 -0800204 cat7 = d3u.cat7(),
Simon Hunt434cf142014-11-24 11:10:28 -0800205 colorAffinity = false,
Simon Hunt27d322d2014-11-28 10:45:43 -0800206 showHosts = false,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800207 showOffline = true,
Simon Hunt27d322d2014-11-28 10:45:43 -0800208 useDetails = true,
209 haveDetails = false;
Simon Hunt195cb382014-11-03 17:50:51 -0800210
Simon Hunt434cf142014-11-24 11:10:28 -0800211 // constants
Thomas Vachuska9edca302014-11-22 17:06:42 -0800212 var hoverModeAll = 1,
213 hoverModeFlows = 2,
214 hoverModeIntents = 3,
215 hoverMode = hoverModeFlows;
216
Simon Hunt934c3ce2014-11-05 11:45:07 -0800217 // D3 selections
218 var svg,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800219 zoomPanContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800220 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800221 topoG,
222 nodeG,
223 linkG,
Simon Hunte2575b62014-11-18 15:25:53 -0800224 linkLabelG,
Simon Huntc7ee0662014-11-05 16:44:37 -0800225 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800226 link,
Simon Hunte2575b62014-11-18 15:25:53 -0800227 linkLabel,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800228 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800229
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800230 // the projection for the map background
231 var geoMapProjection;
232
Paul Greysonfcba0e82014-11-13 10:21:16 -0800233 // the zoom function
234 var zoom;
235
Simon Hunt142d0032014-11-04 20:13:09 -0800236 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800237 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800238
Simon Hunt99c13842014-11-06 18:23:12 -0800239 function note(label, msg) {
240 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800241 }
242
Simon Hunt99c13842014-11-06 18:23:12 -0800243 function debug(what) {
244 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800245 }
246
Simon Huntfc274c92014-11-11 11:05:46 -0800247 function fnTrace(msg, id) {
248 if (config.fnTrace) {
249 console.log('FN: ' + msg + ' [' + id + ']');
250 }
251 }
Simon Hunt99c13842014-11-06 18:23:12 -0800252
Simon Hunta5e89142014-11-14 07:00:33 -0800253 function evTrace(data) {
254 fnTrace(data.event, data.payload.id);
255 }
256
Simon Hunt934c3ce2014-11-05 11:45:07 -0800257 // ==============================
258 // Key Callbacks
259
Simon Hunt27d322d2014-11-28 10:45:43 -0800260 function flash(txt) {
261 network.view.flash(txt);
262 }
263
Simon Hunt99c13842014-11-06 18:23:12 -0800264 function testMe(view) {
Simon Hunt8f40cce2014-11-23 15:57:30 -0800265 //view.alert('Theme is ' + view.getTheme());
Simon Hunta3dd9572014-11-20 15:22:41 -0800266 //view.flash('This is some text');
Simon Hunt8f40cce2014-11-23 15:57:30 -0800267 cat7.testCard(svg);
Simon Hunt99c13842014-11-06 18:23:12 -0800268 }
269
Simon Hunt56d51852014-11-09 13:03:35 -0800270 function injectTestEvent(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800271 if (config.useLiveData) { return; }
272
Simon Hunt56d51852014-11-09 13:03:35 -0800273 var sc = scenario,
274 evn = ++sc.evNumber,
275 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
276 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800277 uiUrl = pfx + sc.evUi,
278 stack = [
279 { url: onosUrl, cb: handleServerEvent },
280 { url: uiUrl, cb: handleUiEvent }
281 ];
282 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800283 }
284
Simon Hunt7cd48f32014-11-09 23:42:50 -0800285 function recurseFetchEvent(stack, evn) {
286 var v = scenario.view,
287 frame;
288 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800289 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800290 return;
291 }
292 frame = stack.shift();
293
294 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800295 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800296 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800297 // if we didn't find the data, try the next stack frame
298 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800299 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800300 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800301 }
Simon Hunt99c13842014-11-06 18:23:12 -0800302 } else {
Simon Hunt1712ed82014-11-17 12:56:00 -0800303 wsTrace('test', JSON.stringify(data));
Simon Hunt7cd48f32014-11-09 23:42:50 -0800304 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800305 }
306 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800307
Simon Hunt56d51852014-11-09 13:03:35 -0800308 }
Simon Hunt50128c02014-11-08 13:36:15 -0800309
Simon Hunt56d51852014-11-09 13:03:35 -0800310 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800311 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
312 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800313 }
314
315 function injectStartupEvents(view) {
316 var last = scenario.params.lastAuto || 0;
Simon Hunt434cf142014-11-24 11:10:28 -0800317 if (config.useLiveData) { return; }
Simon Hunt56d51852014-11-09 13:03:35 -0800318
319 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800320 injectTestEvent(view);
321 }
322 }
323
Simon Hunt934c3ce2014-11-05 11:45:07 -0800324 function toggleBg() {
325 var vis = bgImg.style('visibility');
Simon Hunt434cf142014-11-24 11:10:28 -0800326 bgImg.style('visibility', visVal(vis === 'hidden'));
327 }
328
329 function toggleHosts() {
330 showHosts = !showHosts;
331 updateHostVisibility();
Simon Hunt27d322d2014-11-28 10:45:43 -0800332 flash('Hosts ' + visVal(showHosts));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800333 }
334
Simon Hunt6d9bd032014-11-28 22:16:40 -0800335 function toggleOffline() {
336 showOffline = !showOffline;
337 updateOfflineVisibility();
338 flash('Offline devices ' + visVal(showOffline));
339 }
340
Simon Hunt99c13842014-11-06 18:23:12 -0800341 function cycleLabels() {
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800342 deviceLabelIndex = (deviceLabelIndex === 2)
Simon Huntbb282f52014-11-10 11:08:19 -0800343 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800344
Simon Hunt99c13842014-11-06 18:23:12 -0800345 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800346 if (d.class === 'device') {
347 updateDeviceLabel(d);
348 }
Simon Hunt99c13842014-11-06 18:23:12 -0800349 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800350 }
351
352 function togglePorts(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800353 //view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800354 }
355
Simon Hunt6ac93f32014-11-13 12:17:27 -0800356 function unpin() {
357 if (hovered) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800358 sendUpdateMeta(hovered);
Simon Hunt6ac93f32014-11-13 12:17:27 -0800359 hovered.fixed = false;
360 hovered.el.classed('fixed', false);
361 network.force.resume();
362 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800363 }
364
Simon Hunt9462e8c2014-11-14 17:28:09 -0800365 function handleEscape(view) {
366 if (oiShowMaster) {
367 cancelAffinity();
Simon Hunt27d322d2014-11-28 10:45:43 -0800368 } else if (haveDetails) {
Simon Hunt9462e8c2014-11-14 17:28:09 -0800369 deselectAll();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800370 } else if (oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800371 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800372 } else if (summaryPane.isVisible()) {
373 cancelSummary();
Thomas Vachuska5bde31f2014-11-25 15:29:18 -0800374 stopAntTimer();
375 } else {
376 hoverMode = hoverModeFlows;
Simon Hunt9462e8c2014-11-14 17:28:09 -0800377 }
378 }
379
Simon Hunt934c3ce2014-11-05 11:45:07 -0800380 // ==============================
381 // Radio Button Callbacks
382
Simon Hunta5e89142014-11-14 07:00:33 -0800383 var layerLookup = {
384 host: {
Simon Hunt209155e2014-11-21 12:16:09 -0800385 endstation: 'pkt', // default, if host event does not define type
Thomas Vachuska89543292014-11-19 11:28:33 -0800386 router: 'pkt',
Simon Hunta5e89142014-11-14 07:00:33 -0800387 bgpSpeaker: 'pkt'
388 },
389 device: {
390 switch: 'pkt',
391 roadm: 'opt'
392 },
393 link: {
394 hostLink: 'pkt',
395 direct: 'pkt',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800396 indirect: '',
397 tunnel: '',
Simon Hunta5e89142014-11-14 07:00:33 -0800398 optical: 'opt'
399 }
400 };
401
402 function inLayer(d, layer) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800403 var type = d.class === 'link' ? d.type() : d.type,
404 look = layerLookup[d.class],
405 lyr = look && look[type];
Simon Hunta5e89142014-11-14 07:00:33 -0800406 return lyr === layer;
407 }
408
409 function unsuppressLayer(which) {
410 node.each(function (d) {
411 var node = d.el;
412 if (inLayer(d, which)) {
413 node.classed('suppressed', false);
414 }
415 });
416
417 link.each(function (d) {
418 var link = d.el;
419 if (inLayer(d, which)) {
420 link.classed('suppressed', false);
421 }
422 });
423 }
424
Simon Hunt9462e8c2014-11-14 17:28:09 -0800425 function suppressLayers(b) {
426 node.classed('suppressed', b);
427 link.classed('suppressed', b);
Simon Hunt142d0032014-11-04 20:13:09 -0800428// d3.selectAll('svg .port').classed('inactive', false);
429// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt195cb382014-11-03 17:50:51 -0800430 }
431
Simon Hunt9462e8c2014-11-14 17:28:09 -0800432 function showAllLayers() {
433 suppressLayers(false);
434 }
435
Simon Hunt195cb382014-11-03 17:50:51 -0800436 function showPacketLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800437 node.classed('suppressed', true);
438 link.classed('suppressed', true);
439 unsuppressLayer('pkt');
Simon Hunt195cb382014-11-03 17:50:51 -0800440 }
441
442 function showOpticalLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800443 node.classed('suppressed', true);
444 link.classed('suppressed', true);
445 unsuppressLayer('opt');
Simon Hunt195cb382014-11-03 17:50:51 -0800446 }
447
Simon Hunt9462e8c2014-11-14 17:28:09 -0800448 function restoreLayerState() {
449 layerBtnDispatch[layerBtnSet.selected()]();
450 }
451
Simon Hunt142d0032014-11-04 20:13:09 -0800452 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800453 // Private functions
454
Simon Hunt99c13842014-11-06 18:23:12 -0800455 function safeId(s) {
456 return s.replace(/[^a-z0-9]/gi, '-');
457 }
458
Simon Huntc7ee0662014-11-05 16:44:37 -0800459 // set the size of the given element to that of the view (reduced if padded)
460 function setSize(el, view, pad) {
461 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800462 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800463 width: view.width() - padding,
464 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800465 });
466 }
467
Simon Hunt8257f4c2014-11-16 19:34:54 -0800468 function makeNodeKey(d, what) {
469 var port = what + 'Port';
470 return d[what] + '/' + d[port];
471 }
472
473 function makeLinkKey(d, flipped) {
474 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
475 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
476 return one + '-' + two;
477 }
478
Simon Hunt269670f2014-11-17 16:17:43 -0800479 function findLinkById(id) {
480 // check to see if this is a reverse lookup, else default to given id
481 var key = network.revLinkToKey[id] || id;
482 return key && network.lookup[key];
483 }
484
Simon Hunt8257f4c2014-11-16 19:34:54 -0800485 function findLink(linkData, op) {
486 var key = makeLinkKey(linkData),
487 keyrev = makeLinkKey(linkData, 1),
488 link = network.lookup[key],
489 linkRev = network.lookup[keyrev],
490 result = {},
491 ldata = link || linkRev,
492 rawLink;
493
494 if (op === 'add') {
495 if (link) {
496 // trying to add a link that we already know about
497 result.ldata = link;
498 result.badLogic = 'addLink: link already added';
499
500 } else if (linkRev) {
501 // we found the reverse of the link to be added
502 result.ldata = linkRev;
503 if (linkRev.fromTarget) {
504 result.badLogic = 'addLink: link already added';
505 }
506 }
507 } else if (op === 'update') {
508 if (!ldata) {
509 result.badLogic = 'updateLink: link not found';
510 } else {
511 rawLink = link ? ldata.fromSource : ldata.fromTarget;
512 result.updateWith = function (data) {
513 $.extend(rawLink, data);
514 restyleLinkElement(ldata);
515 }
516 }
517 } else if (op === 'remove') {
518 if (!ldata) {
519 result.badLogic = 'removeLink: link not found';
520 } else {
521 rawLink = link ? ldata.fromSource : ldata.fromTarget;
522
523 if (!rawLink) {
524 result.badLogic = 'removeLink: link not found';
525
526 } else {
527 result.removeRawLink = function () {
528 if (link) {
529 // remove fromSource
530 ldata.fromSource = null;
531 if (ldata.fromTarget) {
532 // promote target into source position
533 ldata.fromSource = ldata.fromTarget;
534 ldata.fromTarget = null;
535 ldata.key = keyrev;
536 delete network.lookup[key];
537 network.lookup[keyrev] = ldata;
Simon Hunt269670f2014-11-17 16:17:43 -0800538 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800539 }
540 } else {
541 // remove fromTarget
542 ldata.fromTarget = null;
Simon Hunt269670f2014-11-17 16:17:43 -0800543 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800544 }
545 if (ldata.fromSource) {
546 restyleLinkElement(ldata);
547 } else {
548 removeLinkElement(ldata);
549 }
550 }
551 }
552 }
553 }
554 return result;
555 }
556
557 function addLinkUpdate(ldata, link) {
558 // add link event, but we already have the reverse link installed
559 ldata.fromTarget = link;
Simon Hunt269670f2014-11-17 16:17:43 -0800560 network.revLinkToKey[link.id] = ldata.key;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800561 restyleLinkElement(ldata);
562 }
563
564 var allLinkTypes = 'direct indirect optical tunnel',
565 defaultLinkType = 'direct';
566
567 function restyleLinkElement(ldata) {
568 // this fn's job is to look at raw links and decide what svg classes
569 // need to be applied to the line element in the DOM
570 var el = ldata.el,
571 type = ldata.type(),
572 lw = ldata.linkWidth(),
573 online = ldata.online();
574
575 el.classed('link', true);
576 el.classed('inactive', !online);
577 el.classed(allLinkTypes, false);
578 if (type) {
579 el.classed(type, true);
580 }
581 el.transition()
582 .duration(1000)
Thomas Vachuska89543292014-11-19 11:28:33 -0800583 .attr('stroke-width', linkScale(lw))
584 .attr('stroke', config.topo.linkBaseColor);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800585 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800586
Simon Hunt99c13842014-11-06 18:23:12 -0800587 // ==============================
588 // Event handlers for server-pushed events
589
Simon Huntbb282f52014-11-10 11:08:19 -0800590 function logicError(msg) {
591 // TODO, report logic error to server, via websock, so it can be logged
Simon Huntfc274c92014-11-11 11:05:46 -0800592 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800593 }
594
Simon Hunt99c13842014-11-06 18:23:12 -0800595 var eventDispatch = {
Simon Hunta5e89142014-11-14 07:00:33 -0800596 addInstance: addInstance,
Simon Hunt99c13842014-11-06 18:23:12 -0800597 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800598 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800599 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800600
Simon Huntfcfb46c2014-11-19 12:53:38 -0800601 updateInstance: updateInstance,
Simon Huntbb282f52014-11-10 11:08:19 -0800602 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800603 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800604 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800605
Simon Hunt7b403bc2014-11-22 19:01:00 -0800606 removeInstance: removeInstance,
Simon Huntca867ac2014-11-28 18:07:35 -0800607 removeDevice: removeDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800608 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800609 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800610
Simon Hunt61d04042014-11-11 17:27:16 -0800611 showDetails: showDetails,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800612 showSummary: showSummary,
Simon Huntb53e0682014-11-12 13:32:01 -0800613 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800614 };
615
Simon Hunta5e89142014-11-14 07:00:33 -0800616 function addInstance(data) {
617 evTrace(data);
618 var inst = data.payload,
619 id = inst.id;
620 if (onosInstances[id]) {
621 logicError('ONOS instance already added: ' + id);
622 return;
623 }
624 onosInstances[id] = inst;
625 onosOrder.push(inst);
626 updateInstances();
627 }
628
Simon Hunt99c13842014-11-06 18:23:12 -0800629 function addDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800630 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800631 var device = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800632 id = device.id,
633 d;
634
635 if (network.lookup[id]) {
636 logicError('Device already added: ' + id);
637 return;
638 }
639
640 d = createDeviceNode(device);
641 network.nodes.push(d);
642 network.lookup[id] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800643 updateNodes();
644 network.force.start();
645 }
646
Simon Hunt99c13842014-11-06 18:23:12 -0800647 function addLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800648 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800649 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800650 result = findLink(link, 'add'),
651 bad = result.badLogic,
Simon Huntca867ac2014-11-28 18:07:35 -0800652 d = result.ldata;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800653
654 if (bad) {
655 logicError(bad + ': ' + link.id);
656 return;
657 }
658
Simon Huntca867ac2014-11-28 18:07:35 -0800659 if (d) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800660 // we already have a backing store link for src/dst nodes
Simon Huntca867ac2014-11-28 18:07:35 -0800661 addLinkUpdate(d, link);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800662 return;
663 }
664
665 // no backing store link yet
Simon Huntca867ac2014-11-28 18:07:35 -0800666 d = createLink(link);
667 if (d) {
668 network.links.push(d);
669 network.lookup[d.key] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800670 updateLinks();
671 network.force.start();
672 }
673 }
674
Simon Hunt56d51852014-11-09 13:03:35 -0800675 function addHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800676 evTrace(data);
Simon Hunt56d51852014-11-09 13:03:35 -0800677 var host = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800678 id = host.id,
679 d,
Simon Hunt56d51852014-11-09 13:03:35 -0800680 lnk;
Simon Huntca867ac2014-11-28 18:07:35 -0800681
682 if (network.lookup[id]) {
683 logicError('Host already added: ' + id);
684 return;
685 }
686
687 d = createHostNode(host);
688 network.nodes.push(d);
689 network.lookup[host.id] = d;
Simon Hunt56d51852014-11-09 13:03:35 -0800690 updateNodes();
691
692 lnk = createHostLink(host);
693 if (lnk) {
Simon Huntca867ac2014-11-28 18:07:35 -0800694 d.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800695 network.links.push(lnk);
Simon Huntca867ac2014-11-28 18:07:35 -0800696 network.lookup[d.ingress] = lnk;
697 network.lookup[d.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800698 updateLinks();
699 }
700 network.force.start();
701 }
702
Simon Hunt44031102014-11-11 13:20:36 -0800703 // TODO: fold updateX(...) methods into one base method; remove duplication
Simon Hunt56a2ea42014-11-19 12:39:31 -0800704
705 function updateInstance(data) {
706 evTrace(data);
707 var inst = data.payload,
708 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800709 d = onosInstances[id];
710 if (d) {
711 $.extend(d, inst);
Simon Hunt56a2ea42014-11-19 12:39:31 -0800712 updateInstances();
Simon Hunt56a2ea42014-11-19 12:39:31 -0800713 } else {
714 logicError('updateInstance lookup fail. ID = "' + id + '"');
715 }
716 }
717
Simon Huntbb282f52014-11-10 11:08:19 -0800718 function updateDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800719 evTrace(data);
Simon Huntbb282f52014-11-10 11:08:19 -0800720 var device = data.payload,
721 id = device.id,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800722 d = network.lookup[id],
723 wasOnline;
724
Simon Hunt62c47542014-11-22 22:16:32 -0800725 if (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -0800726 wasOnline = d.online;
Simon Hunt62c47542014-11-22 22:16:32 -0800727 $.extend(d, device);
728 if (positionNode(d, true)) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800729 sendUpdateMeta(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -0800730 }
731 updateNodes();
Simon Hunt6d9bd032014-11-28 22:16:40 -0800732 if (wasOnline !== d.online) {
733 findAttachedLinks(d.id).forEach(restyleLinkElement);
734 updateOfflineVisibility(d);
735 }
Simon Huntbb282f52014-11-10 11:08:19 -0800736 } else {
737 logicError('updateDevice lookup fail. ID = "' + id + '"');
738 }
739 }
740
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800741 function updateLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800742 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800743 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800744 result = findLink(link, 'update'),
745 bad = result.badLogic;
746 if (bad) {
747 logicError(bad + ': ' + link.id);
748 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800749 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800750 result.updateWith(link);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800751 }
752
Simon Hunt7cd48f32014-11-09 23:42:50 -0800753 function updateHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800754 evTrace(data);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800755 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800756 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800757 d = network.lookup[id];
758 if (d) {
759 $.extend(d, host);
Simon Hunt6d9bd032014-11-28 22:16:40 -0800760 if (positionNode(d, true)) {
761 sendUpdateMeta(d, true);
762 }
763 updateNodes(d);
Simon Huntbb282f52014-11-10 11:08:19 -0800764 } else {
765 logicError('updateHost lookup fail. ID = "' + id + '"');
766 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800767 }
768
Simon Hunt44031102014-11-11 13:20:36 -0800769 // TODO: fold removeX(...) methods into base method - remove dup code
Simon Hunt7b403bc2014-11-22 19:01:00 -0800770 function removeInstance(data) {
771 evTrace(data);
772 var inst = data.payload,
773 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800774 d = onosInstances[id];
775 if (d) {
776 var idx = find(id, onosOrder);
Simon Hunt7b403bc2014-11-22 19:01:00 -0800777 if (idx >= 0) {
778 onosOrder.splice(idx, 1);
779 }
780 delete onosInstances[id];
781 updateInstances();
782 } else {
783 logicError('updateInstance lookup fail. ID = "' + id + '"');
784 }
785 }
786
Simon Huntca867ac2014-11-28 18:07:35 -0800787 function removeDevice(data) {
788 evTrace(data);
789 var device = data.payload,
790 id = device.id,
791 d = network.lookup[id];
792 if (d) {
793 removeDeviceElement(d);
794 } else {
795 logicError('removeDevice lookup fail. ID = "' + id + '"');
796 }
797 }
798
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800799 function removeLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800800 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800801 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800802 result = findLink(link, 'remove'),
803 bad = result.badLogic;
804 if (bad) {
Simon Huntca867ac2014-11-28 18:07:35 -0800805 // may have already removed link, if attached to removed device
806 console.warn(bad + ': ' + link.id);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800807 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800808 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800809 result.removeRawLink();
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800810 }
811
Simon Hunt44031102014-11-11 13:20:36 -0800812 function removeHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800813 evTrace(data);
Simon Hunt44031102014-11-11 13:20:36 -0800814 var host = data.payload,
815 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800816 d = network.lookup[id];
817 if (d) {
818 removeHostElement(d, true);
Simon Hunt44031102014-11-11 13:20:36 -0800819 } else {
Simon Huntca867ac2014-11-28 18:07:35 -0800820 // may have already removed host, if attached to removed device
821 console.warn('removeHost lookup fail. ID = "' + id + '"');
Simon Hunt44031102014-11-11 13:20:36 -0800822 }
823 }
824
Simon Huntca867ac2014-11-28 18:07:35 -0800825 // the following events are server responses to user actions
Thomas Vachuska47635c62014-11-22 01:21:36 -0800826 function showSummary(data) {
827 evTrace(data);
828 populateSummary(data.payload);
Simon Hunt06811b72014-11-25 18:54:48 -0800829 showSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800830 }
831
Simon Hunt61d04042014-11-11 17:27:16 -0800832 function showDetails(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800833 evTrace(data);
Simon Hunt27d322d2014-11-28 10:45:43 -0800834 haveDetails = true;
Simon Hunt61d04042014-11-11 17:27:16 -0800835 populateDetails(data.payload);
Simon Hunt27d322d2014-11-28 10:45:43 -0800836 if (useDetails) {
837 showDetailPane();
838 }
Simon Hunt61d04042014-11-11 17:27:16 -0800839 }
840
Simon Huntb53e0682014-11-12 13:32:01 -0800841 function showTraffic(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800842 evTrace(data);
Thomas Vachuska4731f122014-11-20 04:56:19 -0800843 var paths = data.payload.paths,
844 hasTraffic = false;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800845
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800846 // Revert any links hilighted previously.
Thomas Vachuska4731f122014-11-20 04:56:19 -0800847 link.style('stroke-width', null)
Thomas Vachuskaa3148a72014-11-19 21:38:35 -0800848 .classed('primary secondary animated optical', false);
Simon Hunte2575b62014-11-18 15:25:53 -0800849 // Remove all previous labels.
850 removeLinkLabels();
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800851
Simon Hunte2575b62014-11-18 15:25:53 -0800852 // Now hilight all links in the paths payload, and attach
853 // labels to them, if they are defined.
Simon Hunta255a2c2014-11-13 22:29:35 -0800854 paths.forEach(function (p) {
Simon Hunte2575b62014-11-18 15:25:53 -0800855 var n = p.links.length,
856 i,
857 ldata;
858
Thomas Vachuska4731f122014-11-20 04:56:19 -0800859 hasTraffic = hasTraffic || p.traffic;
Simon Hunte2575b62014-11-18 15:25:53 -0800860 for (i=0; i<n; i++) {
861 ldata = findLinkById(p.links[i]);
Thomas Vachuska4731f122014-11-20 04:56:19 -0800862 if (ldata && ldata.el) {
Simon Hunte2575b62014-11-18 15:25:53 -0800863 ldata.el.classed(p.class, true);
864 ldata.label = p.labels[i];
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800865 }
Simon Hunte2575b62014-11-18 15:25:53 -0800866 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800867 });
Thomas Vachuska4731f122014-11-20 04:56:19 -0800868
Simon Hunte2575b62014-11-18 15:25:53 -0800869 updateLinks();
Thomas Vachuska4731f122014-11-20 04:56:19 -0800870
871 if (hasTraffic && !antTimer) {
872 startAntTimer();
873 } else if (!hasTraffic && antTimer) {
874 stopAntTimer();
875 }
Simon Huntb53e0682014-11-12 13:32:01 -0800876 }
877
Simon Hunt56d51852014-11-09 13:03:35 -0800878 // ...............................
879
Simon Hunt99c13842014-11-06 18:23:12 -0800880 function unknownEvent(data) {
Simon Hunt434cf142014-11-24 11:10:28 -0800881 console.warn('Unknown event type: "' + data.event + '"', data);
Simon Hunt99c13842014-11-06 18:23:12 -0800882 }
883
884 function handleServerEvent(data) {
885 var fn = eventDispatch[data.event] || unknownEvent;
886 fn(data);
887 }
888
889 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -0800890 // Out-going messages...
891
Simon Huntb53e0682014-11-12 13:32:01 -0800892 function nSel() {
893 return selectOrder.length;
894 }
Simon Hunt61d04042014-11-11 17:27:16 -0800895 function getSel(idx) {
896 return selections[selectOrder[idx]];
897 }
Simon Huntb53e0682014-11-12 13:32:01 -0800898 function allSelectionsClass(cls) {
899 for (var i=0, n=nSel(); i<n; i++) {
900 if (getSel(i).obj.class !== cls) {
901 return false;
902 }
903 }
904 return true;
905 }
Simon Hunt61d04042014-11-11 17:27:16 -0800906
Thomas Vachuska47635c62014-11-22 01:21:36 -0800907 function toggleInstances() {
908 if (!oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800909 showInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800910 } else {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800911 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800912 }
913 }
914
Simon Huntb0ecfa52014-11-23 21:05:12 -0800915 function showInstances() {
916 oiBox.show();
917 colorAffinity = true;
918 updateDeviceColors();
919 }
920
921 function hideInstances() {
922 oiBox.hide();
923 colorAffinity = false;
924 cancelAffinity();
925 updateDeviceColors();
926 }
927
Thomas Vachuska47635c62014-11-22 01:21:36 -0800928 function toggleSummary() {
929 if (!summaryPane.isVisible()) {
930 requestSummary();
931 } else {
932 cancelSummary();
933 }
934 }
935
936 // request overall summary data
937 function requestSummary() {
938 sendMessage('requestSummary', {});
939 }
940
941 function cancelSummary() {
942 sendMessage('cancelSummary', {});
Simon Hunt06811b72014-11-25 18:54:48 -0800943 hideSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800944 }
945
Simon Hunt27d322d2014-11-28 10:45:43 -0800946 function toggleDetails() {
947 useDetails = !useDetails;
948 if (useDetails) {
949 flash('Enable details pane');
950 if (haveDetails) {
951 showDetailPane();
952 }
953 } else {
954 flash('Disable details pane');
955 hideDetailPane();
956 }
957 }
958
Simon Hunt06811b72014-11-25 18:54:48 -0800959 // encapsulate interaction between summary and details panes
960 function showSummaryPane() {
961 if (detailPane.isVisible()) {
962 detailPane.down(summaryPane.show);
963 } else {
964 summaryPane.show();
965 }
966 }
967
968 function hideSummaryPane() {
969 summaryPane.hide(function () {
970 if (detailPane.isVisible()) {
971 detailPane.up();
972 }
973 });
974 }
975
976 function showDetailPane() {
977 if (summaryPane.isVisible()) {
978 detailPane.down(detailPane.show);
979 } else {
980 detailPane.up(detailPane.show);
981 }
982 }
983
984 function hideDetailPane() {
985 detailPane.hide();
986 }
987
988
Simon Hunt61d04042014-11-11 17:27:16 -0800989 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -0800990 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -0800991 function requestDetails() {
992 var data = getSel(0).obj,
993 payload = {
994 id: data.id,
995 class: data.class
996 };
997 sendMessage('requestDetails', payload);
998 }
999
Thomas Vachuska9edca302014-11-22 17:06:42 -08001000 function addHostIntentAction() {
Simon Huntd72bc702014-11-13 18:38:04 -08001001 sendMessage('addHostIntent', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001002 one: selectOrder[0],
1003 two: selectOrder[1],
1004 ids: selectOrder
Simon Huntd72bc702014-11-13 18:38:04 -08001005 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001006 flash('Host-to-Host flow added');
Simon Huntd72bc702014-11-13 18:38:04 -08001007 }
1008
Thomas Vachuska9edca302014-11-22 17:06:42 -08001009 function addMultiSourceIntentAction() {
1010 sendMessage('addMultiSourceIntent', {
1011 src: selectOrder.slice(0, selectOrder.length - 1),
1012 dst: selectOrder[selectOrder.length - 1],
1013 ids: selectOrder
1014 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001015 flash('Multi-Source flow added');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001016 }
1017
Thomas Vachuska9edca302014-11-22 17:06:42 -08001018
Thomas Vachuska47635c62014-11-22 01:21:36 -08001019 function cancelTraffic() {
1020 sendMessage('cancelTraffic', {});
1021 }
1022
Thomas Vachuska9edca302014-11-22 17:06:42 -08001023 function requestTrafficForMode() {
1024 if (hoverMode === hoverModeAll) {
1025 requestAllTraffic();
1026 } else if (hoverMode === hoverModeFlows) {
1027 requestDeviceLinkFlows();
1028 } else if (hoverMode === hoverModeIntents) {
1029 requestSelectTraffic();
Simon Huntd72bc702014-11-13 18:38:04 -08001030 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001031 }
Simon Huntd72bc702014-11-13 18:38:04 -08001032
Thomas Vachuska9edca302014-11-22 17:06:42 -08001033 function showTrafficAction() {
1034 hoverMode = hoverModeIntents;
1035 requestSelectTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001036 flash('Related Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001037 }
1038
1039 function requestSelectTraffic() {
1040 if (validateSelectionContext()) {
1041 var hoverId = (hoverMode === hoverModeIntents && hovered &&
1042 (hovered.class === 'host' || hovered.class === 'device'))
Simon Huntd72bc702014-11-13 18:38:04 -08001043 ? hovered.id : '';
Thomas Vachuska9edca302014-11-22 17:06:42 -08001044 sendMessage('requestTraffic', {
1045 ids: selectOrder,
1046 hover: hoverId
1047 });
1048 }
Simon Huntd72bc702014-11-13 18:38:04 -08001049 }
1050
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -08001051
Thomas Vachuska29617e52014-11-20 03:17:46 -08001052 function showDeviceLinkFlowsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001053 hoverMode = hoverModeFlows;
1054 requestDeviceLinkFlows();
Simon Hunt27d322d2014-11-28 10:45:43 -08001055 flash('Device Flows');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001056 }
1057
Thomas Vachuska9edca302014-11-22 17:06:42 -08001058 function requestDeviceLinkFlows() {
1059 if (validateSelectionContext()) {
1060 var hoverId = (hoverMode === hoverModeFlows && hovered &&
1061 (hovered.class === 'device')) ? hovered.id : '';
1062 sendMessage('requestDeviceLinkFlows', {
1063 ids: selectOrder,
1064 hover: hoverId
1065 });
1066 }
1067 }
1068
1069
1070 function showAllTrafficAction() {
1071 hoverMode = hoverModeAll;
1072 requestAllTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001073 flash('All Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001074 }
1075
1076 function requestAllTraffic() {
1077 sendMessage('requestAllTraffic', {});
1078 }
1079
1080 function validateSelectionContext() {
Thomas Vachuska29617e52014-11-20 03:17:46 -08001081 if (!hovered && nSel() === 0) {
Thomas Vachuska47635c62014-11-22 01:21:36 -08001082 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08001083 return false;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001084 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001085 return true;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001086 }
Simon Huntd72bc702014-11-13 18:38:04 -08001087
Simon Hunta6a9fe72014-11-20 11:17:12 -08001088
Simon Hunt61d04042014-11-11 17:27:16 -08001089 // ==============================
Simon Hunta5e89142014-11-14 07:00:33 -08001090 // onos instance panel functions
Simon Huntb82f6902014-11-22 11:53:15 -08001091
Simon Hunt7b403bc2014-11-22 19:01:00 -08001092 var instCfg = {
1093 rectPad: 8,
1094 nodeOx: 9,
1095 nodeOy: 9,
1096 nodeDim: 40,
1097 birdOx: 19,
1098 birdOy: 21,
1099 birdDim: 21,
1100 uiDy: 45,
1101 titleDy: 30,
1102 textYOff: 20,
1103 textYSpc: 15
1104 };
1105
1106 function viewBox(dim) {
1107 return '0 0 ' + dim.w + ' ' + dim.h;
1108 }
1109
1110 function instRectAttr(dim) {
1111 var pad = instCfg.rectPad;
1112 return {
1113 x: pad,
1114 y: pad,
1115 width: dim.w - pad*2,
1116 height: dim.h - pad*2,
Simon Huntb0ecfa52014-11-23 21:05:12 -08001117 rx: 6
Simon Hunt7b403bc2014-11-22 19:01:00 -08001118 };
1119 }
1120
1121 function computeDim(self) {
1122 var css = window.getComputedStyle(self);
1123 return {
1124 w: stripPx(css.width),
1125 h: stripPx(css.height)
1126 };
Simon Huntb82f6902014-11-22 11:53:15 -08001127 }
Simon Hunta5e89142014-11-14 07:00:33 -08001128
1129 function updateInstances() {
1130 var onoses = oiBox.el.selectAll('.onosInst')
Simon Huntb82f6902014-11-22 11:53:15 -08001131 .data(onosOrder, function (d) { return d.id; }),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001132 instDim = {w:0,h:0},
1133 c = instCfg;
Simon Hunta5e89142014-11-14 07:00:33 -08001134
Simon Hunt7b403bc2014-11-22 19:01:00 -08001135 function nSw(n) {
1136 return '# Switches: ' + n;
1137 }
Simon Hunta5e89142014-11-14 07:00:33 -08001138
Simon Huntb82f6902014-11-22 11:53:15 -08001139 // operate on existing onos instances if necessary
1140 onoses.each(function (d) {
1141 var el = d3.select(this),
1142 svg = el.select('svg');
Simon Hunt7b403bc2014-11-22 19:01:00 -08001143 instDim = computeDim(this);
Simon Huntb82f6902014-11-22 11:53:15 -08001144
1145 // update online state
1146 el.classed('online', d.online);
1147
1148 // update ui-attached state
1149 svg.select('use.uiBadge').remove();
1150 if (d.uiAttached) {
1151 attachUiBadge(svg);
1152 }
1153
Simon Hunt7b403bc2014-11-22 19:01:00 -08001154 function updAttr(id, value) {
1155 svg.select('text.instLabel.'+id).text(value);
1156 }
1157
1158 updAttr('ip', d.ip);
1159 updAttr('ns', nSw(d.switches));
Simon Huntb82f6902014-11-22 11:53:15 -08001160 });
1161
1162
1163 // operate on new onos instances
Simon Hunta5e89142014-11-14 07:00:33 -08001164 var entering = onoses.enter()
1165 .append('div')
1166 .attr('class', 'onosInst')
1167 .classed('online', function (d) { return d.online; })
Simon Hunt9c15eca2014-11-15 18:37:59 -08001168 .on('click', clickInst);
1169
Simon Huntb82f6902014-11-22 11:53:15 -08001170 entering.each(function (d) {
Simon Hunt9c15eca2014-11-15 18:37:59 -08001171 var el = d3.select(this),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001172 rectAttr,
1173 svg;
1174 instDim = computeDim(this);
1175 rectAttr = instRectAttr(instDim);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001176
Simon Hunt7b403bc2014-11-22 19:01:00 -08001177 svg = el.append('svg').attr({
1178 width: instDim.w,
1179 height: instDim.h,
1180 viewBox: viewBox(instDim)
Simon Hunt95908012014-11-20 10:20:26 -08001181 });
Simon Huntb82f6902014-11-22 11:53:15 -08001182
Simon Hunt7b403bc2014-11-22 19:01:00 -08001183 svg.append('rect').attr(rectAttr);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001184
Thomas Vachuskae02e11c2014-11-24 16:13:52 -08001185 //appendGlyph(svg, c.nodeOx, c.nodeOy, c.nodeDim, '#node');
1186 appendBadge(svg, 14, 14, 28, '#bird');
Simon Huntb82f6902014-11-22 11:53:15 -08001187
1188 if (d.uiAttached) {
1189 attachUiBadge(svg);
1190 }
1191
Simon Hunt7b403bc2014-11-22 19:01:00 -08001192 var left = c.nodeOx + c.nodeDim,
1193 len = rectAttr.width - left,
1194 hlen = len / 2,
1195 midline = hlen + left;
Simon Hunt9c15eca2014-11-15 18:37:59 -08001196
Simon Hunt7b403bc2014-11-22 19:01:00 -08001197 // title
1198 svg.append('text')
1199 .attr({
1200 class: 'instTitle',
1201 x: midline,
1202 y: c.titleDy
1203 })
1204 .text(d.id);
1205
1206 // a couple of attributes
1207 var ty = c.titleDy + c.textYOff;
1208
1209 function addAttr(id, label) {
1210 svg.append('text').attr({
1211 class: 'instLabel ' + id,
1212 x: midline,
1213 y: ty
1214 }).text(label);
1215 ty += c.textYSpc;
1216 }
1217
1218 addAttr('ip', d.ip);
1219 addAttr('ns', nSw(d.switches));
Simon Hunt9c15eca2014-11-15 18:37:59 -08001220 });
Simon Hunta5e89142014-11-14 07:00:33 -08001221
1222 // operate on existing + new onoses here
Simon Hunt8f40cce2014-11-23 15:57:30 -08001223 // set the affinity colors...
1224 onoses.each(function (d) {
1225 var el = d3.select(this),
1226 rect = el.select('svg').select('rect'),
1227 col = instColor(d.id, d.online);
1228 rect.style('fill', col);
1229 });
Simon Hunta5e89142014-11-14 07:00:33 -08001230
Simon Hunt7b403bc2014-11-22 19:01:00 -08001231 // adjust the panel size appropriately...
1232 oiBox.width(instDim.w * onosOrder.length);
1233 oiBox.height(instDim.h);
1234
1235 // remove any outgoing instances
1236 onoses.exit().remove();
Simon Hunta5e89142014-11-14 07:00:33 -08001237 }
1238
Simon Hunt8f40cce2014-11-23 15:57:30 -08001239 function instColor(id, online) {
1240 return cat7.get(id, !online, network.view.getTheme());
1241 }
1242
Simon Hunt9462e8c2014-11-14 17:28:09 -08001243 function clickInst(d) {
1244 var el = d3.select(this),
1245 aff = el.classed('affinity');
1246 if (!aff) {
1247 setAffinity(el, d);
1248 } else {
1249 cancelAffinity();
1250 }
1251 }
1252
1253 function setAffinity(el, d) {
1254 d3.selectAll('.onosInst')
1255 .classed('mastership', true)
1256 .classed('affinity', false);
1257 el.classed('affinity', true);
1258
1259 suppressLayers(true);
1260 node.each(function (n) {
1261 if (n.master === d.id) {
1262 n.el.classed('suppressed', false);
1263 }
1264 });
1265 oiShowMaster = true;
1266 }
1267
1268 function cancelAffinity() {
1269 d3.selectAll('.onosInst')
1270 .classed('mastership affinity', false);
1271 restoreLayerState();
1272 oiShowMaster = false;
1273 }
1274
Simon Hunt7b403bc2014-11-22 19:01:00 -08001275 // TODO: these should be moved out to utility module.
1276 function stripPx(s) {
1277 return s.replace(/px$/,'');
1278 }
1279
1280 function appendUse(svg, ox, oy, dim, iid, cls) {
1281 var use = svg.append('use').attr({
1282 transform: translate(ox,oy),
1283 'xlink:href': iid,
1284 width: dim,
1285 height: dim
1286 });
1287 if (cls) {
1288 use.classed(cls, true);
1289 }
1290 return use;
1291 }
1292
1293 function appendGlyph(svg, ox, oy, dim, iid, cls) {
1294 appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
1295 }
1296
1297 function appendBadge(svg, ox, oy, dim, iid, cls) {
1298 appendUse(svg, ox, oy, dim, iid,cls ).classed('badgeIcon', true);
1299 }
1300
1301 function attachUiBadge(svg) {
1302 appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
1303 }
1304
Simon Hunt434cf142014-11-24 11:10:28 -08001305 function visVal(b) {
1306 return b ? 'visible' : 'hidden';
1307 }
1308
Simon Hunta5e89142014-11-14 07:00:33 -08001309 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -08001310 // force layout modification functions
1311
1312 function translate(x, y) {
1313 return 'translate(' + x + ',' + y + ')';
1314 }
1315
Simon Hunte2575b62014-11-18 15:25:53 -08001316 function rotate(deg) {
1317 return 'rotate(' + deg + ')';
1318 }
1319
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001320 function missMsg(what, id) {
1321 return '\n[' + what + '] "' + id + '" missing ';
1322 }
1323
1324 function linkEndPoints(srcId, dstId) {
1325 var srcNode = network.lookup[srcId],
1326 dstNode = network.lookup[dstId],
1327 sMiss = !srcNode ? missMsg('src', srcId) : '',
1328 dMiss = !dstNode ? missMsg('dst', dstId) : '';
1329
1330 if (sMiss || dMiss) {
1331 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
1332 return null;
1333 }
1334 return {
1335 source: srcNode,
1336 target: dstNode,
1337 x1: srcNode.x,
1338 y1: srcNode.y,
1339 x2: dstNode.x,
1340 y2: dstNode.y
1341 };
1342 }
1343
Simon Hunt56d51852014-11-09 13:03:35 -08001344 function createHostLink(host) {
1345 var src = host.id,
1346 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -08001347 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001348 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -08001349
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001350 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -08001351 return null;
1352 }
1353
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001354 // Synthesize link ...
1355 $.extend(lnk, {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001356 key: id,
Simon Hunt56d51852014-11-09 13:03:35 -08001357 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001358
1359 type: function () { return 'hostLink'; },
Simon Hunt6d9bd032014-11-28 22:16:40 -08001360 online: function () {
1361 // hostlink target is edge switch
1362 return lnk.target.online;
1363 },
Simon Hunt8257f4c2014-11-16 19:34:54 -08001364 linkWidth: function () { return 1; }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001365 });
Simon Hunt99c13842014-11-06 18:23:12 -08001366 return lnk;
1367 }
1368
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001369 function createLink(link) {
Simon Hunta6a9fe72014-11-20 11:17:12 -08001370 var lnk = linkEndPoints(link.src, link.dst);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001371
1372 if (!lnk) {
1373 return null;
1374 }
1375
Simon Hunt8257f4c2014-11-16 19:34:54 -08001376 $.extend(lnk, {
1377 key: link.id,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001378 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001379 fromSource: link,
1380
1381 // functions to aggregate dual link state
1382 type: function () {
1383 var s = lnk.fromSource,
1384 t = lnk.fromTarget;
1385 return (s && s.type) || (t && t.type) || defaultLinkType;
1386 },
1387 online: function () {
1388 var s = lnk.fromSource,
Simon Hunt6d9bd032014-11-28 22:16:40 -08001389 t = lnk.fromTarget,
1390 both = lnk.source.online && lnk.target.online;
1391 return both && ((s && s.online) || (t && t.online));
Simon Hunt8257f4c2014-11-16 19:34:54 -08001392 },
1393 linkWidth: function () {
1394 var s = lnk.fromSource,
1395 t = lnk.fromTarget,
1396 ws = (s && s.linkWidth) || 0,
1397 wt = (t && t.linkWidth) || 0;
1398 return Math.max(ws, wt);
1399 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001400 });
1401 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -08001402 }
1403
Simon Hunte2575b62014-11-18 15:25:53 -08001404 function removeLinkLabels() {
1405 network.links.forEach(function (d) {
1406 d.label = '';
1407 });
1408 }
1409
Simon Hunt434cf142014-11-24 11:10:28 -08001410 function showHostVis(el) {
1411 el.style('visibility', visVal(showHosts));
1412 }
1413
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001414 var widthRatio = 1.4,
1415 linkScale = d3.scale.linear()
1416 .domain([1, 12])
1417 .range([widthRatio, 12 * widthRatio])
1418 .clamp(true);
1419
Simon Hunt99c13842014-11-06 18:23:12 -08001420 function updateLinks() {
1421 link = linkG.selectAll('.link')
Simon Hunt8257f4c2014-11-16 19:34:54 -08001422 .data(network.links, function (d) { return d.key; });
Simon Hunt99c13842014-11-06 18:23:12 -08001423
1424 // operate on existing links, if necessary
1425 // link .foo() .bar() ...
1426
1427 // operate on entering links:
1428 var entering = link.enter()
1429 .append('line')
1430 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001431 x1: function (d) { return d.x1; },
1432 y1: function (d) { return d.y1; },
1433 x2: function (d) { return d.x2; },
1434 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001435 stroke: config.topo.linkInColor,
1436 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -08001437 });
1438
1439 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -08001440 entering.each(function (d) {
1441 var link = d3.select(this);
1442 // provide ref to element selection from backing data....
1443 d.el = link;
Simon Hunt8257f4c2014-11-16 19:34:54 -08001444 restyleLinkElement(d);
Simon Hunt434cf142014-11-24 11:10:28 -08001445 if (d.type() === 'hostLink') {
1446 showHostVis(link);
1447 }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001448 });
Thomas Vachuska4830d392014-11-09 17:09:56 -08001449
1450 // operate on both existing and new links, if necessary
1451 //link .foo() .bar() ...
1452
Simon Hunte2575b62014-11-18 15:25:53 -08001453 // apply or remove labels
1454 var labelData = getLabelData();
1455 applyLinkLabels(labelData);
1456
Thomas Vachuska4830d392014-11-09 17:09:56 -08001457 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -08001458 link.exit()
Simon Hunt6d9bd032014-11-28 22:16:40 -08001459 .attr('stroke-dasharray', '3 3')
Simon Hunt13bf9c82014-11-18 07:26:44 -08001460 .style('opacity', 0.5)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001461 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -08001462 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001463 .attr({
Simon Hunt6d9bd032014-11-28 22:16:40 -08001464 'stroke-dasharray': '3 12',
Simon Hunt13bf9c82014-11-18 07:26:44 -08001465 stroke: config.topo.linkOutColor,
1466 'stroke-width': config.topo.linkOutWidth
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001467 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001468 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -08001469 .remove();
Simon Hunte2575b62014-11-18 15:25:53 -08001470
1471 // NOTE: invoke a single tick to force the labels to position
1472 // onto their links.
1473 tick();
1474 }
1475
1476 function getLabelData() {
1477 // create the backing data for showing labels..
1478 var data = [];
1479 link.each(function (d) {
1480 if (d.label) {
1481 data.push({
1482 id: 'lab-' + d.key,
1483 key: d.key,
1484 label: d.label,
1485 ldata: d
1486 });
1487 }
1488 });
1489 return data;
1490 }
1491
1492 var linkLabelOffset = '0.3em';
1493
1494 function applyLinkLabels(data) {
1495 var entering;
1496
1497 linkLabel = linkLabelG.selectAll('.linkLabel')
1498 .data(data, function (d) { return d.id; });
1499
Simon Hunt56a2ea42014-11-19 12:39:31 -08001500 // for elements already existing, we need to update the text
1501 // and adjust the rectangle size to fit
1502 linkLabel.each(function (d) {
1503 var el = d3.select(this),
1504 rect = el.select('rect'),
1505 text = el.select('text');
1506 text.text(d.label);
1507 rect.attr(rectAroundText(el));
1508 });
Thomas Vachuskaf75b7ab2014-11-19 12:15:55 -08001509
Simon Hunte2575b62014-11-18 15:25:53 -08001510 entering = linkLabel.enter().append('g')
1511 .classed('linkLabel', true)
1512 .attr('id', function (d) { return d.id; });
1513
1514 entering.each(function (d) {
1515 var el = d3.select(this),
1516 rect,
1517 text,
1518 parms = {
1519 x1: d.ldata.x1,
1520 y1: d.ldata.y1,
1521 x2: d.ldata.x2,
1522 y2: d.ldata.y2
1523 };
1524
1525 d.el = el;
1526 rect = el.append('rect');
1527 text = el.append('text').text(d.label);
1528 rect.attr(rectAroundText(el));
1529 text.attr('dy', linkLabelOffset);
1530
1531 el.attr('transform', transformLabel(parms));
1532 });
1533
1534 // Remove any links that are no longer required.
1535 linkLabel.exit().remove();
1536 }
1537
1538 function rectAroundText(el) {
1539 var text = el.select('text'),
1540 box = text.node().getBBox();
1541
1542 // translate the bbox so that it is centered on [x,y]
1543 box.x = -box.width / 2;
1544 box.y = -box.height / 2;
1545
1546 // add padding
1547 box.x -= 1;
1548 box.width += 2;
1549 return box;
1550 }
1551
1552 function transformLabel(p) {
1553 var dx = p.x2 - p.x1,
1554 dy = p.y2 - p.y1,
1555 xMid = dx/2 + p.x1,
1556 yMid = dy/2 + p.y1;
Simon Hunte2575b62014-11-18 15:25:53 -08001557 return translate(xMid, yMid);
Simon Hunt99c13842014-11-06 18:23:12 -08001558 }
1559
1560 function createDeviceNode(device) {
1561 // start with the object as is
1562 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -08001563 type = device.type,
Simon Huntc72967b2014-11-20 09:21:42 -08001564 svgCls = type ? 'node device ' + type : 'node device',
1565 labels = device.labels || [];
1566
Simon Hunt99c13842014-11-06 18:23:12 -08001567 // Augment as needed...
1568 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -08001569 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -08001570 positionNode(node);
Simon Hunt99c13842014-11-06 18:23:12 -08001571 return node;
1572 }
1573
Simon Hunt56d51852014-11-09 13:03:35 -08001574 function createHostNode(host) {
1575 // start with the object as is
1576 var node = host;
1577
1578 // Augment as needed...
1579 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001580 if (!node.type) {
Simon Hunt209155e2014-11-21 12:16:09 -08001581 node.type = 'endstation';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001582 }
Simon Hunt7fa116d2014-11-17 14:16:55 -08001583 node.svgClass = 'node host ' + node.type;
Simon Hunt56d51852014-11-09 13:03:35 -08001584 positionNode(node);
Simon Hunt56d51852014-11-09 13:03:35 -08001585 return node;
1586 }
1587
Simon Hunt62c47542014-11-22 22:16:32 -08001588 function positionNode(node, forUpdate) {
Simon Hunt99c13842014-11-06 18:23:12 -08001589 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -08001590 x = meta && meta.x,
1591 y = meta && meta.y,
1592 xy;
Simon Hunt99c13842014-11-06 18:23:12 -08001593
Simon Huntac9e24f2014-11-12 10:12:21 -08001594 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -08001595 if (x && y) {
1596 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001597 node.px = node.x = x;
1598 node.py = node.y = y;
Simon Huntac9e24f2014-11-12 10:12:21 -08001599 return;
Simon Hunt99c13842014-11-06 18:23:12 -08001600 }
Simon Huntac9e24f2014-11-12 10:12:21 -08001601
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001602 var location = node.location;
1603 if (location && location.type === 'latlng') {
1604 var coord = geoMapProjection([location.lng, location.lat]);
1605 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001606 node.px = node.x = coord[0];
1607 node.py = node.y = coord[1];
Simon Hunt62c47542014-11-22 22:16:32 -08001608 return true;
1609 }
1610
1611 // if this is a node update (not a node add).. skip randomizer
1612 if (forUpdate) {
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001613 return;
1614 }
1615
Simon Huntac9e24f2014-11-12 10:12:21 -08001616 // Note: Placing incoming unpinned nodes at exactly the same point
1617 // (center of the view) causes them to explode outwards when
1618 // the force layout kicks in. So, we spread them out a bit
1619 // initially, to provide a more serene layout convergence.
1620 // Additionally, if the node is a host, we place it near
1621 // the device it is connected to.
1622
1623 function spread(s) {
1624 return Math.floor((Math.random() * s) - s/2);
1625 }
1626
1627 function randDim(dim) {
1628 return dim / 2 + spread(dim * 0.7071);
1629 }
1630
1631 function rand() {
1632 return {
1633 x: randDim(network.view.width()),
1634 y: randDim(network.view.height())
1635 };
1636 }
1637
1638 function near(node) {
1639 var min = 12,
1640 dx = spread(12),
1641 dy = spread(12);
1642 return {
1643 x: node.x + min + dx,
1644 y: node.y + min + dy
1645 };
1646 }
1647
1648 function getDevice(cp) {
1649 var d = network.lookup[cp.device];
1650 return d || rand();
1651 }
1652
1653 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1654 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -08001655 }
1656
Simon Hunt99c13842014-11-06 18:23:12 -08001657
Simon Huntc72967b2014-11-20 09:21:42 -08001658 function iconGlyphUrl(d) {
1659 var which = d.type || 'unknown';
1660 return '#' + which;
1661 }
1662
Simon Hunt99c13842014-11-06 18:23:12 -08001663 // returns the newly computed bounding box of the rectangle
1664 function adjustRectToFitText(n) {
1665 var text = n.select('text'),
1666 box = text.node().getBBox(),
1667 lab = config.labels;
1668
1669 text.attr('text-anchor', 'middle')
1670 .attr('y', '-0.8em')
1671 .attr('x', lab.imgPad/2);
1672
1673 // translate the bbox so that it is centered on [x,y]
1674 box.x = -box.width / 2;
1675 box.y = -box.height / 2;
1676
1677 // add padding
1678 box.x -= (lab.padLR + lab.imgPad/2);
1679 box.width += lab.padLR * 2 + lab.imgPad;
1680 box.y -= lab.padTB;
1681 box.height += lab.padTB * 2;
1682
1683 return box;
1684 }
1685
Simon Hunt1a9eff92014-11-07 11:06:34 -08001686 function mkSvgClass(d) {
1687 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1688 }
1689
Simon Hunt7cd48f32014-11-09 23:42:50 -08001690 function hostLabel(d) {
1691 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1692 return d.labels[idx];
1693 }
1694 function deviceLabel(d) {
1695 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1696 return d.labels[idx];
1697 }
Simon Huntc72967b2014-11-20 09:21:42 -08001698 function trimLabel(label) {
1699 return (label && label.trim()) || '';
1700 }
1701
1702 function emptyBox() {
1703 return {
1704 x: -2,
1705 y: -2,
1706 width: 4,
1707 height: 4
1708 };
Simon Hunt7cd48f32014-11-09 23:42:50 -08001709 }
1710
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001711 function updateDeviceLabel(d) {
Simon Huntc72967b2014-11-20 09:21:42 -08001712 var label = trimLabel(deviceLabel(d)),
1713 noLabel = !label,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001714 node = d.el,
Simon Huntc72967b2014-11-20 09:21:42 -08001715 box,
1716 dx,
Simon Hunt395a70c2014-11-22 23:17:40 -08001717 dy,
1718 cfg = config.icons.device;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001719
1720 node.select('text')
1721 .text(label)
1722 .style('opacity', 0)
1723 .transition()
1724 .style('opacity', 1);
1725
Simon Huntc72967b2014-11-20 09:21:42 -08001726 if (noLabel) {
1727 box = emptyBox();
Simon Hunt395a70c2014-11-22 23:17:40 -08001728 dx = -cfg.dim/2;
1729 dy = -cfg.dim/2;
Simon Huntc72967b2014-11-20 09:21:42 -08001730 } else {
1731 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08001732 dx = box.x + cfg.xoff;
1733 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08001734 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001735
1736 node.select('rect')
1737 .transition()
1738 .attr(box);
1739
Simon Huntc72967b2014-11-20 09:21:42 -08001740 node.select('g.deviceIcon')
Thomas Vachuska89543292014-11-19 11:28:33 -08001741 .transition()
Simon Huntc72967b2014-11-20 09:21:42 -08001742 .attr('transform', translate(dx, dy));
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001743 }
1744
1745 function updateHostLabel(d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001746 var label = trimLabel(hostLabel(d));
1747 d.el.select('text').text(label);
Simon Huntbb282f52014-11-10 11:08:19 -08001748 }
1749
Simon Hunt434cf142014-11-24 11:10:28 -08001750 function updateHostVisibility() {
1751 var v = visVal(showHosts);
1752 nodeG.selectAll('.host').style('visibility', v);
1753 linkG.selectAll('.hostLink').style('visibility', v);
1754 }
1755
Simon Hunt6d9bd032014-11-28 22:16:40 -08001756 function findOfflineNodes() {
1757 var a = [];
1758 network.nodes.forEach(function (d) {
1759 if (d.class === 'device' && !d.online) {
1760 a.push(d);
1761 }
1762 });
1763 return a;
1764 }
1765
1766 function updateOfflineVisibility(dev) {
1767 var so = showOffline,
1768 sh = showHosts,
1769 vb = 'visibility',
1770 v, off, al, ah, db, b;
1771
1772 function updAtt(show) {
1773 al.forEach(function (d) {
1774 b = show && ((d.type() !== 'hostLink') || sh);
1775 d.el.style(vb, visVal(b));
1776 });
1777 ah.forEach(function (d) {
1778 b = show && sh;
1779 d.el.style(vb, visVal(b));
1780 });
1781 }
1782
1783 if (dev) {
1784 // updating a specific device that just toggled off/on-line
1785 db = dev.online || so;
1786 dev.el.style(vb, visVal(db));
1787 al = findAttachedLinks(dev.id);
1788 ah = findAttachedHosts(dev.id);
1789 updAtt(db);
1790 } else {
1791 // updating all offline devices
1792 v = visVal(so);
1793 off = findOfflineNodes();
1794 off.forEach(function (d) {
1795 d.el.style(vb, v);
1796 al = findAttachedLinks(d.id);
1797 ah = findAttachedHosts(d.id);
1798 updAtt(so);
1799 });
1800 }
1801 }
1802
Simon Hunt6ac93f32014-11-13 12:17:27 -08001803 function nodeMouseOver(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001804 hovered = d;
Thomas Vachuska9edca302014-11-22 17:06:42 -08001805 requestTrafficForMode();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001806 }
1807
1808 function nodeMouseOut(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001809 hovered = null;
Thomas Vachuska9edca302014-11-22 17:06:42 -08001810 requestTrafficForMode();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001811 }
Simon Huntbb282f52014-11-10 11:08:19 -08001812
Simon Hunteb1514d2014-11-20 09:57:29 -08001813 function addHostIcon(node, radius, iid) {
Thomas Vachuska89543292014-11-19 11:28:33 -08001814 var dim = radius * 1.5,
1815 xlate = -dim / 2;
1816
Simon Hunteb1514d2014-11-20 09:57:29 -08001817 node.append('use').attr({
1818 class: 'glyphIcon hostIcon',
1819 transform: translate(xlate,xlate),
1820 'xlink:href': iid,
1821 width: dim,
1822 height: dim
1823 });
Thomas Vachuska89543292014-11-19 11:28:33 -08001824 }
1825
Simon Hunt99c13842014-11-06 18:23:12 -08001826 function updateNodes() {
1827 node = nodeG.selectAll('.node')
1828 .data(network.nodes, function (d) { return d.id; });
1829
Simon Hunt62c47542014-11-22 22:16:32 -08001830 // operate on existing nodes...
1831 node.filter('.device').each(function (d) {
Simon Hunt62c47542014-11-22 22:16:32 -08001832 var node = d.el;
1833 node.classed('online', d.online);
1834 updateDeviceLabel(d);
1835 positionNode(d, true);
1836 });
1837
1838 node.filter('.host').each(function (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001839 updateHostLabel(d);
1840 positionNode(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -08001841 });
Simon Hunt99c13842014-11-06 18:23:12 -08001842
1843 // operate on entering nodes:
1844 var entering = node.enter()
1845 .append('g')
1846 .attr({
1847 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001848 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -08001849 transform: function (d) { return translate(d.x, d.y); },
1850 opacity: 0
1851 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08001852 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -08001853 .on('mouseover', nodeMouseOver)
1854 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -08001855 .transition()
1856 .attr('opacity', 1);
1857
1858 // augment device nodes...
1859 entering.filter('.device').each(function (d) {
1860 var node = d3.select(this),
Simon Huntc72967b2014-11-20 09:21:42 -08001861 label = trimLabel(deviceLabel(d)),
1862 noLabel = !label,
Simon Hunt99c13842014-11-06 18:23:12 -08001863 box;
1864
Simon Hunt7cd48f32014-11-09 23:42:50 -08001865 // provide ref to element from backing data....
1866 d.el = node;
1867
Simon Hunt62c47542014-11-22 22:16:32 -08001868 node.append('rect').attr({ rx: 5, ry: 5 });
1869 node.append('text').text(label).attr('dy', '1.1em');
Simon Hunt99c13842014-11-06 18:23:12 -08001870 box = adjustRectToFitText(node);
Simon Hunta3dd9572014-11-20 15:22:41 -08001871 node.select('rect').attr(box);
Simon Huntc72967b2014-11-20 09:21:42 -08001872 addDeviceIcon(node, box, noLabel, iconGlyphUrl(d));
Simon Huntc7ee0662014-11-05 16:44:37 -08001873 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08001874
Thomas Vachuska89543292014-11-19 11:28:33 -08001875
Simon Hunt56d51852014-11-09 13:03:35 -08001876 // augment host nodes...
1877 entering.filter('.host').each(function (d) {
1878 var node = d3.select(this),
Simon Hunt395a70c2014-11-22 23:17:40 -08001879 cfg = config.icons.host,
1880 r = cfg.radius[d.type] || cfg.defaultRadius,
Thomas Vachuska89543292014-11-19 11:28:33 -08001881 textDy = r + 10,
Simon Hunteb1514d2014-11-20 09:57:29 -08001882 iid = iconGlyphUrl(d);
Simon Hunt56d51852014-11-09 13:03:35 -08001883
Simon Hunt7cd48f32014-11-09 23:42:50 -08001884 // provide ref to element from backing data....
1885 d.el = node;
Simon Hunt434cf142014-11-24 11:10:28 -08001886 showHostVis(node);
Simon Hunt7cd48f32014-11-09 23:42:50 -08001887
Simon Hunt62c47542014-11-22 22:16:32 -08001888 node.append('circle').attr('r', r);
Simon Hunteb1514d2014-11-20 09:57:29 -08001889 if (iid) {
1890 addHostIcon(node, r, iid);
Simon Hunt7fa116d2014-11-17 14:16:55 -08001891 }
Simon Hunt56d51852014-11-09 13:03:35 -08001892 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001893 .text(hostLabel)
Thomas Vachuska89543292014-11-19 11:28:33 -08001894 .attr('dy', textDy)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001895 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08001896 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001897
Simon Hunt99c13842014-11-06 18:23:12 -08001898 // operate on both existing and new nodes, if necessary
Simon Huntb0ecfa52014-11-23 21:05:12 -08001899 updateDeviceColors();
Simon Huntc7ee0662014-11-05 16:44:37 -08001900
Simon Hunt99c13842014-11-06 18:23:12 -08001901 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08001902 // Note that the node is removed after 2 seconds.
1903 // Sub element animations should be shorter than 2 seconds.
1904 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08001905 .transition()
1906 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08001907 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08001908 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08001909
1910 // host node exits....
1911 exiting.filter('.host').each(function (d) {
Simon Huntca867ac2014-11-28 18:07:35 -08001912 var node = d.el;
1913 node.select('use')
1914 .style('opacity', 0.5)
1915 .transition()
1916 .duration(800)
1917 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001918
1919 node.select('text')
1920 .style('opacity', 0.5)
1921 .transition()
Simon Huntca867ac2014-11-28 18:07:35 -08001922 .duration(800)
Simon Huntea80eb42014-11-11 13:46:57 -08001923 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001924
Thomas Vachuska89543292014-11-19 11:28:33 -08001925 node.select('circle')
1926 .style('stroke-fill', '#555')
1927 .style('fill', '#888')
Simon Huntea80eb42014-11-11 13:46:57 -08001928 .style('opacity', 0.5)
1929 .transition()
1930 .duration(1500)
1931 .attr('r', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001932 });
1933
Simon Huntca867ac2014-11-28 18:07:35 -08001934 // device node exits....
1935 exiting.filter('.device').each(function (d) {
1936 var node = d.el;
1937 node.select('use')
1938 .style('opacity', 0.5)
1939 .transition()
1940 .duration(800)
1941 .style('opacity', 0);
1942
1943 node.selectAll('rect')
1944 .style('stroke-fill', '#555')
1945 .style('fill', '#888')
1946 .style('opacity', 0.5);
1947 });
Simon Hunt62c47542014-11-22 22:16:32 -08001948
1949 network.force.resume();
Simon Huntc7ee0662014-11-05 16:44:37 -08001950 }
1951
Simon Hunt95dad922014-11-24 09:43:31 -08001952 var dCol = {
1953 black: '#000',
1954 paleblue: '#acf',
1955 offwhite: '#ddd',
1956 midgrey: '#888',
1957 lightgrey: '#bbb',
1958 orange: '#f90'
1959 };
1960
Simon Huntb0ecfa52014-11-23 21:05:12 -08001961 // note: these are the device icon colors without affinity
Simon Hunt95dad922014-11-24 09:43:31 -08001962 var dColTheme = {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001963 light: {
1964 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08001965 glyph: dCol.black,
1966 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08001967 },
1968 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08001969 glyph: dCol.midgrey,
1970 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08001971 }
1972 },
Simon Hunt95dad922014-11-24 09:43:31 -08001973 // TODO: theme
Simon Huntb0ecfa52014-11-23 21:05:12 -08001974 dark: {
1975 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08001976 glyph: dCol.black,
1977 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08001978 },
1979 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08001980 glyph: dCol.midgrey,
1981 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08001982 }
1983 }
1984 };
1985
1986 function devBaseColor(d) {
1987 var t = network.view.getTheme(),
1988 o = d.online ? 'online' : 'offline';
Simon Hunt95dad922014-11-24 09:43:31 -08001989 return dColTheme[t][o];
Simon Huntb0ecfa52014-11-23 21:05:12 -08001990 }
1991
1992 function setDeviceColor(d) {
1993 var o = d.online,
1994 s = d.el.classed('selected'),
1995 c = devBaseColor(d),
1996 a = instColor(d.master, o),
1997 g, r,
1998 icon = d.el.select('g.deviceIcon');
1999
2000 if (s) {
2001 g = c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002002 r = dColTheme.sel;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002003 } else if (colorAffinity) {
2004 g = o ? a : c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002005 r = o ? dCol.offwhite : a;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002006 } else {
2007 g = c.glyph;
2008 r = c.rect;
2009 }
2010
2011 icon.select('use')
2012 .style('fill', g);
2013 icon.select('rect')
2014 .style('fill', r);
2015 }
2016
Simon Huntc72967b2014-11-20 09:21:42 -08002017 function addDeviceIcon(node, box, noLabel, iid) {
2018 var cfg = config.icons.device,
2019 dx,
2020 dy,
2021 g;
2022
2023 if (noLabel) {
2024 box = emptyBox();
2025 dx = -cfg.dim/2;
2026 dy = -cfg.dim/2;
2027 } else {
2028 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08002029 dx = box.x + cfg.xoff;
2030 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08002031 }
2032
Simon Hunteb1514d2014-11-20 09:57:29 -08002033 g = node.append('g')
2034 .attr('class', 'glyphIcon deviceIcon')
Simon Huntc72967b2014-11-20 09:21:42 -08002035 .attr('transform', translate(dx, dy));
2036
2037 g.append('rect').attr({
2038 x: 0,
2039 y: 0,
2040 rx: cfg.rx,
2041 width: cfg.dim,
2042 height: cfg.dim
2043 });
2044
2045 g.append('use').attr({
2046 'xlink:href': iid,
2047 width: cfg.dim,
2048 height: cfg.dim
2049 });
2050
Simon Huntc72967b2014-11-20 09:21:42 -08002051 }
2052
Simon Hunt7b403bc2014-11-22 19:01:00 -08002053 function find(key, array, tag) {
Simon Huntca867ac2014-11-28 18:07:35 -08002054 var _tag = tag || 'id',
Simon Hunt7b403bc2014-11-22 19:01:00 -08002055 idx, n, d;
2056 for (idx = 0, n = array.length; idx < n; idx++) {
2057 d = array[idx];
2058 if (d[_tag] === key) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002059 return idx;
2060 }
2061 }
2062 return -1;
2063 }
2064
Simon Huntca867ac2014-11-28 18:07:35 -08002065 function removeLinkElement(d) {
2066 var idx = find(d.key, network.links, 'key'),
Simon Hunt8257f4c2014-11-16 19:34:54 -08002067 removed;
2068 if (idx >=0) {
2069 // remove from links array
2070 removed = network.links.splice(idx, 1);
2071 // remove from lookup cache
2072 delete network.lookup[removed[0].key];
2073 updateLinks();
2074 network.force.resume();
2075 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002076 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002077
Simon Huntca867ac2014-11-28 18:07:35 -08002078 function removeHostElement(d, upd) {
2079 var lu = network.lookup;
Simon Hunt44031102014-11-11 13:20:36 -08002080 // first, remove associated hostLink...
Simon Huntca867ac2014-11-28 18:07:35 -08002081 removeLinkElement(d.linkData);
2082
2083 // remove hostLink bindings
2084 delete lu[d.ingress];
2085 delete lu[d.egress];
Simon Hunt44031102014-11-11 13:20:36 -08002086
2087 // remove from lookup cache
Simon Huntca867ac2014-11-28 18:07:35 -08002088 delete lu[d.id];
Simon Hunt44031102014-11-11 13:20:36 -08002089 // remove from nodes array
Simon Huntca867ac2014-11-28 18:07:35 -08002090 var idx = find(d.id, network.nodes);
2091 network.nodes.splice(idx, 1);
2092 // remove from SVG
2093 // NOTE: upd is false if we were called from removeDeviceElement()
2094 if (upd) {
2095 updateNodes();
2096 network.force.resume();
2097 }
2098 }
2099
2100
2101 function removeDeviceElement(d) {
2102 var id = d.id;
2103 // first, remove associated hosts and links..
2104 findAttachedHosts(id).forEach(removeHostElement);
2105 findAttachedLinks(id).forEach(removeLinkElement);
2106
2107 // remove from lookup cache
2108 delete network.lookup[id];
2109 // remove from nodes array
2110 var idx = find(id, network.nodes);
Simon Hunt44031102014-11-11 13:20:36 -08002111 network.nodes.splice(idx, 1);
2112 // remove from SVG
2113 updateNodes();
2114 network.force.resume();
2115 }
2116
Simon Huntca867ac2014-11-28 18:07:35 -08002117 function findAttachedHosts(devId) {
2118 var hosts = [];
2119 network.nodes.forEach(function (d) {
2120 if (d.class === 'host' && d.cp.device === devId) {
2121 hosts.push(d);
2122 }
2123 });
2124 return hosts;
2125 }
2126
2127 function findAttachedLinks(devId) {
2128 var links = [];
2129 network.links.forEach(function (d) {
2130 if (d.source.id === devId || d.target.id === devId) {
2131 links.push(d);
2132 }
2133 });
2134 return links;
2135 }
Simon Hunt44031102014-11-11 13:20:36 -08002136
Simon Huntc7ee0662014-11-05 16:44:37 -08002137 function tick() {
2138 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -08002139 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -08002140 });
2141
2142 link.attr({
2143 x1: function (d) { return d.source.x; },
2144 y1: function (d) { return d.source.y; },
2145 x2: function (d) { return d.target.x; },
2146 y2: function (d) { return d.target.y; }
2147 });
Simon Hunte2575b62014-11-18 15:25:53 -08002148
2149 linkLabel.each(function (d) {
2150 var el = d3.select(this);
Thomas Vachuska4731f122014-11-20 04:56:19 -08002151 var lnk = findLinkById(d.key);
2152
2153 if (lnk) {
2154 var parms = {
Simon Hunte2575b62014-11-18 15:25:53 -08002155 x1: lnk.source.x,
2156 y1: lnk.source.y,
2157 x2: lnk.target.x,
2158 y2: lnk.target.y
2159 };
Thomas Vachuska4731f122014-11-20 04:56:19 -08002160 el.attr('transform', transformLabel(parms));
2161 }
Simon Hunte2575b62014-11-18 15:25:53 -08002162 });
Simon Huntc7ee0662014-11-05 16:44:37 -08002163 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08002164
2165 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002166 // Web-Socket for live data
2167
2168 function webSockUrl() {
2169 return document.location.toString()
2170 .replace(/\#.*/, '')
2171 .replace('http://', 'ws://')
2172 .replace('https://', 'wss://')
Simon Hunte5ab1382014-11-25 10:28:51 -08002173 .replace('index.html', config.webSockUrl);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002174 }
2175
2176 webSock = {
2177 ws : null,
2178
2179 connect : function() {
2180 webSock.ws = new WebSocket(webSockUrl());
2181
2182 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08002183 noWebSock(false);
Thomas Vachuska47635c62014-11-22 01:21:36 -08002184 requestSummary();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002185 showInstances();
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002186 };
2187
2188 webSock.ws.onmessage = function(m) {
2189 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002190 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08002191 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002192 }
2193 };
2194
2195 webSock.ws.onclose = function(m) {
2196 webSock.ws = null;
Simon Hunt0c6d4192014-11-12 12:07:10 -08002197 noWebSock(true);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002198 };
2199 },
2200
2201 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002202 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002203 webSock._send(text);
2204 }
2205 },
2206
2207 _send : function(message) {
2208 if (webSock.ws) {
2209 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002210 } else if (config.useLiveData) {
Simon Hunt434cf142014-11-24 11:10:28 -08002211 console.warn('no web socket open', message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002212 } else {
Simon Hunt434cf142014-11-24 11:10:28 -08002213 console.log('WS Send: ', message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002214 }
2215 }
2216
2217 };
2218
Simon Hunt0c6d4192014-11-12 12:07:10 -08002219 function noWebSock(b) {
2220 mask.style('display',b ? 'block' : 'none');
2221 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002222
2223 function sendMessage(evType, payload) {
2224 var toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08002225 event: evType,
2226 sid: ++sid,
2227 payload: payload
2228 },
2229 asText = JSON.stringify(toSend);
2230 wsTraceTx(asText);
2231 webSock.send(asText);
Simon Huntc76ae892014-11-18 17:31:51 -08002232
2233 // Temporary measure for debugging UI behavior ...
2234 if (!config.useLiveData) {
2235 handleTestSend(toSend);
2236 }
Simon Huntbb282f52014-11-10 11:08:19 -08002237 }
2238
2239 function wsTraceTx(msg) {
2240 wsTrace('tx', msg);
2241 }
2242 function wsTraceRx(msg) {
2243 wsTrace('rx', msg);
2244 }
2245 function wsTrace(rxtx, msg) {
Simon Huntbb282f52014-11-10 11:08:19 -08002246 console.log('[' + rxtx + '] ' + msg);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002247 }
2248
Simon Huntc76ae892014-11-18 17:31:51 -08002249 // NOTE: Temporary hardcoded example for showing detail pane
2250 // while we fine-
2251 // Probably should not merge this change...
2252 function handleTestSend(msg) {
2253 if (msg.event === 'requestDetails') {
2254 showDetails({
2255 event: 'showDetails',
2256 sid: 1001,
2257 payload: {
2258 "id": "of:0000ffffffffff09",
2259 "type": "roadm",
2260 "propOrder": [
2261 "Name",
2262 "Vendor",
2263 "H/W Version",
2264 "S/W Version",
2265 "-",
2266 "Latitude",
2267 "Longitude",
2268 "Ports"
2269 ],
2270 "props": {
2271 "Name": null,
2272 "Vendor": "Linc",
2273 "H/W Version": "OE",
2274 "S/W Version": "?",
2275 "-": "",
2276 "Latitude": "40.8",
2277 "Longitude": "73.1",
2278 "Ports": "2"
2279 }
2280 }
2281 });
2282 }
2283 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002284
2285 // ==============================
2286 // Selection stuff
2287
2288 function selectObject(obj, el) {
2289 var n,
Simon Hunt01095ff2014-11-13 16:37:29 -08002290 srcEv = d3.event.sourceEvent,
2291 meta = srcEv.metaKey,
2292 shift = srcEv.shiftKey;
2293
Simon Huntdeab4322014-11-13 18:49:07 -08002294 if ((panZoom() && !meta) || (!panZoom() && meta)) {
Simon Hunt01095ff2014-11-13 16:37:29 -08002295 return;
2296 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002297
2298 if (el) {
2299 n = d3.select(el);
2300 } else {
2301 node.each(function(d) {
2302 if (d == obj) {
2303 n = d3.select(el = this);
2304 }
2305 });
2306 }
2307 if (!n) return;
2308
Simon Hunt01095ff2014-11-13 16:37:29 -08002309 if (shift && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002310 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002311 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002312 return;
2313 }
2314
Simon Hunt01095ff2014-11-13 16:37:29 -08002315 if (!shift) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002316 deselectAll();
2317 }
2318
Simon Huntc31d5692014-11-12 13:27:18 -08002319 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002320 selectOrder.push(obj.id);
2321
2322 n.classed('selected', true);
Simon Huntb0ecfa52014-11-23 21:05:12 -08002323 updateDeviceColors(obj);
Simon Hunt61d04042014-11-11 17:27:16 -08002324 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002325 }
2326
2327 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08002328 var obj = selections[id],
2329 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002330 if (obj) {
2331 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08002332 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08002333 idx = $.inArray(id, selectOrder);
2334 if (idx >= 0) {
2335 selectOrder.splice(idx, 1);
2336 }
Simon Huntb0ecfa52014-11-23 21:05:12 -08002337 updateDeviceColors(obj.obj);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002338 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002339 }
2340
2341 function deselectAll() {
2342 // deselect all nodes in the network...
2343 node.classed('selected', false);
2344 selections = {};
2345 selectOrder = [];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002346 updateDeviceColors();
Simon Hunt61d04042014-11-11 17:27:16 -08002347 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002348 }
2349
Simon Huntb0ecfa52014-11-23 21:05:12 -08002350 function updateDeviceColors(d) {
2351 if (d) {
2352 setDeviceColor(d);
2353 } else {
2354 node.filter('.device').each(function (d) {
2355 setDeviceColor(d);
2356 });
2357 }
Thomas Vachuska47635c62014-11-22 01:21:36 -08002358 }
2359
Simon Hunt61d04042014-11-11 17:27:16 -08002360 // update the state of the detail pane, based on current selections
2361 function updateDetailPane() {
2362 var nSel = selectOrder.length;
2363 if (!nSel) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002364 emptySelect();
Simon Hunt61d04042014-11-11 17:27:16 -08002365 } else if (nSel === 1) {
2366 singleSelect();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002367 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002368 } else {
2369 multiSelect();
2370 }
2371 }
2372
Thomas Vachuska9edca302014-11-22 17:06:42 -08002373 function emptySelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002374 haveDetails = false;
Simon Hunt06811b72014-11-25 18:54:48 -08002375 hideDetailPane();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002376 cancelTraffic();
2377 }
2378
Simon Hunt61d04042014-11-11 17:27:16 -08002379 function singleSelect() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002380 // NOTE: detail is shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08002381 requestDetails();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002382 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002383 }
2384
2385 function multiSelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002386 haveDetails = true;
Simon Huntb53e0682014-11-12 13:32:01 -08002387 populateMultiSelect();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002388 requestTrafficForMode();
Simon Huntb53e0682014-11-12 13:32:01 -08002389 }
2390
2391 function addSep(tbody) {
2392 var tr = tbody.append('tr');
2393 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
2394 }
2395
2396 function addProp(tbody, label, value) {
2397 var tr = tbody.append('tr');
2398
2399 tr.append('td')
2400 .attr('class', 'label')
2401 .text(label + ' :');
2402
2403 tr.append('td')
2404 .attr('class', 'value')
2405 .text(value);
2406 }
2407
2408 function populateMultiSelect() {
2409 detailPane.empty();
2410
Simon Hunta3dd9572014-11-20 15:22:41 -08002411 var title = detailPane.append('h3'),
2412 table = detailPane.append('table'),
2413 tbody = table.append('tbody');
Simon Huntb53e0682014-11-12 13:32:01 -08002414
Thomas Vachuska4731f122014-11-20 04:56:19 -08002415 title.text('Selected Nodes');
Simon Huntb53e0682014-11-12 13:32:01 -08002416
2417 selectOrder.forEach(function (d, i) {
2418 addProp(tbody, i+1, d);
2419 });
Simon Huntd72bc702014-11-13 18:38:04 -08002420
2421 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08002422 }
2423
Thomas Vachuska47635c62014-11-22 01:21:36 -08002424 // TODO: refactor to consolidate with populateDetails
2425 function populateSummary(data) {
2426 summaryPane.empty();
2427
2428 var svg = summaryPane.append('svg'),
2429 iid = iconGlyphUrl(data);
2430
2431 var title = summaryPane.append('h2'),
2432 table = summaryPane.append('table'),
2433 tbody = table.append('tbody');
2434
2435 appendGlyph(svg, 0, 0, 40, iid);
2436
2437 svg.append('use')
2438 .attr({
2439 class: 'birdBadge',
2440 transform: translate(8,12),
2441 'xlink:href': '#bird',
2442 width: 24,
2443 height: 24,
2444 fill: '#fff'
2445 });
2446
2447 title.text('ONOS Summary');
2448
2449 data.propOrder.forEach(function(p) {
2450 if (p === '-') {
2451 addSep(tbody);
2452 } else {
2453 addProp(tbody, p, data.props[p]);
2454 }
2455 });
2456 }
2457
Simon Hunt61d04042014-11-11 17:27:16 -08002458 function populateDetails(data) {
2459 detailPane.empty();
2460
Simon Hunta6a9fe72014-11-20 11:17:12 -08002461 var svg = detailPane.append('svg'),
2462 iid = iconGlyphUrl(data);
2463
Simon Hunta3dd9572014-11-20 15:22:41 -08002464 var title = detailPane.append('h2'),
2465 table = detailPane.append('table'),
2466 tbody = table.append('tbody');
Simon Hunt61d04042014-11-11 17:27:16 -08002467
Simon Hunta6a9fe72014-11-20 11:17:12 -08002468 appendGlyph(svg, 0, 0, 40, iid);
2469 title.text(data.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002470
2471 data.propOrder.forEach(function(p) {
2472 if (p === '-') {
2473 addSep(tbody);
2474 } else {
2475 addProp(tbody, p, data.props[p]);
2476 }
2477 });
Simon Huntd72bc702014-11-13 18:38:04 -08002478
Thomas Vachuska4731f122014-11-20 04:56:19 -08002479 addSingleSelectActions(data);
Simon Hunt61d04042014-11-11 17:27:16 -08002480 }
2481
Thomas Vachuska4731f122014-11-20 04:56:19 -08002482 function addSingleSelectActions(data) {
Simon Huntd72bc702014-11-13 18:38:04 -08002483 detailPane.append('hr');
2484 // always want to allow 'show traffic'
Thomas Vachuska4731f122014-11-20 04:56:19 -08002485 addAction(detailPane, 'Show Related Traffic', showTrafficAction);
2486
2487 if (data.type === 'switch') {
2488 addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
2489 }
Simon Huntd72bc702014-11-13 18:38:04 -08002490 }
2491
2492 function addMultiSelectActions() {
2493 detailPane.append('hr');
2494 // always want to allow 'show traffic'
Thomas Vachuska4731f122014-11-20 04:56:19 -08002495 addAction(detailPane, 'Show Related Traffic', showTrafficAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002496 // if exactly two hosts are selected, also want 'add host intent'
2497 if (nSel() === 2 && allSelectionsClass('host')) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002498 addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2499 } else if (nSel() >= 2 && allSelectionsClass('host')) {
2500 addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002501 }
2502 }
2503
Simon Hunta5e89142014-11-14 07:00:33 -08002504 function addAction(panel, text, cb) {
2505 panel.append('div')
Simon Huntd72bc702014-11-13 18:38:04 -08002506 .classed('actionBtn', true)
2507 .text(text)
2508 .on('click', cb);
2509 }
2510
2511
Paul Greysonfcba0e82014-11-13 10:21:16 -08002512 function zoomPan(scale, translate) {
2513 zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
2514 // keep the map lines constant width while zooming
Thomas Vachuska89543292014-11-19 11:28:33 -08002515 bgImg.style("stroke-width", 2.0 / scale + "px");
Paul Greysonfcba0e82014-11-13 10:21:16 -08002516 }
2517
2518 function resetZoomPan() {
2519 zoomPan(1, [0,0]);
2520 zoom.scale(1).translate([0,0]);
2521 }
2522
2523 function setupZoomPan() {
2524 function zoomed() {
Simon Huntdeab4322014-11-13 18:49:07 -08002525 if (!panZoom() ^ !d3.event.sourceEvent.metaKey) {
Paul Greysonfcba0e82014-11-13 10:21:16 -08002526 zoomPan(d3.event.scale, d3.event.translate);
2527 }
2528 }
2529
2530 zoom = d3.behavior.zoom()
2531 .translate([0, 0])
2532 .scale(1)
2533 .scaleExtent([1, 8])
2534 .on("zoom", zoomed);
2535
2536 svg.call(zoom);
2537 }
2538
Simon Hunt61d04042014-11-11 17:27:16 -08002539 // ==============================
2540 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08002541
2542 function prepareScenario(view, ctx, dbg) {
2543 var sc = scenario,
2544 urlSc = sc.evDir + ctx + sc.evScenario;
2545
2546 if (!ctx) {
2547 view.alert("No scenario specified (null ctx)");
2548 return;
2549 }
2550
2551 sc.view = view;
2552 sc.ctx = ctx;
2553 sc.debug = dbg;
2554 sc.evNumber = 0;
2555
2556 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002557 var p = data && data.params || {},
2558 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08002559 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08002560
Simon Hunt56d51852014-11-09 13:03:35 -08002561 if (err) {
2562 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
2563 } else {
2564 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08002565 if (desc) {
2566 intro += '\n\n ' + desc.join('\n ');
2567 }
2568 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08002569 }
2570 });
2571
2572 }
2573
Simon Hunt01095ff2014-11-13 16:37:29 -08002574 // ==============================
2575 // Toggle Buttons in masthead
Simon Hunt0c6d4192014-11-12 12:07:10 -08002576
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002577 // TODO: toggle button (and other widgets in the masthead) should be provided
2578 // by the framework; not generated by the view.
2579
Thomas Vachuska47635c62014-11-22 01:21:36 -08002580 //var showInstances;
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002581
Simon Hunt87514342014-11-24 16:41:27 -08002582/*
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002583 function addButtonBar(view) {
2584 var bb = d3.select('#mast')
2585 .append('span').classed('right', true).attr('id', 'bb');
2586
Simon Hunta5e89142014-11-14 07:00:33 -08002587 function mkTogBtn(text, cb) {
2588 return bb.append('span')
2589 .classed('btn', true)
2590 .text(text)
2591 .on('click', cb);
2592 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002593
Thomas Vachuska47635c62014-11-22 01:21:36 -08002594 //showInstances = mkTogBtn('Show Instances', toggleInst);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002595 }
Simon Hunt87514342014-11-24 16:41:27 -08002596*/
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002597
Simon Huntdeab4322014-11-13 18:49:07 -08002598 function panZoom() {
Simon Hunte5b71752014-11-18 20:06:07 -08002599 return false;
Simon Hunta5e89142014-11-14 07:00:33 -08002600 }
2601
Simon Hunt7fa116d2014-11-17 14:16:55 -08002602 function loadGlyphs(svg) {
2603 var defs = svg.append('defs');
2604 gly.defBird(defs);
Simon Huntc72967b2014-11-20 09:21:42 -08002605 gly.defGlyphs(defs);
Simon Huntb82f6902014-11-22 11:53:15 -08002606 gly.defBadges(defs);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002607 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002608
Simon Hunt395a70c2014-11-22 23:17:40 -08002609 function sendUpdateMeta(d, store) {
2610 var metaUi = {},
2611 ll;
2612
2613 if (store) {
2614 ll = geoMapProjection.invert([d.x, d.y]);
Simon Hunt62c47542014-11-22 22:16:32 -08002615 metaUi = {
2616 x: d.x,
2617 y: d.y,
2618 lng: ll[0],
2619 lat: ll[1]
2620 };
Simon Hunt395a70c2014-11-22 23:17:40 -08002621 }
Simon Hunt62c47542014-11-22 22:16:32 -08002622 d.metaUi = metaUi;
2623 sendMessage('updateMeta', {
2624 id: d.id,
2625 'class': d.class,
2626 'memento': metaUi
2627 });
2628 }
2629
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002630 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08002631 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08002632
Simon Huntf67722a2014-11-10 09:32:06 -08002633 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08002634 var w = view.width(),
2635 h = view.height(),
Simon Huntc7ee0662014-11-05 16:44:37 -08002636 fcfg = config.force,
2637 fpad = fcfg.pad,
2638 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08002639
Simon Hunt142d0032014-11-04 20:13:09 -08002640 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002641 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
2642 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002643 setSize(svg, view);
2644
Simon Hunt7fa116d2014-11-17 14:16:55 -08002645 loadGlyphs(svg);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002646
Paul Greysonfcba0e82014-11-13 10:21:16 -08002647 zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002648 setupZoomPan();
2649
Simon Hunt1a9eff92014-11-07 11:06:34 -08002650 // add blue glow filter to svg layer
Paul Greysonfcba0e82014-11-13 10:21:16 -08002651 d3u.appendGlow(zoomPanContainer);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002652
Simon Huntc7ee0662014-11-05 16:44:37 -08002653 // group for the topology
Paul Greysonfcba0e82014-11-13 10:21:16 -08002654 topoG = zoomPanContainer.append('g')
Simon Huntd3b7d512014-11-12 15:48:41 -08002655 .attr('id', 'topo-G')
Simon Huntc7ee0662014-11-05 16:44:37 -08002656 .attr('transform', fcfg.translate());
2657
Simon Hunte2575b62014-11-18 15:25:53 -08002658 // subgroups for links, link labels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002659 linkG = topoG.append('g').attr('id', 'links');
Simon Hunte2575b62014-11-18 15:25:53 -08002660 linkLabelG = topoG.append('g').attr('id', 'linkLabels');
Simon Huntc7ee0662014-11-05 16:44:37 -08002661 nodeG = topoG.append('g').attr('id', 'nodes');
2662
Simon Hunte2575b62014-11-18 15:25:53 -08002663 // selection of links, linkLabels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002664 link = linkG.selectAll('.link');
Simon Hunte2575b62014-11-18 15:25:53 -08002665 linkLabel = linkLabelG.selectAll('.linkLabel');
Simon Huntc7ee0662014-11-05 16:44:37 -08002666 node = nodeG.selectAll('.node');
2667
Simon Hunt7cd48f32014-11-09 23:42:50 -08002668 function chrg(d) {
2669 return fcfg.charge[d.class] || -12000;
2670 }
Simon Hunt99c13842014-11-06 18:23:12 -08002671 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002672 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08002673 }
2674 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002675 // 0.0 - 1.0
2676 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08002677 }
2678
Simon Hunt1a9eff92014-11-07 11:06:34 -08002679 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002680 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002681 }
2682
2683 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08002684 // once we've finished moving, pin the node in position
2685 d.fixed = true;
2686 d3.select(self).classed('fixed', true);
2687 if (config.useLiveData) {
Simon Hunt395a70c2014-11-22 23:17:40 -08002688 sendUpdateMeta(d, true);
Simon Hunta255a2c2014-11-13 22:29:35 -08002689 } else {
2690 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08002691 }
2692 }
2693
Simon Huntc7ee0662014-11-05 16:44:37 -08002694 // set up the force layout
2695 network.force = d3.layout.force()
2696 .size(forceDim)
2697 .nodes(network.nodes)
2698 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002699 .gravity(0.4)
2700 .friction(0.7)
2701 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08002702 .linkDistance(ldist)
2703 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08002704 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08002705
Simon Hunt01095ff2014-11-13 16:37:29 -08002706 network.drag = d3u.createDragBehavior(network.force,
Simon Huntdeab4322014-11-13 18:49:07 -08002707 selectCb, atDragEnd, panZoom);
Simon Hunt0c6d4192014-11-12 12:07:10 -08002708
2709 // create mask layer for when we lose connection to server.
Simon Hunta5e89142014-11-14 07:00:33 -08002710 // TODO: this should be part of the framework
Simon Hunt0c6d4192014-11-12 12:07:10 -08002711 mask = view.$div.append('div').attr('id','topo-mask');
2712 para(mask, 'Oops!');
2713 para(mask, 'Web-socket connection to server closed...');
2714 para(mask, 'Try refreshing the page.');
Simon Hunt12ce12e2014-11-15 21:13:19 -08002715
2716 mask.append('svg')
2717 .attr({
2718 id: 'mask-bird',
2719 width: w,
2720 height: h
2721 })
2722 .append('g')
2723 .attr('transform', birdTranslate(w, h))
2724 .style('opacity', 0.3)
2725 .append('use')
2726 .attr({
2727 'xlink:href': '#bird',
2728 width: config.birdDim,
2729 height: config.birdDim,
2730 fill: '#111'
Thomas Vachuska89543292014-11-19 11:28:33 -08002731 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002732 }
Simon Hunt195cb382014-11-03 17:50:51 -08002733
Simon Hunt01095ff2014-11-13 16:37:29 -08002734 function para(sel, text) {
2735 sel.append('p').text(text);
2736 }
2737
2738
Simon Hunt56d51852014-11-09 13:03:35 -08002739 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08002740 // resize, in case the window was resized while we were not loaded
2741 resize(view, ctx, flags);
2742
Simon Hunt99c13842014-11-06 18:23:12 -08002743 // cache the view token, so network topo functions can access it
2744 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08002745 config.useLiveData = !flags.local;
2746
2747 if (!config.useLiveData) {
2748 prepareScenario(view, ctx, flags.debug);
2749 }
Simon Hunt99c13842014-11-06 18:23:12 -08002750
2751 // set our radio buttons and key bindings
Simon Hunt9462e8c2014-11-14 17:28:09 -08002752 layerBtnSet = view.setRadio(layerButtons);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002753 view.setKeys(keyDispatch);
Simon Hunt87514342014-11-24 16:41:27 -08002754 view.setGestures(gestures);
Simon Hunt195cb382014-11-03 17:50:51 -08002755
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002756 // patch in our "button bar" for now
2757 // TODO: implement a more official frameworky way of doing this..
Simon Hunt87514342014-11-24 16:41:27 -08002758 //addButtonBar(view);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002759
Simon Huntd3b7d512014-11-12 15:48:41 -08002760 // Load map data asynchronously; complete startup after that..
2761 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08002762 }
2763
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002764 function startAntTimer() {
Thomas Vachuska4731f122014-11-20 04:56:19 -08002765 if (!antTimer) {
2766 var pulses = [5, 3, 1.2, 3],
2767 pulse = 0;
2768 antTimer = setInterval(function () {
2769 pulse = pulse + 1;
2770 pulse = pulse === pulses.length ? 0 : pulse;
2771 d3.selectAll('.animated').style('stroke-width', pulses[pulse]);
2772 }, 200);
2773 }
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002774 }
2775
2776 function stopAntTimer() {
Simon Hunta255a2c2014-11-13 22:29:35 -08002777 if (antTimer) {
2778 clearInterval(antTimer);
2779 antTimer = null;
2780 }
Simon Huntd3b7d512014-11-12 15:48:41 -08002781 }
2782
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002783 function unload(view, ctx, flags) {
2784 stopAntTimer();
2785 }
2786
Simon Huntd3b7d512014-11-12 15:48:41 -08002787 // TODO: move these to config/state portion of script
Simon Hunta6a9fe72014-11-20 11:17:12 -08002788 var geoJsonUrl = 'json/map/continental_us.json',
Simon Huntd3b7d512014-11-12 15:48:41 -08002789 geoJson;
2790
2791 function loadGeoJsonData() {
2792 d3.json(geoJsonUrl, function (err, data) {
2793 if (err) {
2794 // fall back to USA map background
2795 loadStaticMap();
2796 } else {
2797 geoJson = data;
2798 loadGeoMap();
2799 }
2800
2801 // finally, connect to the server...
2802 if (config.useLiveData) {
2803 webSock.connect();
2804 }
2805 });
2806 }
2807
2808 function showBg() {
Simon Hunt434cf142014-11-24 11:10:28 -08002809 return visVal(config.options.showBackground);
Simon Huntd3b7d512014-11-12 15:48:41 -08002810 }
2811
2812 function loadStaticMap() {
2813 fnTrace('loadStaticMap', config.backgroundUrl);
2814 var w = network.view.width(),
2815 h = network.view.height();
2816
2817 // load the background image
2818 bgImg = svg.insert('svg:image', '#topo-G')
2819 .attr({
2820 id: 'topo-bg',
2821 width: w,
2822 height: h,
2823 'xlink:href': config.backgroundUrl
2824 })
2825 .style({
2826 visibility: showBg()
2827 });
2828 }
2829
2830 function loadGeoMap() {
2831 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08002832
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002833 // extracts the topojson data into geocoordinate-based geometry
2834 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08002835
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002836 // see: http://bl.ocks.org/mbostock/4707858
2837 geoMapProjection = d3.geo.mercator();
2838 var path = d3.geo.path().projection(geoMapProjection);
Simon Huntd3b7d512014-11-12 15:48:41 -08002839
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002840 geoMapProjection
2841 .scale(1)
2842 .translate([0, 0]);
Simon Huntd3b7d512014-11-12 15:48:41 -08002843
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002844 // [[x1,y1],[x2,y2]]
2845 var b = path.bounds(topoData);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002846 // size map to 95% of minimum dimension to fill space
2847 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 -08002848 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 -08002849
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002850 geoMapProjection
2851 .scale(s)
2852 .translate(t);
2853
Paul Greysonfcba0e82014-11-13 10:21:16 -08002854 bgImg = zoomPanContainer.insert("g", '#topo-G');
Thomas Vachuska89543292014-11-19 11:28:33 -08002855 bgImg.attr('id', 'map').selectAll('path')
2856 .data(topoData.features)
2857 .enter()
2858 .append('path')
2859 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08002860 }
2861
Simon Huntf67722a2014-11-10 09:32:06 -08002862 function resize(view, ctx, flags) {
Simon Hunt12ce12e2014-11-15 21:13:19 -08002863 var w = view.width(),
2864 h = view.height();
2865
Simon Hunt934c3ce2014-11-05 11:45:07 -08002866 setSize(svg, view);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002867
2868 d3.select('#mask-bird').attr({ width: w, height: h})
2869 .select('g').attr('transform', birdTranslate(w, h));
Simon Hunt142d0032014-11-04 20:13:09 -08002870 }
2871
Simon Hunt8f40cce2014-11-23 15:57:30 -08002872 function theme(view, ctx, flags) {
2873 updateInstances();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002874 updateDeviceColors();
Simon Hunt8f40cce2014-11-23 15:57:30 -08002875 }
2876
Simon Hunt12ce12e2014-11-15 21:13:19 -08002877 function birdTranslate(w, h) {
2878 var bdim = config.birdDim;
2879 return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
2880 }
Simon Hunt142d0032014-11-04 20:13:09 -08002881
Simon Hunt06811b72014-11-25 18:54:48 -08002882 function isF(f) { return $.isFunction(f) ? f : null; }
2883 function noop() {}
2884
2885 function augmentDetailPane() {
2886 var dp = detailPane;
2887 dp.ypos = { up: 64, down: 320, current: 320};
2888
2889 dp._move = function (y, cb) {
2890 var endCb = isF(cb) || noop,
2891 yp = dp.ypos;
2892 if (yp.current !== y) {
2893 yp.current = y;
2894 dp.el.transition().duration(300)
2895 .each('end', endCb)
2896 .style('top', yp.current + 'px');
2897 } else {
2898 endCb();
2899 }
2900 };
2901
2902 dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
2903 dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
2904 }
2905
Simon Hunt142d0032014-11-04 20:13:09 -08002906 // ==============================
2907 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08002908
Simon Hunt25248912014-11-04 11:25:48 -08002909 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08002910 preload: preload,
2911 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08002912 unload: unload,
Simon Hunt8f40cce2014-11-23 15:57:30 -08002913 resize: resize,
2914 theme: theme
Simon Hunt195cb382014-11-03 17:50:51 -08002915 });
2916
Thomas Vachuska47635c62014-11-22 01:21:36 -08002917 summaryPane = onos.ui.addFloatingPanel('topo-summary');
Simon Hunt61d04042014-11-11 17:27:16 -08002918 detailPane = onos.ui.addFloatingPanel('topo-detail');
Simon Hunt06811b72014-11-25 18:54:48 -08002919 augmentDetailPane();
Simon Hunta5e89142014-11-14 07:00:33 -08002920 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
Simon Huntb82f6902014-11-22 11:53:15 -08002921 oiBox.width(20);
Simon Hunt61d04042014-11-11 17:27:16 -08002922
Simon Hunt195cb382014-11-03 17:50:51 -08002923}(ONOS));