blob: ea3c6e67ff290bb598eabafa44db7dea0acc548f [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
21 */
22
23(function (onos) {
24 'use strict';
25
Simon Hunt1a9eff92014-11-07 11:06:34 -080026 // shorter names for library APIs
Simon Huntbb282f52014-11-10 11:08:19 -080027 var d3u = onos.lib.d3util,
28 trace;
Simon Hunt1a9eff92014-11-07 11:06:34 -080029
Simon Hunt195cb382014-11-03 17:50:51 -080030 // configuration data
31 var config = {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080032 useLiveData: true,
Simon Huntfc274c92014-11-11 11:05:46 -080033 fnTrace: true,
Simon Hunt195cb382014-11-03 17:50:51 -080034 debugOn: false,
35 debug: {
Simon Hunt99c13842014-11-06 18:23:12 -080036 showNodeXY: true,
37 showKeyHandler: false
Simon Hunt195cb382014-11-03 17:50:51 -080038 },
39 options: {
40 layering: true,
41 collisionPrevention: true,
Simon Hunt142d0032014-11-04 20:13:09 -080042 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080043 },
44 backgroundUrl: 'img/us-map.png',
Thomas Vachuska7d638d32014-11-07 10:24:43 -080045 webSockUrl: 'ws/topology',
Simon Hunt195cb382014-11-03 17:50:51 -080046 data: {
47 live: {
48 jsonUrl: 'rs/topology/graph',
49 detailPrefix: 'rs/topology/graph/',
50 detailSuffix: ''
51 },
52 fake: {
53 jsonUrl: 'json/network2.json',
54 detailPrefix: 'json/',
55 detailSuffix: '.json'
56 }
57 },
Simon Hunt99c13842014-11-06 18:23:12 -080058 labels: {
59 imgPad: 16,
60 padLR: 4,
61 padTB: 3,
62 marginLR: 3,
63 marginTB: 2,
64 port: {
65 gap: 3,
66 width: 18,
67 height: 14
68 }
69 },
Simon Hunt1a9eff92014-11-07 11:06:34 -080070 topo: {
71 linkInColor: '#66f',
72 linkInWidth: 14
73 },
Simon Hunt99c13842014-11-06 18:23:12 -080074 icons: {
75 w: 28,
76 h: 28,
77 xoff: -12,
78 yoff: -8
79 },
Simon Hunt195cb382014-11-03 17:50:51 -080080 iconUrl: {
81 device: 'img/device.png',
82 host: 'img/host.png',
83 pkt: 'img/pkt.png',
84 opt: 'img/opt.png'
85 },
Simon Hunt195cb382014-11-03 17:50:51 -080086 force: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080087 note_for_links: 'link.type is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080088 linkDistance: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080089 direct: 100,
90 optical: 120,
Thomas Vachuska3266abf2014-11-13 09:28:46 -080091 hostLink: 3
Simon Huntc7ee0662014-11-05 16:44:37 -080092 },
93 linkStrength: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080094 direct: 1.0,
95 optical: 1.0,
96 hostLink: 1.0
Simon Huntc7ee0662014-11-05 16:44:37 -080097 },
Simon Hunt7cd48f32014-11-09 23:42:50 -080098 note_for_nodes: 'node.class is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080099 charge: {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800100 device: -8000,
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800101 host: -5000
Simon Huntc7ee0662014-11-05 16:44:37 -0800102 },
103 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -0800104 translate: function() {
105 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -0800106 config.force.pad + ',' +
107 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -0800108 }
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800109 },
110 // see below in creation of viewBox on main svg
111 logicalSize: 1000
Simon Hunt195cb382014-11-03 17:50:51 -0800112 };
113
Simon Hunt142d0032014-11-04 20:13:09 -0800114 // radio buttons
115 var btnSet = [
Simon Hunt934c3ce2014-11-05 11:45:07 -0800116 { text: 'All Layers', cb: showAllLayers },
117 { text: 'Packet Only', cb: showPacketLayer },
118 { text: 'Optical Only', cb: showOpticalLayer }
119 ];
120
121 // key bindings
122 var keyDispatch = {
Simon Hunta255a2c2014-11-13 22:29:35 -0800123 M: testMe, // TODO: remove (testing only)
124 S: injectStartupEvents, // TODO: remove (testing only)
125 space: injectTestEvent, // TODO: remove (testing only)
Simon Hunt99c13842014-11-06 18:23:12 -0800126
Simon Hunt01095ff2014-11-13 16:37:29 -0800127 B: toggleBg,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800128 L: cycleLabels,
129 P: togglePorts,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800130 U: unpin,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800131 R: resetZoomPan,
Simon Huntd72bc702014-11-13 18:38:04 -0800132 esc: deselectAll
Simon Hunt934c3ce2014-11-05 11:45:07 -0800133 };
Simon Hunt142d0032014-11-04 20:13:09 -0800134
Simon Hunt195cb382014-11-03 17:50:51 -0800135 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800136 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800137 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800138 nodes: [],
139 links: [],
140 lookup: {}
141 },
Simon Hunt56d51852014-11-09 13:03:35 -0800142 scenario = {
143 evDir: 'json/ev/',
144 evScenario: '/scenario.json',
145 evPrefix: '/ev_',
146 evOnos: '_onos.json',
147 evUi: '_ui.json',
148 ctx: null,
149 params: {},
150 evNumber: 0,
151 view: null,
152 debug: false
153 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800154 webSock,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800155 sid = 0,
Simon Hunt56d51852014-11-09 13:03:35 -0800156 deviceLabelIndex = 0,
157 hostLabelIndex = 0,
Simon Hunt61d04042014-11-11 17:27:16 -0800158 detailPane,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800159 selectOrder = [],
160 selections = {},
Simon Hunt6ac93f32014-11-13 12:17:27 -0800161 hovered = null,
Simon Hunta255a2c2014-11-13 22:29:35 -0800162 antTimer = null,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800163
Simon Hunt195cb382014-11-03 17:50:51 -0800164 viewMode = 'showAll',
165 portLabelsOn = false;
166
Simon Hunt934c3ce2014-11-05 11:45:07 -0800167 // D3 selections
168 var svg,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800169 zoomPanContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800170 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800171 topoG,
172 nodeG,
173 linkG,
174 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800175 link,
176 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800177
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800178 // the projection for the map background
179 var geoMapProjection;
180
Paul Greysonfcba0e82014-11-13 10:21:16 -0800181 // the zoom function
182 var zoom;
183
Simon Hunt142d0032014-11-04 20:13:09 -0800184 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800185 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800186
Simon Hunt99c13842014-11-06 18:23:12 -0800187 function note(label, msg) {
188 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800189 }
190
Simon Hunt99c13842014-11-06 18:23:12 -0800191 function debug(what) {
192 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800193 }
194
Simon Huntfc274c92014-11-11 11:05:46 -0800195 function fnTrace(msg, id) {
196 if (config.fnTrace) {
197 console.log('FN: ' + msg + ' [' + id + ']');
198 }
199 }
Simon Hunt99c13842014-11-06 18:23:12 -0800200
Simon Hunt934c3ce2014-11-05 11:45:07 -0800201 // ==============================
202 // Key Callbacks
203
Simon Hunt99c13842014-11-06 18:23:12 -0800204 function testMe(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800205 view.alert('test');
Simon Hunt99c13842014-11-06 18:23:12 -0800206 }
207
Simon Hunt56d51852014-11-09 13:03:35 -0800208 function abortIfLive() {
Simon Hunt50128c02014-11-08 13:36:15 -0800209 if (config.useLiveData) {
Simon Huntb53e0682014-11-12 13:32:01 -0800210 network.view.alert("Sorry, currently using live data..");
Simon Hunt56d51852014-11-09 13:03:35 -0800211 return true;
Simon Hunt50128c02014-11-08 13:36:15 -0800212 }
Simon Hunt56d51852014-11-09 13:03:35 -0800213 return false;
214 }
Simon Hunt50128c02014-11-08 13:36:15 -0800215
Simon Hunt56d51852014-11-09 13:03:35 -0800216 function testDebug(msg) {
217 if (scenario.debug) {
218 scenario.view.alert(msg);
219 }
220 }
Simon Hunt99c13842014-11-06 18:23:12 -0800221
Simon Hunt56d51852014-11-09 13:03:35 -0800222 function injectTestEvent(view) {
223 if (abortIfLive()) { return; }
224 var sc = scenario,
225 evn = ++sc.evNumber,
226 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
227 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800228 uiUrl = pfx + sc.evUi,
229 stack = [
230 { url: onosUrl, cb: handleServerEvent },
231 { url: uiUrl, cb: handleUiEvent }
232 ];
233 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800234 }
235
Simon Hunt7cd48f32014-11-09 23:42:50 -0800236 function recurseFetchEvent(stack, evn) {
237 var v = scenario.view,
238 frame;
239 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800240 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800241 return;
242 }
243 frame = stack.shift();
244
245 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800246 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800247 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800248 // if we didn't find the data, try the next stack frame
249 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800250 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800251 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800252 }
Simon Hunt99c13842014-11-06 18:23:12 -0800253 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800254 testDebug('loaded: ' + frame.url);
255 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800256 }
257 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800258
Simon Hunt56d51852014-11-09 13:03:35 -0800259 }
Simon Hunt50128c02014-11-08 13:36:15 -0800260
Simon Hunt56d51852014-11-09 13:03:35 -0800261 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800262 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
263 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800264 }
265
266 function injectStartupEvents(view) {
267 var last = scenario.params.lastAuto || 0;
268 if (abortIfLive()) { return; }
269
270 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800271 injectTestEvent(view);
272 }
273 }
274
Simon Hunt934c3ce2014-11-05 11:45:07 -0800275 function toggleBg() {
276 var vis = bgImg.style('visibility');
277 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
278 }
279
Simon Hunt99c13842014-11-06 18:23:12 -0800280 function cycleLabels() {
Simon Huntbb282f52014-11-10 11:08:19 -0800281 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1)
282 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800283
Simon Hunt99c13842014-11-06 18:23:12 -0800284 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800285 if (d.class === 'device') {
286 updateDeviceLabel(d);
287 }
Simon Hunt99c13842014-11-06 18:23:12 -0800288 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800289 }
290
291 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800292 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800293 }
294
Simon Hunt6ac93f32014-11-13 12:17:27 -0800295 function unpin() {
296 if (hovered) {
297 hovered.fixed = false;
298 hovered.el.classed('fixed', false);
299 network.force.resume();
300 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800301 }
302
303 // ==============================
304 // Radio Button Callbacks
305
Simon Hunt195cb382014-11-03 17:50:51 -0800306 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800307// network.node.classed('inactive', false);
308// network.link.classed('inactive', false);
309// d3.selectAll('svg .port').classed('inactive', false);
310// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800311 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800312 network.view.alert('showAllLayers() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800313 }
314
315 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800316 showAllLayers();
317 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800318 network.view.alert('showPacketLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800319 }
320
321 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800322 showAllLayers();
323 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800324 network.view.alert('showOpticalLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800325 }
326
Simon Hunt142d0032014-11-04 20:13:09 -0800327 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800328 // Private functions
329
Simon Hunt99c13842014-11-06 18:23:12 -0800330 function safeId(s) {
331 return s.replace(/[^a-z0-9]/gi, '-');
332 }
333
Simon Huntc7ee0662014-11-05 16:44:37 -0800334 // set the size of the given element to that of the view (reduced if padded)
335 function setSize(el, view, pad) {
336 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800337 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800338 width: view.width() - padding,
339 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800340 });
341 }
342
Simon Hunt934c3ce2014-11-05 11:45:07 -0800343
Simon Hunt99c13842014-11-06 18:23:12 -0800344 // ==============================
345 // Event handlers for server-pushed events
346
Simon Huntbb282f52014-11-10 11:08:19 -0800347 function logicError(msg) {
348 // TODO, report logic error to server, via websock, so it can be logged
349 network.view.alert('Logic Error:\n\n' + msg);
Simon Huntfc274c92014-11-11 11:05:46 -0800350 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800351 }
352
Simon Hunt99c13842014-11-06 18:23:12 -0800353 var eventDispatch = {
Simon Huntd72bc702014-11-13 18:38:04 -0800354 addInstance: stillToImplement,
Simon Hunt99c13842014-11-06 18:23:12 -0800355 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800356 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800357 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800358
Simon Huntd72bc702014-11-13 18:38:04 -0800359 updateInstance: stillToImplement,
Simon Huntbb282f52014-11-10 11:08:19 -0800360 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800361 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800362 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800363
Simon Huntd72bc702014-11-13 18:38:04 -0800364 removeInstance: stillToImplement,
Simon Huntbb282f52014-11-10 11:08:19 -0800365 removeDevice: stillToImplement,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800366 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800367 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800368
Simon Hunt61d04042014-11-11 17:27:16 -0800369 showDetails: showDetails,
Simon Huntb53e0682014-11-12 13:32:01 -0800370 showPath: showPath,
371 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800372 };
373
374 function addDevice(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800375 fnTrace('addDevice', data.payload.id);
Simon Hunt99c13842014-11-06 18:23:12 -0800376 var device = data.payload,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800377 nodeData = createDeviceNode(device);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800378 network.nodes.push(nodeData);
379 network.lookup[nodeData.id] = nodeData;
Simon Hunt99c13842014-11-06 18:23:12 -0800380 updateNodes();
381 network.force.start();
382 }
383
Simon Hunt99c13842014-11-06 18:23:12 -0800384 function addLink(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800385 fnTrace('addLink', data.payload.id);
Simon Hunt99c13842014-11-06 18:23:12 -0800386 var link = data.payload,
387 lnk = createLink(link);
Simon Hunt99c13842014-11-06 18:23:12 -0800388 if (lnk) {
Simon Hunt99c13842014-11-06 18:23:12 -0800389 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800390 network.lookup[lnk.id] = lnk;
Simon Hunt99c13842014-11-06 18:23:12 -0800391 updateLinks();
392 network.force.start();
393 }
394 }
395
Simon Hunt56d51852014-11-09 13:03:35 -0800396 function addHost(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800397 fnTrace('addHost', data.payload.id);
Simon Hunt56d51852014-11-09 13:03:35 -0800398 var host = data.payload,
399 node = createHostNode(host),
400 lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800401 network.nodes.push(node);
402 network.lookup[host.id] = node;
403 updateNodes();
404
405 lnk = createHostLink(host);
406 if (lnk) {
Simon Hunt44031102014-11-11 13:20:36 -0800407 node.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800408 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800409 network.lookup[host.ingress] = lnk;
410 network.lookup[host.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800411 updateLinks();
412 }
413 network.force.start();
414 }
415
Simon Hunt44031102014-11-11 13:20:36 -0800416 // TODO: fold updateX(...) methods into one base method; remove duplication
Simon Huntbb282f52014-11-10 11:08:19 -0800417 function updateDevice(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800418 fnTrace('updateDevice', data.payload.id);
Simon Huntbb282f52014-11-10 11:08:19 -0800419 var device = data.payload,
420 id = device.id,
421 nodeData = network.lookup[id];
422 if (nodeData) {
423 $.extend(nodeData, device);
424 updateDeviceState(nodeData);
425 } else {
426 logicError('updateDevice lookup fail. ID = "' + id + '"');
427 }
428 }
429
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800430 function updateLink(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800431 fnTrace('updateLink', data.payload.id);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800432 var link = data.payload,
433 id = link.id,
434 linkData = network.lookup[id];
435 if (linkData) {
436 $.extend(linkData, link);
437 updateLinkState(linkData);
438 } else {
439 logicError('updateLink lookup fail. ID = "' + id + '"');
440 }
441 }
442
Simon Hunt7cd48f32014-11-09 23:42:50 -0800443 function updateHost(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800444 fnTrace('updateHost', data.payload.id);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800445 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800446 id = host.id,
447 hostData = network.lookup[id];
448 if (hostData) {
449 $.extend(hostData, host);
450 updateHostState(hostData);
451 } else {
452 logicError('updateHost lookup fail. ID = "' + id + '"');
453 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800454 }
455
Simon Hunt44031102014-11-11 13:20:36 -0800456 // TODO: fold removeX(...) methods into base method - remove dup code
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800457 function removeLink(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800458 fnTrace('removeLink', data.payload.id);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800459 var link = data.payload,
460 id = link.id,
461 linkData = network.lookup[id];
462 if (linkData) {
463 removeLinkElement(linkData);
464 } else {
465 logicError('removeLink lookup fail. ID = "' + id + '"');
466 }
467 }
468
Simon Hunt44031102014-11-11 13:20:36 -0800469 function removeHost(data) {
470 fnTrace('removeHost', data.payload.id);
471 var host = data.payload,
472 id = host.id,
473 hostData = network.lookup[id];
474 if (hostData) {
475 removeHostElement(hostData);
476 } else {
477 logicError('removeHost lookup fail. ID = "' + id + '"');
478 }
479 }
480
Simon Hunt61d04042014-11-11 17:27:16 -0800481 function showDetails(data) {
482 fnTrace('showDetails', data.payload.id);
483 populateDetails(data.payload);
484 detailPane.show();
485 }
486
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800487 function showPath(data) {
Simon Huntd72bc702014-11-13 18:38:04 -0800488 // TODO: review - making sure we are handling the payload correctly.
Simon Huntfc274c92014-11-11 11:05:46 -0800489 fnTrace('showPath', data.payload.id);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800490 var links = data.payload.links,
491 s = [ data.event + "\n" + links.length ];
492 links.forEach(function (d, i) {
493 s.push(d);
494 });
495 network.view.alert(s.join('\n'));
496
497 links.forEach(function (d, i) {
498 var link = network.lookup[d];
499 if (link) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800500 link.el.classed('showPath', true);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800501 }
502 });
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800503 }
504
Simon Huntb53e0682014-11-12 13:32:01 -0800505 function showTraffic(data) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800506 fnTrace('showTraffic', data.payload.id);
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800507 var paths = data.payload.paths;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800508
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800509 // Revert any links hilighted previously.
Simon Hunta255a2c2014-11-13 22:29:35 -0800510 link.classed('primary secondary animated optical', false);
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800511
512 // Now hilight all links in the paths payload.
Simon Hunta255a2c2014-11-13 22:29:35 -0800513 paths.forEach(function (p) {
514 var cls = p.class;
515 p.links.forEach(function (id) {
516 var lnk = network.lookup[id];
517 if (lnk) {
518 lnk.el.classed(cls, true);
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800519 }
520 });
521 });
Simon Huntb53e0682014-11-12 13:32:01 -0800522 }
523
Simon Hunt56d51852014-11-09 13:03:35 -0800524 // ...............................
525
526 function stillToImplement(data) {
527 var p = data.payload;
528 note(data.event, p.id);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800529 network.view.alert('Not yet implemented: "' + data.event + '"');
Simon Hunt56d51852014-11-09 13:03:35 -0800530 }
Simon Hunt99c13842014-11-06 18:23:12 -0800531
532 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800533 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800534 }
535
536 function handleServerEvent(data) {
537 var fn = eventDispatch[data.event] || unknownEvent;
538 fn(data);
539 }
540
541 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -0800542 // Out-going messages...
543
Simon Huntb53e0682014-11-12 13:32:01 -0800544 function userFeedback(msg) {
545 // for now, use the alert pane as is. Maybe different alert style in
546 // the future (centered on view; dismiss button?)
547 network.view.alert(msg);
548 }
549
550 function nSel() {
551 return selectOrder.length;
552 }
Simon Hunt61d04042014-11-11 17:27:16 -0800553 function getSel(idx) {
554 return selections[selectOrder[idx]];
555 }
Simon Huntb53e0682014-11-12 13:32:01 -0800556 function getSelId(idx) {
557 return getSel(idx).obj.id;
558 }
559 function allSelectionsClass(cls) {
560 for (var i=0, n=nSel(); i<n; i++) {
561 if (getSel(i).obj.class !== cls) {
562 return false;
563 }
564 }
565 return true;
566 }
Simon Hunt61d04042014-11-11 17:27:16 -0800567
Simon Hunt61d04042014-11-11 17:27:16 -0800568 // request details for the selected element
Simon Huntd72bc702014-11-13 18:38:04 -0800569 // invoked from selection of a single node.
Simon Hunt61d04042014-11-11 17:27:16 -0800570 function requestDetails() {
571 var data = getSel(0).obj,
572 payload = {
573 id: data.id,
574 class: data.class
575 };
576 sendMessage('requestDetails', payload);
577 }
578
Simon Huntd72bc702014-11-13 18:38:04 -0800579 function addIntentAction() {
580 sendMessage('addHostIntent', {
581 one: getSelId(0),
582 two: getSelId(1)
583 });
584 }
585
586 function showTrafficAction() {
587 // if nothing is hovered over, and nothing selected, send cancel request
588 if (!hovered && nSel() === 0) {
589 sendMessage('cancelTraffic', {});
590 return;
591 }
592
593 // NOTE: hover is only populated if "show traffic on hover" is
594 // toggled on, and the item hovered is a host...
595 var hoverId = (trafficHover() && hovered && hovered.class === 'host')
596 ? hovered.id : '';
597 sendMessage('requestTraffic', {
598 ids: selectOrder,
599 hover: hoverId
600 });
601 }
602
603
Simon Hunt61d04042014-11-11 17:27:16 -0800604 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -0800605 // force layout modification functions
606
607 function translate(x, y) {
608 return 'translate(' + x + ',' + y + ')';
609 }
610
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800611 function missMsg(what, id) {
612 return '\n[' + what + '] "' + id + '" missing ';
613 }
614
615 function linkEndPoints(srcId, dstId) {
616 var srcNode = network.lookup[srcId],
617 dstNode = network.lookup[dstId],
618 sMiss = !srcNode ? missMsg('src', srcId) : '',
619 dMiss = !dstNode ? missMsg('dst', dstId) : '';
620
621 if (sMiss || dMiss) {
622 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
623 return null;
624 }
625 return {
626 source: srcNode,
627 target: dstNode,
628 x1: srcNode.x,
629 y1: srcNode.y,
630 x2: dstNode.x,
631 y2: dstNode.y
632 };
633 }
634
Simon Hunt56d51852014-11-09 13:03:35 -0800635 function createHostLink(host) {
636 var src = host.id,
637 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800638 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800639 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -0800640
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800641 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -0800642 return null;
643 }
644
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800645 // Synthesize link ...
646 $.extend(lnk, {
Thomas Vachuska4830d392014-11-09 17:09:56 -0800647 id: id,
Simon Hunt56d51852014-11-09 13:03:35 -0800648 class: 'link',
Simon Hunt7cd48f32014-11-09 23:42:50 -0800649 type: 'hostLink',
Simon Hunt56d51852014-11-09 13:03:35 -0800650 svgClass: 'link hostLink',
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800651 linkWidth: 1
Simon Hunt7cd48f32014-11-09 23:42:50 -0800652 });
Simon Hunt99c13842014-11-06 18:23:12 -0800653 return lnk;
654 }
655
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800656 function createLink(link) {
657 var lnk = linkEndPoints(link.src, link.dst),
658 type = link.type;
659
660 if (!lnk) {
661 return null;
662 }
663
664 // merge in remaining data
665 $.extend(lnk, link, {
666 class: 'link',
667 svgClass: type ? 'link ' + type : 'link'
668 });
669 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -0800670 }
671
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800672 var widthRatio = 1.4,
673 linkScale = d3.scale.linear()
674 .domain([1, 12])
675 .range([widthRatio, 12 * widthRatio])
676 .clamp(true);
677
678 function updateLinkWidth (d) {
679 // TODO: watch out for .showPath/.showTraffic classes
680 d.el.transition()
681 .duration(1000)
682 .attr('stroke-width', linkScale(d.linkWidth));
683 }
684
685
Simon Hunt99c13842014-11-06 18:23:12 -0800686 function updateLinks() {
687 link = linkG.selectAll('.link')
688 .data(network.links, function (d) { return d.id; });
689
690 // operate on existing links, if necessary
691 // link .foo() .bar() ...
692
693 // operate on entering links:
694 var entering = link.enter()
695 .append('line')
696 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800697 class: function (d) { return d.svgClass; },
698 x1: function (d) { return d.x1; },
699 y1: function (d) { return d.y1; },
700 x2: function (d) { return d.x2; },
701 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800702 stroke: config.topo.linkInColor,
703 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -0800704 })
705 .transition().duration(1000)
706 .attr({
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800707 'stroke-width': function (d) { return linkScale(d.linkWidth); },
Simon Hunt99c13842014-11-06 18:23:12 -0800708 stroke: '#666' // TODO: remove explicit stroke, rather...
709 });
710
711 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -0800712 entering.each(function (d) {
713 var link = d3.select(this);
714 // provide ref to element selection from backing data....
715 d.el = link;
Simon Hunt99c13842014-11-06 18:23:12 -0800716
Simon Hunt7cd48f32014-11-09 23:42:50 -0800717 // TODO: add src/dst port labels etc.
718 });
Thomas Vachuska4830d392014-11-09 17:09:56 -0800719
720 // operate on both existing and new links, if necessary
721 //link .foo() .bar() ...
722
723 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -0800724 link.exit()
Thomas Vachuska4830d392014-11-09 17:09:56 -0800725 .attr({
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800726 'stroke-dasharray': '3, 3'
Thomas Vachuska4830d392014-11-09 17:09:56 -0800727 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800728 .style('opacity', 0.4)
729 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -0800730 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800731 .attr({
732 'stroke-dasharray': '3, 12'
733 })
734 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -0800735 .duration(500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800736 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -0800737 .remove();
Simon Hunt99c13842014-11-06 18:23:12 -0800738 }
739
740 function createDeviceNode(device) {
741 // start with the object as is
742 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -0800743 type = device.type,
744 svgCls = type ? 'node device ' + type : 'node device';
Simon Hunt99c13842014-11-06 18:23:12 -0800745
746 // Augment as needed...
747 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -0800748 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -0800749 positionNode(node);
750
751 // cache label array length
752 network.deviceLabelCount = device.labels.length;
Simon Hunt99c13842014-11-06 18:23:12 -0800753 return node;
754 }
755
Simon Hunt56d51852014-11-09 13:03:35 -0800756 function createHostNode(host) {
757 // start with the object as is
758 var node = host;
759
760 // Augment as needed...
761 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -0800762 if (!node.type) {
763 // TODO: perhaps type would be: {phone, tablet, laptop, endstation} ?
764 node.type = 'endstation';
765 }
Simon Hunt56d51852014-11-09 13:03:35 -0800766 node.svgClass = 'node host';
767 // TODO: consider placing near its switch, if [x,y] not defined
768 positionNode(node);
769
770 // cache label array length
771 network.hostLabelCount = host.labels.length;
Simon Hunt56d51852014-11-09 13:03:35 -0800772 return node;
773 }
774
Simon Hunt99c13842014-11-06 18:23:12 -0800775 function positionNode(node) {
776 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -0800777 x = meta && meta.x,
778 y = meta && meta.y,
779 xy;
Simon Hunt99c13842014-11-06 18:23:12 -0800780
Simon Huntac9e24f2014-11-12 10:12:21 -0800781 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -0800782 if (x && y) {
783 node.fixed = true;
Simon Huntac9e24f2014-11-12 10:12:21 -0800784 node.x = x;
785 node.y = y;
786 return;
Simon Hunt99c13842014-11-06 18:23:12 -0800787 }
Simon Huntac9e24f2014-11-12 10:12:21 -0800788
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800789 var location = node.location;
790 if (location && location.type === 'latlng') {
791 var coord = geoMapProjection([location.lng, location.lat]);
792 node.fixed = true;
793 node.x = coord[0];
794 node.y = coord[1];
795 return;
796 }
797
Simon Huntac9e24f2014-11-12 10:12:21 -0800798 // Note: Placing incoming unpinned nodes at exactly the same point
799 // (center of the view) causes them to explode outwards when
800 // the force layout kicks in. So, we spread them out a bit
801 // initially, to provide a more serene layout convergence.
802 // Additionally, if the node is a host, we place it near
803 // the device it is connected to.
804
805 function spread(s) {
806 return Math.floor((Math.random() * s) - s/2);
807 }
808
809 function randDim(dim) {
810 return dim / 2 + spread(dim * 0.7071);
811 }
812
813 function rand() {
814 return {
815 x: randDim(network.view.width()),
816 y: randDim(network.view.height())
817 };
818 }
819
820 function near(node) {
821 var min = 12,
822 dx = spread(12),
823 dy = spread(12);
824 return {
825 x: node.x + min + dx,
826 y: node.y + min + dy
827 };
828 }
829
830 function getDevice(cp) {
831 var d = network.lookup[cp.device];
832 return d || rand();
833 }
834
835 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
836 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -0800837 }
838
Simon Hunt99c13842014-11-06 18:23:12 -0800839 function iconUrl(d) {
840 return 'img/' + d.type + '.png';
841 }
842
843 // returns the newly computed bounding box of the rectangle
844 function adjustRectToFitText(n) {
845 var text = n.select('text'),
846 box = text.node().getBBox(),
847 lab = config.labels;
848
849 text.attr('text-anchor', 'middle')
850 .attr('y', '-0.8em')
851 .attr('x', lab.imgPad/2);
852
853 // translate the bbox so that it is centered on [x,y]
854 box.x = -box.width / 2;
855 box.y = -box.height / 2;
856
857 // add padding
858 box.x -= (lab.padLR + lab.imgPad/2);
859 box.width += lab.padLR * 2 + lab.imgPad;
860 box.y -= lab.padTB;
861 box.height += lab.padTB * 2;
862
863 return box;
864 }
865
Simon Hunt1a9eff92014-11-07 11:06:34 -0800866 function mkSvgClass(d) {
867 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
868 }
869
Simon Hunt7cd48f32014-11-09 23:42:50 -0800870 function hostLabel(d) {
871 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
872 return d.labels[idx];
873 }
874 function deviceLabel(d) {
875 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
876 return d.labels[idx];
877 }
878 function niceLabel(label) {
879 return (label && label.trim()) ? label : '.';
880 }
881
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800882 function updateDeviceLabel(d) {
883 var label = niceLabel(deviceLabel(d)),
884 node = d.el,
885 box;
886
887 node.select('text')
888 .text(label)
889 .style('opacity', 0)
890 .transition()
891 .style('opacity', 1);
892
893 box = adjustRectToFitText(node);
894
895 node.select('rect')
896 .transition()
897 .attr(box);
898
899 node.select('image')
900 .transition()
901 .attr('x', box.x + config.icons.xoff)
902 .attr('y', box.y + config.icons.yoff);
903 }
904
905 function updateHostLabel(d) {
906 var label = hostLabel(d),
907 host = d.el;
908
909 host.select('text').text(label);
910 }
911
Simon Huntbb282f52014-11-10 11:08:19 -0800912 function updateDeviceState(nodeData) {
913 nodeData.el.classed('online', nodeData.online);
914 updateDeviceLabel(nodeData);
915 // TODO: review what else might need to be updated
916 }
917
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800918 function updateLinkState(linkData) {
919 updateLinkWidth(linkData);
920 // TODO: review what else might need to be updated
921 // update label, if showing
922 }
923
Simon Huntbb282f52014-11-10 11:08:19 -0800924 function updateHostState(hostData) {
925 updateHostLabel(hostData);
926 // TODO: review what else might need to be updated
927 }
928
Simon Hunt6ac93f32014-11-13 12:17:27 -0800929 function nodeMouseOver(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -0800930 hovered = d;
Simon Huntd72bc702014-11-13 18:38:04 -0800931 if (trafficHover() && d.class === 'host') {
932 showTrafficAction();
Simon Hunt6ac93f32014-11-13 12:17:27 -0800933 }
934 }
935
936 function nodeMouseOut(d) {
Simon Hunt6ac93f32014-11-13 12:17:27 -0800937 hovered = null;
Simon Huntd72bc702014-11-13 18:38:04 -0800938 if (trafficHover() && d.class === 'host') {
939 showTrafficAction();
Simon Hunt6ac93f32014-11-13 12:17:27 -0800940 }
941 }
Simon Huntbb282f52014-11-10 11:08:19 -0800942
Simon Hunt99c13842014-11-06 18:23:12 -0800943 function updateNodes() {
944 node = nodeG.selectAll('.node')
945 .data(network.nodes, function (d) { return d.id; });
946
947 // operate on existing nodes, if necessary
Simon Hunt7cd48f32014-11-09 23:42:50 -0800948 // update host labels
Simon Hunt99c13842014-11-06 18:23:12 -0800949 //node .foo() .bar() ...
950
951 // operate on entering nodes:
952 var entering = node.enter()
953 .append('g')
954 .attr({
955 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800956 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800957 transform: function (d) { return translate(d.x, d.y); },
958 opacity: 0
959 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800960 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -0800961 .on('mouseover', nodeMouseOver)
962 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -0800963 .transition()
964 .attr('opacity', 1);
965
966 // augment device nodes...
967 entering.filter('.device').each(function (d) {
968 var node = d3.select(this),
969 icon = iconUrl(d),
Simon Hunt7cd48f32014-11-09 23:42:50 -0800970 label = niceLabel(deviceLabel(d)),
Simon Hunt99c13842014-11-06 18:23:12 -0800971 box;
972
Simon Hunt7cd48f32014-11-09 23:42:50 -0800973 // provide ref to element from backing data....
974 d.el = node;
975
Simon Hunt99c13842014-11-06 18:23:12 -0800976 node.append('rect')
977 .attr({
978 'rx': 5,
979 'ry': 5
980 });
981
982 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -0800983 .text(label)
Simon Hunt99c13842014-11-06 18:23:12 -0800984 .attr('dy', '1.1em');
985
986 box = adjustRectToFitText(node);
987
988 node.select('rect')
989 .attr(box);
990
991 if (icon) {
992 var cfg = config.icons;
993 node.append('svg:image')
994 .attr({
995 x: box.x + config.icons.xoff,
996 y: box.y + config.icons.yoff,
997 width: cfg.w,
998 height: cfg.h,
999 'xlink:href': icon
1000 });
1001 }
1002
1003 // debug function to show the modelled x,y coordinates of nodes...
1004 if (debug('showNodeXY')) {
1005 node.select('rect').attr('fill-opacity', 0.5);
1006 node.append('circle')
1007 .attr({
1008 class: 'debug',
1009 cx: 0,
1010 cy: 0,
1011 r: '3px'
1012 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001013 }
1014 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08001015
Simon Hunt56d51852014-11-09 13:03:35 -08001016 // augment host nodes...
1017 entering.filter('.host').each(function (d) {
1018 var node = d3.select(this),
Simon Hunt56d51852014-11-09 13:03:35 -08001019 box;
1020
Simon Hunt7cd48f32014-11-09 23:42:50 -08001021 // provide ref to element from backing data....
1022 d.el = node;
1023
Simon Hunt56d51852014-11-09 13:03:35 -08001024 node.append('circle')
1025 .attr('r', 8); // TODO: define host circle radius
1026
Simon Hunt56d51852014-11-09 13:03:35 -08001027 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001028 .text(hostLabel)
1029 .attr('dy', '1.3em')
1030 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08001031
1032 // debug function to show the modelled x,y coordinates of nodes...
1033 if (debug('showNodeXY')) {
1034 node.select('circle').attr('fill-opacity', 0.5);
1035 node.append('circle')
1036 .attr({
1037 class: 'debug',
1038 cx: 0,
1039 cy: 0,
1040 r: '3px'
1041 });
1042 }
1043 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001044
Simon Hunt99c13842014-11-06 18:23:12 -08001045 // operate on both existing and new nodes, if necessary
1046 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -08001047
Simon Hunt99c13842014-11-06 18:23:12 -08001048 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08001049 // Note that the node is removed after 2 seconds.
1050 // Sub element animations should be shorter than 2 seconds.
1051 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08001052 .transition()
1053 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08001054 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08001055 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08001056
1057 // host node exits....
1058 exiting.filter('.host').each(function (d) {
1059 var node = d3.select(this);
1060
1061 node.select('text')
1062 .style('opacity', 0.5)
1063 .transition()
1064 .duration(1000)
1065 .style('opacity', 0);
1066 // note, leave <g>.remove to remove this element
1067
1068 node.select('circle')
1069 .style('stroke-fill', '#555')
1070 .style('fill', '#888')
1071 .style('opacity', 0.5)
1072 .transition()
1073 .duration(1500)
1074 .attr('r', 0);
1075 // note, leave <g>.remove to remove this element
1076
1077 });
1078
1079 // TODO: device node exits
Simon Huntc7ee0662014-11-05 16:44:37 -08001080 }
1081
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001082 function find(id, array) {
1083 for (var idx = 0, n = array.length; idx < n; idx++) {
1084 if (array[idx].id === id) {
1085 return idx;
1086 }
1087 }
1088 return -1;
1089 }
1090
1091 function removeLinkElement(linkData) {
1092 // remove from lookup cache
1093 delete network.lookup[linkData.id];
1094 // remove from links array
1095 var idx = find(linkData.id, network.links);
Simon Huntfc274c92014-11-11 11:05:46 -08001096 network.links.splice(idx, 1);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001097 // remove from SVG
1098 updateLinks();
Simon Hunt44031102014-11-11 13:20:36 -08001099 network.force.resume();
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001100 }
Simon Huntc7ee0662014-11-05 16:44:37 -08001101
Simon Hunt44031102014-11-11 13:20:36 -08001102 function removeHostElement(hostData) {
1103 // first, remove associated hostLink...
1104 removeLinkElement(hostData.linkData);
1105
1106 // remove from lookup cache
1107 delete network.lookup[hostData.id];
1108 // remove from nodes array
1109 var idx = find(hostData.id, network.nodes);
1110 network.nodes.splice(idx, 1);
1111 // remove from SVG
1112 updateNodes();
1113 network.force.resume();
1114 }
1115
1116
Simon Huntc7ee0662014-11-05 16:44:37 -08001117 function tick() {
1118 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001119 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -08001120 });
1121
1122 link.attr({
1123 x1: function (d) { return d.source.x; },
1124 y1: function (d) { return d.source.y; },
1125 x2: function (d) { return d.target.x; },
1126 y2: function (d) { return d.target.y; }
1127 });
1128 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08001129
1130 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001131 // Web-Socket for live data
1132
1133 function webSockUrl() {
1134 return document.location.toString()
1135 .replace(/\#.*/, '')
1136 .replace('http://', 'ws://')
1137 .replace('https://', 'wss://')
1138 .replace('index2.html', config.webSockUrl);
1139 }
1140
1141 webSock = {
1142 ws : null,
1143
1144 connect : function() {
1145 webSock.ws = new WebSocket(webSockUrl());
1146
1147 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08001148 noWebSock(false);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001149 };
1150
1151 webSock.ws.onmessage = function(m) {
1152 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08001153 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08001154 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001155 }
1156 };
1157
1158 webSock.ws.onclose = function(m) {
1159 webSock.ws = null;
Simon Hunt0c6d4192014-11-12 12:07:10 -08001160 noWebSock(true);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001161 };
1162 },
1163
1164 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001165 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001166 webSock._send(text);
1167 }
1168 },
1169
1170 _send : function(message) {
1171 if (webSock.ws) {
1172 webSock.ws.send(message);
Simon Hunta255a2c2014-11-13 22:29:35 -08001173 } else if (config.useLiveData) {
Simon Hunt56d51852014-11-09 13:03:35 -08001174 network.view.alert('no web socket open\n\n' + message);
Simon Hunta255a2c2014-11-13 22:29:35 -08001175 } else {
1176 console.log('WS Send: ' + JSON.stringify(message));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001177 }
1178 }
1179
1180 };
1181
Simon Hunt0c6d4192014-11-12 12:07:10 -08001182 function noWebSock(b) {
1183 mask.style('display',b ? 'block' : 'none');
1184 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001185
Simon Hunt61d04042014-11-11 17:27:16 -08001186 // TODO: use cache of pending messages (key = sid) to reconcile responses
1187
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001188 function sendMessage(evType, payload) {
1189 var toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08001190 event: evType,
1191 sid: ++sid,
1192 payload: payload
1193 },
1194 asText = JSON.stringify(toSend);
1195 wsTraceTx(asText);
1196 webSock.send(asText);
1197 }
1198
1199 function wsTraceTx(msg) {
1200 wsTrace('tx', msg);
1201 }
1202 function wsTraceRx(msg) {
1203 wsTrace('rx', msg);
1204 }
1205 function wsTrace(rxtx, msg) {
Simon Huntbb282f52014-11-10 11:08:19 -08001206 console.log('[' + rxtx + '] ' + msg);
1207 // TODO: integrate with trace view
1208 //if (trace) {
1209 // trace.output(rxtx, msg);
1210 //}
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001211 }
1212
1213
1214 // ==============================
1215 // Selection stuff
1216
1217 function selectObject(obj, el) {
1218 var n,
Simon Hunt01095ff2014-11-13 16:37:29 -08001219 srcEv = d3.event.sourceEvent,
1220 meta = srcEv.metaKey,
1221 shift = srcEv.shiftKey;
1222
Simon Huntdeab4322014-11-13 18:49:07 -08001223 if ((panZoom() && !meta) || (!panZoom() && meta)) {
Simon Hunt01095ff2014-11-13 16:37:29 -08001224 return;
1225 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001226
1227 if (el) {
1228 n = d3.select(el);
1229 } else {
1230 node.each(function(d) {
1231 if (d == obj) {
1232 n = d3.select(el = this);
1233 }
1234 });
1235 }
1236 if (!n) return;
1237
Simon Hunt01095ff2014-11-13 16:37:29 -08001238 if (shift && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001239 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08001240 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001241 return;
1242 }
1243
Simon Hunt01095ff2014-11-13 16:37:29 -08001244 if (!shift) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001245 deselectAll();
1246 }
1247
Simon Huntc31d5692014-11-12 13:27:18 -08001248 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001249 selectOrder.push(obj.id);
1250
1251 n.classed('selected', true);
Simon Hunt61d04042014-11-11 17:27:16 -08001252 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001253 }
1254
1255 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08001256 var obj = selections[id],
1257 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001258 if (obj) {
1259 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08001260 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08001261 idx = $.inArray(id, selectOrder);
1262 if (idx >= 0) {
1263 selectOrder.splice(idx, 1);
1264 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001265 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001266 }
1267
1268 function deselectAll() {
1269 // deselect all nodes in the network...
1270 node.classed('selected', false);
1271 selections = {};
1272 selectOrder = [];
Simon Hunt61d04042014-11-11 17:27:16 -08001273 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001274 }
1275
Simon Hunt61d04042014-11-11 17:27:16 -08001276 // update the state of the detail pane, based on current selections
1277 function updateDetailPane() {
1278 var nSel = selectOrder.length;
1279 if (!nSel) {
1280 detailPane.hide();
Simon Huntd72bc702014-11-13 18:38:04 -08001281 showTrafficAction(); // sends cancelTraffic event
Simon Hunt61d04042014-11-11 17:27:16 -08001282 } else if (nSel === 1) {
1283 singleSelect();
1284 } else {
1285 multiSelect();
1286 }
1287 }
1288
1289 function singleSelect() {
1290 requestDetails();
Simon Huntb53e0682014-11-12 13:32:01 -08001291 // NOTE: detail pane will be shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08001292 }
1293
1294 function multiSelect() {
Simon Huntb53e0682014-11-12 13:32:01 -08001295 populateMultiSelect();
Simon Huntb53e0682014-11-12 13:32:01 -08001296 }
1297
1298 function addSep(tbody) {
1299 var tr = tbody.append('tr');
1300 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
1301 }
1302
1303 function addProp(tbody, label, value) {
1304 var tr = tbody.append('tr');
1305
1306 tr.append('td')
1307 .attr('class', 'label')
1308 .text(label + ' :');
1309
1310 tr.append('td')
1311 .attr('class', 'value')
1312 .text(value);
1313 }
1314
1315 function populateMultiSelect() {
1316 detailPane.empty();
1317
1318 var title = detailPane.append("h2"),
1319 table = detailPane.append("table"),
1320 tbody = table.append("tbody");
1321
1322 title.text('Multi-Select...');
1323
1324 selectOrder.forEach(function (d, i) {
1325 addProp(tbody, i+1, d);
1326 });
Simon Huntd72bc702014-11-13 18:38:04 -08001327
1328 addMultiSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08001329 }
1330
1331 function populateDetails(data) {
1332 detailPane.empty();
1333
1334 var title = detailPane.append("h2"),
1335 table = detailPane.append("table"),
1336 tbody = table.append("tbody");
1337
1338 $('<img src="img/' + data.type + '.png">').appendTo(title);
1339 $('<span>').attr('class', 'icon').text(data.id).appendTo(title);
1340
1341 data.propOrder.forEach(function(p) {
1342 if (p === '-') {
1343 addSep(tbody);
1344 } else {
1345 addProp(tbody, p, data.props[p]);
1346 }
1347 });
Simon Huntd72bc702014-11-13 18:38:04 -08001348
1349 addSingleSelectActions();
Simon Hunt61d04042014-11-11 17:27:16 -08001350 }
1351
Simon Huntd72bc702014-11-13 18:38:04 -08001352 function addSingleSelectActions() {
1353 detailPane.append('hr');
1354 // always want to allow 'show traffic'
1355 addAction('Show Traffic', showTrafficAction);
1356 }
1357
1358 function addMultiSelectActions() {
1359 detailPane.append('hr');
1360 // always want to allow 'show traffic'
1361 addAction('Show Traffic', showTrafficAction);
1362 // if exactly two hosts are selected, also want 'add host intent'
1363 if (nSel() === 2 && allSelectionsClass('host')) {
1364 addAction('Add Host Intent', addIntentAction);
1365 }
1366 }
1367
1368 function addAction(text, cb) {
1369 detailPane.append('div')
1370 .classed('actionBtn', true)
1371 .text(text)
1372 .on('click', cb);
1373 }
1374
1375
Paul Greysonfcba0e82014-11-13 10:21:16 -08001376 function zoomPan(scale, translate) {
1377 zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
1378 // keep the map lines constant width while zooming
1379 bgImg.style("stroke-width", 2.0 / scale + "px");
1380 }
1381
1382 function resetZoomPan() {
1383 zoomPan(1, [0,0]);
1384 zoom.scale(1).translate([0,0]);
1385 }
1386
1387 function setupZoomPan() {
1388 function zoomed() {
Simon Huntdeab4322014-11-13 18:49:07 -08001389 if (!panZoom() ^ !d3.event.sourceEvent.metaKey) {
Paul Greysonfcba0e82014-11-13 10:21:16 -08001390 zoomPan(d3.event.scale, d3.event.translate);
1391 }
1392 }
1393
1394 zoom = d3.behavior.zoom()
1395 .translate([0, 0])
1396 .scale(1)
1397 .scaleExtent([1, 8])
1398 .on("zoom", zoomed);
1399
1400 svg.call(zoom);
1401 }
1402
Simon Hunt61d04042014-11-11 17:27:16 -08001403 // ==============================
1404 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08001405
1406 function prepareScenario(view, ctx, dbg) {
1407 var sc = scenario,
1408 urlSc = sc.evDir + ctx + sc.evScenario;
1409
1410 if (!ctx) {
1411 view.alert("No scenario specified (null ctx)");
1412 return;
1413 }
1414
1415 sc.view = view;
1416 sc.ctx = ctx;
1417 sc.debug = dbg;
1418 sc.evNumber = 0;
1419
1420 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08001421 var p = data && data.params || {},
1422 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08001423 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08001424
Simon Hunt56d51852014-11-09 13:03:35 -08001425 if (err) {
1426 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
1427 } else {
1428 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08001429 if (desc) {
1430 intro += '\n\n ' + desc.join('\n ');
1431 }
1432 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08001433 }
1434 });
1435
1436 }
1437
Simon Hunt01095ff2014-11-13 16:37:29 -08001438 // ==============================
1439 // Toggle Buttons in masthead
Simon Hunt0c6d4192014-11-12 12:07:10 -08001440
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001441 // TODO: toggle button (and other widgets in the masthead) should be provided
1442 // by the framework; not generated by the view.
1443
Simon Hunt01095ff2014-11-13 16:37:29 -08001444 var showTrafficOnHover,
Simon Huntdeab4322014-11-13 18:49:07 -08001445 doPanZoom;
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001446
1447 function addButtonBar(view) {
1448 var bb = d3.select('#mast')
1449 .append('span').classed('right', true).attr('id', 'bb');
1450
Simon Huntdeab4322014-11-13 18:49:07 -08001451 doPanZoom = bb.append('span')
Simon Hunt01095ff2014-11-13 16:37:29 -08001452 .classed('btn', true)
Simon Huntdeab4322014-11-13 18:49:07 -08001453 .text('Pan/Zoom')
1454 .on('click', togglePanZoom);
Simon Hunt01095ff2014-11-13 16:37:29 -08001455
1456 showTrafficOnHover = bb.append('span')
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001457 .classed('btn', true)
1458 .text('Show traffic on hover')
Simon Hunt01095ff2014-11-13 16:37:29 -08001459 .on('click', toggleTrafficHover);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001460 }
1461
Simon Hunt01095ff2014-11-13 16:37:29 -08001462 function toggleTrafficHover() {
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001463 showTrafficOnHover.classed('active', !trafficHover());
1464 }
1465
1466 function trafficHover() {
1467 return showTrafficOnHover.classed('active');
1468 }
1469
Simon Huntdeab4322014-11-13 18:49:07 -08001470 function togglePanZoom() {
1471 doPanZoom.classed('active', !panZoom());
Simon Hunt01095ff2014-11-13 16:37:29 -08001472 }
1473
Simon Huntdeab4322014-11-13 18:49:07 -08001474 function panZoom() {
1475 return doPanZoom.classed('active');
Simon Hunt01095ff2014-11-13 16:37:29 -08001476 }
1477
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001478 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08001479 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08001480
Simon Huntf67722a2014-11-10 09:32:06 -08001481 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08001482 var w = view.width(),
1483 h = view.height(),
Simon Huntc7ee0662014-11-05 16:44:37 -08001484 fcfg = config.force,
1485 fpad = fcfg.pad,
1486 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08001487
Simon Huntbb282f52014-11-10 11:08:19 -08001488 // TODO: set trace api
1489 //trace = onos.exported.webSockTrace;
1490
Simon Hunt142d0032014-11-04 20:13:09 -08001491 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001492 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
1493 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08001494 setSize(svg, view);
1495
Paul Greysonfcba0e82014-11-13 10:21:16 -08001496 zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
1497
1498 setupZoomPan();
1499
Simon Hunt1a9eff92014-11-07 11:06:34 -08001500 // add blue glow filter to svg layer
Paul Greysonfcba0e82014-11-13 10:21:16 -08001501 d3u.appendGlow(zoomPanContainer);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001502
Simon Huntc7ee0662014-11-05 16:44:37 -08001503 // group for the topology
Paul Greysonfcba0e82014-11-13 10:21:16 -08001504 topoG = zoomPanContainer.append('g')
Simon Huntd3b7d512014-11-12 15:48:41 -08001505 .attr('id', 'topo-G')
Simon Huntc7ee0662014-11-05 16:44:37 -08001506 .attr('transform', fcfg.translate());
1507
1508 // subgroups for links and nodes
1509 linkG = topoG.append('g').attr('id', 'links');
1510 nodeG = topoG.append('g').attr('id', 'nodes');
1511
1512 // selection of nodes and links
1513 link = linkG.selectAll('.link');
1514 node = nodeG.selectAll('.node');
1515
Simon Hunt7cd48f32014-11-09 23:42:50 -08001516 function chrg(d) {
1517 return fcfg.charge[d.class] || -12000;
1518 }
Simon Hunt99c13842014-11-06 18:23:12 -08001519 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001520 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08001521 }
1522 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001523 // 0.0 - 1.0
1524 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08001525 }
1526
Simon Hunt1a9eff92014-11-07 11:06:34 -08001527 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001528 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001529 }
1530
1531 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08001532 // once we've finished moving, pin the node in position
1533 d.fixed = true;
1534 d3.select(self).classed('fixed', true);
1535 if (config.useLiveData) {
Simon Hunt902c9922014-11-11 11:59:31 -08001536 sendUpdateMeta(d);
Simon Hunta255a2c2014-11-13 22:29:35 -08001537 } else {
1538 console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
Simon Hunt1a9eff92014-11-07 11:06:34 -08001539 }
1540 }
1541
Simon Hunt902c9922014-11-11 11:59:31 -08001542 function sendUpdateMeta(d) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001543 sendMessage('updateMeta', {
1544 id: d.id,
1545 'class': d.class,
Simon Hunt902c9922014-11-11 11:59:31 -08001546 'memento': {
Simon Hunt01095ff2014-11-13 16:37:29 -08001547 x: d.x,
1548 y: d.y
Simon Hunt902c9922014-11-11 11:59:31 -08001549 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001550 });
1551 }
1552
Simon Huntc7ee0662014-11-05 16:44:37 -08001553 // set up the force layout
1554 network.force = d3.layout.force()
1555 .size(forceDim)
1556 .nodes(network.nodes)
1557 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001558 .gravity(0.4)
1559 .friction(0.7)
1560 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08001561 .linkDistance(ldist)
1562 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08001563 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08001564
Simon Hunt01095ff2014-11-13 16:37:29 -08001565 network.drag = d3u.createDragBehavior(network.force,
Simon Huntdeab4322014-11-13 18:49:07 -08001566 selectCb, atDragEnd, panZoom);
Simon Hunt0c6d4192014-11-12 12:07:10 -08001567
1568 // create mask layer for when we lose connection to server.
1569 mask = view.$div.append('div').attr('id','topo-mask');
1570 para(mask, 'Oops!');
1571 para(mask, 'Web-socket connection to server closed...');
1572 para(mask, 'Try refreshing the page.');
Simon Hunt1a9eff92014-11-07 11:06:34 -08001573 }
Simon Hunt195cb382014-11-03 17:50:51 -08001574
Simon Hunt01095ff2014-11-13 16:37:29 -08001575 function para(sel, text) {
1576 sel.append('p').text(text);
1577 }
1578
1579
Simon Hunt56d51852014-11-09 13:03:35 -08001580 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08001581 // resize, in case the window was resized while we were not loaded
1582 resize(view, ctx, flags);
1583
Simon Hunt99c13842014-11-06 18:23:12 -08001584 // cache the view token, so network topo functions can access it
1585 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08001586 config.useLiveData = !flags.local;
1587
1588 if (!config.useLiveData) {
1589 prepareScenario(view, ctx, flags.debug);
1590 }
Simon Hunt99c13842014-11-06 18:23:12 -08001591
1592 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -08001593 view.setRadio(btnSet);
1594 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -08001595
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001596 // patch in our "button bar" for now
1597 // TODO: implement a more official frameworky way of doing this..
1598 addButtonBar(view);
1599
Simon Huntd3b7d512014-11-12 15:48:41 -08001600 // Load map data asynchronously; complete startup after that..
1601 loadGeoJsonData();
Simon Hunta255a2c2014-11-13 22:29:35 -08001602
1603 // start the and timer
1604 var dashIdx = 0;
1605 antTimer = setInterval(function () {
1606 // TODO: figure out how to choose Src-->Dst and Dst-->Src, per link
1607 dashIdx = dashIdx === 0 ? 14 : dashIdx - 2;
1608 d3.selectAll('.animated').style('stroke-dashoffset', dashIdx);
1609 }, 35);
1610 }
1611
1612 function unload(view, ctx, flags) {
1613 if (antTimer) {
1614 clearInterval(antTimer);
1615 antTimer = null;
1616 }
Simon Huntd3b7d512014-11-12 15:48:41 -08001617 }
1618
1619 // TODO: move these to config/state portion of script
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001620 var geoJsonUrl = 'json/map/continental_us.json', // TODO: Paul
Simon Huntd3b7d512014-11-12 15:48:41 -08001621 geoJson;
1622
1623 function loadGeoJsonData() {
1624 d3.json(geoJsonUrl, function (err, data) {
1625 if (err) {
1626 // fall back to USA map background
1627 loadStaticMap();
1628 } else {
1629 geoJson = data;
1630 loadGeoMap();
1631 }
1632
1633 // finally, connect to the server...
1634 if (config.useLiveData) {
1635 webSock.connect();
1636 }
1637 });
1638 }
1639
1640 function showBg() {
1641 return config.options.showBackground ? 'visible' : 'hidden';
1642 }
1643
1644 function loadStaticMap() {
1645 fnTrace('loadStaticMap', config.backgroundUrl);
1646 var w = network.view.width(),
1647 h = network.view.height();
1648
1649 // load the background image
1650 bgImg = svg.insert('svg:image', '#topo-G')
1651 .attr({
1652 id: 'topo-bg',
1653 width: w,
1654 height: h,
1655 'xlink:href': config.backgroundUrl
1656 })
1657 .style({
1658 visibility: showBg()
1659 });
1660 }
1661
1662 function loadGeoMap() {
1663 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08001664
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001665 // extracts the topojson data into geocoordinate-based geometry
1666 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08001667
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001668 // see: http://bl.ocks.org/mbostock/4707858
1669 geoMapProjection = d3.geo.mercator();
1670 var path = d3.geo.path().projection(geoMapProjection);
Simon Huntd3b7d512014-11-12 15:48:41 -08001671
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001672 geoMapProjection
1673 .scale(1)
1674 .translate([0, 0]);
Simon Huntd3b7d512014-11-12 15:48:41 -08001675
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001676 // [[x1,y1],[x2,y2]]
1677 var b = path.bounds(topoData);
Paul Greysonfcba0e82014-11-13 10:21:16 -08001678 // size map to 95% of minimum dimension to fill space
1679 var s = .95 / Math.min((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001680 var t = [(config.logicalSize - s * (b[1][0] + b[0][0])) / 2, (config.logicalSize - s * (b[1][1] + b[0][1])) / 2];
Simon Huntd3b7d512014-11-12 15:48:41 -08001681
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001682 geoMapProjection
1683 .scale(s)
1684 .translate(t);
1685
Paul Greysonfcba0e82014-11-13 10:21:16 -08001686 bgImg = zoomPanContainer.insert("g", '#topo-G');
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001687 bgImg.attr('id', 'map').selectAll('path')
1688 .data(topoData.features)
1689 .enter()
1690 .append('path')
1691 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08001692 }
1693
Simon Huntf67722a2014-11-10 09:32:06 -08001694 function resize(view, ctx, flags) {
Simon Hunt934c3ce2014-11-05 11:45:07 -08001695 setSize(svg, view);
Simon Hunt142d0032014-11-04 20:13:09 -08001696 }
1697
1698
1699 // ==============================
1700 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08001701
Simon Hunt25248912014-11-04 11:25:48 -08001702 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08001703 preload: preload,
1704 load: load,
Simon Hunta255a2c2014-11-13 22:29:35 -08001705 unload: unload,
Simon Hunt142d0032014-11-04 20:13:09 -08001706 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -08001707 });
1708
Simon Hunt61d04042014-11-11 17:27:16 -08001709 detailPane = onos.ui.addFloatingPanel('topo-detail');
1710
Simon Hunt195cb382014-11-03 17:50:51 -08001711}(ONOS));