blob: 401875761d036db2f4a90163fa34b2eb99aa05b7 [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 Vachuska1e68bdd2014-11-29 13:53:10 -0800143 E: [equalizeMasters, 'Equalize mastership roles'],
Thomas Vachuska47635c62014-11-22 01:21:36 -0800144 O: [toggleSummary, 'Toggle ONOS summary pane'],
145 I: [toggleInstances, 'Toggle ONOS instances pane'],
Simon Hunt27d322d2014-11-28 10:45:43 -0800146 D: [toggleDetails, 'Disable / enable details pane'],
Simon Hunt988c6fc2014-11-20 17:43:03 -0800147 B: [toggleBg, 'Toggle background image'],
Simon Hunt434cf142014-11-24 11:10:28 -0800148 H: [toggleHosts, 'Toggle host visibility'],
Simon Hunt6d9bd032014-11-28 22:16:40 -0800149 M: [toggleOffline, 'Toggle offline visibility'],
Simon Hunt27d322d2014-11-28 10:45:43 -0800150 L: [cycleLabels, 'Cycle device labels'],
Simon Hunt934c3ce2014-11-05 11:45:07 -0800151 P: togglePorts,
Simon Hunt87514342014-11-24 16:41:27 -0800152 U: [unpin, 'Unpin node (hover mouse over)'],
153 R: [resetZoomPan, 'Reset zoom / pan'],
Thomas Vachuska9edca302014-11-22 17:06:42 -0800154 V: [showTrafficAction, 'Show related traffic'],
Simon Hunt56ef0fe2014-11-21 08:24:43 -0800155 A: [showAllTrafficAction, 'Show all traffic'],
156 F: [showDeviceLinkFlowsAction, 'Show device link flows'],
Simon Hunt9462e8c2014-11-14 17:28:09 -0800157 esc: handleEscape
Simon Hunt934c3ce2014-11-05 11:45:07 -0800158 };
Simon Hunt142d0032014-11-04 20:13:09 -0800159
Simon Hunt87514342014-11-24 16:41:27 -0800160 // mouse gestures
161 var gestures = [
162 ['click', 'Select the item and show details'],
163 ['shift-click', 'Toggle selection state'],
164 ['drag', 'Reposition (and pin) device / host'],
165 ['cmd-scroll', 'Zoom in / out'],
166 ['cmd-drag', 'Pan']
167 ];
168
Simon Hunt195cb382014-11-03 17:50:51 -0800169 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800170 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800171 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800172 nodes: [],
173 links: [],
Simon Hunt269670f2014-11-17 16:17:43 -0800174 lookup: {},
175 revLinkToKey: {}
Simon Hunt99c13842014-11-06 18:23:12 -0800176 },
Simon Hunt56d51852014-11-09 13:03:35 -0800177 scenario = {
178 evDir: 'json/ev/',
179 evScenario: '/scenario.json',
180 evPrefix: '/ev_',
181 evOnos: '_onos.json',
182 evUi: '_ui.json',
183 ctx: null,
184 params: {},
185 evNumber: 0,
Simon Hunt434cf142014-11-24 11:10:28 -0800186 view: null
Simon Hunt56d51852014-11-09 13:03:35 -0800187 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800188 webSock,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800189 sid = 0,
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800190 deviceLabelCount = 3,
Simon Hunt209155e2014-11-21 12:16:09 -0800191 hostLabelCount = 2,
Simon Hunt56d51852014-11-09 13:03:35 -0800192 deviceLabelIndex = 0,
193 hostLabelIndex = 0,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800194 selections = {},
Simon Hunta5e89142014-11-14 07:00:33 -0800195 selectOrder = [],
Simon Hunt6ac93f32014-11-13 12:17:27 -0800196 hovered = null,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800197 summaryPane,
Simon Hunta5e89142014-11-14 07:00:33 -0800198 detailPane,
Simon Hunta255a2c2014-11-13 22:29:35 -0800199 antTimer = null,
Simon Hunta5e89142014-11-14 07:00:33 -0800200 onosInstances = {},
201 onosOrder = [],
202 oiBox,
Simon Hunt9462e8c2014-11-14 17:28:09 -0800203 oiShowMaster = false,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800204 portLabelsOn = false,
Simon Huntb0ecfa52014-11-23 21:05:12 -0800205 cat7 = d3u.cat7(),
Simon Hunt434cf142014-11-24 11:10:28 -0800206 colorAffinity = false,
Simon Hunt27d322d2014-11-28 10:45:43 -0800207 showHosts = false,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800208 showOffline = true,
Simon Hunt27d322d2014-11-28 10:45:43 -0800209 useDetails = true,
Simon Hunt6e18fe32014-11-29 13:35:41 -0800210 haveDetails = false,
211 dragAllowed = true;
Simon Hunt195cb382014-11-03 17:50:51 -0800212
Simon Hunt434cf142014-11-24 11:10:28 -0800213 // constants
Thomas Vachuska9edca302014-11-22 17:06:42 -0800214 var hoverModeAll = 1,
215 hoverModeFlows = 2,
216 hoverModeIntents = 3,
217 hoverMode = hoverModeFlows;
218
Simon Hunt934c3ce2014-11-05 11:45:07 -0800219 // D3 selections
220 var svg,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800221 zoomPanContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800222 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800223 topoG,
224 nodeG,
225 linkG,
Simon Hunte2575b62014-11-18 15:25:53 -0800226 linkLabelG,
Simon Huntc7ee0662014-11-05 16:44:37 -0800227 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800228 link,
Simon Hunte2575b62014-11-18 15:25:53 -0800229 linkLabel,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800230 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800231
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800232 // the projection for the map background
233 var geoMapProjection;
234
Paul Greysonfcba0e82014-11-13 10:21:16 -0800235 // the zoom function
236 var zoom;
237
Simon Hunt142d0032014-11-04 20:13:09 -0800238 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800239 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800240
Simon Hunt99c13842014-11-06 18:23:12 -0800241 function note(label, msg) {
242 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800243 }
244
Simon Hunt99c13842014-11-06 18:23:12 -0800245 function debug(what) {
246 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800247 }
248
Simon Huntfc274c92014-11-11 11:05:46 -0800249 function fnTrace(msg, id) {
250 if (config.fnTrace) {
251 console.log('FN: ' + msg + ' [' + id + ']');
252 }
253 }
Simon Hunt99c13842014-11-06 18:23:12 -0800254
Simon Hunta5e89142014-11-14 07:00:33 -0800255 function evTrace(data) {
256 fnTrace(data.event, data.payload.id);
257 }
258
Simon Hunt934c3ce2014-11-05 11:45:07 -0800259 // ==============================
260 // Key Callbacks
261
Simon Hunt27d322d2014-11-28 10:45:43 -0800262 function flash(txt) {
263 network.view.flash(txt);
264 }
265
Simon Hunt99c13842014-11-06 18:23:12 -0800266 function testMe(view) {
Simon Hunt8f40cce2014-11-23 15:57:30 -0800267 //view.alert('Theme is ' + view.getTheme());
Simon Hunta3dd9572014-11-20 15:22:41 -0800268 //view.flash('This is some text');
Simon Hunt8f40cce2014-11-23 15:57:30 -0800269 cat7.testCard(svg);
Simon Hunt99c13842014-11-06 18:23:12 -0800270 }
271
Simon Hunt56d51852014-11-09 13:03:35 -0800272 function injectTestEvent(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800273 if (config.useLiveData) { return; }
274
Simon Hunt56d51852014-11-09 13:03:35 -0800275 var sc = scenario,
276 evn = ++sc.evNumber,
277 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
278 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800279 uiUrl = pfx + sc.evUi,
280 stack = [
281 { url: onosUrl, cb: handleServerEvent },
282 { url: uiUrl, cb: handleUiEvent }
283 ];
284 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800285 }
286
Simon Hunt7cd48f32014-11-09 23:42:50 -0800287 function recurseFetchEvent(stack, evn) {
288 var v = scenario.view,
289 frame;
290 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800291 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800292 return;
293 }
294 frame = stack.shift();
295
296 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800297 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800298 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800299 // if we didn't find the data, try the next stack frame
300 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800301 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800302 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800303 }
Simon Hunt99c13842014-11-06 18:23:12 -0800304 } else {
Simon Hunt1712ed82014-11-17 12:56:00 -0800305 wsTrace('test', JSON.stringify(data));
Simon Hunt7cd48f32014-11-09 23:42:50 -0800306 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800307 }
308 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800309
Simon Hunt56d51852014-11-09 13:03:35 -0800310 }
Simon Hunt50128c02014-11-08 13:36:15 -0800311
Simon Hunt56d51852014-11-09 13:03:35 -0800312 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800313 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
314 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800315 }
316
317 function injectStartupEvents(view) {
318 var last = scenario.params.lastAuto || 0;
Simon Hunt434cf142014-11-24 11:10:28 -0800319 if (config.useLiveData) { return; }
Simon Hunt56d51852014-11-09 13:03:35 -0800320
321 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800322 injectTestEvent(view);
323 }
324 }
325
Simon Hunt934c3ce2014-11-05 11:45:07 -0800326 function toggleBg() {
327 var vis = bgImg.style('visibility');
Simon Hunt434cf142014-11-24 11:10:28 -0800328 bgImg.style('visibility', visVal(vis === 'hidden'));
329 }
330
331 function toggleHosts() {
332 showHosts = !showHosts;
333 updateHostVisibility();
Simon Hunt27d322d2014-11-28 10:45:43 -0800334 flash('Hosts ' + visVal(showHosts));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800335 }
336
Simon Hunt6d9bd032014-11-28 22:16:40 -0800337 function toggleOffline() {
338 showOffline = !showOffline;
339 updateOfflineVisibility();
340 flash('Offline devices ' + visVal(showOffline));
341 }
342
Simon Hunt99c13842014-11-06 18:23:12 -0800343 function cycleLabels() {
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800344 deviceLabelIndex = (deviceLabelIndex === 2)
Simon Huntbb282f52014-11-10 11:08:19 -0800345 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800346
Simon Hunt99c13842014-11-06 18:23:12 -0800347 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800348 if (d.class === 'device') {
349 updateDeviceLabel(d);
350 }
Simon Hunt99c13842014-11-06 18:23:12 -0800351 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800352 }
353
354 function togglePorts(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800355 //view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800356 }
357
Simon Hunt6ac93f32014-11-13 12:17:27 -0800358 function unpin() {
359 if (hovered) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800360 sendUpdateMeta(hovered);
Simon Hunt6ac93f32014-11-13 12:17:27 -0800361 hovered.fixed = false;
362 hovered.el.classed('fixed', false);
363 network.force.resume();
364 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800365 }
366
Simon Hunt9462e8c2014-11-14 17:28:09 -0800367 function handleEscape(view) {
368 if (oiShowMaster) {
369 cancelAffinity();
Simon Hunt27d322d2014-11-28 10:45:43 -0800370 } else if (haveDetails) {
Simon Hunt9462e8c2014-11-14 17:28:09 -0800371 deselectAll();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800372 } else if (oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800373 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800374 } else if (summaryPane.isVisible()) {
375 cancelSummary();
Thomas Vachuska5bde31f2014-11-25 15:29:18 -0800376 stopAntTimer();
377 } else {
378 hoverMode = hoverModeFlows;
Simon Hunt9462e8c2014-11-14 17:28:09 -0800379 }
380 }
381
Simon Hunt934c3ce2014-11-05 11:45:07 -0800382 // ==============================
383 // Radio Button Callbacks
384
Simon Hunta5e89142014-11-14 07:00:33 -0800385 var layerLookup = {
386 host: {
Simon Hunt209155e2014-11-21 12:16:09 -0800387 endstation: 'pkt', // default, if host event does not define type
Thomas Vachuska89543292014-11-19 11:28:33 -0800388 router: 'pkt',
Simon Hunta5e89142014-11-14 07:00:33 -0800389 bgpSpeaker: 'pkt'
390 },
391 device: {
392 switch: 'pkt',
393 roadm: 'opt'
394 },
395 link: {
396 hostLink: 'pkt',
397 direct: 'pkt',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800398 indirect: '',
399 tunnel: '',
Simon Hunta5e89142014-11-14 07:00:33 -0800400 optical: 'opt'
401 }
402 };
403
404 function inLayer(d, layer) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800405 var type = d.class === 'link' ? d.type() : d.type,
406 look = layerLookup[d.class],
407 lyr = look && look[type];
Simon Hunta5e89142014-11-14 07:00:33 -0800408 return lyr === layer;
409 }
410
411 function unsuppressLayer(which) {
412 node.each(function (d) {
413 var node = d.el;
414 if (inLayer(d, which)) {
415 node.classed('suppressed', false);
416 }
417 });
418
419 link.each(function (d) {
420 var link = d.el;
421 if (inLayer(d, which)) {
422 link.classed('suppressed', false);
423 }
424 });
425 }
426
Simon Hunt9462e8c2014-11-14 17:28:09 -0800427 function suppressLayers(b) {
428 node.classed('suppressed', b);
429 link.classed('suppressed', b);
Simon Hunt142d0032014-11-04 20:13:09 -0800430// d3.selectAll('svg .port').classed('inactive', false);
431// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt195cb382014-11-03 17:50:51 -0800432 }
433
Simon Hunt9462e8c2014-11-14 17:28:09 -0800434 function showAllLayers() {
435 suppressLayers(false);
436 }
437
Simon Hunt195cb382014-11-03 17:50:51 -0800438 function showPacketLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800439 node.classed('suppressed', true);
440 link.classed('suppressed', true);
441 unsuppressLayer('pkt');
Simon Hunt195cb382014-11-03 17:50:51 -0800442 }
443
444 function showOpticalLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800445 node.classed('suppressed', true);
446 link.classed('suppressed', true);
447 unsuppressLayer('opt');
Simon Hunt195cb382014-11-03 17:50:51 -0800448 }
449
Simon Hunt9462e8c2014-11-14 17:28:09 -0800450 function restoreLayerState() {
451 layerBtnDispatch[layerBtnSet.selected()]();
452 }
453
Simon Hunt142d0032014-11-04 20:13:09 -0800454 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800455 // Private functions
456
Simon Hunt99c13842014-11-06 18:23:12 -0800457 function safeId(s) {
458 return s.replace(/[^a-z0-9]/gi, '-');
459 }
460
Simon Huntc7ee0662014-11-05 16:44:37 -0800461 // set the size of the given element to that of the view (reduced if padded)
462 function setSize(el, view, pad) {
463 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800464 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800465 width: view.width() - padding,
466 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800467 });
468 }
469
Simon Hunt8257f4c2014-11-16 19:34:54 -0800470 function makeNodeKey(d, what) {
471 var port = what + 'Port';
472 return d[what] + '/' + d[port];
473 }
474
475 function makeLinkKey(d, flipped) {
476 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
477 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
478 return one + '-' + two;
479 }
480
Simon Hunt269670f2014-11-17 16:17:43 -0800481 function findLinkById(id) {
482 // check to see if this is a reverse lookup, else default to given id
483 var key = network.revLinkToKey[id] || id;
484 return key && network.lookup[key];
485 }
486
Simon Hunt8257f4c2014-11-16 19:34:54 -0800487 function findLink(linkData, op) {
488 var key = makeLinkKey(linkData),
489 keyrev = makeLinkKey(linkData, 1),
490 link = network.lookup[key],
491 linkRev = network.lookup[keyrev],
492 result = {},
493 ldata = link || linkRev,
494 rawLink;
495
496 if (op === 'add') {
497 if (link) {
498 // trying to add a link that we already know about
499 result.ldata = link;
500 result.badLogic = 'addLink: link already added';
501
502 } else if (linkRev) {
503 // we found the reverse of the link to be added
504 result.ldata = linkRev;
505 if (linkRev.fromTarget) {
506 result.badLogic = 'addLink: link already added';
507 }
508 }
509 } else if (op === 'update') {
510 if (!ldata) {
511 result.badLogic = 'updateLink: link not found';
512 } else {
513 rawLink = link ? ldata.fromSource : ldata.fromTarget;
514 result.updateWith = function (data) {
515 $.extend(rawLink, data);
516 restyleLinkElement(ldata);
517 }
518 }
519 } else if (op === 'remove') {
520 if (!ldata) {
521 result.badLogic = 'removeLink: link not found';
522 } else {
523 rawLink = link ? ldata.fromSource : ldata.fromTarget;
524
525 if (!rawLink) {
526 result.badLogic = 'removeLink: link not found';
527
528 } else {
529 result.removeRawLink = function () {
530 if (link) {
531 // remove fromSource
532 ldata.fromSource = null;
533 if (ldata.fromTarget) {
534 // promote target into source position
535 ldata.fromSource = ldata.fromTarget;
536 ldata.fromTarget = null;
537 ldata.key = keyrev;
538 delete network.lookup[key];
539 network.lookup[keyrev] = ldata;
Simon Hunt269670f2014-11-17 16:17:43 -0800540 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800541 }
542 } else {
543 // remove fromTarget
544 ldata.fromTarget = null;
Simon Hunt269670f2014-11-17 16:17:43 -0800545 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800546 }
547 if (ldata.fromSource) {
548 restyleLinkElement(ldata);
549 } else {
550 removeLinkElement(ldata);
551 }
552 }
553 }
554 }
555 }
556 return result;
557 }
558
559 function addLinkUpdate(ldata, link) {
560 // add link event, but we already have the reverse link installed
561 ldata.fromTarget = link;
Simon Hunt269670f2014-11-17 16:17:43 -0800562 network.revLinkToKey[link.id] = ldata.key;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800563 restyleLinkElement(ldata);
564 }
565
566 var allLinkTypes = 'direct indirect optical tunnel',
567 defaultLinkType = 'direct';
568
569 function restyleLinkElement(ldata) {
570 // this fn's job is to look at raw links and decide what svg classes
571 // need to be applied to the line element in the DOM
572 var el = ldata.el,
573 type = ldata.type(),
574 lw = ldata.linkWidth(),
575 online = ldata.online();
576
577 el.classed('link', true);
578 el.classed('inactive', !online);
579 el.classed(allLinkTypes, false);
580 if (type) {
581 el.classed(type, true);
582 }
583 el.transition()
584 .duration(1000)
Thomas Vachuska89543292014-11-19 11:28:33 -0800585 .attr('stroke-width', linkScale(lw))
586 .attr('stroke', config.topo.linkBaseColor);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800587 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800588
Simon Hunt99c13842014-11-06 18:23:12 -0800589 // ==============================
590 // Event handlers for server-pushed events
591
Simon Huntbb282f52014-11-10 11:08:19 -0800592 function logicError(msg) {
593 // TODO, report logic error to server, via websock, so it can be logged
Simon Huntfc274c92014-11-11 11:05:46 -0800594 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800595 }
596
Simon Hunt99c13842014-11-06 18:23:12 -0800597 var eventDispatch = {
Simon Hunta5e89142014-11-14 07:00:33 -0800598 addInstance: addInstance,
Simon Hunt99c13842014-11-06 18:23:12 -0800599 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800600 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800601 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800602
Simon Huntfcfb46c2014-11-19 12:53:38 -0800603 updateInstance: updateInstance,
Simon Huntbb282f52014-11-10 11:08:19 -0800604 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800605 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800606 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800607
Simon Hunt7b403bc2014-11-22 19:01:00 -0800608 removeInstance: removeInstance,
Simon Huntca867ac2014-11-28 18:07:35 -0800609 removeDevice: removeDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800610 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800611 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800612
Simon Hunt61d04042014-11-11 17:27:16 -0800613 showDetails: showDetails,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800614 showSummary: showSummary,
Simon Huntb53e0682014-11-12 13:32:01 -0800615 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800616 };
617
Simon Hunta5e89142014-11-14 07:00:33 -0800618 function addInstance(data) {
619 evTrace(data);
620 var inst = data.payload,
621 id = inst.id;
622 if (onosInstances[id]) {
623 logicError('ONOS instance already added: ' + id);
624 return;
625 }
626 onosInstances[id] = inst;
627 onosOrder.push(inst);
628 updateInstances();
629 }
630
Simon Hunt99c13842014-11-06 18:23:12 -0800631 function addDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800632 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800633 var device = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800634 id = device.id,
635 d;
636
637 if (network.lookup[id]) {
638 logicError('Device already added: ' + id);
639 return;
640 }
641
642 d = createDeviceNode(device);
643 network.nodes.push(d);
644 network.lookup[id] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800645 updateNodes();
646 network.force.start();
647 }
648
Simon Hunt99c13842014-11-06 18:23:12 -0800649 function addLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800650 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800651 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800652 result = findLink(link, 'add'),
653 bad = result.badLogic,
Simon Huntca867ac2014-11-28 18:07:35 -0800654 d = result.ldata;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800655
656 if (bad) {
657 logicError(bad + ': ' + link.id);
658 return;
659 }
660
Simon Huntca867ac2014-11-28 18:07:35 -0800661 if (d) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800662 // we already have a backing store link for src/dst nodes
Simon Huntca867ac2014-11-28 18:07:35 -0800663 addLinkUpdate(d, link);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800664 return;
665 }
666
667 // no backing store link yet
Simon Huntca867ac2014-11-28 18:07:35 -0800668 d = createLink(link);
669 if (d) {
670 network.links.push(d);
671 network.lookup[d.key] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800672 updateLinks();
673 network.force.start();
674 }
675 }
676
Simon Hunt56d51852014-11-09 13:03:35 -0800677 function addHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800678 evTrace(data);
Simon Hunt56d51852014-11-09 13:03:35 -0800679 var host = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800680 id = host.id,
681 d,
Simon Hunt56d51852014-11-09 13:03:35 -0800682 lnk;
Simon Huntca867ac2014-11-28 18:07:35 -0800683
684 if (network.lookup[id]) {
685 logicError('Host already added: ' + id);
686 return;
687 }
688
689 d = createHostNode(host);
690 network.nodes.push(d);
691 network.lookup[host.id] = d;
Simon Hunt56d51852014-11-09 13:03:35 -0800692 updateNodes();
693
694 lnk = createHostLink(host);
695 if (lnk) {
Simon Huntca867ac2014-11-28 18:07:35 -0800696 d.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800697 network.links.push(lnk);
Simon Huntca867ac2014-11-28 18:07:35 -0800698 network.lookup[d.ingress] = lnk;
699 network.lookup[d.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800700 updateLinks();
701 }
702 network.force.start();
703 }
704
Simon Hunt44031102014-11-11 13:20:36 -0800705 // TODO: fold updateX(...) methods into one base method; remove duplication
Simon Hunt56a2ea42014-11-19 12:39:31 -0800706
707 function updateInstance(data) {
708 evTrace(data);
709 var inst = data.payload,
710 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800711 d = onosInstances[id];
712 if (d) {
713 $.extend(d, inst);
Simon Hunt56a2ea42014-11-19 12:39:31 -0800714 updateInstances();
Simon Hunt56a2ea42014-11-19 12:39:31 -0800715 } else {
716 logicError('updateInstance lookup fail. ID = "' + id + '"');
717 }
718 }
719
Simon Huntbb282f52014-11-10 11:08:19 -0800720 function updateDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800721 evTrace(data);
Simon Huntbb282f52014-11-10 11:08:19 -0800722 var device = data.payload,
723 id = device.id,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800724 d = network.lookup[id],
725 wasOnline;
726
Simon Hunt62c47542014-11-22 22:16:32 -0800727 if (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -0800728 wasOnline = d.online;
Simon Hunt62c47542014-11-22 22:16:32 -0800729 $.extend(d, device);
730 if (positionNode(d, true)) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800731 sendUpdateMeta(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -0800732 }
733 updateNodes();
Simon Hunt6d9bd032014-11-28 22:16:40 -0800734 if (wasOnline !== d.online) {
735 findAttachedLinks(d.id).forEach(restyleLinkElement);
736 updateOfflineVisibility(d);
737 }
Simon Huntbb282f52014-11-10 11:08:19 -0800738 } else {
739 logicError('updateDevice lookup fail. ID = "' + id + '"');
740 }
741 }
742
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800743 function updateLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800744 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800745 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800746 result = findLink(link, 'update'),
747 bad = result.badLogic;
748 if (bad) {
749 logicError(bad + ': ' + link.id);
750 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800751 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800752 result.updateWith(link);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800753 }
754
Simon Hunt7cd48f32014-11-09 23:42:50 -0800755 function updateHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800756 evTrace(data);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800757 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800758 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800759 d = network.lookup[id];
760 if (d) {
761 $.extend(d, host);
Simon Hunt6d9bd032014-11-28 22:16:40 -0800762 if (positionNode(d, true)) {
763 sendUpdateMeta(d, true);
764 }
765 updateNodes(d);
Simon Huntbb282f52014-11-10 11:08:19 -0800766 } else {
767 logicError('updateHost lookup fail. ID = "' + id + '"');
768 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800769 }
770
Simon Hunt44031102014-11-11 13:20:36 -0800771 // TODO: fold removeX(...) methods into base method - remove dup code
Simon Hunt7b403bc2014-11-22 19:01:00 -0800772 function removeInstance(data) {
773 evTrace(data);
774 var inst = data.payload,
775 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800776 d = onosInstances[id];
777 if (d) {
778 var idx = find(id, onosOrder);
Simon Hunt7b403bc2014-11-22 19:01:00 -0800779 if (idx >= 0) {
780 onosOrder.splice(idx, 1);
781 }
782 delete onosInstances[id];
783 updateInstances();
784 } else {
785 logicError('updateInstance lookup fail. ID = "' + id + '"');
786 }
787 }
788
Simon Huntca867ac2014-11-28 18:07:35 -0800789 function removeDevice(data) {
790 evTrace(data);
791 var device = data.payload,
792 id = device.id,
793 d = network.lookup[id];
794 if (d) {
795 removeDeviceElement(d);
796 } else {
797 logicError('removeDevice lookup fail. ID = "' + id + '"');
798 }
799 }
800
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800801 function removeLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800802 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800803 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800804 result = findLink(link, 'remove'),
805 bad = result.badLogic;
806 if (bad) {
Simon Huntca867ac2014-11-28 18:07:35 -0800807 // may have already removed link, if attached to removed device
808 console.warn(bad + ': ' + link.id);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800809 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800810 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800811 result.removeRawLink();
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800812 }
813
Simon Hunt44031102014-11-11 13:20:36 -0800814 function removeHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800815 evTrace(data);
Simon Hunt44031102014-11-11 13:20:36 -0800816 var host = data.payload,
817 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800818 d = network.lookup[id];
819 if (d) {
820 removeHostElement(d, true);
Simon Hunt44031102014-11-11 13:20:36 -0800821 } else {
Simon Huntca867ac2014-11-28 18:07:35 -0800822 // may have already removed host, if attached to removed device
823 console.warn('removeHost lookup fail. ID = "' + id + '"');
Simon Hunt44031102014-11-11 13:20:36 -0800824 }
825 }
826
Simon Huntca867ac2014-11-28 18:07:35 -0800827 // the following events are server responses to user actions
Thomas Vachuska47635c62014-11-22 01:21:36 -0800828 function showSummary(data) {
829 evTrace(data);
830 populateSummary(data.payload);
Simon Hunt06811b72014-11-25 18:54:48 -0800831 showSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800832 }
833
Simon Hunt61d04042014-11-11 17:27:16 -0800834 function showDetails(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800835 evTrace(data);
Simon Hunt27d322d2014-11-28 10:45:43 -0800836 haveDetails = true;
Simon Hunt61d04042014-11-11 17:27:16 -0800837 populateDetails(data.payload);
Simon Hunt27d322d2014-11-28 10:45:43 -0800838 if (useDetails) {
839 showDetailPane();
840 }
Simon Hunt61d04042014-11-11 17:27:16 -0800841 }
842
Simon Huntb53e0682014-11-12 13:32:01 -0800843 function showTraffic(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800844 evTrace(data);
Thomas Vachuska4731f122014-11-20 04:56:19 -0800845 var paths = data.payload.paths,
846 hasTraffic = false;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800847
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800848 // Revert any links hilighted previously.
Thomas Vachuska4731f122014-11-20 04:56:19 -0800849 link.style('stroke-width', null)
Thomas Vachuskaa3148a72014-11-19 21:38:35 -0800850 .classed('primary secondary animated optical', false);
Simon Hunte2575b62014-11-18 15:25:53 -0800851 // Remove all previous labels.
852 removeLinkLabels();
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800853
Simon Hunte2575b62014-11-18 15:25:53 -0800854 // Now hilight all links in the paths payload, and attach
855 // labels to them, if they are defined.
Simon Hunta255a2c2014-11-13 22:29:35 -0800856 paths.forEach(function (p) {
Simon Hunte2575b62014-11-18 15:25:53 -0800857 var n = p.links.length,
858 i,
859 ldata;
860
Thomas Vachuska4731f122014-11-20 04:56:19 -0800861 hasTraffic = hasTraffic || p.traffic;
Simon Hunte2575b62014-11-18 15:25:53 -0800862 for (i=0; i<n; i++) {
863 ldata = findLinkById(p.links[i]);
Thomas Vachuska4731f122014-11-20 04:56:19 -0800864 if (ldata && ldata.el) {
Simon Hunte2575b62014-11-18 15:25:53 -0800865 ldata.el.classed(p.class, true);
866 ldata.label = p.labels[i];
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800867 }
Simon Hunte2575b62014-11-18 15:25:53 -0800868 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800869 });
Thomas Vachuska4731f122014-11-20 04:56:19 -0800870
Simon Hunte2575b62014-11-18 15:25:53 -0800871 updateLinks();
Thomas Vachuska4731f122014-11-20 04:56:19 -0800872
873 if (hasTraffic && !antTimer) {
874 startAntTimer();
875 } else if (!hasTraffic && antTimer) {
876 stopAntTimer();
877 }
Simon Huntb53e0682014-11-12 13:32:01 -0800878 }
879
Simon Hunt56d51852014-11-09 13:03:35 -0800880 // ...............................
881
Simon Hunt99c13842014-11-06 18:23:12 -0800882 function unknownEvent(data) {
Simon Hunt434cf142014-11-24 11:10:28 -0800883 console.warn('Unknown event type: "' + data.event + '"', data);
Simon Hunt99c13842014-11-06 18:23:12 -0800884 }
885
886 function handleServerEvent(data) {
887 var fn = eventDispatch[data.event] || unknownEvent;
888 fn(data);
889 }
890
891 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -0800892 // Out-going messages...
893
Simon Huntb53e0682014-11-12 13:32:01 -0800894 function nSel() {
895 return selectOrder.length;
896 }
Simon Hunt61d04042014-11-11 17:27:16 -0800897 function getSel(idx) {
898 return selections[selectOrder[idx]];
899 }
Simon Huntb53e0682014-11-12 13:32:01 -0800900 function allSelectionsClass(cls) {
901 for (var i=0, n=nSel(); i<n; i++) {
902 if (getSel(i).obj.class !== cls) {
903 return false;
904 }
905 }
906 return true;
907 }
Simon Hunt61d04042014-11-11 17:27:16 -0800908
Thomas Vachuska47635c62014-11-22 01:21:36 -0800909 function toggleInstances() {
910 if (!oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800911 showInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800912 } else {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800913 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800914 }
915 }
916
Simon Huntb0ecfa52014-11-23 21:05:12 -0800917 function showInstances() {
918 oiBox.show();
919 colorAffinity = true;
920 updateDeviceColors();
921 }
922
923 function hideInstances() {
924 oiBox.hide();
925 colorAffinity = false;
926 cancelAffinity();
927 updateDeviceColors();
928 }
929
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800930 function equalizeMasters() {
931 flash('Equalizing master roles');
932 sendMessage('equalizeMasters');
933 }
934
Thomas Vachuska47635c62014-11-22 01:21:36 -0800935 function toggleSummary() {
936 if (!summaryPane.isVisible()) {
937 requestSummary();
938 } else {
939 cancelSummary();
940 }
941 }
942
943 // request overall summary data
944 function requestSummary() {
945 sendMessage('requestSummary', {});
946 }
947
948 function cancelSummary() {
949 sendMessage('cancelSummary', {});
Simon Hunt06811b72014-11-25 18:54:48 -0800950 hideSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800951 }
952
Simon Hunt27d322d2014-11-28 10:45:43 -0800953 function toggleDetails() {
954 useDetails = !useDetails;
955 if (useDetails) {
956 flash('Enable details pane');
957 if (haveDetails) {
958 showDetailPane();
959 }
960 } else {
961 flash('Disable details pane');
962 hideDetailPane();
963 }
964 }
965
Simon Hunt06811b72014-11-25 18:54:48 -0800966 // encapsulate interaction between summary and details panes
967 function showSummaryPane() {
968 if (detailPane.isVisible()) {
969 detailPane.down(summaryPane.show);
970 } else {
971 summaryPane.show();
972 }
973 }
974
975 function hideSummaryPane() {
976 summaryPane.hide(function () {
977 if (detailPane.isVisible()) {
978 detailPane.up();
979 }
980 });
981 }
982
983 function showDetailPane() {
984 if (summaryPane.isVisible()) {
985 detailPane.down(detailPane.show);
986 } else {
987 detailPane.up(detailPane.show);
988 }
989 }
990
991 function hideDetailPane() {
992 detailPane.hide();
993 }
994
995
Simon Hunt61d04042014-11-11 17:27:16 -0800996 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -0800997 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -0800998 function requestDetails() {
999 var data = getSel(0).obj,
1000 payload = {
1001 id: data.id,
1002 class: data.class
1003 };
1004 sendMessage('requestDetails', payload);
1005 }
1006
Thomas Vachuska9edca302014-11-22 17:06:42 -08001007 function addHostIntentAction() {
Simon Huntd72bc702014-11-13 18:38:04 -08001008 sendMessage('addHostIntent', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001009 one: selectOrder[0],
1010 two: selectOrder[1],
1011 ids: selectOrder
Simon Huntd72bc702014-11-13 18:38:04 -08001012 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001013 flash('Host-to-Host flow added');
Simon Huntd72bc702014-11-13 18:38:04 -08001014 }
1015
Thomas Vachuska9edca302014-11-22 17:06:42 -08001016 function addMultiSourceIntentAction() {
1017 sendMessage('addMultiSourceIntent', {
1018 src: selectOrder.slice(0, selectOrder.length - 1),
1019 dst: selectOrder[selectOrder.length - 1],
1020 ids: selectOrder
1021 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001022 flash('Multi-Source flow added');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001023 }
1024
Thomas Vachuska9edca302014-11-22 17:06:42 -08001025
Thomas Vachuska47635c62014-11-22 01:21:36 -08001026 function cancelTraffic() {
1027 sendMessage('cancelTraffic', {});
1028 }
1029
Thomas Vachuska9edca302014-11-22 17:06:42 -08001030 function requestTrafficForMode() {
1031 if (hoverMode === hoverModeAll) {
1032 requestAllTraffic();
1033 } else if (hoverMode === hoverModeFlows) {
1034 requestDeviceLinkFlows();
1035 } else if (hoverMode === hoverModeIntents) {
1036 requestSelectTraffic();
Simon Huntd72bc702014-11-13 18:38:04 -08001037 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001038 }
Simon Huntd72bc702014-11-13 18:38:04 -08001039
Thomas Vachuska9edca302014-11-22 17:06:42 -08001040 function showTrafficAction() {
1041 hoverMode = hoverModeIntents;
1042 requestSelectTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001043 flash('Related Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001044 }
1045
1046 function requestSelectTraffic() {
1047 if (validateSelectionContext()) {
1048 var hoverId = (hoverMode === hoverModeIntents && hovered &&
1049 (hovered.class === 'host' || hovered.class === 'device'))
Simon Huntd72bc702014-11-13 18:38:04 -08001050 ? hovered.id : '';
Thomas Vachuska9edca302014-11-22 17:06:42 -08001051 sendMessage('requestTraffic', {
1052 ids: selectOrder,
1053 hover: hoverId
1054 });
1055 }
Simon Huntd72bc702014-11-13 18:38:04 -08001056 }
1057
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -08001058
Thomas Vachuska29617e52014-11-20 03:17:46 -08001059 function showDeviceLinkFlowsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001060 hoverMode = hoverModeFlows;
1061 requestDeviceLinkFlows();
Simon Hunt27d322d2014-11-28 10:45:43 -08001062 flash('Device Flows');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001063 }
1064
Thomas Vachuska9edca302014-11-22 17:06:42 -08001065 function requestDeviceLinkFlows() {
1066 if (validateSelectionContext()) {
1067 var hoverId = (hoverMode === hoverModeFlows && hovered &&
1068 (hovered.class === 'device')) ? hovered.id : '';
1069 sendMessage('requestDeviceLinkFlows', {
1070 ids: selectOrder,
1071 hover: hoverId
1072 });
1073 }
1074 }
1075
1076
1077 function showAllTrafficAction() {
1078 hoverMode = hoverModeAll;
1079 requestAllTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001080 flash('All Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001081 }
1082
1083 function requestAllTraffic() {
1084 sendMessage('requestAllTraffic', {});
1085 }
1086
1087 function validateSelectionContext() {
Thomas Vachuska29617e52014-11-20 03:17:46 -08001088 if (!hovered && nSel() === 0) {
Thomas Vachuska47635c62014-11-22 01:21:36 -08001089 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08001090 return false;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001091 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001092 return true;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001093 }
Simon Huntd72bc702014-11-13 18:38:04 -08001094
Simon Hunta6a9fe72014-11-20 11:17:12 -08001095
Simon Hunt61d04042014-11-11 17:27:16 -08001096 // ==============================
Simon Hunta5e89142014-11-14 07:00:33 -08001097 // onos instance panel functions
Simon Huntb82f6902014-11-22 11:53:15 -08001098
Simon Hunt7b403bc2014-11-22 19:01:00 -08001099 var instCfg = {
1100 rectPad: 8,
1101 nodeOx: 9,
1102 nodeOy: 9,
1103 nodeDim: 40,
1104 birdOx: 19,
1105 birdOy: 21,
1106 birdDim: 21,
1107 uiDy: 45,
1108 titleDy: 30,
1109 textYOff: 20,
1110 textYSpc: 15
1111 };
1112
1113 function viewBox(dim) {
1114 return '0 0 ' + dim.w + ' ' + dim.h;
1115 }
1116
1117 function instRectAttr(dim) {
1118 var pad = instCfg.rectPad;
1119 return {
1120 x: pad,
1121 y: pad,
1122 width: dim.w - pad*2,
1123 height: dim.h - pad*2,
Simon Huntb0ecfa52014-11-23 21:05:12 -08001124 rx: 6
Simon Hunt7b403bc2014-11-22 19:01:00 -08001125 };
1126 }
1127
1128 function computeDim(self) {
1129 var css = window.getComputedStyle(self);
1130 return {
1131 w: stripPx(css.width),
1132 h: stripPx(css.height)
1133 };
Simon Huntb82f6902014-11-22 11:53:15 -08001134 }
Simon Hunta5e89142014-11-14 07:00:33 -08001135
1136 function updateInstances() {
1137 var onoses = oiBox.el.selectAll('.onosInst')
Simon Huntb82f6902014-11-22 11:53:15 -08001138 .data(onosOrder, function (d) { return d.id; }),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001139 instDim = {w:0,h:0},
1140 c = instCfg;
Simon Hunta5e89142014-11-14 07:00:33 -08001141
Simon Hunt7b403bc2014-11-22 19:01:00 -08001142 function nSw(n) {
1143 return '# Switches: ' + n;
1144 }
Simon Hunta5e89142014-11-14 07:00:33 -08001145
Simon Huntb82f6902014-11-22 11:53:15 -08001146 // operate on existing onos instances if necessary
1147 onoses.each(function (d) {
1148 var el = d3.select(this),
1149 svg = el.select('svg');
Simon Hunt7b403bc2014-11-22 19:01:00 -08001150 instDim = computeDim(this);
Simon Huntb82f6902014-11-22 11:53:15 -08001151
1152 // update online state
1153 el.classed('online', d.online);
1154
1155 // update ui-attached state
1156 svg.select('use.uiBadge').remove();
1157 if (d.uiAttached) {
1158 attachUiBadge(svg);
1159 }
1160
Simon Hunt7b403bc2014-11-22 19:01:00 -08001161 function updAttr(id, value) {
1162 svg.select('text.instLabel.'+id).text(value);
1163 }
1164
1165 updAttr('ip', d.ip);
1166 updAttr('ns', nSw(d.switches));
Simon Huntb82f6902014-11-22 11:53:15 -08001167 });
1168
1169
1170 // operate on new onos instances
Simon Hunta5e89142014-11-14 07:00:33 -08001171 var entering = onoses.enter()
1172 .append('div')
1173 .attr('class', 'onosInst')
1174 .classed('online', function (d) { return d.online; })
Simon Hunt9c15eca2014-11-15 18:37:59 -08001175 .on('click', clickInst);
1176
Simon Huntb82f6902014-11-22 11:53:15 -08001177 entering.each(function (d) {
Simon Hunt9c15eca2014-11-15 18:37:59 -08001178 var el = d3.select(this),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001179 rectAttr,
1180 svg;
1181 instDim = computeDim(this);
1182 rectAttr = instRectAttr(instDim);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001183
Simon Hunt7b403bc2014-11-22 19:01:00 -08001184 svg = el.append('svg').attr({
1185 width: instDim.w,
1186 height: instDim.h,
1187 viewBox: viewBox(instDim)
Simon Hunt95908012014-11-20 10:20:26 -08001188 });
Simon Huntb82f6902014-11-22 11:53:15 -08001189
Simon Hunt7b403bc2014-11-22 19:01:00 -08001190 svg.append('rect').attr(rectAttr);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001191
Thomas Vachuskae02e11c2014-11-24 16:13:52 -08001192 //appendGlyph(svg, c.nodeOx, c.nodeOy, c.nodeDim, '#node');
1193 appendBadge(svg, 14, 14, 28, '#bird');
Simon Huntb82f6902014-11-22 11:53:15 -08001194
1195 if (d.uiAttached) {
1196 attachUiBadge(svg);
1197 }
1198
Simon Hunt7b403bc2014-11-22 19:01:00 -08001199 var left = c.nodeOx + c.nodeDim,
1200 len = rectAttr.width - left,
1201 hlen = len / 2,
1202 midline = hlen + left;
Simon Hunt9c15eca2014-11-15 18:37:59 -08001203
Simon Hunt7b403bc2014-11-22 19:01:00 -08001204 // title
1205 svg.append('text')
1206 .attr({
1207 class: 'instTitle',
1208 x: midline,
1209 y: c.titleDy
1210 })
1211 .text(d.id);
1212
1213 // a couple of attributes
1214 var ty = c.titleDy + c.textYOff;
1215
1216 function addAttr(id, label) {
1217 svg.append('text').attr({
1218 class: 'instLabel ' + id,
1219 x: midline,
1220 y: ty
1221 }).text(label);
1222 ty += c.textYSpc;
1223 }
1224
1225 addAttr('ip', d.ip);
1226 addAttr('ns', nSw(d.switches));
Simon Hunt9c15eca2014-11-15 18:37:59 -08001227 });
Simon Hunta5e89142014-11-14 07:00:33 -08001228
1229 // operate on existing + new onoses here
Simon Hunt8f40cce2014-11-23 15:57:30 -08001230 // set the affinity colors...
1231 onoses.each(function (d) {
1232 var el = d3.select(this),
1233 rect = el.select('svg').select('rect'),
1234 col = instColor(d.id, d.online);
1235 rect.style('fill', col);
1236 });
Simon Hunta5e89142014-11-14 07:00:33 -08001237
Simon Hunt7b403bc2014-11-22 19:01:00 -08001238 // adjust the panel size appropriately...
1239 oiBox.width(instDim.w * onosOrder.length);
1240 oiBox.height(instDim.h);
1241
1242 // remove any outgoing instances
1243 onoses.exit().remove();
Simon Hunta5e89142014-11-14 07:00:33 -08001244 }
1245
Simon Hunt8f40cce2014-11-23 15:57:30 -08001246 function instColor(id, online) {
1247 return cat7.get(id, !online, network.view.getTheme());
1248 }
1249
Simon Hunt9462e8c2014-11-14 17:28:09 -08001250 function clickInst(d) {
1251 var el = d3.select(this),
1252 aff = el.classed('affinity');
1253 if (!aff) {
1254 setAffinity(el, d);
1255 } else {
1256 cancelAffinity();
1257 }
1258 }
1259
1260 function setAffinity(el, d) {
1261 d3.selectAll('.onosInst')
1262 .classed('mastership', true)
1263 .classed('affinity', false);
1264 el.classed('affinity', true);
1265
1266 suppressLayers(true);
1267 node.each(function (n) {
1268 if (n.master === d.id) {
1269 n.el.classed('suppressed', false);
1270 }
1271 });
1272 oiShowMaster = true;
1273 }
1274
1275 function cancelAffinity() {
1276 d3.selectAll('.onosInst')
1277 .classed('mastership affinity', false);
1278 restoreLayerState();
1279 oiShowMaster = false;
1280 }
1281
Simon Hunt7b403bc2014-11-22 19:01:00 -08001282 // TODO: these should be moved out to utility module.
1283 function stripPx(s) {
1284 return s.replace(/px$/,'');
1285 }
1286
1287 function appendUse(svg, ox, oy, dim, iid, cls) {
1288 var use = svg.append('use').attr({
1289 transform: translate(ox,oy),
1290 'xlink:href': iid,
1291 width: dim,
1292 height: dim
1293 });
1294 if (cls) {
1295 use.classed(cls, true);
1296 }
1297 return use;
1298 }
1299
1300 function appendGlyph(svg, ox, oy, dim, iid, cls) {
1301 appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
1302 }
1303
1304 function appendBadge(svg, ox, oy, dim, iid, cls) {
1305 appendUse(svg, ox, oy, dim, iid,cls ).classed('badgeIcon', true);
1306 }
1307
1308 function attachUiBadge(svg) {
1309 appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
1310 }
1311
Simon Hunt434cf142014-11-24 11:10:28 -08001312 function visVal(b) {
1313 return b ? 'visible' : 'hidden';
1314 }
1315
Simon Hunta5e89142014-11-14 07:00:33 -08001316 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -08001317 // force layout modification functions
1318
1319 function translate(x, y) {
1320 return 'translate(' + x + ',' + y + ')';
1321 }
1322
Simon Hunte2575b62014-11-18 15:25:53 -08001323 function rotate(deg) {
1324 return 'rotate(' + deg + ')';
1325 }
1326
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001327 function missMsg(what, id) {
1328 return '\n[' + what + '] "' + id + '" missing ';
1329 }
1330
1331 function linkEndPoints(srcId, dstId) {
1332 var srcNode = network.lookup[srcId],
1333 dstNode = network.lookup[dstId],
1334 sMiss = !srcNode ? missMsg('src', srcId) : '',
1335 dMiss = !dstNode ? missMsg('dst', dstId) : '';
1336
1337 if (sMiss || dMiss) {
1338 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
1339 return null;
1340 }
1341 return {
1342 source: srcNode,
1343 target: dstNode,
1344 x1: srcNode.x,
1345 y1: srcNode.y,
1346 x2: dstNode.x,
1347 y2: dstNode.y
1348 };
1349 }
1350
Simon Hunt56d51852014-11-09 13:03:35 -08001351 function createHostLink(host) {
1352 var src = host.id,
1353 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -08001354 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001355 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -08001356
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001357 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -08001358 return null;
1359 }
1360
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001361 // Synthesize link ...
1362 $.extend(lnk, {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001363 key: id,
Simon Hunt56d51852014-11-09 13:03:35 -08001364 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001365
1366 type: function () { return 'hostLink'; },
Simon Hunt6d9bd032014-11-28 22:16:40 -08001367 online: function () {
1368 // hostlink target is edge switch
1369 return lnk.target.online;
1370 },
Simon Hunt8257f4c2014-11-16 19:34:54 -08001371 linkWidth: function () { return 1; }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001372 });
Simon Hunt99c13842014-11-06 18:23:12 -08001373 return lnk;
1374 }
1375
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001376 function createLink(link) {
Simon Hunta6a9fe72014-11-20 11:17:12 -08001377 var lnk = linkEndPoints(link.src, link.dst);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001378
1379 if (!lnk) {
1380 return null;
1381 }
1382
Simon Hunt8257f4c2014-11-16 19:34:54 -08001383 $.extend(lnk, {
1384 key: link.id,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001385 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001386 fromSource: link,
1387
1388 // functions to aggregate dual link state
1389 type: function () {
1390 var s = lnk.fromSource,
1391 t = lnk.fromTarget;
1392 return (s && s.type) || (t && t.type) || defaultLinkType;
1393 },
1394 online: function () {
1395 var s = lnk.fromSource,
Simon Hunt6d9bd032014-11-28 22:16:40 -08001396 t = lnk.fromTarget,
1397 both = lnk.source.online && lnk.target.online;
1398 return both && ((s && s.online) || (t && t.online));
Simon Hunt8257f4c2014-11-16 19:34:54 -08001399 },
1400 linkWidth: function () {
1401 var s = lnk.fromSource,
1402 t = lnk.fromTarget,
1403 ws = (s && s.linkWidth) || 0,
1404 wt = (t && t.linkWidth) || 0;
1405 return Math.max(ws, wt);
1406 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001407 });
1408 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -08001409 }
1410
Simon Hunte2575b62014-11-18 15:25:53 -08001411 function removeLinkLabels() {
1412 network.links.forEach(function (d) {
1413 d.label = '';
1414 });
1415 }
1416
Simon Hunt434cf142014-11-24 11:10:28 -08001417 function showHostVis(el) {
1418 el.style('visibility', visVal(showHosts));
1419 }
1420
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001421 var widthRatio = 1.4,
1422 linkScale = d3.scale.linear()
1423 .domain([1, 12])
1424 .range([widthRatio, 12 * widthRatio])
1425 .clamp(true);
1426
Simon Hunt99c13842014-11-06 18:23:12 -08001427 function updateLinks() {
1428 link = linkG.selectAll('.link')
Simon Hunt8257f4c2014-11-16 19:34:54 -08001429 .data(network.links, function (d) { return d.key; });
Simon Hunt99c13842014-11-06 18:23:12 -08001430
1431 // operate on existing links, if necessary
1432 // link .foo() .bar() ...
1433
1434 // operate on entering links:
1435 var entering = link.enter()
1436 .append('line')
1437 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001438 x1: function (d) { return d.x1; },
1439 y1: function (d) { return d.y1; },
1440 x2: function (d) { return d.x2; },
1441 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001442 stroke: config.topo.linkInColor,
1443 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -08001444 });
1445
1446 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -08001447 entering.each(function (d) {
1448 var link = d3.select(this);
1449 // provide ref to element selection from backing data....
1450 d.el = link;
Simon Hunt8257f4c2014-11-16 19:34:54 -08001451 restyleLinkElement(d);
Simon Hunt434cf142014-11-24 11:10:28 -08001452 if (d.type() === 'hostLink') {
1453 showHostVis(link);
1454 }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001455 });
Thomas Vachuska4830d392014-11-09 17:09:56 -08001456
1457 // operate on both existing and new links, if necessary
1458 //link .foo() .bar() ...
1459
Simon Hunte2575b62014-11-18 15:25:53 -08001460 // apply or remove labels
1461 var labelData = getLabelData();
1462 applyLinkLabels(labelData);
1463
Thomas Vachuska4830d392014-11-09 17:09:56 -08001464 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -08001465 link.exit()
Simon Hunt6d9bd032014-11-28 22:16:40 -08001466 .attr('stroke-dasharray', '3 3')
Simon Hunt13bf9c82014-11-18 07:26:44 -08001467 .style('opacity', 0.5)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001468 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -08001469 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001470 .attr({
Simon Hunt6d9bd032014-11-28 22:16:40 -08001471 'stroke-dasharray': '3 12',
Simon Hunt13bf9c82014-11-18 07:26:44 -08001472 stroke: config.topo.linkOutColor,
1473 'stroke-width': config.topo.linkOutWidth
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001474 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001475 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -08001476 .remove();
Simon Hunte2575b62014-11-18 15:25:53 -08001477
1478 // NOTE: invoke a single tick to force the labels to position
1479 // onto their links.
1480 tick();
1481 }
1482
1483 function getLabelData() {
1484 // create the backing data for showing labels..
1485 var data = [];
1486 link.each(function (d) {
1487 if (d.label) {
1488 data.push({
1489 id: 'lab-' + d.key,
1490 key: d.key,
1491 label: d.label,
1492 ldata: d
1493 });
1494 }
1495 });
1496 return data;
1497 }
1498
1499 var linkLabelOffset = '0.3em';
1500
1501 function applyLinkLabels(data) {
1502 var entering;
1503
1504 linkLabel = linkLabelG.selectAll('.linkLabel')
1505 .data(data, function (d) { return d.id; });
1506
Simon Hunt56a2ea42014-11-19 12:39:31 -08001507 // for elements already existing, we need to update the text
1508 // and adjust the rectangle size to fit
1509 linkLabel.each(function (d) {
1510 var el = d3.select(this),
1511 rect = el.select('rect'),
1512 text = el.select('text');
1513 text.text(d.label);
1514 rect.attr(rectAroundText(el));
1515 });
Thomas Vachuskaf75b7ab2014-11-19 12:15:55 -08001516
Simon Hunte2575b62014-11-18 15:25:53 -08001517 entering = linkLabel.enter().append('g')
1518 .classed('linkLabel', true)
1519 .attr('id', function (d) { return d.id; });
1520
1521 entering.each(function (d) {
1522 var el = d3.select(this),
1523 rect,
1524 text,
1525 parms = {
1526 x1: d.ldata.x1,
1527 y1: d.ldata.y1,
1528 x2: d.ldata.x2,
1529 y2: d.ldata.y2
1530 };
1531
1532 d.el = el;
1533 rect = el.append('rect');
1534 text = el.append('text').text(d.label);
1535 rect.attr(rectAroundText(el));
1536 text.attr('dy', linkLabelOffset);
1537
1538 el.attr('transform', transformLabel(parms));
1539 });
1540
1541 // Remove any links that are no longer required.
1542 linkLabel.exit().remove();
1543 }
1544
1545 function rectAroundText(el) {
1546 var text = el.select('text'),
1547 box = text.node().getBBox();
1548
1549 // translate the bbox so that it is centered on [x,y]
1550 box.x = -box.width / 2;
1551 box.y = -box.height / 2;
1552
1553 // add padding
1554 box.x -= 1;
1555 box.width += 2;
1556 return box;
1557 }
1558
1559 function transformLabel(p) {
1560 var dx = p.x2 - p.x1,
1561 dy = p.y2 - p.y1,
1562 xMid = dx/2 + p.x1,
1563 yMid = dy/2 + p.y1;
Simon Hunte2575b62014-11-18 15:25:53 -08001564 return translate(xMid, yMid);
Simon Hunt99c13842014-11-06 18:23:12 -08001565 }
1566
1567 function createDeviceNode(device) {
1568 // start with the object as is
1569 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -08001570 type = device.type,
Simon Huntc72967b2014-11-20 09:21:42 -08001571 svgCls = type ? 'node device ' + type : 'node device',
1572 labels = device.labels || [];
1573
Simon Hunt99c13842014-11-06 18:23:12 -08001574 // Augment as needed...
1575 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -08001576 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -08001577 positionNode(node);
Simon Hunt99c13842014-11-06 18:23:12 -08001578 return node;
1579 }
1580
Simon Hunt56d51852014-11-09 13:03:35 -08001581 function createHostNode(host) {
1582 // start with the object as is
1583 var node = host;
1584
1585 // Augment as needed...
1586 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001587 if (!node.type) {
Simon Hunt209155e2014-11-21 12:16:09 -08001588 node.type = 'endstation';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001589 }
Simon Hunt7fa116d2014-11-17 14:16:55 -08001590 node.svgClass = 'node host ' + node.type;
Simon Hunt56d51852014-11-09 13:03:35 -08001591 positionNode(node);
Simon Hunt56d51852014-11-09 13:03:35 -08001592 return node;
1593 }
1594
Simon Hunt62c47542014-11-22 22:16:32 -08001595 function positionNode(node, forUpdate) {
Simon Hunt99c13842014-11-06 18:23:12 -08001596 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -08001597 x = meta && meta.x,
1598 y = meta && meta.y,
1599 xy;
Simon Hunt99c13842014-11-06 18:23:12 -08001600
Simon Huntac9e24f2014-11-12 10:12:21 -08001601 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -08001602 if (x && y) {
1603 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001604 node.px = node.x = x;
1605 node.py = node.y = y;
Simon Huntac9e24f2014-11-12 10:12:21 -08001606 return;
Simon Hunt99c13842014-11-06 18:23:12 -08001607 }
Simon Huntac9e24f2014-11-12 10:12:21 -08001608
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001609 var location = node.location;
1610 if (location && location.type === 'latlng') {
1611 var coord = geoMapProjection([location.lng, location.lat]);
1612 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001613 node.px = node.x = coord[0];
1614 node.py = node.y = coord[1];
Simon Hunt62c47542014-11-22 22:16:32 -08001615 return true;
1616 }
1617
1618 // if this is a node update (not a node add).. skip randomizer
1619 if (forUpdate) {
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001620 return;
1621 }
1622
Simon Huntac9e24f2014-11-12 10:12:21 -08001623 // Note: Placing incoming unpinned nodes at exactly the same point
1624 // (center of the view) causes them to explode outwards when
1625 // the force layout kicks in. So, we spread them out a bit
1626 // initially, to provide a more serene layout convergence.
1627 // Additionally, if the node is a host, we place it near
1628 // the device it is connected to.
1629
1630 function spread(s) {
1631 return Math.floor((Math.random() * s) - s/2);
1632 }
1633
1634 function randDim(dim) {
1635 return dim / 2 + spread(dim * 0.7071);
1636 }
1637
1638 function rand() {
1639 return {
1640 x: randDim(network.view.width()),
1641 y: randDim(network.view.height())
1642 };
1643 }
1644
1645 function near(node) {
1646 var min = 12,
1647 dx = spread(12),
1648 dy = spread(12);
1649 return {
1650 x: node.x + min + dx,
1651 y: node.y + min + dy
1652 };
1653 }
1654
1655 function getDevice(cp) {
1656 var d = network.lookup[cp.device];
1657 return d || rand();
1658 }
1659
1660 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1661 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -08001662 }
1663
Simon Hunt99c13842014-11-06 18:23:12 -08001664
Simon Huntc72967b2014-11-20 09:21:42 -08001665 function iconGlyphUrl(d) {
1666 var which = d.type || 'unknown';
1667 return '#' + which;
1668 }
1669
Simon Hunt99c13842014-11-06 18:23:12 -08001670 // returns the newly computed bounding box of the rectangle
1671 function adjustRectToFitText(n) {
1672 var text = n.select('text'),
1673 box = text.node().getBBox(),
1674 lab = config.labels;
1675
1676 text.attr('text-anchor', 'middle')
1677 .attr('y', '-0.8em')
1678 .attr('x', lab.imgPad/2);
1679
1680 // translate the bbox so that it is centered on [x,y]
1681 box.x = -box.width / 2;
1682 box.y = -box.height / 2;
1683
1684 // add padding
1685 box.x -= (lab.padLR + lab.imgPad/2);
1686 box.width += lab.padLR * 2 + lab.imgPad;
1687 box.y -= lab.padTB;
1688 box.height += lab.padTB * 2;
1689
1690 return box;
1691 }
1692
Simon Hunt1a9eff92014-11-07 11:06:34 -08001693 function mkSvgClass(d) {
1694 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1695 }
1696
Simon Hunt7cd48f32014-11-09 23:42:50 -08001697 function hostLabel(d) {
1698 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1699 return d.labels[idx];
1700 }
1701 function deviceLabel(d) {
1702 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1703 return d.labels[idx];
1704 }
Simon Huntc72967b2014-11-20 09:21:42 -08001705 function trimLabel(label) {
1706 return (label && label.trim()) || '';
1707 }
1708
1709 function emptyBox() {
1710 return {
1711 x: -2,
1712 y: -2,
1713 width: 4,
1714 height: 4
1715 };
Simon Hunt7cd48f32014-11-09 23:42:50 -08001716 }
1717
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001718 function updateDeviceLabel(d) {
Simon Huntc72967b2014-11-20 09:21:42 -08001719 var label = trimLabel(deviceLabel(d)),
1720 noLabel = !label,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001721 node = d.el,
Simon Huntc72967b2014-11-20 09:21:42 -08001722 box,
1723 dx,
Simon Hunt395a70c2014-11-22 23:17:40 -08001724 dy,
1725 cfg = config.icons.device;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001726
1727 node.select('text')
1728 .text(label)
1729 .style('opacity', 0)
1730 .transition()
1731 .style('opacity', 1);
1732
Simon Huntc72967b2014-11-20 09:21:42 -08001733 if (noLabel) {
1734 box = emptyBox();
Simon Hunt395a70c2014-11-22 23:17:40 -08001735 dx = -cfg.dim/2;
1736 dy = -cfg.dim/2;
Simon Huntc72967b2014-11-20 09:21:42 -08001737 } else {
1738 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08001739 dx = box.x + cfg.xoff;
1740 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08001741 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001742
1743 node.select('rect')
1744 .transition()
1745 .attr(box);
1746
Simon Huntc72967b2014-11-20 09:21:42 -08001747 node.select('g.deviceIcon')
Thomas Vachuska89543292014-11-19 11:28:33 -08001748 .transition()
Simon Huntc72967b2014-11-20 09:21:42 -08001749 .attr('transform', translate(dx, dy));
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001750 }
1751
1752 function updateHostLabel(d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001753 var label = trimLabel(hostLabel(d));
1754 d.el.select('text').text(label);
Simon Huntbb282f52014-11-10 11:08:19 -08001755 }
1756
Simon Hunt434cf142014-11-24 11:10:28 -08001757 function updateHostVisibility() {
1758 var v = visVal(showHosts);
1759 nodeG.selectAll('.host').style('visibility', v);
1760 linkG.selectAll('.hostLink').style('visibility', v);
1761 }
1762
Simon Hunt6d9bd032014-11-28 22:16:40 -08001763 function findOfflineNodes() {
1764 var a = [];
1765 network.nodes.forEach(function (d) {
1766 if (d.class === 'device' && !d.online) {
1767 a.push(d);
1768 }
1769 });
1770 return a;
1771 }
1772
1773 function updateOfflineVisibility(dev) {
1774 var so = showOffline,
1775 sh = showHosts,
1776 vb = 'visibility',
1777 v, off, al, ah, db, b;
1778
1779 function updAtt(show) {
1780 al.forEach(function (d) {
1781 b = show && ((d.type() !== 'hostLink') || sh);
1782 d.el.style(vb, visVal(b));
1783 });
1784 ah.forEach(function (d) {
1785 b = show && sh;
1786 d.el.style(vb, visVal(b));
1787 });
1788 }
1789
1790 if (dev) {
1791 // updating a specific device that just toggled off/on-line
1792 db = dev.online || so;
1793 dev.el.style(vb, visVal(db));
1794 al = findAttachedLinks(dev.id);
1795 ah = findAttachedHosts(dev.id);
1796 updAtt(db);
1797 } else {
1798 // updating all offline devices
1799 v = visVal(so);
1800 off = findOfflineNodes();
1801 off.forEach(function (d) {
1802 d.el.style(vb, v);
1803 al = findAttachedLinks(d.id);
1804 ah = findAttachedHosts(d.id);
1805 updAtt(so);
1806 });
1807 }
1808 }
1809
Simon Hunt6ac93f32014-11-13 12:17:27 -08001810 function nodeMouseOver(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001811 hovered = d;
Thomas Vachuska9edca302014-11-22 17:06:42 -08001812 requestTrafficForMode();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001813 }
1814
1815 function nodeMouseOut(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001816 hovered = null;
Thomas Vachuska9edca302014-11-22 17:06:42 -08001817 requestTrafficForMode();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001818 }
Simon Huntbb282f52014-11-10 11:08:19 -08001819
Simon Hunteb1514d2014-11-20 09:57:29 -08001820 function addHostIcon(node, radius, iid) {
Thomas Vachuska89543292014-11-19 11:28:33 -08001821 var dim = radius * 1.5,
1822 xlate = -dim / 2;
1823
Simon Hunteb1514d2014-11-20 09:57:29 -08001824 node.append('use').attr({
1825 class: 'glyphIcon hostIcon',
1826 transform: translate(xlate,xlate),
1827 'xlink:href': iid,
1828 width: dim,
1829 height: dim
1830 });
Thomas Vachuska89543292014-11-19 11:28:33 -08001831 }
1832
Simon Hunt99c13842014-11-06 18:23:12 -08001833 function updateNodes() {
1834 node = nodeG.selectAll('.node')
1835 .data(network.nodes, function (d) { return d.id; });
1836
Simon Hunt62c47542014-11-22 22:16:32 -08001837 // operate on existing nodes...
1838 node.filter('.device').each(function (d) {
Simon Hunt62c47542014-11-22 22:16:32 -08001839 var node = d.el;
1840 node.classed('online', d.online);
1841 updateDeviceLabel(d);
1842 positionNode(d, true);
1843 });
1844
1845 node.filter('.host').each(function (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001846 updateHostLabel(d);
1847 positionNode(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -08001848 });
Simon Hunt99c13842014-11-06 18:23:12 -08001849
1850 // operate on entering nodes:
1851 var entering = node.enter()
1852 .append('g')
1853 .attr({
1854 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001855 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -08001856 transform: function (d) { return translate(d.x, d.y); },
1857 opacity: 0
1858 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08001859 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -08001860 .on('mouseover', nodeMouseOver)
1861 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -08001862 .transition()
1863 .attr('opacity', 1);
1864
1865 // augment device nodes...
1866 entering.filter('.device').each(function (d) {
1867 var node = d3.select(this),
Simon Huntc72967b2014-11-20 09:21:42 -08001868 label = trimLabel(deviceLabel(d)),
1869 noLabel = !label,
Simon Hunt99c13842014-11-06 18:23:12 -08001870 box;
1871
Simon Hunt7cd48f32014-11-09 23:42:50 -08001872 // provide ref to element from backing data....
1873 d.el = node;
1874
Simon Hunt62c47542014-11-22 22:16:32 -08001875 node.append('rect').attr({ rx: 5, ry: 5 });
1876 node.append('text').text(label).attr('dy', '1.1em');
Simon Hunt99c13842014-11-06 18:23:12 -08001877 box = adjustRectToFitText(node);
Simon Hunta3dd9572014-11-20 15:22:41 -08001878 node.select('rect').attr(box);
Simon Huntc72967b2014-11-20 09:21:42 -08001879 addDeviceIcon(node, box, noLabel, iconGlyphUrl(d));
Simon Huntc7ee0662014-11-05 16:44:37 -08001880 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08001881
Thomas Vachuska89543292014-11-19 11:28:33 -08001882
Simon Hunt56d51852014-11-09 13:03:35 -08001883 // augment host nodes...
1884 entering.filter('.host').each(function (d) {
1885 var node = d3.select(this),
Simon Hunt395a70c2014-11-22 23:17:40 -08001886 cfg = config.icons.host,
1887 r = cfg.radius[d.type] || cfg.defaultRadius,
Thomas Vachuska89543292014-11-19 11:28:33 -08001888 textDy = r + 10,
Simon Hunteb1514d2014-11-20 09:57:29 -08001889 iid = iconGlyphUrl(d);
Simon Hunt56d51852014-11-09 13:03:35 -08001890
Simon Hunt7cd48f32014-11-09 23:42:50 -08001891 // provide ref to element from backing data....
1892 d.el = node;
Simon Hunt434cf142014-11-24 11:10:28 -08001893 showHostVis(node);
Simon Hunt7cd48f32014-11-09 23:42:50 -08001894
Simon Hunt62c47542014-11-22 22:16:32 -08001895 node.append('circle').attr('r', r);
Simon Hunteb1514d2014-11-20 09:57:29 -08001896 if (iid) {
1897 addHostIcon(node, r, iid);
Simon Hunt7fa116d2014-11-17 14:16:55 -08001898 }
Simon Hunt56d51852014-11-09 13:03:35 -08001899 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001900 .text(hostLabel)
Thomas Vachuska89543292014-11-19 11:28:33 -08001901 .attr('dy', textDy)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001902 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08001903 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001904
Simon Hunt99c13842014-11-06 18:23:12 -08001905 // operate on both existing and new nodes, if necessary
Simon Huntb0ecfa52014-11-23 21:05:12 -08001906 updateDeviceColors();
Simon Huntc7ee0662014-11-05 16:44:37 -08001907
Simon Hunt99c13842014-11-06 18:23:12 -08001908 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08001909 // Note that the node is removed after 2 seconds.
1910 // Sub element animations should be shorter than 2 seconds.
1911 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08001912 .transition()
1913 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08001914 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08001915 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08001916
1917 // host node exits....
1918 exiting.filter('.host').each(function (d) {
Simon Huntca867ac2014-11-28 18:07:35 -08001919 var node = d.el;
1920 node.select('use')
1921 .style('opacity', 0.5)
1922 .transition()
1923 .duration(800)
1924 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001925
1926 node.select('text')
1927 .style('opacity', 0.5)
1928 .transition()
Simon Huntca867ac2014-11-28 18:07:35 -08001929 .duration(800)
Simon Huntea80eb42014-11-11 13:46:57 -08001930 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001931
Thomas Vachuska89543292014-11-19 11:28:33 -08001932 node.select('circle')
1933 .style('stroke-fill', '#555')
1934 .style('fill', '#888')
Simon Huntea80eb42014-11-11 13:46:57 -08001935 .style('opacity', 0.5)
1936 .transition()
1937 .duration(1500)
1938 .attr('r', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001939 });
1940
Simon Huntca867ac2014-11-28 18:07:35 -08001941 // device node exits....
1942 exiting.filter('.device').each(function (d) {
1943 var node = d.el;
1944 node.select('use')
1945 .style('opacity', 0.5)
1946 .transition()
1947 .duration(800)
1948 .style('opacity', 0);
1949
1950 node.selectAll('rect')
1951 .style('stroke-fill', '#555')
1952 .style('fill', '#888')
1953 .style('opacity', 0.5);
1954 });
Simon Hunt62c47542014-11-22 22:16:32 -08001955
1956 network.force.resume();
Simon Huntc7ee0662014-11-05 16:44:37 -08001957 }
1958
Simon Hunt95dad922014-11-24 09:43:31 -08001959 var dCol = {
1960 black: '#000',
1961 paleblue: '#acf',
1962 offwhite: '#ddd',
1963 midgrey: '#888',
1964 lightgrey: '#bbb',
1965 orange: '#f90'
1966 };
1967
Simon Huntb0ecfa52014-11-23 21:05:12 -08001968 // note: these are the device icon colors without affinity
Simon Hunt95dad922014-11-24 09:43:31 -08001969 var dColTheme = {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001970 light: {
1971 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08001972 glyph: dCol.black,
1973 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08001974 },
1975 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08001976 glyph: dCol.midgrey,
1977 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08001978 }
1979 },
Simon Hunt95dad922014-11-24 09:43:31 -08001980 // TODO: theme
Simon Huntb0ecfa52014-11-23 21:05:12 -08001981 dark: {
1982 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08001983 glyph: dCol.black,
1984 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08001985 },
1986 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08001987 glyph: dCol.midgrey,
1988 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08001989 }
1990 }
1991 };
1992
1993 function devBaseColor(d) {
1994 var t = network.view.getTheme(),
1995 o = d.online ? 'online' : 'offline';
Simon Hunt95dad922014-11-24 09:43:31 -08001996 return dColTheme[t][o];
Simon Huntb0ecfa52014-11-23 21:05:12 -08001997 }
1998
1999 function setDeviceColor(d) {
2000 var o = d.online,
2001 s = d.el.classed('selected'),
2002 c = devBaseColor(d),
2003 a = instColor(d.master, o),
2004 g, r,
2005 icon = d.el.select('g.deviceIcon');
2006
2007 if (s) {
2008 g = c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002009 r = dColTheme.sel;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002010 } else if (colorAffinity) {
2011 g = o ? a : c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002012 r = o ? dCol.offwhite : a;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002013 } else {
2014 g = c.glyph;
2015 r = c.rect;
2016 }
2017
2018 icon.select('use')
2019 .style('fill', g);
2020 icon.select('rect')
2021 .style('fill', r);
2022 }
2023
Simon Huntc72967b2014-11-20 09:21:42 -08002024 function addDeviceIcon(node, box, noLabel, iid) {
2025 var cfg = config.icons.device,
2026 dx,
2027 dy,
2028 g;
2029
2030 if (noLabel) {
2031 box = emptyBox();
2032 dx = -cfg.dim/2;
2033 dy = -cfg.dim/2;
2034 } else {
2035 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08002036 dx = box.x + cfg.xoff;
2037 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08002038 }
2039
Simon Hunteb1514d2014-11-20 09:57:29 -08002040 g = node.append('g')
2041 .attr('class', 'glyphIcon deviceIcon')
Simon Huntc72967b2014-11-20 09:21:42 -08002042 .attr('transform', translate(dx, dy));
2043
2044 g.append('rect').attr({
2045 x: 0,
2046 y: 0,
2047 rx: cfg.rx,
2048 width: cfg.dim,
2049 height: cfg.dim
2050 });
2051
2052 g.append('use').attr({
2053 'xlink:href': iid,
2054 width: cfg.dim,
2055 height: cfg.dim
2056 });
2057
Simon Huntc72967b2014-11-20 09:21:42 -08002058 }
2059
Simon Hunt7b403bc2014-11-22 19:01:00 -08002060 function find(key, array, tag) {
Simon Huntca867ac2014-11-28 18:07:35 -08002061 var _tag = tag || 'id',
Simon Hunt7b403bc2014-11-22 19:01:00 -08002062 idx, n, d;
2063 for (idx = 0, n = array.length; idx < n; idx++) {
2064 d = array[idx];
2065 if (d[_tag] === key) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002066 return idx;
2067 }
2068 }
2069 return -1;
2070 }
2071
Simon Huntca867ac2014-11-28 18:07:35 -08002072 function removeLinkElement(d) {
2073 var idx = find(d.key, network.links, 'key'),
Simon Hunt8257f4c2014-11-16 19:34:54 -08002074 removed;
2075 if (idx >=0) {
2076 // remove from links array
2077 removed = network.links.splice(idx, 1);
2078 // remove from lookup cache
2079 delete network.lookup[removed[0].key];
2080 updateLinks();
2081 network.force.resume();
2082 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002083 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002084
Simon Huntca867ac2014-11-28 18:07:35 -08002085 function removeHostElement(d, upd) {
2086 var lu = network.lookup;
Simon Hunt44031102014-11-11 13:20:36 -08002087 // first, remove associated hostLink...
Simon Huntca867ac2014-11-28 18:07:35 -08002088 removeLinkElement(d.linkData);
2089
2090 // remove hostLink bindings
2091 delete lu[d.ingress];
2092 delete lu[d.egress];
Simon Hunt44031102014-11-11 13:20:36 -08002093
2094 // remove from lookup cache
Simon Huntca867ac2014-11-28 18:07:35 -08002095 delete lu[d.id];
Simon Hunt44031102014-11-11 13:20:36 -08002096 // remove from nodes array
Simon Huntca867ac2014-11-28 18:07:35 -08002097 var idx = find(d.id, network.nodes);
2098 network.nodes.splice(idx, 1);
2099 // remove from SVG
2100 // NOTE: upd is false if we were called from removeDeviceElement()
2101 if (upd) {
2102 updateNodes();
2103 network.force.resume();
2104 }
2105 }
2106
2107
2108 function removeDeviceElement(d) {
2109 var id = d.id;
2110 // first, remove associated hosts and links..
2111 findAttachedHosts(id).forEach(removeHostElement);
2112 findAttachedLinks(id).forEach(removeLinkElement);
2113
2114 // remove from lookup cache
2115 delete network.lookup[id];
2116 // remove from nodes array
2117 var idx = find(id, network.nodes);
Simon Hunt44031102014-11-11 13:20:36 -08002118 network.nodes.splice(idx, 1);
2119 // remove from SVG
2120 updateNodes();
2121 network.force.resume();
2122 }
2123
Simon Huntca867ac2014-11-28 18:07:35 -08002124 function findAttachedHosts(devId) {
2125 var hosts = [];
2126 network.nodes.forEach(function (d) {
2127 if (d.class === 'host' && d.cp.device === devId) {
2128 hosts.push(d);
2129 }
2130 });
2131 return hosts;
2132 }
2133
2134 function findAttachedLinks(devId) {
2135 var links = [];
2136 network.links.forEach(function (d) {
2137 if (d.source.id === devId || d.target.id === devId) {
2138 links.push(d);
2139 }
2140 });
2141 return links;
2142 }
Simon Hunt44031102014-11-11 13:20:36 -08002143
Simon Huntc7ee0662014-11-05 16:44:37 -08002144 function tick() {
2145 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -08002146 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -08002147 });
2148
2149 link.attr({
2150 x1: function (d) { return d.source.x; },
2151 y1: function (d) { return d.source.y; },
2152 x2: function (d) { return d.target.x; },
2153 y2: function (d) { return d.target.y; }
2154 });
Simon Hunte2575b62014-11-18 15:25:53 -08002155
2156 linkLabel.each(function (d) {
2157 var el = d3.select(this);
Thomas Vachuska4731f122014-11-20 04:56:19 -08002158 var lnk = findLinkById(d.key);
2159
2160 if (lnk) {
2161 var parms = {
Simon Hunte2575b62014-11-18 15:25:53 -08002162 x1: lnk.source.x,
2163 y1: lnk.source.y,
2164 x2: lnk.target.x,
2165 y2: lnk.target.y
2166 };
Thomas Vachuska4731f122014-11-20 04:56:19 -08002167 el.attr('transform', transformLabel(parms));
2168 }
Simon Hunte2575b62014-11-18 15:25:53 -08002169 });
Simon Huntc7ee0662014-11-05 16:44:37 -08002170 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08002171
2172 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002173 // Web-Socket for live data
2174
2175 function webSockUrl() {
2176 return document.location.toString()
2177 .replace(/\#.*/, '')
2178 .replace('http://', 'ws://')
2179 .replace('https://', 'wss://')
Simon Hunte5ab1382014-11-25 10:28:51 -08002180 .replace('index.html', config.webSockUrl);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002181 }
2182
2183 webSock = {
2184 ws : null,
2185
2186 connect : function() {
2187 webSock.ws = new WebSocket(webSockUrl());
2188
2189 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08002190 noWebSock(false);
Thomas Vachuska47635c62014-11-22 01:21:36 -08002191 requestSummary();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002192 showInstances();
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002193 };
2194
2195 webSock.ws.onmessage = function(m) {
2196 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002197 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08002198 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002199 }
2200 };
2201
2202 webSock.ws.onclose = function(m) {
2203 webSock.ws = null;
Simon Hunt0c6d4192014-11-12 12:07:10 -08002204 noWebSock(true);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002205 };
2206 },
2207
2208 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002209 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002210 webSock._send(text);
2211 }
2212 },
2213
2214 _send : function(message) {
2215 if (webSock.ws) {
2216 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002217 } else if (config.useLiveData) {
Simon Hunt434cf142014-11-24 11:10:28 -08002218 console.warn('no web socket open', message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002219 } else {
Simon Hunt434cf142014-11-24 11:10:28 -08002220 console.log('WS Send: ', message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002221 }
2222 }
2223
2224 };
2225
Simon Hunt0c6d4192014-11-12 12:07:10 -08002226 function noWebSock(b) {
2227 mask.style('display',b ? 'block' : 'none');
2228 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002229
2230 function sendMessage(evType, payload) {
2231 var toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08002232 event: evType,
2233 sid: ++sid,
2234 payload: payload
2235 },
2236 asText = JSON.stringify(toSend);
2237 wsTraceTx(asText);
2238 webSock.send(asText);
Simon Huntc76ae892014-11-18 17:31:51 -08002239
2240 // Temporary measure for debugging UI behavior ...
2241 if (!config.useLiveData) {
2242 handleTestSend(toSend);
2243 }
Simon Huntbb282f52014-11-10 11:08:19 -08002244 }
2245
2246 function wsTraceTx(msg) {
2247 wsTrace('tx', msg);
2248 }
2249 function wsTraceRx(msg) {
2250 wsTrace('rx', msg);
2251 }
2252 function wsTrace(rxtx, msg) {
Simon Huntbb282f52014-11-10 11:08:19 -08002253 console.log('[' + rxtx + '] ' + msg);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002254 }
2255
Simon Huntc76ae892014-11-18 17:31:51 -08002256 // NOTE: Temporary hardcoded example for showing detail pane
2257 // while we fine-
2258 // Probably should not merge this change...
2259 function handleTestSend(msg) {
2260 if (msg.event === 'requestDetails') {
2261 showDetails({
2262 event: 'showDetails',
2263 sid: 1001,
2264 payload: {
2265 "id": "of:0000ffffffffff09",
2266 "type": "roadm",
2267 "propOrder": [
2268 "Name",
2269 "Vendor",
2270 "H/W Version",
2271 "S/W Version",
2272 "-",
2273 "Latitude",
2274 "Longitude",
2275 "Ports"
2276 ],
2277 "props": {
2278 "Name": null,
2279 "Vendor": "Linc",
2280 "H/W Version": "OE",
2281 "S/W Version": "?",
2282 "-": "",
2283 "Latitude": "40.8",
2284 "Longitude": "73.1",
2285 "Ports": "2"
2286 }
2287 }
2288 });
2289 }
2290 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002291
2292 // ==============================
2293 // Selection stuff
2294
2295 function selectObject(obj, el) {
2296 var n,
Simon Hunt01095ff2014-11-13 16:37:29 -08002297 srcEv = d3.event.sourceEvent,
2298 meta = srcEv.metaKey,
2299 shift = srcEv.shiftKey;
2300
Simon Huntdeab4322014-11-13 18:49:07 -08002301 if ((panZoom() && !meta) || (!panZoom() && meta)) {
Simon Hunt01095ff2014-11-13 16:37:29 -08002302 return;
2303 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002304
2305 if (el) {
2306 n = d3.select(el);
2307 } else {
2308 node.each(function(d) {
2309 if (d == obj) {
2310 n = d3.select(el = this);
2311 }
2312 });
2313 }
2314 if (!n) return;
2315
Simon Hunt01095ff2014-11-13 16:37:29 -08002316 if (shift && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002317 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002318 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002319 return;
2320 }
2321
Simon Hunt01095ff2014-11-13 16:37:29 -08002322 if (!shift) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002323 deselectAll();
2324 }
2325
Simon Huntc31d5692014-11-12 13:27:18 -08002326 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002327 selectOrder.push(obj.id);
2328
2329 n.classed('selected', true);
Simon Huntb0ecfa52014-11-23 21:05:12 -08002330 updateDeviceColors(obj);
Simon Hunt61d04042014-11-11 17:27:16 -08002331 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002332 }
2333
2334 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08002335 var obj = selections[id],
2336 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002337 if (obj) {
2338 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08002339 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08002340 idx = $.inArray(id, selectOrder);
2341 if (idx >= 0) {
2342 selectOrder.splice(idx, 1);
2343 }
Simon Huntb0ecfa52014-11-23 21:05:12 -08002344 updateDeviceColors(obj.obj);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002345 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002346 }
2347
2348 function deselectAll() {
2349 // deselect all nodes in the network...
2350 node.classed('selected', false);
2351 selections = {};
2352 selectOrder = [];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002353 updateDeviceColors();
Simon Hunt61d04042014-11-11 17:27:16 -08002354 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002355 }
2356
Simon Huntb0ecfa52014-11-23 21:05:12 -08002357 function updateDeviceColors(d) {
2358 if (d) {
2359 setDeviceColor(d);
2360 } else {
2361 node.filter('.device').each(function (d) {
2362 setDeviceColor(d);
2363 });
2364 }
Thomas Vachuska47635c62014-11-22 01:21:36 -08002365 }
2366
Simon Hunt61d04042014-11-11 17:27:16 -08002367 // update the state of the detail pane, based on current selections
2368 function updateDetailPane() {
2369 var nSel = selectOrder.length;
2370 if (!nSel) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002371 emptySelect();
Simon Hunt61d04042014-11-11 17:27:16 -08002372 } else if (nSel === 1) {
2373 singleSelect();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002374 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002375 } else {
2376 multiSelect();
2377 }
2378 }
2379
Thomas Vachuska9edca302014-11-22 17:06:42 -08002380 function emptySelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002381 haveDetails = false;
Simon Hunt06811b72014-11-25 18:54:48 -08002382 hideDetailPane();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002383 cancelTraffic();
2384 }
2385
Simon Hunt61d04042014-11-11 17:27:16 -08002386 function singleSelect() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002387 // NOTE: detail is shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08002388 requestDetails();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002389 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002390 }
2391
2392 function multiSelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002393 haveDetails = true;
Simon Huntb53e0682014-11-12 13:32:01 -08002394 populateMultiSelect();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002395 requestTrafficForMode();
Simon Huntb53e0682014-11-12 13:32:01 -08002396 }
2397
2398 function addSep(tbody) {
2399 var tr = tbody.append('tr');
2400 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
2401 }
2402
2403 function addProp(tbody, label, value) {
2404 var tr = tbody.append('tr');
2405
2406 tr.append('td')
2407 .attr('class', 'label')
2408 .text(label + ' :');
2409
2410 tr.append('td')
2411 .attr('class', 'value')
2412 .text(value);
2413 }
2414
2415 function populateMultiSelect() {
2416 detailPane.empty();
2417
Simon Hunta3dd9572014-11-20 15:22:41 -08002418 var title = detailPane.append('h3'),
2419 table = detailPane.append('table'),
2420 tbody = table.append('tbody');
Simon Huntb53e0682014-11-12 13:32:01 -08002421
Thomas Vachuska4731f122014-11-20 04:56:19 -08002422 title.text('Selected Nodes');
Simon Huntb53e0682014-11-12 13:32:01 -08002423
2424 selectOrder.forEach(function (d, i) {
2425 addProp(tbody, i+1, d);
2426 });
Simon Huntd72bc702014-11-13 18:38:04 -08002427
2428 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08002429 }
2430
Thomas Vachuska47635c62014-11-22 01:21:36 -08002431 // TODO: refactor to consolidate with populateDetails
2432 function populateSummary(data) {
2433 summaryPane.empty();
2434
2435 var svg = summaryPane.append('svg'),
2436 iid = iconGlyphUrl(data);
2437
2438 var title = summaryPane.append('h2'),
2439 table = summaryPane.append('table'),
2440 tbody = table.append('tbody');
2441
2442 appendGlyph(svg, 0, 0, 40, iid);
2443
2444 svg.append('use')
2445 .attr({
2446 class: 'birdBadge',
2447 transform: translate(8,12),
2448 'xlink:href': '#bird',
2449 width: 24,
2450 height: 24,
2451 fill: '#fff'
2452 });
2453
2454 title.text('ONOS Summary');
2455
2456 data.propOrder.forEach(function(p) {
2457 if (p === '-') {
2458 addSep(tbody);
2459 } else {
2460 addProp(tbody, p, data.props[p]);
2461 }
2462 });
2463 }
2464
Simon Hunt61d04042014-11-11 17:27:16 -08002465 function populateDetails(data) {
2466 detailPane.empty();
2467
Simon Hunta6a9fe72014-11-20 11:17:12 -08002468 var svg = detailPane.append('svg'),
2469 iid = iconGlyphUrl(data);
2470
Simon Hunta3dd9572014-11-20 15:22:41 -08002471 var title = detailPane.append('h2'),
2472 table = detailPane.append('table'),
2473 tbody = table.append('tbody');
Simon Hunt61d04042014-11-11 17:27:16 -08002474
Simon Hunta6a9fe72014-11-20 11:17:12 -08002475 appendGlyph(svg, 0, 0, 40, iid);
2476 title.text(data.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002477
2478 data.propOrder.forEach(function(p) {
2479 if (p === '-') {
2480 addSep(tbody);
2481 } else {
2482 addProp(tbody, p, data.props[p]);
2483 }
2484 });
Simon Huntd72bc702014-11-13 18:38:04 -08002485
Thomas Vachuska4731f122014-11-20 04:56:19 -08002486 addSingleSelectActions(data);
Simon Hunt61d04042014-11-11 17:27:16 -08002487 }
2488
Thomas Vachuska4731f122014-11-20 04:56:19 -08002489 function addSingleSelectActions(data) {
Simon Huntd72bc702014-11-13 18:38:04 -08002490 detailPane.append('hr');
2491 // always want to allow 'show traffic'
Thomas Vachuska4731f122014-11-20 04:56:19 -08002492 addAction(detailPane, 'Show Related Traffic', showTrafficAction);
2493
2494 if (data.type === 'switch') {
2495 addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
2496 }
Simon Huntd72bc702014-11-13 18:38:04 -08002497 }
2498
2499 function addMultiSelectActions() {
2500 detailPane.append('hr');
2501 // always want to allow 'show traffic'
Thomas Vachuska4731f122014-11-20 04:56:19 -08002502 addAction(detailPane, 'Show Related Traffic', showTrafficAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002503 // if exactly two hosts are selected, also want 'add host intent'
2504 if (nSel() === 2 && allSelectionsClass('host')) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002505 addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2506 } else if (nSel() >= 2 && allSelectionsClass('host')) {
2507 addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002508 }
2509 }
2510
Simon Hunta5e89142014-11-14 07:00:33 -08002511 function addAction(panel, text, cb) {
2512 panel.append('div')
Simon Huntd72bc702014-11-13 18:38:04 -08002513 .classed('actionBtn', true)
2514 .text(text)
2515 .on('click', cb);
2516 }
2517
2518
Paul Greysonfcba0e82014-11-13 10:21:16 -08002519 function zoomPan(scale, translate) {
2520 zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
2521 // keep the map lines constant width while zooming
Thomas Vachuska89543292014-11-19 11:28:33 -08002522 bgImg.style("stroke-width", 2.0 / scale + "px");
Paul Greysonfcba0e82014-11-13 10:21:16 -08002523 }
2524
2525 function resetZoomPan() {
2526 zoomPan(1, [0,0]);
2527 zoom.scale(1).translate([0,0]);
2528 }
2529
2530 function setupZoomPan() {
2531 function zoomed() {
Simon Huntdeab4322014-11-13 18:49:07 -08002532 if (!panZoom() ^ !d3.event.sourceEvent.metaKey) {
Paul Greysonfcba0e82014-11-13 10:21:16 -08002533 zoomPan(d3.event.scale, d3.event.translate);
2534 }
2535 }
2536
2537 zoom = d3.behavior.zoom()
2538 .translate([0, 0])
2539 .scale(1)
2540 .scaleExtent([1, 8])
2541 .on("zoom", zoomed);
2542
2543 svg.call(zoom);
2544 }
2545
Simon Hunt61d04042014-11-11 17:27:16 -08002546 // ==============================
2547 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08002548
2549 function prepareScenario(view, ctx, dbg) {
2550 var sc = scenario,
2551 urlSc = sc.evDir + ctx + sc.evScenario;
2552
2553 if (!ctx) {
2554 view.alert("No scenario specified (null ctx)");
2555 return;
2556 }
2557
2558 sc.view = view;
2559 sc.ctx = ctx;
2560 sc.debug = dbg;
2561 sc.evNumber = 0;
2562
2563 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002564 var p = data && data.params || {},
2565 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08002566 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08002567
Simon Hunt56d51852014-11-09 13:03:35 -08002568 if (err) {
2569 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
2570 } else {
2571 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08002572 if (desc) {
2573 intro += '\n\n ' + desc.join('\n ');
2574 }
2575 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08002576 }
2577 });
2578
2579 }
2580
Simon Hunt01095ff2014-11-13 16:37:29 -08002581 // ==============================
2582 // Toggle Buttons in masthead
Simon Hunt0c6d4192014-11-12 12:07:10 -08002583
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002584 // TODO: toggle button (and other widgets in the masthead) should be provided
2585 // by the framework; not generated by the view.
2586
Thomas Vachuska47635c62014-11-22 01:21:36 -08002587 //var showInstances;
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002588
Simon Hunt87514342014-11-24 16:41:27 -08002589/*
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002590 function addButtonBar(view) {
2591 var bb = d3.select('#mast')
2592 .append('span').classed('right', true).attr('id', 'bb');
2593
Simon Hunta5e89142014-11-14 07:00:33 -08002594 function mkTogBtn(text, cb) {
2595 return bb.append('span')
2596 .classed('btn', true)
2597 .text(text)
2598 .on('click', cb);
2599 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002600
Thomas Vachuska47635c62014-11-22 01:21:36 -08002601 //showInstances = mkTogBtn('Show Instances', toggleInst);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002602 }
Simon Hunt87514342014-11-24 16:41:27 -08002603*/
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002604
Simon Huntdeab4322014-11-13 18:49:07 -08002605 function panZoom() {
Simon Hunte5b71752014-11-18 20:06:07 -08002606 return false;
Simon Hunta5e89142014-11-14 07:00:33 -08002607 }
2608
Simon Hunt7fa116d2014-11-17 14:16:55 -08002609 function loadGlyphs(svg) {
2610 var defs = svg.append('defs');
2611 gly.defBird(defs);
Simon Huntc72967b2014-11-20 09:21:42 -08002612 gly.defGlyphs(defs);
Simon Huntb82f6902014-11-22 11:53:15 -08002613 gly.defBadges(defs);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002614 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002615
Simon Hunt395a70c2014-11-22 23:17:40 -08002616 function sendUpdateMeta(d, store) {
2617 var metaUi = {},
2618 ll;
2619
2620 if (store) {
2621 ll = geoMapProjection.invert([d.x, d.y]);
Simon Hunt62c47542014-11-22 22:16:32 -08002622 metaUi = {
2623 x: d.x,
2624 y: d.y,
2625 lng: ll[0],
2626 lat: ll[1]
2627 };
Simon Hunt395a70c2014-11-22 23:17:40 -08002628 }
Simon Hunt62c47542014-11-22 22:16:32 -08002629 d.metaUi = metaUi;
2630 sendMessage('updateMeta', {
2631 id: d.id,
2632 'class': d.class,
2633 'memento': metaUi
2634 });
2635 }
2636
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002637 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08002638 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08002639
Simon Huntf67722a2014-11-10 09:32:06 -08002640 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08002641 var w = view.width(),
2642 h = view.height(),
Simon Huntc7ee0662014-11-05 16:44:37 -08002643 fcfg = config.force,
2644 fpad = fcfg.pad,
2645 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08002646
Simon Hunt142d0032014-11-04 20:13:09 -08002647 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002648 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
2649 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002650 setSize(svg, view);
2651
Simon Hunt6e18fe32014-11-29 13:35:41 -08002652 // load glyphs and filters...
Simon Hunt7fa116d2014-11-17 14:16:55 -08002653 loadGlyphs(svg);
Simon Hunt6e18fe32014-11-29 13:35:41 -08002654 d3u.appendGlow(svg);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002655
Paul Greysonfcba0e82014-11-13 10:21:16 -08002656 zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002657 setupZoomPan();
2658
Simon Huntc7ee0662014-11-05 16:44:37 -08002659 // group for the topology
Paul Greysonfcba0e82014-11-13 10:21:16 -08002660 topoG = zoomPanContainer.append('g')
Simon Huntd3b7d512014-11-12 15:48:41 -08002661 .attr('id', 'topo-G')
Simon Huntc7ee0662014-11-05 16:44:37 -08002662 .attr('transform', fcfg.translate());
2663
Simon Hunte2575b62014-11-18 15:25:53 -08002664 // subgroups for links, link labels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002665 linkG = topoG.append('g').attr('id', 'links');
Simon Hunte2575b62014-11-18 15:25:53 -08002666 linkLabelG = topoG.append('g').attr('id', 'linkLabels');
Simon Huntc7ee0662014-11-05 16:44:37 -08002667 nodeG = topoG.append('g').attr('id', 'nodes');
2668
Simon Hunte2575b62014-11-18 15:25:53 -08002669 // selection of links, linkLabels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002670 link = linkG.selectAll('.link');
Simon Hunte2575b62014-11-18 15:25:53 -08002671 linkLabel = linkLabelG.selectAll('.linkLabel');
Simon Huntc7ee0662014-11-05 16:44:37 -08002672 node = nodeG.selectAll('.node');
2673
Simon Hunt7cd48f32014-11-09 23:42:50 -08002674 function chrg(d) {
2675 return fcfg.charge[d.class] || -12000;
2676 }
Simon Hunt99c13842014-11-06 18:23:12 -08002677 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002678 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08002679 }
2680 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002681 // 0.0 - 1.0
2682 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08002683 }
2684
Simon Hunt1a9eff92014-11-07 11:06:34 -08002685 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002686 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002687 }
2688
2689 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08002690 // once we've finished moving, pin the node in position
2691 d.fixed = true;
2692 d3.select(self).classed('fixed', true);
2693 if (config.useLiveData) {
Simon Hunt395a70c2014-11-22 23:17:40 -08002694 sendUpdateMeta(d, true);
Simon Hunta255a2c2014-11-13 22:29:35 -08002695 } else {
2696 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08002697 }
2698 }
2699
Simon Hunt6e18fe32014-11-29 13:35:41 -08002700 // predicate that indicates when dragging is active
2701 function dragEnabled() {
2702 // meta key pressed means we are zooming/panning (so disable drag)
2703 return dragAllowed && !d3.event.sourceEvent.metaKey;
2704 // dragAllowed will be set false when we are in oblique view
2705 // or when we 'lock' node positions
2706 }
2707
Simon Huntc7ee0662014-11-05 16:44:37 -08002708 // set up the force layout
2709 network.force = d3.layout.force()
2710 .size(forceDim)
2711 .nodes(network.nodes)
2712 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002713 .gravity(0.4)
2714 .friction(0.7)
2715 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08002716 .linkDistance(ldist)
2717 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08002718 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08002719
Simon Hunt01095ff2014-11-13 16:37:29 -08002720 network.drag = d3u.createDragBehavior(network.force,
Simon Hunt6e18fe32014-11-29 13:35:41 -08002721 selectCb, atDragEnd, dragEnabled);
2722
Simon Hunt0c6d4192014-11-12 12:07:10 -08002723
2724 // create mask layer for when we lose connection to server.
Simon Hunta5e89142014-11-14 07:00:33 -08002725 // TODO: this should be part of the framework
Simon Hunt6e18fe32014-11-29 13:35:41 -08002726
2727 function para(sel, text) {
2728 sel.append('p').text(text);
2729 }
2730
Simon Hunt0c6d4192014-11-12 12:07:10 -08002731 mask = view.$div.append('div').attr('id','topo-mask');
2732 para(mask, 'Oops!');
2733 para(mask, 'Web-socket connection to server closed...');
2734 para(mask, 'Try refreshing the page.');
Simon Hunt12ce12e2014-11-15 21:13:19 -08002735
2736 mask.append('svg')
2737 .attr({
2738 id: 'mask-bird',
2739 width: w,
2740 height: h
2741 })
2742 .append('g')
2743 .attr('transform', birdTranslate(w, h))
2744 .style('opacity', 0.3)
2745 .append('use')
2746 .attr({
2747 'xlink:href': '#bird',
2748 width: config.birdDim,
2749 height: config.birdDim,
2750 fill: '#111'
Thomas Vachuska89543292014-11-19 11:28:33 -08002751 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002752 }
Simon Hunt195cb382014-11-03 17:50:51 -08002753
Simon Hunt01095ff2014-11-13 16:37:29 -08002754
Simon Hunt56d51852014-11-09 13:03:35 -08002755 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08002756 // resize, in case the window was resized while we were not loaded
2757 resize(view, ctx, flags);
2758
Simon Hunt99c13842014-11-06 18:23:12 -08002759 // cache the view token, so network topo functions can access it
2760 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08002761 config.useLiveData = !flags.local;
2762
2763 if (!config.useLiveData) {
2764 prepareScenario(view, ctx, flags.debug);
2765 }
Simon Hunt99c13842014-11-06 18:23:12 -08002766
2767 // set our radio buttons and key bindings
Simon Hunt9462e8c2014-11-14 17:28:09 -08002768 layerBtnSet = view.setRadio(layerButtons);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002769 view.setKeys(keyDispatch);
Simon Hunt87514342014-11-24 16:41:27 -08002770 view.setGestures(gestures);
Simon Hunt195cb382014-11-03 17:50:51 -08002771
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002772 // patch in our "button bar" for now
2773 // TODO: implement a more official frameworky way of doing this..
Simon Hunt87514342014-11-24 16:41:27 -08002774 //addButtonBar(view);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002775
Simon Huntd3b7d512014-11-12 15:48:41 -08002776 // Load map data asynchronously; complete startup after that..
2777 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08002778 }
2779
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002780 function startAntTimer() {
Thomas Vachuska4731f122014-11-20 04:56:19 -08002781 if (!antTimer) {
2782 var pulses = [5, 3, 1.2, 3],
2783 pulse = 0;
2784 antTimer = setInterval(function () {
2785 pulse = pulse + 1;
2786 pulse = pulse === pulses.length ? 0 : pulse;
2787 d3.selectAll('.animated').style('stroke-width', pulses[pulse]);
2788 }, 200);
2789 }
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002790 }
2791
2792 function stopAntTimer() {
Simon Hunta255a2c2014-11-13 22:29:35 -08002793 if (antTimer) {
2794 clearInterval(antTimer);
2795 antTimer = null;
2796 }
Simon Huntd3b7d512014-11-12 15:48:41 -08002797 }
2798
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002799 function unload(view, ctx, flags) {
2800 stopAntTimer();
2801 }
2802
Simon Huntd3b7d512014-11-12 15:48:41 -08002803 // TODO: move these to config/state portion of script
Simon Hunta6a9fe72014-11-20 11:17:12 -08002804 var geoJsonUrl = 'json/map/continental_us.json',
Simon Huntd3b7d512014-11-12 15:48:41 -08002805 geoJson;
2806
2807 function loadGeoJsonData() {
2808 d3.json(geoJsonUrl, function (err, data) {
2809 if (err) {
2810 // fall back to USA map background
2811 loadStaticMap();
2812 } else {
2813 geoJson = data;
2814 loadGeoMap();
2815 }
2816
2817 // finally, connect to the server...
2818 if (config.useLiveData) {
2819 webSock.connect();
2820 }
2821 });
2822 }
2823
2824 function showBg() {
Simon Hunt434cf142014-11-24 11:10:28 -08002825 return visVal(config.options.showBackground);
Simon Huntd3b7d512014-11-12 15:48:41 -08002826 }
2827
2828 function loadStaticMap() {
2829 fnTrace('loadStaticMap', config.backgroundUrl);
2830 var w = network.view.width(),
2831 h = network.view.height();
2832
2833 // load the background image
2834 bgImg = svg.insert('svg:image', '#topo-G')
2835 .attr({
2836 id: 'topo-bg',
2837 width: w,
2838 height: h,
2839 'xlink:href': config.backgroundUrl
2840 })
2841 .style({
2842 visibility: showBg()
2843 });
2844 }
2845
2846 function loadGeoMap() {
2847 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08002848
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002849 // extracts the topojson data into geocoordinate-based geometry
2850 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08002851
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002852 // see: http://bl.ocks.org/mbostock/4707858
2853 geoMapProjection = d3.geo.mercator();
2854 var path = d3.geo.path().projection(geoMapProjection);
Simon Huntd3b7d512014-11-12 15:48:41 -08002855
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002856 geoMapProjection
2857 .scale(1)
2858 .translate([0, 0]);
Simon Huntd3b7d512014-11-12 15:48:41 -08002859
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002860 // [[x1,y1],[x2,y2]]
2861 var b = path.bounds(topoData);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002862 // size map to 95% of minimum dimension to fill space
2863 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 -08002864 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 -08002865
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002866 geoMapProjection
2867 .scale(s)
2868 .translate(t);
2869
Paul Greysonfcba0e82014-11-13 10:21:16 -08002870 bgImg = zoomPanContainer.insert("g", '#topo-G');
Thomas Vachuska89543292014-11-19 11:28:33 -08002871 bgImg.attr('id', 'map').selectAll('path')
2872 .data(topoData.features)
2873 .enter()
2874 .append('path')
2875 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08002876 }
2877
Simon Huntf67722a2014-11-10 09:32:06 -08002878 function resize(view, ctx, flags) {
Simon Hunt12ce12e2014-11-15 21:13:19 -08002879 var w = view.width(),
2880 h = view.height();
2881
Simon Hunt934c3ce2014-11-05 11:45:07 -08002882 setSize(svg, view);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002883
2884 d3.select('#mask-bird').attr({ width: w, height: h})
2885 .select('g').attr('transform', birdTranslate(w, h));
Simon Hunt142d0032014-11-04 20:13:09 -08002886 }
2887
Simon Hunt8f40cce2014-11-23 15:57:30 -08002888 function theme(view, ctx, flags) {
2889 updateInstances();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002890 updateDeviceColors();
Simon Hunt8f40cce2014-11-23 15:57:30 -08002891 }
2892
Simon Hunt12ce12e2014-11-15 21:13:19 -08002893 function birdTranslate(w, h) {
2894 var bdim = config.birdDim;
2895 return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
2896 }
Simon Hunt142d0032014-11-04 20:13:09 -08002897
Simon Hunt06811b72014-11-25 18:54:48 -08002898 function isF(f) { return $.isFunction(f) ? f : null; }
2899 function noop() {}
2900
2901 function augmentDetailPane() {
2902 var dp = detailPane;
2903 dp.ypos = { up: 64, down: 320, current: 320};
2904
2905 dp._move = function (y, cb) {
2906 var endCb = isF(cb) || noop,
2907 yp = dp.ypos;
2908 if (yp.current !== y) {
2909 yp.current = y;
2910 dp.el.transition().duration(300)
2911 .each('end', endCb)
2912 .style('top', yp.current + 'px');
2913 } else {
2914 endCb();
2915 }
2916 };
2917
2918 dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
2919 dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
2920 }
2921
Simon Hunt142d0032014-11-04 20:13:09 -08002922 // ==============================
2923 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08002924
Simon Hunt25248912014-11-04 11:25:48 -08002925 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08002926 preload: preload,
2927 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08002928 unload: unload,
Simon Hunt8f40cce2014-11-23 15:57:30 -08002929 resize: resize,
2930 theme: theme
Simon Hunt195cb382014-11-03 17:50:51 -08002931 });
2932
Thomas Vachuska47635c62014-11-22 01:21:36 -08002933 summaryPane = onos.ui.addFloatingPanel('topo-summary');
Simon Hunt61d04042014-11-11 17:27:16 -08002934 detailPane = onos.ui.addFloatingPanel('topo-detail');
Simon Hunt06811b72014-11-25 18:54:48 -08002935 augmentDetailPane();
Simon Hunta5e89142014-11-14 07:00:33 -08002936 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
Simon Huntb82f6902014-11-22 11:53:15 -08002937 oiBox.width(20);
Simon Hunt61d04042014-11-11 17:27:16 -08002938
Simon Hunt195cb382014-11-03 17:50:51 -08002939}(ONOS));