blob: 7620009ef9dbff00ebc76c9088d9c1cf9d4f4819 [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 Hunt233747b2014-12-04 12:01:58 -0800125 // ==== "development mode" ====
126 //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 Hunt233747b2014-12-04 12:01:58 -0800228 noDevices,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800229 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800230 topoG,
231 nodeG,
232 linkG,
Simon Hunte2575b62014-11-18 15:25:53 -0800233 linkLabelG,
Simon Huntc7ee0662014-11-05 16:44:37 -0800234 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800235 link,
Simon Hunte2575b62014-11-18 15:25:53 -0800236 linkLabel,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800237 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800238
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800239 // the projection for the map background
Simon Hunt1b18aa52014-11-29 17:57:55 -0800240 var geoMapProj;
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800241
Paul Greysonfcba0e82014-11-13 10:21:16 -0800242 // the zoom function
243 var zoom;
244
Simon Hunt142d0032014-11-04 20:13:09 -0800245 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800246 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800247
Simon Huntfc274c92014-11-11 11:05:46 -0800248 function fnTrace(msg, id) {
249 if (config.fnTrace) {
250 console.log('FN: ' + msg + ' [' + id + ']');
251 }
252 }
Simon Hunt99c13842014-11-06 18:23:12 -0800253
Simon Hunta5e89142014-11-14 07:00:33 -0800254 function evTrace(data) {
255 fnTrace(data.event, data.payload.id);
256 }
257
Simon Hunt934c3ce2014-11-05 11:45:07 -0800258 // ==============================
259 // Key Callbacks
260
Simon Hunt27d322d2014-11-28 10:45:43 -0800261 function flash(txt) {
262 network.view.flash(txt);
263 }
264
Simon Hunt99c13842014-11-06 18:23:12 -0800265 function testMe(view) {
Simon Hunt8f40cce2014-11-23 15:57:30 -0800266 //view.alert('Theme is ' + view.getTheme());
Simon Hunta3dd9572014-11-20 15:22:41 -0800267 //view.flash('This is some text');
Simon Hunt8f40cce2014-11-23 15:57:30 -0800268 cat7.testCard(svg);
Simon Hunt99c13842014-11-06 18:23:12 -0800269 }
270
Simon Hunt56d51852014-11-09 13:03:35 -0800271 function injectTestEvent(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800272 if (config.useLiveData) { return; }
273
Simon Hunt56d51852014-11-09 13:03:35 -0800274 var sc = scenario,
275 evn = ++sc.evNumber,
276 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
277 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800278 uiUrl = pfx + sc.evUi,
279 stack = [
280 { url: onosUrl, cb: handleServerEvent },
281 { url: uiUrl, cb: handleUiEvent }
282 ];
283 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800284 }
285
Simon Hunt7cd48f32014-11-09 23:42:50 -0800286 function recurseFetchEvent(stack, evn) {
287 var v = scenario.view,
288 frame;
289 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800290 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800291 return;
292 }
293 frame = stack.shift();
294
295 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800296 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800297 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800298 // if we didn't find the data, try the next stack frame
299 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800300 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800301 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800302 }
Simon Hunt99c13842014-11-06 18:23:12 -0800303 } else {
Simon Hunt1712ed82014-11-17 12:56:00 -0800304 wsTrace('test', JSON.stringify(data));
Simon Hunt7cd48f32014-11-09 23:42:50 -0800305 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800306 }
307 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800308
Simon Hunt56d51852014-11-09 13:03:35 -0800309 }
Simon Hunt50128c02014-11-08 13:36:15 -0800310
Simon Hunt56d51852014-11-09 13:03:35 -0800311 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800312 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
313 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800314 }
315
316 function injectStartupEvents(view) {
317 var last = scenario.params.lastAuto || 0;
Simon Hunt434cf142014-11-24 11:10:28 -0800318 if (config.useLiveData) { return; }
Simon Hunt56d51852014-11-09 13:03:35 -0800319
320 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800321 injectTestEvent(view);
322 }
323 }
324
Simon Hunt934c3ce2014-11-05 11:45:07 -0800325 function toggleBg() {
326 var vis = bgImg.style('visibility');
Simon Hunt434cf142014-11-24 11:10:28 -0800327 bgImg.style('visibility', visVal(vis === 'hidden'));
328 }
329
Simon Huntd6f5a272014-11-29 23:45:50 -0800330 function opacifyBg(b) {
331 bgImg.transition()
332 .duration(1000)
333 .attr('opacity', b ? 1 : 0);
334 }
335
Simon Huntc2367d52014-11-29 19:30:23 -0800336 function toggleNodeLock() {
337 nodeLock = !nodeLock;
338 flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked'))
339 }
340
341 function toggleOblique() {
342 oblique = !oblique;
Simon Huntd6f5a272014-11-29 23:45:50 -0800343 if (oblique) {
344 network.force.stop();
345 toObliqueView();
346 } else {
347 toNormalView();
348 }
Simon Huntc2367d52014-11-29 19:30:23 -0800349 }
350
Simon Hunt434cf142014-11-24 11:10:28 -0800351 function toggleHosts() {
352 showHosts = !showHosts;
353 updateHostVisibility();
Simon Hunt27d322d2014-11-28 10:45:43 -0800354 flash('Hosts ' + visVal(showHosts));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800355 }
356
Simon Hunt6d9bd032014-11-28 22:16:40 -0800357 function toggleOffline() {
358 showOffline = !showOffline;
359 updateOfflineVisibility();
360 flash('Offline devices ' + visVal(showOffline));
361 }
362
Simon Hunt99c13842014-11-06 18:23:12 -0800363 function cycleLabels() {
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800364 deviceLabelIndex = (deviceLabelIndex === 2)
Simon Huntbb282f52014-11-10 11:08:19 -0800365 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800366
Simon Hunt99c13842014-11-06 18:23:12 -0800367 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800368 if (d.class === 'device') {
369 updateDeviceLabel(d);
370 }
Simon Hunt99c13842014-11-06 18:23:12 -0800371 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800372 }
373
374 function togglePorts(view) {
Simon Hunt434cf142014-11-24 11:10:28 -0800375 //view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800376 }
377
Simon Hunt6ac93f32014-11-13 12:17:27 -0800378 function unpin() {
379 if (hovered) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800380 sendUpdateMeta(hovered);
Simon Hunt6ac93f32014-11-13 12:17:27 -0800381 hovered.fixed = false;
382 hovered.el.classed('fixed', false);
Simon Huntd6f5a272014-11-29 23:45:50 -0800383 fResume();
Simon Hunt6ac93f32014-11-13 12:17:27 -0800384 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800385 }
386
Simon Hunt9462e8c2014-11-14 17:28:09 -0800387 function handleEscape(view) {
388 if (oiShowMaster) {
389 cancelAffinity();
Simon Hunt27d322d2014-11-28 10:45:43 -0800390 } else if (haveDetails) {
Simon Hunt9462e8c2014-11-14 17:28:09 -0800391 deselectAll();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800392 } else if (oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -0800393 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800394 } else if (summaryPane.isVisible()) {
395 cancelSummary();
Thomas Vachuska5bde31f2014-11-25 15:29:18 -0800396 stopAntTimer();
397 } else {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800398 hoverMode = hoverModeNone;
Simon Hunt9462e8c2014-11-14 17:28:09 -0800399 }
400 }
401
Simon Hunt233747b2014-12-04 12:01:58 -0800402 function showNoDevs(b) {
403 noDevices.style('visibility', visVal(b));
404 }
405
Simon Hunt934c3ce2014-11-05 11:45:07 -0800406 // ==============================
Simon Huntd6f5a272014-11-29 23:45:50 -0800407 // Oblique view ...
408
409 var obview = {
410 tt: -.7, // x skew y factor
411 xsk: -35, // x skew angle
412 ysc: 0.5, // y scale
413 pad: 50,
414 time: 1500,
415 fill: {
416 pkt: 'rgba(130,130,170,0.3)',
417 opt: 'rgba(170,130,170,0.3)'
418 },
419 id: function (tag) {
420 return 'obview-' + tag + 'Plane';
421 },
422 yt: function (h, dir) {
423 return h * obview.ysc * dir * 1.1;
424 },
425 obXform: function (h, dir) {
426 var yt = obview.yt(h, dir);
427 return scale(1, obview.ysc) + translate(0, yt) + skewX(obview.xsk);
428 },
429 noXform: function () {
430 return skewX(0) + translate(0,0) + scale(1,1);
431 },
432 xffn: null,
433 plane: {}
434 };
435
436
437 function toObliqueView() {
438 var box = nodeG.node().getBBox(),
439 ox, oy;
440
441 padBox(box, obview.pad);
442
443 ox = box.x + box.width / 2;
444 oy = box.y + box.height / 2;
445
446 // remember node lock state, then lock the nodes down
447 obview.nodeLock = nodeLock;
448 nodeLock = true;
449 opacifyBg(false);
450
451 insertPlanes(ox, oy);
452
453 obview.xffn = function (xy, dir) {
454 var yt = obview.yt(box.height, dir),
455 ax = xy.x - ox,
456 ay = xy.y - oy,
457 x = ax + ay * obview.tt,
458 y = ay * obview.ysc + obview.ysc * yt;
459 return {x: ox + x, y: oy + y};
460 };
461
462 showPlane('pkt', box, -1);
463 showPlane('opt', box, 1);
464 obTransitionNodes();
465 }
466
467 function toNormalView() {
468 obview.xffn = null;
469
470 hidePlane('pkt');
471 hidePlane('opt');
472 obTransitionNodes();
473
474 removePlanes();
475
476 // restore node lock state
477 nodeLock = obview.nodeLock;
478 opacifyBg(true);
479 }
480
481 function obTransitionNodes() {
482 var xffn = obview.xffn;
483
484 // return the direction for the node
485 // -1 for pkt layer, 1 for optical layer
486 function dir(d) {
487 return inLayer(d, 'pkt') ? -1 : 1;
488 }
489
490 if (xffn) {
491 network.nodes.forEach(function (d) {
492 var oldxy = {x: d.x, y: d.y},
493 coords = xffn(oldxy, dir(d));
494 d.oldxy = oldxy;
495 d.px = d.x = coords.x;
496 d.py = d.y = coords.y;
497 });
498 } else {
499 network.nodes.forEach(function (d) {
500 var old = d.oldxy || {x: d.x, y: d.y};
501 d.px = d.x = old.x;
502 d.py = d.y = old.y;
503 delete d.oldxy;
504 });
505 }
506
507 node.transition()
508 .duration(obview.time)
509 .attr(tickStuff.nodeAttr);
510 link.transition()
511 .duration(obview.time)
512 .attr(tickStuff.linkAttr);
513 linkLabel.transition()
514 .duration(obview.time)
515 .attr(tickStuff.linkLabelAttr);
516 }
517
518 function showPlane(tag, box, dir) {
519 var g = obview.plane[tag];
520
521 // set box origin at center..
522 box.x = -box.width/2;
523 box.y = -box.height/2;
524
525 g.select('rect')
526 .attr(box)
527 .attr('opacity', 0)
528 .transition()
529 .duration(obview.time)
530 .attr('opacity', 1)
531 .attr('transform', obview.obXform(box.height, dir));
532 }
533
534 function hidePlane(tag) {
535 var g = obview.plane[tag];
536
537 g.select('rect')
538 .transition()
539 .duration(obview.time)
540 .attr('opacity', 0)
541 .attr('transform', obview.noXform());
542 }
543
544 function insertPlanes(ox, oy) {
545 function ins(tag) {
546 var id = obview.id(tag),
547 g = panZoomContainer.insert('g', '#topo-G')
548 .attr('id', id)
549 .attr('transform', translate(ox,oy));
550 g.append('rect')
551 .attr('fill', obview.fill[tag])
552 .attr('opacity', 0);
553 obview.plane[tag] = g;
554 }
555 ins('opt');
556 ins('pkt');
557 }
558
559 function removePlanes() {
560 function rem(tag) {
561 var id = obview.id(tag);
562 panZoomContainer.select('#'+id)
563 .transition()
564 .duration(obview.time + 50)
565 .remove();
566 delete obview.plane[tag];
567 }
568 rem('opt');
569 rem('pkt');
570 }
571
572 function padBox(box, p) {
573 box.x -= p;
574 box.y -= p;
575 box.width += p*2;
576 box.height += p*2;
577 }
578
579 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800580 // Radio Button Callbacks
581
Simon Hunta5e89142014-11-14 07:00:33 -0800582 var layerLookup = {
583 host: {
Simon Hunt209155e2014-11-21 12:16:09 -0800584 endstation: 'pkt', // default, if host event does not define type
Thomas Vachuska89543292014-11-19 11:28:33 -0800585 router: 'pkt',
Simon Hunta5e89142014-11-14 07:00:33 -0800586 bgpSpeaker: 'pkt'
587 },
588 device: {
589 switch: 'pkt',
590 roadm: 'opt'
591 },
592 link: {
593 hostLink: 'pkt',
594 direct: 'pkt',
Simon Hunt8257f4c2014-11-16 19:34:54 -0800595 indirect: '',
596 tunnel: '',
Simon Hunta5e89142014-11-14 07:00:33 -0800597 optical: 'opt'
598 }
599 };
600
601 function inLayer(d, layer) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800602 var type = d.class === 'link' ? d.type() : d.type,
603 look = layerLookup[d.class],
604 lyr = look && look[type];
Simon Hunta5e89142014-11-14 07:00:33 -0800605 return lyr === layer;
606 }
607
608 function unsuppressLayer(which) {
609 node.each(function (d) {
610 var node = d.el;
611 if (inLayer(d, which)) {
612 node.classed('suppressed', false);
613 }
614 });
615
616 link.each(function (d) {
617 var link = d.el;
618 if (inLayer(d, which)) {
619 link.classed('suppressed', false);
620 }
621 });
622 }
623
Simon Hunt9462e8c2014-11-14 17:28:09 -0800624 function suppressLayers(b) {
625 node.classed('suppressed', b);
626 link.classed('suppressed', b);
Simon Hunt142d0032014-11-04 20:13:09 -0800627// d3.selectAll('svg .port').classed('inactive', false);
628// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt195cb382014-11-03 17:50:51 -0800629 }
630
Simon Hunt9462e8c2014-11-14 17:28:09 -0800631 function showAllLayers() {
632 suppressLayers(false);
633 }
634
Simon Hunt195cb382014-11-03 17:50:51 -0800635 function showPacketLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800636 node.classed('suppressed', true);
637 link.classed('suppressed', true);
638 unsuppressLayer('pkt');
Simon Hunt195cb382014-11-03 17:50:51 -0800639 }
640
641 function showOpticalLayer() {
Simon Hunta5e89142014-11-14 07:00:33 -0800642 node.classed('suppressed', true);
643 link.classed('suppressed', true);
644 unsuppressLayer('opt');
Simon Hunt195cb382014-11-03 17:50:51 -0800645 }
646
Simon Hunt9462e8c2014-11-14 17:28:09 -0800647 function restoreLayerState() {
648 layerBtnDispatch[layerBtnSet.selected()]();
649 }
650
Simon Hunt142d0032014-11-04 20:13:09 -0800651 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800652 // Private functions
653
Simon Hunt99c13842014-11-06 18:23:12 -0800654 function safeId(s) {
655 return s.replace(/[^a-z0-9]/gi, '-');
656 }
657
Simon Huntc7ee0662014-11-05 16:44:37 -0800658 // set the size of the given element to that of the view (reduced if padded)
659 function setSize(el, view, pad) {
660 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800661 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800662 width: view.width() - padding,
663 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800664 });
665 }
666
Simon Hunt8257f4c2014-11-16 19:34:54 -0800667 function makeNodeKey(d, what) {
668 var port = what + 'Port';
669 return d[what] + '/' + d[port];
670 }
671
672 function makeLinkKey(d, flipped) {
673 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
674 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
675 return one + '-' + two;
676 }
677
Simon Hunt269670f2014-11-17 16:17:43 -0800678 function findLinkById(id) {
679 // check to see if this is a reverse lookup, else default to given id
680 var key = network.revLinkToKey[id] || id;
681 return key && network.lookup[key];
682 }
683
Simon Hunt8257f4c2014-11-16 19:34:54 -0800684 function findLink(linkData, op) {
685 var key = makeLinkKey(linkData),
686 keyrev = makeLinkKey(linkData, 1),
687 link = network.lookup[key],
688 linkRev = network.lookup[keyrev],
689 result = {},
690 ldata = link || linkRev,
691 rawLink;
692
693 if (op === 'add') {
694 if (link) {
695 // trying to add a link that we already know about
696 result.ldata = link;
697 result.badLogic = 'addLink: link already added';
698
699 } else if (linkRev) {
700 // we found the reverse of the link to be added
701 result.ldata = linkRev;
702 if (linkRev.fromTarget) {
703 result.badLogic = 'addLink: link already added';
704 }
705 }
706 } else if (op === 'update') {
707 if (!ldata) {
708 result.badLogic = 'updateLink: link not found';
709 } else {
710 rawLink = link ? ldata.fromSource : ldata.fromTarget;
711 result.updateWith = function (data) {
712 $.extend(rawLink, data);
713 restyleLinkElement(ldata);
714 }
715 }
716 } else if (op === 'remove') {
717 if (!ldata) {
718 result.badLogic = 'removeLink: link not found';
719 } else {
720 rawLink = link ? ldata.fromSource : ldata.fromTarget;
721
722 if (!rawLink) {
723 result.badLogic = 'removeLink: link not found';
724
725 } else {
726 result.removeRawLink = function () {
727 if (link) {
728 // remove fromSource
729 ldata.fromSource = null;
730 if (ldata.fromTarget) {
731 // promote target into source position
732 ldata.fromSource = ldata.fromTarget;
733 ldata.fromTarget = null;
734 ldata.key = keyrev;
735 delete network.lookup[key];
736 network.lookup[keyrev] = ldata;
Simon Hunt269670f2014-11-17 16:17:43 -0800737 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800738 }
739 } else {
740 // remove fromTarget
741 ldata.fromTarget = null;
Simon Hunt269670f2014-11-17 16:17:43 -0800742 delete network.revLinkToKey[keyrev];
Simon Hunt8257f4c2014-11-16 19:34:54 -0800743 }
744 if (ldata.fromSource) {
745 restyleLinkElement(ldata);
746 } else {
747 removeLinkElement(ldata);
748 }
749 }
750 }
751 }
752 }
753 return result;
754 }
755
756 function addLinkUpdate(ldata, link) {
757 // add link event, but we already have the reverse link installed
758 ldata.fromTarget = link;
Simon Hunt269670f2014-11-17 16:17:43 -0800759 network.revLinkToKey[link.id] = ldata.key;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800760 restyleLinkElement(ldata);
761 }
762
763 var allLinkTypes = 'direct indirect optical tunnel',
764 defaultLinkType = 'direct';
765
766 function restyleLinkElement(ldata) {
767 // this fn's job is to look at raw links and decide what svg classes
768 // need to be applied to the line element in the DOM
769 var el = ldata.el,
770 type = ldata.type(),
771 lw = ldata.linkWidth(),
772 online = ldata.online();
773
774 el.classed('link', true);
775 el.classed('inactive', !online);
776 el.classed(allLinkTypes, false);
777 if (type) {
778 el.classed(type, true);
779 }
780 el.transition()
781 .duration(1000)
Thomas Vachuska89543292014-11-19 11:28:33 -0800782 .attr('stroke-width', linkScale(lw))
783 .attr('stroke', config.topo.linkBaseColor);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800784 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800785
Simon Hunt99c13842014-11-06 18:23:12 -0800786 // ==============================
787 // Event handlers for server-pushed events
788
Simon Huntbb282f52014-11-10 11:08:19 -0800789 function logicError(msg) {
790 // TODO, report logic error to server, via websock, so it can be logged
Simon Huntfc274c92014-11-11 11:05:46 -0800791 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800792 }
793
Simon Hunt99c13842014-11-06 18:23:12 -0800794 var eventDispatch = {
Simon Hunta5e89142014-11-14 07:00:33 -0800795 addInstance: addInstance,
Simon Hunt99c13842014-11-06 18:23:12 -0800796 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800797 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800798 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800799
Simon Huntfcfb46c2014-11-19 12:53:38 -0800800 updateInstance: updateInstance,
Simon Huntbb282f52014-11-10 11:08:19 -0800801 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800802 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800803 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800804
Simon Hunt7b403bc2014-11-22 19:01:00 -0800805 removeInstance: removeInstance,
Simon Huntca867ac2014-11-28 18:07:35 -0800806 removeDevice: removeDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800807 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800808 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800809
Simon Hunt61d04042014-11-11 17:27:16 -0800810 showDetails: showDetails,
Thomas Vachuska47635c62014-11-22 01:21:36 -0800811 showSummary: showSummary,
Simon Huntb53e0682014-11-12 13:32:01 -0800812 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800813 };
814
Simon Hunta5e89142014-11-14 07:00:33 -0800815 function addInstance(data) {
816 evTrace(data);
817 var inst = data.payload,
818 id = inst.id;
819 if (onosInstances[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800820 updateInstance(data);
Simon Hunta5e89142014-11-14 07:00:33 -0800821 return;
822 }
823 onosInstances[id] = inst;
824 onosOrder.push(inst);
825 updateInstances();
826 }
827
Simon Hunt99c13842014-11-06 18:23:12 -0800828 function addDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800829 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800830 var device = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800831 id = device.id,
832 d;
833
Simon Hunt233747b2014-12-04 12:01:58 -0800834 showNoDevs(false);
835
Simon Huntca867ac2014-11-28 18:07:35 -0800836 if (network.lookup[id]) {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -0800837 updateDevice(data);
Simon Huntca867ac2014-11-28 18:07:35 -0800838 return;
839 }
840
841 d = createDeviceNode(device);
842 network.nodes.push(d);
843 network.lookup[id] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800844 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -0800845 fStart();
Simon Hunt99c13842014-11-06 18:23:12 -0800846 }
847
Simon Hunt99c13842014-11-06 18:23:12 -0800848 function addLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800849 evTrace(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800850 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800851 result = findLink(link, 'add'),
852 bad = result.badLogic,
Simon Huntca867ac2014-11-28 18:07:35 -0800853 d = result.ldata;
Simon Hunt8257f4c2014-11-16 19:34:54 -0800854
855 if (bad) {
856 logicError(bad + ': ' + link.id);
857 return;
858 }
859
Simon Huntca867ac2014-11-28 18:07:35 -0800860 if (d) {
Simon Hunt8257f4c2014-11-16 19:34:54 -0800861 // we already have a backing store link for src/dst nodes
Simon Huntca867ac2014-11-28 18:07:35 -0800862 addLinkUpdate(d, link);
Simon Hunt8257f4c2014-11-16 19:34:54 -0800863 return;
864 }
865
866 // no backing store link yet
Simon Huntca867ac2014-11-28 18:07:35 -0800867 d = createLink(link);
868 if (d) {
869 network.links.push(d);
870 network.lookup[d.key] = d;
Simon Hunt99c13842014-11-06 18:23:12 -0800871 updateLinks();
Simon Huntd6f5a272014-11-29 23:45:50 -0800872 fStart();
Simon Hunt99c13842014-11-06 18:23:12 -0800873 }
874 }
875
Simon Hunt56d51852014-11-09 13:03:35 -0800876 function addHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800877 evTrace(data);
Simon Hunt56d51852014-11-09 13:03:35 -0800878 var host = data.payload,
Simon Huntca867ac2014-11-28 18:07:35 -0800879 id = host.id,
880 d,
Simon Hunt56d51852014-11-09 13:03:35 -0800881 lnk;
Simon Huntca867ac2014-11-28 18:07:35 -0800882
883 if (network.lookup[id]) {
884 logicError('Host already added: ' + id);
885 return;
886 }
887
888 d = createHostNode(host);
889 network.nodes.push(d);
890 network.lookup[host.id] = d;
Simon Hunt56d51852014-11-09 13:03:35 -0800891 updateNodes();
892
893 lnk = createHostLink(host);
894 if (lnk) {
Simon Huntca867ac2014-11-28 18:07:35 -0800895 d.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800896 network.links.push(lnk);
Simon Huntca867ac2014-11-28 18:07:35 -0800897 network.lookup[d.ingress] = lnk;
898 network.lookup[d.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800899 updateLinks();
900 }
Simon Huntd6f5a272014-11-29 23:45:50 -0800901 fStart();
Simon Hunt56d51852014-11-09 13:03:35 -0800902 }
903
Simon Hunt56a2ea42014-11-19 12:39:31 -0800904 function updateInstance(data) {
905 evTrace(data);
906 var inst = data.payload,
907 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800908 d = onosInstances[id];
909 if (d) {
910 $.extend(d, inst);
Simon Hunt56a2ea42014-11-19 12:39:31 -0800911 updateInstances();
Simon Hunt56a2ea42014-11-19 12:39:31 -0800912 } else {
913 logicError('updateInstance lookup fail. ID = "' + id + '"');
914 }
915 }
916
Simon Huntbb282f52014-11-10 11:08:19 -0800917 function updateDevice(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800918 evTrace(data);
Simon Huntbb282f52014-11-10 11:08:19 -0800919 var device = data.payload,
920 id = device.id,
Simon Hunt6d9bd032014-11-28 22:16:40 -0800921 d = network.lookup[id],
922 wasOnline;
923
Simon Hunt62c47542014-11-22 22:16:32 -0800924 if (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -0800925 wasOnline = d.online;
Simon Hunt62c47542014-11-22 22:16:32 -0800926 $.extend(d, device);
927 if (positionNode(d, true)) {
Simon Hunt395a70c2014-11-22 23:17:40 -0800928 sendUpdateMeta(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -0800929 }
930 updateNodes();
Simon Hunt6d9bd032014-11-28 22:16:40 -0800931 if (wasOnline !== d.online) {
932 findAttachedLinks(d.id).forEach(restyleLinkElement);
933 updateOfflineVisibility(d);
934 }
Simon Huntbb282f52014-11-10 11:08:19 -0800935 } else {
936 logicError('updateDevice lookup fail. ID = "' + id + '"');
937 }
938 }
939
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800940 function updateLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800941 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800942 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -0800943 result = findLink(link, 'update'),
944 bad = result.badLogic;
945 if (bad) {
946 logicError(bad + ': ' + link.id);
947 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800948 }
Simon Hunt8257f4c2014-11-16 19:34:54 -0800949 result.updateWith(link);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800950 }
951
Simon Hunt7cd48f32014-11-09 23:42:50 -0800952 function updateHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800953 evTrace(data);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800954 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800955 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800956 d = network.lookup[id];
957 if (d) {
958 $.extend(d, host);
Simon Hunt6d9bd032014-11-28 22:16:40 -0800959 if (positionNode(d, true)) {
960 sendUpdateMeta(d, true);
961 }
962 updateNodes(d);
Simon Huntbb282f52014-11-10 11:08:19 -0800963 } else {
964 logicError('updateHost lookup fail. ID = "' + id + '"');
965 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800966 }
967
Simon Hunt7b403bc2014-11-22 19:01:00 -0800968 function removeInstance(data) {
969 evTrace(data);
970 var inst = data.payload,
971 id = inst.id,
Simon Huntca867ac2014-11-28 18:07:35 -0800972 d = onosInstances[id];
973 if (d) {
974 var idx = find(id, onosOrder);
Simon Hunt7b403bc2014-11-22 19:01:00 -0800975 if (idx >= 0) {
976 onosOrder.splice(idx, 1);
977 }
978 delete onosInstances[id];
979 updateInstances();
980 } else {
981 logicError('updateInstance lookup fail. ID = "' + id + '"');
982 }
983 }
984
Simon Huntca867ac2014-11-28 18:07:35 -0800985 function removeDevice(data) {
986 evTrace(data);
987 var device = data.payload,
988 id = device.id,
989 d = network.lookup[id];
990 if (d) {
991 removeDeviceElement(d);
992 } else {
993 logicError('removeDevice lookup fail. ID = "' + id + '"');
994 }
995 }
996
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800997 function removeLink(data) {
Simon Hunta5e89142014-11-14 07:00:33 -0800998 evTrace(data);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800999 var link = data.payload,
Simon Hunt8257f4c2014-11-16 19:34:54 -08001000 result = findLink(link, 'remove'),
1001 bad = result.badLogic;
1002 if (bad) {
Simon Huntca867ac2014-11-28 18:07:35 -08001003 // may have already removed link, if attached to removed device
1004 console.warn(bad + ': ' + link.id);
Simon Hunt8257f4c2014-11-16 19:34:54 -08001005 return;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001006 }
Simon Hunt8257f4c2014-11-16 19:34:54 -08001007 result.removeRawLink();
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001008 }
1009
Simon Hunt44031102014-11-11 13:20:36 -08001010 function removeHost(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001011 evTrace(data);
Simon Hunt44031102014-11-11 13:20:36 -08001012 var host = data.payload,
1013 id = host.id,
Simon Huntca867ac2014-11-28 18:07:35 -08001014 d = network.lookup[id];
1015 if (d) {
1016 removeHostElement(d, true);
Simon Hunt44031102014-11-11 13:20:36 -08001017 } else {
Simon Huntca867ac2014-11-28 18:07:35 -08001018 // may have already removed host, if attached to removed device
1019 console.warn('removeHost lookup fail. ID = "' + id + '"');
Simon Hunt44031102014-11-11 13:20:36 -08001020 }
1021 }
1022
Simon Huntca867ac2014-11-28 18:07:35 -08001023 // the following events are server responses to user actions
Thomas Vachuska47635c62014-11-22 01:21:36 -08001024 function showSummary(data) {
1025 evTrace(data);
1026 populateSummary(data.payload);
Simon Hunt06811b72014-11-25 18:54:48 -08001027 showSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001028 }
1029
Simon Hunt61d04042014-11-11 17:27:16 -08001030 function showDetails(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001031 evTrace(data);
Simon Hunt27d322d2014-11-28 10:45:43 -08001032 haveDetails = true;
Simon Hunt61d04042014-11-11 17:27:16 -08001033 populateDetails(data.payload);
Simon Hunt27d322d2014-11-28 10:45:43 -08001034 if (useDetails) {
1035 showDetailPane();
1036 }
Simon Hunt61d04042014-11-11 17:27:16 -08001037 }
1038
Simon Huntb53e0682014-11-12 13:32:01 -08001039 function showTraffic(data) {
Simon Hunta5e89142014-11-14 07:00:33 -08001040 evTrace(data);
Thomas Vachuska4731f122014-11-20 04:56:19 -08001041 var paths = data.payload.paths,
1042 hasTraffic = false;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001043
Thomas Vachuska3266abf2014-11-13 09:28:46 -08001044 // Revert any links hilighted previously.
Thomas Vachuska4731f122014-11-20 04:56:19 -08001045 link.style('stroke-width', null)
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08001046 .classed('primary secondary animated optical', false);
Simon Hunte2575b62014-11-18 15:25:53 -08001047 // Remove all previous labels.
1048 removeLinkLabels();
Thomas Vachuska3266abf2014-11-13 09:28:46 -08001049
Simon Hunte2575b62014-11-18 15:25:53 -08001050 // Now hilight all links in the paths payload, and attach
1051 // labels to them, if they are defined.
Simon Hunta255a2c2014-11-13 22:29:35 -08001052 paths.forEach(function (p) {
Simon Hunte2575b62014-11-18 15:25:53 -08001053 var n = p.links.length,
1054 i,
1055 ldata;
1056
Thomas Vachuska4731f122014-11-20 04:56:19 -08001057 hasTraffic = hasTraffic || p.traffic;
Simon Hunte2575b62014-11-18 15:25:53 -08001058 for (i=0; i<n; i++) {
1059 ldata = findLinkById(p.links[i]);
Thomas Vachuska4731f122014-11-20 04:56:19 -08001060 if (ldata && ldata.el) {
Simon Hunte2575b62014-11-18 15:25:53 -08001061 ldata.el.classed(p.class, true);
1062 ldata.label = p.labels[i];
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001063 }
Simon Hunte2575b62014-11-18 15:25:53 -08001064 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -08001065 });
Thomas Vachuska4731f122014-11-20 04:56:19 -08001066
Simon Hunte2575b62014-11-18 15:25:53 -08001067 updateLinks();
Thomas Vachuska4731f122014-11-20 04:56:19 -08001068
1069 if (hasTraffic && !antTimer) {
1070 startAntTimer();
1071 } else if (!hasTraffic && antTimer) {
1072 stopAntTimer();
1073 }
Simon Huntb53e0682014-11-12 13:32:01 -08001074 }
1075
Simon Hunt56d51852014-11-09 13:03:35 -08001076 // ...............................
1077
Simon Hunt99c13842014-11-06 18:23:12 -08001078 function unknownEvent(data) {
Simon Hunt434cf142014-11-24 11:10:28 -08001079 console.warn('Unknown event type: "' + data.event + '"', data);
Simon Hunt99c13842014-11-06 18:23:12 -08001080 }
1081
1082 function handleServerEvent(data) {
1083 var fn = eventDispatch[data.event] || unknownEvent;
1084 fn(data);
1085 }
1086
1087 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -08001088 // Out-going messages...
1089
Simon Huntb53e0682014-11-12 13:32:01 -08001090 function nSel() {
1091 return selectOrder.length;
1092 }
Simon Hunt61d04042014-11-11 17:27:16 -08001093 function getSel(idx) {
1094 return selections[selectOrder[idx]];
1095 }
Simon Huntb53e0682014-11-12 13:32:01 -08001096 function allSelectionsClass(cls) {
1097 for (var i=0, n=nSel(); i<n; i++) {
1098 if (getSel(i).obj.class !== cls) {
1099 return false;
1100 }
1101 }
1102 return true;
1103 }
Simon Hunt61d04042014-11-11 17:27:16 -08001104
Thomas Vachuska47635c62014-11-22 01:21:36 -08001105 function toggleInstances() {
1106 if (!oiBox.isVisible()) {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001107 showInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001108 } else {
Simon Huntb0ecfa52014-11-23 21:05:12 -08001109 hideInstances();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001110 }
1111 }
1112
Simon Huntb0ecfa52014-11-23 21:05:12 -08001113 function showInstances() {
1114 oiBox.show();
1115 colorAffinity = true;
1116 updateDeviceColors();
1117 }
1118
1119 function hideInstances() {
1120 oiBox.hide();
1121 colorAffinity = false;
1122 cancelAffinity();
1123 updateDeviceColors();
1124 }
1125
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001126 function equalizeMasters() {
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001127 sendMessage('equalizeMasters');
Simon Huntc1cc81c2014-11-29 14:59:01 -08001128 flash('Equalizing master roles');
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -08001129 }
1130
Thomas Vachuska47635c62014-11-22 01:21:36 -08001131 function toggleSummary() {
1132 if (!summaryPane.isVisible()) {
1133 requestSummary();
1134 } else {
1135 cancelSummary();
1136 }
1137 }
1138
Thomas Vachuska47635c62014-11-22 01:21:36 -08001139 function requestSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001140 sendMessage('requestSummary');
Thomas Vachuska47635c62014-11-22 01:21:36 -08001141 }
1142
1143 function cancelSummary() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001144 sendMessage('cancelSummary');
Simon Hunt06811b72014-11-25 18:54:48 -08001145 hideSummaryPane();
Thomas Vachuska47635c62014-11-22 01:21:36 -08001146 }
1147
Simon Hunt27d322d2014-11-28 10:45:43 -08001148 function toggleDetails() {
1149 useDetails = !useDetails;
1150 if (useDetails) {
1151 flash('Enable details pane');
1152 if (haveDetails) {
1153 showDetailPane();
1154 }
1155 } else {
1156 flash('Disable details pane');
1157 hideDetailPane();
1158 }
1159 }
1160
Simon Hunt06811b72014-11-25 18:54:48 -08001161 // encapsulate interaction between summary and details panes
1162 function showSummaryPane() {
1163 if (detailPane.isVisible()) {
1164 detailPane.down(summaryPane.show);
1165 } else {
1166 summaryPane.show();
1167 }
1168 }
1169
1170 function hideSummaryPane() {
1171 summaryPane.hide(function () {
1172 if (detailPane.isVisible()) {
1173 detailPane.up();
1174 }
1175 });
1176 }
1177
1178 function showDetailPane() {
1179 if (summaryPane.isVisible()) {
1180 detailPane.down(detailPane.show);
1181 } else {
1182 detailPane.up(detailPane.show);
1183 }
1184 }
1185
1186 function hideDetailPane() {
1187 detailPane.hide();
1188 }
1189
1190
Simon Hunt61d04042014-11-11 17:27:16 -08001191 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -08001192 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -08001193 function requestDetails() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001194 var data = getSel(0).obj;
1195 sendMessage('requestDetails', {
1196 id: data.id,
1197 class: data.class
1198 });
Simon Hunt61d04042014-11-11 17:27:16 -08001199 }
1200
Thomas Vachuska9edca302014-11-22 17:06:42 -08001201 function addHostIntentAction() {
Simon Huntd72bc702014-11-13 18:38:04 -08001202 sendMessage('addHostIntent', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001203 one: selectOrder[0],
1204 two: selectOrder[1],
1205 ids: selectOrder
Simon Huntd72bc702014-11-13 18:38:04 -08001206 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001207 flash('Host-to-Host flow added');
Simon Huntd72bc702014-11-13 18:38:04 -08001208 }
1209
Thomas Vachuska9edca302014-11-22 17:06:42 -08001210 function addMultiSourceIntentAction() {
1211 sendMessage('addMultiSourceIntent', {
1212 src: selectOrder.slice(0, selectOrder.length - 1),
1213 dst: selectOrder[selectOrder.length - 1],
1214 ids: selectOrder
1215 });
Simon Hunt27d322d2014-11-28 10:45:43 -08001216 flash('Multi-Source flow added');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001217 }
1218
Thomas Vachuska47635c62014-11-22 01:21:36 -08001219 function cancelTraffic() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001220 sendMessage('cancelTraffic');
Thomas Vachuska47635c62014-11-22 01:21:36 -08001221 }
1222
Thomas Vachuska9edca302014-11-22 17:06:42 -08001223 function requestTrafficForMode() {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001224 if (hoverMode === hoverModeFlows) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001225 requestDeviceLinkFlows();
1226 } else if (hoverMode === hoverModeIntents) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001227 requestRelatedIntents();
Simon Huntd72bc702014-11-13 18:38:04 -08001228 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001229 }
Simon Huntd72bc702014-11-13 18:38:04 -08001230
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001231 function showRelatedIntentsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001232 hoverMode = hoverModeIntents;
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001233 requestRelatedIntents();
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001234 flash('Related Paths');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001235 }
1236
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001237 function requestRelatedIntents() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001238 function hoverValid() {
1239 return hoverMode === hoverModeIntents &&
1240 hovered &&
1241 (hovered.class === 'host' || hovered.class === 'device');
1242 }
1243
Thomas Vachuska9edca302014-11-22 17:06:42 -08001244 if (validateSelectionContext()) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001245 sendMessage('requestRelatedIntents', {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001246 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001247 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001248 });
1249 }
Simon Huntd72bc702014-11-13 18:38:04 -08001250 }
1251
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001252 function showNextIntentAction() {
1253 hoverMode = hoverModeNone;
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001254 sendMessage('requestNextRelatedIntent');
1255 flash('>');
1256 }
1257
1258 function showPrevIntentAction() {
1259 hoverMode = hoverModeNone;
1260 sendMessage('requestPrevRelatedIntent');
1261 flash('<');
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001262 }
1263
1264 function showSelectedIntentTrafficAction() {
1265 hoverMode = hoverModeNone;
Thomas Vachuskab7e40642014-12-03 11:16:06 -08001266 sendMessage('requestSelectedIntentTraffic');
1267 flash('Traffic on Selected Path');
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08001268 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -08001269
Thomas Vachuska29617e52014-11-20 03:17:46 -08001270 function showDeviceLinkFlowsAction() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001271 hoverMode = hoverModeFlows;
1272 requestDeviceLinkFlows();
Simon Hunt27d322d2014-11-28 10:45:43 -08001273 flash('Device Flows');
Thomas Vachuska29617e52014-11-20 03:17:46 -08001274 }
1275
Thomas Vachuska9edca302014-11-22 17:06:42 -08001276 function requestDeviceLinkFlows() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001277 function hoverValid() {
1278 return hoverMode === hoverModeFlows &&
1279 hovered && (hovered.class === 'device');
1280 }
1281
Thomas Vachuska9edca302014-11-22 17:06:42 -08001282 if (validateSelectionContext()) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08001283 sendMessage('requestDeviceLinkFlows', {
1284 ids: selectOrder,
Simon Huntc1cc81c2014-11-29 14:59:01 -08001285 hover: hoverValid() ? hovered.id : ''
Thomas Vachuska9edca302014-11-22 17:06:42 -08001286 });
1287 }
1288 }
1289
Thomas Vachuska9edca302014-11-22 17:06:42 -08001290 function showAllTrafficAction() {
1291 hoverMode = hoverModeAll;
1292 requestAllTraffic();
Simon Hunt27d322d2014-11-28 10:45:43 -08001293 flash('All Traffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001294 }
1295
1296 function requestAllTraffic() {
Simon Huntc1cc81c2014-11-29 14:59:01 -08001297 sendMessage('requestAllTraffic');
Thomas Vachuska9edca302014-11-22 17:06:42 -08001298 }
1299
1300 function validateSelectionContext() {
Thomas Vachuska29617e52014-11-20 03:17:46 -08001301 if (!hovered && nSel() === 0) {
Thomas Vachuska47635c62014-11-22 01:21:36 -08001302 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08001303 return false;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001304 }
Thomas Vachuska9edca302014-11-22 17:06:42 -08001305 return true;
Thomas Vachuska29617e52014-11-20 03:17:46 -08001306 }
Simon Huntd72bc702014-11-13 18:38:04 -08001307
Simon Hunta6a9fe72014-11-20 11:17:12 -08001308
Simon Hunt61d04042014-11-11 17:27:16 -08001309 // ==============================
Simon Hunta5e89142014-11-14 07:00:33 -08001310 // onos instance panel functions
Simon Huntb82f6902014-11-22 11:53:15 -08001311
Simon Hunt7b403bc2014-11-22 19:01:00 -08001312 var instCfg = {
1313 rectPad: 8,
1314 nodeOx: 9,
1315 nodeOy: 9,
1316 nodeDim: 40,
1317 birdOx: 19,
1318 birdOy: 21,
1319 birdDim: 21,
1320 uiDy: 45,
1321 titleDy: 30,
1322 textYOff: 20,
1323 textYSpc: 15
1324 };
1325
1326 function viewBox(dim) {
1327 return '0 0 ' + dim.w + ' ' + dim.h;
1328 }
1329
1330 function instRectAttr(dim) {
1331 var pad = instCfg.rectPad;
1332 return {
1333 x: pad,
1334 y: pad,
1335 width: dim.w - pad*2,
1336 height: dim.h - pad*2,
Simon Huntb0ecfa52014-11-23 21:05:12 -08001337 rx: 6
Simon Hunt7b403bc2014-11-22 19:01:00 -08001338 };
1339 }
1340
1341 function computeDim(self) {
1342 var css = window.getComputedStyle(self);
1343 return {
1344 w: stripPx(css.width),
1345 h: stripPx(css.height)
1346 };
Simon Huntb82f6902014-11-22 11:53:15 -08001347 }
Simon Hunta5e89142014-11-14 07:00:33 -08001348
1349 function updateInstances() {
1350 var onoses = oiBox.el.selectAll('.onosInst')
Simon Huntb82f6902014-11-22 11:53:15 -08001351 .data(onosOrder, function (d) { return d.id; }),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001352 instDim = {w:0,h:0},
1353 c = instCfg;
Simon Hunta5e89142014-11-14 07:00:33 -08001354
Simon Hunt7b403bc2014-11-22 19:01:00 -08001355 function nSw(n) {
1356 return '# Switches: ' + n;
1357 }
Simon Hunta5e89142014-11-14 07:00:33 -08001358
Simon Huntb82f6902014-11-22 11:53:15 -08001359 // operate on existing onos instances if necessary
1360 onoses.each(function (d) {
1361 var el = d3.select(this),
1362 svg = el.select('svg');
Simon Hunt7b403bc2014-11-22 19:01:00 -08001363 instDim = computeDim(this);
Simon Huntb82f6902014-11-22 11:53:15 -08001364
1365 // update online state
1366 el.classed('online', d.online);
1367
1368 // update ui-attached state
1369 svg.select('use.uiBadge').remove();
1370 if (d.uiAttached) {
1371 attachUiBadge(svg);
1372 }
1373
Simon Hunt7b403bc2014-11-22 19:01:00 -08001374 function updAttr(id, value) {
1375 svg.select('text.instLabel.'+id).text(value);
1376 }
1377
1378 updAttr('ip', d.ip);
1379 updAttr('ns', nSw(d.switches));
Simon Huntb82f6902014-11-22 11:53:15 -08001380 });
1381
1382
1383 // operate on new onos instances
Simon Hunta5e89142014-11-14 07:00:33 -08001384 var entering = onoses.enter()
1385 .append('div')
1386 .attr('class', 'onosInst')
1387 .classed('online', function (d) { return d.online; })
Simon Hunt9c15eca2014-11-15 18:37:59 -08001388 .on('click', clickInst);
1389
Simon Huntb82f6902014-11-22 11:53:15 -08001390 entering.each(function (d) {
Simon Hunt9c15eca2014-11-15 18:37:59 -08001391 var el = d3.select(this),
Simon Hunt7b403bc2014-11-22 19:01:00 -08001392 rectAttr,
1393 svg;
1394 instDim = computeDim(this);
1395 rectAttr = instRectAttr(instDim);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001396
Simon Hunt7b403bc2014-11-22 19:01:00 -08001397 svg = el.append('svg').attr({
1398 width: instDim.w,
1399 height: instDim.h,
1400 viewBox: viewBox(instDim)
Simon Hunt95908012014-11-20 10:20:26 -08001401 });
Simon Huntb82f6902014-11-22 11:53:15 -08001402
Simon Hunt7b403bc2014-11-22 19:01:00 -08001403 svg.append('rect').attr(rectAttr);
Simon Hunt9c15eca2014-11-15 18:37:59 -08001404
Thomas Vachuskae02e11c2014-11-24 16:13:52 -08001405 appendBadge(svg, 14, 14, 28, '#bird');
Simon Huntb82f6902014-11-22 11:53:15 -08001406
1407 if (d.uiAttached) {
1408 attachUiBadge(svg);
1409 }
1410
Simon Hunt7b403bc2014-11-22 19:01:00 -08001411 var left = c.nodeOx + c.nodeDim,
1412 len = rectAttr.width - left,
1413 hlen = len / 2,
1414 midline = hlen + left;
Simon Hunt9c15eca2014-11-15 18:37:59 -08001415
Simon Hunt7b403bc2014-11-22 19:01:00 -08001416 // title
1417 svg.append('text')
1418 .attr({
1419 class: 'instTitle',
1420 x: midline,
1421 y: c.titleDy
1422 })
1423 .text(d.id);
1424
1425 // a couple of attributes
1426 var ty = c.titleDy + c.textYOff;
1427
1428 function addAttr(id, label) {
1429 svg.append('text').attr({
1430 class: 'instLabel ' + id,
1431 x: midline,
1432 y: ty
1433 }).text(label);
1434 ty += c.textYSpc;
1435 }
1436
1437 addAttr('ip', d.ip);
1438 addAttr('ns', nSw(d.switches));
Simon Hunt9c15eca2014-11-15 18:37:59 -08001439 });
Simon Hunta5e89142014-11-14 07:00:33 -08001440
1441 // operate on existing + new onoses here
Simon Hunt8f40cce2014-11-23 15:57:30 -08001442 // set the affinity colors...
1443 onoses.each(function (d) {
1444 var el = d3.select(this),
1445 rect = el.select('svg').select('rect'),
1446 col = instColor(d.id, d.online);
1447 rect.style('fill', col);
1448 });
Simon Hunta5e89142014-11-14 07:00:33 -08001449
Simon Hunt7b403bc2014-11-22 19:01:00 -08001450 // adjust the panel size appropriately...
1451 oiBox.width(instDim.w * onosOrder.length);
1452 oiBox.height(instDim.h);
1453
1454 // remove any outgoing instances
1455 onoses.exit().remove();
Simon Hunta5e89142014-11-14 07:00:33 -08001456 }
1457
Simon Hunt8f40cce2014-11-23 15:57:30 -08001458 function instColor(id, online) {
1459 return cat7.get(id, !online, network.view.getTheme());
1460 }
1461
Simon Hunt9462e8c2014-11-14 17:28:09 -08001462 function clickInst(d) {
1463 var el = d3.select(this),
1464 aff = el.classed('affinity');
1465 if (!aff) {
1466 setAffinity(el, d);
1467 } else {
1468 cancelAffinity();
1469 }
1470 }
1471
1472 function setAffinity(el, d) {
1473 d3.selectAll('.onosInst')
1474 .classed('mastership', true)
1475 .classed('affinity', false);
1476 el.classed('affinity', true);
1477
1478 suppressLayers(true);
1479 node.each(function (n) {
1480 if (n.master === d.id) {
1481 n.el.classed('suppressed', false);
1482 }
1483 });
1484 oiShowMaster = true;
1485 }
1486
1487 function cancelAffinity() {
1488 d3.selectAll('.onosInst')
1489 .classed('mastership affinity', false);
1490 restoreLayerState();
1491 oiShowMaster = false;
1492 }
1493
Simon Hunt7b403bc2014-11-22 19:01:00 -08001494 // TODO: these should be moved out to utility module.
1495 function stripPx(s) {
1496 return s.replace(/px$/,'');
1497 }
1498
1499 function appendUse(svg, ox, oy, dim, iid, cls) {
1500 var use = svg.append('use').attr({
1501 transform: translate(ox,oy),
1502 'xlink:href': iid,
1503 width: dim,
1504 height: dim
1505 });
1506 if (cls) {
1507 use.classed(cls, true);
1508 }
1509 return use;
1510 }
1511
1512 function appendGlyph(svg, ox, oy, dim, iid, cls) {
1513 appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
1514 }
1515
1516 function appendBadge(svg, ox, oy, dim, iid, cls) {
Simon Hunt233747b2014-12-04 12:01:58 -08001517 appendUse(svg, ox, oy, dim, iid, cls).classed('badgeIcon', true);
Simon Hunt7b403bc2014-11-22 19:01:00 -08001518 }
1519
1520 function attachUiBadge(svg) {
1521 appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
1522 }
1523
Simon Hunt434cf142014-11-24 11:10:28 -08001524 function visVal(b) {
1525 return b ? 'visible' : 'hidden';
1526 }
1527
Simon Hunta5e89142014-11-14 07:00:33 -08001528 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -08001529 // force layout modification functions
1530
1531 function translate(x, y) {
1532 return 'translate(' + x + ',' + y + ')';
1533 }
Simon Huntd6f5a272014-11-29 23:45:50 -08001534 function scale(x,y) {
1535 return 'scale(' + x + ',' + y + ')';
1536 }
1537 function skewX(x) {
1538 return 'skewX(' + x + ')';
1539 }
Simon Hunte2575b62014-11-18 15:25:53 -08001540 function rotate(deg) {
1541 return 'rotate(' + deg + ')';
1542 }
1543
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001544 function missMsg(what, id) {
1545 return '\n[' + what + '] "' + id + '" missing ';
1546 }
1547
1548 function linkEndPoints(srcId, dstId) {
1549 var srcNode = network.lookup[srcId],
1550 dstNode = network.lookup[dstId],
1551 sMiss = !srcNode ? missMsg('src', srcId) : '',
1552 dMiss = !dstNode ? missMsg('dst', dstId) : '';
1553
1554 if (sMiss || dMiss) {
1555 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
1556 return null;
1557 }
1558 return {
1559 source: srcNode,
1560 target: dstNode,
1561 x1: srcNode.x,
1562 y1: srcNode.y,
1563 x2: dstNode.x,
1564 y2: dstNode.y
1565 };
1566 }
1567
Simon Hunt56d51852014-11-09 13:03:35 -08001568 function createHostLink(host) {
1569 var src = host.id,
1570 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -08001571 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001572 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -08001573
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001574 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -08001575 return null;
1576 }
1577
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001578 // Synthesize link ...
1579 $.extend(lnk, {
Simon Hunt8257f4c2014-11-16 19:34:54 -08001580 key: id,
Simon Hunt56d51852014-11-09 13:03:35 -08001581 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001582
1583 type: function () { return 'hostLink'; },
Simon Hunt6d9bd032014-11-28 22:16:40 -08001584 online: function () {
1585 // hostlink target is edge switch
1586 return lnk.target.online;
1587 },
Simon Hunt8257f4c2014-11-16 19:34:54 -08001588 linkWidth: function () { return 1; }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001589 });
Simon Hunt99c13842014-11-06 18:23:12 -08001590 return lnk;
1591 }
1592
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001593 function createLink(link) {
Simon Hunta6a9fe72014-11-20 11:17:12 -08001594 var lnk = linkEndPoints(link.src, link.dst);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001595
1596 if (!lnk) {
1597 return null;
1598 }
1599
Simon Hunt8257f4c2014-11-16 19:34:54 -08001600 $.extend(lnk, {
1601 key: link.id,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001602 class: 'link',
Simon Hunt8257f4c2014-11-16 19:34:54 -08001603 fromSource: link,
1604
1605 // functions to aggregate dual link state
1606 type: function () {
1607 var s = lnk.fromSource,
1608 t = lnk.fromTarget;
1609 return (s && s.type) || (t && t.type) || defaultLinkType;
1610 },
1611 online: function () {
1612 var s = lnk.fromSource,
Simon Hunt6d9bd032014-11-28 22:16:40 -08001613 t = lnk.fromTarget,
1614 both = lnk.source.online && lnk.target.online;
1615 return both && ((s && s.online) || (t && t.online));
Simon Hunt8257f4c2014-11-16 19:34:54 -08001616 },
1617 linkWidth: function () {
1618 var s = lnk.fromSource,
1619 t = lnk.fromTarget,
1620 ws = (s && s.linkWidth) || 0,
1621 wt = (t && t.linkWidth) || 0;
1622 return Math.max(ws, wt);
1623 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001624 });
1625 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -08001626 }
1627
Simon Hunte2575b62014-11-18 15:25:53 -08001628 function removeLinkLabels() {
1629 network.links.forEach(function (d) {
1630 d.label = '';
1631 });
1632 }
1633
Simon Hunt434cf142014-11-24 11:10:28 -08001634 function showHostVis(el) {
1635 el.style('visibility', visVal(showHosts));
1636 }
1637
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001638 var widthRatio = 1.4,
1639 linkScale = d3.scale.linear()
1640 .domain([1, 12])
1641 .range([widthRatio, 12 * widthRatio])
1642 .clamp(true);
1643
Simon Hunt99c13842014-11-06 18:23:12 -08001644 function updateLinks() {
1645 link = linkG.selectAll('.link')
Simon Hunt8257f4c2014-11-16 19:34:54 -08001646 .data(network.links, function (d) { return d.key; });
Simon Hunt99c13842014-11-06 18:23:12 -08001647
1648 // operate on existing links, if necessary
1649 // link .foo() .bar() ...
1650
1651 // operate on entering links:
1652 var entering = link.enter()
1653 .append('line')
1654 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001655 x1: function (d) { return d.x1; },
1656 y1: function (d) { return d.y1; },
1657 x2: function (d) { return d.x2; },
1658 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -08001659 stroke: config.topo.linkInColor,
1660 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -08001661 });
1662
1663 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -08001664 entering.each(function (d) {
1665 var link = d3.select(this);
1666 // provide ref to element selection from backing data....
1667 d.el = link;
Simon Hunt8257f4c2014-11-16 19:34:54 -08001668 restyleLinkElement(d);
Simon Hunt434cf142014-11-24 11:10:28 -08001669 if (d.type() === 'hostLink') {
1670 showHostVis(link);
1671 }
Simon Hunt7cd48f32014-11-09 23:42:50 -08001672 });
Thomas Vachuska4830d392014-11-09 17:09:56 -08001673
1674 // operate on both existing and new links, if necessary
1675 //link .foo() .bar() ...
1676
Simon Hunte2575b62014-11-18 15:25:53 -08001677 // apply or remove labels
1678 var labelData = getLabelData();
1679 applyLinkLabels(labelData);
1680
Thomas Vachuska4830d392014-11-09 17:09:56 -08001681 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -08001682 link.exit()
Simon Hunt6d9bd032014-11-28 22:16:40 -08001683 .attr('stroke-dasharray', '3 3')
Simon Hunt13bf9c82014-11-18 07:26:44 -08001684 .style('opacity', 0.5)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001685 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -08001686 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001687 .attr({
Simon Hunt6d9bd032014-11-28 22:16:40 -08001688 'stroke-dasharray': '3 12',
Simon Hunt13bf9c82014-11-18 07:26:44 -08001689 stroke: config.topo.linkOutColor,
1690 'stroke-width': config.topo.linkOutWidth
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001691 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001692 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -08001693 .remove();
Simon Hunte2575b62014-11-18 15:25:53 -08001694
1695 // NOTE: invoke a single tick to force the labels to position
1696 // onto their links.
1697 tick();
1698 }
1699
1700 function getLabelData() {
1701 // create the backing data for showing labels..
1702 var data = [];
1703 link.each(function (d) {
1704 if (d.label) {
1705 data.push({
1706 id: 'lab-' + d.key,
1707 key: d.key,
1708 label: d.label,
1709 ldata: d
1710 });
1711 }
1712 });
1713 return data;
1714 }
1715
1716 var linkLabelOffset = '0.3em';
1717
1718 function applyLinkLabels(data) {
1719 var entering;
1720
1721 linkLabel = linkLabelG.selectAll('.linkLabel')
1722 .data(data, function (d) { return d.id; });
1723
Simon Hunt56a2ea42014-11-19 12:39:31 -08001724 // for elements already existing, we need to update the text
1725 // and adjust the rectangle size to fit
1726 linkLabel.each(function (d) {
1727 var el = d3.select(this),
1728 rect = el.select('rect'),
1729 text = el.select('text');
1730 text.text(d.label);
1731 rect.attr(rectAroundText(el));
1732 });
Thomas Vachuskaf75b7ab2014-11-19 12:15:55 -08001733
Simon Hunte2575b62014-11-18 15:25:53 -08001734 entering = linkLabel.enter().append('g')
1735 .classed('linkLabel', true)
1736 .attr('id', function (d) { return d.id; });
1737
1738 entering.each(function (d) {
1739 var el = d3.select(this),
1740 rect,
1741 text,
1742 parms = {
1743 x1: d.ldata.x1,
1744 y1: d.ldata.y1,
1745 x2: d.ldata.x2,
1746 y2: d.ldata.y2
1747 };
1748
1749 d.el = el;
1750 rect = el.append('rect');
1751 text = el.append('text').text(d.label);
1752 rect.attr(rectAroundText(el));
1753 text.attr('dy', linkLabelOffset);
1754
1755 el.attr('transform', transformLabel(parms));
1756 });
1757
Simon Huntc2465f42014-12-04 10:28:07 -08001758 // Remove any labels that are no longer required.
Simon Hunte2575b62014-11-18 15:25:53 -08001759 linkLabel.exit().remove();
1760 }
1761
1762 function rectAroundText(el) {
1763 var text = el.select('text'),
1764 box = text.node().getBBox();
1765
1766 // translate the bbox so that it is centered on [x,y]
1767 box.x = -box.width / 2;
1768 box.y = -box.height / 2;
1769
1770 // add padding
1771 box.x -= 1;
1772 box.width += 2;
1773 return box;
1774 }
1775
1776 function transformLabel(p) {
1777 var dx = p.x2 - p.x1,
1778 dy = p.y2 - p.y1,
1779 xMid = dx/2 + p.x1,
1780 yMid = dy/2 + p.y1;
Simon Hunte2575b62014-11-18 15:25:53 -08001781 return translate(xMid, yMid);
Simon Hunt99c13842014-11-06 18:23:12 -08001782 }
1783
1784 function createDeviceNode(device) {
1785 // start with the object as is
1786 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -08001787 type = device.type,
Simon Huntc2465f42014-12-04 10:28:07 -08001788 svgCls = type ? 'node device ' + type : 'node device';
Simon Huntc72967b2014-11-20 09:21:42 -08001789
Simon Hunt99c13842014-11-06 18:23:12 -08001790 // Augment as needed...
1791 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -08001792 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -08001793 positionNode(node);
Simon Hunt99c13842014-11-06 18:23:12 -08001794 return node;
1795 }
1796
Simon Hunt56d51852014-11-09 13:03:35 -08001797 function createHostNode(host) {
1798 // start with the object as is
1799 var node = host;
1800
1801 // Augment as needed...
1802 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001803 if (!node.type) {
Simon Hunt209155e2014-11-21 12:16:09 -08001804 node.type = 'endstation';
Simon Hunt7cd48f32014-11-09 23:42:50 -08001805 }
Simon Hunt7fa116d2014-11-17 14:16:55 -08001806 node.svgClass = 'node host ' + node.type;
Simon Hunt56d51852014-11-09 13:03:35 -08001807 positionNode(node);
Simon Hunt56d51852014-11-09 13:03:35 -08001808 return node;
1809 }
1810
Simon Hunt62c47542014-11-22 22:16:32 -08001811 function positionNode(node, forUpdate) {
Simon Hunt99c13842014-11-06 18:23:12 -08001812 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -08001813 x = meta && meta.x,
1814 y = meta && meta.y,
1815 xy;
Simon Hunt99c13842014-11-06 18:23:12 -08001816
Simon Huntac9e24f2014-11-12 10:12:21 -08001817 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -08001818 if (x && y) {
1819 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001820 node.px = node.x = x;
1821 node.py = node.y = y;
Simon Huntac9e24f2014-11-12 10:12:21 -08001822 return;
Simon Hunt99c13842014-11-06 18:23:12 -08001823 }
Simon Huntac9e24f2014-11-12 10:12:21 -08001824
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001825 var location = node.location;
1826 if (location && location.type === 'latlng') {
Simon Hunt1b18aa52014-11-29 17:57:55 -08001827 var coord = geoMapProj([location.lng, location.lat]);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001828 node.fixed = true;
Simon Hunt62c47542014-11-22 22:16:32 -08001829 node.px = node.x = coord[0];
1830 node.py = node.y = coord[1];
Simon Hunt62c47542014-11-22 22:16:32 -08001831 return true;
1832 }
1833
1834 // if this is a node update (not a node add).. skip randomizer
1835 if (forUpdate) {
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001836 return;
1837 }
1838
Simon Huntac9e24f2014-11-12 10:12:21 -08001839 // Note: Placing incoming unpinned nodes at exactly the same point
1840 // (center of the view) causes them to explode outwards when
1841 // the force layout kicks in. So, we spread them out a bit
1842 // initially, to provide a more serene layout convergence.
1843 // Additionally, if the node is a host, we place it near
1844 // the device it is connected to.
1845
1846 function spread(s) {
1847 return Math.floor((Math.random() * s) - s/2);
1848 }
1849
1850 function randDim(dim) {
1851 return dim / 2 + spread(dim * 0.7071);
1852 }
1853
1854 function rand() {
1855 return {
1856 x: randDim(network.view.width()),
1857 y: randDim(network.view.height())
1858 };
1859 }
1860
1861 function near(node) {
1862 var min = 12,
1863 dx = spread(12),
1864 dy = spread(12);
1865 return {
1866 x: node.x + min + dx,
1867 y: node.y + min + dy
1868 };
1869 }
1870
1871 function getDevice(cp) {
1872 var d = network.lookup[cp.device];
1873 return d || rand();
1874 }
1875
1876 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1877 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -08001878 }
1879
Simon Hunt99c13842014-11-06 18:23:12 -08001880
Simon Huntc72967b2014-11-20 09:21:42 -08001881 function iconGlyphUrl(d) {
1882 var which = d.type || 'unknown';
1883 return '#' + which;
1884 }
1885
Simon Hunt99c13842014-11-06 18:23:12 -08001886 // returns the newly computed bounding box of the rectangle
1887 function adjustRectToFitText(n) {
1888 var text = n.select('text'),
1889 box = text.node().getBBox(),
1890 lab = config.labels;
1891
1892 text.attr('text-anchor', 'middle')
1893 .attr('y', '-0.8em')
1894 .attr('x', lab.imgPad/2);
1895
1896 // translate the bbox so that it is centered on [x,y]
1897 box.x = -box.width / 2;
1898 box.y = -box.height / 2;
1899
1900 // add padding
1901 box.x -= (lab.padLR + lab.imgPad/2);
1902 box.width += lab.padLR * 2 + lab.imgPad;
1903 box.y -= lab.padTB;
1904 box.height += lab.padTB * 2;
1905
1906 return box;
1907 }
1908
Simon Hunt1a9eff92014-11-07 11:06:34 -08001909 function mkSvgClass(d) {
1910 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1911 }
1912
Simon Hunt7cd48f32014-11-09 23:42:50 -08001913 function hostLabel(d) {
1914 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1915 return d.labels[idx];
1916 }
1917 function deviceLabel(d) {
1918 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1919 return d.labels[idx];
1920 }
Simon Huntc72967b2014-11-20 09:21:42 -08001921 function trimLabel(label) {
1922 return (label && label.trim()) || '';
1923 }
1924
1925 function emptyBox() {
1926 return {
1927 x: -2,
1928 y: -2,
1929 width: 4,
1930 height: 4
1931 };
Simon Hunt7cd48f32014-11-09 23:42:50 -08001932 }
1933
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001934 function updateDeviceLabel(d) {
Simon Huntc72967b2014-11-20 09:21:42 -08001935 var label = trimLabel(deviceLabel(d)),
1936 noLabel = !label,
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001937 node = d.el,
Simon Huntc72967b2014-11-20 09:21:42 -08001938 box,
1939 dx,
Simon Hunt395a70c2014-11-22 23:17:40 -08001940 dy,
1941 cfg = config.icons.device;
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001942
1943 node.select('text')
1944 .text(label)
1945 .style('opacity', 0)
1946 .transition()
1947 .style('opacity', 1);
1948
Simon Huntc72967b2014-11-20 09:21:42 -08001949 if (noLabel) {
1950 box = emptyBox();
Simon Hunt395a70c2014-11-22 23:17:40 -08001951 dx = -cfg.dim/2;
1952 dy = -cfg.dim/2;
Simon Huntc72967b2014-11-20 09:21:42 -08001953 } else {
1954 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08001955 dx = box.x + cfg.xoff;
1956 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08001957 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001958
1959 node.select('rect')
1960 .transition()
1961 .attr(box);
1962
Simon Huntc72967b2014-11-20 09:21:42 -08001963 node.select('g.deviceIcon')
Thomas Vachuska89543292014-11-19 11:28:33 -08001964 .transition()
Simon Huntc72967b2014-11-20 09:21:42 -08001965 .attr('transform', translate(dx, dy));
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001966 }
1967
1968 function updateHostLabel(d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08001969 var label = trimLabel(hostLabel(d));
1970 d.el.select('text').text(label);
Simon Huntbb282f52014-11-10 11:08:19 -08001971 }
1972
Simon Hunt434cf142014-11-24 11:10:28 -08001973 function updateHostVisibility() {
1974 var v = visVal(showHosts);
1975 nodeG.selectAll('.host').style('visibility', v);
1976 linkG.selectAll('.hostLink').style('visibility', v);
1977 }
1978
Simon Hunt6d9bd032014-11-28 22:16:40 -08001979 function findOfflineNodes() {
1980 var a = [];
1981 network.nodes.forEach(function (d) {
1982 if (d.class === 'device' && !d.online) {
1983 a.push(d);
1984 }
1985 });
1986 return a;
1987 }
1988
1989 function updateOfflineVisibility(dev) {
1990 var so = showOffline,
1991 sh = showHosts,
1992 vb = 'visibility',
1993 v, off, al, ah, db, b;
1994
1995 function updAtt(show) {
1996 al.forEach(function (d) {
1997 b = show && ((d.type() !== 'hostLink') || sh);
1998 d.el.style(vb, visVal(b));
1999 });
2000 ah.forEach(function (d) {
2001 b = show && sh;
2002 d.el.style(vb, visVal(b));
2003 });
2004 }
2005
2006 if (dev) {
2007 // updating a specific device that just toggled off/on-line
2008 db = dev.online || so;
2009 dev.el.style(vb, visVal(db));
2010 al = findAttachedLinks(dev.id);
2011 ah = findAttachedHosts(dev.id);
2012 updAtt(db);
2013 } else {
2014 // updating all offline devices
2015 v = visVal(so);
2016 off = findOfflineNodes();
2017 off.forEach(function (d) {
2018 d.el.style(vb, v);
2019 al = findAttachedLinks(d.id);
2020 ah = findAttachedHosts(d.id);
2021 updAtt(so);
2022 });
2023 }
2024 }
2025
Simon Hunt6ac93f32014-11-13 12:17:27 -08002026 function nodeMouseOver(d) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002027 if (hovered != d) {
2028 hovered = d;
2029 requestTrafficForMode();
2030 }
Simon Hunt6ac93f32014-11-13 12:17:27 -08002031 }
2032
2033 function nodeMouseOut(d) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002034 if (hovered != null) {
2035 hovered = null;
2036 requestTrafficForMode();
2037 }
Simon Hunt6ac93f32014-11-13 12:17:27 -08002038 }
Simon Huntbb282f52014-11-10 11:08:19 -08002039
Simon Hunteb1514d2014-11-20 09:57:29 -08002040 function addHostIcon(node, radius, iid) {
Thomas Vachuska89543292014-11-19 11:28:33 -08002041 var dim = radius * 1.5,
2042 xlate = -dim / 2;
2043
Simon Hunteb1514d2014-11-20 09:57:29 -08002044 node.append('use').attr({
2045 class: 'glyphIcon hostIcon',
2046 transform: translate(xlate,xlate),
2047 'xlink:href': iid,
2048 width: dim,
2049 height: dim
2050 });
Thomas Vachuska89543292014-11-19 11:28:33 -08002051 }
2052
Simon Hunt99c13842014-11-06 18:23:12 -08002053 function updateNodes() {
2054 node = nodeG.selectAll('.node')
2055 .data(network.nodes, function (d) { return d.id; });
2056
Simon Hunt62c47542014-11-22 22:16:32 -08002057 // operate on existing nodes...
2058 node.filter('.device').each(function (d) {
Simon Hunt62c47542014-11-22 22:16:32 -08002059 var node = d.el;
2060 node.classed('online', d.online);
2061 updateDeviceLabel(d);
2062 positionNode(d, true);
2063 });
2064
2065 node.filter('.host').each(function (d) {
Simon Hunt6d9bd032014-11-28 22:16:40 -08002066 updateHostLabel(d);
2067 positionNode(d, true);
Simon Hunt62c47542014-11-22 22:16:32 -08002068 });
Simon Hunt99c13842014-11-06 18:23:12 -08002069
2070 // operate on entering nodes:
2071 var entering = node.enter()
2072 .append('g')
2073 .attr({
2074 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -08002075 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -08002076 transform: function (d) { return translate(d.x, d.y); },
2077 opacity: 0
2078 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002079 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -08002080 .on('mouseover', nodeMouseOver)
2081 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -08002082 .transition()
2083 .attr('opacity', 1);
2084
2085 // augment device nodes...
2086 entering.filter('.device').each(function (d) {
2087 var node = d3.select(this),
Simon Huntc72967b2014-11-20 09:21:42 -08002088 label = trimLabel(deviceLabel(d)),
2089 noLabel = !label,
Simon Hunt99c13842014-11-06 18:23:12 -08002090 box;
2091
Simon Hunt7cd48f32014-11-09 23:42:50 -08002092 // provide ref to element from backing data....
2093 d.el = node;
2094
Simon Hunt62c47542014-11-22 22:16:32 -08002095 node.append('rect').attr({ rx: 5, ry: 5 });
2096 node.append('text').text(label).attr('dy', '1.1em');
Simon Hunt99c13842014-11-06 18:23:12 -08002097 box = adjustRectToFitText(node);
Simon Hunta3dd9572014-11-20 15:22:41 -08002098 node.select('rect').attr(box);
Simon Huntc72967b2014-11-20 09:21:42 -08002099 addDeviceIcon(node, box, noLabel, iconGlyphUrl(d));
Simon Huntc7ee0662014-11-05 16:44:37 -08002100 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08002101
Simon Hunt56d51852014-11-09 13:03:35 -08002102 // augment host nodes...
2103 entering.filter('.host').each(function (d) {
2104 var node = d3.select(this),
Simon Hunt395a70c2014-11-22 23:17:40 -08002105 cfg = config.icons.host,
2106 r = cfg.radius[d.type] || cfg.defaultRadius,
Thomas Vachuska89543292014-11-19 11:28:33 -08002107 textDy = r + 10,
Simon Hunteb1514d2014-11-20 09:57:29 -08002108 iid = iconGlyphUrl(d);
Simon Hunt56d51852014-11-09 13:03:35 -08002109
Simon Hunt7cd48f32014-11-09 23:42:50 -08002110 // provide ref to element from backing data....
2111 d.el = node;
Simon Hunt434cf142014-11-24 11:10:28 -08002112 showHostVis(node);
Simon Hunt7cd48f32014-11-09 23:42:50 -08002113
Simon Hunt62c47542014-11-22 22:16:32 -08002114 node.append('circle').attr('r', r);
Simon Hunteb1514d2014-11-20 09:57:29 -08002115 if (iid) {
2116 addHostIcon(node, r, iid);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002117 }
Simon Hunt56d51852014-11-09 13:03:35 -08002118 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08002119 .text(hostLabel)
Thomas Vachuska89543292014-11-19 11:28:33 -08002120 .attr('dy', textDy)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002121 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08002122 });
Simon Huntc7ee0662014-11-05 16:44:37 -08002123
Simon Hunt99c13842014-11-06 18:23:12 -08002124 // operate on both existing and new nodes, if necessary
Simon Huntb0ecfa52014-11-23 21:05:12 -08002125 updateDeviceColors();
Simon Huntc7ee0662014-11-05 16:44:37 -08002126
Simon Hunt99c13842014-11-06 18:23:12 -08002127 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08002128 // Note that the node is removed after 2 seconds.
2129 // Sub element animations should be shorter than 2 seconds.
2130 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08002131 .transition()
2132 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08002133 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08002134 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08002135
2136 // host node exits....
2137 exiting.filter('.host').each(function (d) {
Simon Huntca867ac2014-11-28 18:07:35 -08002138 var node = d.el;
2139 node.select('use')
2140 .style('opacity', 0.5)
2141 .transition()
2142 .duration(800)
2143 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002144
2145 node.select('text')
2146 .style('opacity', 0.5)
2147 .transition()
Simon Huntca867ac2014-11-28 18:07:35 -08002148 .duration(800)
Simon Huntea80eb42014-11-11 13:46:57 -08002149 .style('opacity', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002150
Thomas Vachuska89543292014-11-19 11:28:33 -08002151 node.select('circle')
2152 .style('stroke-fill', '#555')
2153 .style('fill', '#888')
Simon Huntea80eb42014-11-11 13:46:57 -08002154 .style('opacity', 0.5)
2155 .transition()
2156 .duration(1500)
2157 .attr('r', 0);
Simon Huntea80eb42014-11-11 13:46:57 -08002158 });
2159
Simon Huntca867ac2014-11-28 18:07:35 -08002160 // device node exits....
2161 exiting.filter('.device').each(function (d) {
2162 var node = d.el;
2163 node.select('use')
2164 .style('opacity', 0.5)
2165 .transition()
2166 .duration(800)
2167 .style('opacity', 0);
2168
2169 node.selectAll('rect')
2170 .style('stroke-fill', '#555')
2171 .style('fill', '#888')
2172 .style('opacity', 0.5);
2173 });
Simon Huntd6f5a272014-11-29 23:45:50 -08002174 fResume();
Simon Huntc7ee0662014-11-05 16:44:37 -08002175 }
2176
Simon Hunt95dad922014-11-24 09:43:31 -08002177 var dCol = {
2178 black: '#000',
2179 paleblue: '#acf',
2180 offwhite: '#ddd',
2181 midgrey: '#888',
2182 lightgrey: '#bbb',
2183 orange: '#f90'
2184 };
2185
Simon Huntb0ecfa52014-11-23 21:05:12 -08002186 // note: these are the device icon colors without affinity
Simon Hunt95dad922014-11-24 09:43:31 -08002187 var dColTheme = {
Simon Huntb0ecfa52014-11-23 21:05:12 -08002188 light: {
2189 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08002190 glyph: dCol.black,
2191 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08002192 },
2193 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08002194 glyph: dCol.midgrey,
2195 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08002196 }
2197 },
Simon Hunt95dad922014-11-24 09:43:31 -08002198 // TODO: theme
Simon Huntb0ecfa52014-11-23 21:05:12 -08002199 dark: {
2200 online: {
Simon Hunt95dad922014-11-24 09:43:31 -08002201 glyph: dCol.black,
2202 rect: dCol.paleblue
Simon Huntb0ecfa52014-11-23 21:05:12 -08002203 },
2204 offline: {
Simon Hunt95dad922014-11-24 09:43:31 -08002205 glyph: dCol.midgrey,
2206 rect: dCol.lightgrey
Simon Huntb0ecfa52014-11-23 21:05:12 -08002207 }
2208 }
2209 };
2210
2211 function devBaseColor(d) {
2212 var t = network.view.getTheme(),
2213 o = d.online ? 'online' : 'offline';
Simon Hunt95dad922014-11-24 09:43:31 -08002214 return dColTheme[t][o];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002215 }
2216
2217 function setDeviceColor(d) {
2218 var o = d.online,
2219 s = d.el.classed('selected'),
2220 c = devBaseColor(d),
2221 a = instColor(d.master, o),
2222 g, r,
2223 icon = d.el.select('g.deviceIcon');
2224
2225 if (s) {
2226 g = c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002227 r = dColTheme.sel;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002228 } else if (colorAffinity) {
2229 g = o ? a : c.glyph;
Simon Hunt95dad922014-11-24 09:43:31 -08002230 r = o ? dCol.offwhite : a;
Simon Huntb0ecfa52014-11-23 21:05:12 -08002231 } else {
2232 g = c.glyph;
2233 r = c.rect;
2234 }
2235
2236 icon.select('use')
2237 .style('fill', g);
2238 icon.select('rect')
2239 .style('fill', r);
2240 }
2241
Simon Huntc72967b2014-11-20 09:21:42 -08002242 function addDeviceIcon(node, box, noLabel, iid) {
2243 var cfg = config.icons.device,
2244 dx,
2245 dy,
2246 g;
2247
2248 if (noLabel) {
Simon Huntc72967b2014-11-20 09:21:42 -08002249 dx = -cfg.dim/2;
2250 dy = -cfg.dim/2;
2251 } else {
2252 box = adjustRectToFitText(node);
Simon Hunt395a70c2014-11-22 23:17:40 -08002253 dx = box.x + cfg.xoff;
2254 dy = box.y + cfg.yoff;
Simon Huntc72967b2014-11-20 09:21:42 -08002255 }
2256
Simon Hunteb1514d2014-11-20 09:57:29 -08002257 g = node.append('g')
2258 .attr('class', 'glyphIcon deviceIcon')
Simon Huntc72967b2014-11-20 09:21:42 -08002259 .attr('transform', translate(dx, dy));
2260
2261 g.append('rect').attr({
2262 x: 0,
2263 y: 0,
2264 rx: cfg.rx,
2265 width: cfg.dim,
2266 height: cfg.dim
2267 });
2268
2269 g.append('use').attr({
2270 'xlink:href': iid,
2271 width: cfg.dim,
2272 height: cfg.dim
2273 });
2274
Simon Huntc72967b2014-11-20 09:21:42 -08002275 }
2276
Simon Hunt7b403bc2014-11-22 19:01:00 -08002277 function find(key, array, tag) {
Simon Huntca867ac2014-11-28 18:07:35 -08002278 var _tag = tag || 'id',
Simon Hunt7b403bc2014-11-22 19:01:00 -08002279 idx, n, d;
2280 for (idx = 0, n = array.length; idx < n; idx++) {
2281 d = array[idx];
2282 if (d[_tag] === key) {
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002283 return idx;
2284 }
2285 }
2286 return -1;
2287 }
2288
Simon Huntca867ac2014-11-28 18:07:35 -08002289 function removeLinkElement(d) {
2290 var idx = find(d.key, network.links, 'key'),
Simon Hunt8257f4c2014-11-16 19:34:54 -08002291 removed;
2292 if (idx >=0) {
2293 // remove from links array
2294 removed = network.links.splice(idx, 1);
2295 // remove from lookup cache
2296 delete network.lookup[removed[0].key];
2297 updateLinks();
Simon Huntd6f5a272014-11-29 23:45:50 -08002298 fResume();
Simon Hunt8257f4c2014-11-16 19:34:54 -08002299 }
Simon Hunt3f03d4a2014-11-10 20:14:37 -08002300 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002301
Simon Huntca867ac2014-11-28 18:07:35 -08002302 function removeHostElement(d, upd) {
2303 var lu = network.lookup;
Simon Hunt44031102014-11-11 13:20:36 -08002304 // first, remove associated hostLink...
Simon Huntca867ac2014-11-28 18:07:35 -08002305 removeLinkElement(d.linkData);
2306
2307 // remove hostLink bindings
2308 delete lu[d.ingress];
2309 delete lu[d.egress];
Simon Hunt44031102014-11-11 13:20:36 -08002310
2311 // remove from lookup cache
Simon Huntca867ac2014-11-28 18:07:35 -08002312 delete lu[d.id];
Simon Hunt44031102014-11-11 13:20:36 -08002313 // remove from nodes array
Simon Huntca867ac2014-11-28 18:07:35 -08002314 var idx = find(d.id, network.nodes);
2315 network.nodes.splice(idx, 1);
2316 // remove from SVG
2317 // NOTE: upd is false if we were called from removeDeviceElement()
2318 if (upd) {
2319 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -08002320 fResume();
Simon Huntca867ac2014-11-28 18:07:35 -08002321 }
2322 }
2323
2324
2325 function removeDeviceElement(d) {
2326 var id = d.id;
2327 // first, remove associated hosts and links..
2328 findAttachedHosts(id).forEach(removeHostElement);
2329 findAttachedLinks(id).forEach(removeLinkElement);
2330
2331 // remove from lookup cache
2332 delete network.lookup[id];
2333 // remove from nodes array
2334 var idx = find(id, network.nodes);
Simon Hunt44031102014-11-11 13:20:36 -08002335 network.nodes.splice(idx, 1);
Simon Hunt233747b2014-12-04 12:01:58 -08002336
2337 if (!network.nodes.length) {
2338 showNoDevs(true);
2339 }
2340
Simon Hunt44031102014-11-11 13:20:36 -08002341 // remove from SVG
2342 updateNodes();
Simon Huntd6f5a272014-11-29 23:45:50 -08002343 fResume();
Simon Hunt44031102014-11-11 13:20:36 -08002344 }
2345
Simon Huntca867ac2014-11-28 18:07:35 -08002346 function findAttachedHosts(devId) {
2347 var hosts = [];
2348 network.nodes.forEach(function (d) {
2349 if (d.class === 'host' && d.cp.device === devId) {
2350 hosts.push(d);
2351 }
2352 });
2353 return hosts;
2354 }
2355
2356 function findAttachedLinks(devId) {
2357 var links = [];
2358 network.links.forEach(function (d) {
2359 if (d.source.id === devId || d.target.id === devId) {
2360 links.push(d);
2361 }
2362 });
2363 return links;
2364 }
Simon Hunt44031102014-11-11 13:20:36 -08002365
Simon Huntd6f5a272014-11-29 23:45:50 -08002366 function fResume() {
2367 if (!oblique) {
2368 network.force.resume();
2369 }
2370 }
Simon Huntc7ee0662014-11-05 16:44:37 -08002371
Simon Huntd6f5a272014-11-29 23:45:50 -08002372 function fStart() {
2373 if (!oblique) {
2374 network.force.start();
2375 }
2376 }
2377
2378 var tickStuff = {
2379 nodeAttr: {
2380 transform: function (d) { return translate(d.x, d.y); }
2381 },
2382 linkAttr: {
Simon Huntc7ee0662014-11-05 16:44:37 -08002383 x1: function (d) { return d.source.x; },
2384 y1: function (d) { return d.source.y; },
2385 x2: function (d) { return d.target.x; },
2386 y2: function (d) { return d.target.y; }
Simon Huntd6f5a272014-11-29 23:45:50 -08002387 },
2388 linkLabelAttr: {
2389 transform: function (d) {
2390 var lnk = findLinkById(d.key);
Simon Hunte2575b62014-11-18 15:25:53 -08002391
Simon Huntd6f5a272014-11-29 23:45:50 -08002392 if (lnk) {
2393 var parms = {
2394 x1: lnk.source.x,
2395 y1: lnk.source.y,
2396 x2: lnk.target.x,
2397 y2: lnk.target.y
2398 };
2399 return transformLabel(parms);
2400 }
Thomas Vachuska4731f122014-11-20 04:56:19 -08002401 }
Simon Huntd6f5a272014-11-29 23:45:50 -08002402 }
2403 };
2404
2405 function tick() {
2406 node.attr(tickStuff.nodeAttr);
2407 link.attr(tickStuff.linkAttr);
2408 linkLabel.attr(tickStuff.linkLabelAttr);
Simon Huntc7ee0662014-11-05 16:44:37 -08002409 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08002410
2411 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002412 // Web-Socket for live data
2413
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002414 function findGuiSuccessor() {
2415 var idx = -1;
2416 onosOrder.forEach(function (d, i) {
2417 if (d.uiAttached) {
2418 idx = i;
2419 }
2420 });
2421
2422 for (var i = 0; i < onosOrder.length - 1; i++) {
2423 var ni = (idx + 1 + i) % onosOrder.length;
2424 if (onosOrder[ni].online) {
2425 return onosOrder[ni].ip;
2426 }
2427 }
2428 return null;
2429 }
2430
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002431 function webSockUrl() {
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002432 var url = document.location.toString()
2433 .replace(/\#.*/, '')
2434 .replace('http://', 'ws://')
2435 .replace('https://', 'wss://')
2436 .replace('index.html', config.webSockUrl);
2437 if (guiSuccessor) {
2438 url = url.replace(location.hostname, guiSuccessor);
2439 }
2440 return url;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002441 }
2442
2443 webSock = {
2444 ws : null,
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002445 retries: 0,
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002446
2447 connect : function() {
2448 webSock.ws = new WebSocket(webSockUrl());
2449
2450 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08002451 noWebSock(false);
Thomas Vachuska47635c62014-11-22 01:21:36 -08002452 requestSummary();
Simon Huntb0ecfa52014-11-23 21:05:12 -08002453 showInstances();
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002454 webSock.retries = 0;
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002455 };
2456
2457 webSock.ws.onmessage = function(m) {
2458 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002459 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08002460 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002461 }
2462 };
2463
2464 webSock.ws.onclose = function(m) {
2465 webSock.ws = null;
Thomas Vachuska12dfdc32014-11-29 16:03:12 -08002466 guiSuccessor = findGuiSuccessor();
2467 if (guiSuccessor && webSock.retries < onosOrder.length) {
2468 webSock.retries++;
2469 webSock.connect();
2470 } else {
2471 noWebSock(true);
2472 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002473 };
2474 },
2475
2476 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002477 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002478 webSock._send(text);
2479 }
2480 },
2481
2482 _send : function(message) {
2483 if (webSock.ws) {
2484 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002485 } else if (config.useLiveData) {
Simon Hunt434cf142014-11-24 11:10:28 -08002486 console.warn('no web socket open', message);
Simon Hunta255a2c2014-11-13 22:29:35 -08002487 } else {
Simon Hunt434cf142014-11-24 11:10:28 -08002488 console.log('WS Send: ', message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002489 }
2490 }
2491
2492 };
2493
Simon Hunt0c6d4192014-11-12 12:07:10 -08002494 function noWebSock(b) {
2495 mask.style('display',b ? 'block' : 'none');
2496 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002497
2498 function sendMessage(evType, payload) {
Simon Huntc1cc81c2014-11-29 14:59:01 -08002499 var p = payload || {},
2500 toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08002501 event: evType,
2502 sid: ++sid,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002503 payload: p
Simon Huntbb282f52014-11-10 11:08:19 -08002504 },
2505 asText = JSON.stringify(toSend);
2506 wsTraceTx(asText);
2507 webSock.send(asText);
Simon Huntc76ae892014-11-18 17:31:51 -08002508
2509 // Temporary measure for debugging UI behavior ...
2510 if (!config.useLiveData) {
2511 handleTestSend(toSend);
2512 }
Simon Huntbb282f52014-11-10 11:08:19 -08002513 }
2514
2515 function wsTraceTx(msg) {
2516 wsTrace('tx', msg);
2517 }
2518 function wsTraceRx(msg) {
2519 wsTrace('rx', msg);
2520 }
2521 function wsTrace(rxtx, msg) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002522 // console.log('[' + rxtx + '] ' + msg);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002523 }
2524
Simon Huntc2465f42014-12-04 10:28:07 -08002525 function handleTestSend(msg) { }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002526
2527 // ==============================
2528 // Selection stuff
2529
2530 function selectObject(obj, el) {
2531 var n,
Simon Hunta1162d82014-12-03 14:34:43 -08002532 ev = d3.event.sourceEvent;
Simon Hunt01095ff2014-11-13 16:37:29 -08002533
Simon Hunta1162d82014-12-03 14:34:43 -08002534 // if the meta or alt key is pressed, we are panning/zooming, so ignore
2535 if (ev.metaKey || ev.altKey) {
Simon Hunt01095ff2014-11-13 16:37:29 -08002536 return;
2537 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002538
2539 if (el) {
2540 n = d3.select(el);
2541 } else {
2542 node.each(function(d) {
2543 if (d == obj) {
2544 n = d3.select(el = this);
2545 }
2546 });
2547 }
2548 if (!n) return;
2549
Simon Hunta1162d82014-12-03 14:34:43 -08002550 if (ev.shiftKey && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002551 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002552 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002553 return;
2554 }
2555
Simon Hunta1162d82014-12-03 14:34:43 -08002556 if (!ev.shiftKey) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002557 deselectAll();
2558 }
2559
Simon Huntc31d5692014-11-12 13:27:18 -08002560 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002561 selectOrder.push(obj.id);
2562
2563 n.classed('selected', true);
Simon Huntb0ecfa52014-11-23 21:05:12 -08002564 updateDeviceColors(obj);
Simon Hunt61d04042014-11-11 17:27:16 -08002565 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002566 }
2567
2568 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08002569 var obj = selections[id],
2570 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002571 if (obj) {
2572 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08002573 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08002574 idx = $.inArray(id, selectOrder);
2575 if (idx >= 0) {
2576 selectOrder.splice(idx, 1);
2577 }
Simon Huntb0ecfa52014-11-23 21:05:12 -08002578 updateDeviceColors(obj.obj);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002579 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002580 }
2581
2582 function deselectAll() {
2583 // deselect all nodes in the network...
2584 node.classed('selected', false);
2585 selections = {};
2586 selectOrder = [];
Simon Huntb0ecfa52014-11-23 21:05:12 -08002587 updateDeviceColors();
Simon Hunt61d04042014-11-11 17:27:16 -08002588 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002589 }
2590
Simon Huntb0ecfa52014-11-23 21:05:12 -08002591 function updateDeviceColors(d) {
2592 if (d) {
2593 setDeviceColor(d);
2594 } else {
2595 node.filter('.device').each(function (d) {
2596 setDeviceColor(d);
2597 });
2598 }
Thomas Vachuska47635c62014-11-22 01:21:36 -08002599 }
2600
Simon Hunt61d04042014-11-11 17:27:16 -08002601 // update the state of the detail pane, based on current selections
2602 function updateDetailPane() {
2603 var nSel = selectOrder.length;
2604 if (!nSel) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002605 emptySelect();
Simon Hunt61d04042014-11-11 17:27:16 -08002606 } else if (nSel === 1) {
2607 singleSelect();
2608 } else {
2609 multiSelect();
2610 }
2611 }
2612
Thomas Vachuska9edca302014-11-22 17:06:42 -08002613 function emptySelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002614 haveDetails = false;
Simon Hunt06811b72014-11-25 18:54:48 -08002615 hideDetailPane();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002616 cancelTraffic();
2617 }
2618
Simon Hunt61d04042014-11-11 17:27:16 -08002619 function singleSelect() {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002620 // NOTE: detail is shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08002621 requestDetails();
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002622 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002623 requestTrafficForMode();
Simon Hunt61d04042014-11-11 17:27:16 -08002624 }
2625
2626 function multiSelect() {
Simon Hunt27d322d2014-11-28 10:45:43 -08002627 haveDetails = true;
Simon Huntb53e0682014-11-12 13:32:01 -08002628 populateMultiSelect();
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002629 cancelTraffic();
Thomas Vachuska9edca302014-11-22 17:06:42 -08002630 requestTrafficForMode();
Simon Huntb53e0682014-11-12 13:32:01 -08002631 }
2632
2633 function addSep(tbody) {
2634 var tr = tbody.append('tr');
2635 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
2636 }
2637
2638 function addProp(tbody, label, value) {
2639 var tr = tbody.append('tr');
2640
2641 tr.append('td')
2642 .attr('class', 'label')
2643 .text(label + ' :');
2644
2645 tr.append('td')
2646 .attr('class', 'value')
2647 .text(value);
2648 }
2649
2650 function populateMultiSelect() {
2651 detailPane.empty();
2652
Simon Hunta3dd9572014-11-20 15:22:41 -08002653 var title = detailPane.append('h3'),
2654 table = detailPane.append('table'),
2655 tbody = table.append('tbody');
Simon Huntb53e0682014-11-12 13:32:01 -08002656
Thomas Vachuska4731f122014-11-20 04:56:19 -08002657 title.text('Selected Nodes');
Simon Huntb53e0682014-11-12 13:32:01 -08002658
2659 selectOrder.forEach(function (d, i) {
2660 addProp(tbody, i+1, d);
2661 });
Simon Huntd72bc702014-11-13 18:38:04 -08002662
2663 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08002664 }
2665
Thomas Vachuska47635c62014-11-22 01:21:36 -08002666 function populateSummary(data) {
2667 summaryPane.empty();
2668
2669 var svg = summaryPane.append('svg'),
2670 iid = iconGlyphUrl(data);
2671
2672 var title = summaryPane.append('h2'),
2673 table = summaryPane.append('table'),
2674 tbody = table.append('tbody');
2675
2676 appendGlyph(svg, 0, 0, 40, iid);
2677
2678 svg.append('use')
2679 .attr({
2680 class: 'birdBadge',
2681 transform: translate(8,12),
2682 'xlink:href': '#bird',
2683 width: 24,
2684 height: 24,
2685 fill: '#fff'
2686 });
2687
2688 title.text('ONOS Summary');
2689
2690 data.propOrder.forEach(function(p) {
2691 if (p === '-') {
2692 addSep(tbody);
2693 } else {
2694 addProp(tbody, p, data.props[p]);
2695 }
2696 });
2697 }
2698
Simon Hunt61d04042014-11-11 17:27:16 -08002699 function populateDetails(data) {
2700 detailPane.empty();
2701
Simon Hunta6a9fe72014-11-20 11:17:12 -08002702 var svg = detailPane.append('svg'),
2703 iid = iconGlyphUrl(data);
2704
Simon Hunta3dd9572014-11-20 15:22:41 -08002705 var title = detailPane.append('h2'),
2706 table = detailPane.append('table'),
2707 tbody = table.append('tbody');
Simon Hunt61d04042014-11-11 17:27:16 -08002708
Simon Hunta6a9fe72014-11-20 11:17:12 -08002709 appendGlyph(svg, 0, 0, 40, iid);
2710 title.text(data.id);
Simon Hunt61d04042014-11-11 17:27:16 -08002711
2712 data.propOrder.forEach(function(p) {
2713 if (p === '-') {
2714 addSep(tbody);
2715 } else {
2716 addProp(tbody, p, data.props[p]);
2717 }
2718 });
Simon Huntd72bc702014-11-13 18:38:04 -08002719
Thomas Vachuska4731f122014-11-20 04:56:19 -08002720 addSingleSelectActions(data);
Simon Hunt61d04042014-11-11 17:27:16 -08002721 }
2722
Thomas Vachuska4731f122014-11-20 04:56:19 -08002723 function addSingleSelectActions(data) {
Simon Huntd72bc702014-11-13 18:38:04 -08002724 detailPane.append('hr');
2725 // always want to allow 'show traffic'
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002726 addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
Thomas Vachuska4731f122014-11-20 04:56:19 -08002727
2728 if (data.type === 'switch') {
2729 addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
2730 }
Simon Huntd72bc702014-11-13 18:38:04 -08002731 }
2732
2733 function addMultiSelectActions() {
2734 detailPane.append('hr');
2735 // always want to allow 'show traffic'
Thomas Vachuska164fa5c2014-12-02 21:59:41 -08002736 addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002737 // if exactly two hosts are selected, also want 'add host intent'
2738 if (nSel() === 2 && allSelectionsClass('host')) {
Thomas Vachuska9edca302014-11-22 17:06:42 -08002739 addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2740 } else if (nSel() >= 2 && allSelectionsClass('host')) {
2741 addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
Simon Huntd72bc702014-11-13 18:38:04 -08002742 }
2743 }
2744
Simon Hunta5e89142014-11-14 07:00:33 -08002745 function addAction(panel, text, cb) {
2746 panel.append('div')
Simon Huntd72bc702014-11-13 18:38:04 -08002747 .classed('actionBtn', true)
2748 .text(text)
2749 .on('click', cb);
2750 }
2751
2752
Simon Hunt3c5ca542014-11-29 14:11:43 -08002753 // === Pan and Zoom behaviors...
2754
2755 function panZoom(translate, scale) {
2756 panZoomContainer.attr('transform',
2757 'translate(' + translate + ')scale(' + scale + ')');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002758 // keep the map lines constant width while zooming
Simon Hunt3c5ca542014-11-29 14:11:43 -08002759 bgImg.style('stroke-width', 2.0 / scale + 'px');
Paul Greysonfcba0e82014-11-13 10:21:16 -08002760 }
2761
Simon Hunt3c5ca542014-11-29 14:11:43 -08002762 function resetPanZoom() {
2763 panZoom([0,0], 1);
2764 zoom.translate([0,0]).scale(1);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002765 }
2766
Simon Hunt3c5ca542014-11-29 14:11:43 -08002767 function setupPanZoom() {
Paul Greysonfcba0e82014-11-13 10:21:16 -08002768 function zoomed() {
Simon Hunta1162d82014-12-03 14:34:43 -08002769 var ev = d3.event.sourceEvent;
2770 // pan/zoom active when meta or alt key is pressed...
2771 if (ev.metaKey || ev.altKey) {
Simon Hunt3c5ca542014-11-29 14:11:43 -08002772 panZoom(d3.event.translate, d3.event.scale);
Paul Greysonfcba0e82014-11-13 10:21:16 -08002773 }
2774 }
2775
2776 zoom = d3.behavior.zoom()
2777 .translate([0, 0])
2778 .scale(1)
Simon Hunt1b18aa52014-11-29 17:57:55 -08002779 .scaleExtent([0.25, 10])
Paul Greysonfcba0e82014-11-13 10:21:16 -08002780 .on("zoom", zoomed);
2781
2782 svg.call(zoom);
2783 }
2784
Simon Hunt233747b2014-12-04 12:01:58 -08002785
2786 function setupNoDevices() {
2787 var g = noDevices.append('g');
2788 appendBadge(g, 0, 0, 100, '#bird', 'noDevsBird');
2789 var text = g.append('text')
2790 .text('No devices are connected')
2791 .attr({ x: 120, y: 80});
2792 }
2793
2794 function repositionNoDevices() {
2795 var g = noDevices.select('g');
2796 var box = g.node().getBBox();
2797 box.x -= box.width/2;
2798 box.y -= box.height/2;
2799 g.attr('transform', translate(box.x, box.y));
2800 }
2801
2802
Simon Hunt61d04042014-11-11 17:27:16 -08002803 // ==============================
2804 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08002805
2806 function prepareScenario(view, ctx, dbg) {
2807 var sc = scenario,
2808 urlSc = sc.evDir + ctx + sc.evScenario;
2809
2810 if (!ctx) {
2811 view.alert("No scenario specified (null ctx)");
2812 return;
2813 }
2814
2815 sc.view = view;
2816 sc.ctx = ctx;
2817 sc.debug = dbg;
2818 sc.evNumber = 0;
2819
2820 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08002821 var p = data && data.params || {},
2822 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08002823 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08002824
Simon Hunt56d51852014-11-09 13:03:35 -08002825 if (err) {
2826 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
2827 } else {
2828 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08002829 if (desc) {
2830 intro += '\n\n ' + desc.join('\n ');
2831 }
2832 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08002833 }
2834 });
2835
2836 }
2837
Simon Hunt9f1bced2014-12-02 14:36:39 -08002838 function setupDefs(svg) {
Simon Hunt7fa116d2014-11-17 14:16:55 -08002839 var defs = svg.append('defs');
Simon Hunt9f1bced2014-12-02 14:36:39 -08002840 gly.loadDefs(defs);
2841 d3u.loadGlow(defs);
Simon Hunt7fa116d2014-11-17 14:16:55 -08002842 }
Simon Hunt01095ff2014-11-13 16:37:29 -08002843
Simon Hunt395a70c2014-11-22 23:17:40 -08002844 function sendUpdateMeta(d, store) {
2845 var metaUi = {},
2846 ll;
2847
2848 if (store) {
Simon Hunt1b18aa52014-11-29 17:57:55 -08002849 ll = geoMapProj.invert([d.x, d.y]);
Simon Hunt62c47542014-11-22 22:16:32 -08002850 metaUi = {
2851 x: d.x,
2852 y: d.y,
2853 lng: ll[0],
2854 lat: ll[1]
2855 };
Simon Hunt395a70c2014-11-22 23:17:40 -08002856 }
Simon Hunt62c47542014-11-22 22:16:32 -08002857 d.metaUi = metaUi;
2858 sendMessage('updateMeta', {
2859 id: d.id,
2860 'class': d.class,
Simon Huntc1cc81c2014-11-29 14:59:01 -08002861 memento: metaUi
Simon Hunt62c47542014-11-22 22:16:32 -08002862 });
2863 }
2864
Thomas Vachuska7d638d32014-11-07 10:24:43 -08002865 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08002866 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08002867
Simon Hunta2994cc2014-12-02 14:19:15 -08002868 function init(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08002869 var w = view.width(),
2870 h = view.height(),
Simon Hunt233747b2014-12-04 12:01:58 -08002871 logSize = config.logicalSize,
Simon Hunt1b18aa52014-11-29 17:57:55 -08002872 fcfg = config.force;
Simon Hunt195cb382014-11-03 17:50:51 -08002873
Simon Hunt142d0032014-11-04 20:13:09 -08002874 // NOTE: view.$div is a D3 selection of the view's div
Simon Hunt233747b2014-12-04 12:01:58 -08002875 var viewBox = '0 0 ' + logSize + ' ' + logSize;
Paul Greyson6cb8ca02014-11-12 18:09:02 -08002876 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08002877 setSize(svg, view);
2878
Simon Hunt9f1bced2014-12-02 14:36:39 -08002879 // load glyphs, filters, and other definitions...
2880 setupDefs(svg);
Simon Hunt12ce12e2014-11-15 21:13:19 -08002881
Simon Hunt3c5ca542014-11-29 14:11:43 -08002882 panZoomContainer = svg.append('g').attr('id', 'panZoomContainer');
2883 setupPanZoom();
Paul Greysonfcba0e82014-11-13 10:21:16 -08002884
Simon Hunt233747b2014-12-04 12:01:58 -08002885 noDevices = svg.append('g')
2886 .attr('class', 'noDevsLayer')
2887 .attr('transform', translate(logSize/2, logSize/2));
2888 setupNoDevices();
2889
Simon Huntc7ee0662014-11-05 16:44:37 -08002890 // group for the topology
Simon Hunt3c5ca542014-11-29 14:11:43 -08002891 topoG = panZoomContainer.append('g')
Simon Hunt1b18aa52014-11-29 17:57:55 -08002892 .attr('id', 'topo-G');
Simon Huntc7ee0662014-11-05 16:44:37 -08002893
Simon Hunte2575b62014-11-18 15:25:53 -08002894 // subgroups for links, link labels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002895 linkG = topoG.append('g').attr('id', 'links');
Simon Hunte2575b62014-11-18 15:25:53 -08002896 linkLabelG = topoG.append('g').attr('id', 'linkLabels');
Simon Huntc7ee0662014-11-05 16:44:37 -08002897 nodeG = topoG.append('g').attr('id', 'nodes');
2898
Simon Hunte2575b62014-11-18 15:25:53 -08002899 // selection of links, linkLabels, and nodes
Simon Huntc7ee0662014-11-05 16:44:37 -08002900 link = linkG.selectAll('.link');
Simon Hunte2575b62014-11-18 15:25:53 -08002901 linkLabel = linkLabelG.selectAll('.linkLabel');
Simon Huntc7ee0662014-11-05 16:44:37 -08002902 node = nodeG.selectAll('.node');
2903
Simon Hunt7cd48f32014-11-09 23:42:50 -08002904 function chrg(d) {
2905 return fcfg.charge[d.class] || -12000;
2906 }
Simon Hunt99c13842014-11-06 18:23:12 -08002907 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002908 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08002909 }
2910 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08002911 // 0.0 - 1.0
2912 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08002913 }
2914
Simon Hunt1a9eff92014-11-07 11:06:34 -08002915 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08002916 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08002917 }
2918
2919 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08002920 // once we've finished moving, pin the node in position
2921 d.fixed = true;
2922 d3.select(self).classed('fixed', true);
2923 if (config.useLiveData) {
Simon Hunt395a70c2014-11-22 23:17:40 -08002924 sendUpdateMeta(d, true);
Simon Hunta255a2c2014-11-13 22:29:35 -08002925 } else {
2926 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08002927 }
2928 }
2929
Simon Hunt6e18fe32014-11-29 13:35:41 -08002930 // predicate that indicates when dragging is active
2931 function dragEnabled() {
Simon Hunta1162d82014-12-03 14:34:43 -08002932 var ev = d3.event.sourceEvent;
2933 // nodeLock means we aren't allowing nodes to be dragged...
2934 // meta or alt key pressed means we are zooming/panning...
2935 return !nodeLock && !(ev.metaKey || ev.altKey);
Simon Huntc2367d52014-11-29 19:30:23 -08002936 }
2937
2938 // predicate that indicates when clicking is active
2939 function clickEnabled() {
2940 return true;
Simon Hunt6e18fe32014-11-29 13:35:41 -08002941 }
2942
Simon Huntc7ee0662014-11-05 16:44:37 -08002943 // set up the force layout
2944 network.force = d3.layout.force()
Simon Hunt1b18aa52014-11-29 17:57:55 -08002945 .size([w, h])
Simon Huntc7ee0662014-11-05 16:44:37 -08002946 .nodes(network.nodes)
2947 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08002948 .gravity(0.4)
2949 .friction(0.7)
2950 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08002951 .linkDistance(ldist)
2952 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08002953 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08002954
Simon Hunt01095ff2014-11-13 16:37:29 -08002955 network.drag = d3u.createDragBehavior(network.force,
Simon Huntc2367d52014-11-29 19:30:23 -08002956 selectCb, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt6e18fe32014-11-29 13:35:41 -08002957
Simon Hunt0c6d4192014-11-12 12:07:10 -08002958
2959 // create mask layer for when we lose connection to server.
Simon Hunta5e89142014-11-14 07:00:33 -08002960 // TODO: this should be part of the framework
Simon Hunt6e18fe32014-11-29 13:35:41 -08002961
2962 function para(sel, text) {
2963 sel.append('p').text(text);
2964 }
2965
Simon Hunt0c6d4192014-11-12 12:07:10 -08002966 mask = view.$div.append('div').attr('id','topo-mask');
2967 para(mask, 'Oops!');
2968 para(mask, 'Web-socket connection to server closed...');
2969 para(mask, 'Try refreshing the page.');
Simon Hunt12ce12e2014-11-15 21:13:19 -08002970
2971 mask.append('svg')
2972 .attr({
2973 id: 'mask-bird',
2974 width: w,
2975 height: h
2976 })
2977 .append('g')
2978 .attr('transform', birdTranslate(w, h))
2979 .style('opacity', 0.3)
2980 .append('use')
2981 .attr({
2982 'xlink:href': '#bird',
2983 width: config.birdDim,
2984 height: config.birdDim,
2985 fill: '#111'
Thomas Vachuska89543292014-11-19 11:28:33 -08002986 })
Simon Hunt1a9eff92014-11-07 11:06:34 -08002987 }
Simon Hunt195cb382014-11-03 17:50:51 -08002988
Simon Hunt01095ff2014-11-13 16:37:29 -08002989
Simon Hunt56d51852014-11-09 13:03:35 -08002990 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08002991 // resize, in case the window was resized while we were not loaded
2992 resize(view, ctx, flags);
2993
Simon Hunt99c13842014-11-06 18:23:12 -08002994 // cache the view token, so network topo functions can access it
2995 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08002996 config.useLiveData = !flags.local;
2997
2998 if (!config.useLiveData) {
2999 prepareScenario(view, ctx, flags.debug);
3000 }
Simon Hunt99c13842014-11-06 18:23:12 -08003001
3002 // set our radio buttons and key bindings
Simon Hunt9462e8c2014-11-14 17:28:09 -08003003 layerBtnSet = view.setRadio(layerButtons);
Simon Hunt934c3ce2014-11-05 11:45:07 -08003004 view.setKeys(keyDispatch);
Simon Hunt87514342014-11-24 16:41:27 -08003005 view.setGestures(gestures);
Simon Hunt195cb382014-11-03 17:50:51 -08003006
Simon Huntd3b7d512014-11-12 15:48:41 -08003007 // Load map data asynchronously; complete startup after that..
3008 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08003009 }
3010
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08003011 function startAntTimer() {
Thomas Vachuskab7e40642014-12-03 11:16:06 -08003012 // Note: disabled until traffic can be allotted to intents properly
3013 if (false && !antTimer) {
Thomas Vachuska4731f122014-11-20 04:56:19 -08003014 var pulses = [5, 3, 1.2, 3],
3015 pulse = 0;
3016 antTimer = setInterval(function () {
3017 pulse = pulse + 1;
3018 pulse = pulse === pulses.length ? 0 : pulse;
3019 d3.selectAll('.animated').style('stroke-width', pulses[pulse]);
3020 }, 200);
3021 }
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08003022 }
3023
3024 function stopAntTimer() {
Simon Hunta255a2c2014-11-13 22:29:35 -08003025 if (antTimer) {
3026 clearInterval(antTimer);
3027 antTimer = null;
3028 }
Simon Huntd3b7d512014-11-12 15:48:41 -08003029 }
3030
Thomas Vachuskaa3148a72014-11-19 21:38:35 -08003031 function unload(view, ctx, flags) {
3032 stopAntTimer();
3033 }
3034
Simon Hunta6a9fe72014-11-20 11:17:12 -08003035 var geoJsonUrl = 'json/map/continental_us.json',
Simon Huntd3b7d512014-11-12 15:48:41 -08003036 geoJson;
3037
3038 function loadGeoJsonData() {
3039 d3.json(geoJsonUrl, function (err, data) {
3040 if (err) {
Simon Huntc2465f42014-12-04 10:28:07 -08003041 console.error('failed to load Map data', err);
Simon Huntd3b7d512014-11-12 15:48:41 -08003042 } else {
3043 geoJson = data;
3044 loadGeoMap();
3045 }
3046
Simon Hunt233747b2014-12-04 12:01:58 -08003047 repositionNoDevices();
3048 showNoDevs(true);
3049
Simon Huntd3b7d512014-11-12 15:48:41 -08003050 // finally, connect to the server...
3051 if (config.useLiveData) {
3052 webSock.connect();
3053 }
3054 });
3055 }
3056
Simon Hunt1b18aa52014-11-29 17:57:55 -08003057 function setProjForView(path, topoData) {
3058 var dim = config.logicalSize;
3059
3060 // start with unit scale, no translation..
3061 geoMapProj.scale(1).translate([0, 0]);
3062
3063 // figure out dimensions of map data..
3064 var b = path.bounds(topoData),
3065 x1 = b[0][0],
3066 y1 = b[0][1],
3067 x2 = b[1][0],
3068 y2 = b[1][1],
3069 dx = x2 - x1,
3070 dy = y2 - y1,
3071 x = (x1 + x2) / 2,
3072 y = (y1 + y2) / 2;
3073
3074 // size map to 95% of minimum dimension to fill space..
3075 var s = .95 / Math.min(dx / dim, dy / dim);
3076 var t = [dim / 2 - s * x, dim / 2 - s * y];
3077
3078 // set new scale, translation on the projection..
3079 geoMapProj.scale(s).translate(t);
3080 }
3081
Simon Huntd3b7d512014-11-12 15:48:41 -08003082 function loadGeoMap() {
3083 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08003084
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003085 // extracts the topojson data into geocoordinate-based geometry
3086 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08003087
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003088 // see: http://bl.ocks.org/mbostock/4707858
Simon Hunt1b18aa52014-11-29 17:57:55 -08003089 geoMapProj = d3.geo.mercator();
3090 var path = d3.geo.path().projection(geoMapProj);
Simon Huntd3b7d512014-11-12 15:48:41 -08003091
Simon Hunt1b18aa52014-11-29 17:57:55 -08003092 setProjForView(path, topoData);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08003093
Simon Hunt3c5ca542014-11-29 14:11:43 -08003094 bgImg = panZoomContainer.insert("g", '#topo-G');
Thomas Vachuska89543292014-11-19 11:28:33 -08003095 bgImg.attr('id', 'map').selectAll('path')
3096 .data(topoData.features)
3097 .enter()
3098 .append('path')
3099 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08003100 }
3101
Simon Huntf67722a2014-11-10 09:32:06 -08003102 function resize(view, ctx, flags) {
Simon Hunt12ce12e2014-11-15 21:13:19 -08003103 var w = view.width(),
3104 h = view.height();
3105
Simon Hunt934c3ce2014-11-05 11:45:07 -08003106 setSize(svg, view);
Simon Hunt12ce12e2014-11-15 21:13:19 -08003107
3108 d3.select('#mask-bird').attr({ width: w, height: h})
3109 .select('g').attr('transform', birdTranslate(w, h));
Simon Hunt142d0032014-11-04 20:13:09 -08003110 }
3111
Simon Hunt8f40cce2014-11-23 15:57:30 -08003112 function theme(view, ctx, flags) {
3113 updateInstances();
Simon Huntb0ecfa52014-11-23 21:05:12 -08003114 updateDeviceColors();
Simon Hunt8f40cce2014-11-23 15:57:30 -08003115 }
3116
Simon Hunt12ce12e2014-11-15 21:13:19 -08003117 function birdTranslate(w, h) {
3118 var bdim = config.birdDim;
3119 return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
3120 }
Simon Hunt142d0032014-11-04 20:13:09 -08003121
Simon Hunt06811b72014-11-25 18:54:48 -08003122 function isF(f) { return $.isFunction(f) ? f : null; }
3123 function noop() {}
3124
3125 function augmentDetailPane() {
3126 var dp = detailPane;
3127 dp.ypos = { up: 64, down: 320, current: 320};
3128
3129 dp._move = function (y, cb) {
3130 var endCb = isF(cb) || noop,
3131 yp = dp.ypos;
3132 if (yp.current !== y) {
3133 yp.current = y;
3134 dp.el.transition().duration(300)
3135 .each('end', endCb)
3136 .style('top', yp.current + 'px');
3137 } else {
3138 endCb();
3139 }
3140 };
3141
3142 dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
3143 dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
3144 }
3145
Simon Hunt142d0032014-11-04 20:13:09 -08003146 // ==============================
3147 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08003148
Simon Hunt25248912014-11-04 11:25:48 -08003149 onos.ui.addView('topo', {
Simon Hunta2994cc2014-12-02 14:19:15 -08003150 init: init,
Simon Hunt142d0032014-11-04 20:13:09 -08003151 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08003152 unload: unload,
Simon Hunt8f40cce2014-11-23 15:57:30 -08003153 resize: resize,
3154 theme: theme
Simon Hunt195cb382014-11-03 17:50:51 -08003155 });
3156
Thomas Vachuska47635c62014-11-22 01:21:36 -08003157 summaryPane = onos.ui.addFloatingPanel('topo-summary');
Simon Hunt61d04042014-11-11 17:27:16 -08003158 detailPane = onos.ui.addFloatingPanel('topo-detail');
Simon Hunt06811b72014-11-25 18:54:48 -08003159 augmentDetailPane();
Simon Hunta5e89142014-11-14 07:00:33 -08003160 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
Simon Huntb82f6902014-11-22 11:53:15 -08003161 oiBox.width(20);
Simon Hunt61d04042014-11-11 17:27:16 -08003162
Simon Hunt195cb382014-11-03 17:50:51 -08003163}(ONOS));