blob: 32cc891e6b9f031c07c998029645277f8abcf2dc [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 Hunt195cb382014-11-03 17:50:51 -0800111 }
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800112 },
113 // see below in creation of viewBox on main svg
114 logicalSize: 1000
Simon Hunt195cb382014-11-03 17:50:51 -0800115 };
116
Simon Hunt142d0032014-11-04 20:13:09 -0800117 // radio buttons
Simon Hunt9462e8c2014-11-14 17:28:09 -0800118 var layerButtons = [
119 { text: 'All Layers', id: 'all', cb: showAllLayers },
120 { text: 'Packet Only', id: 'pkt', cb: showPacketLayer },
121 { text: 'Optical Only', id: 'opt', cb: showOpticalLayer }
122 ],
123 layerBtnSet,
124 layerBtnDispatch = {
125 all: showAllLayers,
126 pkt: showPacketLayer,
127 opt: showOpticalLayer
128 };
Simon Hunt934c3ce2014-11-05 11:45:07 -0800129
130 // key bindings
131 var keyDispatch = {
Simon Hunt988c6fc2014-11-20 17:43:03 -0800132 // TODO: remove these "development only" bindings
Simon Hunt8f40cce2014-11-23 15:57:30 -0800133 0: testMe,
134 equals: injectStartupEvents,
135 dash: injectTestEvent,
Simon Hunt99c13842014-11-06 18:23:12 -0800136
Thomas Vachuska47635c62014-11-22 01:21:36 -0800137 O: [toggleSummary, 'Toggle ONOS summary pane'],
138 I: [toggleInstances, 'Toggle ONOS instances pane'],
Simon Hunt27d322d2014-11-28 10:45:43 -0800139 D: [toggleDetails, 'Disable / enable details pane'],
Simon Hunta1162d82014-12-03 14:34:43 -0800140
Simon Hunt434cf142014-11-24 11:10:28 -0800141 H: [toggleHosts, 'Toggle host visibility'],
Simon Hunt6d9bd032014-11-28 22:16:40 -0800142 M: [toggleOffline, 'Toggle offline visibility'],
Simon Hunta1162d82014-12-03 14:34:43 -0800143 B: [toggleBg, 'Toggle background image'],
Simon Hunt934c3ce2014-11-05 11:45:07 -0800144 P: togglePorts,
Simon Hunta1162d82014-12-03 14:34:43 -0800145
146 X: [toggleNodeLock, 'Lock / unlock node positions'],
147 Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
148 L: [cycleLabels, 'Cycle device labels'],
Simon Hunt87514342014-11-24 16:41:27 -0800149 U: [unpin, 'Unpin node (hover mouse over)'],
Simon Hunt3c5ca542014-11-29 14:11:43 -0800150 R: [resetPanZoom, 'Reset pan / zoom'],
Simon Hunta1162d82014-12-03 14:34:43 -0800151
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800152 V: [showRelatedIntentsAction, 'Show all related intents'],
Thomas Vachuskab7e40642014-12-03 11:16:06 -0800153 rightArrow: [showNextIntentAction, 'Show next related intent'],
154 leftArrow: [showPrevIntentAction, 'Show previous related intent'],
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800155 W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
156 A: [showAllTrafficAction, 'Monitor all traffic'],
Simon Hunt56ef0fe2014-11-21 08:24:43 -0800157 F: [showDeviceLinkFlowsAction, 'Show device link flows'],
Simon Hunta1162d82014-12-03 14:34:43 -0800158
159 E: [equalizeMasters, 'Equalize mastership roles'],
160
161 esc: handleEscape,
162
163 _helpFormat: [
164 ['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
165 ['X', 'Z', 'L', 'U', 'R' ],
166 ['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
167 ]
Simon Hunt934c3ce2014-11-05 11:45:07 -0800168 };
Simon Hunt142d0032014-11-04 20:13:09 -0800169
Simon Hunt87514342014-11-24 16:41:27 -0800170 // mouse gestures
171 var gestures = [
172 ['click', 'Select the item and show details'],
173 ['shift-click', 'Toggle selection state'],
174 ['drag', 'Reposition (and pin) device / host'],
175 ['cmd-scroll', 'Zoom in / out'],
176 ['cmd-drag', 'Pan']
177 ];
178
Simon Hunt195cb382014-11-03 17:50:51 -0800179 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800180 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800181 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800182 nodes: [],
183 links: [],
Simon Hunt269670f2014-11-17 16:17:43 -0800184 lookup: {},
185 revLinkToKey: {}
Simon Hunt99c13842014-11-06 18:23:12 -0800186 },
Simon Hunt56d51852014-11-09 13:03:35 -0800187 scenario = {
188 evDir: 'json/ev/',
189 evScenario: '/scenario.json',
190 evPrefix: '/ev_',
191 evOnos: '_onos.json',
192 evUi: '_ui.json',
193 ctx: null,
194 params: {},
195 evNumber: 0,
Simon Hunt434cf142014-11-24 11:10:28 -0800196 view: null
Simon Hunt56d51852014-11-09 13:03:35 -0800197 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800198 webSock,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800199 sid = 0,
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800200 deviceLabelCount = 3,
Simon Hunt209155e2014-11-21 12:16:09 -0800201 hostLabelCount = 2,
Simon Hunt56d51852014-11-09 13:03:35 -0800202 deviceLabelIndex = 0,
203 hostLabelIndex = 0,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800204 selections = {},
Simon Hunta5e89142014-11-14 07:00:33 -0800205 selectOrder = [],
Simon Hunt6ac93f32014-11-13 12:17:27 -0800206 hovered = null,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800207 summaryPane,
Simon Hunta5e89142014-11-14 07:00:33 -0800208 detailPane,
Simon Hunta255a2c2014-11-13 22:29:35 -0800209 antTimer = null,
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800210 guiSuccessor = null,
Simon Hunta5e89142014-11-14 07:00:33 -0800211 onosInstances = {},
212 onosOrder = [],
213 oiBox,
Simon Hunt9462e8c2014-11-14 17:28:09 -0800214 oiShowMaster = false,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800215 portLabelsOn = false,
Simon Huntb0ecfa52014-11-23 21:05:12 -0800216 cat7 = d3u.cat7(),
Simon Hunt434cf142014-11-24 11:10:28 -0800217 colorAffinity = false,
Simon Hunt27d322d2014-11-28 10:45:43 -0800218 showHosts = false,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800219 showOffline = true,
Simon Hunt27d322d2014-11-28 10:45:43 -0800220 useDetails = true,
Simon Hunt6e18fe32014-11-29 13:35:41 -0800221 haveDetails = false,
Simon Huntc2367d52014-11-29 19:30:23 -0800222 nodeLock = false,
223 oblique = false;
Simon Hunt195cb382014-11-03 17:50:51 -0800224
Simon Hunt434cf142014-11-24 11:10:28 -0800225 // constants
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800226 var hoverModeNone = 0,
227 hoverModeAll = 1,
Thomas Vachuska9edca302014-11-22 17:06:42 -0800228 hoverModeFlows = 2,
229 hoverModeIntents = 3,
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800230 hoverMode = hoverModeNone;
Thomas Vachuska9edca302014-11-22 17:06:42 -0800231
Simon Hunt934c3ce2014-11-05 11:45:07 -0800232 // D3 selections
233 var svg,
Simon Hunt3c5ca542014-11-29 14:11:43 -0800234 panZoomContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800235 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800236 topoG,
237 nodeG,
238 linkG,
Simon Hunte2575b62014-11-18 15:25:53 -0800239 linkLabelG,
Simon Huntc7ee0662014-11-05 16:44:37 -0800240 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800241 link,
Simon Hunte2575b62014-11-18 15:25:53 -0800242 linkLabel,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800243 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800244
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800245 // the projection for the map background
Simon Hunt1b18aa52014-11-29 17:57:55 -0800246 var geoMapProj;
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800247
Paul Greysonfcba0e82014-11-13 10:21:16 -0800248 // the zoom function
249 var zoom;
250
Simon Hunt142d0032014-11-04 20:13:09 -0800251 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800252 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800253
Simon Hunt99c13842014-11-06 18:23:12 -0800254 function note(label, msg) {
255 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800256 }
257
Simon Hunt99c13842014-11-06 18:23:12 -0800258 function debug(what) {
259 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800260 }
261
Simon Huntfc274c92014-11-11 11:05:46 -0800262 function fnTrace(msg, id) {
263 if (config.fnTrace) {
264 console.log('FN: ' + msg + ' [' + id + ']');
265 }
266 }
Simon Hunt99c13842014-11-06 18:23:12 -0800267
Simon Hunta5e89142014-11-14 07:00:33 -0800268 function evTrace(data) {
269 fnTrace(data.event, data.payload.id);
270 }
271
Simon Hunt934c3ce2014-11-05 11:45:07 -0800272 // ==============================
273 // Key Callbacks
274
Simon Hunt27d322d2014-11-28 10:45:43 -0800275 function flash(txt) {
276 network.view.flash(txt);
277 }
278
Simon Hunt99c13842014-11-06 18:23:12 -0800279 function testMe(view) {
Simon Hunt8f40cce2014-11-23 15:57:30 -0800280 //view.alert('Theme is ' + view.getTheme());
Simon Hunta3dd9572014-11-20 15:22:41 -0800281 //view.flash('This is some text');
Simon Hunt8f40cce2014-11-23 15:57:30 -0800282 cat7.testCard(svg);
Simon Hunt99c13842014-11-06 18:23:12 -0800283 }
284
Simon Hunt56d51852014-11-09 13:03:35 -0800285 function injectTestEvent(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800286 if (config.useLiveData) { return; }
287
Simon Hunt56d51852014-11-09 13:03:35 -0800288 var sc = scenario,
289 evn = ++sc.evNumber,
290 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
291 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800292 uiUrl = pfx + sc.evUi,
293 stack = [
294 { url: onosUrl, cb: handleServerEvent },
295 { url: uiUrl, cb: handleUiEvent }
296 ];
297 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800298 }
299
Simon Hunt7cd48f32014-11-09 23:42:50 -0800300 function recurseFetchEvent(stack, evn) {
301 var v = scenario.view,
302 frame;
303 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800304 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800305 return;
306 }
307 frame = stack.shift();
308
309 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800310 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800311 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800312 // if we didn't find the data, try the next stack frame
313 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800314 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800315 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800316 }
Simon Hunt99c13842014-11-06 18:23:12 -0800317 } else {
Simon Hunt1712ed82014-11-17 12:56:00 -0800318 wsTrace('test', JSON.stringify(data));
Simon Hunt7cd48f32014-11-09 23:42:50 -0800319 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800320 }
321 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800322
Simon Hunt56d51852014-11-09 13:03:35 -0800323 }
Simon Hunt50128c02014-11-08 13:36:15 -0800324
Simon Hunt56d51852014-11-09 13:03:35 -0800325 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800326 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
327 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800328 }
329
330 function injectStartupEvents(view) {
331 var last = scenario.params.lastAuto || 0;
Simon Hunt434cf142014-11-24 11:10:28 -0800332 if (config.useLiveData) { return; }
Simon Hunt56d51852014-11-09 13:03:35 -0800333
334 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800335 injectTestEvent(view);
336 }
337 }
338
Simon Hunt934c3ce2014-11-05 11:45:07 -0800339 function toggleBg() {
340 var vis = bgImg.style('visibility');
Simon Hunt434cf142014-11-24 11:10:28 -0800341 bgImg.style('visibility', visVal(vis === 'hidden'));
342 }
343
Simon Huntd6f5a272014-11-29 23:45:50 -0800344 function opacifyBg(b) {
345 bgImg.transition()
346 .duration(1000)
347 .attr('opacity', b ? 1 : 0);
348 }
349
Simon Huntc2367d52014-11-29 19:30:23 -0800350 function toggleNodeLock() {
351 nodeLock = !nodeLock;
352 flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked'))
353 }
354
355 function toggleOblique() {
356 oblique = !oblique;
Simon Huntd6f5a272014-11-29 23:45:50 -0800357 if (oblique) {
358 network.force.stop();
359 toObliqueView();
360 } else {
361 toNormalView();
362 }
Simon Huntc2367d52014-11-29 19:30:23 -0800363 }
364
Simon Hunt434cf142014-11-24 11:10:28 -0800365 function toggleHosts() {
366 showHosts = !showHosts;
367 updateHostVisibility();
Simon Hunt27d322d2014-11-28 10:45:43 -0800368 flash('Hosts ' + visVal(showHosts));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800369 }
370
Simon Hunt6d9bd032014-11-28 22:16:40 -0800371 function toggleOffline() {
372 showOffline = !showOffline;
373 updateOfflineVisibility();
374 flash('Offline devices ' + visVal(showOffline));
375 }
376
Simon Hunt99c13842014-11-06 18:23:12 -0800377 function cycleLabels() {
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800378 deviceLabelIndex = (deviceLabelIndex === 2)
Simon Huntbb282f52014-11-10 11:08:19 -0800379 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800380
Simon Hunt99c13842014-11-06 18:23:12 -0800381 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800382 if (d.class === 'device') {
383 updateDeviceLabel(d);
384 }
Simon Hunt99c13842014-11-06 18:23:12 -0800385 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800386 }
387
388 function togglePorts(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800389 //view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800390 }
391
Simon Hunt6ac93f32014-11-13 12:17:27 -0800392 function unpin() {
393 if (hovered) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800394 sendUpdateMeta(hovered);
Simon Hunt6ac93f32014-11-13 12:17:27 -0800395 hovered.fixed = false;
396 hovered.el.classed('fixed', false);
Simon Huntd6f5a272014-11-29 23:45:50 -0800397 fResume();
Simon Hunt6ac93f32014-11-13 12:17:27 -0800398 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800399 }
400
Simon Hunt9462e8c2014-11-14 17:28:09 -0800401 function handleEscape(view) {
402 if (oiShowMaster) {
403 cancelAffinity();
Simon Hunt27d322d2014-11-28 10:45:43 -0800404 } else if (haveDetails) {
Simon Hunt9462e8c2014-11-14 17:28:09 -0800405 deselectAll();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800406 } else if (oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800407 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800408 } else if (summaryPane.isVisible()) {
409 cancelSummary();
Thomas Vachuska5bde31f2014-11-25 15:29:18 -0800410 stopAntTimer();
411 } else {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800412 hoverMode = hoverModeNone;
Simon Hunt9462e8c2014-11-14 17:28:09 -0800413 }
414 }
415
Simon Hunt934c3ce2014-11-05 11:45:07 -0800416 // ==============================
Simon Huntd6f5a272014-11-29 23:45:50 -0800417 // Oblique view ...
418
419 var obview = {
420 tt: -.7, // x skew y factor
421 xsk: -35, // x skew angle
422 ysc: 0.5, // y scale
423 pad: 50,
424 time: 1500,
425 fill: {
426 pkt: 'rgba(130,130,170,0.3)',
427 opt: 'rgba(170,130,170,0.3)'
428 },
429 id: function (tag) {
430 return 'obview-' + tag + 'Plane';
431 },
432 yt: function (h, dir) {
433 return h * obview.ysc * dir * 1.1;
434 },
435 obXform: function (h, dir) {
436 var yt = obview.yt(h, dir);
437 return scale(1, obview.ysc) + translate(0, yt) + skewX(obview.xsk);
438 },
439 noXform: function () {
440 return skewX(0) + translate(0,0) + scale(1,1);
441 },
442 xffn: null,
443 plane: {}
444 };
445
446
447 function toObliqueView() {
448 var box = nodeG.node().getBBox(),
449 ox, oy;
450
451 padBox(box, obview.pad);
452
453 ox = box.x + box.width / 2;
454 oy = box.y + box.height / 2;
455
456 // remember node lock state, then lock the nodes down
457 obview.nodeLock = nodeLock;
458 nodeLock = true;
459 opacifyBg(false);
460
461 insertPlanes(ox, oy);
462
463 obview.xffn = function (xy, dir) {
464 var yt = obview.yt(box.height, dir),
465 ax = xy.x - ox,
466 ay = xy.y - oy,
467 x = ax + ay * obview.tt,
468 y = ay * obview.ysc + obview.ysc * yt;
469 return {x: ox + x, y: oy + y};
470 };
471
472 showPlane('pkt', box, -1);
473 showPlane('opt', box, 1);
474 obTransitionNodes();
475 }
476
477 function toNormalView() {
478 obview.xffn = null;
479
480 hidePlane('pkt');
481 hidePlane('opt');
482 obTransitionNodes();
483
484 removePlanes();
485
486 // restore node lock state
487 nodeLock = obview.nodeLock;
488 opacifyBg(true);
489 }
490
491 function obTransitionNodes() {
492 var xffn = obview.xffn;
493
494 // return the direction for the node
495 // -1 for pkt layer, 1 for optical layer
496 function dir(d) {
497 return inLayer(d, 'pkt') ? -1 : 1;
498 }
499
500 if (xffn) {
501 network.nodes.forEach(function (d) {
502 var oldxy = {x: d.x, y: d.y},
503 coords = xffn(oldxy, dir(d));
504 d.oldxy = oldxy;
505 d.px = d.x = coords.x;
506 d.py = d.y = coords.y;
507 });
508 } else {
509 network.nodes.forEach(function (d) {
510 var old = d.oldxy || {x: d.x, y: d.y};
511 d.px = d.x = old.x;
512 d.py = d.y = old.y;
513 delete d.oldxy;
514 });
515 }
516
517 node.transition()
518 .duration(obview.time)
519 .attr(tickStuff.nodeAttr);
520 link.transition()
521 .duration(obview.time)
522 .attr(tickStuff.linkAttr);
523 linkLabel.transition()
524 .duration(obview.time)
525 .attr(tickStuff.linkLabelAttr);
526 }
527
528 function showPlane(tag, box, dir) {
529 var g = obview.plane[tag];
530
531 // set box origin at center..
532 box.x = -box.width/2;
533 box.y = -box.height/2;
534
535 g.select('rect')
536 .attr(box)
537 .attr('opacity', 0)
538 .transition()
539 .duration(obview.time)
540 .attr('opacity', 1)
541 .attr('transform', obview.obXform(box.height, dir));
542 }
543
544 function hidePlane(tag) {
545 var g = obview.plane[tag];
546
547 g.select('rect')
548 .transition()
549 .duration(obview.time)
550 .attr('opacity', 0)
551 .attr('transform', obview.noXform());
552 }
553
554 function insertPlanes(ox, oy) {
555 function ins(tag) {
556 var id = obview.id(tag),
557 g = panZoomContainer.insert('g', '#topo-G')
558 .attr('id', id)
559 .attr('transform', translate(ox,oy));
560 g.append('rect')
561 .attr('fill', obview.fill[tag])
562 .attr('opacity', 0);
563 obview.plane[tag] = g;
564 }
565 ins('opt');
566 ins('pkt');
567 }
568
569 function removePlanes() {
570 function rem(tag) {
571 var id = obview.id(tag);
572 panZoomContainer.select('#'+id)
573 .transition()
574 .duration(obview.time + 50)
575 .remove();
576 delete obview.plane[tag];
577 }
578 rem('opt');
579 rem('pkt');
580 }
581
582 function padBox(box, p) {
583 box.x -= p;
584 box.y -= p;
585 box.width += p*2;
586 box.height += p*2;
587 }
588
589 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800590 // Radio Button Callbacks
591
Simon Hunta5e89142014-11-14 07:00:33 -0800592 var layerLookup = {
593 host: {
Simon Hunt209155e2014-11-21 12:16:09 -0800594 endstation: 'pkt', // default, if host event does not define type
Thomas Vachuska89543292014-11-19 11:28:33 -0800595 router: 'pkt',
Simon Hunta5e89142014-11-14 07:00:33 -0800596 bgpSpeaker: 'pkt'
597 },
598 device: {
599 switch: 'pkt',
600 roadm: 'opt'
601 },
602 link: {
603 hostLink: 'pkt',
604 direct: 'pkt',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800605 indirect: '',
606 tunnel: '',
Simon Hunta5e89142014-11-14 07:00:33 -0800607 optical: 'opt'
608 }
609 };
610
611 function inLayer(d, layer) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800612 var type = d.class === 'link' ? d.type() : d.type,
613 look = layerLookup[d.class],
614 lyr = look && look[type];
Simon Hunta5e89142014-11-14 07:00:33 -0800615 return lyr === layer;
616 }
617
618 function unsuppressLayer(which) {
619 node.each(function (d) {
620 var node = d.el;
621 if (inLayer(d, which)) {
622 node.classed('suppressed', false);
623 }
624 });
625
626 link.each(function (d) {
627 var link = d.el;
628 if (inLayer(d, which)) {
629 link.classed('suppressed', false);
630 }
631 });
632 }
633
Simon Hunt9462e8c2014-11-14 17:28:09 -0800634 function suppressLayers(b) {
635 node.classed('suppressed', b);
636 link.classed('suppressed', b);
Simon Hunt142d0032014-11-04 20:13:09 -0800637// d3.selectAll('svg .port').classed('inactive', false);
638// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt195cb382014-11-03 17:50:51 -0800639 }
640
Simon Hunt9462e8c2014-11-14 17:28:09 -0800641 function showAllLayers() {
642 suppressLayers(false);
643 }
644
Simon Hunt195cb382014-11-03 17:50:51 -0800645 function showPacketLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800646 node.classed('suppressed', true);
647 link.classed('suppressed', true);
648 unsuppressLayer('pkt');
Simon Hunt195cb382014-11-03 17:50:51 -0800649 }
650
651 function showOpticalLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800652 node.classed('suppressed', true);
653 link.classed('suppressed', true);
654 unsuppressLayer('opt');
Simon Hunt195cb382014-11-03 17:50:51 -0800655 }
656
Simon Hunt9462e8c2014-11-14 17:28:09 -0800657 function restoreLayerState() {
658 layerBtnDispatch[layerBtnSet.selected()]();
659 }
660
Simon Hunt142d0032014-11-04 20:13:09 -0800661 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800662 // Private functions
663
Simon Hunt99c13842014-11-06 18:23:12 -0800664 function safeId(s) {
665 return s.replace(/[^a-z0-9]/gi, '-');
666 }
667
Simon Huntc7ee0662014-11-05 16:44:37 -0800668 // set the size of the given element to that of the view (reduced if padded)
669 function setSize(el, view, pad) {
670 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800671 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800672 width: view.width() - padding,
673 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800674 });
675 }
676
Simon Hunt8257f4c2014-11-16 19:34:54 -0800677 function makeNodeKey(d, what) {
678 var port = what + 'Port';
679 return d[what] + '/' + d[port];
680 }
681
682 function makeLinkKey(d, flipped) {
683 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
684 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
685 return one + '-' + two;
686 }
687
Simon Hunt269670f2014-11-17 16:17:43 -0800688 function findLinkById(id) {
689 // check to see if this is a reverse lookup, else default to given id
690 var key = network.revLinkToKey[id] || id;
691 return key && network.lookup[key];
692 }
693
Simon Hunt8257f4c2014-11-16 19:34:54 -0800694 function findLink(linkData, op) {
695 var key = makeLinkKey(linkData),
696 keyrev = makeLinkKey(linkData, 1),
697 link = network.lookup[key],
698 linkRev = network.lookup[keyrev],
699 result = {},
700 ldata = link || linkRev,
701 rawLink;
702
703 if (op === 'add') {
704 if (link) {
705 // trying to add a link that we already know about
706 result.ldata = link;
707 result.badLogic = 'addLink: link already added';
708
709 } else if (linkRev) {
710 // we found the reverse of the link to be added
711 result.ldata = linkRev;
712 if (linkRev.fromTarget) {
713 result.badLogic = 'addLink: link already added';
714 }
715 }
716 } else if (op === 'update') {
717 if (!ldata) {
718 result.badLogic = 'updateLink: link not found';
719 } else {
720 rawLink = link ? ldata.fromSource : ldata.fromTarget;
721 result.updateWith = function (data) {
722 $.extend(rawLink, data);
723 restyleLinkElement(ldata);
724 }
725 }
726 } else if (op === 'remove') {
727 if (!ldata) {
728 result.badLogic = 'removeLink: link not found';
729 } else {
730 rawLink = link ? ldata.fromSource : ldata.fromTarget;
731
732 if (!rawLink) {
733 result.badLogic = 'removeLink: link not found';
734
735 } else {
736 result.removeRawLink = function () {
737 if (link) {
738 // remove fromSource
739 ldata.fromSource = null;
740 if (ldata.fromTarget) {
741 // promote target into source position
742 ldata.fromSource = ldata.fromTarget;
743 ldata.fromTarget = null;
744 ldata.key = keyrev;
745 delete network.lookup[key];
746 network.lookup[keyrev] = ldata;
Simon Hunt269670f2014-11-17 16:17:43 -0800747 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800748 }
749 } else {
750 // remove fromTarget
751 ldata.fromTarget = null;
Simon Hunt269670f2014-11-17 16:17:43 -0800752 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800753 }
754 if (ldata.fromSource) {
755 restyleLinkElement(ldata);
756 } else {
757 removeLinkElement(ldata);
758 }
759 }
760 }
761 }
762 }
763 return result;
764 }
765
766 function addLinkUpdate(ldata, link) {
767 // add link event, but we already have the reverse link installed
768 ldata.fromTarget = link;
Simon Hunt269670f2014-11-17 16:17:43 -0800769 network.revLinkToKey[link.id] = ldata.key;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800770 restyleLinkElement(ldata);
771 }
772
773 var allLinkTypes = 'direct indirect optical tunnel',
774 defaultLinkType = 'direct';
775
776 function restyleLinkElement(ldata) {
777 // this fn's job is to look at raw links and decide what svg classes
778 // need to be applied to the line element in the DOM
779 var el = ldata.el,
780 type = ldata.type(),
781 lw = ldata.linkWidth(),
782 online = ldata.online();
783
784 el.classed('link', true);
785 el.classed('inactive', !online);
786 el.classed(allLinkTypes, false);
787 if (type) {
788 el.classed(type, true);
789 }
790 el.transition()
791 .duration(1000)
Thomas Vachuska89543292014-11-19 11:28:33 -0800792 .attr('stroke-width', linkScale(lw))
793 .attr('stroke', config.topo.linkBaseColor);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800794 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800795
Simon Hunt99c13842014-11-06 18:23:12 -0800796 // ==============================
797 // Event handlers for server-pushed events
798
Simon Huntbb282f52014-11-10 11:08:19 -0800799 function logicError(msg) {
800 // TODO, report logic error to server, via websock, so it can be logged
Simon Huntfc274c92014-11-11 11:05:46 -0800801 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800802 }
803
Simon Hunt99c13842014-11-06 18:23:12 -0800804 var eventDispatch = {
Simon Hunta5e89142014-11-14 07:00:33 -0800805 addInstance: addInstance,
Simon Hunt99c13842014-11-06 18:23:12 -0800806 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800807 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800808 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800809
Simon Huntfcfb46c2014-11-19 12:53:38 -0800810 updateInstance: updateInstance,
Simon Huntbb282f52014-11-10 11:08:19 -0800811 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800812 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800813 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800814
Simon Hunt7b403bc2014-11-22 19:01:00 -0800815 removeInstance: removeInstance,
Simon Huntca867ac2014-11-28 18:07:35 -0800816 removeDevice: removeDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800817 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800818 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800819
Simon Hunt61d04042014-11-11 17:27:16 -0800820 showDetails: showDetails,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800821 showSummary: showSummary,
Simon Huntb53e0682014-11-12 13:32:01 -0800822 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800823 };
824
Simon Hunta5e89142014-11-14 07:00:33 -0800825 function addInstance(data) {
826 evTrace(data);
827 var inst = data.payload,
828 id = inst.id;
829 if (onosInstances[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800830 updateInstance(data);
Simon Hunta5e89142014-11-14 07:00:33 -0800831 return;
832 }
833 onosInstances[id] = inst;
834 onosOrder.push(inst);
835 updateInstances();
836 }
837
Simon Hunt99c13842014-11-06 18:23:12 -0800838 function addDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800839 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800840 var device = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800841 id = device.id,
842 d;
843
844 if (network.lookup[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800845 updateDevice(data);
Simon Huntca867ac2014-11-28 18:07:35 -0800846 return;
847 }
848
849 d = createDeviceNode(device);
850 network.nodes.push(d);
851 network.lookup[id] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800852 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -0800853 fStart();
Simon Hunt99c13842014-11-06 18:23:12 -0800854 }
855
Simon Hunt99c13842014-11-06 18:23:12 -0800856 function addLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800857 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800858 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800859 result = findLink(link, 'add'),
860 bad = result.badLogic,
Simon Huntca867ac2014-11-28 18:07:35 -0800861 d = result.ldata;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800862
863 if (bad) {
864 logicError(bad + ': ' + link.id);
865 return;
866 }
867
Simon Huntca867ac2014-11-28 18:07:35 -0800868 if (d) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800869 // we already have a backing store link for src/dst nodes
Simon Huntca867ac2014-11-28 18:07:35 -0800870 addLinkUpdate(d, link);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800871 return;
872 }
873
874 // no backing store link yet
Simon Huntca867ac2014-11-28 18:07:35 -0800875 d = createLink(link);
876 if (d) {
877 network.links.push(d);
878 network.lookup[d.key] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800879 updateLinks();
Simon Huntd6f5a272014-11-29 23:45:50 -0800880 fStart();
Simon Hunt99c13842014-11-06 18:23:12 -0800881 }
882 }
883
Simon Hunt56d51852014-11-09 13:03:35 -0800884 function addHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800885 evTrace(data);
Simon Hunt56d51852014-11-09 13:03:35 -0800886 var host = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800887 id = host.id,
888 d,
Simon Hunt56d51852014-11-09 13:03:35 -0800889 lnk;
Simon Huntca867ac2014-11-28 18:07:35 -0800890
891 if (network.lookup[id]) {
892 logicError('Host already added: ' + id);
893 return;
894 }
895
896 d = createHostNode(host);
897 network.nodes.push(d);
898 network.lookup[host.id] = d;
Simon Hunt56d51852014-11-09 13:03:35 -0800899 updateNodes();
900
901 lnk = createHostLink(host);
902 if (lnk) {
Simon Huntca867ac2014-11-28 18:07:35 -0800903 d.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800904 network.links.push(lnk);
Simon Huntca867ac2014-11-28 18:07:35 -0800905 network.lookup[d.ingress] = lnk;
906 network.lookup[d.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800907 updateLinks();
908 }
Simon Huntd6f5a272014-11-29 23:45:50 -0800909 fStart();
Simon Hunt56d51852014-11-09 13:03:35 -0800910 }
911
Simon Hunt44031102014-11-11 13:20:36 -0800912 // TODO: fold updateX(...) methods into one base method; remove duplication
Simon Hunt56a2ea42014-11-19 12:39:31 -0800913
914 function updateInstance(data) {
915 evTrace(data);
916 var inst = data.payload,
917 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800918 d = onosInstances[id];
919 if (d) {
920 $.extend(d, inst);
Simon Hunt56a2ea42014-11-19 12:39:31 -0800921 updateInstances();
Simon Hunt56a2ea42014-11-19 12:39:31 -0800922 } else {
923 logicError('updateInstance lookup fail. ID = "' + id + '"');
924 }
925 }
926
Simon Huntbb282f52014-11-10 11:08:19 -0800927 function updateDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800928 evTrace(data);
Simon Huntbb282f52014-11-10 11:08:19 -0800929 var device = data.payload,
930 id = device.id,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800931 d = network.lookup[id],
932 wasOnline;
933
Simon Hunt62c47542014-11-22 22:16:32 -0800934 if (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -0800935 wasOnline = d.online;
Simon Hunt62c47542014-11-22 22:16:32 -0800936 $.extend(d, device);
937 if (positionNode(d, true)) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800938 sendUpdateMeta(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -0800939 }
940 updateNodes();
Simon Hunt6d9bd032014-11-28 22:16:40 -0800941 if (wasOnline !== d.online) {
942 findAttachedLinks(d.id).forEach(restyleLinkElement);
943 updateOfflineVisibility(d);
944 }
Simon Huntbb282f52014-11-10 11:08:19 -0800945 } else {
946 logicError('updateDevice lookup fail. ID = "' + id + '"');
947 }
948 }
949
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800950 function updateLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800951 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800952 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800953 result = findLink(link, 'update'),
954 bad = result.badLogic;
955 if (bad) {
956 logicError(bad + ': ' + link.id);
957 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800958 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800959 result.updateWith(link);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800960 }
961
Simon Hunt7cd48f32014-11-09 23:42:50 -0800962 function updateHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800963 evTrace(data);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800964 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800965 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800966 d = network.lookup[id];
967 if (d) {
968 $.extend(d, host);
Simon Hunt6d9bd032014-11-28 22:16:40 -0800969 if (positionNode(d, true)) {
970 sendUpdateMeta(d, true);
971 }
972 updateNodes(d);
Simon Huntbb282f52014-11-10 11:08:19 -0800973 } else {
974 logicError('updateHost lookup fail. ID = "' + id + '"');
975 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800976 }
977
Simon Hunt44031102014-11-11 13:20:36 -0800978 // TODO: fold removeX(...) methods into base method - remove dup code
Simon Hunt7b403bc2014-11-22 19:01:00 -0800979 function removeInstance(data) {
980 evTrace(data);
981 var inst = data.payload,
982 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800983 d = onosInstances[id];
984 if (d) {
985 var idx = find(id, onosOrder);
Simon Hunt7b403bc2014-11-22 19:01:00 -0800986 if (idx >= 0) {
987 onosOrder.splice(idx, 1);
988 }
989 delete onosInstances[id];
990 updateInstances();
991 } else {
992 logicError('updateInstance lookup fail. ID = "' + id + '"');
993 }
994 }
995
Simon Huntca867ac2014-11-28 18:07:35 -0800996 function removeDevice(data) {
997 evTrace(data);
998 var device = data.payload,
999 id = device.id,
1000 d = network.lookup[id];
1001 if (d) {
1002 removeDeviceElement(d);
1003 } else {
1004 logicError('removeDevice lookup fail. ID = "' + id + '"');
1005 }
1006 }
1007
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001008 function removeLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001009 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001010 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -08001011 result = findLink(link, 'remove'),
1012 bad = result.badLogic;
1013 if (bad) {
Simon Huntca867ac2014-11-28 18:07:35 -08001014 // may have already removed link, if attached to removed device
1015 console.warn(bad + ': ' + link.id);
Simon Hunt8257f4c2014-11-16 19:34:54 -08001016 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001017 }
Simon Hunt8257f4c2014-11-16 19:34:54 -08001018 result.removeRawLink();
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001019 }
1020
Simon Hunt44031102014-11-11 13:20:36 -08001021 function removeHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001022 evTrace(data);
Simon Hunt44031102014-11-11 13:20:36 -08001023 var host = data.payload,
1024 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -08001025 d = network.lookup[id];
1026 if (d) {
1027 removeHostElement(d, true);
Simon Hunt44031102014-11-11 13:20:36 -08001028 } else {
Simon Huntca867ac2014-11-28 18:07:35 -08001029 // may have already removed host, if attached to removed device
1030 console.warn('removeHost lookup fail. ID = "' + id + '"');
Simon Hunt44031102014-11-11 13:20:36 -08001031 }
1032 }
1033
Simon Huntca867ac2014-11-28 18:07:35 -08001034 // the following events are server responses to user actions
Thomas Vachuska47635c62014-11-22 01:21:36 -08001035 function showSummary(data) {
1036 evTrace(data);
1037 populateSummary(data.payload);
Simon Hunt06811b72014-11-25 18:54:48 -08001038 showSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001039 }
1040
Simon Hunt61d04042014-11-11 17:27:16 -08001041 function showDetails(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001042 evTrace(data);
Simon Hunt27d322d2014-11-28 10:45:43 -08001043 haveDetails = true;
Simon Hunt61d04042014-11-11 17:27:16 -08001044 populateDetails(data.payload);
Simon Hunt27d322d2014-11-28 10:45:43 -08001045 if (useDetails) {
1046 showDetailPane();
1047 }
Simon Hunt61d04042014-11-11 17:27:16 -08001048 }
1049
Simon Huntb53e0682014-11-12 13:32:01 -08001050 function showTraffic(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001051 evTrace(data);
Thomas Vachuska4731f122014-11-20 04:56:19 -08001052 var paths = data.payload.paths,
1053 hasTraffic = false;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001054
Thomas Vachuska3266abf2014-11-13 09:28:46 -08001055 // Revert any links hilighted previously.
Thomas Vachuska4731f122014-11-20 04:56:19 -08001056 link.style('stroke-width', null)
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08001057 .classed('primary secondary animated optical', false);
Simon Hunte2575b62014-11-18 15:25:53 -08001058 // Remove all previous labels.
1059 removeLinkLabels();
Thomas Vachuska3266abf2014-11-13 09:28:46 -08001060
Simon Hunte2575b62014-11-18 15:25:53 -08001061 // Now hilight all links in the paths payload, and attach
1062 // labels to them, if they are defined.
Simon Hunta255a2c2014-11-13 22:29:35 -08001063 paths.forEach(function (p) {
Simon Hunte2575b62014-11-18 15:25:53 -08001064 var n = p.links.length,
1065 i,
1066 ldata;
1067
Thomas Vachuska4731f122014-11-20 04:56:19 -08001068 hasTraffic = hasTraffic || p.traffic;
Simon Hunte2575b62014-11-18 15:25:53 -08001069 for (i=0; i<n; i++) {
1070 ldata = findLinkById(p.links[i]);
Thomas Vachuska4731f122014-11-20 04:56:19 -08001071 if (ldata && ldata.el) {
Simon Hunte2575b62014-11-18 15:25:53 -08001072 ldata.el.classed(p.class, true);
1073 ldata.label = p.labels[i];
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001074 }
Simon Hunte2575b62014-11-18 15:25:53 -08001075 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001076 });
Thomas Vachuska4731f122014-11-20 04:56:19 -08001077
Simon Hunte2575b62014-11-18 15:25:53 -08001078 updateLinks();
Thomas Vachuska4731f122014-11-20 04:56:19 -08001079
1080 if (hasTraffic && !antTimer) {
1081 startAntTimer();
1082 } else if (!hasTraffic && antTimer) {
1083 stopAntTimer();
1084 }
Simon Huntb53e0682014-11-12 13:32:01 -08001085 }
1086
Simon Hunt56d51852014-11-09 13:03:35 -08001087 // ...............................
1088
Simon Hunt99c13842014-11-06 18:23:12 -08001089 function unknownEvent(data) {
Simon Hunt434cf142014-11-24 11:10:28 -08001090 console.warn('Unknown event type: "' + data.event + '"', data);
Simon Hunt99c13842014-11-06 18:23:12 -08001091 }
1092
1093 function handleServerEvent(data) {
1094 var fn = eventDispatch[data.event] || unknownEvent;
1095 fn(data);
1096 }
1097
1098 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -08001099 // Out-going messages...
1100
Simon Huntb53e0682014-11-12 13:32:01 -08001101 function nSel() {
1102 return selectOrder.length;
1103 }
Simon Hunt61d04042014-11-11 17:27:16 -08001104 function getSel(idx) {
1105 return selections[selectOrder[idx]];
1106 }
Simon Huntb53e0682014-11-12 13:32:01 -08001107 function allSelectionsClass(cls) {
1108 for (var i=0, n=nSel(); i<n; i++) {
1109 if (getSel(i).obj.class !== cls) {
1110 return false;
1111 }
1112 }
1113 return true;
1114 }
Simon Hunt61d04042014-11-11 17:27:16 -08001115
Thomas Vachuska47635c62014-11-22 01:21:36 -08001116 function toggleInstances() {
1117 if (!oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001118 showInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001119 } else {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001120 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001121 }
1122 }
1123
Simon Huntb0ecfa52014-11-23 21:05:12 -08001124 function showInstances() {
1125 oiBox.show();
1126 colorAffinity = true;
1127 updateDeviceColors();
1128 }
1129
1130 function hideInstances() {
1131 oiBox.hide();
1132 colorAffinity = false;
1133 cancelAffinity();
1134 updateDeviceColors();
1135 }
1136
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001137 function equalizeMasters() {
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001138 sendMessage('equalizeMasters');
Simon Huntc1cc81c2014-11-29 14:59:01 -08001139 flash('Equalizing master roles');
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001140 }
1141
Thomas Vachuska47635c62014-11-22 01:21:36 -08001142 function toggleSummary() {
1143 if (!summaryPane.isVisible()) {
1144 requestSummary();
1145 } else {
1146 cancelSummary();
1147 }
1148 }
1149
1150 // request overall summary data
1151 function requestSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001152 sendMessage('requestSummary');
Thomas Vachuska47635c62014-11-22 01:21:36 -08001153 }
1154
1155 function cancelSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001156 sendMessage('cancelSummary');
Simon Hunt06811b72014-11-25 18:54:48 -08001157 hideSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001158 }
1159
Simon Hunt27d322d2014-11-28 10:45:43 -08001160 function toggleDetails() {
1161 useDetails = !useDetails;
1162 if (useDetails) {
1163 flash('Enable details pane');
1164 if (haveDetails) {
1165 showDetailPane();
1166 }
1167 } else {
1168 flash('Disable details pane');
1169 hideDetailPane();
1170 }
1171 }
1172
Simon Hunt06811b72014-11-25 18:54:48 -08001173 // encapsulate interaction between summary and details panes
1174 function showSummaryPane() {
1175 if (detailPane.isVisible()) {
1176 detailPane.down(summaryPane.show);
1177 } else {
1178 summaryPane.show();
1179 }
1180 }
1181
1182 function hideSummaryPane() {
1183 summaryPane.hide(function () {
1184 if (detailPane.isVisible()) {
1185 detailPane.up();
1186 }
1187 });
1188 }
1189
1190 function showDetailPane() {
1191 if (summaryPane.isVisible()) {
1192 detailPane.down(detailPane.show);
1193 } else {
1194 detailPane.up(detailPane.show);
1195 }
1196 }
1197
1198 function hideDetailPane() {
1199 detailPane.hide();
1200 }
1201
1202
Simon Hunt61d04042014-11-11 17:27:16 -08001203 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -08001204 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -08001205 function requestDetails() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001206 var data = getSel(0).obj;
1207 sendMessage('requestDetails', {
1208 id: data.id,
1209 class: data.class
1210 });
Simon Hunt61d04042014-11-11 17:27:16 -08001211 }
1212
Thomas Vachuska9edca302014-11-22 17:06:42 -08001213 function addHostIntentAction() {
Simon Huntd72bc702014-11-13 18:38:04 -08001214 sendMessage('addHostIntent', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001215 one: selectOrder[0],
1216 two: selectOrder[1],
1217 ids: selectOrder
Simon Huntd72bc702014-11-13 18:38:04 -08001218 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001219 flash('Host-to-Host flow added');
Simon Huntd72bc702014-11-13 18:38:04 -08001220 }
1221
Thomas Vachuska9edca302014-11-22 17:06:42 -08001222 function addMultiSourceIntentAction() {
1223 sendMessage('addMultiSourceIntent', {
1224 src: selectOrder.slice(0, selectOrder.length - 1),
1225 dst: selectOrder[selectOrder.length - 1],
1226 ids: selectOrder
1227 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001228 flash('Multi-Source flow added');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001229 }
1230
Thomas Vachuska9edca302014-11-22 17:06:42 -08001231
Thomas Vachuska47635c62014-11-22 01:21:36 -08001232 function cancelTraffic() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001233 sendMessage('cancelTraffic');
Thomas Vachuska47635c62014-11-22 01:21:36 -08001234 }
1235
Thomas Vachuska9edca302014-11-22 17:06:42 -08001236 function requestTrafficForMode() {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001237 if (hoverMode === hoverModeFlows) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001238 requestDeviceLinkFlows();
1239 } else if (hoverMode === hoverModeIntents) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001240 requestRelatedIntents();
Simon Huntd72bc702014-11-13 18:38:04 -08001241 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001242 }
Simon Huntd72bc702014-11-13 18:38:04 -08001243
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001244 function showRelatedIntentsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001245 hoverMode = hoverModeIntents;
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001246 requestRelatedIntents();
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001247 flash('Related Paths');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001248 }
1249
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001250 function requestRelatedIntents() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001251 function hoverValid() {
1252 return hoverMode === hoverModeIntents &&
1253 hovered &&
1254 (hovered.class === 'host' || hovered.class === 'device');
1255 }
1256
Thomas Vachuska9edca302014-11-22 17:06:42 -08001257 if (validateSelectionContext()) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001258 sendMessage('requestRelatedIntents', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001259 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001260 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001261 });
1262 }
Simon Huntd72bc702014-11-13 18:38:04 -08001263 }
1264
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001265 function showNextIntentAction() {
1266 hoverMode = hoverModeNone;
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001267 sendMessage('requestNextRelatedIntent');
1268 flash('>');
1269 }
1270
1271 function showPrevIntentAction() {
1272 hoverMode = hoverModeNone;
1273 sendMessage('requestPrevRelatedIntent');
1274 flash('<');
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001275 }
1276
1277 function showSelectedIntentTrafficAction() {
1278 hoverMode = hoverModeNone;
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001279 sendMessage('requestSelectedIntentTraffic');
1280 flash('Traffic on Selected Path');
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001281 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -08001282
Thomas Vachuska29617e52014-11-20 03:17:46 -08001283 function showDeviceLinkFlowsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001284 hoverMode = hoverModeFlows;
1285 requestDeviceLinkFlows();
Simon Hunt27d322d2014-11-28 10:45:43 -08001286 flash('Device Flows');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001287 }
1288
Thomas Vachuska9edca302014-11-22 17:06:42 -08001289 function requestDeviceLinkFlows() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001290 function hoverValid() {
1291 return hoverMode === hoverModeFlows &&
1292 hovered && (hovered.class === 'device');
1293 }
1294
Thomas Vachuska9edca302014-11-22 17:06:42 -08001295 if (validateSelectionContext()) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001296 sendMessage('requestDeviceLinkFlows', {
1297 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001298 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001299 });
1300 }
1301 }
1302
1303
1304 function showAllTrafficAction() {
1305 hoverMode = hoverModeAll;
1306 requestAllTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001307 flash('All Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001308 }
1309
1310 function requestAllTraffic() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001311 sendMessage('requestAllTraffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001312 }
1313
1314 function validateSelectionContext() {
Thomas Vachuska29617e52014-11-20 03:17:46 -08001315 if (!hovered && nSel() === 0) {
Thomas Vachuska47635c62014-11-22 01:21:36 -08001316 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08001317 return false;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001318 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001319 return true;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001320 }
Simon Huntd72bc702014-11-13 18:38:04 -08001321
Simon Hunta6a9fe72014-11-20 11:17:12 -08001322
Simon Hunt61d04042014-11-11 17:27:16 -08001323 // ==============================
Simon Hunta5e89142014-11-14 07:00:33 -08001324 // onos instance panel functions
Simon Huntb82f6902014-11-22 11:53:15 -08001325
Simon Hunt7b403bc2014-11-22 19:01:00 -08001326 var instCfg = {
1327 rectPad: 8,
1328 nodeOx: 9,
1329 nodeOy: 9,
1330 nodeDim: 40,
1331 birdOx: 19,
1332 birdOy: 21,
1333 birdDim: 21,
1334 uiDy: 45,
1335 titleDy: 30,
1336 textYOff: 20,
1337 textYSpc: 15
1338 };
1339
1340 function viewBox(dim) {
1341 return '0 0 ' + dim.w + ' ' + dim.h;
1342 }
1343
1344 function instRectAttr(dim) {
1345 var pad = instCfg.rectPad;
1346 return {
1347 x: pad,
1348 y: pad,
1349 width: dim.w - pad*2,
1350 height: dim.h - pad*2,
Simon Huntb0ecfa52014-11-23 21:05:12 -08001351 rx: 6
Simon Hunt7b403bc2014-11-22 19:01:00 -08001352 };
1353 }
1354
1355 function computeDim(self) {
1356 var css = window.getComputedStyle(self);
1357 return {
1358 w: stripPx(css.width),
1359 h: stripPx(css.height)
1360 };
Simon Huntb82f6902014-11-22 11:53:15 -08001361 }
Simon Hunta5e89142014-11-14 07:00:33 -08001362
1363 function updateInstances() {
1364 var onoses = oiBox.el.selectAll('.onosInst')
Simon Huntb82f6902014-11-22 11:53:15 -08001365 .data(onosOrder, function (d) { return d.id; }),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001366 instDim = {w:0,h:0},
1367 c = instCfg;
Simon Hunta5e89142014-11-14 07:00:33 -08001368
Simon Hunt7b403bc2014-11-22 19:01:00 -08001369 function nSw(n) {
1370 return '# Switches: ' + n;
1371 }
Simon Hunta5e89142014-11-14 07:00:33 -08001372
Simon Huntb82f6902014-11-22 11:53:15 -08001373 // operate on existing onos instances if necessary
1374 onoses.each(function (d) {
1375 var el = d3.select(this),
1376 svg = el.select('svg');
Simon Hunt7b403bc2014-11-22 19:01:00 -08001377 instDim = computeDim(this);
Simon Huntb82f6902014-11-22 11:53:15 -08001378
1379 // update online state
1380 el.classed('online', d.online);
1381
1382 // update ui-attached state
1383 svg.select('use.uiBadge').remove();
1384 if (d.uiAttached) {
1385 attachUiBadge(svg);
1386 }
1387
Simon Hunt7b403bc2014-11-22 19:01:00 -08001388 function updAttr(id, value) {
1389 svg.select('text.instLabel.'+id).text(value);
1390 }
1391
1392 updAttr('ip', d.ip);
1393 updAttr('ns', nSw(d.switches));
Simon Huntb82f6902014-11-22 11:53:15 -08001394 });
1395
1396
1397 // operate on new onos instances
Simon Hunta5e89142014-11-14 07:00:33 -08001398 var entering = onoses.enter()
1399 .append('div')
1400 .attr('class', 'onosInst')
1401 .classed('online', function (d) { return d.online; })
Simon Hunt9c15eca2014-11-15 18:37:59 -08001402 .on('click', clickInst);
1403
Simon Huntb82f6902014-11-22 11:53:15 -08001404 entering.each(function (d) {
Simon Hunt9c15eca2014-11-15 18:37:59 -08001405 var el = d3.select(this),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001406 rectAttr,
1407 svg;
1408 instDim = computeDim(this);
1409 rectAttr = instRectAttr(instDim);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001410
Simon Hunt7b403bc2014-11-22 19:01:00 -08001411 svg = el.append('svg').attr({
1412 width: instDim.w,
1413 height: instDim.h,
1414 viewBox: viewBox(instDim)
Simon Hunt95908012014-11-20 10:20:26 -08001415 });
Simon Huntb82f6902014-11-22 11:53:15 -08001416
Simon Hunt7b403bc2014-11-22 19:01:00 -08001417 svg.append('rect').attr(rectAttr);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001418
Thomas Vachuskae02e11c2014-11-24 16:13:52 -08001419 //appendGlyph(svg, c.nodeOx, c.nodeOy, c.nodeDim, '#node');
1420 appendBadge(svg, 14, 14, 28, '#bird');
Simon Huntb82f6902014-11-22 11:53:15 -08001421
1422 if (d.uiAttached) {
1423 attachUiBadge(svg);
1424 }
1425
Simon Hunt7b403bc2014-11-22 19:01:00 -08001426 var left = c.nodeOx + c.nodeDim,
1427 len = rectAttr.width - left,
1428 hlen = len / 2,
1429 midline = hlen + left;
Simon Hunt9c15eca2014-11-15 18:37:59 -08001430
Simon Hunt7b403bc2014-11-22 19:01:00 -08001431 // title
1432 svg.append('text')
1433 .attr({
1434 class: 'instTitle',
1435 x: midline,
1436 y: c.titleDy
1437 })
1438 .text(d.id);
1439
1440 // a couple of attributes
1441 var ty = c.titleDy + c.textYOff;
1442
1443 function addAttr(id, label) {
1444 svg.append('text').attr({
1445 class: 'instLabel ' + id,
1446 x: midline,
1447 y: ty
1448 }).text(label);
1449 ty += c.textYSpc;
1450 }
1451
1452 addAttr('ip', d.ip);
1453 addAttr('ns', nSw(d.switches));
Simon Hunt9c15eca2014-11-15 18:37:59 -08001454 });
Simon Hunta5e89142014-11-14 07:00:33 -08001455
1456 // operate on existing + new onoses here
Simon Hunt8f40cce2014-11-23 15:57:30 -08001457 // set the affinity colors...
1458 onoses.each(function (d) {
1459 var el = d3.select(this),
1460 rect = el.select('svg').select('rect'),
1461 col = instColor(d.id, d.online);
1462 rect.style('fill', col);
1463 });
Simon Hunta5e89142014-11-14 07:00:33 -08001464
Simon Hunt7b403bc2014-11-22 19:01:00 -08001465 // adjust the panel size appropriately...
1466 oiBox.width(instDim.w * onosOrder.length);
1467 oiBox.height(instDim.h);
1468
1469 // remove any outgoing instances
1470 onoses.exit().remove();
Simon Hunta5e89142014-11-14 07:00:33 -08001471 }
1472
Simon Hunt8f40cce2014-11-23 15:57:30 -08001473 function instColor(id, online) {
1474 return cat7.get(id, !online, network.view.getTheme());
1475 }
1476
Simon Hunt9462e8c2014-11-14 17:28:09 -08001477 function clickInst(d) {
1478 var el = d3.select(this),
1479 aff = el.classed('affinity');
1480 if (!aff) {
1481 setAffinity(el, d);
1482 } else {
1483 cancelAffinity();
1484 }
1485 }
1486
1487 function setAffinity(el, d) {
1488 d3.selectAll('.onosInst')
1489 .classed('mastership', true)
1490 .classed('affinity', false);
1491 el.classed('affinity', true);
1492
1493 suppressLayers(true);
1494 node.each(function (n) {
1495 if (n.master === d.id) {
1496 n.el.classed('suppressed', false);
1497 }
1498 });
1499 oiShowMaster = true;
1500 }
1501
1502 function cancelAffinity() {
1503 d3.selectAll('.onosInst')
1504 .classed('mastership affinity', false);
1505 restoreLayerState();
1506 oiShowMaster = false;
1507 }
1508
Simon Hunt7b403bc2014-11-22 19:01:00 -08001509 // TODO: these should be moved out to utility module.
1510 function stripPx(s) {
1511 return s.replace(/px$/,'');
1512 }
1513
1514 function appendUse(svg, ox, oy, dim, iid, cls) {
1515 var use = svg.append('use').attr({
1516 transform: translate(ox,oy),
1517 'xlink:href': iid,
1518 width: dim,
1519 height: dim
1520 });
1521 if (cls) {
1522 use.classed(cls, true);
1523 }
1524 return use;
1525 }
1526
1527 function appendGlyph(svg, ox, oy, dim, iid, cls) {
1528 appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
1529 }
1530
1531 function appendBadge(svg, ox, oy, dim, iid, cls) {
1532 appendUse(svg, ox, oy, dim, iid,cls ).classed('badgeIcon', true);
1533 }
1534
1535 function attachUiBadge(svg) {
1536 appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
1537 }
1538
Simon Hunt434cf142014-11-24 11:10:28 -08001539 function visVal(b) {
1540 return b ? 'visible' : 'hidden';
1541 }
1542
Simon Hunta5e89142014-11-14 07:00:33 -08001543 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -08001544 // force layout modification functions
1545
1546 function translate(x, y) {
1547 return 'translate(' + x + ',' + y + ')';
1548 }
Simon Huntd6f5a272014-11-29 23:45:50 -08001549 function scale(x,y) {
1550 return 'scale(' + x + ',' + y + ')';
1551 }
1552 function skewX(x) {
1553 return 'skewX(' + x + ')';
1554 }
Simon Hunte2575b62014-11-18 15:25:53 -08001555 function rotate(deg) {
1556 return 'rotate(' + deg + ')';
1557 }
1558
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001559 function missMsg(what, id) {
1560 return '\n[' + what + '] "' + id + '" missing ';
1561 }
1562
1563 function linkEndPoints(srcId, dstId) {
1564 var srcNode = network.lookup[srcId],
1565 dstNode = network.lookup[dstId],
1566 sMiss = !srcNode ? missMsg('src', srcId) : '',
1567 dMiss = !dstNode ? missMsg('dst', dstId) : '';
1568
1569 if (sMiss || dMiss) {
1570 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
1571 return null;
1572 }
1573 return {
1574 source: srcNode,
1575 target: dstNode,
1576 x1: srcNode.x,
1577 y1: srcNode.y,
1578 x2: dstNode.x,
1579 y2: dstNode.y
1580 };
1581 }
1582
Simon Hunt56d51852014-11-09 13:03:35 -08001583 function createHostLink(host) {
1584 var src = host.id,
1585 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -08001586 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001587 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -08001588
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001589 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -08001590 return null;
1591 }
1592
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001593 // Synthesize link ...
1594 $.extend(lnk, {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001595 key: id,
Simon Hunt56d51852014-11-09 13:03:35 -08001596 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001597
1598 type: function () { return 'hostLink'; },
Simon Hunt6d9bd032014-11-28 22:16:40 -08001599 online: function () {
1600 // hostlink target is edge switch
1601 return lnk.target.online;
1602 },
Simon Hunt8257f4c2014-11-16 19:34:54 -08001603 linkWidth: function () { return 1; }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001604 });
Simon Hunt99c13842014-11-06 18:23:12 -08001605 return lnk;
1606 }
1607
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001608 function createLink(link) {
Simon Hunta6a9fe72014-11-20 11:17:12 -08001609 var lnk = linkEndPoints(link.src, link.dst);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001610
1611 if (!lnk) {
1612 return null;
1613 }
1614
Simon Hunt8257f4c2014-11-16 19:34:54 -08001615 $.extend(lnk, {
1616 key: link.id,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001617 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001618 fromSource: link,
1619
1620 // functions to aggregate dual link state
1621 type: function () {
1622 var s = lnk.fromSource,
1623 t = lnk.fromTarget;
1624 return (s && s.type) || (t && t.type) || defaultLinkType;
1625 },
1626 online: function () {
1627 var s = lnk.fromSource,
Simon Hunt6d9bd032014-11-28 22:16:40 -08001628 t = lnk.fromTarget,
1629 both = lnk.source.online && lnk.target.online;
1630 return both && ((s && s.online) || (t && t.online));
Simon Hunt8257f4c2014-11-16 19:34:54 -08001631 },
1632 linkWidth: function () {
1633 var s = lnk.fromSource,
1634 t = lnk.fromTarget,
1635 ws = (s && s.linkWidth) || 0,
1636 wt = (t && t.linkWidth) || 0;
1637 return Math.max(ws, wt);
1638 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001639 });
1640 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -08001641 }
1642
Simon Hunte2575b62014-11-18 15:25:53 -08001643 function removeLinkLabels() {
1644 network.links.forEach(function (d) {
1645 d.label = '';
1646 });
1647 }
1648
Simon Hunt434cf142014-11-24 11:10:28 -08001649 function showHostVis(el) {
1650 el.style('visibility', visVal(showHosts));
1651 }
1652
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001653 var widthRatio = 1.4,
1654 linkScale = d3.scale.linear()
1655 .domain([1, 12])
1656 .range([widthRatio, 12 * widthRatio])
1657 .clamp(true);
1658
Simon Hunt99c13842014-11-06 18:23:12 -08001659 function updateLinks() {
1660 link = linkG.selectAll('.link')
Simon Hunt8257f4c2014-11-16 19:34:54 -08001661 .data(network.links, function (d) { return d.key; });
Simon Hunt99c13842014-11-06 18:23:12 -08001662
1663 // operate on existing links, if necessary
1664 // link .foo() .bar() ...
1665
1666 // operate on entering links:
1667 var entering = link.enter()
1668 .append('line')
1669 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001670 x1: function (d) { return d.x1; },
1671 y1: function (d) { return d.y1; },
1672 x2: function (d) { return d.x2; },
1673 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001674 stroke: config.topo.linkInColor,
1675 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -08001676 });
1677
1678 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -08001679 entering.each(function (d) {
1680 var link = d3.select(this);
1681 // provide ref to element selection from backing data....
1682 d.el = link;
Simon Hunt8257f4c2014-11-16 19:34:54 -08001683 restyleLinkElement(d);
Simon Hunt434cf142014-11-24 11:10:28 -08001684 if (d.type() === 'hostLink') {
1685 showHostVis(link);
1686 }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001687 });
Thomas Vachuska4830d392014-11-09 17:09:56 -08001688
1689 // operate on both existing and new links, if necessary
1690 //link .foo() .bar() ...
1691
Simon Hunte2575b62014-11-18 15:25:53 -08001692 // apply or remove labels
1693 var labelData = getLabelData();
1694 applyLinkLabels(labelData);
1695
Thomas Vachuska4830d392014-11-09 17:09:56 -08001696 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -08001697 link.exit()
Simon Hunt6d9bd032014-11-28 22:16:40 -08001698 .attr('stroke-dasharray', '3 3')
Simon Hunt13bf9c82014-11-18 07:26:44 -08001699 .style('opacity', 0.5)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001700 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -08001701 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001702 .attr({
Simon Hunt6d9bd032014-11-28 22:16:40 -08001703 'stroke-dasharray': '3 12',
Simon Hunt13bf9c82014-11-18 07:26:44 -08001704 stroke: config.topo.linkOutColor,
1705 'stroke-width': config.topo.linkOutWidth
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001706 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001707 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -08001708 .remove();
Simon Hunte2575b62014-11-18 15:25:53 -08001709
1710 // NOTE: invoke a single tick to force the labels to position
1711 // onto their links.
1712 tick();
1713 }
1714
1715 function getLabelData() {
1716 // create the backing data for showing labels..
1717 var data = [];
1718 link.each(function (d) {
1719 if (d.label) {
1720 data.push({
1721 id: 'lab-' + d.key,
1722 key: d.key,
1723 label: d.label,
1724 ldata: d
1725 });
1726 }
1727 });
1728 return data;
1729 }
1730
1731 var linkLabelOffset = '0.3em';
1732
1733 function applyLinkLabels(data) {
1734 var entering;
1735
1736 linkLabel = linkLabelG.selectAll('.linkLabel')
1737 .data(data, function (d) { return d.id; });
1738
Simon Hunt56a2ea42014-11-19 12:39:31 -08001739 // for elements already existing, we need to update the text
1740 // and adjust the rectangle size to fit
1741 linkLabel.each(function (d) {
1742 var el = d3.select(this),
1743 rect = el.select('rect'),
1744 text = el.select('text');
1745 text.text(d.label);
1746 rect.attr(rectAroundText(el));
1747 });
Thomas Vachuskaf75b7ab2014-11-19 12:15:55 -08001748
Simon Hunte2575b62014-11-18 15:25:53 -08001749 entering = linkLabel.enter().append('g')
1750 .classed('linkLabel', true)
1751 .attr('id', function (d) { return d.id; });
1752
1753 entering.each(function (d) {
1754 var el = d3.select(this),
1755 rect,
1756 text,
1757 parms = {
1758 x1: d.ldata.x1,
1759 y1: d.ldata.y1,
1760 x2: d.ldata.x2,
1761 y2: d.ldata.y2
1762 };
1763
1764 d.el = el;
1765 rect = el.append('rect');
1766 text = el.append('text').text(d.label);
1767 rect.attr(rectAroundText(el));
1768 text.attr('dy', linkLabelOffset);
1769
1770 el.attr('transform', transformLabel(parms));
1771 });
1772
1773 // Remove any links that are no longer required.
1774 linkLabel.exit().remove();
1775 }
1776
1777 function rectAroundText(el) {
1778 var text = el.select('text'),
1779 box = text.node().getBBox();
1780
1781 // translate the bbox so that it is centered on [x,y]
1782 box.x = -box.width / 2;
1783 box.y = -box.height / 2;
1784
1785 // add padding
1786 box.x -= 1;
1787 box.width += 2;
1788 return box;
1789 }
1790
1791 function transformLabel(p) {
1792 var dx = p.x2 - p.x1,
1793 dy = p.y2 - p.y1,
1794 xMid = dx/2 + p.x1,
1795 yMid = dy/2 + p.y1;
Simon Hunte2575b62014-11-18 15:25:53 -08001796 return translate(xMid, yMid);
Simon Hunt99c13842014-11-06 18:23:12 -08001797 }
1798
1799 function createDeviceNode(device) {
1800 // start with the object as is
1801 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -08001802 type = device.type,
Simon Huntc72967b2014-11-20 09:21:42 -08001803 svgCls = type ? 'node device ' + type : 'node device',
1804 labels = device.labels || [];
1805
Simon Hunt99c13842014-11-06 18:23:12 -08001806 // Augment as needed...
1807 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -08001808 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -08001809 positionNode(node);
Simon Hunt99c13842014-11-06 18:23:12 -08001810 return node;
1811 }
1812
Simon Hunt56d51852014-11-09 13:03:35 -08001813 function createHostNode(host) {
1814 // start with the object as is
1815 var node = host;
1816
1817 // Augment as needed...
1818 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001819 if (!node.type) {
Simon Hunt209155e2014-11-21 12:16:09 -08001820 node.type = 'endstation';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001821 }
Simon Hunt7fa116d2014-11-17 14:16:55 -08001822 node.svgClass = 'node host ' + node.type;
Simon Hunt56d51852014-11-09 13:03:35 -08001823 positionNode(node);
Simon Hunt56d51852014-11-09 13:03:35 -08001824 return node;
1825 }
1826
Simon Hunt62c47542014-11-22 22:16:32 -08001827 function positionNode(node, forUpdate) {
Simon Hunt99c13842014-11-06 18:23:12 -08001828 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -08001829 x = meta && meta.x,
1830 y = meta && meta.y,
1831 xy;
Simon Hunt99c13842014-11-06 18:23:12 -08001832
Simon Huntac9e24f2014-11-12 10:12:21 -08001833 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -08001834 if (x && y) {
1835 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001836 node.px = node.x = x;
1837 node.py = node.y = y;
Simon Huntac9e24f2014-11-12 10:12:21 -08001838 return;
Simon Hunt99c13842014-11-06 18:23:12 -08001839 }
Simon Huntac9e24f2014-11-12 10:12:21 -08001840
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001841 var location = node.location;
1842 if (location && location.type === 'latlng') {
Simon Hunt1b18aa52014-11-29 17:57:55 -08001843 var coord = geoMapProj([location.lng, location.lat]);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001844 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001845 node.px = node.x = coord[0];
1846 node.py = node.y = coord[1];
Simon Hunt62c47542014-11-22 22:16:32 -08001847 return true;
1848 }
1849
1850 // if this is a node update (not a node add).. skip randomizer
1851 if (forUpdate) {
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001852 return;
1853 }
1854
Simon Huntac9e24f2014-11-12 10:12:21 -08001855 // Note: Placing incoming unpinned nodes at exactly the same point
1856 // (center of the view) causes them to explode outwards when
1857 // the force layout kicks in. So, we spread them out a bit
1858 // initially, to provide a more serene layout convergence.
1859 // Additionally, if the node is a host, we place it near
1860 // the device it is connected to.
1861
1862 function spread(s) {
1863 return Math.floor((Math.random() * s) - s/2);
1864 }
1865
1866 function randDim(dim) {
1867 return dim / 2 + spread(dim * 0.7071);
1868 }
1869
1870 function rand() {
1871 return {
1872 x: randDim(network.view.width()),
1873 y: randDim(network.view.height())
1874 };
1875 }
1876
1877 function near(node) {
1878 var min = 12,
1879 dx = spread(12),
1880 dy = spread(12);
1881 return {
1882 x: node.x + min + dx,
1883 y: node.y + min + dy
1884 };
1885 }
1886
1887 function getDevice(cp) {
1888 var d = network.lookup[cp.device];
1889 return d || rand();
1890 }
1891
1892 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1893 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -08001894 }
1895
Simon Hunt99c13842014-11-06 18:23:12 -08001896
Simon Huntc72967b2014-11-20 09:21:42 -08001897 function iconGlyphUrl(d) {
1898 var which = d.type || 'unknown';
1899 return '#' + which;
1900 }
1901
Simon Hunt99c13842014-11-06 18:23:12 -08001902 // returns the newly computed bounding box of the rectangle
1903 function adjustRectToFitText(n) {
1904 var text = n.select('text'),
1905 box = text.node().getBBox(),
1906 lab = config.labels;
1907
1908 text.attr('text-anchor', 'middle')
1909 .attr('y', '-0.8em')
1910 .attr('x', lab.imgPad/2);
1911
1912 // translate the bbox so that it is centered on [x,y]
1913 box.x = -box.width / 2;
1914 box.y = -box.height / 2;
1915
1916 // add padding
1917 box.x -= (lab.padLR + lab.imgPad/2);
1918 box.width += lab.padLR * 2 + lab.imgPad;
1919 box.y -= lab.padTB;
1920 box.height += lab.padTB * 2;
1921
1922 return box;
1923 }
1924
Simon Hunt1a9eff92014-11-07 11:06:34 -08001925 function mkSvgClass(d) {
1926 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1927 }
1928
Simon Hunt7cd48f32014-11-09 23:42:50 -08001929 function hostLabel(d) {
1930 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1931 return d.labels[idx];
1932 }
1933 function deviceLabel(d) {
1934 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1935 return d.labels[idx];
1936 }
Simon Huntc72967b2014-11-20 09:21:42 -08001937 function trimLabel(label) {
1938 return (label && label.trim()) || '';
1939 }
1940
1941 function emptyBox() {
1942 return {
1943 x: -2,
1944 y: -2,
1945 width: 4,
1946 height: 4
1947 };
Simon Hunt7cd48f32014-11-09 23:42:50 -08001948 }
1949
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001950 function updateDeviceLabel(d) {
Simon Huntc72967b2014-11-20 09:21:42 -08001951 var label = trimLabel(deviceLabel(d)),
1952 noLabel = !label,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001953 node = d.el,
Simon Huntc72967b2014-11-20 09:21:42 -08001954 box,
1955 dx,
Simon Hunt395a70c2014-11-22 23:17:40 -08001956 dy,
1957 cfg = config.icons.device;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001958
1959 node.select('text')
1960 .text(label)
1961 .style('opacity', 0)
1962 .transition()
1963 .style('opacity', 1);
1964
Simon Huntc72967b2014-11-20 09:21:42 -08001965 if (noLabel) {
1966 box = emptyBox();
Simon Hunt395a70c2014-11-22 23:17:40 -08001967 dx = -cfg.dim/2;
1968 dy = -cfg.dim/2;
Simon Huntc72967b2014-11-20 09:21:42 -08001969 } else {
1970 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08001971 dx = box.x + cfg.xoff;
1972 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08001973 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001974
1975 node.select('rect')
1976 .transition()
1977 .attr(box);
1978
Simon Huntc72967b2014-11-20 09:21:42 -08001979 node.select('g.deviceIcon')
Thomas Vachuska89543292014-11-19 11:28:33 -08001980 .transition()
Simon Huntc72967b2014-11-20 09:21:42 -08001981 .attr('transform', translate(dx, dy));
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001982 }
1983
1984 function updateHostLabel(d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001985 var label = trimLabel(hostLabel(d));
1986 d.el.select('text').text(label);
Simon Huntbb282f52014-11-10 11:08:19 -08001987 }
1988
Simon Hunt434cf142014-11-24 11:10:28 -08001989 function updateHostVisibility() {
1990 var v = visVal(showHosts);
1991 nodeG.selectAll('.host').style('visibility', v);
1992 linkG.selectAll('.hostLink').style('visibility', v);
1993 }
1994
Simon Hunt6d9bd032014-11-28 22:16:40 -08001995 function findOfflineNodes() {
1996 var a = [];
1997 network.nodes.forEach(function (d) {
1998 if (d.class === 'device' && !d.online) {
1999 a.push(d);
2000 }
2001 });
2002 return a;
2003 }
2004
2005 function updateOfflineVisibility(dev) {
2006 var so = showOffline,
2007 sh = showHosts,
2008 vb = 'visibility',
2009 v, off, al, ah, db, b;
2010
2011 function updAtt(show) {
2012 al.forEach(function (d) {
2013 b = show && ((d.type() !== 'hostLink') || sh);
2014 d.el.style(vb, visVal(b));
2015 });
2016 ah.forEach(function (d) {
2017 b = show && sh;
2018 d.el.style(vb, visVal(b));
2019 });
2020 }
2021
2022 if (dev) {
2023 // updating a specific device that just toggled off/on-line
2024 db = dev.online || so;
2025 dev.el.style(vb, visVal(db));
2026 al = findAttachedLinks(dev.id);
2027 ah = findAttachedHosts(dev.id);
2028 updAtt(db);
2029 } else {
2030 // updating all offline devices
2031 v = visVal(so);
2032 off = findOfflineNodes();
2033 off.forEach(function (d) {
2034 d.el.style(vb, v);
2035 al = findAttachedLinks(d.id);
2036 ah = findAttachedHosts(d.id);
2037 updAtt(so);
2038 });
2039 }
2040 }
2041
Simon Hunt6ac93f32014-11-13 12:17:27 -08002042 function nodeMouseOver(d) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002043 if (hovered != d) {
2044 hovered = d;
2045 requestTrafficForMode();
2046 }
Simon Hunt6ac93f32014-11-13 12:17:27 -08002047 }
2048
2049 function nodeMouseOut(d) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002050 if (hovered != null) {
2051 hovered = null;
2052 requestTrafficForMode();
2053 }
Simon Hunt6ac93f32014-11-13 12:17:27 -08002054 }
Simon Huntbb282f52014-11-10 11:08:19 -08002055
Simon Hunteb1514d2014-11-20 09:57:29 -08002056 function addHostIcon(node, radius, iid) {
Thomas Vachuska89543292014-11-19 11:28:33 -08002057 var dim = radius * 1.5,
2058 xlate = -dim / 2;
2059
Simon Hunteb1514d2014-11-20 09:57:29 -08002060 node.append('use').attr({
2061 class: 'glyphIcon hostIcon',
2062 transform: translate(xlate,xlate),
2063 'xlink:href': iid,
2064 width: dim,
2065 height: dim
2066 });
Thomas Vachuska89543292014-11-19 11:28:33 -08002067 }
2068
Simon Hunt99c13842014-11-06 18:23:12 -08002069 function updateNodes() {
2070 node = nodeG.selectAll('.node')
2071 .data(network.nodes, function (d) { return d.id; });
2072
Simon Hunt62c47542014-11-22 22:16:32 -08002073 // operate on existing nodes...
2074 node.filter('.device').each(function (d) {
Simon Hunt62c47542014-11-22 22:16:32 -08002075 var node = d.el;
2076 node.classed('online', d.online);
2077 updateDeviceLabel(d);
2078 positionNode(d, true);
2079 });
2080
2081 node.filter('.host').each(function (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08002082 updateHostLabel(d);
2083 positionNode(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -08002084 });
Simon Hunt99c13842014-11-06 18:23:12 -08002085
2086 // operate on entering nodes:
2087 var entering = node.enter()
2088 .append('g')
2089 .attr({
2090 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -08002091 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -08002092 transform: function (d) { return translate(d.x, d.y); },
2093 opacity: 0
2094 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002095 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -08002096 .on('mouseover', nodeMouseOver)
2097 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -08002098 .transition()
2099 .attr('opacity', 1);
2100
2101 // augment device nodes...
2102 entering.filter('.device').each(function (d) {
2103 var node = d3.select(this),
Simon Huntc72967b2014-11-20 09:21:42 -08002104 label = trimLabel(deviceLabel(d)),
2105 noLabel = !label,
Simon Hunt99c13842014-11-06 18:23:12 -08002106 box;
2107
Simon Hunt7cd48f32014-11-09 23:42:50 -08002108 // provide ref to element from backing data....
2109 d.el = node;
2110
Simon Hunt62c47542014-11-22 22:16:32 -08002111 node.append('rect').attr({ rx: 5, ry: 5 });
2112 node.append('text').text(label).attr('dy', '1.1em');
Simon Hunt99c13842014-11-06 18:23:12 -08002113 box = adjustRectToFitText(node);
Simon Hunta3dd9572014-11-20 15:22:41 -08002114 node.select('rect').attr(box);
Simon Huntc72967b2014-11-20 09:21:42 -08002115 addDeviceIcon(node, box, noLabel, iconGlyphUrl(d));
Simon Huntc7ee0662014-11-05 16:44:37 -08002116 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08002117
Thomas Vachuska89543292014-11-19 11:28:33 -08002118
Simon Hunt56d51852014-11-09 13:03:35 -08002119 // augment host nodes...
2120 entering.filter('.host').each(function (d) {
2121 var node = d3.select(this),
Simon Hunt395a70c2014-11-22 23:17:40 -08002122 cfg = config.icons.host,
2123 r = cfg.radius[d.type] || cfg.defaultRadius,
Thomas Vachuska89543292014-11-19 11:28:33 -08002124 textDy = r + 10,
Simon Hunteb1514d2014-11-20 09:57:29 -08002125 iid = iconGlyphUrl(d);
Simon Hunt56d51852014-11-09 13:03:35 -08002126
Simon Hunt7cd48f32014-11-09 23:42:50 -08002127 // provide ref to element from backing data....
2128 d.el = node;
Simon Hunt434cf142014-11-24 11:10:28 -08002129 showHostVis(node);
Simon Hunt7cd48f32014-11-09 23:42:50 -08002130
Simon Hunt62c47542014-11-22 22:16:32 -08002131 node.append('circle').attr('r', r);
Simon Hunteb1514d2014-11-20 09:57:29 -08002132 if (iid) {
2133 addHostIcon(node, r, iid);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002134 }
Simon Hunt56d51852014-11-09 13:03:35 -08002135 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08002136 .text(hostLabel)
Thomas Vachuska89543292014-11-19 11:28:33 -08002137 .attr('dy', textDy)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002138 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08002139 });
Simon Huntc7ee0662014-11-05 16:44:37 -08002140
Simon Hunt99c13842014-11-06 18:23:12 -08002141 // operate on both existing and new nodes, if necessary
Simon Huntb0ecfa52014-11-23 21:05:12 -08002142 updateDeviceColors();
Simon Huntc7ee0662014-11-05 16:44:37 -08002143
Simon Hunt99c13842014-11-06 18:23:12 -08002144 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08002145 // Note that the node is removed after 2 seconds.
2146 // Sub element animations should be shorter than 2 seconds.
2147 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08002148 .transition()
2149 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08002150 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08002151 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08002152
2153 // host node exits....
2154 exiting.filter('.host').each(function (d) {
Simon Huntca867ac2014-11-28 18:07:35 -08002155 var node = d.el;
2156 node.select('use')
2157 .style('opacity', 0.5)
2158 .transition()
2159 .duration(800)
2160 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002161
2162 node.select('text')
2163 .style('opacity', 0.5)
2164 .transition()
Simon Huntca867ac2014-11-28 18:07:35 -08002165 .duration(800)
Simon Huntea80eb42014-11-11 13:46:57 -08002166 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002167
Thomas Vachuska89543292014-11-19 11:28:33 -08002168 node.select('circle')
2169 .style('stroke-fill', '#555')
2170 .style('fill', '#888')
Simon Huntea80eb42014-11-11 13:46:57 -08002171 .style('opacity', 0.5)
2172 .transition()
2173 .duration(1500)
2174 .attr('r', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002175 });
2176
Simon Huntca867ac2014-11-28 18:07:35 -08002177 // device node exits....
2178 exiting.filter('.device').each(function (d) {
2179 var node = d.el;
2180 node.select('use')
2181 .style('opacity', 0.5)
2182 .transition()
2183 .duration(800)
2184 .style('opacity', 0);
2185
2186 node.selectAll('rect')
2187 .style('stroke-fill', '#555')
2188 .style('fill', '#888')
2189 .style('opacity', 0.5);
2190 });
Simon Huntd6f5a272014-11-29 23:45:50 -08002191 fResume();
Simon Huntc7ee0662014-11-05 16:44:37 -08002192 }
2193
Simon Hunt95dad922014-11-24 09:43:31 -08002194 var dCol = {
2195 black: '#000',
2196 paleblue: '#acf',
2197 offwhite: '#ddd',
2198 midgrey: '#888',
2199 lightgrey: '#bbb',
2200 orange: '#f90'
2201 };
2202
Simon Huntb0ecfa52014-11-23 21:05:12 -08002203 // note: these are the device icon colors without affinity
Simon Hunt95dad922014-11-24 09:43:31 -08002204 var dColTheme = {
Simon Huntb0ecfa52014-11-23 21:05:12 -08002205 light: {
2206 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08002207 glyph: dCol.black,
2208 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08002209 },
2210 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08002211 glyph: dCol.midgrey,
2212 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08002213 }
2214 },
Simon Hunt95dad922014-11-24 09:43:31 -08002215 // TODO: theme
Simon Huntb0ecfa52014-11-23 21:05:12 -08002216 dark: {
2217 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08002218 glyph: dCol.black,
2219 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08002220 },
2221 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08002222 glyph: dCol.midgrey,
2223 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08002224 }
2225 }
2226 };
2227
2228 function devBaseColor(d) {
2229 var t = network.view.getTheme(),
2230 o = d.online ? 'online' : 'offline';
Simon Hunt95dad922014-11-24 09:43:31 -08002231 return dColTheme[t][o];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002232 }
2233
2234 function setDeviceColor(d) {
2235 var o = d.online,
2236 s = d.el.classed('selected'),
2237 c = devBaseColor(d),
2238 a = instColor(d.master, o),
2239 g, r,
2240 icon = d.el.select('g.deviceIcon');
2241
2242 if (s) {
2243 g = c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002244 r = dColTheme.sel;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002245 } else if (colorAffinity) {
2246 g = o ? a : c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002247 r = o ? dCol.offwhite : a;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002248 } else {
2249 g = c.glyph;
2250 r = c.rect;
2251 }
2252
2253 icon.select('use')
2254 .style('fill', g);
2255 icon.select('rect')
2256 .style('fill', r);
2257 }
2258
Simon Huntc72967b2014-11-20 09:21:42 -08002259 function addDeviceIcon(node, box, noLabel, iid) {
2260 var cfg = config.icons.device,
2261 dx,
2262 dy,
2263 g;
2264
2265 if (noLabel) {
2266 box = emptyBox();
2267 dx = -cfg.dim/2;
2268 dy = -cfg.dim/2;
2269 } else {
2270 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08002271 dx = box.x + cfg.xoff;
2272 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08002273 }
2274
Simon Hunteb1514d2014-11-20 09:57:29 -08002275 g = node.append('g')
2276 .attr('class', 'glyphIcon deviceIcon')
Simon Huntc72967b2014-11-20 09:21:42 -08002277 .attr('transform', translate(dx, dy));
2278
2279 g.append('rect').attr({
2280 x: 0,
2281 y: 0,
2282 rx: cfg.rx,
2283 width: cfg.dim,
2284 height: cfg.dim
2285 });
2286
2287 g.append('use').attr({
2288 'xlink:href': iid,
2289 width: cfg.dim,
2290 height: cfg.dim
2291 });
2292
Simon Huntc72967b2014-11-20 09:21:42 -08002293 }
2294
Simon Hunt7b403bc2014-11-22 19:01:00 -08002295 function find(key, array, tag) {
Simon Huntca867ac2014-11-28 18:07:35 -08002296 var _tag = tag || 'id',
Simon Hunt7b403bc2014-11-22 19:01:00 -08002297 idx, n, d;
2298 for (idx = 0, n = array.length; idx < n; idx++) {
2299 d = array[idx];
2300 if (d[_tag] === key) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002301 return idx;
2302 }
2303 }
2304 return -1;
2305 }
2306
Simon Huntca867ac2014-11-28 18:07:35 -08002307 function removeLinkElement(d) {
2308 var idx = find(d.key, network.links, 'key'),
Simon Hunt8257f4c2014-11-16 19:34:54 -08002309 removed;
2310 if (idx >=0) {
2311 // remove from links array
2312 removed = network.links.splice(idx, 1);
2313 // remove from lookup cache
2314 delete network.lookup[removed[0].key];
2315 updateLinks();
Simon Huntd6f5a272014-11-29 23:45:50 -08002316 fResume();
Simon Hunt8257f4c2014-11-16 19:34:54 -08002317 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002318 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002319
Simon Huntca867ac2014-11-28 18:07:35 -08002320 function removeHostElement(d, upd) {
2321 var lu = network.lookup;
Simon Hunt44031102014-11-11 13:20:36 -08002322 // first, remove associated hostLink...
Simon Huntca867ac2014-11-28 18:07:35 -08002323 removeLinkElement(d.linkData);
2324
2325 // remove hostLink bindings
2326 delete lu[d.ingress];
2327 delete lu[d.egress];
Simon Hunt44031102014-11-11 13:20:36 -08002328
2329 // remove from lookup cache
Simon Huntca867ac2014-11-28 18:07:35 -08002330 delete lu[d.id];
Simon Hunt44031102014-11-11 13:20:36 -08002331 // remove from nodes array
Simon Huntca867ac2014-11-28 18:07:35 -08002332 var idx = find(d.id, network.nodes);
2333 network.nodes.splice(idx, 1);
2334 // remove from SVG
2335 // NOTE: upd is false if we were called from removeDeviceElement()
2336 if (upd) {
2337 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -08002338 fResume();
Simon Huntca867ac2014-11-28 18:07:35 -08002339 }
2340 }
2341
2342
2343 function removeDeviceElement(d) {
2344 var id = d.id;
2345 // first, remove associated hosts and links..
2346 findAttachedHosts(id).forEach(removeHostElement);
2347 findAttachedLinks(id).forEach(removeLinkElement);
2348
2349 // remove from lookup cache
2350 delete network.lookup[id];
2351 // remove from nodes array
2352 var idx = find(id, network.nodes);
Simon Hunt44031102014-11-11 13:20:36 -08002353 network.nodes.splice(idx, 1);
2354 // remove from SVG
2355 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -08002356 fResume();
Simon Hunt44031102014-11-11 13:20:36 -08002357 }
2358
Simon Huntca867ac2014-11-28 18:07:35 -08002359 function findAttachedHosts(devId) {
2360 var hosts = [];
2361 network.nodes.forEach(function (d) {
2362 if (d.class === 'host' && d.cp.device === devId) {
2363 hosts.push(d);
2364 }
2365 });
2366 return hosts;
2367 }
2368
2369 function findAttachedLinks(devId) {
2370 var links = [];
2371 network.links.forEach(function (d) {
2372 if (d.source.id === devId || d.target.id === devId) {
2373 links.push(d);
2374 }
2375 });
2376 return links;
2377 }
Simon Hunt44031102014-11-11 13:20:36 -08002378
Simon Huntd6f5a272014-11-29 23:45:50 -08002379 function fResume() {
2380 if (!oblique) {
2381 network.force.resume();
2382 }
2383 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002384
Simon Huntd6f5a272014-11-29 23:45:50 -08002385 function fStart() {
2386 if (!oblique) {
2387 network.force.start();
2388 }
2389 }
2390
2391 var tickStuff = {
2392 nodeAttr: {
2393 transform: function (d) { return translate(d.x, d.y); }
2394 },
2395 linkAttr: {
Simon Huntc7ee0662014-11-05 16:44:37 -08002396 x1: function (d) { return d.source.x; },
2397 y1: function (d) { return d.source.y; },
2398 x2: function (d) { return d.target.x; },
2399 y2: function (d) { return d.target.y; }
Simon Huntd6f5a272014-11-29 23:45:50 -08002400 },
2401 linkLabelAttr: {
2402 transform: function (d) {
2403 var lnk = findLinkById(d.key);
Simon Hunte2575b62014-11-18 15:25:53 -08002404
Simon Huntd6f5a272014-11-29 23:45:50 -08002405 if (lnk) {
2406 var parms = {
2407 x1: lnk.source.x,
2408 y1: lnk.source.y,
2409 x2: lnk.target.x,
2410 y2: lnk.target.y
2411 };
2412 return transformLabel(parms);
2413 }
Thomas Vachuska4731f122014-11-20 04:56:19 -08002414 }
Simon Huntd6f5a272014-11-29 23:45:50 -08002415 }
2416 };
2417
2418 function tick() {
2419 node.attr(tickStuff.nodeAttr);
2420 link.attr(tickStuff.linkAttr);
2421 linkLabel.attr(tickStuff.linkLabelAttr);
Simon Huntc7ee0662014-11-05 16:44:37 -08002422 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08002423
2424 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002425 // Web-Socket for live data
2426
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002427 function findGuiSuccessor() {
2428 var idx = -1;
2429 onosOrder.forEach(function (d, i) {
2430 if (d.uiAttached) {
2431 idx = i;
2432 }
2433 });
2434
2435 for (var i = 0; i < onosOrder.length - 1; i++) {
2436 var ni = (idx + 1 + i) % onosOrder.length;
2437 if (onosOrder[ni].online) {
2438 return onosOrder[ni].ip;
2439 }
2440 }
2441 return null;
2442 }
2443
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002444 function webSockUrl() {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002445 var url = document.location.toString()
2446 .replace(/\#.*/, '')
2447 .replace('http://', 'ws://')
2448 .replace('https://', 'wss://')
2449 .replace('index.html', config.webSockUrl);
2450 if (guiSuccessor) {
2451 url = url.replace(location.hostname, guiSuccessor);
2452 }
2453 return url;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002454 }
2455
2456 webSock = {
2457 ws : null,
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002458 retries: 0,
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002459
2460 connect : function() {
2461 webSock.ws = new WebSocket(webSockUrl());
2462
2463 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08002464 noWebSock(false);
Thomas Vachuska47635c62014-11-22 01:21:36 -08002465 requestSummary();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002466 showInstances();
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002467 webSock.retries = 0;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002468 };
2469
2470 webSock.ws.onmessage = function(m) {
2471 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002472 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08002473 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002474 }
2475 };
2476
2477 webSock.ws.onclose = function(m) {
2478 webSock.ws = null;
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002479 guiSuccessor = findGuiSuccessor();
2480 if (guiSuccessor && webSock.retries < onosOrder.length) {
2481 webSock.retries++;
2482 webSock.connect();
2483 } else {
2484 noWebSock(true);
2485 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002486 };
2487 },
2488
2489 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002490 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002491 webSock._send(text);
2492 }
2493 },
2494
2495 _send : function(message) {
2496 if (webSock.ws) {
2497 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002498 } else if (config.useLiveData) {
Simon Hunt434cf142014-11-24 11:10:28 -08002499 console.warn('no web socket open', message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002500 } else {
Simon Hunt434cf142014-11-24 11:10:28 -08002501 console.log('WS Send: ', message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002502 }
2503 }
2504
2505 };
2506
Simon Hunt0c6d4192014-11-12 12:07:10 -08002507 function noWebSock(b) {
2508 mask.style('display',b ? 'block' : 'none');
2509 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002510
2511 function sendMessage(evType, payload) {
Simon Huntc1cc81c2014-11-29 14:59:01 -08002512 var p = payload || {},
2513 toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08002514 event: evType,
2515 sid: ++sid,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002516 payload: p
Simon Huntbb282f52014-11-10 11:08:19 -08002517 },
2518 asText = JSON.stringify(toSend);
2519 wsTraceTx(asText);
2520 webSock.send(asText);
Simon Huntc76ae892014-11-18 17:31:51 -08002521
2522 // Temporary measure for debugging UI behavior ...
2523 if (!config.useLiveData) {
2524 handleTestSend(toSend);
2525 }
Simon Huntbb282f52014-11-10 11:08:19 -08002526 }
2527
2528 function wsTraceTx(msg) {
2529 wsTrace('tx', msg);
2530 }
2531 function wsTraceRx(msg) {
2532 wsTrace('rx', msg);
2533 }
2534 function wsTrace(rxtx, msg) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002535 // console.log('[' + rxtx + '] ' + msg);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002536 }
2537
Simon Huntc76ae892014-11-18 17:31:51 -08002538 // NOTE: Temporary hardcoded example for showing detail pane
2539 // while we fine-
2540 // Probably should not merge this change...
2541 function handleTestSend(msg) {
2542 if (msg.event === 'requestDetails') {
2543 showDetails({
2544 event: 'showDetails',
2545 sid: 1001,
2546 payload: {
2547 "id": "of:0000ffffffffff09",
2548 "type": "roadm",
2549 "propOrder": [
2550 "Name",
2551 "Vendor",
2552 "H/W Version",
2553 "S/W Version",
2554 "-",
2555 "Latitude",
2556 "Longitude",
2557 "Ports"
2558 ],
2559 "props": {
2560 "Name": null,
2561 "Vendor": "Linc",
2562 "H/W Version": "OE",
2563 "S/W Version": "?",
2564 "-": "",
2565 "Latitude": "40.8",
2566 "Longitude": "73.1",
2567 "Ports": "2"
2568 }
2569 }
2570 });
2571 }
2572 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002573
2574 // ==============================
2575 // Selection stuff
2576
2577 function selectObject(obj, el) {
2578 var n,
Simon Hunta1162d82014-12-03 14:34:43 -08002579 ev = d3.event.sourceEvent;
Simon Hunt01095ff2014-11-13 16:37:29 -08002580
Simon Hunta1162d82014-12-03 14:34:43 -08002581 // if the meta or alt key is pressed, we are panning/zooming, so ignore
2582 if (ev.metaKey || ev.altKey) {
Simon Hunt01095ff2014-11-13 16:37:29 -08002583 return;
2584 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002585
2586 if (el) {
2587 n = d3.select(el);
2588 } else {
2589 node.each(function(d) {
2590 if (d == obj) {
2591 n = d3.select(el = this);
2592 }
2593 });
2594 }
2595 if (!n) return;
2596
Simon Hunta1162d82014-12-03 14:34:43 -08002597 if (ev.shiftKey && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002598 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002599 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002600 return;
2601 }
2602
Simon Hunta1162d82014-12-03 14:34:43 -08002603 if (!ev.shiftKey) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002604 deselectAll();
2605 }
2606
Simon Huntc31d5692014-11-12 13:27:18 -08002607 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002608 selectOrder.push(obj.id);
2609
2610 n.classed('selected', true);
Simon Huntb0ecfa52014-11-23 21:05:12 -08002611 updateDeviceColors(obj);
Simon Hunt61d04042014-11-11 17:27:16 -08002612 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002613 }
2614
2615 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08002616 var obj = selections[id],
2617 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002618 if (obj) {
2619 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08002620 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08002621 idx = $.inArray(id, selectOrder);
2622 if (idx >= 0) {
2623 selectOrder.splice(idx, 1);
2624 }
Simon Huntb0ecfa52014-11-23 21:05:12 -08002625 updateDeviceColors(obj.obj);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002626 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002627 }
2628
2629 function deselectAll() {
2630 // deselect all nodes in the network...
2631 node.classed('selected', false);
2632 selections = {};
2633 selectOrder = [];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002634 updateDeviceColors();
Simon Hunt61d04042014-11-11 17:27:16 -08002635 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002636 }
2637
Simon Huntb0ecfa52014-11-23 21:05:12 -08002638 function updateDeviceColors(d) {
2639 if (d) {
2640 setDeviceColor(d);
2641 } else {
2642 node.filter('.device').each(function (d) {
2643 setDeviceColor(d);
2644 });
2645 }
Thomas Vachuska47635c62014-11-22 01:21:36 -08002646 }
2647
Simon Hunt61d04042014-11-11 17:27:16 -08002648 // update the state of the detail pane, based on current selections
2649 function updateDetailPane() {
2650 var nSel = selectOrder.length;
2651 if (!nSel) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002652 emptySelect();
Simon Hunt61d04042014-11-11 17:27:16 -08002653 } else if (nSel === 1) {
2654 singleSelect();
2655 } else {
2656 multiSelect();
2657 }
2658 }
2659
Thomas Vachuska9edca302014-11-22 17:06:42 -08002660 function emptySelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002661 haveDetails = false;
Simon Hunt06811b72014-11-25 18:54:48 -08002662 hideDetailPane();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002663 cancelTraffic();
2664 }
2665
Simon Hunt61d04042014-11-11 17:27:16 -08002666 function singleSelect() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002667 // NOTE: detail is shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08002668 requestDetails();
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002669 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002670 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002671 }
2672
2673 function multiSelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002674 haveDetails = true;
Simon Huntb53e0682014-11-12 13:32:01 -08002675 populateMultiSelect();
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002676 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002677 requestTrafficForMode();
Simon Huntb53e0682014-11-12 13:32:01 -08002678 }
2679
2680 function addSep(tbody) {
2681 var tr = tbody.append('tr');
2682 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
2683 }
2684
2685 function addProp(tbody, label, value) {
2686 var tr = tbody.append('tr');
2687
2688 tr.append('td')
2689 .attr('class', 'label')
2690 .text(label + ' :');
2691
2692 tr.append('td')
2693 .attr('class', 'value')
2694 .text(value);
2695 }
2696
2697 function populateMultiSelect() {
2698 detailPane.empty();
2699
Simon Hunta3dd9572014-11-20 15:22:41 -08002700 var title = detailPane.append('h3'),
2701 table = detailPane.append('table'),
2702 tbody = table.append('tbody');
Simon Huntb53e0682014-11-12 13:32:01 -08002703
Thomas Vachuska4731f122014-11-20 04:56:19 -08002704 title.text('Selected Nodes');
Simon Huntb53e0682014-11-12 13:32:01 -08002705
2706 selectOrder.forEach(function (d, i) {
2707 addProp(tbody, i+1, d);
2708 });
Simon Huntd72bc702014-11-13 18:38:04 -08002709
2710 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08002711 }
2712
Thomas Vachuska47635c62014-11-22 01:21:36 -08002713 // TODO: refactor to consolidate with populateDetails
2714 function populateSummary(data) {
2715 summaryPane.empty();
2716
2717 var svg = summaryPane.append('svg'),
2718 iid = iconGlyphUrl(data);
2719
2720 var title = summaryPane.append('h2'),
2721 table = summaryPane.append('table'),
2722 tbody = table.append('tbody');
2723
2724 appendGlyph(svg, 0, 0, 40, iid);
2725
2726 svg.append('use')
2727 .attr({
2728 class: 'birdBadge',
2729 transform: translate(8,12),
2730 'xlink:href': '#bird',
2731 width: 24,
2732 height: 24,
2733 fill: '#fff'
2734 });
2735
2736 title.text('ONOS Summary');
2737
2738 data.propOrder.forEach(function(p) {
2739 if (p === '-') {
2740 addSep(tbody);
2741 } else {
2742 addProp(tbody, p, data.props[p]);
2743 }
2744 });
2745 }
2746
Simon Hunt61d04042014-11-11 17:27:16 -08002747 function populateDetails(data) {
2748 detailPane.empty();
2749
Simon Hunta6a9fe72014-11-20 11:17:12 -08002750 var svg = detailPane.append('svg'),
2751 iid = iconGlyphUrl(data);
2752
Simon Hunta3dd9572014-11-20 15:22:41 -08002753 var title = detailPane.append('h2'),
2754 table = detailPane.append('table'),
2755 tbody = table.append('tbody');
Simon Hunt61d04042014-11-11 17:27:16 -08002756
Simon Hunta6a9fe72014-11-20 11:17:12 -08002757 appendGlyph(svg, 0, 0, 40, iid);
2758 title.text(data.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002759
2760 data.propOrder.forEach(function(p) {
2761 if (p === '-') {
2762 addSep(tbody);
2763 } else {
2764 addProp(tbody, p, data.props[p]);
2765 }
2766 });
Simon Huntd72bc702014-11-13 18:38:04 -08002767
Thomas Vachuska4731f122014-11-20 04:56:19 -08002768 addSingleSelectActions(data);
Simon Hunt61d04042014-11-11 17:27:16 -08002769 }
2770
Thomas Vachuska4731f122014-11-20 04:56:19 -08002771 function addSingleSelectActions(data) {
Simon Huntd72bc702014-11-13 18:38:04 -08002772 detailPane.append('hr');
2773 // always want to allow 'show traffic'
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002774 addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
Thomas Vachuska4731f122014-11-20 04:56:19 -08002775
2776 if (data.type === 'switch') {
2777 addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
2778 }
Simon Huntd72bc702014-11-13 18:38:04 -08002779 }
2780
2781 function addMultiSelectActions() {
2782 detailPane.append('hr');
2783 // always want to allow 'show traffic'
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002784 addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002785 // if exactly two hosts are selected, also want 'add host intent'
2786 if (nSel() === 2 && allSelectionsClass('host')) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002787 addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2788 } else if (nSel() >= 2 && allSelectionsClass('host')) {
2789 addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002790 }
2791 }
2792
Simon Hunta5e89142014-11-14 07:00:33 -08002793 function addAction(panel, text, cb) {
2794 panel.append('div')
Simon Huntd72bc702014-11-13 18:38:04 -08002795 .classed('actionBtn', true)
2796 .text(text)
2797 .on('click', cb);
2798 }
2799
2800
Simon Hunt3c5ca542014-11-29 14:11:43 -08002801 // === Pan and Zoom behaviors...
2802
2803 function panZoom(translate, scale) {
2804 panZoomContainer.attr('transform',
2805 'translate(' + translate + ')scale(' + scale + ')');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002806 // keep the map lines constant width while zooming
Simon Hunt3c5ca542014-11-29 14:11:43 -08002807 bgImg.style('stroke-width', 2.0 / scale + 'px');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002808 }
2809
Simon Hunt3c5ca542014-11-29 14:11:43 -08002810 function resetPanZoom() {
2811 panZoom([0,0], 1);
2812 zoom.translate([0,0]).scale(1);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002813 }
2814
Simon Hunt3c5ca542014-11-29 14:11:43 -08002815 function setupPanZoom() {
Paul Greysonfcba0e82014-11-13 10:21:16 -08002816 function zoomed() {
Simon Hunta1162d82014-12-03 14:34:43 -08002817 var ev = d3.event.sourceEvent;
2818 // pan/zoom active when meta or alt key is pressed...
2819 if (ev.metaKey || ev.altKey) {
Simon Hunt3c5ca542014-11-29 14:11:43 -08002820 panZoom(d3.event.translate, d3.event.scale);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002821 }
2822 }
2823
2824 zoom = d3.behavior.zoom()
2825 .translate([0, 0])
2826 .scale(1)
Simon Hunt1b18aa52014-11-29 17:57:55 -08002827 .scaleExtent([0.25, 10])
Paul Greysonfcba0e82014-11-13 10:21:16 -08002828 .on("zoom", zoomed);
2829
2830 svg.call(zoom);
2831 }
2832
Simon Hunt61d04042014-11-11 17:27:16 -08002833 // ==============================
2834 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08002835
2836 function prepareScenario(view, ctx, dbg) {
2837 var sc = scenario,
2838 urlSc = sc.evDir + ctx + sc.evScenario;
2839
2840 if (!ctx) {
2841 view.alert("No scenario specified (null ctx)");
2842 return;
2843 }
2844
2845 sc.view = view;
2846 sc.ctx = ctx;
2847 sc.debug = dbg;
2848 sc.evNumber = 0;
2849
2850 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002851 var p = data && data.params || {},
2852 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08002853 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08002854
Simon Hunt56d51852014-11-09 13:03:35 -08002855 if (err) {
2856 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
2857 } else {
2858 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08002859 if (desc) {
2860 intro += '\n\n ' + desc.join('\n ');
2861 }
2862 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08002863 }
2864 });
2865
2866 }
2867
Simon Hunt9f1bced2014-12-02 14:36:39 -08002868 function setupDefs(svg) {
Simon Hunt7fa116d2014-11-17 14:16:55 -08002869 var defs = svg.append('defs');
Simon Hunt9f1bced2014-12-02 14:36:39 -08002870 gly.loadDefs(defs);
2871 d3u.loadGlow(defs);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002872 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002873
Simon Hunt395a70c2014-11-22 23:17:40 -08002874 function sendUpdateMeta(d, store) {
2875 var metaUi = {},
2876 ll;
2877
2878 if (store) {
Simon Hunt1b18aa52014-11-29 17:57:55 -08002879 ll = geoMapProj.invert([d.x, d.y]);
Simon Hunt62c47542014-11-22 22:16:32 -08002880 metaUi = {
2881 x: d.x,
2882 y: d.y,
2883 lng: ll[0],
2884 lat: ll[1]
2885 };
Simon Hunt395a70c2014-11-22 23:17:40 -08002886 }
Simon Hunt62c47542014-11-22 22:16:32 -08002887 d.metaUi = metaUi;
2888 sendMessage('updateMeta', {
2889 id: d.id,
2890 'class': d.class,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002891 memento: metaUi
Simon Hunt62c47542014-11-22 22:16:32 -08002892 });
2893 }
2894
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002895 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08002896 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08002897
Simon Hunta2994cc2014-12-02 14:19:15 -08002898 function init(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08002899 var w = view.width(),
2900 h = view.height(),
Simon Hunt1b18aa52014-11-29 17:57:55 -08002901 fcfg = config.force;
Simon Hunt195cb382014-11-03 17:50:51 -08002902
Simon Hunt142d0032014-11-04 20:13:09 -08002903 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002904 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
2905 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002906 setSize(svg, view);
2907
Simon Hunt9f1bced2014-12-02 14:36:39 -08002908 // load glyphs, filters, and other definitions...
2909 setupDefs(svg);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002910
Simon Hunt3c5ca542014-11-29 14:11:43 -08002911 panZoomContainer = svg.append('g').attr('id', 'panZoomContainer');
2912 setupPanZoom();
Paul Greysonfcba0e82014-11-13 10:21:16 -08002913
Simon Huntc7ee0662014-11-05 16:44:37 -08002914 // group for the topology
Simon Hunt3c5ca542014-11-29 14:11:43 -08002915 topoG = panZoomContainer.append('g')
Simon Hunt1b18aa52014-11-29 17:57:55 -08002916 .attr('id', 'topo-G');
Simon Huntc7ee0662014-11-05 16:44:37 -08002917
Simon Hunte2575b62014-11-18 15:25:53 -08002918 // subgroups for links, link labels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002919 linkG = topoG.append('g').attr('id', 'links');
Simon Hunte2575b62014-11-18 15:25:53 -08002920 linkLabelG = topoG.append('g').attr('id', 'linkLabels');
Simon Huntc7ee0662014-11-05 16:44:37 -08002921 nodeG = topoG.append('g').attr('id', 'nodes');
2922
Simon Hunte2575b62014-11-18 15:25:53 -08002923 // selection of links, linkLabels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002924 link = linkG.selectAll('.link');
Simon Hunte2575b62014-11-18 15:25:53 -08002925 linkLabel = linkLabelG.selectAll('.linkLabel');
Simon Huntc7ee0662014-11-05 16:44:37 -08002926 node = nodeG.selectAll('.node');
2927
Simon Hunt7cd48f32014-11-09 23:42:50 -08002928 function chrg(d) {
2929 return fcfg.charge[d.class] || -12000;
2930 }
Simon Hunt99c13842014-11-06 18:23:12 -08002931 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002932 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08002933 }
2934 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002935 // 0.0 - 1.0
2936 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08002937 }
2938
Simon Hunt1a9eff92014-11-07 11:06:34 -08002939 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002940 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002941 }
2942
2943 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08002944 // once we've finished moving, pin the node in position
2945 d.fixed = true;
2946 d3.select(self).classed('fixed', true);
2947 if (config.useLiveData) {
Simon Hunt395a70c2014-11-22 23:17:40 -08002948 sendUpdateMeta(d, true);
Simon Hunta255a2c2014-11-13 22:29:35 -08002949 } else {
2950 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08002951 }
2952 }
2953
Simon Hunt6e18fe32014-11-29 13:35:41 -08002954 // predicate that indicates when dragging is active
2955 function dragEnabled() {
Simon Hunta1162d82014-12-03 14:34:43 -08002956 var ev = d3.event.sourceEvent;
2957 // nodeLock means we aren't allowing nodes to be dragged...
2958 // meta or alt key pressed means we are zooming/panning...
2959 return !nodeLock && !(ev.metaKey || ev.altKey);
Simon Huntc2367d52014-11-29 19:30:23 -08002960 }
2961
2962 // predicate that indicates when clicking is active
2963 function clickEnabled() {
2964 return true;
Simon Hunt6e18fe32014-11-29 13:35:41 -08002965 }
2966
Simon Huntc7ee0662014-11-05 16:44:37 -08002967 // set up the force layout
2968 network.force = d3.layout.force()
Simon Hunt1b18aa52014-11-29 17:57:55 -08002969 .size([w, h])
Simon Huntc7ee0662014-11-05 16:44:37 -08002970 .nodes(network.nodes)
2971 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002972 .gravity(0.4)
2973 .friction(0.7)
2974 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08002975 .linkDistance(ldist)
2976 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08002977 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08002978
Simon Hunt01095ff2014-11-13 16:37:29 -08002979 network.drag = d3u.createDragBehavior(network.force,
Simon Huntc2367d52014-11-29 19:30:23 -08002980 selectCb, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt6e18fe32014-11-29 13:35:41 -08002981
Simon Hunt0c6d4192014-11-12 12:07:10 -08002982
2983 // create mask layer for when we lose connection to server.
Simon Hunta5e89142014-11-14 07:00:33 -08002984 // TODO: this should be part of the framework
Simon Hunt6e18fe32014-11-29 13:35:41 -08002985
2986 function para(sel, text) {
2987 sel.append('p').text(text);
2988 }
2989
Simon Hunt0c6d4192014-11-12 12:07:10 -08002990 mask = view.$div.append('div').attr('id','topo-mask');
2991 para(mask, 'Oops!');
2992 para(mask, 'Web-socket connection to server closed...');
2993 para(mask, 'Try refreshing the page.');
Simon Hunt12ce12e2014-11-15 21:13:19 -08002994
2995 mask.append('svg')
2996 .attr({
2997 id: 'mask-bird',
2998 width: w,
2999 height: h
3000 })
3001 .append('g')
3002 .attr('transform', birdTranslate(w, h))
3003 .style('opacity', 0.3)
3004 .append('use')
3005 .attr({
3006 'xlink:href': '#bird',
3007 width: config.birdDim,
3008 height: config.birdDim,
3009 fill: '#111'
Thomas Vachuska89543292014-11-19 11:28:33 -08003010 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08003011 }
Simon Hunt195cb382014-11-03 17:50:51 -08003012
Simon Hunt01095ff2014-11-13 16:37:29 -08003013
Simon Hunt56d51852014-11-09 13:03:35 -08003014 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08003015 // resize, in case the window was resized while we were not loaded
3016 resize(view, ctx, flags);
3017
Simon Hunt99c13842014-11-06 18:23:12 -08003018 // cache the view token, so network topo functions can access it
3019 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08003020 config.useLiveData = !flags.local;
3021
3022 if (!config.useLiveData) {
3023 prepareScenario(view, ctx, flags.debug);
3024 }
Simon Hunt99c13842014-11-06 18:23:12 -08003025
3026 // set our radio buttons and key bindings
Simon Hunt9462e8c2014-11-14 17:28:09 -08003027 layerBtnSet = view.setRadio(layerButtons);
Simon Hunt934c3ce2014-11-05 11:45:07 -08003028 view.setKeys(keyDispatch);
Simon Hunt87514342014-11-24 16:41:27 -08003029 view.setGestures(gestures);
Simon Hunt195cb382014-11-03 17:50:51 -08003030
Simon Huntf8e5b4e02014-11-13 11:17:57 -08003031 // patch in our "button bar" for now
3032 // TODO: implement a more official frameworky way of doing this..
Simon Hunt87514342014-11-24 16:41:27 -08003033 //addButtonBar(view);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08003034
Simon Huntd3b7d512014-11-12 15:48:41 -08003035 // Load map data asynchronously; complete startup after that..
3036 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08003037 }
3038
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08003039 function startAntTimer() {
Thomas Vachuskab7e40642014-12-03 11:16:06 -08003040 // Note: disabled until traffic can be allotted to intents properly
3041 if (false && !antTimer) {
Thomas Vachuska4731f122014-11-20 04:56:19 -08003042 var pulses = [5, 3, 1.2, 3],
3043 pulse = 0;
3044 antTimer = setInterval(function () {
3045 pulse = pulse + 1;
3046 pulse = pulse === pulses.length ? 0 : pulse;
3047 d3.selectAll('.animated').style('stroke-width', pulses[pulse]);
3048 }, 200);
3049 }
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08003050 }
3051
3052 function stopAntTimer() {
Simon Hunta255a2c2014-11-13 22:29:35 -08003053 if (antTimer) {
3054 clearInterval(antTimer);
3055 antTimer = null;
3056 }
Simon Huntd3b7d512014-11-12 15:48:41 -08003057 }
3058
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08003059 function unload(view, ctx, flags) {
3060 stopAntTimer();
3061 }
3062
Simon Huntd3b7d512014-11-12 15:48:41 -08003063 // TODO: move these to config/state portion of script
Simon Hunta6a9fe72014-11-20 11:17:12 -08003064 var geoJsonUrl = 'json/map/continental_us.json',
Simon Huntd3b7d512014-11-12 15:48:41 -08003065 geoJson;
3066
3067 function loadGeoJsonData() {
3068 d3.json(geoJsonUrl, function (err, data) {
3069 if (err) {
3070 // fall back to USA map background
3071 loadStaticMap();
3072 } else {
3073 geoJson = data;
3074 loadGeoMap();
3075 }
3076
3077 // finally, connect to the server...
3078 if (config.useLiveData) {
3079 webSock.connect();
3080 }
3081 });
3082 }
3083
3084 function showBg() {
Simon Hunt434cf142014-11-24 11:10:28 -08003085 return visVal(config.options.showBackground);
Simon Huntd3b7d512014-11-12 15:48:41 -08003086 }
3087
3088 function loadStaticMap() {
3089 fnTrace('loadStaticMap', config.backgroundUrl);
3090 var w = network.view.width(),
3091 h = network.view.height();
3092
3093 // load the background image
3094 bgImg = svg.insert('svg:image', '#topo-G')
3095 .attr({
3096 id: 'topo-bg',
3097 width: w,
3098 height: h,
3099 'xlink:href': config.backgroundUrl
3100 })
3101 .style({
3102 visibility: showBg()
3103 });
3104 }
3105
Simon Hunt1b18aa52014-11-29 17:57:55 -08003106 function setProjForView(path, topoData) {
3107 var dim = config.logicalSize;
3108
3109 // start with unit scale, no translation..
3110 geoMapProj.scale(1).translate([0, 0]);
3111
3112 // figure out dimensions of map data..
3113 var b = path.bounds(topoData),
3114 x1 = b[0][0],
3115 y1 = b[0][1],
3116 x2 = b[1][0],
3117 y2 = b[1][1],
3118 dx = x2 - x1,
3119 dy = y2 - y1,
3120 x = (x1 + x2) / 2,
3121 y = (y1 + y2) / 2;
3122
3123 // size map to 95% of minimum dimension to fill space..
3124 var s = .95 / Math.min(dx / dim, dy / dim);
3125 var t = [dim / 2 - s * x, dim / 2 - s * y];
3126
3127 // set new scale, translation on the projection..
3128 geoMapProj.scale(s).translate(t);
3129 }
3130
Simon Huntd3b7d512014-11-12 15:48:41 -08003131 function loadGeoMap() {
3132 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08003133
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003134 // extracts the topojson data into geocoordinate-based geometry
3135 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08003136
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003137 // see: http://bl.ocks.org/mbostock/4707858
Simon Hunt1b18aa52014-11-29 17:57:55 -08003138 geoMapProj = d3.geo.mercator();
3139 var path = d3.geo.path().projection(geoMapProj);
Simon Huntd3b7d512014-11-12 15:48:41 -08003140
Simon Hunt1b18aa52014-11-29 17:57:55 -08003141 setProjForView(path, topoData);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003142
Simon Hunt3c5ca542014-11-29 14:11:43 -08003143 bgImg = panZoomContainer.insert("g", '#topo-G');
Thomas Vachuska89543292014-11-19 11:28:33 -08003144 bgImg.attr('id', 'map').selectAll('path')
3145 .data(topoData.features)
3146 .enter()
3147 .append('path')
3148 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08003149 }
3150
Simon Huntf67722a2014-11-10 09:32:06 -08003151 function resize(view, ctx, flags) {
Simon Hunt12ce12e2014-11-15 21:13:19 -08003152 var w = view.width(),
3153 h = view.height();
3154
Simon Hunt934c3ce2014-11-05 11:45:07 -08003155 setSize(svg, view);
Simon Hunt12ce12e2014-11-15 21:13:19 -08003156
3157 d3.select('#mask-bird').attr({ width: w, height: h})
3158 .select('g').attr('transform', birdTranslate(w, h));
Simon Hunt142d0032014-11-04 20:13:09 -08003159 }
3160
Simon Hunt8f40cce2014-11-23 15:57:30 -08003161 function theme(view, ctx, flags) {
3162 updateInstances();
Simon Huntb0ecfa52014-11-23 21:05:12 -08003163 updateDeviceColors();
Simon Hunt8f40cce2014-11-23 15:57:30 -08003164 }
3165
Simon Hunt12ce12e2014-11-15 21:13:19 -08003166 function birdTranslate(w, h) {
3167 var bdim = config.birdDim;
3168 return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
3169 }
Simon Hunt142d0032014-11-04 20:13:09 -08003170
Simon Hunt06811b72014-11-25 18:54:48 -08003171 function isF(f) { return $.isFunction(f) ? f : null; }
3172 function noop() {}
3173
3174 function augmentDetailPane() {
3175 var dp = detailPane;
3176 dp.ypos = { up: 64, down: 320, current: 320};
3177
3178 dp._move = function (y, cb) {
3179 var endCb = isF(cb) || noop,
3180 yp = dp.ypos;
3181 if (yp.current !== y) {
3182 yp.current = y;
3183 dp.el.transition().duration(300)
3184 .each('end', endCb)
3185 .style('top', yp.current + 'px');
3186 } else {
3187 endCb();
3188 }
3189 };
3190
3191 dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
3192 dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
3193 }
3194
Simon Hunt142d0032014-11-04 20:13:09 -08003195 // ==============================
3196 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08003197
Simon Hunt25248912014-11-04 11:25:48 -08003198 onos.ui.addView('topo', {
Simon Hunta2994cc2014-12-02 14:19:15 -08003199 init: init,
Simon Hunt142d0032014-11-04 20:13:09 -08003200 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08003201 unload: unload,
Simon Hunt8f40cce2014-11-23 15:57:30 -08003202 resize: resize,
3203 theme: theme
Simon Hunt195cb382014-11-03 17:50:51 -08003204 });
3205
Thomas Vachuska47635c62014-11-22 01:21:36 -08003206 summaryPane = onos.ui.addFloatingPanel('topo-summary');
Simon Hunt61d04042014-11-11 17:27:16 -08003207 detailPane = onos.ui.addFloatingPanel('topo-detail');
Simon Hunt06811b72014-11-25 18:54:48 -08003208 augmentDetailPane();
Simon Hunta5e89142014-11-14 07:00:33 -08003209 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
Simon Huntb82f6902014-11-22 11:53:15 -08003210 oiBox.width(20);
Simon Hunt61d04042014-11-11 17:27:16 -08003211
Simon Hunt195cb382014-11-03 17:50:51 -08003212}(ONOS));