blob: f3cb1ad6a4952a0b2e71d494449f7fa95f0e72be [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,
Simon Hunt12ce12e2014-11-15 21:13:19 -080036 birdDim: 400,
Simon Hunt195cb382014-11-03 17:50:51 -080037 options: {
Simon Hunt142d0032014-11-04 20:13:09 -080038 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080039 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -080040 webSockUrl: 'ws/topology',
Simon Hunt195cb382014-11-03 17:50:51 -080041 data: {
42 live: {
43 jsonUrl: 'rs/topology/graph',
44 detailPrefix: 'rs/topology/graph/',
45 detailSuffix: ''
46 },
47 fake: {
48 jsonUrl: 'json/network2.json',
49 detailPrefix: 'json/',
50 detailSuffix: '.json'
51 }
52 },
Simon Hunt99c13842014-11-06 18:23:12 -080053 labels: {
54 imgPad: 16,
55 padLR: 4,
56 padTB: 3,
57 marginLR: 3,
58 marginTB: 2,
59 port: {
60 gap: 3,
61 width: 18,
62 height: 14
63 }
64 },
Simon Hunt1a9eff92014-11-07 11:06:34 -080065 topo: {
Thomas Vachuska89543292014-11-19 11:28:33 -080066 linkBaseColor: '#666',
Simon Hunt1a9eff92014-11-07 11:06:34 -080067 linkInColor: '#66f',
Thomas Vachuska9edca302014-11-22 17:06:42 -080068 linkInWidth: 12,
Thomas Vachuska89543292014-11-19 11:28:33 -080069 linkOutColor: '#f00',
Thomas Vachuska9edca302014-11-22 17:06:42 -080070 linkOutWidth: 10
Simon Hunt1a9eff92014-11-07 11:06:34 -080071 },
Paul Greyson29cd58f2014-11-18 13:14:57 -080072 icons: {
Simon Huntc72967b2014-11-20 09:21:42 -080073 device: {
Simon Hunt395a70c2014-11-22 23:17:40 -080074 dim: 36,
75 rx: 4,
76 xoff: -20,
77 yoff: -18
78 },
79 host: {
80 defaultRadius: 9,
81 radius: {
82 endstation: 14,
83 bgpSpeaker: 14,
84 router: 14
85 }
Simon Huntc72967b2014-11-20 09:21:42 -080086 }
Thomas Vachuska89543292014-11-19 11:28:33 -080087 },
Simon Hunt195cb382014-11-03 17:50:51 -080088 force: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080089 note_for_links: 'link.type is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080090 linkDistance: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080091 direct: 100,
92 optical: 120,
Thomas Vachuska3266abf2014-11-13 09:28:46 -080093 hostLink: 3
Simon Huntc7ee0662014-11-05 16:44:37 -080094 },
95 linkStrength: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080096 direct: 1.0,
97 optical: 1.0,
98 hostLink: 1.0
Simon Huntc7ee0662014-11-05 16:44:37 -080099 },
Simon Hunt7cd48f32014-11-09 23:42:50 -0800100 note_for_nodes: 'node.class is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -0800101 charge: {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800102 device: -8000,
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800103 host: -5000
Simon Hunt195cb382014-11-03 17:50:51 -0800104 }
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800105 },
106 // see below in creation of viewBox on main svg
107 logicalSize: 1000
Simon Hunt195cb382014-11-03 17:50:51 -0800108 };
109
Simon Hunt142d0032014-11-04 20:13:09 -0800110 // radio buttons
Simon Hunt9462e8c2014-11-14 17:28:09 -0800111 var layerButtons = [
112 { text: 'All Layers', id: 'all', cb: showAllLayers },
113 { text: 'Packet Only', id: 'pkt', cb: showPacketLayer },
114 { text: 'Optical Only', id: 'opt', cb: showOpticalLayer }
115 ],
116 layerBtnSet,
117 layerBtnDispatch = {
118 all: showAllLayers,
119 pkt: showPacketLayer,
120 opt: showOpticalLayer
121 };
Simon Hunt934c3ce2014-11-05 11:45:07 -0800122
123 // key bindings
124 var keyDispatch = {
Simon Hunt988c6fc2014-11-20 17:43:03 -0800125 // TODO: remove these "development only" bindings
Simon Hunt8f40cce2014-11-23 15:57:30 -0800126 0: testMe,
127 equals: injectStartupEvents,
128 dash: injectTestEvent,
Simon Hunt99c13842014-11-06 18:23:12 -0800129
Thomas Vachuska47635c62014-11-22 01:21:36 -0800130 O: [toggleSummary, 'Toggle ONOS summary pane'],
131 I: [toggleInstances, 'Toggle ONOS instances pane'],
Simon Hunt27d322d2014-11-28 10:45:43 -0800132 D: [toggleDetails, 'Disable / enable details pane'],
Simon Hunta1162d82014-12-03 14:34:43 -0800133
Simon Hunt434cf142014-11-24 11:10:28 -0800134 H: [toggleHosts, 'Toggle host visibility'],
Simon Hunt6d9bd032014-11-28 22:16:40 -0800135 M: [toggleOffline, 'Toggle offline visibility'],
Simon Hunta1162d82014-12-03 14:34:43 -0800136 B: [toggleBg, 'Toggle background image'],
Simon Hunt934c3ce2014-11-05 11:45:07 -0800137 P: togglePorts,
Simon Hunta1162d82014-12-03 14:34:43 -0800138
139 X: [toggleNodeLock, 'Lock / unlock node positions'],
140 Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
141 L: [cycleLabels, 'Cycle device labels'],
Simon Hunt87514342014-11-24 16:41:27 -0800142 U: [unpin, 'Unpin node (hover mouse over)'],
Simon Hunt3c5ca542014-11-29 14:11:43 -0800143 R: [resetPanZoom, 'Reset pan / zoom'],
Simon Hunta1162d82014-12-03 14:34:43 -0800144
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800145 V: [showRelatedIntentsAction, 'Show all related intents'],
Thomas Vachuskab7e40642014-12-03 11:16:06 -0800146 rightArrow: [showNextIntentAction, 'Show next related intent'],
147 leftArrow: [showPrevIntentAction, 'Show previous related intent'],
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800148 W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
149 A: [showAllTrafficAction, 'Monitor all traffic'],
Simon Hunt56ef0fe2014-11-21 08:24:43 -0800150 F: [showDeviceLinkFlowsAction, 'Show device link flows'],
Simon Hunta1162d82014-12-03 14:34:43 -0800151
152 E: [equalizeMasters, 'Equalize mastership roles'],
153
154 esc: handleEscape,
155
156 _helpFormat: [
157 ['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
158 ['X', 'Z', 'L', 'U', 'R' ],
159 ['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
160 ]
Simon Hunt934c3ce2014-11-05 11:45:07 -0800161 };
Simon Hunt142d0032014-11-04 20:13:09 -0800162
Simon Hunt87514342014-11-24 16:41:27 -0800163 // mouse gestures
164 var gestures = [
165 ['click', 'Select the item and show details'],
166 ['shift-click', 'Toggle selection state'],
167 ['drag', 'Reposition (and pin) device / host'],
168 ['cmd-scroll', 'Zoom in / out'],
169 ['cmd-drag', 'Pan']
170 ];
171
Simon Hunt195cb382014-11-03 17:50:51 -0800172 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800173 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800174 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800175 nodes: [],
176 links: [],
Simon Hunt269670f2014-11-17 16:17:43 -0800177 lookup: {},
178 revLinkToKey: {}
Simon Hunt99c13842014-11-06 18:23:12 -0800179 },
Simon Hunt56d51852014-11-09 13:03:35 -0800180 scenario = {
181 evDir: 'json/ev/',
182 evScenario: '/scenario.json',
183 evPrefix: '/ev_',
184 evOnos: '_onos.json',
185 evUi: '_ui.json',
186 ctx: null,
187 params: {},
188 evNumber: 0,
Simon Hunt434cf142014-11-24 11:10:28 -0800189 view: null
Simon Hunt56d51852014-11-09 13:03:35 -0800190 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800191 webSock,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800192 sid = 0,
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800193 deviceLabelCount = 3,
Simon Hunt209155e2014-11-21 12:16:09 -0800194 hostLabelCount = 2,
Simon Hunt56d51852014-11-09 13:03:35 -0800195 deviceLabelIndex = 0,
196 hostLabelIndex = 0,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800197 selections = {},
Simon Hunta5e89142014-11-14 07:00:33 -0800198 selectOrder = [],
Simon Hunt6ac93f32014-11-13 12:17:27 -0800199 hovered = null,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800200 summaryPane,
Simon Hunta5e89142014-11-14 07:00:33 -0800201 detailPane,
Simon Hunta255a2c2014-11-13 22:29:35 -0800202 antTimer = null,
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800203 guiSuccessor = null,
Simon Hunta5e89142014-11-14 07:00:33 -0800204 onosInstances = {},
205 onosOrder = [],
206 oiBox,
Simon Hunt9462e8c2014-11-14 17:28:09 -0800207 oiShowMaster = false,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800208 portLabelsOn = false,
Simon Huntb0ecfa52014-11-23 21:05:12 -0800209 cat7 = d3u.cat7(),
Simon Hunt434cf142014-11-24 11:10:28 -0800210 colorAffinity = false,
Simon Hunt27d322d2014-11-28 10:45:43 -0800211 showHosts = false,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800212 showOffline = true,
Simon Hunt27d322d2014-11-28 10:45:43 -0800213 useDetails = true,
Simon Hunt6e18fe32014-11-29 13:35:41 -0800214 haveDetails = false,
Simon Huntc2367d52014-11-29 19:30:23 -0800215 nodeLock = false,
216 oblique = false;
Simon Hunt195cb382014-11-03 17:50:51 -0800217
Simon Hunt434cf142014-11-24 11:10:28 -0800218 // constants
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800219 var hoverModeNone = 0,
220 hoverModeAll = 1,
Thomas Vachuska9edca302014-11-22 17:06:42 -0800221 hoverModeFlows = 2,
222 hoverModeIntents = 3,
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800223 hoverMode = hoverModeNone;
Thomas Vachuska9edca302014-11-22 17:06:42 -0800224
Simon Hunt934c3ce2014-11-05 11:45:07 -0800225 // D3 selections
226 var svg,
Simon Hunt3c5ca542014-11-29 14:11:43 -0800227 panZoomContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800228 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800229 topoG,
230 nodeG,
231 linkG,
Simon Hunte2575b62014-11-18 15:25:53 -0800232 linkLabelG,
Simon Huntc7ee0662014-11-05 16:44:37 -0800233 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800234 link,
Simon Hunte2575b62014-11-18 15:25:53 -0800235 linkLabel,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800236 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800237
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800238 // the projection for the map background
Simon Hunt1b18aa52014-11-29 17:57:55 -0800239 var geoMapProj;
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800240
Paul Greysonfcba0e82014-11-13 10:21:16 -0800241 // the zoom function
242 var zoom;
243
Simon Hunt142d0032014-11-04 20:13:09 -0800244 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800245 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800246
Simon Huntfc274c92014-11-11 11:05:46 -0800247 function fnTrace(msg, id) {
248 if (config.fnTrace) {
249 console.log('FN: ' + msg + ' [' + id + ']');
250 }
251 }
Simon Hunt99c13842014-11-06 18:23:12 -0800252
Simon Hunta5e89142014-11-14 07:00:33 -0800253 function evTrace(data) {
254 fnTrace(data.event, data.payload.id);
255 }
256
Simon Hunt934c3ce2014-11-05 11:45:07 -0800257 // ==============================
258 // Key Callbacks
259
Simon Hunt27d322d2014-11-28 10:45:43 -0800260 function flash(txt) {
261 network.view.flash(txt);
262 }
263
Simon Hunt99c13842014-11-06 18:23:12 -0800264 function testMe(view) {
Simon Hunt8f40cce2014-11-23 15:57:30 -0800265 //view.alert('Theme is ' + view.getTheme());
Simon Hunta3dd9572014-11-20 15:22:41 -0800266 //view.flash('This is some text');
Simon Hunt8f40cce2014-11-23 15:57:30 -0800267 cat7.testCard(svg);
Simon Hunt99c13842014-11-06 18:23:12 -0800268 }
269
Simon Hunt56d51852014-11-09 13:03:35 -0800270 function injectTestEvent(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800271 if (config.useLiveData) { return; }
272
Simon Hunt56d51852014-11-09 13:03:35 -0800273 var sc = scenario,
274 evn = ++sc.evNumber,
275 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
276 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800277 uiUrl = pfx + sc.evUi,
278 stack = [
279 { url: onosUrl, cb: handleServerEvent },
280 { url: uiUrl, cb: handleUiEvent }
281 ];
282 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800283 }
284
Simon Hunt7cd48f32014-11-09 23:42:50 -0800285 function recurseFetchEvent(stack, evn) {
286 var v = scenario.view,
287 frame;
288 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800289 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800290 return;
291 }
292 frame = stack.shift();
293
294 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800295 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800296 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800297 // if we didn't find the data, try the next stack frame
298 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800299 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800300 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800301 }
Simon Hunt99c13842014-11-06 18:23:12 -0800302 } else {
Simon Hunt1712ed82014-11-17 12:56:00 -0800303 wsTrace('test', JSON.stringify(data));
Simon Hunt7cd48f32014-11-09 23:42:50 -0800304 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800305 }
306 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800307
Simon Hunt56d51852014-11-09 13:03:35 -0800308 }
Simon Hunt50128c02014-11-08 13:36:15 -0800309
Simon Hunt56d51852014-11-09 13:03:35 -0800310 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800311 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
312 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800313 }
314
315 function injectStartupEvents(view) {
316 var last = scenario.params.lastAuto || 0;
Simon Hunt434cf142014-11-24 11:10:28 -0800317 if (config.useLiveData) { return; }
Simon Hunt56d51852014-11-09 13:03:35 -0800318
319 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800320 injectTestEvent(view);
321 }
322 }
323
Simon Hunt934c3ce2014-11-05 11:45:07 -0800324 function toggleBg() {
325 var vis = bgImg.style('visibility');
Simon Hunt434cf142014-11-24 11:10:28 -0800326 bgImg.style('visibility', visVal(vis === 'hidden'));
327 }
328
Simon Huntd6f5a272014-11-29 23:45:50 -0800329 function opacifyBg(b) {
330 bgImg.transition()
331 .duration(1000)
332 .attr('opacity', b ? 1 : 0);
333 }
334
Simon Huntc2367d52014-11-29 19:30:23 -0800335 function toggleNodeLock() {
336 nodeLock = !nodeLock;
337 flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked'))
338 }
339
340 function toggleOblique() {
341 oblique = !oblique;
Simon Huntd6f5a272014-11-29 23:45:50 -0800342 if (oblique) {
343 network.force.stop();
344 toObliqueView();
345 } else {
346 toNormalView();
347 }
Simon Huntc2367d52014-11-29 19:30:23 -0800348 }
349
Simon Hunt434cf142014-11-24 11:10:28 -0800350 function toggleHosts() {
351 showHosts = !showHosts;
352 updateHostVisibility();
Simon Hunt27d322d2014-11-28 10:45:43 -0800353 flash('Hosts ' + visVal(showHosts));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800354 }
355
Simon Hunt6d9bd032014-11-28 22:16:40 -0800356 function toggleOffline() {
357 showOffline = !showOffline;
358 updateOfflineVisibility();
359 flash('Offline devices ' + visVal(showOffline));
360 }
361
Simon Hunt99c13842014-11-06 18:23:12 -0800362 function cycleLabels() {
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800363 deviceLabelIndex = (deviceLabelIndex === 2)
Simon Huntbb282f52014-11-10 11:08:19 -0800364 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800365
Simon Hunt99c13842014-11-06 18:23:12 -0800366 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800367 if (d.class === 'device') {
368 updateDeviceLabel(d);
369 }
Simon Hunt99c13842014-11-06 18:23:12 -0800370 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800371 }
372
373 function togglePorts(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800374 //view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800375 }
376
Simon Hunt6ac93f32014-11-13 12:17:27 -0800377 function unpin() {
378 if (hovered) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800379 sendUpdateMeta(hovered);
Simon Hunt6ac93f32014-11-13 12:17:27 -0800380 hovered.fixed = false;
381 hovered.el.classed('fixed', false);
Simon Huntd6f5a272014-11-29 23:45:50 -0800382 fResume();
Simon Hunt6ac93f32014-11-13 12:17:27 -0800383 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800384 }
385
Simon Hunt9462e8c2014-11-14 17:28:09 -0800386 function handleEscape(view) {
387 if (oiShowMaster) {
388 cancelAffinity();
Simon Hunt27d322d2014-11-28 10:45:43 -0800389 } else if (haveDetails) {
Simon Hunt9462e8c2014-11-14 17:28:09 -0800390 deselectAll();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800391 } else if (oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800392 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800393 } else if (summaryPane.isVisible()) {
394 cancelSummary();
Thomas Vachuska5bde31f2014-11-25 15:29:18 -0800395 stopAntTimer();
396 } else {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800397 hoverMode = hoverModeNone;
Simon Hunt9462e8c2014-11-14 17:28:09 -0800398 }
399 }
400
Simon Hunt934c3ce2014-11-05 11:45:07 -0800401 // ==============================
Simon Huntd6f5a272014-11-29 23:45:50 -0800402 // Oblique view ...
403
404 var obview = {
405 tt: -.7, // x skew y factor
406 xsk: -35, // x skew angle
407 ysc: 0.5, // y scale
408 pad: 50,
409 time: 1500,
410 fill: {
411 pkt: 'rgba(130,130,170,0.3)',
412 opt: 'rgba(170,130,170,0.3)'
413 },
414 id: function (tag) {
415 return 'obview-' + tag + 'Plane';
416 },
417 yt: function (h, dir) {
418 return h * obview.ysc * dir * 1.1;
419 },
420 obXform: function (h, dir) {
421 var yt = obview.yt(h, dir);
422 return scale(1, obview.ysc) + translate(0, yt) + skewX(obview.xsk);
423 },
424 noXform: function () {
425 return skewX(0) + translate(0,0) + scale(1,1);
426 },
427 xffn: null,
428 plane: {}
429 };
430
431
432 function toObliqueView() {
433 var box = nodeG.node().getBBox(),
434 ox, oy;
435
436 padBox(box, obview.pad);
437
438 ox = box.x + box.width / 2;
439 oy = box.y + box.height / 2;
440
441 // remember node lock state, then lock the nodes down
442 obview.nodeLock = nodeLock;
443 nodeLock = true;
444 opacifyBg(false);
445
446 insertPlanes(ox, oy);
447
448 obview.xffn = function (xy, dir) {
449 var yt = obview.yt(box.height, dir),
450 ax = xy.x - ox,
451 ay = xy.y - oy,
452 x = ax + ay * obview.tt,
453 y = ay * obview.ysc + obview.ysc * yt;
454 return {x: ox + x, y: oy + y};
455 };
456
457 showPlane('pkt', box, -1);
458 showPlane('opt', box, 1);
459 obTransitionNodes();
460 }
461
462 function toNormalView() {
463 obview.xffn = null;
464
465 hidePlane('pkt');
466 hidePlane('opt');
467 obTransitionNodes();
468
469 removePlanes();
470
471 // restore node lock state
472 nodeLock = obview.nodeLock;
473 opacifyBg(true);
474 }
475
476 function obTransitionNodes() {
477 var xffn = obview.xffn;
478
479 // return the direction for the node
480 // -1 for pkt layer, 1 for optical layer
481 function dir(d) {
482 return inLayer(d, 'pkt') ? -1 : 1;
483 }
484
485 if (xffn) {
486 network.nodes.forEach(function (d) {
487 var oldxy = {x: d.x, y: d.y},
488 coords = xffn(oldxy, dir(d));
489 d.oldxy = oldxy;
490 d.px = d.x = coords.x;
491 d.py = d.y = coords.y;
492 });
493 } else {
494 network.nodes.forEach(function (d) {
495 var old = d.oldxy || {x: d.x, y: d.y};
496 d.px = d.x = old.x;
497 d.py = d.y = old.y;
498 delete d.oldxy;
499 });
500 }
501
502 node.transition()
503 .duration(obview.time)
504 .attr(tickStuff.nodeAttr);
505 link.transition()
506 .duration(obview.time)
507 .attr(tickStuff.linkAttr);
508 linkLabel.transition()
509 .duration(obview.time)
510 .attr(tickStuff.linkLabelAttr);
511 }
512
513 function showPlane(tag, box, dir) {
514 var g = obview.plane[tag];
515
516 // set box origin at center..
517 box.x = -box.width/2;
518 box.y = -box.height/2;
519
520 g.select('rect')
521 .attr(box)
522 .attr('opacity', 0)
523 .transition()
524 .duration(obview.time)
525 .attr('opacity', 1)
526 .attr('transform', obview.obXform(box.height, dir));
527 }
528
529 function hidePlane(tag) {
530 var g = obview.plane[tag];
531
532 g.select('rect')
533 .transition()
534 .duration(obview.time)
535 .attr('opacity', 0)
536 .attr('transform', obview.noXform());
537 }
538
539 function insertPlanes(ox, oy) {
540 function ins(tag) {
541 var id = obview.id(tag),
542 g = panZoomContainer.insert('g', '#topo-G')
543 .attr('id', id)
544 .attr('transform', translate(ox,oy));
545 g.append('rect')
546 .attr('fill', obview.fill[tag])
547 .attr('opacity', 0);
548 obview.plane[tag] = g;
549 }
550 ins('opt');
551 ins('pkt');
552 }
553
554 function removePlanes() {
555 function rem(tag) {
556 var id = obview.id(tag);
557 panZoomContainer.select('#'+id)
558 .transition()
559 .duration(obview.time + 50)
560 .remove();
561 delete obview.plane[tag];
562 }
563 rem('opt');
564 rem('pkt');
565 }
566
567 function padBox(box, p) {
568 box.x -= p;
569 box.y -= p;
570 box.width += p*2;
571 box.height += p*2;
572 }
573
574 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800575 // Radio Button Callbacks
576
Simon Hunta5e89142014-11-14 07:00:33 -0800577 var layerLookup = {
578 host: {
Simon Hunt209155e2014-11-21 12:16:09 -0800579 endstation: 'pkt', // default, if host event does not define type
Thomas Vachuska89543292014-11-19 11:28:33 -0800580 router: 'pkt',
Simon Hunta5e89142014-11-14 07:00:33 -0800581 bgpSpeaker: 'pkt'
582 },
583 device: {
584 switch: 'pkt',
585 roadm: 'opt'
586 },
587 link: {
588 hostLink: 'pkt',
589 direct: 'pkt',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800590 indirect: '',
591 tunnel: '',
Simon Hunta5e89142014-11-14 07:00:33 -0800592 optical: 'opt'
593 }
594 };
595
596 function inLayer(d, layer) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800597 var type = d.class === 'link' ? d.type() : d.type,
598 look = layerLookup[d.class],
599 lyr = look && look[type];
Simon Hunta5e89142014-11-14 07:00:33 -0800600 return lyr === layer;
601 }
602
603 function unsuppressLayer(which) {
604 node.each(function (d) {
605 var node = d.el;
606 if (inLayer(d, which)) {
607 node.classed('suppressed', false);
608 }
609 });
610
611 link.each(function (d) {
612 var link = d.el;
613 if (inLayer(d, which)) {
614 link.classed('suppressed', false);
615 }
616 });
617 }
618
Simon Hunt9462e8c2014-11-14 17:28:09 -0800619 function suppressLayers(b) {
620 node.classed('suppressed', b);
621 link.classed('suppressed', b);
Simon Hunt142d0032014-11-04 20:13:09 -0800622// d3.selectAll('svg .port').classed('inactive', false);
623// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt195cb382014-11-03 17:50:51 -0800624 }
625
Simon Hunt9462e8c2014-11-14 17:28:09 -0800626 function showAllLayers() {
627 suppressLayers(false);
628 }
629
Simon Hunt195cb382014-11-03 17:50:51 -0800630 function showPacketLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800631 node.classed('suppressed', true);
632 link.classed('suppressed', true);
633 unsuppressLayer('pkt');
Simon Hunt195cb382014-11-03 17:50:51 -0800634 }
635
636 function showOpticalLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800637 node.classed('suppressed', true);
638 link.classed('suppressed', true);
639 unsuppressLayer('opt');
Simon Hunt195cb382014-11-03 17:50:51 -0800640 }
641
Simon Hunt9462e8c2014-11-14 17:28:09 -0800642 function restoreLayerState() {
643 layerBtnDispatch[layerBtnSet.selected()]();
644 }
645
Simon Hunt142d0032014-11-04 20:13:09 -0800646 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800647 // Private functions
648
Simon Hunt99c13842014-11-06 18:23:12 -0800649 function safeId(s) {
650 return s.replace(/[^a-z0-9]/gi, '-');
651 }
652
Simon Huntc7ee0662014-11-05 16:44:37 -0800653 // set the size of the given element to that of the view (reduced if padded)
654 function setSize(el, view, pad) {
655 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800656 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800657 width: view.width() - padding,
658 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800659 });
660 }
661
Simon Hunt8257f4c2014-11-16 19:34:54 -0800662 function makeNodeKey(d, what) {
663 var port = what + 'Port';
664 return d[what] + '/' + d[port];
665 }
666
667 function makeLinkKey(d, flipped) {
668 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
669 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
670 return one + '-' + two;
671 }
672
Simon Hunt269670f2014-11-17 16:17:43 -0800673 function findLinkById(id) {
674 // check to see if this is a reverse lookup, else default to given id
675 var key = network.revLinkToKey[id] || id;
676 return key && network.lookup[key];
677 }
678
Simon Hunt8257f4c2014-11-16 19:34:54 -0800679 function findLink(linkData, op) {
680 var key = makeLinkKey(linkData),
681 keyrev = makeLinkKey(linkData, 1),
682 link = network.lookup[key],
683 linkRev = network.lookup[keyrev],
684 result = {},
685 ldata = link || linkRev,
686 rawLink;
687
688 if (op === 'add') {
689 if (link) {
690 // trying to add a link that we already know about
691 result.ldata = link;
692 result.badLogic = 'addLink: link already added';
693
694 } else if (linkRev) {
695 // we found the reverse of the link to be added
696 result.ldata = linkRev;
697 if (linkRev.fromTarget) {
698 result.badLogic = 'addLink: link already added';
699 }
700 }
701 } else if (op === 'update') {
702 if (!ldata) {
703 result.badLogic = 'updateLink: link not found';
704 } else {
705 rawLink = link ? ldata.fromSource : ldata.fromTarget;
706 result.updateWith = function (data) {
707 $.extend(rawLink, data);
708 restyleLinkElement(ldata);
709 }
710 }
711 } else if (op === 'remove') {
712 if (!ldata) {
713 result.badLogic = 'removeLink: link not found';
714 } else {
715 rawLink = link ? ldata.fromSource : ldata.fromTarget;
716
717 if (!rawLink) {
718 result.badLogic = 'removeLink: link not found';
719
720 } else {
721 result.removeRawLink = function () {
722 if (link) {
723 // remove fromSource
724 ldata.fromSource = null;
725 if (ldata.fromTarget) {
726 // promote target into source position
727 ldata.fromSource = ldata.fromTarget;
728 ldata.fromTarget = null;
729 ldata.key = keyrev;
730 delete network.lookup[key];
731 network.lookup[keyrev] = ldata;
Simon Hunt269670f2014-11-17 16:17:43 -0800732 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800733 }
734 } else {
735 // remove fromTarget
736 ldata.fromTarget = null;
Simon Hunt269670f2014-11-17 16:17:43 -0800737 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800738 }
739 if (ldata.fromSource) {
740 restyleLinkElement(ldata);
741 } else {
742 removeLinkElement(ldata);
743 }
744 }
745 }
746 }
747 }
748 return result;
749 }
750
751 function addLinkUpdate(ldata, link) {
752 // add link event, but we already have the reverse link installed
753 ldata.fromTarget = link;
Simon Hunt269670f2014-11-17 16:17:43 -0800754 network.revLinkToKey[link.id] = ldata.key;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800755 restyleLinkElement(ldata);
756 }
757
758 var allLinkTypes = 'direct indirect optical tunnel',
759 defaultLinkType = 'direct';
760
761 function restyleLinkElement(ldata) {
762 // this fn's job is to look at raw links and decide what svg classes
763 // need to be applied to the line element in the DOM
764 var el = ldata.el,
765 type = ldata.type(),
766 lw = ldata.linkWidth(),
767 online = ldata.online();
768
769 el.classed('link', true);
770 el.classed('inactive', !online);
771 el.classed(allLinkTypes, false);
772 if (type) {
773 el.classed(type, true);
774 }
775 el.transition()
776 .duration(1000)
Thomas Vachuska89543292014-11-19 11:28:33 -0800777 .attr('stroke-width', linkScale(lw))
778 .attr('stroke', config.topo.linkBaseColor);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800779 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800780
Simon Hunt99c13842014-11-06 18:23:12 -0800781 // ==============================
782 // Event handlers for server-pushed events
783
Simon Huntbb282f52014-11-10 11:08:19 -0800784 function logicError(msg) {
785 // TODO, report logic error to server, via websock, so it can be logged
Simon Huntfc274c92014-11-11 11:05:46 -0800786 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800787 }
788
Simon Hunt99c13842014-11-06 18:23:12 -0800789 var eventDispatch = {
Simon Hunta5e89142014-11-14 07:00:33 -0800790 addInstance: addInstance,
Simon Hunt99c13842014-11-06 18:23:12 -0800791 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800792 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800793 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800794
Simon Huntfcfb46c2014-11-19 12:53:38 -0800795 updateInstance: updateInstance,
Simon Huntbb282f52014-11-10 11:08:19 -0800796 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800797 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800798 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800799
Simon Hunt7b403bc2014-11-22 19:01:00 -0800800 removeInstance: removeInstance,
Simon Huntca867ac2014-11-28 18:07:35 -0800801 removeDevice: removeDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800802 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800803 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800804
Simon Hunt61d04042014-11-11 17:27:16 -0800805 showDetails: showDetails,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800806 showSummary: showSummary,
Simon Huntb53e0682014-11-12 13:32:01 -0800807 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800808 };
809
Simon Hunta5e89142014-11-14 07:00:33 -0800810 function addInstance(data) {
811 evTrace(data);
812 var inst = data.payload,
813 id = inst.id;
814 if (onosInstances[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800815 updateInstance(data);
Simon Hunta5e89142014-11-14 07:00:33 -0800816 return;
817 }
818 onosInstances[id] = inst;
819 onosOrder.push(inst);
820 updateInstances();
821 }
822
Simon Hunt99c13842014-11-06 18:23:12 -0800823 function addDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800824 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800825 var device = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800826 id = device.id,
827 d;
828
829 if (network.lookup[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800830 updateDevice(data);
Simon Huntca867ac2014-11-28 18:07:35 -0800831 return;
832 }
833
834 d = createDeviceNode(device);
835 network.nodes.push(d);
836 network.lookup[id] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800837 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -0800838 fStart();
Simon Hunt99c13842014-11-06 18:23:12 -0800839 }
840
Simon Hunt99c13842014-11-06 18:23:12 -0800841 function addLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800842 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800843 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800844 result = findLink(link, 'add'),
845 bad = result.badLogic,
Simon Huntca867ac2014-11-28 18:07:35 -0800846 d = result.ldata;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800847
848 if (bad) {
849 logicError(bad + ': ' + link.id);
850 return;
851 }
852
Simon Huntca867ac2014-11-28 18:07:35 -0800853 if (d) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800854 // we already have a backing store link for src/dst nodes
Simon Huntca867ac2014-11-28 18:07:35 -0800855 addLinkUpdate(d, link);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800856 return;
857 }
858
859 // no backing store link yet
Simon Huntca867ac2014-11-28 18:07:35 -0800860 d = createLink(link);
861 if (d) {
862 network.links.push(d);
863 network.lookup[d.key] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800864 updateLinks();
Simon Huntd6f5a272014-11-29 23:45:50 -0800865 fStart();
Simon Hunt99c13842014-11-06 18:23:12 -0800866 }
867 }
868
Simon Hunt56d51852014-11-09 13:03:35 -0800869 function addHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800870 evTrace(data);
Simon Hunt56d51852014-11-09 13:03:35 -0800871 var host = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800872 id = host.id,
873 d,
Simon Hunt56d51852014-11-09 13:03:35 -0800874 lnk;
Simon Huntca867ac2014-11-28 18:07:35 -0800875
876 if (network.lookup[id]) {
877 logicError('Host already added: ' + id);
878 return;
879 }
880
881 d = createHostNode(host);
882 network.nodes.push(d);
883 network.lookup[host.id] = d;
Simon Hunt56d51852014-11-09 13:03:35 -0800884 updateNodes();
885
886 lnk = createHostLink(host);
887 if (lnk) {
Simon Huntca867ac2014-11-28 18:07:35 -0800888 d.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800889 network.links.push(lnk);
Simon Huntca867ac2014-11-28 18:07:35 -0800890 network.lookup[d.ingress] = lnk;
891 network.lookup[d.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800892 updateLinks();
893 }
Simon Huntd6f5a272014-11-29 23:45:50 -0800894 fStart();
Simon Hunt56d51852014-11-09 13:03:35 -0800895 }
896
Simon Hunt56a2ea42014-11-19 12:39:31 -0800897 function updateInstance(data) {
898 evTrace(data);
899 var inst = data.payload,
900 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800901 d = onosInstances[id];
902 if (d) {
903 $.extend(d, inst);
Simon Hunt56a2ea42014-11-19 12:39:31 -0800904 updateInstances();
Simon Hunt56a2ea42014-11-19 12:39:31 -0800905 } else {
906 logicError('updateInstance lookup fail. ID = "' + id + '"');
907 }
908 }
909
Simon Huntbb282f52014-11-10 11:08:19 -0800910 function updateDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800911 evTrace(data);
Simon Huntbb282f52014-11-10 11:08:19 -0800912 var device = data.payload,
913 id = device.id,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800914 d = network.lookup[id],
915 wasOnline;
916
Simon Hunt62c47542014-11-22 22:16:32 -0800917 if (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -0800918 wasOnline = d.online;
Simon Hunt62c47542014-11-22 22:16:32 -0800919 $.extend(d, device);
920 if (positionNode(d, true)) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800921 sendUpdateMeta(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -0800922 }
923 updateNodes();
Simon Hunt6d9bd032014-11-28 22:16:40 -0800924 if (wasOnline !== d.online) {
925 findAttachedLinks(d.id).forEach(restyleLinkElement);
926 updateOfflineVisibility(d);
927 }
Simon Huntbb282f52014-11-10 11:08:19 -0800928 } else {
929 logicError('updateDevice lookup fail. ID = "' + id + '"');
930 }
931 }
932
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800933 function updateLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800934 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800935 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800936 result = findLink(link, 'update'),
937 bad = result.badLogic;
938 if (bad) {
939 logicError(bad + ': ' + link.id);
940 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800941 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800942 result.updateWith(link);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800943 }
944
Simon Hunt7cd48f32014-11-09 23:42:50 -0800945 function updateHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800946 evTrace(data);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800947 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800948 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800949 d = network.lookup[id];
950 if (d) {
951 $.extend(d, host);
Simon Hunt6d9bd032014-11-28 22:16:40 -0800952 if (positionNode(d, true)) {
953 sendUpdateMeta(d, true);
954 }
955 updateNodes(d);
Simon Huntbb282f52014-11-10 11:08:19 -0800956 } else {
957 logicError('updateHost lookup fail. ID = "' + id + '"');
958 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800959 }
960
Simon Hunt7b403bc2014-11-22 19:01:00 -0800961 function removeInstance(data) {
962 evTrace(data);
963 var inst = data.payload,
964 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800965 d = onosInstances[id];
966 if (d) {
967 var idx = find(id, onosOrder);
Simon Hunt7b403bc2014-11-22 19:01:00 -0800968 if (idx >= 0) {
969 onosOrder.splice(idx, 1);
970 }
971 delete onosInstances[id];
972 updateInstances();
973 } else {
974 logicError('updateInstance lookup fail. ID = "' + id + '"');
975 }
976 }
977
Simon Huntca867ac2014-11-28 18:07:35 -0800978 function removeDevice(data) {
979 evTrace(data);
980 var device = data.payload,
981 id = device.id,
982 d = network.lookup[id];
983 if (d) {
984 removeDeviceElement(d);
985 } else {
986 logicError('removeDevice lookup fail. ID = "' + id + '"');
987 }
988 }
989
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800990 function removeLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800991 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800992 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800993 result = findLink(link, 'remove'),
994 bad = result.badLogic;
995 if (bad) {
Simon Huntca867ac2014-11-28 18:07:35 -0800996 // may have already removed link, if attached to removed device
997 console.warn(bad + ': ' + link.id);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800998 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800999 }
Simon Hunt8257f4c2014-11-16 19:34:54 -08001000 result.removeRawLink();
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001001 }
1002
Simon Hunt44031102014-11-11 13:20:36 -08001003 function removeHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001004 evTrace(data);
Simon Hunt44031102014-11-11 13:20:36 -08001005 var host = data.payload,
1006 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -08001007 d = network.lookup[id];
1008 if (d) {
1009 removeHostElement(d, true);
Simon Hunt44031102014-11-11 13:20:36 -08001010 } else {
Simon Huntca867ac2014-11-28 18:07:35 -08001011 // may have already removed host, if attached to removed device
1012 console.warn('removeHost lookup fail. ID = "' + id + '"');
Simon Hunt44031102014-11-11 13:20:36 -08001013 }
1014 }
1015
Simon Huntca867ac2014-11-28 18:07:35 -08001016 // the following events are server responses to user actions
Thomas Vachuska47635c62014-11-22 01:21:36 -08001017 function showSummary(data) {
1018 evTrace(data);
1019 populateSummary(data.payload);
Simon Hunt06811b72014-11-25 18:54:48 -08001020 showSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001021 }
1022
Simon Hunt61d04042014-11-11 17:27:16 -08001023 function showDetails(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001024 evTrace(data);
Simon Hunt27d322d2014-11-28 10:45:43 -08001025 haveDetails = true;
Simon Hunt61d04042014-11-11 17:27:16 -08001026 populateDetails(data.payload);
Simon Hunt27d322d2014-11-28 10:45:43 -08001027 if (useDetails) {
1028 showDetailPane();
1029 }
Simon Hunt61d04042014-11-11 17:27:16 -08001030 }
1031
Simon Huntb53e0682014-11-12 13:32:01 -08001032 function showTraffic(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001033 evTrace(data);
Thomas Vachuska4731f122014-11-20 04:56:19 -08001034 var paths = data.payload.paths,
1035 hasTraffic = false;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001036
Thomas Vachuska3266abf2014-11-13 09:28:46 -08001037 // Revert any links hilighted previously.
Thomas Vachuska4731f122014-11-20 04:56:19 -08001038 link.style('stroke-width', null)
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08001039 .classed('primary secondary animated optical', false);
Simon Hunte2575b62014-11-18 15:25:53 -08001040 // Remove all previous labels.
1041 removeLinkLabels();
Thomas Vachuska3266abf2014-11-13 09:28:46 -08001042
Simon Hunte2575b62014-11-18 15:25:53 -08001043 // Now hilight all links in the paths payload, and attach
1044 // labels to them, if they are defined.
Simon Hunta255a2c2014-11-13 22:29:35 -08001045 paths.forEach(function (p) {
Simon Hunte2575b62014-11-18 15:25:53 -08001046 var n = p.links.length,
1047 i,
1048 ldata;
1049
Thomas Vachuska4731f122014-11-20 04:56:19 -08001050 hasTraffic = hasTraffic || p.traffic;
Simon Hunte2575b62014-11-18 15:25:53 -08001051 for (i=0; i<n; i++) {
1052 ldata = findLinkById(p.links[i]);
Thomas Vachuska4731f122014-11-20 04:56:19 -08001053 if (ldata && ldata.el) {
Simon Hunte2575b62014-11-18 15:25:53 -08001054 ldata.el.classed(p.class, true);
1055 ldata.label = p.labels[i];
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001056 }
Simon Hunte2575b62014-11-18 15:25:53 -08001057 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001058 });
Thomas Vachuska4731f122014-11-20 04:56:19 -08001059
Simon Hunte2575b62014-11-18 15:25:53 -08001060 updateLinks();
Thomas Vachuska4731f122014-11-20 04:56:19 -08001061
1062 if (hasTraffic && !antTimer) {
1063 startAntTimer();
1064 } else if (!hasTraffic && antTimer) {
1065 stopAntTimer();
1066 }
Simon Huntb53e0682014-11-12 13:32:01 -08001067 }
1068
Simon Hunt56d51852014-11-09 13:03:35 -08001069 // ...............................
1070
Simon Hunt99c13842014-11-06 18:23:12 -08001071 function unknownEvent(data) {
Simon Hunt434cf142014-11-24 11:10:28 -08001072 console.warn('Unknown event type: "' + data.event + '"', data);
Simon Hunt99c13842014-11-06 18:23:12 -08001073 }
1074
1075 function handleServerEvent(data) {
1076 var fn = eventDispatch[data.event] || unknownEvent;
1077 fn(data);
1078 }
1079
1080 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -08001081 // Out-going messages...
1082
Simon Huntb53e0682014-11-12 13:32:01 -08001083 function nSel() {
1084 return selectOrder.length;
1085 }
Simon Hunt61d04042014-11-11 17:27:16 -08001086 function getSel(idx) {
1087 return selections[selectOrder[idx]];
1088 }
Simon Huntb53e0682014-11-12 13:32:01 -08001089 function allSelectionsClass(cls) {
1090 for (var i=0, n=nSel(); i<n; i++) {
1091 if (getSel(i).obj.class !== cls) {
1092 return false;
1093 }
1094 }
1095 return true;
1096 }
Simon Hunt61d04042014-11-11 17:27:16 -08001097
Thomas Vachuska47635c62014-11-22 01:21:36 -08001098 function toggleInstances() {
1099 if (!oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001100 showInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001101 } else {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001102 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001103 }
1104 }
1105
Simon Huntb0ecfa52014-11-23 21:05:12 -08001106 function showInstances() {
1107 oiBox.show();
1108 colorAffinity = true;
1109 updateDeviceColors();
1110 }
1111
1112 function hideInstances() {
1113 oiBox.hide();
1114 colorAffinity = false;
1115 cancelAffinity();
1116 updateDeviceColors();
1117 }
1118
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001119 function equalizeMasters() {
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001120 sendMessage('equalizeMasters');
Simon Huntc1cc81c2014-11-29 14:59:01 -08001121 flash('Equalizing master roles');
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001122 }
1123
Thomas Vachuska47635c62014-11-22 01:21:36 -08001124 function toggleSummary() {
1125 if (!summaryPane.isVisible()) {
1126 requestSummary();
1127 } else {
1128 cancelSummary();
1129 }
1130 }
1131
Thomas Vachuska47635c62014-11-22 01:21:36 -08001132 function requestSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001133 sendMessage('requestSummary');
Thomas Vachuska47635c62014-11-22 01:21:36 -08001134 }
1135
1136 function cancelSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001137 sendMessage('cancelSummary');
Simon Hunt06811b72014-11-25 18:54:48 -08001138 hideSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001139 }
1140
Simon Hunt27d322d2014-11-28 10:45:43 -08001141 function toggleDetails() {
1142 useDetails = !useDetails;
1143 if (useDetails) {
1144 flash('Enable details pane');
1145 if (haveDetails) {
1146 showDetailPane();
1147 }
1148 } else {
1149 flash('Disable details pane');
1150 hideDetailPane();
1151 }
1152 }
1153
Simon Hunt06811b72014-11-25 18:54:48 -08001154 // encapsulate interaction between summary and details panes
1155 function showSummaryPane() {
1156 if (detailPane.isVisible()) {
1157 detailPane.down(summaryPane.show);
1158 } else {
1159 summaryPane.show();
1160 }
1161 }
1162
1163 function hideSummaryPane() {
1164 summaryPane.hide(function () {
1165 if (detailPane.isVisible()) {
1166 detailPane.up();
1167 }
1168 });
1169 }
1170
1171 function showDetailPane() {
1172 if (summaryPane.isVisible()) {
1173 detailPane.down(detailPane.show);
1174 } else {
1175 detailPane.up(detailPane.show);
1176 }
1177 }
1178
1179 function hideDetailPane() {
1180 detailPane.hide();
1181 }
1182
1183
Simon Hunt61d04042014-11-11 17:27:16 -08001184 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -08001185 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -08001186 function requestDetails() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001187 var data = getSel(0).obj;
1188 sendMessage('requestDetails', {
1189 id: data.id,
1190 class: data.class
1191 });
Simon Hunt61d04042014-11-11 17:27:16 -08001192 }
1193
Thomas Vachuska9edca302014-11-22 17:06:42 -08001194 function addHostIntentAction() {
Simon Huntd72bc702014-11-13 18:38:04 -08001195 sendMessage('addHostIntent', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001196 one: selectOrder[0],
1197 two: selectOrder[1],
1198 ids: selectOrder
Simon Huntd72bc702014-11-13 18:38:04 -08001199 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001200 flash('Host-to-Host flow added');
Simon Huntd72bc702014-11-13 18:38:04 -08001201 }
1202
Thomas Vachuska9edca302014-11-22 17:06:42 -08001203 function addMultiSourceIntentAction() {
1204 sendMessage('addMultiSourceIntent', {
1205 src: selectOrder.slice(0, selectOrder.length - 1),
1206 dst: selectOrder[selectOrder.length - 1],
1207 ids: selectOrder
1208 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001209 flash('Multi-Source flow added');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001210 }
1211
Thomas Vachuska47635c62014-11-22 01:21:36 -08001212 function cancelTraffic() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001213 sendMessage('cancelTraffic');
Thomas Vachuska47635c62014-11-22 01:21:36 -08001214 }
1215
Thomas Vachuska9edca302014-11-22 17:06:42 -08001216 function requestTrafficForMode() {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001217 if (hoverMode === hoverModeFlows) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001218 requestDeviceLinkFlows();
1219 } else if (hoverMode === hoverModeIntents) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001220 requestRelatedIntents();
Simon Huntd72bc702014-11-13 18:38:04 -08001221 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001222 }
Simon Huntd72bc702014-11-13 18:38:04 -08001223
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001224 function showRelatedIntentsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001225 hoverMode = hoverModeIntents;
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001226 requestRelatedIntents();
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001227 flash('Related Paths');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001228 }
1229
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001230 function requestRelatedIntents() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001231 function hoverValid() {
1232 return hoverMode === hoverModeIntents &&
1233 hovered &&
1234 (hovered.class === 'host' || hovered.class === 'device');
1235 }
1236
Thomas Vachuska9edca302014-11-22 17:06:42 -08001237 if (validateSelectionContext()) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001238 sendMessage('requestRelatedIntents', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001239 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001240 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001241 });
1242 }
Simon Huntd72bc702014-11-13 18:38:04 -08001243 }
1244
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001245 function showNextIntentAction() {
1246 hoverMode = hoverModeNone;
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001247 sendMessage('requestNextRelatedIntent');
1248 flash('>');
1249 }
1250
1251 function showPrevIntentAction() {
1252 hoverMode = hoverModeNone;
1253 sendMessage('requestPrevRelatedIntent');
1254 flash('<');
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001255 }
1256
1257 function showSelectedIntentTrafficAction() {
1258 hoverMode = hoverModeNone;
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001259 sendMessage('requestSelectedIntentTraffic');
1260 flash('Traffic on Selected Path');
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001261 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -08001262
Thomas Vachuska29617e52014-11-20 03:17:46 -08001263 function showDeviceLinkFlowsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001264 hoverMode = hoverModeFlows;
1265 requestDeviceLinkFlows();
Simon Hunt27d322d2014-11-28 10:45:43 -08001266 flash('Device Flows');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001267 }
1268
Thomas Vachuska9edca302014-11-22 17:06:42 -08001269 function requestDeviceLinkFlows() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001270 function hoverValid() {
1271 return hoverMode === hoverModeFlows &&
1272 hovered && (hovered.class === 'device');
1273 }
1274
Thomas Vachuska9edca302014-11-22 17:06:42 -08001275 if (validateSelectionContext()) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001276 sendMessage('requestDeviceLinkFlows', {
1277 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001278 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001279 });
1280 }
1281 }
1282
Thomas Vachuska9edca302014-11-22 17:06:42 -08001283 function showAllTrafficAction() {
1284 hoverMode = hoverModeAll;
1285 requestAllTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001286 flash('All Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001287 }
1288
1289 function requestAllTraffic() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001290 sendMessage('requestAllTraffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001291 }
1292
1293 function validateSelectionContext() {
Thomas Vachuska29617e52014-11-20 03:17:46 -08001294 if (!hovered && nSel() === 0) {
Thomas Vachuska47635c62014-11-22 01:21:36 -08001295 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08001296 return false;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001297 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001298 return true;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001299 }
Simon Huntd72bc702014-11-13 18:38:04 -08001300
Simon Hunta6a9fe72014-11-20 11:17:12 -08001301
Simon Hunt61d04042014-11-11 17:27:16 -08001302 // ==============================
Simon Hunta5e89142014-11-14 07:00:33 -08001303 // onos instance panel functions
Simon Huntb82f6902014-11-22 11:53:15 -08001304
Simon Hunt7b403bc2014-11-22 19:01:00 -08001305 var instCfg = {
1306 rectPad: 8,
1307 nodeOx: 9,
1308 nodeOy: 9,
1309 nodeDim: 40,
1310 birdOx: 19,
1311 birdOy: 21,
1312 birdDim: 21,
1313 uiDy: 45,
1314 titleDy: 30,
1315 textYOff: 20,
1316 textYSpc: 15
1317 };
1318
1319 function viewBox(dim) {
1320 return '0 0 ' + dim.w + ' ' + dim.h;
1321 }
1322
1323 function instRectAttr(dim) {
1324 var pad = instCfg.rectPad;
1325 return {
1326 x: pad,
1327 y: pad,
1328 width: dim.w - pad*2,
1329 height: dim.h - pad*2,
Simon Huntb0ecfa52014-11-23 21:05:12 -08001330 rx: 6
Simon Hunt7b403bc2014-11-22 19:01:00 -08001331 };
1332 }
1333
1334 function computeDim(self) {
1335 var css = window.getComputedStyle(self);
1336 return {
1337 w: stripPx(css.width),
1338 h: stripPx(css.height)
1339 };
Simon Huntb82f6902014-11-22 11:53:15 -08001340 }
Simon Hunta5e89142014-11-14 07:00:33 -08001341
1342 function updateInstances() {
1343 var onoses = oiBox.el.selectAll('.onosInst')
Simon Huntb82f6902014-11-22 11:53:15 -08001344 .data(onosOrder, function (d) { return d.id; }),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001345 instDim = {w:0,h:0},
1346 c = instCfg;
Simon Hunta5e89142014-11-14 07:00:33 -08001347
Simon Hunt7b403bc2014-11-22 19:01:00 -08001348 function nSw(n) {
1349 return '# Switches: ' + n;
1350 }
Simon Hunta5e89142014-11-14 07:00:33 -08001351
Simon Huntb82f6902014-11-22 11:53:15 -08001352 // operate on existing onos instances if necessary
1353 onoses.each(function (d) {
1354 var el = d3.select(this),
1355 svg = el.select('svg');
Simon Hunt7b403bc2014-11-22 19:01:00 -08001356 instDim = computeDim(this);
Simon Huntb82f6902014-11-22 11:53:15 -08001357
1358 // update online state
1359 el.classed('online', d.online);
1360
1361 // update ui-attached state
1362 svg.select('use.uiBadge').remove();
1363 if (d.uiAttached) {
1364 attachUiBadge(svg);
1365 }
1366
Simon Hunt7b403bc2014-11-22 19:01:00 -08001367 function updAttr(id, value) {
1368 svg.select('text.instLabel.'+id).text(value);
1369 }
1370
1371 updAttr('ip', d.ip);
1372 updAttr('ns', nSw(d.switches));
Simon Huntb82f6902014-11-22 11:53:15 -08001373 });
1374
1375
1376 // operate on new onos instances
Simon Hunta5e89142014-11-14 07:00:33 -08001377 var entering = onoses.enter()
1378 .append('div')
1379 .attr('class', 'onosInst')
1380 .classed('online', function (d) { return d.online; })
Simon Hunt9c15eca2014-11-15 18:37:59 -08001381 .on('click', clickInst);
1382
Simon Huntb82f6902014-11-22 11:53:15 -08001383 entering.each(function (d) {
Simon Hunt9c15eca2014-11-15 18:37:59 -08001384 var el = d3.select(this),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001385 rectAttr,
1386 svg;
1387 instDim = computeDim(this);
1388 rectAttr = instRectAttr(instDim);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001389
Simon Hunt7b403bc2014-11-22 19:01:00 -08001390 svg = el.append('svg').attr({
1391 width: instDim.w,
1392 height: instDim.h,
1393 viewBox: viewBox(instDim)
Simon Hunt95908012014-11-20 10:20:26 -08001394 });
Simon Huntb82f6902014-11-22 11:53:15 -08001395
Simon Hunt7b403bc2014-11-22 19:01:00 -08001396 svg.append('rect').attr(rectAttr);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001397
Thomas Vachuskae02e11c2014-11-24 16:13:52 -08001398 appendBadge(svg, 14, 14, 28, '#bird');
Simon Huntb82f6902014-11-22 11:53:15 -08001399
1400 if (d.uiAttached) {
1401 attachUiBadge(svg);
1402 }
1403
Simon Hunt7b403bc2014-11-22 19:01:00 -08001404 var left = c.nodeOx + c.nodeDim,
1405 len = rectAttr.width - left,
1406 hlen = len / 2,
1407 midline = hlen + left;
Simon Hunt9c15eca2014-11-15 18:37:59 -08001408
Simon Hunt7b403bc2014-11-22 19:01:00 -08001409 // title
1410 svg.append('text')
1411 .attr({
1412 class: 'instTitle',
1413 x: midline,
1414 y: c.titleDy
1415 })
1416 .text(d.id);
1417
1418 // a couple of attributes
1419 var ty = c.titleDy + c.textYOff;
1420
1421 function addAttr(id, label) {
1422 svg.append('text').attr({
1423 class: 'instLabel ' + id,
1424 x: midline,
1425 y: ty
1426 }).text(label);
1427 ty += c.textYSpc;
1428 }
1429
1430 addAttr('ip', d.ip);
1431 addAttr('ns', nSw(d.switches));
Simon Hunt9c15eca2014-11-15 18:37:59 -08001432 });
Simon Hunta5e89142014-11-14 07:00:33 -08001433
1434 // operate on existing + new onoses here
Simon Hunt8f40cce2014-11-23 15:57:30 -08001435 // set the affinity colors...
1436 onoses.each(function (d) {
1437 var el = d3.select(this),
1438 rect = el.select('svg').select('rect'),
1439 col = instColor(d.id, d.online);
1440 rect.style('fill', col);
1441 });
Simon Hunta5e89142014-11-14 07:00:33 -08001442
Simon Hunt7b403bc2014-11-22 19:01:00 -08001443 // adjust the panel size appropriately...
1444 oiBox.width(instDim.w * onosOrder.length);
1445 oiBox.height(instDim.h);
1446
1447 // remove any outgoing instances
1448 onoses.exit().remove();
Simon Hunta5e89142014-11-14 07:00:33 -08001449 }
1450
Simon Hunt8f40cce2014-11-23 15:57:30 -08001451 function instColor(id, online) {
1452 return cat7.get(id, !online, network.view.getTheme());
1453 }
1454
Simon Hunt9462e8c2014-11-14 17:28:09 -08001455 function clickInst(d) {
1456 var el = d3.select(this),
1457 aff = el.classed('affinity');
1458 if (!aff) {
1459 setAffinity(el, d);
1460 } else {
1461 cancelAffinity();
1462 }
1463 }
1464
1465 function setAffinity(el, d) {
1466 d3.selectAll('.onosInst')
1467 .classed('mastership', true)
1468 .classed('affinity', false);
1469 el.classed('affinity', true);
1470
1471 suppressLayers(true);
1472 node.each(function (n) {
1473 if (n.master === d.id) {
1474 n.el.classed('suppressed', false);
1475 }
1476 });
1477 oiShowMaster = true;
1478 }
1479
1480 function cancelAffinity() {
1481 d3.selectAll('.onosInst')
1482 .classed('mastership affinity', false);
1483 restoreLayerState();
1484 oiShowMaster = false;
1485 }
1486
Simon Hunt7b403bc2014-11-22 19:01:00 -08001487 // TODO: these should be moved out to utility module.
1488 function stripPx(s) {
1489 return s.replace(/px$/,'');
1490 }
1491
1492 function appendUse(svg, ox, oy, dim, iid, cls) {
1493 var use = svg.append('use').attr({
1494 transform: translate(ox,oy),
1495 'xlink:href': iid,
1496 width: dim,
1497 height: dim
1498 });
1499 if (cls) {
1500 use.classed(cls, true);
1501 }
1502 return use;
1503 }
1504
1505 function appendGlyph(svg, ox, oy, dim, iid, cls) {
1506 appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
1507 }
1508
1509 function appendBadge(svg, ox, oy, dim, iid, cls) {
1510 appendUse(svg, ox, oy, dim, iid,cls ).classed('badgeIcon', true);
1511 }
1512
1513 function attachUiBadge(svg) {
1514 appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
1515 }
1516
Simon Hunt434cf142014-11-24 11:10:28 -08001517 function visVal(b) {
1518 return b ? 'visible' : 'hidden';
1519 }
1520
Simon Hunta5e89142014-11-14 07:00:33 -08001521 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -08001522 // force layout modification functions
1523
1524 function translate(x, y) {
1525 return 'translate(' + x + ',' + y + ')';
1526 }
Simon Huntd6f5a272014-11-29 23:45:50 -08001527 function scale(x,y) {
1528 return 'scale(' + x + ',' + y + ')';
1529 }
1530 function skewX(x) {
1531 return 'skewX(' + x + ')';
1532 }
Simon Hunte2575b62014-11-18 15:25:53 -08001533 function rotate(deg) {
1534 return 'rotate(' + deg + ')';
1535 }
1536
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001537 function missMsg(what, id) {
1538 return '\n[' + what + '] "' + id + '" missing ';
1539 }
1540
1541 function linkEndPoints(srcId, dstId) {
1542 var srcNode = network.lookup[srcId],
1543 dstNode = network.lookup[dstId],
1544 sMiss = !srcNode ? missMsg('src', srcId) : '',
1545 dMiss = !dstNode ? missMsg('dst', dstId) : '';
1546
1547 if (sMiss || dMiss) {
1548 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
1549 return null;
1550 }
1551 return {
1552 source: srcNode,
1553 target: dstNode,
1554 x1: srcNode.x,
1555 y1: srcNode.y,
1556 x2: dstNode.x,
1557 y2: dstNode.y
1558 };
1559 }
1560
Simon Hunt56d51852014-11-09 13:03:35 -08001561 function createHostLink(host) {
1562 var src = host.id,
1563 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -08001564 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001565 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -08001566
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001567 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -08001568 return null;
1569 }
1570
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001571 // Synthesize link ...
1572 $.extend(lnk, {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001573 key: id,
Simon Hunt56d51852014-11-09 13:03:35 -08001574 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001575
1576 type: function () { return 'hostLink'; },
Simon Hunt6d9bd032014-11-28 22:16:40 -08001577 online: function () {
1578 // hostlink target is edge switch
1579 return lnk.target.online;
1580 },
Simon Hunt8257f4c2014-11-16 19:34:54 -08001581 linkWidth: function () { return 1; }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001582 });
Simon Hunt99c13842014-11-06 18:23:12 -08001583 return lnk;
1584 }
1585
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001586 function createLink(link) {
Simon Hunta6a9fe72014-11-20 11:17:12 -08001587 var lnk = linkEndPoints(link.src, link.dst);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001588
1589 if (!lnk) {
1590 return null;
1591 }
1592
Simon Hunt8257f4c2014-11-16 19:34:54 -08001593 $.extend(lnk, {
1594 key: link.id,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001595 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001596 fromSource: link,
1597
1598 // functions to aggregate dual link state
1599 type: function () {
1600 var s = lnk.fromSource,
1601 t = lnk.fromTarget;
1602 return (s && s.type) || (t && t.type) || defaultLinkType;
1603 },
1604 online: function () {
1605 var s = lnk.fromSource,
Simon Hunt6d9bd032014-11-28 22:16:40 -08001606 t = lnk.fromTarget,
1607 both = lnk.source.online && lnk.target.online;
1608 return both && ((s && s.online) || (t && t.online));
Simon Hunt8257f4c2014-11-16 19:34:54 -08001609 },
1610 linkWidth: function () {
1611 var s = lnk.fromSource,
1612 t = lnk.fromTarget,
1613 ws = (s && s.linkWidth) || 0,
1614 wt = (t && t.linkWidth) || 0;
1615 return Math.max(ws, wt);
1616 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001617 });
1618 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -08001619 }
1620
Simon Hunte2575b62014-11-18 15:25:53 -08001621 function removeLinkLabels() {
1622 network.links.forEach(function (d) {
1623 d.label = '';
1624 });
1625 }
1626
Simon Hunt434cf142014-11-24 11:10:28 -08001627 function showHostVis(el) {
1628 el.style('visibility', visVal(showHosts));
1629 }
1630
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001631 var widthRatio = 1.4,
1632 linkScale = d3.scale.linear()
1633 .domain([1, 12])
1634 .range([widthRatio, 12 * widthRatio])
1635 .clamp(true);
1636
Simon Hunt99c13842014-11-06 18:23:12 -08001637 function updateLinks() {
1638 link = linkG.selectAll('.link')
Simon Hunt8257f4c2014-11-16 19:34:54 -08001639 .data(network.links, function (d) { return d.key; });
Simon Hunt99c13842014-11-06 18:23:12 -08001640
1641 // operate on existing links, if necessary
1642 // link .foo() .bar() ...
1643
1644 // operate on entering links:
1645 var entering = link.enter()
1646 .append('line')
1647 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001648 x1: function (d) { return d.x1; },
1649 y1: function (d) { return d.y1; },
1650 x2: function (d) { return d.x2; },
1651 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001652 stroke: config.topo.linkInColor,
1653 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -08001654 });
1655
1656 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -08001657 entering.each(function (d) {
1658 var link = d3.select(this);
1659 // provide ref to element selection from backing data....
1660 d.el = link;
Simon Hunt8257f4c2014-11-16 19:34:54 -08001661 restyleLinkElement(d);
Simon Hunt434cf142014-11-24 11:10:28 -08001662 if (d.type() === 'hostLink') {
1663 showHostVis(link);
1664 }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001665 });
Thomas Vachuska4830d392014-11-09 17:09:56 -08001666
1667 // operate on both existing and new links, if necessary
1668 //link .foo() .bar() ...
1669
Simon Hunte2575b62014-11-18 15:25:53 -08001670 // apply or remove labels
1671 var labelData = getLabelData();
1672 applyLinkLabels(labelData);
1673
Thomas Vachuska4830d392014-11-09 17:09:56 -08001674 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -08001675 link.exit()
Simon Hunt6d9bd032014-11-28 22:16:40 -08001676 .attr('stroke-dasharray', '3 3')
Simon Hunt13bf9c82014-11-18 07:26:44 -08001677 .style('opacity', 0.5)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001678 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -08001679 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001680 .attr({
Simon Hunt6d9bd032014-11-28 22:16:40 -08001681 'stroke-dasharray': '3 12',
Simon Hunt13bf9c82014-11-18 07:26:44 -08001682 stroke: config.topo.linkOutColor,
1683 'stroke-width': config.topo.linkOutWidth
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001684 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001685 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -08001686 .remove();
Simon Hunte2575b62014-11-18 15:25:53 -08001687
1688 // NOTE: invoke a single tick to force the labels to position
1689 // onto their links.
1690 tick();
1691 }
1692
1693 function getLabelData() {
1694 // create the backing data for showing labels..
1695 var data = [];
1696 link.each(function (d) {
1697 if (d.label) {
1698 data.push({
1699 id: 'lab-' + d.key,
1700 key: d.key,
1701 label: d.label,
1702 ldata: d
1703 });
1704 }
1705 });
1706 return data;
1707 }
1708
1709 var linkLabelOffset = '0.3em';
1710
1711 function applyLinkLabels(data) {
1712 var entering;
1713
1714 linkLabel = linkLabelG.selectAll('.linkLabel')
1715 .data(data, function (d) { return d.id; });
1716
Simon Hunt56a2ea42014-11-19 12:39:31 -08001717 // for elements already existing, we need to update the text
1718 // and adjust the rectangle size to fit
1719 linkLabel.each(function (d) {
1720 var el = d3.select(this),
1721 rect = el.select('rect'),
1722 text = el.select('text');
1723 text.text(d.label);
1724 rect.attr(rectAroundText(el));
1725 });
Thomas Vachuskaf75b7ab2014-11-19 12:15:55 -08001726
Simon Hunte2575b62014-11-18 15:25:53 -08001727 entering = linkLabel.enter().append('g')
1728 .classed('linkLabel', true)
1729 .attr('id', function (d) { return d.id; });
1730
1731 entering.each(function (d) {
1732 var el = d3.select(this),
1733 rect,
1734 text,
1735 parms = {
1736 x1: d.ldata.x1,
1737 y1: d.ldata.y1,
1738 x2: d.ldata.x2,
1739 y2: d.ldata.y2
1740 };
1741
1742 d.el = el;
1743 rect = el.append('rect');
1744 text = el.append('text').text(d.label);
1745 rect.attr(rectAroundText(el));
1746 text.attr('dy', linkLabelOffset);
1747
1748 el.attr('transform', transformLabel(parms));
1749 });
1750
Simon Huntc2465f42014-12-04 10:28:07 -08001751 // Remove any labels that are no longer required.
Simon Hunte2575b62014-11-18 15:25:53 -08001752 linkLabel.exit().remove();
1753 }
1754
1755 function rectAroundText(el) {
1756 var text = el.select('text'),
1757 box = text.node().getBBox();
1758
1759 // translate the bbox so that it is centered on [x,y]
1760 box.x = -box.width / 2;
1761 box.y = -box.height / 2;
1762
1763 // add padding
1764 box.x -= 1;
1765 box.width += 2;
1766 return box;
1767 }
1768
1769 function transformLabel(p) {
1770 var dx = p.x2 - p.x1,
1771 dy = p.y2 - p.y1,
1772 xMid = dx/2 + p.x1,
1773 yMid = dy/2 + p.y1;
Simon Hunte2575b62014-11-18 15:25:53 -08001774 return translate(xMid, yMid);
Simon Hunt99c13842014-11-06 18:23:12 -08001775 }
1776
1777 function createDeviceNode(device) {
1778 // start with the object as is
1779 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -08001780 type = device.type,
Simon Huntc2465f42014-12-04 10:28:07 -08001781 svgCls = type ? 'node device ' + type : 'node device';
Simon Huntc72967b2014-11-20 09:21:42 -08001782
Simon Hunt99c13842014-11-06 18:23:12 -08001783 // Augment as needed...
1784 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -08001785 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -08001786 positionNode(node);
Simon Hunt99c13842014-11-06 18:23:12 -08001787 return node;
1788 }
1789
Simon Hunt56d51852014-11-09 13:03:35 -08001790 function createHostNode(host) {
1791 // start with the object as is
1792 var node = host;
1793
1794 // Augment as needed...
1795 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001796 if (!node.type) {
Simon Hunt209155e2014-11-21 12:16:09 -08001797 node.type = 'endstation';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001798 }
Simon Hunt7fa116d2014-11-17 14:16:55 -08001799 node.svgClass = 'node host ' + node.type;
Simon Hunt56d51852014-11-09 13:03:35 -08001800 positionNode(node);
Simon Hunt56d51852014-11-09 13:03:35 -08001801 return node;
1802 }
1803
Simon Hunt62c47542014-11-22 22:16:32 -08001804 function positionNode(node, forUpdate) {
Simon Hunt99c13842014-11-06 18:23:12 -08001805 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -08001806 x = meta && meta.x,
1807 y = meta && meta.y,
1808 xy;
Simon Hunt99c13842014-11-06 18:23:12 -08001809
Simon Huntac9e24f2014-11-12 10:12:21 -08001810 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -08001811 if (x && y) {
1812 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001813 node.px = node.x = x;
1814 node.py = node.y = y;
Simon Huntac9e24f2014-11-12 10:12:21 -08001815 return;
Simon Hunt99c13842014-11-06 18:23:12 -08001816 }
Simon Huntac9e24f2014-11-12 10:12:21 -08001817
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001818 var location = node.location;
1819 if (location && location.type === 'latlng') {
Simon Hunt1b18aa52014-11-29 17:57:55 -08001820 var coord = geoMapProj([location.lng, location.lat]);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001821 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001822 node.px = node.x = coord[0];
1823 node.py = node.y = coord[1];
Simon Hunt62c47542014-11-22 22:16:32 -08001824 return true;
1825 }
1826
1827 // if this is a node update (not a node add).. skip randomizer
1828 if (forUpdate) {
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001829 return;
1830 }
1831
Simon Huntac9e24f2014-11-12 10:12:21 -08001832 // Note: Placing incoming unpinned nodes at exactly the same point
1833 // (center of the view) causes them to explode outwards when
1834 // the force layout kicks in. So, we spread them out a bit
1835 // initially, to provide a more serene layout convergence.
1836 // Additionally, if the node is a host, we place it near
1837 // the device it is connected to.
1838
1839 function spread(s) {
1840 return Math.floor((Math.random() * s) - s/2);
1841 }
1842
1843 function randDim(dim) {
1844 return dim / 2 + spread(dim * 0.7071);
1845 }
1846
1847 function rand() {
1848 return {
1849 x: randDim(network.view.width()),
1850 y: randDim(network.view.height())
1851 };
1852 }
1853
1854 function near(node) {
1855 var min = 12,
1856 dx = spread(12),
1857 dy = spread(12);
1858 return {
1859 x: node.x + min + dx,
1860 y: node.y + min + dy
1861 };
1862 }
1863
1864 function getDevice(cp) {
1865 var d = network.lookup[cp.device];
1866 return d || rand();
1867 }
1868
1869 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1870 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -08001871 }
1872
Simon Hunt99c13842014-11-06 18:23:12 -08001873
Simon Huntc72967b2014-11-20 09:21:42 -08001874 function iconGlyphUrl(d) {
1875 var which = d.type || 'unknown';
1876 return '#' + which;
1877 }
1878
Simon Hunt99c13842014-11-06 18:23:12 -08001879 // returns the newly computed bounding box of the rectangle
1880 function adjustRectToFitText(n) {
1881 var text = n.select('text'),
1882 box = text.node().getBBox(),
1883 lab = config.labels;
1884
1885 text.attr('text-anchor', 'middle')
1886 .attr('y', '-0.8em')
1887 .attr('x', lab.imgPad/2);
1888
1889 // translate the bbox so that it is centered on [x,y]
1890 box.x = -box.width / 2;
1891 box.y = -box.height / 2;
1892
1893 // add padding
1894 box.x -= (lab.padLR + lab.imgPad/2);
1895 box.width += lab.padLR * 2 + lab.imgPad;
1896 box.y -= lab.padTB;
1897 box.height += lab.padTB * 2;
1898
1899 return box;
1900 }
1901
Simon Hunt1a9eff92014-11-07 11:06:34 -08001902 function mkSvgClass(d) {
1903 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1904 }
1905
Simon Hunt7cd48f32014-11-09 23:42:50 -08001906 function hostLabel(d) {
1907 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1908 return d.labels[idx];
1909 }
1910 function deviceLabel(d) {
1911 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1912 return d.labels[idx];
1913 }
Simon Huntc72967b2014-11-20 09:21:42 -08001914 function trimLabel(label) {
1915 return (label && label.trim()) || '';
1916 }
1917
1918 function emptyBox() {
1919 return {
1920 x: -2,
1921 y: -2,
1922 width: 4,
1923 height: 4
1924 };
Simon Hunt7cd48f32014-11-09 23:42:50 -08001925 }
1926
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001927 function updateDeviceLabel(d) {
Simon Huntc72967b2014-11-20 09:21:42 -08001928 var label = trimLabel(deviceLabel(d)),
1929 noLabel = !label,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001930 node = d.el,
Simon Huntc72967b2014-11-20 09:21:42 -08001931 box,
1932 dx,
Simon Hunt395a70c2014-11-22 23:17:40 -08001933 dy,
1934 cfg = config.icons.device;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001935
1936 node.select('text')
1937 .text(label)
1938 .style('opacity', 0)
1939 .transition()
1940 .style('opacity', 1);
1941
Simon Huntc72967b2014-11-20 09:21:42 -08001942 if (noLabel) {
1943 box = emptyBox();
Simon Hunt395a70c2014-11-22 23:17:40 -08001944 dx = -cfg.dim/2;
1945 dy = -cfg.dim/2;
Simon Huntc72967b2014-11-20 09:21:42 -08001946 } else {
1947 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08001948 dx = box.x + cfg.xoff;
1949 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08001950 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001951
1952 node.select('rect')
1953 .transition()
1954 .attr(box);
1955
Simon Huntc72967b2014-11-20 09:21:42 -08001956 node.select('g.deviceIcon')
Thomas Vachuska89543292014-11-19 11:28:33 -08001957 .transition()
Simon Huntc72967b2014-11-20 09:21:42 -08001958 .attr('transform', translate(dx, dy));
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001959 }
1960
1961 function updateHostLabel(d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001962 var label = trimLabel(hostLabel(d));
1963 d.el.select('text').text(label);
Simon Huntbb282f52014-11-10 11:08:19 -08001964 }
1965
Simon Hunt434cf142014-11-24 11:10:28 -08001966 function updateHostVisibility() {
1967 var v = visVal(showHosts);
1968 nodeG.selectAll('.host').style('visibility', v);
1969 linkG.selectAll('.hostLink').style('visibility', v);
1970 }
1971
Simon Hunt6d9bd032014-11-28 22:16:40 -08001972 function findOfflineNodes() {
1973 var a = [];
1974 network.nodes.forEach(function (d) {
1975 if (d.class === 'device' && !d.online) {
1976 a.push(d);
1977 }
1978 });
1979 return a;
1980 }
1981
1982 function updateOfflineVisibility(dev) {
1983 var so = showOffline,
1984 sh = showHosts,
1985 vb = 'visibility',
1986 v, off, al, ah, db, b;
1987
1988 function updAtt(show) {
1989 al.forEach(function (d) {
1990 b = show && ((d.type() !== 'hostLink') || sh);
1991 d.el.style(vb, visVal(b));
1992 });
1993 ah.forEach(function (d) {
1994 b = show && sh;
1995 d.el.style(vb, visVal(b));
1996 });
1997 }
1998
1999 if (dev) {
2000 // updating a specific device that just toggled off/on-line
2001 db = dev.online || so;
2002 dev.el.style(vb, visVal(db));
2003 al = findAttachedLinks(dev.id);
2004 ah = findAttachedHosts(dev.id);
2005 updAtt(db);
2006 } else {
2007 // updating all offline devices
2008 v = visVal(so);
2009 off = findOfflineNodes();
2010 off.forEach(function (d) {
2011 d.el.style(vb, v);
2012 al = findAttachedLinks(d.id);
2013 ah = findAttachedHosts(d.id);
2014 updAtt(so);
2015 });
2016 }
2017 }
2018
Simon Hunt6ac93f32014-11-13 12:17:27 -08002019 function nodeMouseOver(d) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002020 if (hovered != d) {
2021 hovered = d;
2022 requestTrafficForMode();
2023 }
Simon Hunt6ac93f32014-11-13 12:17:27 -08002024 }
2025
2026 function nodeMouseOut(d) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002027 if (hovered != null) {
2028 hovered = null;
2029 requestTrafficForMode();
2030 }
Simon Hunt6ac93f32014-11-13 12:17:27 -08002031 }
Simon Huntbb282f52014-11-10 11:08:19 -08002032
Simon Hunteb1514d2014-11-20 09:57:29 -08002033 function addHostIcon(node, radius, iid) {
Thomas Vachuska89543292014-11-19 11:28:33 -08002034 var dim = radius * 1.5,
2035 xlate = -dim / 2;
2036
Simon Hunteb1514d2014-11-20 09:57:29 -08002037 node.append('use').attr({
2038 class: 'glyphIcon hostIcon',
2039 transform: translate(xlate,xlate),
2040 'xlink:href': iid,
2041 width: dim,
2042 height: dim
2043 });
Thomas Vachuska89543292014-11-19 11:28:33 -08002044 }
2045
Simon Hunt99c13842014-11-06 18:23:12 -08002046 function updateNodes() {
2047 node = nodeG.selectAll('.node')
2048 .data(network.nodes, function (d) { return d.id; });
2049
Simon Hunt62c47542014-11-22 22:16:32 -08002050 // operate on existing nodes...
2051 node.filter('.device').each(function (d) {
Simon Hunt62c47542014-11-22 22:16:32 -08002052 var node = d.el;
2053 node.classed('online', d.online);
2054 updateDeviceLabel(d);
2055 positionNode(d, true);
2056 });
2057
2058 node.filter('.host').each(function (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08002059 updateHostLabel(d);
2060 positionNode(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -08002061 });
Simon Hunt99c13842014-11-06 18:23:12 -08002062
2063 // operate on entering nodes:
2064 var entering = node.enter()
2065 .append('g')
2066 .attr({
2067 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -08002068 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -08002069 transform: function (d) { return translate(d.x, d.y); },
2070 opacity: 0
2071 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002072 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -08002073 .on('mouseover', nodeMouseOver)
2074 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -08002075 .transition()
2076 .attr('opacity', 1);
2077
2078 // augment device nodes...
2079 entering.filter('.device').each(function (d) {
2080 var node = d3.select(this),
Simon Huntc72967b2014-11-20 09:21:42 -08002081 label = trimLabel(deviceLabel(d)),
2082 noLabel = !label,
Simon Hunt99c13842014-11-06 18:23:12 -08002083 box;
2084
Simon Hunt7cd48f32014-11-09 23:42:50 -08002085 // provide ref to element from backing data....
2086 d.el = node;
2087
Simon Hunt62c47542014-11-22 22:16:32 -08002088 node.append('rect').attr({ rx: 5, ry: 5 });
2089 node.append('text').text(label).attr('dy', '1.1em');
Simon Hunt99c13842014-11-06 18:23:12 -08002090 box = adjustRectToFitText(node);
Simon Hunta3dd9572014-11-20 15:22:41 -08002091 node.select('rect').attr(box);
Simon Huntc72967b2014-11-20 09:21:42 -08002092 addDeviceIcon(node, box, noLabel, iconGlyphUrl(d));
Simon Huntc7ee0662014-11-05 16:44:37 -08002093 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08002094
Simon Hunt56d51852014-11-09 13:03:35 -08002095 // augment host nodes...
2096 entering.filter('.host').each(function (d) {
2097 var node = d3.select(this),
Simon Hunt395a70c2014-11-22 23:17:40 -08002098 cfg = config.icons.host,
2099 r = cfg.radius[d.type] || cfg.defaultRadius,
Thomas Vachuska89543292014-11-19 11:28:33 -08002100 textDy = r + 10,
Simon Hunteb1514d2014-11-20 09:57:29 -08002101 iid = iconGlyphUrl(d);
Simon Hunt56d51852014-11-09 13:03:35 -08002102
Simon Hunt7cd48f32014-11-09 23:42:50 -08002103 // provide ref to element from backing data....
2104 d.el = node;
Simon Hunt434cf142014-11-24 11:10:28 -08002105 showHostVis(node);
Simon Hunt7cd48f32014-11-09 23:42:50 -08002106
Simon Hunt62c47542014-11-22 22:16:32 -08002107 node.append('circle').attr('r', r);
Simon Hunteb1514d2014-11-20 09:57:29 -08002108 if (iid) {
2109 addHostIcon(node, r, iid);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002110 }
Simon Hunt56d51852014-11-09 13:03:35 -08002111 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08002112 .text(hostLabel)
Thomas Vachuska89543292014-11-19 11:28:33 -08002113 .attr('dy', textDy)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002114 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08002115 });
Simon Huntc7ee0662014-11-05 16:44:37 -08002116
Simon Hunt99c13842014-11-06 18:23:12 -08002117 // operate on both existing and new nodes, if necessary
Simon Huntb0ecfa52014-11-23 21:05:12 -08002118 updateDeviceColors();
Simon Huntc7ee0662014-11-05 16:44:37 -08002119
Simon Hunt99c13842014-11-06 18:23:12 -08002120 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08002121 // Note that the node is removed after 2 seconds.
2122 // Sub element animations should be shorter than 2 seconds.
2123 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08002124 .transition()
2125 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08002126 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08002127 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08002128
2129 // host node exits....
2130 exiting.filter('.host').each(function (d) {
Simon Huntca867ac2014-11-28 18:07:35 -08002131 var node = d.el;
2132 node.select('use')
2133 .style('opacity', 0.5)
2134 .transition()
2135 .duration(800)
2136 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002137
2138 node.select('text')
2139 .style('opacity', 0.5)
2140 .transition()
Simon Huntca867ac2014-11-28 18:07:35 -08002141 .duration(800)
Simon Huntea80eb42014-11-11 13:46:57 -08002142 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002143
Thomas Vachuska89543292014-11-19 11:28:33 -08002144 node.select('circle')
2145 .style('stroke-fill', '#555')
2146 .style('fill', '#888')
Simon Huntea80eb42014-11-11 13:46:57 -08002147 .style('opacity', 0.5)
2148 .transition()
2149 .duration(1500)
2150 .attr('r', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002151 });
2152
Simon Huntca867ac2014-11-28 18:07:35 -08002153 // device node exits....
2154 exiting.filter('.device').each(function (d) {
2155 var node = d.el;
2156 node.select('use')
2157 .style('opacity', 0.5)
2158 .transition()
2159 .duration(800)
2160 .style('opacity', 0);
2161
2162 node.selectAll('rect')
2163 .style('stroke-fill', '#555')
2164 .style('fill', '#888')
2165 .style('opacity', 0.5);
2166 });
Simon Huntd6f5a272014-11-29 23:45:50 -08002167 fResume();
Simon Huntc7ee0662014-11-05 16:44:37 -08002168 }
2169
Simon Hunt95dad922014-11-24 09:43:31 -08002170 var dCol = {
2171 black: '#000',
2172 paleblue: '#acf',
2173 offwhite: '#ddd',
2174 midgrey: '#888',
2175 lightgrey: '#bbb',
2176 orange: '#f90'
2177 };
2178
Simon Huntb0ecfa52014-11-23 21:05:12 -08002179 // note: these are the device icon colors without affinity
Simon Hunt95dad922014-11-24 09:43:31 -08002180 var dColTheme = {
Simon Huntb0ecfa52014-11-23 21:05:12 -08002181 light: {
2182 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08002183 glyph: dCol.black,
2184 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08002185 },
2186 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08002187 glyph: dCol.midgrey,
2188 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08002189 }
2190 },
Simon Hunt95dad922014-11-24 09:43:31 -08002191 // TODO: theme
Simon Huntb0ecfa52014-11-23 21:05:12 -08002192 dark: {
2193 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08002194 glyph: dCol.black,
2195 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08002196 },
2197 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08002198 glyph: dCol.midgrey,
2199 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08002200 }
2201 }
2202 };
2203
2204 function devBaseColor(d) {
2205 var t = network.view.getTheme(),
2206 o = d.online ? 'online' : 'offline';
Simon Hunt95dad922014-11-24 09:43:31 -08002207 return dColTheme[t][o];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002208 }
2209
2210 function setDeviceColor(d) {
2211 var o = d.online,
2212 s = d.el.classed('selected'),
2213 c = devBaseColor(d),
2214 a = instColor(d.master, o),
2215 g, r,
2216 icon = d.el.select('g.deviceIcon');
2217
2218 if (s) {
2219 g = c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002220 r = dColTheme.sel;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002221 } else if (colorAffinity) {
2222 g = o ? a : c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002223 r = o ? dCol.offwhite : a;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002224 } else {
2225 g = c.glyph;
2226 r = c.rect;
2227 }
2228
2229 icon.select('use')
2230 .style('fill', g);
2231 icon.select('rect')
2232 .style('fill', r);
2233 }
2234
Simon Huntc72967b2014-11-20 09:21:42 -08002235 function addDeviceIcon(node, box, noLabel, iid) {
2236 var cfg = config.icons.device,
2237 dx,
2238 dy,
2239 g;
2240
2241 if (noLabel) {
Simon Huntc72967b2014-11-20 09:21:42 -08002242 dx = -cfg.dim/2;
2243 dy = -cfg.dim/2;
2244 } else {
2245 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08002246 dx = box.x + cfg.xoff;
2247 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08002248 }
2249
Simon Hunteb1514d2014-11-20 09:57:29 -08002250 g = node.append('g')
2251 .attr('class', 'glyphIcon deviceIcon')
Simon Huntc72967b2014-11-20 09:21:42 -08002252 .attr('transform', translate(dx, dy));
2253
2254 g.append('rect').attr({
2255 x: 0,
2256 y: 0,
2257 rx: cfg.rx,
2258 width: cfg.dim,
2259 height: cfg.dim
2260 });
2261
2262 g.append('use').attr({
2263 'xlink:href': iid,
2264 width: cfg.dim,
2265 height: cfg.dim
2266 });
2267
Simon Huntc72967b2014-11-20 09:21:42 -08002268 }
2269
Simon Hunt7b403bc2014-11-22 19:01:00 -08002270 function find(key, array, tag) {
Simon Huntca867ac2014-11-28 18:07:35 -08002271 var _tag = tag || 'id',
Simon Hunt7b403bc2014-11-22 19:01:00 -08002272 idx, n, d;
2273 for (idx = 0, n = array.length; idx < n; idx++) {
2274 d = array[idx];
2275 if (d[_tag] === key) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002276 return idx;
2277 }
2278 }
2279 return -1;
2280 }
2281
Simon Huntca867ac2014-11-28 18:07:35 -08002282 function removeLinkElement(d) {
2283 var idx = find(d.key, network.links, 'key'),
Simon Hunt8257f4c2014-11-16 19:34:54 -08002284 removed;
2285 if (idx >=0) {
2286 // remove from links array
2287 removed = network.links.splice(idx, 1);
2288 // remove from lookup cache
2289 delete network.lookup[removed[0].key];
2290 updateLinks();
Simon Huntd6f5a272014-11-29 23:45:50 -08002291 fResume();
Simon Hunt8257f4c2014-11-16 19:34:54 -08002292 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002293 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002294
Simon Huntca867ac2014-11-28 18:07:35 -08002295 function removeHostElement(d, upd) {
2296 var lu = network.lookup;
Simon Hunt44031102014-11-11 13:20:36 -08002297 // first, remove associated hostLink...
Simon Huntca867ac2014-11-28 18:07:35 -08002298 removeLinkElement(d.linkData);
2299
2300 // remove hostLink bindings
2301 delete lu[d.ingress];
2302 delete lu[d.egress];
Simon Hunt44031102014-11-11 13:20:36 -08002303
2304 // remove from lookup cache
Simon Huntca867ac2014-11-28 18:07:35 -08002305 delete lu[d.id];
Simon Hunt44031102014-11-11 13:20:36 -08002306 // remove from nodes array
Simon Huntca867ac2014-11-28 18:07:35 -08002307 var idx = find(d.id, network.nodes);
2308 network.nodes.splice(idx, 1);
2309 // remove from SVG
2310 // NOTE: upd is false if we were called from removeDeviceElement()
2311 if (upd) {
2312 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -08002313 fResume();
Simon Huntca867ac2014-11-28 18:07:35 -08002314 }
2315 }
2316
2317
2318 function removeDeviceElement(d) {
2319 var id = d.id;
2320 // first, remove associated hosts and links..
2321 findAttachedHosts(id).forEach(removeHostElement);
2322 findAttachedLinks(id).forEach(removeLinkElement);
2323
2324 // remove from lookup cache
2325 delete network.lookup[id];
2326 // remove from nodes array
2327 var idx = find(id, network.nodes);
Simon Hunt44031102014-11-11 13:20:36 -08002328 network.nodes.splice(idx, 1);
2329 // remove from SVG
2330 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -08002331 fResume();
Simon Hunt44031102014-11-11 13:20:36 -08002332 }
2333
Simon Huntca867ac2014-11-28 18:07:35 -08002334 function findAttachedHosts(devId) {
2335 var hosts = [];
2336 network.nodes.forEach(function (d) {
2337 if (d.class === 'host' && d.cp.device === devId) {
2338 hosts.push(d);
2339 }
2340 });
2341 return hosts;
2342 }
2343
2344 function findAttachedLinks(devId) {
2345 var links = [];
2346 network.links.forEach(function (d) {
2347 if (d.source.id === devId || d.target.id === devId) {
2348 links.push(d);
2349 }
2350 });
2351 return links;
2352 }
Simon Hunt44031102014-11-11 13:20:36 -08002353
Simon Huntd6f5a272014-11-29 23:45:50 -08002354 function fResume() {
2355 if (!oblique) {
2356 network.force.resume();
2357 }
2358 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002359
Simon Huntd6f5a272014-11-29 23:45:50 -08002360 function fStart() {
2361 if (!oblique) {
2362 network.force.start();
2363 }
2364 }
2365
2366 var tickStuff = {
2367 nodeAttr: {
2368 transform: function (d) { return translate(d.x, d.y); }
2369 },
2370 linkAttr: {
Simon Huntc7ee0662014-11-05 16:44:37 -08002371 x1: function (d) { return d.source.x; },
2372 y1: function (d) { return d.source.y; },
2373 x2: function (d) { return d.target.x; },
2374 y2: function (d) { return d.target.y; }
Simon Huntd6f5a272014-11-29 23:45:50 -08002375 },
2376 linkLabelAttr: {
2377 transform: function (d) {
2378 var lnk = findLinkById(d.key);
Simon Hunte2575b62014-11-18 15:25:53 -08002379
Simon Huntd6f5a272014-11-29 23:45:50 -08002380 if (lnk) {
2381 var parms = {
2382 x1: lnk.source.x,
2383 y1: lnk.source.y,
2384 x2: lnk.target.x,
2385 y2: lnk.target.y
2386 };
2387 return transformLabel(parms);
2388 }
Thomas Vachuska4731f122014-11-20 04:56:19 -08002389 }
Simon Huntd6f5a272014-11-29 23:45:50 -08002390 }
2391 };
2392
2393 function tick() {
2394 node.attr(tickStuff.nodeAttr);
2395 link.attr(tickStuff.linkAttr);
2396 linkLabel.attr(tickStuff.linkLabelAttr);
Simon Huntc7ee0662014-11-05 16:44:37 -08002397 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08002398
2399 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002400 // Web-Socket for live data
2401
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002402 function findGuiSuccessor() {
2403 var idx = -1;
2404 onosOrder.forEach(function (d, i) {
2405 if (d.uiAttached) {
2406 idx = i;
2407 }
2408 });
2409
2410 for (var i = 0; i < onosOrder.length - 1; i++) {
2411 var ni = (idx + 1 + i) % onosOrder.length;
2412 if (onosOrder[ni].online) {
2413 return onosOrder[ni].ip;
2414 }
2415 }
2416 return null;
2417 }
2418
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002419 function webSockUrl() {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002420 var url = document.location.toString()
2421 .replace(/\#.*/, '')
2422 .replace('http://', 'ws://')
2423 .replace('https://', 'wss://')
2424 .replace('index.html', config.webSockUrl);
2425 if (guiSuccessor) {
2426 url = url.replace(location.hostname, guiSuccessor);
2427 }
2428 return url;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002429 }
2430
2431 webSock = {
2432 ws : null,
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002433 retries: 0,
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002434
2435 connect : function() {
2436 webSock.ws = new WebSocket(webSockUrl());
2437
2438 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08002439 noWebSock(false);
Thomas Vachuska47635c62014-11-22 01:21:36 -08002440 requestSummary();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002441 showInstances();
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002442 webSock.retries = 0;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002443 };
2444
2445 webSock.ws.onmessage = function(m) {
2446 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002447 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08002448 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002449 }
2450 };
2451
2452 webSock.ws.onclose = function(m) {
2453 webSock.ws = null;
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002454 guiSuccessor = findGuiSuccessor();
2455 if (guiSuccessor && webSock.retries < onosOrder.length) {
2456 webSock.retries++;
2457 webSock.connect();
2458 } else {
2459 noWebSock(true);
2460 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002461 };
2462 },
2463
2464 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002465 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002466 webSock._send(text);
2467 }
2468 },
2469
2470 _send : function(message) {
2471 if (webSock.ws) {
2472 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002473 } else if (config.useLiveData) {
Simon Hunt434cf142014-11-24 11:10:28 -08002474 console.warn('no web socket open', message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002475 } else {
Simon Hunt434cf142014-11-24 11:10:28 -08002476 console.log('WS Send: ', message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002477 }
2478 }
2479
2480 };
2481
Simon Hunt0c6d4192014-11-12 12:07:10 -08002482 function noWebSock(b) {
2483 mask.style('display',b ? 'block' : 'none');
2484 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002485
2486 function sendMessage(evType, payload) {
Simon Huntc1cc81c2014-11-29 14:59:01 -08002487 var p = payload || {},
2488 toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08002489 event: evType,
2490 sid: ++sid,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002491 payload: p
Simon Huntbb282f52014-11-10 11:08:19 -08002492 },
2493 asText = JSON.stringify(toSend);
2494 wsTraceTx(asText);
2495 webSock.send(asText);
Simon Huntc76ae892014-11-18 17:31:51 -08002496
2497 // Temporary measure for debugging UI behavior ...
2498 if (!config.useLiveData) {
2499 handleTestSend(toSend);
2500 }
Simon Huntbb282f52014-11-10 11:08:19 -08002501 }
2502
2503 function wsTraceTx(msg) {
2504 wsTrace('tx', msg);
2505 }
2506 function wsTraceRx(msg) {
2507 wsTrace('rx', msg);
2508 }
2509 function wsTrace(rxtx, msg) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002510 // console.log('[' + rxtx + '] ' + msg);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002511 }
2512
Simon Huntc2465f42014-12-04 10:28:07 -08002513 function handleTestSend(msg) { }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002514
2515 // ==============================
2516 // Selection stuff
2517
2518 function selectObject(obj, el) {
2519 var n,
Simon Hunta1162d82014-12-03 14:34:43 -08002520 ev = d3.event.sourceEvent;
Simon Hunt01095ff2014-11-13 16:37:29 -08002521
Simon Hunta1162d82014-12-03 14:34:43 -08002522 // if the meta or alt key is pressed, we are panning/zooming, so ignore
2523 if (ev.metaKey || ev.altKey) {
Simon Hunt01095ff2014-11-13 16:37:29 -08002524 return;
2525 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002526
2527 if (el) {
2528 n = d3.select(el);
2529 } else {
2530 node.each(function(d) {
2531 if (d == obj) {
2532 n = d3.select(el = this);
2533 }
2534 });
2535 }
2536 if (!n) return;
2537
Simon Hunta1162d82014-12-03 14:34:43 -08002538 if (ev.shiftKey && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002539 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002540 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002541 return;
2542 }
2543
Simon Hunta1162d82014-12-03 14:34:43 -08002544 if (!ev.shiftKey) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002545 deselectAll();
2546 }
2547
Simon Huntc31d5692014-11-12 13:27:18 -08002548 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002549 selectOrder.push(obj.id);
2550
2551 n.classed('selected', true);
Simon Huntb0ecfa52014-11-23 21:05:12 -08002552 updateDeviceColors(obj);
Simon Hunt61d04042014-11-11 17:27:16 -08002553 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002554 }
2555
2556 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08002557 var obj = selections[id],
2558 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002559 if (obj) {
2560 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08002561 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08002562 idx = $.inArray(id, selectOrder);
2563 if (idx >= 0) {
2564 selectOrder.splice(idx, 1);
2565 }
Simon Huntb0ecfa52014-11-23 21:05:12 -08002566 updateDeviceColors(obj.obj);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002567 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002568 }
2569
2570 function deselectAll() {
2571 // deselect all nodes in the network...
2572 node.classed('selected', false);
2573 selections = {};
2574 selectOrder = [];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002575 updateDeviceColors();
Simon Hunt61d04042014-11-11 17:27:16 -08002576 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002577 }
2578
Simon Huntb0ecfa52014-11-23 21:05:12 -08002579 function updateDeviceColors(d) {
2580 if (d) {
2581 setDeviceColor(d);
2582 } else {
2583 node.filter('.device').each(function (d) {
2584 setDeviceColor(d);
2585 });
2586 }
Thomas Vachuska47635c62014-11-22 01:21:36 -08002587 }
2588
Simon Hunt61d04042014-11-11 17:27:16 -08002589 // update the state of the detail pane, based on current selections
2590 function updateDetailPane() {
2591 var nSel = selectOrder.length;
2592 if (!nSel) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002593 emptySelect();
Simon Hunt61d04042014-11-11 17:27:16 -08002594 } else if (nSel === 1) {
2595 singleSelect();
2596 } else {
2597 multiSelect();
2598 }
2599 }
2600
Thomas Vachuska9edca302014-11-22 17:06:42 -08002601 function emptySelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002602 haveDetails = false;
Simon Hunt06811b72014-11-25 18:54:48 -08002603 hideDetailPane();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002604 cancelTraffic();
2605 }
2606
Simon Hunt61d04042014-11-11 17:27:16 -08002607 function singleSelect() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002608 // NOTE: detail is shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08002609 requestDetails();
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002610 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002611 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002612 }
2613
2614 function multiSelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002615 haveDetails = true;
Simon Huntb53e0682014-11-12 13:32:01 -08002616 populateMultiSelect();
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002617 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002618 requestTrafficForMode();
Simon Huntb53e0682014-11-12 13:32:01 -08002619 }
2620
2621 function addSep(tbody) {
2622 var tr = tbody.append('tr');
2623 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
2624 }
2625
2626 function addProp(tbody, label, value) {
2627 var tr = tbody.append('tr');
2628
2629 tr.append('td')
2630 .attr('class', 'label')
2631 .text(label + ' :');
2632
2633 tr.append('td')
2634 .attr('class', 'value')
2635 .text(value);
2636 }
2637
2638 function populateMultiSelect() {
2639 detailPane.empty();
2640
Simon Hunta3dd9572014-11-20 15:22:41 -08002641 var title = detailPane.append('h3'),
2642 table = detailPane.append('table'),
2643 tbody = table.append('tbody');
Simon Huntb53e0682014-11-12 13:32:01 -08002644
Thomas Vachuska4731f122014-11-20 04:56:19 -08002645 title.text('Selected Nodes');
Simon Huntb53e0682014-11-12 13:32:01 -08002646
2647 selectOrder.forEach(function (d, i) {
2648 addProp(tbody, i+1, d);
2649 });
Simon Huntd72bc702014-11-13 18:38:04 -08002650
2651 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08002652 }
2653
Thomas Vachuska47635c62014-11-22 01:21:36 -08002654 function populateSummary(data) {
2655 summaryPane.empty();
2656
2657 var svg = summaryPane.append('svg'),
2658 iid = iconGlyphUrl(data);
2659
2660 var title = summaryPane.append('h2'),
2661 table = summaryPane.append('table'),
2662 tbody = table.append('tbody');
2663
2664 appendGlyph(svg, 0, 0, 40, iid);
2665
2666 svg.append('use')
2667 .attr({
2668 class: 'birdBadge',
2669 transform: translate(8,12),
2670 'xlink:href': '#bird',
2671 width: 24,
2672 height: 24,
2673 fill: '#fff'
2674 });
2675
2676 title.text('ONOS Summary');
2677
2678 data.propOrder.forEach(function(p) {
2679 if (p === '-') {
2680 addSep(tbody);
2681 } else {
2682 addProp(tbody, p, data.props[p]);
2683 }
2684 });
2685 }
2686
Simon Hunt61d04042014-11-11 17:27:16 -08002687 function populateDetails(data) {
2688 detailPane.empty();
2689
Simon Hunta6a9fe72014-11-20 11:17:12 -08002690 var svg = detailPane.append('svg'),
2691 iid = iconGlyphUrl(data);
2692
Simon Hunta3dd9572014-11-20 15:22:41 -08002693 var title = detailPane.append('h2'),
2694 table = detailPane.append('table'),
2695 tbody = table.append('tbody');
Simon Hunt61d04042014-11-11 17:27:16 -08002696
Simon Hunta6a9fe72014-11-20 11:17:12 -08002697 appendGlyph(svg, 0, 0, 40, iid);
2698 title.text(data.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002699
2700 data.propOrder.forEach(function(p) {
2701 if (p === '-') {
2702 addSep(tbody);
2703 } else {
2704 addProp(tbody, p, data.props[p]);
2705 }
2706 });
Simon Huntd72bc702014-11-13 18:38:04 -08002707
Thomas Vachuska4731f122014-11-20 04:56:19 -08002708 addSingleSelectActions(data);
Simon Hunt61d04042014-11-11 17:27:16 -08002709 }
2710
Thomas Vachuska4731f122014-11-20 04:56:19 -08002711 function addSingleSelectActions(data) {
Simon Huntd72bc702014-11-13 18:38:04 -08002712 detailPane.append('hr');
2713 // always want to allow 'show traffic'
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002714 addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
Thomas Vachuska4731f122014-11-20 04:56:19 -08002715
2716 if (data.type === 'switch') {
2717 addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
2718 }
Simon Huntd72bc702014-11-13 18:38:04 -08002719 }
2720
2721 function addMultiSelectActions() {
2722 detailPane.append('hr');
2723 // always want to allow 'show traffic'
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002724 addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002725 // if exactly two hosts are selected, also want 'add host intent'
2726 if (nSel() === 2 && allSelectionsClass('host')) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002727 addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2728 } else if (nSel() >= 2 && allSelectionsClass('host')) {
2729 addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002730 }
2731 }
2732
Simon Hunta5e89142014-11-14 07:00:33 -08002733 function addAction(panel, text, cb) {
2734 panel.append('div')
Simon Huntd72bc702014-11-13 18:38:04 -08002735 .classed('actionBtn', true)
2736 .text(text)
2737 .on('click', cb);
2738 }
2739
2740
Simon Hunt3c5ca542014-11-29 14:11:43 -08002741 // === Pan and Zoom behaviors...
2742
2743 function panZoom(translate, scale) {
2744 panZoomContainer.attr('transform',
2745 'translate(' + translate + ')scale(' + scale + ')');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002746 // keep the map lines constant width while zooming
Simon Hunt3c5ca542014-11-29 14:11:43 -08002747 bgImg.style('stroke-width', 2.0 / scale + 'px');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002748 }
2749
Simon Hunt3c5ca542014-11-29 14:11:43 -08002750 function resetPanZoom() {
2751 panZoom([0,0], 1);
2752 zoom.translate([0,0]).scale(1);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002753 }
2754
Simon Hunt3c5ca542014-11-29 14:11:43 -08002755 function setupPanZoom() {
Paul Greysonfcba0e82014-11-13 10:21:16 -08002756 function zoomed() {
Simon Hunta1162d82014-12-03 14:34:43 -08002757 var ev = d3.event.sourceEvent;
2758 // pan/zoom active when meta or alt key is pressed...
2759 if (ev.metaKey || ev.altKey) {
Simon Hunt3c5ca542014-11-29 14:11:43 -08002760 panZoom(d3.event.translate, d3.event.scale);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002761 }
2762 }
2763
2764 zoom = d3.behavior.zoom()
2765 .translate([0, 0])
2766 .scale(1)
Simon Hunt1b18aa52014-11-29 17:57:55 -08002767 .scaleExtent([0.25, 10])
Paul Greysonfcba0e82014-11-13 10:21:16 -08002768 .on("zoom", zoomed);
2769
2770 svg.call(zoom);
2771 }
2772
Simon Hunt61d04042014-11-11 17:27:16 -08002773 // ==============================
2774 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08002775
2776 function prepareScenario(view, ctx, dbg) {
2777 var sc = scenario,
2778 urlSc = sc.evDir + ctx + sc.evScenario;
2779
2780 if (!ctx) {
2781 view.alert("No scenario specified (null ctx)");
2782 return;
2783 }
2784
2785 sc.view = view;
2786 sc.ctx = ctx;
2787 sc.debug = dbg;
2788 sc.evNumber = 0;
2789
2790 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002791 var p = data && data.params || {},
2792 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08002793 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08002794
Simon Hunt56d51852014-11-09 13:03:35 -08002795 if (err) {
2796 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
2797 } else {
2798 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08002799 if (desc) {
2800 intro += '\n\n ' + desc.join('\n ');
2801 }
2802 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08002803 }
2804 });
2805
2806 }
2807
Simon Hunt9f1bced2014-12-02 14:36:39 -08002808 function setupDefs(svg) {
Simon Hunt7fa116d2014-11-17 14:16:55 -08002809 var defs = svg.append('defs');
Simon Hunt9f1bced2014-12-02 14:36:39 -08002810 gly.loadDefs(defs);
2811 d3u.loadGlow(defs);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002812 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002813
Simon Hunt395a70c2014-11-22 23:17:40 -08002814 function sendUpdateMeta(d, store) {
2815 var metaUi = {},
2816 ll;
2817
2818 if (store) {
Simon Hunt1b18aa52014-11-29 17:57:55 -08002819 ll = geoMapProj.invert([d.x, d.y]);
Simon Hunt62c47542014-11-22 22:16:32 -08002820 metaUi = {
2821 x: d.x,
2822 y: d.y,
2823 lng: ll[0],
2824 lat: ll[1]
2825 };
Simon Hunt395a70c2014-11-22 23:17:40 -08002826 }
Simon Hunt62c47542014-11-22 22:16:32 -08002827 d.metaUi = metaUi;
2828 sendMessage('updateMeta', {
2829 id: d.id,
2830 'class': d.class,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002831 memento: metaUi
Simon Hunt62c47542014-11-22 22:16:32 -08002832 });
2833 }
2834
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002835 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08002836 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08002837
Simon Hunta2994cc2014-12-02 14:19:15 -08002838 function init(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08002839 var w = view.width(),
2840 h = view.height(),
Simon Hunt1b18aa52014-11-29 17:57:55 -08002841 fcfg = config.force;
Simon Hunt195cb382014-11-03 17:50:51 -08002842
Simon Hunt142d0032014-11-04 20:13:09 -08002843 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002844 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
2845 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002846 setSize(svg, view);
2847
Simon Hunt9f1bced2014-12-02 14:36:39 -08002848 // load glyphs, filters, and other definitions...
2849 setupDefs(svg);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002850
Simon Hunt3c5ca542014-11-29 14:11:43 -08002851 panZoomContainer = svg.append('g').attr('id', 'panZoomContainer');
2852 setupPanZoom();
Paul Greysonfcba0e82014-11-13 10:21:16 -08002853
Simon Huntc7ee0662014-11-05 16:44:37 -08002854 // group for the topology
Simon Hunt3c5ca542014-11-29 14:11:43 -08002855 topoG = panZoomContainer.append('g')
Simon Hunt1b18aa52014-11-29 17:57:55 -08002856 .attr('id', 'topo-G');
Simon Huntc7ee0662014-11-05 16:44:37 -08002857
Simon Hunte2575b62014-11-18 15:25:53 -08002858 // subgroups for links, link labels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002859 linkG = topoG.append('g').attr('id', 'links');
Simon Hunte2575b62014-11-18 15:25:53 -08002860 linkLabelG = topoG.append('g').attr('id', 'linkLabels');
Simon Huntc7ee0662014-11-05 16:44:37 -08002861 nodeG = topoG.append('g').attr('id', 'nodes');
2862
Simon Hunte2575b62014-11-18 15:25:53 -08002863 // selection of links, linkLabels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002864 link = linkG.selectAll('.link');
Simon Hunte2575b62014-11-18 15:25:53 -08002865 linkLabel = linkLabelG.selectAll('.linkLabel');
Simon Huntc7ee0662014-11-05 16:44:37 -08002866 node = nodeG.selectAll('.node');
2867
Simon Hunt7cd48f32014-11-09 23:42:50 -08002868 function chrg(d) {
2869 return fcfg.charge[d.class] || -12000;
2870 }
Simon Hunt99c13842014-11-06 18:23:12 -08002871 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002872 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08002873 }
2874 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002875 // 0.0 - 1.0
2876 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08002877 }
2878
Simon Hunt1a9eff92014-11-07 11:06:34 -08002879 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002880 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002881 }
2882
2883 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08002884 // once we've finished moving, pin the node in position
2885 d.fixed = true;
2886 d3.select(self).classed('fixed', true);
2887 if (config.useLiveData) {
Simon Hunt395a70c2014-11-22 23:17:40 -08002888 sendUpdateMeta(d, true);
Simon Hunta255a2c2014-11-13 22:29:35 -08002889 } else {
2890 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08002891 }
2892 }
2893
Simon Hunt6e18fe32014-11-29 13:35:41 -08002894 // predicate that indicates when dragging is active
2895 function dragEnabled() {
Simon Hunta1162d82014-12-03 14:34:43 -08002896 var ev = d3.event.sourceEvent;
2897 // nodeLock means we aren't allowing nodes to be dragged...
2898 // meta or alt key pressed means we are zooming/panning...
2899 return !nodeLock && !(ev.metaKey || ev.altKey);
Simon Huntc2367d52014-11-29 19:30:23 -08002900 }
2901
2902 // predicate that indicates when clicking is active
2903 function clickEnabled() {
2904 return true;
Simon Hunt6e18fe32014-11-29 13:35:41 -08002905 }
2906
Simon Huntc7ee0662014-11-05 16:44:37 -08002907 // set up the force layout
2908 network.force = d3.layout.force()
Simon Hunt1b18aa52014-11-29 17:57:55 -08002909 .size([w, h])
Simon Huntc7ee0662014-11-05 16:44:37 -08002910 .nodes(network.nodes)
2911 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002912 .gravity(0.4)
2913 .friction(0.7)
2914 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08002915 .linkDistance(ldist)
2916 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08002917 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08002918
Simon Hunt01095ff2014-11-13 16:37:29 -08002919 network.drag = d3u.createDragBehavior(network.force,
Simon Huntc2367d52014-11-29 19:30:23 -08002920 selectCb, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt6e18fe32014-11-29 13:35:41 -08002921
Simon Hunt0c6d4192014-11-12 12:07:10 -08002922
2923 // create mask layer for when we lose connection to server.
Simon Hunta5e89142014-11-14 07:00:33 -08002924 // TODO: this should be part of the framework
Simon Hunt6e18fe32014-11-29 13:35:41 -08002925
2926 function para(sel, text) {
2927 sel.append('p').text(text);
2928 }
2929
Simon Hunt0c6d4192014-11-12 12:07:10 -08002930 mask = view.$div.append('div').attr('id','topo-mask');
2931 para(mask, 'Oops!');
2932 para(mask, 'Web-socket connection to server closed...');
2933 para(mask, 'Try refreshing the page.');
Simon Hunt12ce12e2014-11-15 21:13:19 -08002934
2935 mask.append('svg')
2936 .attr({
2937 id: 'mask-bird',
2938 width: w,
2939 height: h
2940 })
2941 .append('g')
2942 .attr('transform', birdTranslate(w, h))
2943 .style('opacity', 0.3)
2944 .append('use')
2945 .attr({
2946 'xlink:href': '#bird',
2947 width: config.birdDim,
2948 height: config.birdDim,
2949 fill: '#111'
Thomas Vachuska89543292014-11-19 11:28:33 -08002950 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002951 }
Simon Hunt195cb382014-11-03 17:50:51 -08002952
Simon Hunt01095ff2014-11-13 16:37:29 -08002953
Simon Hunt56d51852014-11-09 13:03:35 -08002954 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08002955 // resize, in case the window was resized while we were not loaded
2956 resize(view, ctx, flags);
2957
Simon Hunt99c13842014-11-06 18:23:12 -08002958 // cache the view token, so network topo functions can access it
2959 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08002960 config.useLiveData = !flags.local;
2961
2962 if (!config.useLiveData) {
2963 prepareScenario(view, ctx, flags.debug);
2964 }
Simon Hunt99c13842014-11-06 18:23:12 -08002965
2966 // set our radio buttons and key bindings
Simon Hunt9462e8c2014-11-14 17:28:09 -08002967 layerBtnSet = view.setRadio(layerButtons);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002968 view.setKeys(keyDispatch);
Simon Hunt87514342014-11-24 16:41:27 -08002969 view.setGestures(gestures);
Simon Hunt195cb382014-11-03 17:50:51 -08002970
Simon Huntd3b7d512014-11-12 15:48:41 -08002971 // Load map data asynchronously; complete startup after that..
2972 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08002973 }
2974
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002975 function startAntTimer() {
Thomas Vachuskab7e40642014-12-03 11:16:06 -08002976 // Note: disabled until traffic can be allotted to intents properly
2977 if (false && !antTimer) {
Thomas Vachuska4731f122014-11-20 04:56:19 -08002978 var pulses = [5, 3, 1.2, 3],
2979 pulse = 0;
2980 antTimer = setInterval(function () {
2981 pulse = pulse + 1;
2982 pulse = pulse === pulses.length ? 0 : pulse;
2983 d3.selectAll('.animated').style('stroke-width', pulses[pulse]);
2984 }, 200);
2985 }
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002986 }
2987
2988 function stopAntTimer() {
Simon Hunta255a2c2014-11-13 22:29:35 -08002989 if (antTimer) {
2990 clearInterval(antTimer);
2991 antTimer = null;
2992 }
Simon Huntd3b7d512014-11-12 15:48:41 -08002993 }
2994
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08002995 function unload(view, ctx, flags) {
2996 stopAntTimer();
2997 }
2998
Simon Hunta6a9fe72014-11-20 11:17:12 -08002999 var geoJsonUrl = 'json/map/continental_us.json',
Simon Huntd3b7d512014-11-12 15:48:41 -08003000 geoJson;
3001
3002 function loadGeoJsonData() {
3003 d3.json(geoJsonUrl, function (err, data) {
3004 if (err) {
Simon Huntc2465f42014-12-04 10:28:07 -08003005 console.error('failed to load Map data', err);
Simon Huntd3b7d512014-11-12 15:48:41 -08003006 } else {
3007 geoJson = data;
3008 loadGeoMap();
3009 }
3010
3011 // finally, connect to the server...
3012 if (config.useLiveData) {
3013 webSock.connect();
3014 }
3015 });
3016 }
3017
Simon Hunt1b18aa52014-11-29 17:57:55 -08003018 function setProjForView(path, topoData) {
3019 var dim = config.logicalSize;
3020
3021 // start with unit scale, no translation..
3022 geoMapProj.scale(1).translate([0, 0]);
3023
3024 // figure out dimensions of map data..
3025 var b = path.bounds(topoData),
3026 x1 = b[0][0],
3027 y1 = b[0][1],
3028 x2 = b[1][0],
3029 y2 = b[1][1],
3030 dx = x2 - x1,
3031 dy = y2 - y1,
3032 x = (x1 + x2) / 2,
3033 y = (y1 + y2) / 2;
3034
3035 // size map to 95% of minimum dimension to fill space..
3036 var s = .95 / Math.min(dx / dim, dy / dim);
3037 var t = [dim / 2 - s * x, dim / 2 - s * y];
3038
3039 // set new scale, translation on the projection..
3040 geoMapProj.scale(s).translate(t);
3041 }
3042
Simon Huntd3b7d512014-11-12 15:48:41 -08003043 function loadGeoMap() {
3044 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08003045
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003046 // extracts the topojson data into geocoordinate-based geometry
3047 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08003048
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003049 // see: http://bl.ocks.org/mbostock/4707858
Simon Hunt1b18aa52014-11-29 17:57:55 -08003050 geoMapProj = d3.geo.mercator();
3051 var path = d3.geo.path().projection(geoMapProj);
Simon Huntd3b7d512014-11-12 15:48:41 -08003052
Simon Hunt1b18aa52014-11-29 17:57:55 -08003053 setProjForView(path, topoData);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003054
Simon Hunt3c5ca542014-11-29 14:11:43 -08003055 bgImg = panZoomContainer.insert("g", '#topo-G');
Thomas Vachuska89543292014-11-19 11:28:33 -08003056 bgImg.attr('id', 'map').selectAll('path')
3057 .data(topoData.features)
3058 .enter()
3059 .append('path')
3060 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08003061 }
3062
Simon Huntf67722a2014-11-10 09:32:06 -08003063 function resize(view, ctx, flags) {
Simon Hunt12ce12e2014-11-15 21:13:19 -08003064 var w = view.width(),
3065 h = view.height();
3066
Simon Hunt934c3ce2014-11-05 11:45:07 -08003067 setSize(svg, view);
Simon Hunt12ce12e2014-11-15 21:13:19 -08003068
3069 d3.select('#mask-bird').attr({ width: w, height: h})
3070 .select('g').attr('transform', birdTranslate(w, h));
Simon Hunt142d0032014-11-04 20:13:09 -08003071 }
3072
Simon Hunt8f40cce2014-11-23 15:57:30 -08003073 function theme(view, ctx, flags) {
3074 updateInstances();
Simon Huntb0ecfa52014-11-23 21:05:12 -08003075 updateDeviceColors();
Simon Hunt8f40cce2014-11-23 15:57:30 -08003076 }
3077
Simon Hunt12ce12e2014-11-15 21:13:19 -08003078 function birdTranslate(w, h) {
3079 var bdim = config.birdDim;
3080 return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
3081 }
Simon Hunt142d0032014-11-04 20:13:09 -08003082
Simon Hunt06811b72014-11-25 18:54:48 -08003083 function isF(f) { return $.isFunction(f) ? f : null; }
3084 function noop() {}
3085
3086 function augmentDetailPane() {
3087 var dp = detailPane;
3088 dp.ypos = { up: 64, down: 320, current: 320};
3089
3090 dp._move = function (y, cb) {
3091 var endCb = isF(cb) || noop,
3092 yp = dp.ypos;
3093 if (yp.current !== y) {
3094 yp.current = y;
3095 dp.el.transition().duration(300)
3096 .each('end', endCb)
3097 .style('top', yp.current + 'px');
3098 } else {
3099 endCb();
3100 }
3101 };
3102
3103 dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
3104 dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
3105 }
3106
Simon Hunt142d0032014-11-04 20:13:09 -08003107 // ==============================
3108 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08003109
Simon Hunt25248912014-11-04 11:25:48 -08003110 onos.ui.addView('topo', {
Simon Hunta2994cc2014-12-02 14:19:15 -08003111 init: init,
Simon Hunt142d0032014-11-04 20:13:09 -08003112 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08003113 unload: unload,
Simon Hunt8f40cce2014-11-23 15:57:30 -08003114 resize: resize,
3115 theme: theme
Simon Hunt195cb382014-11-03 17:50:51 -08003116 });
3117
Thomas Vachuska47635c62014-11-22 01:21:36 -08003118 summaryPane = onos.ui.addFloatingPanel('topo-summary');
Simon Hunt61d04042014-11-11 17:27:16 -08003119 detailPane = onos.ui.addFloatingPanel('topo-detail');
Simon Hunt06811b72014-11-25 18:54:48 -08003120 augmentDetailPane();
Simon Hunta5e89142014-11-14 07:00:33 -08003121 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
Simon Huntb82f6902014-11-22 11:53:15 -08003122 oiBox.width(20);
Simon Hunt61d04042014-11-11 17:27:16 -08003123
Simon Hunt195cb382014-11-03 17:50:51 -08003124}(ONOS));