blob: 17e319bcc4a25d9af31d9ebe3c3ba07d1437f263 [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)'],
Simon Hunt3c5ca542014-11-29 14:11:43 -0800153 R: [resetPanZoom, 'Reset pan / zoom'],
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,
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800200 guiSuccessor = null,
Simon Hunta5e89142014-11-14 07:00:33 -0800201 onosInstances = {},
202 onosOrder = [],
203 oiBox,
Simon Hunt9462e8c2014-11-14 17:28:09 -0800204 oiShowMaster = false,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800205 portLabelsOn = false,
Simon Huntb0ecfa52014-11-23 21:05:12 -0800206 cat7 = d3u.cat7(),
Simon Hunt434cf142014-11-24 11:10:28 -0800207 colorAffinity = false,
Simon Hunt27d322d2014-11-28 10:45:43 -0800208 showHosts = false,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800209 showOffline = true,
Simon Hunt27d322d2014-11-28 10:45:43 -0800210 useDetails = true,
Simon Hunt6e18fe32014-11-29 13:35:41 -0800211 haveDetails = false,
212 dragAllowed = true;
Simon Hunt195cb382014-11-03 17:50:51 -0800213
Simon Hunt434cf142014-11-24 11:10:28 -0800214 // constants
Thomas Vachuska9edca302014-11-22 17:06:42 -0800215 var hoverModeAll = 1,
216 hoverModeFlows = 2,
217 hoverModeIntents = 3,
218 hoverMode = hoverModeFlows;
219
Simon Hunt934c3ce2014-11-05 11:45:07 -0800220 // D3 selections
221 var svg,
Simon Hunt3c5ca542014-11-29 14:11:43 -0800222 panZoomContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800223 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800224 topoG,
225 nodeG,
226 linkG,
Simon Hunte2575b62014-11-18 15:25:53 -0800227 linkLabelG,
Simon Huntc7ee0662014-11-05 16:44:37 -0800228 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800229 link,
Simon Hunte2575b62014-11-18 15:25:53 -0800230 linkLabel,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800231 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800232
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800233 // the projection for the map background
234 var geoMapProjection;
235
Paul Greysonfcba0e82014-11-13 10:21:16 -0800236 // the zoom function
237 var zoom;
238
Simon Hunt142d0032014-11-04 20:13:09 -0800239 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800240 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800241
Simon Hunt99c13842014-11-06 18:23:12 -0800242 function note(label, msg) {
243 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800244 }
245
Simon Hunt99c13842014-11-06 18:23:12 -0800246 function debug(what) {
247 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800248 }
249
Simon Huntfc274c92014-11-11 11:05:46 -0800250 function fnTrace(msg, id) {
251 if (config.fnTrace) {
252 console.log('FN: ' + msg + ' [' + id + ']');
253 }
254 }
Simon Hunt99c13842014-11-06 18:23:12 -0800255
Simon Hunta5e89142014-11-14 07:00:33 -0800256 function evTrace(data) {
257 fnTrace(data.event, data.payload.id);
258 }
259
Simon Hunt934c3ce2014-11-05 11:45:07 -0800260 // ==============================
261 // Key Callbacks
262
Simon Hunt27d322d2014-11-28 10:45:43 -0800263 function flash(txt) {
264 network.view.flash(txt);
265 }
266
Simon Hunt99c13842014-11-06 18:23:12 -0800267 function testMe(view) {
Simon Hunt8f40cce2014-11-23 15:57:30 -0800268 //view.alert('Theme is ' + view.getTheme());
Simon Hunta3dd9572014-11-20 15:22:41 -0800269 //view.flash('This is some text');
Simon Hunt8f40cce2014-11-23 15:57:30 -0800270 cat7.testCard(svg);
Simon Hunt99c13842014-11-06 18:23:12 -0800271 }
272
Simon Hunt56d51852014-11-09 13:03:35 -0800273 function injectTestEvent(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800274 if (config.useLiveData) { return; }
275
Simon Hunt56d51852014-11-09 13:03:35 -0800276 var sc = scenario,
277 evn = ++sc.evNumber,
278 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
279 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800280 uiUrl = pfx + sc.evUi,
281 stack = [
282 { url: onosUrl, cb: handleServerEvent },
283 { url: uiUrl, cb: handleUiEvent }
284 ];
285 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800286 }
287
Simon Hunt7cd48f32014-11-09 23:42:50 -0800288 function recurseFetchEvent(stack, evn) {
289 var v = scenario.view,
290 frame;
291 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800292 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800293 return;
294 }
295 frame = stack.shift();
296
297 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800298 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800299 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800300 // if we didn't find the data, try the next stack frame
301 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800302 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800303 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800304 }
Simon Hunt99c13842014-11-06 18:23:12 -0800305 } else {
Simon Hunt1712ed82014-11-17 12:56:00 -0800306 wsTrace('test', JSON.stringify(data));
Simon Hunt7cd48f32014-11-09 23:42:50 -0800307 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800308 }
309 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800310
Simon Hunt56d51852014-11-09 13:03:35 -0800311 }
Simon Hunt50128c02014-11-08 13:36:15 -0800312
Simon Hunt56d51852014-11-09 13:03:35 -0800313 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800314 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
315 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800316 }
317
318 function injectStartupEvents(view) {
319 var last = scenario.params.lastAuto || 0;
Simon Hunt434cf142014-11-24 11:10:28 -0800320 if (config.useLiveData) { return; }
Simon Hunt56d51852014-11-09 13:03:35 -0800321
322 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800323 injectTestEvent(view);
324 }
325 }
326
Simon Hunt934c3ce2014-11-05 11:45:07 -0800327 function toggleBg() {
328 var vis = bgImg.style('visibility');
Simon Hunt434cf142014-11-24 11:10:28 -0800329 bgImg.style('visibility', visVal(vis === 'hidden'));
330 }
331
332 function toggleHosts() {
333 showHosts = !showHosts;
334 updateHostVisibility();
Simon Hunt27d322d2014-11-28 10:45:43 -0800335 flash('Hosts ' + visVal(showHosts));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800336 }
337
Simon Hunt6d9bd032014-11-28 22:16:40 -0800338 function toggleOffline() {
339 showOffline = !showOffline;
340 updateOfflineVisibility();
341 flash('Offline devices ' + visVal(showOffline));
342 }
343
Simon Hunt99c13842014-11-06 18:23:12 -0800344 function cycleLabels() {
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800345 deviceLabelIndex = (deviceLabelIndex === 2)
Simon Huntbb282f52014-11-10 11:08:19 -0800346 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800347
Simon Hunt99c13842014-11-06 18:23:12 -0800348 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800349 if (d.class === 'device') {
350 updateDeviceLabel(d);
351 }
Simon Hunt99c13842014-11-06 18:23:12 -0800352 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800353 }
354
355 function togglePorts(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800356 //view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800357 }
358
Simon Hunt6ac93f32014-11-13 12:17:27 -0800359 function unpin() {
360 if (hovered) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800361 sendUpdateMeta(hovered);
Simon Hunt6ac93f32014-11-13 12:17:27 -0800362 hovered.fixed = false;
363 hovered.el.classed('fixed', false);
364 network.force.resume();
365 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800366 }
367
Simon Hunt9462e8c2014-11-14 17:28:09 -0800368 function handleEscape(view) {
369 if (oiShowMaster) {
370 cancelAffinity();
Simon Hunt27d322d2014-11-28 10:45:43 -0800371 } else if (haveDetails) {
Simon Hunt9462e8c2014-11-14 17:28:09 -0800372 deselectAll();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800373 } else if (oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800374 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800375 } else if (summaryPane.isVisible()) {
376 cancelSummary();
Thomas Vachuska5bde31f2014-11-25 15:29:18 -0800377 stopAntTimer();
378 } else {
379 hoverMode = hoverModeFlows;
Simon Hunt9462e8c2014-11-14 17:28:09 -0800380 }
381 }
382
Simon Hunt934c3ce2014-11-05 11:45:07 -0800383 // ==============================
384 // Radio Button Callbacks
385
Simon Hunta5e89142014-11-14 07:00:33 -0800386 var layerLookup = {
387 host: {
Simon Hunt209155e2014-11-21 12:16:09 -0800388 endstation: 'pkt', // default, if host event does not define type
Thomas Vachuska89543292014-11-19 11:28:33 -0800389 router: 'pkt',
Simon Hunta5e89142014-11-14 07:00:33 -0800390 bgpSpeaker: 'pkt'
391 },
392 device: {
393 switch: 'pkt',
394 roadm: 'opt'
395 },
396 link: {
397 hostLink: 'pkt',
398 direct: 'pkt',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800399 indirect: '',
400 tunnel: '',
Simon Hunta5e89142014-11-14 07:00:33 -0800401 optical: 'opt'
402 }
403 };
404
405 function inLayer(d, layer) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800406 var type = d.class === 'link' ? d.type() : d.type,
407 look = layerLookup[d.class],
408 lyr = look && look[type];
Simon Hunta5e89142014-11-14 07:00:33 -0800409 return lyr === layer;
410 }
411
412 function unsuppressLayer(which) {
413 node.each(function (d) {
414 var node = d.el;
415 if (inLayer(d, which)) {
416 node.classed('suppressed', false);
417 }
418 });
419
420 link.each(function (d) {
421 var link = d.el;
422 if (inLayer(d, which)) {
423 link.classed('suppressed', false);
424 }
425 });
426 }
427
Simon Hunt9462e8c2014-11-14 17:28:09 -0800428 function suppressLayers(b) {
429 node.classed('suppressed', b);
430 link.classed('suppressed', b);
Simon Hunt142d0032014-11-04 20:13:09 -0800431// d3.selectAll('svg .port').classed('inactive', false);
432// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt195cb382014-11-03 17:50:51 -0800433 }
434
Simon Hunt9462e8c2014-11-14 17:28:09 -0800435 function showAllLayers() {
436 suppressLayers(false);
437 }
438
Simon Hunt195cb382014-11-03 17:50:51 -0800439 function showPacketLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800440 node.classed('suppressed', true);
441 link.classed('suppressed', true);
442 unsuppressLayer('pkt');
Simon Hunt195cb382014-11-03 17:50:51 -0800443 }
444
445 function showOpticalLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800446 node.classed('suppressed', true);
447 link.classed('suppressed', true);
448 unsuppressLayer('opt');
Simon Hunt195cb382014-11-03 17:50:51 -0800449 }
450
Simon Hunt9462e8c2014-11-14 17:28:09 -0800451 function restoreLayerState() {
452 layerBtnDispatch[layerBtnSet.selected()]();
453 }
454
Simon Hunt142d0032014-11-04 20:13:09 -0800455 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800456 // Private functions
457
Simon Hunt99c13842014-11-06 18:23:12 -0800458 function safeId(s) {
459 return s.replace(/[^a-z0-9]/gi, '-');
460 }
461
Simon Huntc7ee0662014-11-05 16:44:37 -0800462 // set the size of the given element to that of the view (reduced if padded)
463 function setSize(el, view, pad) {
464 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800465 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800466 width: view.width() - padding,
467 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800468 });
469 }
470
Simon Hunt8257f4c2014-11-16 19:34:54 -0800471 function makeNodeKey(d, what) {
472 var port = what + 'Port';
473 return d[what] + '/' + d[port];
474 }
475
476 function makeLinkKey(d, flipped) {
477 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
478 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
479 return one + '-' + two;
480 }
481
Simon Hunt269670f2014-11-17 16:17:43 -0800482 function findLinkById(id) {
483 // check to see if this is a reverse lookup, else default to given id
484 var key = network.revLinkToKey[id] || id;
485 return key && network.lookup[key];
486 }
487
Simon Hunt8257f4c2014-11-16 19:34:54 -0800488 function findLink(linkData, op) {
489 var key = makeLinkKey(linkData),
490 keyrev = makeLinkKey(linkData, 1),
491 link = network.lookup[key],
492 linkRev = network.lookup[keyrev],
493 result = {},
494 ldata = link || linkRev,
495 rawLink;
496
497 if (op === 'add') {
498 if (link) {
499 // trying to add a link that we already know about
500 result.ldata = link;
501 result.badLogic = 'addLink: link already added';
502
503 } else if (linkRev) {
504 // we found the reverse of the link to be added
505 result.ldata = linkRev;
506 if (linkRev.fromTarget) {
507 result.badLogic = 'addLink: link already added';
508 }
509 }
510 } else if (op === 'update') {
511 if (!ldata) {
512 result.badLogic = 'updateLink: link not found';
513 } else {
514 rawLink = link ? ldata.fromSource : ldata.fromTarget;
515 result.updateWith = function (data) {
516 $.extend(rawLink, data);
517 restyleLinkElement(ldata);
518 }
519 }
520 } else if (op === 'remove') {
521 if (!ldata) {
522 result.badLogic = 'removeLink: link not found';
523 } else {
524 rawLink = link ? ldata.fromSource : ldata.fromTarget;
525
526 if (!rawLink) {
527 result.badLogic = 'removeLink: link not found';
528
529 } else {
530 result.removeRawLink = function () {
531 if (link) {
532 // remove fromSource
533 ldata.fromSource = null;
534 if (ldata.fromTarget) {
535 // promote target into source position
536 ldata.fromSource = ldata.fromTarget;
537 ldata.fromTarget = null;
538 ldata.key = keyrev;
539 delete network.lookup[key];
540 network.lookup[keyrev] = ldata;
Simon Hunt269670f2014-11-17 16:17:43 -0800541 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800542 }
543 } else {
544 // remove fromTarget
545 ldata.fromTarget = null;
Simon Hunt269670f2014-11-17 16:17:43 -0800546 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800547 }
548 if (ldata.fromSource) {
549 restyleLinkElement(ldata);
550 } else {
551 removeLinkElement(ldata);
552 }
553 }
554 }
555 }
556 }
557 return result;
558 }
559
560 function addLinkUpdate(ldata, link) {
561 // add link event, but we already have the reverse link installed
562 ldata.fromTarget = link;
Simon Hunt269670f2014-11-17 16:17:43 -0800563 network.revLinkToKey[link.id] = ldata.key;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800564 restyleLinkElement(ldata);
565 }
566
567 var allLinkTypes = 'direct indirect optical tunnel',
568 defaultLinkType = 'direct';
569
570 function restyleLinkElement(ldata) {
571 // this fn's job is to look at raw links and decide what svg classes
572 // need to be applied to the line element in the DOM
573 var el = ldata.el,
574 type = ldata.type(),
575 lw = ldata.linkWidth(),
576 online = ldata.online();
577
578 el.classed('link', true);
579 el.classed('inactive', !online);
580 el.classed(allLinkTypes, false);
581 if (type) {
582 el.classed(type, true);
583 }
584 el.transition()
585 .duration(1000)
Thomas Vachuska89543292014-11-19 11:28:33 -0800586 .attr('stroke-width', linkScale(lw))
587 .attr('stroke', config.topo.linkBaseColor);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800588 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800589
Simon Hunt99c13842014-11-06 18:23:12 -0800590 // ==============================
591 // Event handlers for server-pushed events
592
Simon Huntbb282f52014-11-10 11:08:19 -0800593 function logicError(msg) {
594 // TODO, report logic error to server, via websock, so it can be logged
Simon Huntfc274c92014-11-11 11:05:46 -0800595 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800596 }
597
Simon Hunt99c13842014-11-06 18:23:12 -0800598 var eventDispatch = {
Simon Hunta5e89142014-11-14 07:00:33 -0800599 addInstance: addInstance,
Simon Hunt99c13842014-11-06 18:23:12 -0800600 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800601 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800602 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800603
Simon Huntfcfb46c2014-11-19 12:53:38 -0800604 updateInstance: updateInstance,
Simon Huntbb282f52014-11-10 11:08:19 -0800605 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800606 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800607 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800608
Simon Hunt7b403bc2014-11-22 19:01:00 -0800609 removeInstance: removeInstance,
Simon Huntca867ac2014-11-28 18:07:35 -0800610 removeDevice: removeDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800611 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800612 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800613
Simon Hunt61d04042014-11-11 17:27:16 -0800614 showDetails: showDetails,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800615 showSummary: showSummary,
Simon Huntb53e0682014-11-12 13:32:01 -0800616 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800617 };
618
Simon Hunta5e89142014-11-14 07:00:33 -0800619 function addInstance(data) {
620 evTrace(data);
621 var inst = data.payload,
622 id = inst.id;
623 if (onosInstances[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800624 updateInstance(data);
Simon Hunta5e89142014-11-14 07:00:33 -0800625 return;
626 }
627 onosInstances[id] = inst;
628 onosOrder.push(inst);
629 updateInstances();
630 }
631
Simon Hunt99c13842014-11-06 18:23:12 -0800632 function addDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800633 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800634 var device = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800635 id = device.id,
636 d;
637
638 if (network.lookup[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800639 updateDevice(data);
Simon Huntca867ac2014-11-28 18:07:35 -0800640 return;
641 }
642
643 d = createDeviceNode(device);
644 network.nodes.push(d);
645 network.lookup[id] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800646 updateNodes();
647 network.force.start();
648 }
649
Simon Hunt99c13842014-11-06 18:23:12 -0800650 function addLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800651 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800652 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800653 result = findLink(link, 'add'),
654 bad = result.badLogic,
Simon Huntca867ac2014-11-28 18:07:35 -0800655 d = result.ldata;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800656
657 if (bad) {
658 logicError(bad + ': ' + link.id);
659 return;
660 }
661
Simon Huntca867ac2014-11-28 18:07:35 -0800662 if (d) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800663 // we already have a backing store link for src/dst nodes
Simon Huntca867ac2014-11-28 18:07:35 -0800664 addLinkUpdate(d, link);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800665 return;
666 }
667
668 // no backing store link yet
Simon Huntca867ac2014-11-28 18:07:35 -0800669 d = createLink(link);
670 if (d) {
671 network.links.push(d);
672 network.lookup[d.key] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800673 updateLinks();
674 network.force.start();
675 }
676 }
677
Simon Hunt56d51852014-11-09 13:03:35 -0800678 function addHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800679 evTrace(data);
Simon Hunt56d51852014-11-09 13:03:35 -0800680 var host = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800681 id = host.id,
682 d,
Simon Hunt56d51852014-11-09 13:03:35 -0800683 lnk;
Simon Huntca867ac2014-11-28 18:07:35 -0800684
685 if (network.lookup[id]) {
686 logicError('Host already added: ' + id);
687 return;
688 }
689
690 d = createHostNode(host);
691 network.nodes.push(d);
692 network.lookup[host.id] = d;
Simon Hunt56d51852014-11-09 13:03:35 -0800693 updateNodes();
694
695 lnk = createHostLink(host);
696 if (lnk) {
Simon Huntca867ac2014-11-28 18:07:35 -0800697 d.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800698 network.links.push(lnk);
Simon Huntca867ac2014-11-28 18:07:35 -0800699 network.lookup[d.ingress] = lnk;
700 network.lookup[d.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800701 updateLinks();
702 }
703 network.force.start();
704 }
705
Simon Hunt44031102014-11-11 13:20:36 -0800706 // TODO: fold updateX(...) methods into one base method; remove duplication
Simon Hunt56a2ea42014-11-19 12:39:31 -0800707
708 function updateInstance(data) {
709 evTrace(data);
710 var inst = data.payload,
711 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800712 d = onosInstances[id];
713 if (d) {
714 $.extend(d, inst);
Simon Hunt56a2ea42014-11-19 12:39:31 -0800715 updateInstances();
Simon Hunt56a2ea42014-11-19 12:39:31 -0800716 } else {
717 logicError('updateInstance lookup fail. ID = "' + id + '"');
718 }
719 }
720
Simon Huntbb282f52014-11-10 11:08:19 -0800721 function updateDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800722 evTrace(data);
Simon Huntbb282f52014-11-10 11:08:19 -0800723 var device = data.payload,
724 id = device.id,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800725 d = network.lookup[id],
726 wasOnline;
727
Simon Hunt62c47542014-11-22 22:16:32 -0800728 if (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -0800729 wasOnline = d.online;
Simon Hunt62c47542014-11-22 22:16:32 -0800730 $.extend(d, device);
731 if (positionNode(d, true)) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800732 sendUpdateMeta(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -0800733 }
734 updateNodes();
Simon Hunt6d9bd032014-11-28 22:16:40 -0800735 if (wasOnline !== d.online) {
736 findAttachedLinks(d.id).forEach(restyleLinkElement);
737 updateOfflineVisibility(d);
738 }
Simon Huntbb282f52014-11-10 11:08:19 -0800739 } else {
740 logicError('updateDevice lookup fail. ID = "' + id + '"');
741 }
742 }
743
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800744 function updateLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800745 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800746 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800747 result = findLink(link, 'update'),
748 bad = result.badLogic;
749 if (bad) {
750 logicError(bad + ': ' + link.id);
751 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800752 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800753 result.updateWith(link);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800754 }
755
Simon Hunt7cd48f32014-11-09 23:42:50 -0800756 function updateHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800757 evTrace(data);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800758 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800759 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800760 d = network.lookup[id];
761 if (d) {
762 $.extend(d, host);
Simon Hunt6d9bd032014-11-28 22:16:40 -0800763 if (positionNode(d, true)) {
764 sendUpdateMeta(d, true);
765 }
766 updateNodes(d);
Simon Huntbb282f52014-11-10 11:08:19 -0800767 } else {
768 logicError('updateHost lookup fail. ID = "' + id + '"');
769 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800770 }
771
Simon Hunt44031102014-11-11 13:20:36 -0800772 // TODO: fold removeX(...) methods into base method - remove dup code
Simon Hunt7b403bc2014-11-22 19:01:00 -0800773 function removeInstance(data) {
774 evTrace(data);
775 var inst = data.payload,
776 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800777 d = onosInstances[id];
778 if (d) {
779 var idx = find(id, onosOrder);
Simon Hunt7b403bc2014-11-22 19:01:00 -0800780 if (idx >= 0) {
781 onosOrder.splice(idx, 1);
782 }
783 delete onosInstances[id];
784 updateInstances();
785 } else {
786 logicError('updateInstance lookup fail. ID = "' + id + '"');
787 }
788 }
789
Simon Huntca867ac2014-11-28 18:07:35 -0800790 function removeDevice(data) {
791 evTrace(data);
792 var device = data.payload,
793 id = device.id,
794 d = network.lookup[id];
795 if (d) {
796 removeDeviceElement(d);
797 } else {
798 logicError('removeDevice lookup fail. ID = "' + id + '"');
799 }
800 }
801
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800802 function removeLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800803 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800804 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800805 result = findLink(link, 'remove'),
806 bad = result.badLogic;
807 if (bad) {
Simon Huntca867ac2014-11-28 18:07:35 -0800808 // may have already removed link, if attached to removed device
809 console.warn(bad + ': ' + link.id);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800810 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800811 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800812 result.removeRawLink();
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800813 }
814
Simon Hunt44031102014-11-11 13:20:36 -0800815 function removeHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800816 evTrace(data);
Simon Hunt44031102014-11-11 13:20:36 -0800817 var host = data.payload,
818 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800819 d = network.lookup[id];
820 if (d) {
821 removeHostElement(d, true);
Simon Hunt44031102014-11-11 13:20:36 -0800822 } else {
Simon Huntca867ac2014-11-28 18:07:35 -0800823 // may have already removed host, if attached to removed device
824 console.warn('removeHost lookup fail. ID = "' + id + '"');
Simon Hunt44031102014-11-11 13:20:36 -0800825 }
826 }
827
Simon Huntca867ac2014-11-28 18:07:35 -0800828 // the following events are server responses to user actions
Thomas Vachuska47635c62014-11-22 01:21:36 -0800829 function showSummary(data) {
830 evTrace(data);
831 populateSummary(data.payload);
Simon Hunt06811b72014-11-25 18:54:48 -0800832 showSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800833 }
834
Simon Hunt61d04042014-11-11 17:27:16 -0800835 function showDetails(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800836 evTrace(data);
Simon Hunt27d322d2014-11-28 10:45:43 -0800837 haveDetails = true;
Simon Hunt61d04042014-11-11 17:27:16 -0800838 populateDetails(data.payload);
Simon Hunt27d322d2014-11-28 10:45:43 -0800839 if (useDetails) {
840 showDetailPane();
841 }
Simon Hunt61d04042014-11-11 17:27:16 -0800842 }
843
Simon Huntb53e0682014-11-12 13:32:01 -0800844 function showTraffic(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800845 evTrace(data);
Thomas Vachuska4731f122014-11-20 04:56:19 -0800846 var paths = data.payload.paths,
847 hasTraffic = false;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800848
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800849 // Revert any links hilighted previously.
Thomas Vachuska4731f122014-11-20 04:56:19 -0800850 link.style('stroke-width', null)
Thomas Vachuskaa3148a72014-11-19 21:38:35 -0800851 .classed('primary secondary animated optical', false);
Simon Hunte2575b62014-11-18 15:25:53 -0800852 // Remove all previous labels.
853 removeLinkLabels();
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800854
Simon Hunte2575b62014-11-18 15:25:53 -0800855 // Now hilight all links in the paths payload, and attach
856 // labels to them, if they are defined.
Simon Hunta255a2c2014-11-13 22:29:35 -0800857 paths.forEach(function (p) {
Simon Hunte2575b62014-11-18 15:25:53 -0800858 var n = p.links.length,
859 i,
860 ldata;
861
Thomas Vachuska4731f122014-11-20 04:56:19 -0800862 hasTraffic = hasTraffic || p.traffic;
Simon Hunte2575b62014-11-18 15:25:53 -0800863 for (i=0; i<n; i++) {
864 ldata = findLinkById(p.links[i]);
Thomas Vachuska4731f122014-11-20 04:56:19 -0800865 if (ldata && ldata.el) {
Simon Hunte2575b62014-11-18 15:25:53 -0800866 ldata.el.classed(p.class, true);
867 ldata.label = p.labels[i];
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800868 }
Simon Hunte2575b62014-11-18 15:25:53 -0800869 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800870 });
Thomas Vachuska4731f122014-11-20 04:56:19 -0800871
Simon Hunte2575b62014-11-18 15:25:53 -0800872 updateLinks();
Thomas Vachuska4731f122014-11-20 04:56:19 -0800873
874 if (hasTraffic && !antTimer) {
875 startAntTimer();
876 } else if (!hasTraffic && antTimer) {
877 stopAntTimer();
878 }
Simon Huntb53e0682014-11-12 13:32:01 -0800879 }
880
Simon Hunt56d51852014-11-09 13:03:35 -0800881 // ...............................
882
Simon Hunt99c13842014-11-06 18:23:12 -0800883 function unknownEvent(data) {
Simon Hunt434cf142014-11-24 11:10:28 -0800884 console.warn('Unknown event type: "' + data.event + '"', data);
Simon Hunt99c13842014-11-06 18:23:12 -0800885 }
886
887 function handleServerEvent(data) {
888 var fn = eventDispatch[data.event] || unknownEvent;
889 fn(data);
890 }
891
892 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -0800893 // Out-going messages...
894
Simon Huntb53e0682014-11-12 13:32:01 -0800895 function nSel() {
896 return selectOrder.length;
897 }
Simon Hunt61d04042014-11-11 17:27:16 -0800898 function getSel(idx) {
899 return selections[selectOrder[idx]];
900 }
Simon Huntb53e0682014-11-12 13:32:01 -0800901 function allSelectionsClass(cls) {
902 for (var i=0, n=nSel(); i<n; i++) {
903 if (getSel(i).obj.class !== cls) {
904 return false;
905 }
906 }
907 return true;
908 }
Simon Hunt61d04042014-11-11 17:27:16 -0800909
Thomas Vachuska47635c62014-11-22 01:21:36 -0800910 function toggleInstances() {
911 if (!oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800912 showInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800913 } else {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800914 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800915 }
916 }
917
Simon Huntb0ecfa52014-11-23 21:05:12 -0800918 function showInstances() {
919 oiBox.show();
920 colorAffinity = true;
921 updateDeviceColors();
922 }
923
924 function hideInstances() {
925 oiBox.hide();
926 colorAffinity = false;
927 cancelAffinity();
928 updateDeviceColors();
929 }
930
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800931 function equalizeMasters() {
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800932 sendMessage('equalizeMasters');
Simon Huntc1cc81c2014-11-29 14:59:01 -0800933 flash('Equalizing master roles');
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800934 }
935
Thomas Vachuska47635c62014-11-22 01:21:36 -0800936 function toggleSummary() {
937 if (!summaryPane.isVisible()) {
938 requestSummary();
939 } else {
940 cancelSummary();
941 }
942 }
943
944 // request overall summary data
945 function requestSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -0800946 sendMessage('requestSummary');
Thomas Vachuska47635c62014-11-22 01:21:36 -0800947 }
948
949 function cancelSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -0800950 sendMessage('cancelSummary');
Simon Hunt06811b72014-11-25 18:54:48 -0800951 hideSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800952 }
953
Simon Hunt27d322d2014-11-28 10:45:43 -0800954 function toggleDetails() {
955 useDetails = !useDetails;
956 if (useDetails) {
957 flash('Enable details pane');
958 if (haveDetails) {
959 showDetailPane();
960 }
961 } else {
962 flash('Disable details pane');
963 hideDetailPane();
964 }
965 }
966
Simon Hunt06811b72014-11-25 18:54:48 -0800967 // encapsulate interaction between summary and details panes
968 function showSummaryPane() {
969 if (detailPane.isVisible()) {
970 detailPane.down(summaryPane.show);
971 } else {
972 summaryPane.show();
973 }
974 }
975
976 function hideSummaryPane() {
977 summaryPane.hide(function () {
978 if (detailPane.isVisible()) {
979 detailPane.up();
980 }
981 });
982 }
983
984 function showDetailPane() {
985 if (summaryPane.isVisible()) {
986 detailPane.down(detailPane.show);
987 } else {
988 detailPane.up(detailPane.show);
989 }
990 }
991
992 function hideDetailPane() {
993 detailPane.hide();
994 }
995
996
Simon Hunt61d04042014-11-11 17:27:16 -0800997 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -0800998 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -0800999 function requestDetails() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001000 var data = getSel(0).obj;
1001 sendMessage('requestDetails', {
1002 id: data.id,
1003 class: data.class
1004 });
Simon Hunt61d04042014-11-11 17:27:16 -08001005 }
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() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001027 sendMessage('cancelTraffic');
Thomas Vachuska47635c62014-11-22 01:21:36 -08001028 }
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() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001047 function hoverValid() {
1048 return hoverMode === hoverModeIntents &&
1049 hovered &&
1050 (hovered.class === 'host' || hovered.class === 'device');
1051 }
1052
Thomas Vachuska9edca302014-11-22 17:06:42 -08001053 if (validateSelectionContext()) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001054 sendMessage('requestTraffic', {
1055 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001056 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001057 });
1058 }
Simon Huntd72bc702014-11-13 18:38:04 -08001059 }
1060
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -08001061
Thomas Vachuska29617e52014-11-20 03:17:46 -08001062 function showDeviceLinkFlowsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001063 hoverMode = hoverModeFlows;
1064 requestDeviceLinkFlows();
Simon Hunt27d322d2014-11-28 10:45:43 -08001065 flash('Device Flows');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001066 }
1067
Thomas Vachuska9edca302014-11-22 17:06:42 -08001068 function requestDeviceLinkFlows() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001069 function hoverValid() {
1070 return hoverMode === hoverModeFlows &&
1071 hovered && (hovered.class === 'device');
1072 }
1073
Thomas Vachuska9edca302014-11-22 17:06:42 -08001074 if (validateSelectionContext()) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001075 sendMessage('requestDeviceLinkFlows', {
1076 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001077 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001078 });
1079 }
1080 }
1081
1082
1083 function showAllTrafficAction() {
1084 hoverMode = hoverModeAll;
1085 requestAllTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001086 flash('All Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001087 }
1088
1089 function requestAllTraffic() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001090 sendMessage('requestAllTraffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001091 }
1092
1093 function validateSelectionContext() {
Thomas Vachuska29617e52014-11-20 03:17:46 -08001094 if (!hovered && nSel() === 0) {
Thomas Vachuska47635c62014-11-22 01:21:36 -08001095 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08001096 return false;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001097 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001098 return true;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001099 }
Simon Huntd72bc702014-11-13 18:38:04 -08001100
Simon Hunta6a9fe72014-11-20 11:17:12 -08001101
Simon Hunt61d04042014-11-11 17:27:16 -08001102 // ==============================
Simon Hunta5e89142014-11-14 07:00:33 -08001103 // onos instance panel functions
Simon Huntb82f6902014-11-22 11:53:15 -08001104
Simon Hunt7b403bc2014-11-22 19:01:00 -08001105 var instCfg = {
1106 rectPad: 8,
1107 nodeOx: 9,
1108 nodeOy: 9,
1109 nodeDim: 40,
1110 birdOx: 19,
1111 birdOy: 21,
1112 birdDim: 21,
1113 uiDy: 45,
1114 titleDy: 30,
1115 textYOff: 20,
1116 textYSpc: 15
1117 };
1118
1119 function viewBox(dim) {
1120 return '0 0 ' + dim.w + ' ' + dim.h;
1121 }
1122
1123 function instRectAttr(dim) {
1124 var pad = instCfg.rectPad;
1125 return {
1126 x: pad,
1127 y: pad,
1128 width: dim.w - pad*2,
1129 height: dim.h - pad*2,
Simon Huntb0ecfa52014-11-23 21:05:12 -08001130 rx: 6
Simon Hunt7b403bc2014-11-22 19:01:00 -08001131 };
1132 }
1133
1134 function computeDim(self) {
1135 var css = window.getComputedStyle(self);
1136 return {
1137 w: stripPx(css.width),
1138 h: stripPx(css.height)
1139 };
Simon Huntb82f6902014-11-22 11:53:15 -08001140 }
Simon Hunta5e89142014-11-14 07:00:33 -08001141
1142 function updateInstances() {
1143 var onoses = oiBox.el.selectAll('.onosInst')
Simon Huntb82f6902014-11-22 11:53:15 -08001144 .data(onosOrder, function (d) { return d.id; }),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001145 instDim = {w:0,h:0},
1146 c = instCfg;
Simon Hunta5e89142014-11-14 07:00:33 -08001147
Simon Hunt7b403bc2014-11-22 19:01:00 -08001148 function nSw(n) {
1149 return '# Switches: ' + n;
1150 }
Simon Hunta5e89142014-11-14 07:00:33 -08001151
Simon Huntb82f6902014-11-22 11:53:15 -08001152 // operate on existing onos instances if necessary
1153 onoses.each(function (d) {
1154 var el = d3.select(this),
1155 svg = el.select('svg');
Simon Hunt7b403bc2014-11-22 19:01:00 -08001156 instDim = computeDim(this);
Simon Huntb82f6902014-11-22 11:53:15 -08001157
1158 // update online state
1159 el.classed('online', d.online);
1160
1161 // update ui-attached state
1162 svg.select('use.uiBadge').remove();
1163 if (d.uiAttached) {
1164 attachUiBadge(svg);
1165 }
1166
Simon Hunt7b403bc2014-11-22 19:01:00 -08001167 function updAttr(id, value) {
1168 svg.select('text.instLabel.'+id).text(value);
1169 }
1170
1171 updAttr('ip', d.ip);
1172 updAttr('ns', nSw(d.switches));
Simon Huntb82f6902014-11-22 11:53:15 -08001173 });
1174
1175
1176 // operate on new onos instances
Simon Hunta5e89142014-11-14 07:00:33 -08001177 var entering = onoses.enter()
1178 .append('div')
1179 .attr('class', 'onosInst')
1180 .classed('online', function (d) { return d.online; })
Simon Hunt9c15eca2014-11-15 18:37:59 -08001181 .on('click', clickInst);
1182
Simon Huntb82f6902014-11-22 11:53:15 -08001183 entering.each(function (d) {
Simon Hunt9c15eca2014-11-15 18:37:59 -08001184 var el = d3.select(this),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001185 rectAttr,
1186 svg;
1187 instDim = computeDim(this);
1188 rectAttr = instRectAttr(instDim);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001189
Simon Hunt7b403bc2014-11-22 19:01:00 -08001190 svg = el.append('svg').attr({
1191 width: instDim.w,
1192 height: instDim.h,
1193 viewBox: viewBox(instDim)
Simon Hunt95908012014-11-20 10:20:26 -08001194 });
Simon Huntb82f6902014-11-22 11:53:15 -08001195
Simon Hunt7b403bc2014-11-22 19:01:00 -08001196 svg.append('rect').attr(rectAttr);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001197
Thomas Vachuskae02e11c2014-11-24 16:13:52 -08001198 //appendGlyph(svg, c.nodeOx, c.nodeOy, c.nodeDim, '#node');
1199 appendBadge(svg, 14, 14, 28, '#bird');
Simon Huntb82f6902014-11-22 11:53:15 -08001200
1201 if (d.uiAttached) {
1202 attachUiBadge(svg);
1203 }
1204
Simon Hunt7b403bc2014-11-22 19:01:00 -08001205 var left = c.nodeOx + c.nodeDim,
1206 len = rectAttr.width - left,
1207 hlen = len / 2,
1208 midline = hlen + left;
Simon Hunt9c15eca2014-11-15 18:37:59 -08001209
Simon Hunt7b403bc2014-11-22 19:01:00 -08001210 // title
1211 svg.append('text')
1212 .attr({
1213 class: 'instTitle',
1214 x: midline,
1215 y: c.titleDy
1216 })
1217 .text(d.id);
1218
1219 // a couple of attributes
1220 var ty = c.titleDy + c.textYOff;
1221
1222 function addAttr(id, label) {
1223 svg.append('text').attr({
1224 class: 'instLabel ' + id,
1225 x: midline,
1226 y: ty
1227 }).text(label);
1228 ty += c.textYSpc;
1229 }
1230
1231 addAttr('ip', d.ip);
1232 addAttr('ns', nSw(d.switches));
Simon Hunt9c15eca2014-11-15 18:37:59 -08001233 });
Simon Hunta5e89142014-11-14 07:00:33 -08001234
1235 // operate on existing + new onoses here
Simon Hunt8f40cce2014-11-23 15:57:30 -08001236 // set the affinity colors...
1237 onoses.each(function (d) {
1238 var el = d3.select(this),
1239 rect = el.select('svg').select('rect'),
1240 col = instColor(d.id, d.online);
1241 rect.style('fill', col);
1242 });
Simon Hunta5e89142014-11-14 07:00:33 -08001243
Simon Hunt7b403bc2014-11-22 19:01:00 -08001244 // adjust the panel size appropriately...
1245 oiBox.width(instDim.w * onosOrder.length);
1246 oiBox.height(instDim.h);
1247
1248 // remove any outgoing instances
1249 onoses.exit().remove();
Simon Hunta5e89142014-11-14 07:00:33 -08001250 }
1251
Simon Hunt8f40cce2014-11-23 15:57:30 -08001252 function instColor(id, online) {
1253 return cat7.get(id, !online, network.view.getTheme());
1254 }
1255
Simon Hunt9462e8c2014-11-14 17:28:09 -08001256 function clickInst(d) {
1257 var el = d3.select(this),
1258 aff = el.classed('affinity');
1259 if (!aff) {
1260 setAffinity(el, d);
1261 } else {
1262 cancelAffinity();
1263 }
1264 }
1265
1266 function setAffinity(el, d) {
1267 d3.selectAll('.onosInst')
1268 .classed('mastership', true)
1269 .classed('affinity', false);
1270 el.classed('affinity', true);
1271
1272 suppressLayers(true);
1273 node.each(function (n) {
1274 if (n.master === d.id) {
1275 n.el.classed('suppressed', false);
1276 }
1277 });
1278 oiShowMaster = true;
1279 }
1280
1281 function cancelAffinity() {
1282 d3.selectAll('.onosInst')
1283 .classed('mastership affinity', false);
1284 restoreLayerState();
1285 oiShowMaster = false;
1286 }
1287
Simon Hunt7b403bc2014-11-22 19:01:00 -08001288 // TODO: these should be moved out to utility module.
1289 function stripPx(s) {
1290 return s.replace(/px$/,'');
1291 }
1292
1293 function appendUse(svg, ox, oy, dim, iid, cls) {
1294 var use = svg.append('use').attr({
1295 transform: translate(ox,oy),
1296 'xlink:href': iid,
1297 width: dim,
1298 height: dim
1299 });
1300 if (cls) {
1301 use.classed(cls, true);
1302 }
1303 return use;
1304 }
1305
1306 function appendGlyph(svg, ox, oy, dim, iid, cls) {
1307 appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
1308 }
1309
1310 function appendBadge(svg, ox, oy, dim, iid, cls) {
1311 appendUse(svg, ox, oy, dim, iid,cls ).classed('badgeIcon', true);
1312 }
1313
1314 function attachUiBadge(svg) {
1315 appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
1316 }
1317
Simon Hunt434cf142014-11-24 11:10:28 -08001318 function visVal(b) {
1319 return b ? 'visible' : 'hidden';
1320 }
1321
Simon Hunta5e89142014-11-14 07:00:33 -08001322 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -08001323 // force layout modification functions
1324
1325 function translate(x, y) {
1326 return 'translate(' + x + ',' + y + ')';
1327 }
1328
Simon Hunte2575b62014-11-18 15:25:53 -08001329 function rotate(deg) {
1330 return 'rotate(' + deg + ')';
1331 }
1332
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001333 function missMsg(what, id) {
1334 return '\n[' + what + '] "' + id + '" missing ';
1335 }
1336
1337 function linkEndPoints(srcId, dstId) {
1338 var srcNode = network.lookup[srcId],
1339 dstNode = network.lookup[dstId],
1340 sMiss = !srcNode ? missMsg('src', srcId) : '',
1341 dMiss = !dstNode ? missMsg('dst', dstId) : '';
1342
1343 if (sMiss || dMiss) {
1344 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
1345 return null;
1346 }
1347 return {
1348 source: srcNode,
1349 target: dstNode,
1350 x1: srcNode.x,
1351 y1: srcNode.y,
1352 x2: dstNode.x,
1353 y2: dstNode.y
1354 };
1355 }
1356
Simon Hunt56d51852014-11-09 13:03:35 -08001357 function createHostLink(host) {
1358 var src = host.id,
1359 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -08001360 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001361 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -08001362
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001363 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -08001364 return null;
1365 }
1366
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001367 // Synthesize link ...
1368 $.extend(lnk, {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001369 key: id,
Simon Hunt56d51852014-11-09 13:03:35 -08001370 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001371
1372 type: function () { return 'hostLink'; },
Simon Hunt6d9bd032014-11-28 22:16:40 -08001373 online: function () {
1374 // hostlink target is edge switch
1375 return lnk.target.online;
1376 },
Simon Hunt8257f4c2014-11-16 19:34:54 -08001377 linkWidth: function () { return 1; }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001378 });
Simon Hunt99c13842014-11-06 18:23:12 -08001379 return lnk;
1380 }
1381
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001382 function createLink(link) {
Simon Hunta6a9fe72014-11-20 11:17:12 -08001383 var lnk = linkEndPoints(link.src, link.dst);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001384
1385 if (!lnk) {
1386 return null;
1387 }
1388
Simon Hunt8257f4c2014-11-16 19:34:54 -08001389 $.extend(lnk, {
1390 key: link.id,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001391 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001392 fromSource: link,
1393
1394 // functions to aggregate dual link state
1395 type: function () {
1396 var s = lnk.fromSource,
1397 t = lnk.fromTarget;
1398 return (s && s.type) || (t && t.type) || defaultLinkType;
1399 },
1400 online: function () {
1401 var s = lnk.fromSource,
Simon Hunt6d9bd032014-11-28 22:16:40 -08001402 t = lnk.fromTarget,
1403 both = lnk.source.online && lnk.target.online;
1404 return both && ((s && s.online) || (t && t.online));
Simon Hunt8257f4c2014-11-16 19:34:54 -08001405 },
1406 linkWidth: function () {
1407 var s = lnk.fromSource,
1408 t = lnk.fromTarget,
1409 ws = (s && s.linkWidth) || 0,
1410 wt = (t && t.linkWidth) || 0;
1411 return Math.max(ws, wt);
1412 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001413 });
1414 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -08001415 }
1416
Simon Hunte2575b62014-11-18 15:25:53 -08001417 function removeLinkLabels() {
1418 network.links.forEach(function (d) {
1419 d.label = '';
1420 });
1421 }
1422
Simon Hunt434cf142014-11-24 11:10:28 -08001423 function showHostVis(el) {
1424 el.style('visibility', visVal(showHosts));
1425 }
1426
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001427 var widthRatio = 1.4,
1428 linkScale = d3.scale.linear()
1429 .domain([1, 12])
1430 .range([widthRatio, 12 * widthRatio])
1431 .clamp(true);
1432
Simon Hunt99c13842014-11-06 18:23:12 -08001433 function updateLinks() {
1434 link = linkG.selectAll('.link')
Simon Hunt8257f4c2014-11-16 19:34:54 -08001435 .data(network.links, function (d) { return d.key; });
Simon Hunt99c13842014-11-06 18:23:12 -08001436
1437 // operate on existing links, if necessary
1438 // link .foo() .bar() ...
1439
1440 // operate on entering links:
1441 var entering = link.enter()
1442 .append('line')
1443 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001444 x1: function (d) { return d.x1; },
1445 y1: function (d) { return d.y1; },
1446 x2: function (d) { return d.x2; },
1447 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001448 stroke: config.topo.linkInColor,
1449 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -08001450 });
1451
1452 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -08001453 entering.each(function (d) {
1454 var link = d3.select(this);
1455 // provide ref to element selection from backing data....
1456 d.el = link;
Simon Hunt8257f4c2014-11-16 19:34:54 -08001457 restyleLinkElement(d);
Simon Hunt434cf142014-11-24 11:10:28 -08001458 if (d.type() === 'hostLink') {
1459 showHostVis(link);
1460 }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001461 });
Thomas Vachuska4830d392014-11-09 17:09:56 -08001462
1463 // operate on both existing and new links, if necessary
1464 //link .foo() .bar() ...
1465
Simon Hunte2575b62014-11-18 15:25:53 -08001466 // apply or remove labels
1467 var labelData = getLabelData();
1468 applyLinkLabels(labelData);
1469
Thomas Vachuska4830d392014-11-09 17:09:56 -08001470 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -08001471 link.exit()
Simon Hunt6d9bd032014-11-28 22:16:40 -08001472 .attr('stroke-dasharray', '3 3')
Simon Hunt13bf9c82014-11-18 07:26:44 -08001473 .style('opacity', 0.5)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001474 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -08001475 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001476 .attr({
Simon Hunt6d9bd032014-11-28 22:16:40 -08001477 'stroke-dasharray': '3 12',
Simon Hunt13bf9c82014-11-18 07:26:44 -08001478 stroke: config.topo.linkOutColor,
1479 'stroke-width': config.topo.linkOutWidth
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001480 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001481 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -08001482 .remove();
Simon Hunte2575b62014-11-18 15:25:53 -08001483
1484 // NOTE: invoke a single tick to force the labels to position
1485 // onto their links.
1486 tick();
1487 }
1488
1489 function getLabelData() {
1490 // create the backing data for showing labels..
1491 var data = [];
1492 link.each(function (d) {
1493 if (d.label) {
1494 data.push({
1495 id: 'lab-' + d.key,
1496 key: d.key,
1497 label: d.label,
1498 ldata: d
1499 });
1500 }
1501 });
1502 return data;
1503 }
1504
1505 var linkLabelOffset = '0.3em';
1506
1507 function applyLinkLabels(data) {
1508 var entering;
1509
1510 linkLabel = linkLabelG.selectAll('.linkLabel')
1511 .data(data, function (d) { return d.id; });
1512
Simon Hunt56a2ea42014-11-19 12:39:31 -08001513 // for elements already existing, we need to update the text
1514 // and adjust the rectangle size to fit
1515 linkLabel.each(function (d) {
1516 var el = d3.select(this),
1517 rect = el.select('rect'),
1518 text = el.select('text');
1519 text.text(d.label);
1520 rect.attr(rectAroundText(el));
1521 });
Thomas Vachuskaf75b7ab2014-11-19 12:15:55 -08001522
Simon Hunte2575b62014-11-18 15:25:53 -08001523 entering = linkLabel.enter().append('g')
1524 .classed('linkLabel', true)
1525 .attr('id', function (d) { return d.id; });
1526
1527 entering.each(function (d) {
1528 var el = d3.select(this),
1529 rect,
1530 text,
1531 parms = {
1532 x1: d.ldata.x1,
1533 y1: d.ldata.y1,
1534 x2: d.ldata.x2,
1535 y2: d.ldata.y2
1536 };
1537
1538 d.el = el;
1539 rect = el.append('rect');
1540 text = el.append('text').text(d.label);
1541 rect.attr(rectAroundText(el));
1542 text.attr('dy', linkLabelOffset);
1543
1544 el.attr('transform', transformLabel(parms));
1545 });
1546
1547 // Remove any links that are no longer required.
1548 linkLabel.exit().remove();
1549 }
1550
1551 function rectAroundText(el) {
1552 var text = el.select('text'),
1553 box = text.node().getBBox();
1554
1555 // translate the bbox so that it is centered on [x,y]
1556 box.x = -box.width / 2;
1557 box.y = -box.height / 2;
1558
1559 // add padding
1560 box.x -= 1;
1561 box.width += 2;
1562 return box;
1563 }
1564
1565 function transformLabel(p) {
1566 var dx = p.x2 - p.x1,
1567 dy = p.y2 - p.y1,
1568 xMid = dx/2 + p.x1,
1569 yMid = dy/2 + p.y1;
Simon Hunte2575b62014-11-18 15:25:53 -08001570 return translate(xMid, yMid);
Simon Hunt99c13842014-11-06 18:23:12 -08001571 }
1572
1573 function createDeviceNode(device) {
1574 // start with the object as is
1575 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -08001576 type = device.type,
Simon Huntc72967b2014-11-20 09:21:42 -08001577 svgCls = type ? 'node device ' + type : 'node device',
1578 labels = device.labels || [];
1579
Simon Hunt99c13842014-11-06 18:23:12 -08001580 // Augment as needed...
1581 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -08001582 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -08001583 positionNode(node);
Simon Hunt99c13842014-11-06 18:23:12 -08001584 return node;
1585 }
1586
Simon Hunt56d51852014-11-09 13:03:35 -08001587 function createHostNode(host) {
1588 // start with the object as is
1589 var node = host;
1590
1591 // Augment as needed...
1592 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001593 if (!node.type) {
Simon Hunt209155e2014-11-21 12:16:09 -08001594 node.type = 'endstation';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001595 }
Simon Hunt7fa116d2014-11-17 14:16:55 -08001596 node.svgClass = 'node host ' + node.type;
Simon Hunt56d51852014-11-09 13:03:35 -08001597 positionNode(node);
Simon Hunt56d51852014-11-09 13:03:35 -08001598 return node;
1599 }
1600
Simon Hunt62c47542014-11-22 22:16:32 -08001601 function positionNode(node, forUpdate) {
Simon Hunt99c13842014-11-06 18:23:12 -08001602 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -08001603 x = meta && meta.x,
1604 y = meta && meta.y,
1605 xy;
Simon Hunt99c13842014-11-06 18:23:12 -08001606
Simon Huntac9e24f2014-11-12 10:12:21 -08001607 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -08001608 if (x && y) {
1609 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001610 node.px = node.x = x;
1611 node.py = node.y = y;
Simon Huntac9e24f2014-11-12 10:12:21 -08001612 return;
Simon Hunt99c13842014-11-06 18:23:12 -08001613 }
Simon Huntac9e24f2014-11-12 10:12:21 -08001614
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001615 var location = node.location;
1616 if (location && location.type === 'latlng') {
1617 var coord = geoMapProjection([location.lng, location.lat]);
1618 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001619 node.px = node.x = coord[0];
1620 node.py = node.y = coord[1];
Simon Hunt62c47542014-11-22 22:16:32 -08001621 return true;
1622 }
1623
1624 // if this is a node update (not a node add).. skip randomizer
1625 if (forUpdate) {
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001626 return;
1627 }
1628
Simon Huntac9e24f2014-11-12 10:12:21 -08001629 // Note: Placing incoming unpinned nodes at exactly the same point
1630 // (center of the view) causes them to explode outwards when
1631 // the force layout kicks in. So, we spread them out a bit
1632 // initially, to provide a more serene layout convergence.
1633 // Additionally, if the node is a host, we place it near
1634 // the device it is connected to.
1635
1636 function spread(s) {
1637 return Math.floor((Math.random() * s) - s/2);
1638 }
1639
1640 function randDim(dim) {
1641 return dim / 2 + spread(dim * 0.7071);
1642 }
1643
1644 function rand() {
1645 return {
1646 x: randDim(network.view.width()),
1647 y: randDim(network.view.height())
1648 };
1649 }
1650
1651 function near(node) {
1652 var min = 12,
1653 dx = spread(12),
1654 dy = spread(12);
1655 return {
1656 x: node.x + min + dx,
1657 y: node.y + min + dy
1658 };
1659 }
1660
1661 function getDevice(cp) {
1662 var d = network.lookup[cp.device];
1663 return d || rand();
1664 }
1665
1666 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1667 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -08001668 }
1669
Simon Hunt99c13842014-11-06 18:23:12 -08001670
Simon Huntc72967b2014-11-20 09:21:42 -08001671 function iconGlyphUrl(d) {
1672 var which = d.type || 'unknown';
1673 return '#' + which;
1674 }
1675
Simon Hunt99c13842014-11-06 18:23:12 -08001676 // returns the newly computed bounding box of the rectangle
1677 function adjustRectToFitText(n) {
1678 var text = n.select('text'),
1679 box = text.node().getBBox(),
1680 lab = config.labels;
1681
1682 text.attr('text-anchor', 'middle')
1683 .attr('y', '-0.8em')
1684 .attr('x', lab.imgPad/2);
1685
1686 // translate the bbox so that it is centered on [x,y]
1687 box.x = -box.width / 2;
1688 box.y = -box.height / 2;
1689
1690 // add padding
1691 box.x -= (lab.padLR + lab.imgPad/2);
1692 box.width += lab.padLR * 2 + lab.imgPad;
1693 box.y -= lab.padTB;
1694 box.height += lab.padTB * 2;
1695
1696 return box;
1697 }
1698
Simon Hunt1a9eff92014-11-07 11:06:34 -08001699 function mkSvgClass(d) {
1700 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1701 }
1702
Simon Hunt7cd48f32014-11-09 23:42:50 -08001703 function hostLabel(d) {
1704 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1705 return d.labels[idx];
1706 }
1707 function deviceLabel(d) {
1708 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1709 return d.labels[idx];
1710 }
Simon Huntc72967b2014-11-20 09:21:42 -08001711 function trimLabel(label) {
1712 return (label && label.trim()) || '';
1713 }
1714
1715 function emptyBox() {
1716 return {
1717 x: -2,
1718 y: -2,
1719 width: 4,
1720 height: 4
1721 };
Simon Hunt7cd48f32014-11-09 23:42:50 -08001722 }
1723
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001724 function updateDeviceLabel(d) {
Simon Huntc72967b2014-11-20 09:21:42 -08001725 var label = trimLabel(deviceLabel(d)),
1726 noLabel = !label,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001727 node = d.el,
Simon Huntc72967b2014-11-20 09:21:42 -08001728 box,
1729 dx,
Simon Hunt395a70c2014-11-22 23:17:40 -08001730 dy,
1731 cfg = config.icons.device;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001732
1733 node.select('text')
1734 .text(label)
1735 .style('opacity', 0)
1736 .transition()
1737 .style('opacity', 1);
1738
Simon Huntc72967b2014-11-20 09:21:42 -08001739 if (noLabel) {
1740 box = emptyBox();
Simon Hunt395a70c2014-11-22 23:17:40 -08001741 dx = -cfg.dim/2;
1742 dy = -cfg.dim/2;
Simon Huntc72967b2014-11-20 09:21:42 -08001743 } else {
1744 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08001745 dx = box.x + cfg.xoff;
1746 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08001747 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001748
1749 node.select('rect')
1750 .transition()
1751 .attr(box);
1752
Simon Huntc72967b2014-11-20 09:21:42 -08001753 node.select('g.deviceIcon')
Thomas Vachuska89543292014-11-19 11:28:33 -08001754 .transition()
Simon Huntc72967b2014-11-20 09:21:42 -08001755 .attr('transform', translate(dx, dy));
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001756 }
1757
1758 function updateHostLabel(d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001759 var label = trimLabel(hostLabel(d));
1760 d.el.select('text').text(label);
Simon Huntbb282f52014-11-10 11:08:19 -08001761 }
1762
Simon Hunt434cf142014-11-24 11:10:28 -08001763 function updateHostVisibility() {
1764 var v = visVal(showHosts);
1765 nodeG.selectAll('.host').style('visibility', v);
1766 linkG.selectAll('.hostLink').style('visibility', v);
1767 }
1768
Simon Hunt6d9bd032014-11-28 22:16:40 -08001769 function findOfflineNodes() {
1770 var a = [];
1771 network.nodes.forEach(function (d) {
1772 if (d.class === 'device' && !d.online) {
1773 a.push(d);
1774 }
1775 });
1776 return a;
1777 }
1778
1779 function updateOfflineVisibility(dev) {
1780 var so = showOffline,
1781 sh = showHosts,
1782 vb = 'visibility',
1783 v, off, al, ah, db, b;
1784
1785 function updAtt(show) {
1786 al.forEach(function (d) {
1787 b = show && ((d.type() !== 'hostLink') || sh);
1788 d.el.style(vb, visVal(b));
1789 });
1790 ah.forEach(function (d) {
1791 b = show && sh;
1792 d.el.style(vb, visVal(b));
1793 });
1794 }
1795
1796 if (dev) {
1797 // updating a specific device that just toggled off/on-line
1798 db = dev.online || so;
1799 dev.el.style(vb, visVal(db));
1800 al = findAttachedLinks(dev.id);
1801 ah = findAttachedHosts(dev.id);
1802 updAtt(db);
1803 } else {
1804 // updating all offline devices
1805 v = visVal(so);
1806 off = findOfflineNodes();
1807 off.forEach(function (d) {
1808 d.el.style(vb, v);
1809 al = findAttachedLinks(d.id);
1810 ah = findAttachedHosts(d.id);
1811 updAtt(so);
1812 });
1813 }
1814 }
1815
Simon Hunt6ac93f32014-11-13 12:17:27 -08001816 function nodeMouseOver(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001817 hovered = d;
Thomas Vachuska9edca302014-11-22 17:06:42 -08001818 requestTrafficForMode();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001819 }
1820
1821 function nodeMouseOut(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -08001822 hovered = null;
Thomas Vachuska9edca302014-11-22 17:06:42 -08001823 requestTrafficForMode();
Simon Hunt6ac93f32014-11-13 12:17:27 -08001824 }
Simon Huntbb282f52014-11-10 11:08:19 -08001825
Simon Hunteb1514d2014-11-20 09:57:29 -08001826 function addHostIcon(node, radius, iid) {
Thomas Vachuska89543292014-11-19 11:28:33 -08001827 var dim = radius * 1.5,
1828 xlate = -dim / 2;
1829
Simon Hunteb1514d2014-11-20 09:57:29 -08001830 node.append('use').attr({
1831 class: 'glyphIcon hostIcon',
1832 transform: translate(xlate,xlate),
1833 'xlink:href': iid,
1834 width: dim,
1835 height: dim
1836 });
Thomas Vachuska89543292014-11-19 11:28:33 -08001837 }
1838
Simon Hunt99c13842014-11-06 18:23:12 -08001839 function updateNodes() {
1840 node = nodeG.selectAll('.node')
1841 .data(network.nodes, function (d) { return d.id; });
1842
Simon Hunt62c47542014-11-22 22:16:32 -08001843 // operate on existing nodes...
1844 node.filter('.device').each(function (d) {
Simon Hunt62c47542014-11-22 22:16:32 -08001845 var node = d.el;
1846 node.classed('online', d.online);
1847 updateDeviceLabel(d);
1848 positionNode(d, true);
1849 });
1850
1851 node.filter('.host').each(function (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001852 updateHostLabel(d);
1853 positionNode(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -08001854 });
Simon Hunt99c13842014-11-06 18:23:12 -08001855
1856 // operate on entering nodes:
1857 var entering = node.enter()
1858 .append('g')
1859 .attr({
1860 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001861 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -08001862 transform: function (d) { return translate(d.x, d.y); },
1863 opacity: 0
1864 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08001865 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -08001866 .on('mouseover', nodeMouseOver)
1867 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -08001868 .transition()
1869 .attr('opacity', 1);
1870
1871 // augment device nodes...
1872 entering.filter('.device').each(function (d) {
1873 var node = d3.select(this),
Simon Huntc72967b2014-11-20 09:21:42 -08001874 label = trimLabel(deviceLabel(d)),
1875 noLabel = !label,
Simon Hunt99c13842014-11-06 18:23:12 -08001876 box;
1877
Simon Hunt7cd48f32014-11-09 23:42:50 -08001878 // provide ref to element from backing data....
1879 d.el = node;
1880
Simon Hunt62c47542014-11-22 22:16:32 -08001881 node.append('rect').attr({ rx: 5, ry: 5 });
1882 node.append('text').text(label).attr('dy', '1.1em');
Simon Hunt99c13842014-11-06 18:23:12 -08001883 box = adjustRectToFitText(node);
Simon Hunta3dd9572014-11-20 15:22:41 -08001884 node.select('rect').attr(box);
Simon Huntc72967b2014-11-20 09:21:42 -08001885 addDeviceIcon(node, box, noLabel, iconGlyphUrl(d));
Simon Huntc7ee0662014-11-05 16:44:37 -08001886 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08001887
Thomas Vachuska89543292014-11-19 11:28:33 -08001888
Simon Hunt56d51852014-11-09 13:03:35 -08001889 // augment host nodes...
1890 entering.filter('.host').each(function (d) {
1891 var node = d3.select(this),
Simon Hunt395a70c2014-11-22 23:17:40 -08001892 cfg = config.icons.host,
1893 r = cfg.radius[d.type] || cfg.defaultRadius,
Thomas Vachuska89543292014-11-19 11:28:33 -08001894 textDy = r + 10,
Simon Hunteb1514d2014-11-20 09:57:29 -08001895 iid = iconGlyphUrl(d);
Simon Hunt56d51852014-11-09 13:03:35 -08001896
Simon Hunt7cd48f32014-11-09 23:42:50 -08001897 // provide ref to element from backing data....
1898 d.el = node;
Simon Hunt434cf142014-11-24 11:10:28 -08001899 showHostVis(node);
Simon Hunt7cd48f32014-11-09 23:42:50 -08001900
Simon Hunt62c47542014-11-22 22:16:32 -08001901 node.append('circle').attr('r', r);
Simon Hunteb1514d2014-11-20 09:57:29 -08001902 if (iid) {
1903 addHostIcon(node, r, iid);
Simon Hunt7fa116d2014-11-17 14:16:55 -08001904 }
Simon Hunt56d51852014-11-09 13:03:35 -08001905 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001906 .text(hostLabel)
Thomas Vachuska89543292014-11-19 11:28:33 -08001907 .attr('dy', textDy)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001908 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08001909 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001910
Simon Hunt99c13842014-11-06 18:23:12 -08001911 // operate on both existing and new nodes, if necessary
Simon Huntb0ecfa52014-11-23 21:05:12 -08001912 updateDeviceColors();
Simon Huntc7ee0662014-11-05 16:44:37 -08001913
Simon Hunt99c13842014-11-06 18:23:12 -08001914 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08001915 // Note that the node is removed after 2 seconds.
1916 // Sub element animations should be shorter than 2 seconds.
1917 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08001918 .transition()
1919 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08001920 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08001921 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08001922
1923 // host node exits....
1924 exiting.filter('.host').each(function (d) {
Simon Huntca867ac2014-11-28 18:07:35 -08001925 var node = d.el;
1926 node.select('use')
1927 .style('opacity', 0.5)
1928 .transition()
1929 .duration(800)
1930 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001931
1932 node.select('text')
1933 .style('opacity', 0.5)
1934 .transition()
Simon Huntca867ac2014-11-28 18:07:35 -08001935 .duration(800)
Simon Huntea80eb42014-11-11 13:46:57 -08001936 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001937
Thomas Vachuska89543292014-11-19 11:28:33 -08001938 node.select('circle')
1939 .style('stroke-fill', '#555')
1940 .style('fill', '#888')
Simon Huntea80eb42014-11-11 13:46:57 -08001941 .style('opacity', 0.5)
1942 .transition()
1943 .duration(1500)
1944 .attr('r', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08001945 });
1946
Simon Huntca867ac2014-11-28 18:07:35 -08001947 // device node exits....
1948 exiting.filter('.device').each(function (d) {
1949 var node = d.el;
1950 node.select('use')
1951 .style('opacity', 0.5)
1952 .transition()
1953 .duration(800)
1954 .style('opacity', 0);
1955
1956 node.selectAll('rect')
1957 .style('stroke-fill', '#555')
1958 .style('fill', '#888')
1959 .style('opacity', 0.5);
1960 });
Simon Hunt62c47542014-11-22 22:16:32 -08001961
1962 network.force.resume();
Simon Huntc7ee0662014-11-05 16:44:37 -08001963 }
1964
Simon Hunt95dad922014-11-24 09:43:31 -08001965 var dCol = {
1966 black: '#000',
1967 paleblue: '#acf',
1968 offwhite: '#ddd',
1969 midgrey: '#888',
1970 lightgrey: '#bbb',
1971 orange: '#f90'
1972 };
1973
Simon Huntb0ecfa52014-11-23 21:05:12 -08001974 // note: these are the device icon colors without affinity
Simon Hunt95dad922014-11-24 09:43:31 -08001975 var dColTheme = {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001976 light: {
1977 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08001978 glyph: dCol.black,
1979 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08001980 },
1981 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08001982 glyph: dCol.midgrey,
1983 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08001984 }
1985 },
Simon Hunt95dad922014-11-24 09:43:31 -08001986 // TODO: theme
Simon Huntb0ecfa52014-11-23 21:05:12 -08001987 dark: {
1988 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08001989 glyph: dCol.black,
1990 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08001991 },
1992 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08001993 glyph: dCol.midgrey,
1994 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08001995 }
1996 }
1997 };
1998
1999 function devBaseColor(d) {
2000 var t = network.view.getTheme(),
2001 o = d.online ? 'online' : 'offline';
Simon Hunt95dad922014-11-24 09:43:31 -08002002 return dColTheme[t][o];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002003 }
2004
2005 function setDeviceColor(d) {
2006 var o = d.online,
2007 s = d.el.classed('selected'),
2008 c = devBaseColor(d),
2009 a = instColor(d.master, o),
2010 g, r,
2011 icon = d.el.select('g.deviceIcon');
2012
2013 if (s) {
2014 g = c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002015 r = dColTheme.sel;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002016 } else if (colorAffinity) {
2017 g = o ? a : c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002018 r = o ? dCol.offwhite : a;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002019 } else {
2020 g = c.glyph;
2021 r = c.rect;
2022 }
2023
2024 icon.select('use')
2025 .style('fill', g);
2026 icon.select('rect')
2027 .style('fill', r);
2028 }
2029
Simon Huntc72967b2014-11-20 09:21:42 -08002030 function addDeviceIcon(node, box, noLabel, iid) {
2031 var cfg = config.icons.device,
2032 dx,
2033 dy,
2034 g;
2035
2036 if (noLabel) {
2037 box = emptyBox();
2038 dx = -cfg.dim/2;
2039 dy = -cfg.dim/2;
2040 } else {
2041 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08002042 dx = box.x + cfg.xoff;
2043 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08002044 }
2045
Simon Hunteb1514d2014-11-20 09:57:29 -08002046 g = node.append('g')
2047 .attr('class', 'glyphIcon deviceIcon')
Simon Huntc72967b2014-11-20 09:21:42 -08002048 .attr('transform', translate(dx, dy));
2049
2050 g.append('rect').attr({
2051 x: 0,
2052 y: 0,
2053 rx: cfg.rx,
2054 width: cfg.dim,
2055 height: cfg.dim
2056 });
2057
2058 g.append('use').attr({
2059 'xlink:href': iid,
2060 width: cfg.dim,
2061 height: cfg.dim
2062 });
2063
Simon Huntc72967b2014-11-20 09:21:42 -08002064 }
2065
Simon Hunt7b403bc2014-11-22 19:01:00 -08002066 function find(key, array, tag) {
Simon Huntca867ac2014-11-28 18:07:35 -08002067 var _tag = tag || 'id',
Simon Hunt7b403bc2014-11-22 19:01:00 -08002068 idx, n, d;
2069 for (idx = 0, n = array.length; idx < n; idx++) {
2070 d = array[idx];
2071 if (d[_tag] === key) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002072 return idx;
2073 }
2074 }
2075 return -1;
2076 }
2077
Simon Huntca867ac2014-11-28 18:07:35 -08002078 function removeLinkElement(d) {
2079 var idx = find(d.key, network.links, 'key'),
Simon Hunt8257f4c2014-11-16 19:34:54 -08002080 removed;
2081 if (idx >=0) {
2082 // remove from links array
2083 removed = network.links.splice(idx, 1);
2084 // remove from lookup cache
2085 delete network.lookup[removed[0].key];
2086 updateLinks();
2087 network.force.resume();
2088 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002089 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002090
Simon Huntca867ac2014-11-28 18:07:35 -08002091 function removeHostElement(d, upd) {
2092 var lu = network.lookup;
Simon Hunt44031102014-11-11 13:20:36 -08002093 // first, remove associated hostLink...
Simon Huntca867ac2014-11-28 18:07:35 -08002094 removeLinkElement(d.linkData);
2095
2096 // remove hostLink bindings
2097 delete lu[d.ingress];
2098 delete lu[d.egress];
Simon Hunt44031102014-11-11 13:20:36 -08002099
2100 // remove from lookup cache
Simon Huntca867ac2014-11-28 18:07:35 -08002101 delete lu[d.id];
Simon Hunt44031102014-11-11 13:20:36 -08002102 // remove from nodes array
Simon Huntca867ac2014-11-28 18:07:35 -08002103 var idx = find(d.id, network.nodes);
2104 network.nodes.splice(idx, 1);
2105 // remove from SVG
2106 // NOTE: upd is false if we were called from removeDeviceElement()
2107 if (upd) {
2108 updateNodes();
2109 network.force.resume();
2110 }
2111 }
2112
2113
2114 function removeDeviceElement(d) {
2115 var id = d.id;
2116 // first, remove associated hosts and links..
2117 findAttachedHosts(id).forEach(removeHostElement);
2118 findAttachedLinks(id).forEach(removeLinkElement);
2119
2120 // remove from lookup cache
2121 delete network.lookup[id];
2122 // remove from nodes array
2123 var idx = find(id, network.nodes);
Simon Hunt44031102014-11-11 13:20:36 -08002124 network.nodes.splice(idx, 1);
2125 // remove from SVG
2126 updateNodes();
2127 network.force.resume();
2128 }
2129
Simon Huntca867ac2014-11-28 18:07:35 -08002130 function findAttachedHosts(devId) {
2131 var hosts = [];
2132 network.nodes.forEach(function (d) {
2133 if (d.class === 'host' && d.cp.device === devId) {
2134 hosts.push(d);
2135 }
2136 });
2137 return hosts;
2138 }
2139
2140 function findAttachedLinks(devId) {
2141 var links = [];
2142 network.links.forEach(function (d) {
2143 if (d.source.id === devId || d.target.id === devId) {
2144 links.push(d);
2145 }
2146 });
2147 return links;
2148 }
Simon Hunt44031102014-11-11 13:20:36 -08002149
Simon Huntc7ee0662014-11-05 16:44:37 -08002150 function tick() {
2151 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -08002152 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -08002153 });
2154
2155 link.attr({
2156 x1: function (d) { return d.source.x; },
2157 y1: function (d) { return d.source.y; },
2158 x2: function (d) { return d.target.x; },
2159 y2: function (d) { return d.target.y; }
2160 });
Simon Hunte2575b62014-11-18 15:25:53 -08002161
2162 linkLabel.each(function (d) {
2163 var el = d3.select(this);
Thomas Vachuska4731f122014-11-20 04:56:19 -08002164 var lnk = findLinkById(d.key);
2165
2166 if (lnk) {
2167 var parms = {
Simon Hunte2575b62014-11-18 15:25:53 -08002168 x1: lnk.source.x,
2169 y1: lnk.source.y,
2170 x2: lnk.target.x,
2171 y2: lnk.target.y
2172 };
Thomas Vachuska4731f122014-11-20 04:56:19 -08002173 el.attr('transform', transformLabel(parms));
2174 }
Simon Hunte2575b62014-11-18 15:25:53 -08002175 });
Simon Huntc7ee0662014-11-05 16:44:37 -08002176 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08002177
2178 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002179 // Web-Socket for live data
2180
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002181 function findGuiSuccessor() {
2182 var idx = -1;
2183 onosOrder.forEach(function (d, i) {
2184 if (d.uiAttached) {
2185 idx = i;
2186 }
2187 });
2188
2189 for (var i = 0; i < onosOrder.length - 1; i++) {
2190 var ni = (idx + 1 + i) % onosOrder.length;
2191 if (onosOrder[ni].online) {
2192 return onosOrder[ni].ip;
2193 }
2194 }
2195 return null;
2196 }
2197
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002198 function webSockUrl() {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002199 var url = document.location.toString()
2200 .replace(/\#.*/, '')
2201 .replace('http://', 'ws://')
2202 .replace('https://', 'wss://')
2203 .replace('index.html', config.webSockUrl);
2204 if (guiSuccessor) {
2205 url = url.replace(location.hostname, guiSuccessor);
2206 }
2207 return url;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002208 }
2209
2210 webSock = {
2211 ws : null,
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002212 retries: 0,
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002213
2214 connect : function() {
2215 webSock.ws = new WebSocket(webSockUrl());
2216
2217 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08002218 noWebSock(false);
Thomas Vachuska47635c62014-11-22 01:21:36 -08002219 requestSummary();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002220 showInstances();
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002221 webSock.retries = 0;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002222 };
2223
2224 webSock.ws.onmessage = function(m) {
2225 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002226 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08002227 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002228 }
2229 };
2230
2231 webSock.ws.onclose = function(m) {
2232 webSock.ws = null;
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002233 guiSuccessor = findGuiSuccessor();
2234 if (guiSuccessor && webSock.retries < onosOrder.length) {
2235 webSock.retries++;
2236 webSock.connect();
2237 } else {
2238 noWebSock(true);
2239 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002240 };
2241 },
2242
2243 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002244 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002245 webSock._send(text);
2246 }
2247 },
2248
2249 _send : function(message) {
2250 if (webSock.ws) {
2251 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002252 } else if (config.useLiveData) {
Simon Hunt434cf142014-11-24 11:10:28 -08002253 console.warn('no web socket open', message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002254 } else {
Simon Hunt434cf142014-11-24 11:10:28 -08002255 console.log('WS Send: ', message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002256 }
2257 }
2258
2259 };
2260
Simon Hunt0c6d4192014-11-12 12:07:10 -08002261 function noWebSock(b) {
2262 mask.style('display',b ? 'block' : 'none');
2263 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002264
2265 function sendMessage(evType, payload) {
Simon Huntc1cc81c2014-11-29 14:59:01 -08002266 var p = payload || {},
2267 toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08002268 event: evType,
2269 sid: ++sid,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002270 payload: p
Simon Huntbb282f52014-11-10 11:08:19 -08002271 },
2272 asText = JSON.stringify(toSend);
2273 wsTraceTx(asText);
2274 webSock.send(asText);
Simon Huntc76ae892014-11-18 17:31:51 -08002275
2276 // Temporary measure for debugging UI behavior ...
2277 if (!config.useLiveData) {
2278 handleTestSend(toSend);
2279 }
Simon Huntbb282f52014-11-10 11:08:19 -08002280 }
2281
2282 function wsTraceTx(msg) {
2283 wsTrace('tx', msg);
2284 }
2285 function wsTraceRx(msg) {
2286 wsTrace('rx', msg);
2287 }
2288 function wsTrace(rxtx, msg) {
Simon Huntbb282f52014-11-10 11:08:19 -08002289 console.log('[' + rxtx + '] ' + msg);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002290 }
2291
Simon Huntc76ae892014-11-18 17:31:51 -08002292 // NOTE: Temporary hardcoded example for showing detail pane
2293 // while we fine-
2294 // Probably should not merge this change...
2295 function handleTestSend(msg) {
2296 if (msg.event === 'requestDetails') {
2297 showDetails({
2298 event: 'showDetails',
2299 sid: 1001,
2300 payload: {
2301 "id": "of:0000ffffffffff09",
2302 "type": "roadm",
2303 "propOrder": [
2304 "Name",
2305 "Vendor",
2306 "H/W Version",
2307 "S/W Version",
2308 "-",
2309 "Latitude",
2310 "Longitude",
2311 "Ports"
2312 ],
2313 "props": {
2314 "Name": null,
2315 "Vendor": "Linc",
2316 "H/W Version": "OE",
2317 "S/W Version": "?",
2318 "-": "",
2319 "Latitude": "40.8",
2320 "Longitude": "73.1",
2321 "Ports": "2"
2322 }
2323 }
2324 });
2325 }
2326 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002327
2328 // ==============================
2329 // Selection stuff
2330
2331 function selectObject(obj, el) {
2332 var n,
Simon Hunt01095ff2014-11-13 16:37:29 -08002333 srcEv = d3.event.sourceEvent,
2334 meta = srcEv.metaKey,
2335 shift = srcEv.shiftKey;
2336
Simon Hunt3c5ca542014-11-29 14:11:43 -08002337 // if the meta key is pressed, we are panning/zooming, so ignore
2338 if (meta) {
Simon Hunt01095ff2014-11-13 16:37:29 -08002339 return;
2340 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002341
2342 if (el) {
2343 n = d3.select(el);
2344 } else {
2345 node.each(function(d) {
2346 if (d == obj) {
2347 n = d3.select(el = this);
2348 }
2349 });
2350 }
2351 if (!n) return;
2352
Simon Hunt01095ff2014-11-13 16:37:29 -08002353 if (shift && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002354 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002355 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002356 return;
2357 }
2358
Simon Hunt01095ff2014-11-13 16:37:29 -08002359 if (!shift) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002360 deselectAll();
2361 }
2362
Simon Huntc31d5692014-11-12 13:27:18 -08002363 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002364 selectOrder.push(obj.id);
2365
2366 n.classed('selected', true);
Simon Huntb0ecfa52014-11-23 21:05:12 -08002367 updateDeviceColors(obj);
Simon Hunt61d04042014-11-11 17:27:16 -08002368 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002369 }
2370
2371 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08002372 var obj = selections[id],
2373 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002374 if (obj) {
2375 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08002376 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08002377 idx = $.inArray(id, selectOrder);
2378 if (idx >= 0) {
2379 selectOrder.splice(idx, 1);
2380 }
Simon Huntb0ecfa52014-11-23 21:05:12 -08002381 updateDeviceColors(obj.obj);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002382 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002383 }
2384
2385 function deselectAll() {
2386 // deselect all nodes in the network...
2387 node.classed('selected', false);
2388 selections = {};
2389 selectOrder = [];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002390 updateDeviceColors();
Simon Hunt61d04042014-11-11 17:27:16 -08002391 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002392 }
2393
Simon Huntb0ecfa52014-11-23 21:05:12 -08002394 function updateDeviceColors(d) {
2395 if (d) {
2396 setDeviceColor(d);
2397 } else {
2398 node.filter('.device').each(function (d) {
2399 setDeviceColor(d);
2400 });
2401 }
Thomas Vachuska47635c62014-11-22 01:21:36 -08002402 }
2403
Simon Hunt61d04042014-11-11 17:27:16 -08002404 // update the state of the detail pane, based on current selections
2405 function updateDetailPane() {
2406 var nSel = selectOrder.length;
2407 if (!nSel) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002408 emptySelect();
Simon Hunt61d04042014-11-11 17:27:16 -08002409 } else if (nSel === 1) {
2410 singleSelect();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002411 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002412 } else {
2413 multiSelect();
2414 }
2415 }
2416
Thomas Vachuska9edca302014-11-22 17:06:42 -08002417 function emptySelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002418 haveDetails = false;
Simon Hunt06811b72014-11-25 18:54:48 -08002419 hideDetailPane();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002420 cancelTraffic();
2421 }
2422
Simon Hunt61d04042014-11-11 17:27:16 -08002423 function singleSelect() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002424 // NOTE: detail is shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08002425 requestDetails();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002426 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002427 }
2428
2429 function multiSelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002430 haveDetails = true;
Simon Huntb53e0682014-11-12 13:32:01 -08002431 populateMultiSelect();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002432 requestTrafficForMode();
Simon Huntb53e0682014-11-12 13:32:01 -08002433 }
2434
2435 function addSep(tbody) {
2436 var tr = tbody.append('tr');
2437 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
2438 }
2439
2440 function addProp(tbody, label, value) {
2441 var tr = tbody.append('tr');
2442
2443 tr.append('td')
2444 .attr('class', 'label')
2445 .text(label + ' :');
2446
2447 tr.append('td')
2448 .attr('class', 'value')
2449 .text(value);
2450 }
2451
2452 function populateMultiSelect() {
2453 detailPane.empty();
2454
Simon Hunta3dd9572014-11-20 15:22:41 -08002455 var title = detailPane.append('h3'),
2456 table = detailPane.append('table'),
2457 tbody = table.append('tbody');
Simon Huntb53e0682014-11-12 13:32:01 -08002458
Thomas Vachuska4731f122014-11-20 04:56:19 -08002459 title.text('Selected Nodes');
Simon Huntb53e0682014-11-12 13:32:01 -08002460
2461 selectOrder.forEach(function (d, i) {
2462 addProp(tbody, i+1, d);
2463 });
Simon Huntd72bc702014-11-13 18:38:04 -08002464
2465 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08002466 }
2467
Thomas Vachuska47635c62014-11-22 01:21:36 -08002468 // TODO: refactor to consolidate with populateDetails
2469 function populateSummary(data) {
2470 summaryPane.empty();
2471
2472 var svg = summaryPane.append('svg'),
2473 iid = iconGlyphUrl(data);
2474
2475 var title = summaryPane.append('h2'),
2476 table = summaryPane.append('table'),
2477 tbody = table.append('tbody');
2478
2479 appendGlyph(svg, 0, 0, 40, iid);
2480
2481 svg.append('use')
2482 .attr({
2483 class: 'birdBadge',
2484 transform: translate(8,12),
2485 'xlink:href': '#bird',
2486 width: 24,
2487 height: 24,
2488 fill: '#fff'
2489 });
2490
2491 title.text('ONOS Summary');
2492
2493 data.propOrder.forEach(function(p) {
2494 if (p === '-') {
2495 addSep(tbody);
2496 } else {
2497 addProp(tbody, p, data.props[p]);
2498 }
2499 });
2500 }
2501
Simon Hunt61d04042014-11-11 17:27:16 -08002502 function populateDetails(data) {
2503 detailPane.empty();
2504
Simon Hunta6a9fe72014-11-20 11:17:12 -08002505 var svg = detailPane.append('svg'),
2506 iid = iconGlyphUrl(data);
2507
Simon Hunta3dd9572014-11-20 15:22:41 -08002508 var title = detailPane.append('h2'),
2509 table = detailPane.append('table'),
2510 tbody = table.append('tbody');
Simon Hunt61d04042014-11-11 17:27:16 -08002511
Simon Hunta6a9fe72014-11-20 11:17:12 -08002512 appendGlyph(svg, 0, 0, 40, iid);
2513 title.text(data.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002514
2515 data.propOrder.forEach(function(p) {
2516 if (p === '-') {
2517 addSep(tbody);
2518 } else {
2519 addProp(tbody, p, data.props[p]);
2520 }
2521 });
Simon Huntd72bc702014-11-13 18:38:04 -08002522
Thomas Vachuska4731f122014-11-20 04:56:19 -08002523 addSingleSelectActions(data);
Simon Hunt61d04042014-11-11 17:27:16 -08002524 }
2525
Thomas Vachuska4731f122014-11-20 04:56:19 -08002526 function addSingleSelectActions(data) {
Simon Huntd72bc702014-11-13 18:38:04 -08002527 detailPane.append('hr');
2528 // always want to allow 'show traffic'
Thomas Vachuska4731f122014-11-20 04:56:19 -08002529 addAction(detailPane, 'Show Related Traffic', showTrafficAction);
2530
2531 if (data.type === 'switch') {
2532 addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
2533 }
Simon Huntd72bc702014-11-13 18:38:04 -08002534 }
2535
2536 function addMultiSelectActions() {
2537 detailPane.append('hr');
2538 // always want to allow 'show traffic'
Thomas Vachuska4731f122014-11-20 04:56:19 -08002539 addAction(detailPane, 'Show Related Traffic', showTrafficAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002540 // if exactly two hosts are selected, also want 'add host intent'
2541 if (nSel() === 2 && allSelectionsClass('host')) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002542 addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2543 } else if (nSel() >= 2 && allSelectionsClass('host')) {
2544 addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002545 }
2546 }
2547
Simon Hunta5e89142014-11-14 07:00:33 -08002548 function addAction(panel, text, cb) {
2549 panel.append('div')
Simon Huntd72bc702014-11-13 18:38:04 -08002550 .classed('actionBtn', true)
2551 .text(text)
2552 .on('click', cb);
2553 }
2554
2555
Simon Hunt3c5ca542014-11-29 14:11:43 -08002556 // === Pan and Zoom behaviors...
2557
2558 function panZoom(translate, scale) {
2559 panZoomContainer.attr('transform',
2560 'translate(' + translate + ')scale(' + scale + ')');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002561 // keep the map lines constant width while zooming
Simon Hunt3c5ca542014-11-29 14:11:43 -08002562 bgImg.style('stroke-width', 2.0 / scale + 'px');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002563 }
2564
Simon Hunt3c5ca542014-11-29 14:11:43 -08002565 function resetPanZoom() {
2566 panZoom([0,0], 1);
2567 zoom.translate([0,0]).scale(1);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002568 }
2569
Simon Hunt3c5ca542014-11-29 14:11:43 -08002570 function setupPanZoom() {
Paul Greysonfcba0e82014-11-13 10:21:16 -08002571 function zoomed() {
Simon Hunt3c5ca542014-11-29 14:11:43 -08002572 // pan zoom active when meta key is pressed...
2573 if (d3.event.sourceEvent.metaKey) {
2574 panZoom(d3.event.translate, d3.event.scale);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002575 }
2576 }
2577
2578 zoom = d3.behavior.zoom()
2579 .translate([0, 0])
2580 .scale(1)
2581 .scaleExtent([1, 8])
2582 .on("zoom", zoomed);
2583
2584 svg.call(zoom);
2585 }
2586
Simon Hunt61d04042014-11-11 17:27:16 -08002587 // ==============================
2588 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08002589
2590 function prepareScenario(view, ctx, dbg) {
2591 var sc = scenario,
2592 urlSc = sc.evDir + ctx + sc.evScenario;
2593
2594 if (!ctx) {
2595 view.alert("No scenario specified (null ctx)");
2596 return;
2597 }
2598
2599 sc.view = view;
2600 sc.ctx = ctx;
2601 sc.debug = dbg;
2602 sc.evNumber = 0;
2603
2604 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002605 var p = data && data.params || {},
2606 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08002607 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08002608
Simon Hunt56d51852014-11-09 13:03:35 -08002609 if (err) {
2610 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
2611 } else {
2612 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08002613 if (desc) {
2614 intro += '\n\n ' + desc.join('\n ');
2615 }
2616 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08002617 }
2618 });
2619
2620 }
2621
Simon Hunt7fa116d2014-11-17 14:16:55 -08002622 function loadGlyphs(svg) {
2623 var defs = svg.append('defs');
2624 gly.defBird(defs);
Simon Huntc72967b2014-11-20 09:21:42 -08002625 gly.defGlyphs(defs);
Simon Huntb82f6902014-11-22 11:53:15 -08002626 gly.defBadges(defs);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002627 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002628
Simon Hunt395a70c2014-11-22 23:17:40 -08002629 function sendUpdateMeta(d, store) {
2630 var metaUi = {},
2631 ll;
2632
2633 if (store) {
2634 ll = geoMapProjection.invert([d.x, d.y]);
Simon Hunt62c47542014-11-22 22:16:32 -08002635 metaUi = {
2636 x: d.x,
2637 y: d.y,
2638 lng: ll[0],
2639 lat: ll[1]
2640 };
Simon Hunt395a70c2014-11-22 23:17:40 -08002641 }
Simon Hunt62c47542014-11-22 22:16:32 -08002642 d.metaUi = metaUi;
2643 sendMessage('updateMeta', {
2644 id: d.id,
2645 'class': d.class,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002646 memento: metaUi
Simon Hunt62c47542014-11-22 22:16:32 -08002647 });
2648 }
2649
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002650 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08002651 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08002652
Simon Huntf67722a2014-11-10 09:32:06 -08002653 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08002654 var w = view.width(),
2655 h = view.height(),
Simon Huntc7ee0662014-11-05 16:44:37 -08002656 fcfg = config.force,
2657 fpad = fcfg.pad,
2658 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08002659
Simon Hunt142d0032014-11-04 20:13:09 -08002660 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002661 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
2662 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002663 setSize(svg, view);
2664
Simon Hunt6e18fe32014-11-29 13:35:41 -08002665 // load glyphs and filters...
Simon Hunt7fa116d2014-11-17 14:16:55 -08002666 loadGlyphs(svg);
Simon Hunt6e18fe32014-11-29 13:35:41 -08002667 d3u.appendGlow(svg);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002668
Simon Hunt3c5ca542014-11-29 14:11:43 -08002669 panZoomContainer = svg.append('g').attr('id', 'panZoomContainer');
2670 setupPanZoom();
Paul Greysonfcba0e82014-11-13 10:21:16 -08002671
Simon Huntc7ee0662014-11-05 16:44:37 -08002672 // group for the topology
Simon Hunt3c5ca542014-11-29 14:11:43 -08002673 topoG = panZoomContainer.append('g')
Simon Huntd3b7d512014-11-12 15:48:41 -08002674 .attr('id', 'topo-G')
Simon Huntc7ee0662014-11-05 16:44:37 -08002675 .attr('transform', fcfg.translate());
2676
Simon Hunte2575b62014-11-18 15:25:53 -08002677 // subgroups for links, link labels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002678 linkG = topoG.append('g').attr('id', 'links');
Simon Hunte2575b62014-11-18 15:25:53 -08002679 linkLabelG = topoG.append('g').attr('id', 'linkLabels');
Simon Huntc7ee0662014-11-05 16:44:37 -08002680 nodeG = topoG.append('g').attr('id', 'nodes');
2681
Simon Hunte2575b62014-11-18 15:25:53 -08002682 // selection of links, linkLabels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002683 link = linkG.selectAll('.link');
Simon Hunte2575b62014-11-18 15:25:53 -08002684 linkLabel = linkLabelG.selectAll('.linkLabel');
Simon Huntc7ee0662014-11-05 16:44:37 -08002685 node = nodeG.selectAll('.node');
2686
Simon Hunt7cd48f32014-11-09 23:42:50 -08002687 function chrg(d) {
2688 return fcfg.charge[d.class] || -12000;
2689 }
Simon Hunt99c13842014-11-06 18:23:12 -08002690 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002691 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08002692 }
2693 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002694 // 0.0 - 1.0
2695 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08002696 }
2697
Simon Hunt1a9eff92014-11-07 11:06:34 -08002698 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002699 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002700 }
2701
2702 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08002703 // once we've finished moving, pin the node in position
2704 d.fixed = true;
2705 d3.select(self).classed('fixed', true);
2706 if (config.useLiveData) {
Simon Hunt395a70c2014-11-22 23:17:40 -08002707 sendUpdateMeta(d, true);
Simon Hunta255a2c2014-11-13 22:29:35 -08002708 } else {
2709 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08002710 }
2711 }
2712
Simon Hunt6e18fe32014-11-29 13:35:41 -08002713 // predicate that indicates when dragging is active
2714 function dragEnabled() {
2715 // meta key pressed means we are zooming/panning (so disable drag)
2716 return dragAllowed && !d3.event.sourceEvent.metaKey;
2717 // dragAllowed will be set false when we are in oblique view
2718 // or when we 'lock' node positions
2719 }
2720
Simon Huntc7ee0662014-11-05 16:44:37 -08002721 // set up the force layout
2722 network.force = d3.layout.force()
2723 .size(forceDim)
2724 .nodes(network.nodes)
2725 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002726 .gravity(0.4)
2727 .friction(0.7)
2728 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08002729 .linkDistance(ldist)
2730 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08002731 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08002732
Simon Hunt01095ff2014-11-13 16:37:29 -08002733 network.drag = d3u.createDragBehavior(network.force,
Simon Hunt6e18fe32014-11-29 13:35:41 -08002734 selectCb, atDragEnd, dragEnabled);
2735
Simon Hunt0c6d4192014-11-12 12:07:10 -08002736
2737 // create mask layer for when we lose connection to server.
Simon Hunta5e89142014-11-14 07:00:33 -08002738 // TODO: this should be part of the framework
Simon Hunt6e18fe32014-11-29 13:35:41 -08002739
2740 function para(sel, text) {
2741 sel.append('p').text(text);
2742 }
2743
Simon Hunt0c6d4192014-11-12 12:07:10 -08002744 mask = view.$div.append('div').attr('id','topo-mask');
2745 para(mask, 'Oops!');
2746 para(mask, 'Web-socket connection to server closed...');
2747 para(mask, 'Try refreshing the page.');
Simon Hunt12ce12e2014-11-15 21:13:19 -08002748
2749 mask.append('svg')
2750 .attr({
2751 id: 'mask-bird',
2752 width: w,
2753 height: h
2754 })
2755 .append('g')
2756 .attr('transform', birdTranslate(w, h))
2757 .style('opacity', 0.3)
2758 .append('use')
2759 .attr({
2760 'xlink:href': '#bird',
2761 width: config.birdDim,
2762 height: config.birdDim,
2763 fill: '#111'
Thomas Vachuska89543292014-11-19 11:28:33 -08002764 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002765 }
Simon Hunt195cb382014-11-03 17:50:51 -08002766
Simon Hunt01095ff2014-11-13 16:37:29 -08002767
Simon Hunt56d51852014-11-09 13:03:35 -08002768 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08002769 // resize, in case the window was resized while we were not loaded
2770 resize(view, ctx, flags);
2771
Simon Hunt99c13842014-11-06 18:23:12 -08002772 // cache the view token, so network topo functions can access it
2773 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08002774 config.useLiveData = !flags.local;
2775
2776 if (!config.useLiveData) {
2777 prepareScenario(view, ctx, flags.debug);
2778 }
Simon Hunt99c13842014-11-06 18:23:12 -08002779
2780 // set our radio buttons and key bindings
Simon Hunt9462e8c2014-11-14 17:28:09 -08002781 layerBtnSet = view.setRadio(layerButtons);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002782 view.setKeys(keyDispatch);
Simon Hunt87514342014-11-24 16:41:27 -08002783 view.setGestures(gestures);
Simon Hunt195cb382014-11-03 17:50:51 -08002784
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002785 // patch in our "button bar" for now
2786 // TODO: implement a more official frameworky way of doing this..
Simon Hunt87514342014-11-24 16:41:27 -08002787 //addButtonBar(view);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08002788
Simon Huntd3b7d512014-11-12 15:48:41 -08002789 // Load map data asynchronously; complete startup after that..
2790 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08002791 }
2792
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002793 function startAntTimer() {
Thomas Vachuska4731f122014-11-20 04:56:19 -08002794 if (!antTimer) {
2795 var pulses = [5, 3, 1.2, 3],
2796 pulse = 0;
2797 antTimer = setInterval(function () {
2798 pulse = pulse + 1;
2799 pulse = pulse === pulses.length ? 0 : pulse;
2800 d3.selectAll('.animated').style('stroke-width', pulses[pulse]);
2801 }, 200);
2802 }
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002803 }
2804
2805 function stopAntTimer() {
Simon Hunta255a2c2014-11-13 22:29:35 -08002806 if (antTimer) {
2807 clearInterval(antTimer);
2808 antTimer = null;
2809 }
Simon Huntd3b7d512014-11-12 15:48:41 -08002810 }
2811
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002812 function unload(view, ctx, flags) {
2813 stopAntTimer();
2814 }
2815
Simon Huntd3b7d512014-11-12 15:48:41 -08002816 // TODO: move these to config/state portion of script
Simon Hunta6a9fe72014-11-20 11:17:12 -08002817 var geoJsonUrl = 'json/map/continental_us.json',
Simon Huntd3b7d512014-11-12 15:48:41 -08002818 geoJson;
2819
2820 function loadGeoJsonData() {
2821 d3.json(geoJsonUrl, function (err, data) {
2822 if (err) {
2823 // fall back to USA map background
2824 loadStaticMap();
2825 } else {
2826 geoJson = data;
2827 loadGeoMap();
2828 }
2829
2830 // finally, connect to the server...
2831 if (config.useLiveData) {
2832 webSock.connect();
2833 }
2834 });
2835 }
2836
2837 function showBg() {
Simon Hunt434cf142014-11-24 11:10:28 -08002838 return visVal(config.options.showBackground);
Simon Huntd3b7d512014-11-12 15:48:41 -08002839 }
2840
2841 function loadStaticMap() {
2842 fnTrace('loadStaticMap', config.backgroundUrl);
2843 var w = network.view.width(),
2844 h = network.view.height();
2845
2846 // load the background image
2847 bgImg = svg.insert('svg:image', '#topo-G')
2848 .attr({
2849 id: 'topo-bg',
2850 width: w,
2851 height: h,
2852 'xlink:href': config.backgroundUrl
2853 })
2854 .style({
2855 visibility: showBg()
2856 });
2857 }
2858
2859 function loadGeoMap() {
2860 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08002861
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002862 // extracts the topojson data into geocoordinate-based geometry
2863 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08002864
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002865 // see: http://bl.ocks.org/mbostock/4707858
2866 geoMapProjection = d3.geo.mercator();
2867 var path = d3.geo.path().projection(geoMapProjection);
Simon Huntd3b7d512014-11-12 15:48:41 -08002868
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002869 geoMapProjection
2870 .scale(1)
2871 .translate([0, 0]);
Simon Huntd3b7d512014-11-12 15:48:41 -08002872
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002873 // [[x1,y1],[x2,y2]]
2874 var b = path.bounds(topoData);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002875 // size map to 95% of minimum dimension to fill space
2876 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 -08002877 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 -08002878
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002879 geoMapProjection
2880 .scale(s)
2881 .translate(t);
2882
Simon Hunt3c5ca542014-11-29 14:11:43 -08002883 bgImg = panZoomContainer.insert("g", '#topo-G');
Thomas Vachuska89543292014-11-19 11:28:33 -08002884 bgImg.attr('id', 'map').selectAll('path')
2885 .data(topoData.features)
2886 .enter()
2887 .append('path')
2888 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08002889 }
2890
Simon Huntf67722a2014-11-10 09:32:06 -08002891 function resize(view, ctx, flags) {
Simon Hunt12ce12e2014-11-15 21:13:19 -08002892 var w = view.width(),
2893 h = view.height();
2894
Simon Hunt934c3ce2014-11-05 11:45:07 -08002895 setSize(svg, view);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002896
2897 d3.select('#mask-bird').attr({ width: w, height: h})
2898 .select('g').attr('transform', birdTranslate(w, h));
Simon Hunt142d0032014-11-04 20:13:09 -08002899 }
2900
Simon Hunt8f40cce2014-11-23 15:57:30 -08002901 function theme(view, ctx, flags) {
2902 updateInstances();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002903 updateDeviceColors();
Simon Hunt8f40cce2014-11-23 15:57:30 -08002904 }
2905
Simon Hunt12ce12e2014-11-15 21:13:19 -08002906 function birdTranslate(w, h) {
2907 var bdim = config.birdDim;
2908 return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
2909 }
Simon Hunt142d0032014-11-04 20:13:09 -08002910
Simon Hunt06811b72014-11-25 18:54:48 -08002911 function isF(f) { return $.isFunction(f) ? f : null; }
2912 function noop() {}
2913
2914 function augmentDetailPane() {
2915 var dp = detailPane;
2916 dp.ypos = { up: 64, down: 320, current: 320};
2917
2918 dp._move = function (y, cb) {
2919 var endCb = isF(cb) || noop,
2920 yp = dp.ypos;
2921 if (yp.current !== y) {
2922 yp.current = y;
2923 dp.el.transition().duration(300)
2924 .each('end', endCb)
2925 .style('top', yp.current + 'px');
2926 } else {
2927 endCb();
2928 }
2929 };
2930
2931 dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
2932 dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
2933 }
2934
Simon Hunt142d0032014-11-04 20:13:09 -08002935 // ==============================
2936 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08002937
Simon Hunt25248912014-11-04 11:25:48 -08002938 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08002939 preload: preload,
2940 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08002941 unload: unload,
Simon Hunt8f40cce2014-11-23 15:57:30 -08002942 resize: resize,
2943 theme: theme
Simon Hunt195cb382014-11-03 17:50:51 -08002944 });
2945
Thomas Vachuska47635c62014-11-22 01:21:36 -08002946 summaryPane = onos.ui.addFloatingPanel('topo-summary');
Simon Hunt61d04042014-11-11 17:27:16 -08002947 detailPane = onos.ui.addFloatingPanel('topo-detail');
Simon Hunt06811b72014-11-25 18:54:48 -08002948 augmentDetailPane();
Simon Hunta5e89142014-11-14 07:00:33 -08002949 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
Simon Huntb82f6902014-11-22 11:53:15 -08002950 oiBox.width(20);
Simon Hunt61d04042014-11-11 17:27:16 -08002951
Simon Hunt195cb382014-11-03 17:50:51 -08002952}(ONOS));