blob: f6a8456e4099ef1268a95c807d23bc8815fddca1 [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 Hunt195cb382014-11-03 17:50:51 -080033 debugOn: false,
34 debug: {
Simon Hunt99c13842014-11-06 18:23:12 -080035 showNodeXY: true,
36 showKeyHandler: false
Simon Hunt195cb382014-11-03 17:50:51 -080037 },
38 options: {
39 layering: true,
40 collisionPrevention: true,
Simon Hunt142d0032014-11-04 20:13:09 -080041 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080042 },
43 backgroundUrl: 'img/us-map.png',
Thomas Vachuska7d638d32014-11-07 10:24:43 -080044 webSockUrl: 'ws/topology',
Simon Hunt195cb382014-11-03 17:50:51 -080045 data: {
46 live: {
47 jsonUrl: 'rs/topology/graph',
48 detailPrefix: 'rs/topology/graph/',
49 detailSuffix: ''
50 },
51 fake: {
52 jsonUrl: 'json/network2.json',
53 detailPrefix: 'json/',
54 detailSuffix: '.json'
55 }
56 },
Simon Hunt99c13842014-11-06 18:23:12 -080057 labels: {
58 imgPad: 16,
59 padLR: 4,
60 padTB: 3,
61 marginLR: 3,
62 marginTB: 2,
63 port: {
64 gap: 3,
65 width: 18,
66 height: 14
67 }
68 },
Simon Hunt1a9eff92014-11-07 11:06:34 -080069 topo: {
70 linkInColor: '#66f',
71 linkInWidth: 14
72 },
Simon Hunt99c13842014-11-06 18:23:12 -080073 icons: {
74 w: 28,
75 h: 28,
76 xoff: -12,
77 yoff: -8
78 },
Simon Hunt195cb382014-11-03 17:50:51 -080079 iconUrl: {
80 device: 'img/device.png',
81 host: 'img/host.png',
82 pkt: 'img/pkt.png',
83 opt: 'img/opt.png'
84 },
Simon Hunt195cb382014-11-03 17:50:51 -080085 force: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080086 note_for_links: 'link.type is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080087 linkDistance: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080088 direct: 100,
89 optical: 120,
Simon Huntf67722a2014-11-10 09:32:06 -080090 hostLink: 5
Simon Huntc7ee0662014-11-05 16:44:37 -080091 },
92 linkStrength: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080093 direct: 1.0,
94 optical: 1.0,
95 hostLink: 1.0
Simon Huntc7ee0662014-11-05 16:44:37 -080096 },
Simon Hunt7cd48f32014-11-09 23:42:50 -080097 note_for_nodes: 'node.class is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080098 charge: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080099 device: -8000,
100 host: -300
Simon Huntc7ee0662014-11-05 16:44:37 -0800101 },
102 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -0800103 translate: function() {
104 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -0800105 config.force.pad + ',' +
106 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -0800107 }
Simon Hunt142d0032014-11-04 20:13:09 -0800108 }
Simon Hunt195cb382014-11-03 17:50:51 -0800109 };
110
Simon Hunt142d0032014-11-04 20:13:09 -0800111 // radio buttons
112 var btnSet = [
Simon Hunt934c3ce2014-11-05 11:45:07 -0800113 { text: 'All Layers', cb: showAllLayers },
114 { text: 'Packet Only', cb: showPacketLayer },
115 { text: 'Optical Only', cb: showOpticalLayer }
116 ];
117
118 // key bindings
119 var keyDispatch = {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800120 M: testMe, // TODO: remove (testing only)
Simon Hunt50128c02014-11-08 13:36:15 -0800121 S: injectStartupEvents, // TODO: remove (testing only)
122 space: injectTestEvent, // TODO: remove (testing only)
Simon Hunt99c13842014-11-06 18:23:12 -0800123
Thomas Vachuska65368e32014-11-08 16:10:20 -0800124 B: toggleBg, // TODO: do we really need this?
Simon Hunt934c3ce2014-11-05 11:45:07 -0800125 L: cycleLabels,
126 P: togglePorts,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800127 U: unpin,
128
129 X: requestPath
Simon Hunt934c3ce2014-11-05 11:45:07 -0800130 };
Simon Hunt142d0032014-11-04 20:13:09 -0800131
Simon Hunt195cb382014-11-03 17:50:51 -0800132 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800133 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800134 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800135 nodes: [],
136 links: [],
137 lookup: {}
138 },
Simon Hunt56d51852014-11-09 13:03:35 -0800139 scenario = {
140 evDir: 'json/ev/',
141 evScenario: '/scenario.json',
142 evPrefix: '/ev_',
143 evOnos: '_onos.json',
144 evUi: '_ui.json',
145 ctx: null,
146 params: {},
147 evNumber: 0,
148 view: null,
149 debug: false
150 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800151 webSock,
Simon Hunt56d51852014-11-09 13:03:35 -0800152 deviceLabelIndex = 0,
153 hostLabelIndex = 0,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800154
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800155 selectOrder = [],
156 selections = {},
157
Simon Hunt195cb382014-11-03 17:50:51 -0800158 highlighted = null,
159 hovered = null,
160 viewMode = 'showAll',
161 portLabelsOn = false;
162
Simon Hunt934c3ce2014-11-05 11:45:07 -0800163 // D3 selections
164 var svg,
165 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800166 topoG,
167 nodeG,
168 linkG,
169 node,
170 link;
Simon Hunt195cb382014-11-03 17:50:51 -0800171
Simon Hunt142d0032014-11-04 20:13:09 -0800172 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800173 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800174
Simon Hunt99c13842014-11-06 18:23:12 -0800175 function note(label, msg) {
176 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800177 }
178
Simon Hunt99c13842014-11-06 18:23:12 -0800179 function debug(what) {
180 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800181 }
182
Simon Hunt99c13842014-11-06 18:23:12 -0800183
Simon Hunt934c3ce2014-11-05 11:45:07 -0800184 // ==============================
185 // Key Callbacks
186
Simon Hunt99c13842014-11-06 18:23:12 -0800187 function testMe(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800188 view.alert('test');
Simon Hunt99c13842014-11-06 18:23:12 -0800189 }
190
Simon Hunt56d51852014-11-09 13:03:35 -0800191 function abortIfLive() {
Simon Hunt50128c02014-11-08 13:36:15 -0800192 if (config.useLiveData) {
Simon Hunt56d51852014-11-09 13:03:35 -0800193 scenario.view.alert("Sorry, currently using live data..");
194 return true;
Simon Hunt50128c02014-11-08 13:36:15 -0800195 }
Simon Hunt56d51852014-11-09 13:03:35 -0800196 return false;
197 }
Simon Hunt50128c02014-11-08 13:36:15 -0800198
Simon Hunt56d51852014-11-09 13:03:35 -0800199 function testDebug(msg) {
200 if (scenario.debug) {
201 scenario.view.alert(msg);
202 }
203 }
Simon Hunt99c13842014-11-06 18:23:12 -0800204
Simon Hunt56d51852014-11-09 13:03:35 -0800205 function injectTestEvent(view) {
206 if (abortIfLive()) { return; }
207 var sc = scenario,
208 evn = ++sc.evNumber,
209 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
210 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800211 uiUrl = pfx + sc.evUi,
212 stack = [
213 { url: onosUrl, cb: handleServerEvent },
214 { url: uiUrl, cb: handleUiEvent }
215 ];
216 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800217 }
218
Simon Hunt7cd48f32014-11-09 23:42:50 -0800219 function recurseFetchEvent(stack, evn) {
220 var v = scenario.view,
221 frame;
222 if (stack.length === 0) {
223 v.alert('Error:\n\nNo event #' + evn + ' found.');
224 return;
225 }
226 frame = stack.shift();
227
228 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800229 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800230 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800231 // if we didn't find the data, try the next stack frame
232 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800233 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800234 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800235 }
Simon Hunt99c13842014-11-06 18:23:12 -0800236 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800237 testDebug('loaded: ' + frame.url);
238 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800239 }
240 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800241
Simon Hunt56d51852014-11-09 13:03:35 -0800242 }
Simon Hunt50128c02014-11-08 13:36:15 -0800243
Simon Hunt56d51852014-11-09 13:03:35 -0800244 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800245 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
246 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800247 }
248
249 function injectStartupEvents(view) {
250 var last = scenario.params.lastAuto || 0;
251 if (abortIfLive()) { return; }
252
253 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800254 injectTestEvent(view);
255 }
256 }
257
Simon Hunt934c3ce2014-11-05 11:45:07 -0800258 function toggleBg() {
259 var vis = bgImg.style('visibility');
260 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
261 }
262
Simon Huntbb282f52014-11-10 11:08:19 -0800263 function updateDeviceLabel(d) {
264 var label = niceLabel(deviceLabel(d)),
265 node = d.el,
266 box;
267
268 node.select('text')
269 .text(label)
270 .style('opacity', 0)
271 .transition()
272 .style('opacity', 1);
273
274 box = adjustRectToFitText(node);
275
276 node.select('rect')
277 .transition()
278 .attr(box);
279
280 node.select('image')
281 .transition()
282 .attr('x', box.x + config.icons.xoff)
283 .attr('y', box.y + config.icons.yoff);
284 }
285
286 function updateHostLabel(d) {
287 var label = hostLabel(d),
288 host = d.el;
289
290 host.select('text').text(label);
291 }
292
Simon Hunt99c13842014-11-06 18:23:12 -0800293 function cycleLabels() {
Simon Huntbb282f52014-11-10 11:08:19 -0800294 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1)
295 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800296
Simon Hunt99c13842014-11-06 18:23:12 -0800297 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800298 if (d.class === 'device') {
299 updateDeviceLabel(d);
300 }
Simon Hunt99c13842014-11-06 18:23:12 -0800301 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800302 }
303
304 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800305 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800306 }
307
308 function unpin(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800309 view.alert('unpin() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800310 }
311
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800312 function requestPath(view) {
313 var payload = {
314 one: selections[selectOrder[0]].obj.id,
315 two: selections[selectOrder[1]].obj.id
316 }
317 sendMessage('requestPath', payload);
318 }
319
Simon Hunt934c3ce2014-11-05 11:45:07 -0800320 // ==============================
321 // Radio Button Callbacks
322
Simon Hunt195cb382014-11-03 17:50:51 -0800323 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800324// network.node.classed('inactive', false);
325// network.link.classed('inactive', false);
326// d3.selectAll('svg .port').classed('inactive', false);
327// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800328 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800329 network.view.alert('showAllLayers() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800330 }
331
332 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800333 showAllLayers();
334 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800335 network.view.alert('showPacketLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800336 }
337
338 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800339 showAllLayers();
340 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800341 network.view.alert('showOpticalLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800342 }
343
Simon Hunt142d0032014-11-04 20:13:09 -0800344 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800345 // Private functions
346
Simon Hunt99c13842014-11-06 18:23:12 -0800347 function safeId(s) {
348 return s.replace(/[^a-z0-9]/gi, '-');
349 }
350
Simon Huntc7ee0662014-11-05 16:44:37 -0800351 // set the size of the given element to that of the view (reduced if padded)
352 function setSize(el, view, pad) {
353 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800354 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800355 width: view.width() - padding,
356 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800357 });
358 }
359
Simon Hunt934c3ce2014-11-05 11:45:07 -0800360
Simon Hunt99c13842014-11-06 18:23:12 -0800361 // ==============================
362 // Event handlers for server-pushed events
363
Simon Huntbb282f52014-11-10 11:08:19 -0800364 function logicError(msg) {
365 // TODO, report logic error to server, via websock, so it can be logged
366 network.view.alert('Logic Error:\n\n' + msg);
367 }
368
Simon Hunt99c13842014-11-06 18:23:12 -0800369 var eventDispatch = {
370 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800371 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800372 addHost: addHost,
Simon Huntbb282f52014-11-10 11:08:19 -0800373 updateDevice: updateDevice,
374 updateLink: stillToImplement,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800375 updateHost: updateHost,
Simon Huntbb282f52014-11-10 11:08:19 -0800376 removeDevice: stillToImplement,
377 removeLink: stillToImplement,
Simon Hunt56d51852014-11-09 13:03:35 -0800378 removeHost: stillToImplement,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800379 showPath: showPath
Simon Hunt99c13842014-11-06 18:23:12 -0800380 };
381
382 function addDevice(data) {
383 var device = data.payload,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800384 nodeData = createDeviceNode(device);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800385 network.nodes.push(nodeData);
386 network.lookup[nodeData.id] = nodeData;
Simon Hunt99c13842014-11-06 18:23:12 -0800387 updateNodes();
388 network.force.start();
389 }
390
Simon Hunt99c13842014-11-06 18:23:12 -0800391 function addLink(data) {
392 var link = data.payload,
393 lnk = createLink(link);
Simon Hunt99c13842014-11-06 18:23:12 -0800394 if (lnk) {
Simon Hunt99c13842014-11-06 18:23:12 -0800395 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800396 network.lookup[lnk.id] = lnk;
Simon Hunt99c13842014-11-06 18:23:12 -0800397 updateLinks();
398 network.force.start();
399 }
400 }
401
Simon Hunt56d51852014-11-09 13:03:35 -0800402 function addHost(data) {
403 var host = data.payload,
404 node = createHostNode(host),
405 lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800406 network.nodes.push(node);
407 network.lookup[host.id] = node;
408 updateNodes();
409
410 lnk = createHostLink(host);
411 if (lnk) {
412 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800413 network.lookup[host.ingress] = lnk;
414 network.lookup[host.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800415 updateLinks();
416 }
417 network.force.start();
418 }
419
Simon Huntbb282f52014-11-10 11:08:19 -0800420 function updateDevice(data) {
421 var device = data.payload,
422 id = device.id,
423 nodeData = network.lookup[id];
424 if (nodeData) {
425 $.extend(nodeData, device);
426 updateDeviceState(nodeData);
427 } else {
428 logicError('updateDevice lookup fail. ID = "' + id + '"');
429 }
430 }
431
Simon Hunt7cd48f32014-11-09 23:42:50 -0800432 function updateHost(data) {
433 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800434 id = host.id,
435 hostData = network.lookup[id];
436 if (hostData) {
437 $.extend(hostData, host);
438 updateHostState(hostData);
439 } else {
440 logicError('updateHost lookup fail. ID = "' + id + '"');
441 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800442 }
443
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800444 function showPath(data) {
Thomas Vachuska4830d392014-11-09 17:09:56 -0800445 var links = data.payload.links,
446 s = [ data.event + "\n" + links.length ];
447 links.forEach(function (d, i) {
448 s.push(d);
449 });
450 network.view.alert(s.join('\n'));
451
452 links.forEach(function (d, i) {
453 var link = network.lookup[d];
454 if (link) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800455 link.el.classed('showPath', true);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800456 }
457 });
458
459 // TODO: add selection-highlite lines to links
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800460 }
461
Simon Hunt56d51852014-11-09 13:03:35 -0800462 // ...............................
463
464 function stillToImplement(data) {
465 var p = data.payload;
466 note(data.event, p.id);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800467 network.view.alert('Not yet implemented: "' + data.event + '"');
Simon Hunt56d51852014-11-09 13:03:35 -0800468 }
Simon Hunt99c13842014-11-06 18:23:12 -0800469
470 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800471 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800472 }
473
474 function handleServerEvent(data) {
475 var fn = eventDispatch[data.event] || unknownEvent;
476 fn(data);
477 }
478
479 // ==============================
480 // force layout modification functions
481
482 function translate(x, y) {
483 return 'translate(' + x + ',' + y + ')';
484 }
485
Simon Hunt56d51852014-11-09 13:03:35 -0800486 function createHostLink(host) {
487 var src = host.id,
488 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800489 id = host.ingress,
Simon Hunt56d51852014-11-09 13:03:35 -0800490 srcNode = network.lookup[src],
491 dstNode = network.lookup[dst],
492 lnk;
493
494 if (!dstNode) {
Simon Huntbb282f52014-11-10 11:08:19 -0800495 logicError('switch not on map for link\n\n' +
496 'src = ' + src + '\ndst = ' + dst);
Simon Hunt56d51852014-11-09 13:03:35 -0800497 return null;
498 }
499
Simon Hunt7cd48f32014-11-09 23:42:50 -0800500 // Compose link ...
Simon Hunt56d51852014-11-09 13:03:35 -0800501 lnk = {
Thomas Vachuska4830d392014-11-09 17:09:56 -0800502 id: id,
Simon Hunt56d51852014-11-09 13:03:35 -0800503 source: srcNode,
504 target: dstNode,
505 class: 'link',
Simon Hunt7cd48f32014-11-09 23:42:50 -0800506 type: 'hostLink',
Simon Hunt56d51852014-11-09 13:03:35 -0800507 svgClass: 'link hostLink',
508 x1: srcNode.x,
509 y1: srcNode.y,
510 x2: dstNode.x,
511 y2: dstNode.y,
512 width: 1
Simon Hunt7cd48f32014-11-09 23:42:50 -0800513 }
Simon Hunt56d51852014-11-09 13:03:35 -0800514 return lnk;
515 }
516
Simon Hunt99c13842014-11-06 18:23:12 -0800517 function createLink(link) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800518 // start with the link object as is
519 var lnk = link,
520 type = link.type,
Simon Hunt99c13842014-11-06 18:23:12 -0800521 src = link.src,
522 dst = link.dst,
523 w = link.linkWidth,
524 srcNode = network.lookup[src],
Simon Hunt7cd48f32014-11-09 23:42:50 -0800525 dstNode = network.lookup[dst];
Simon Hunt99c13842014-11-06 18:23:12 -0800526
527 if (!(srcNode && dstNode)) {
Simon Huntbb282f52014-11-10 11:08:19 -0800528 logicError('nodes not on map for link\n\n' +
529 'src = ' + src + '\ndst = ' + dst);
Simon Hunt99c13842014-11-06 18:23:12 -0800530 return null;
531 }
532
Simon Hunt7cd48f32014-11-09 23:42:50 -0800533 // Augment as needed...
534 $.extend(lnk, {
535 source: srcNode,
536 target: dstNode,
537 class: 'link',
538 svgClass: type ? 'link ' + type : 'link',
539 x1: srcNode.x,
540 y1: srcNode.y,
541 x2: dstNode.x,
542 y2: dstNode.y,
543 width: w
544 });
Simon Hunt99c13842014-11-06 18:23:12 -0800545 return lnk;
546 }
547
Simon Hunt1a9eff92014-11-07 11:06:34 -0800548 function linkWidth(w) {
549 // w is number of links between nodes. Scale appropriately.
Simon Hunt50128c02014-11-08 13:36:15 -0800550 // TODO: use a d3.scale (linear, log, ... ?)
Simon Hunt1a9eff92014-11-07 11:06:34 -0800551 return w * 1.2;
552 }
553
Simon Hunt99c13842014-11-06 18:23:12 -0800554 function updateLinks() {
555 link = linkG.selectAll('.link')
556 .data(network.links, function (d) { return d.id; });
557
558 // operate on existing links, if necessary
559 // link .foo() .bar() ...
560
561 // operate on entering links:
562 var entering = link.enter()
563 .append('line')
564 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800565 class: function (d) { return d.svgClass; },
566 x1: function (d) { return d.x1; },
567 y1: function (d) { return d.y1; },
568 x2: function (d) { return d.x2; },
569 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800570 stroke: config.topo.linkInColor,
571 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -0800572 })
573 .transition().duration(1000)
574 .attr({
Simon Hunt1a9eff92014-11-07 11:06:34 -0800575 'stroke-width': function (d) { return linkWidth(d.width); },
Simon Hunt99c13842014-11-06 18:23:12 -0800576 stroke: '#666' // TODO: remove explicit stroke, rather...
577 });
578
579 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -0800580 entering.each(function (d) {
581 var link = d3.select(this);
582 // provide ref to element selection from backing data....
583 d.el = link;
Simon Hunt99c13842014-11-06 18:23:12 -0800584
Simon Hunt7cd48f32014-11-09 23:42:50 -0800585 // TODO: add src/dst port labels etc.
586 });
Thomas Vachuska4830d392014-11-09 17:09:56 -0800587
588 // operate on both existing and new links, if necessary
589 //link .foo() .bar() ...
590
591 // operate on exiting links:
592 // TODO: figure out how to remove the node 'g' AND its children
593 link.exit()
594 .transition()
595 .duration(750)
596 .attr({
597 opacity: 0
598 })
599 .remove();
Simon Hunt99c13842014-11-06 18:23:12 -0800600 }
601
602 function createDeviceNode(device) {
603 // start with the object as is
604 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -0800605 type = device.type,
606 svgCls = type ? 'node device ' + type : 'node device';
Simon Hunt99c13842014-11-06 18:23:12 -0800607
608 // Augment as needed...
609 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -0800610 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -0800611 positionNode(node);
612
613 // cache label array length
614 network.deviceLabelCount = device.labels.length;
Simon Hunt99c13842014-11-06 18:23:12 -0800615 return node;
616 }
617
Simon Hunt56d51852014-11-09 13:03:35 -0800618 function createHostNode(host) {
619 // start with the object as is
620 var node = host;
621
622 // Augment as needed...
623 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -0800624 if (!node.type) {
625 // TODO: perhaps type would be: {phone, tablet, laptop, endstation} ?
626 node.type = 'endstation';
627 }
Simon Hunt56d51852014-11-09 13:03:35 -0800628 node.svgClass = 'node host';
629 // TODO: consider placing near its switch, if [x,y] not defined
630 positionNode(node);
631
632 // cache label array length
633 network.hostLabelCount = host.labels.length;
Simon Hunt56d51852014-11-09 13:03:35 -0800634 return node;
635 }
636
Simon Hunt99c13842014-11-06 18:23:12 -0800637 function positionNode(node) {
638 var meta = node.metaUi,
639 x = 0,
640 y = 0;
641
642 if (meta) {
643 x = meta.x;
644 y = meta.y;
645 }
646 if (x && y) {
647 node.fixed = true;
648 }
649 node.x = x || network.view.width() / 2;
650 node.y = y || network.view.height() / 2;
651 }
652
653
654 function iconUrl(d) {
655 return 'img/' + d.type + '.png';
656 }
657
658 // returns the newly computed bounding box of the rectangle
659 function adjustRectToFitText(n) {
660 var text = n.select('text'),
661 box = text.node().getBBox(),
662 lab = config.labels;
663
664 text.attr('text-anchor', 'middle')
665 .attr('y', '-0.8em')
666 .attr('x', lab.imgPad/2);
667
668 // translate the bbox so that it is centered on [x,y]
669 box.x = -box.width / 2;
670 box.y = -box.height / 2;
671
672 // add padding
673 box.x -= (lab.padLR + lab.imgPad/2);
674 box.width += lab.padLR * 2 + lab.imgPad;
675 box.y -= lab.padTB;
676 box.height += lab.padTB * 2;
677
678 return box;
679 }
680
Simon Hunt1a9eff92014-11-07 11:06:34 -0800681 function mkSvgClass(d) {
682 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
683 }
684
Simon Hunt7cd48f32014-11-09 23:42:50 -0800685 function hostLabel(d) {
686 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
687 return d.labels[idx];
688 }
689 function deviceLabel(d) {
690 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
691 return d.labels[idx];
692 }
693 function niceLabel(label) {
694 return (label && label.trim()) ? label : '.';
695 }
696
Simon Huntbb282f52014-11-10 11:08:19 -0800697 function updateDeviceState(nodeData) {
698 nodeData.el.classed('online', nodeData.online);
699 updateDeviceLabel(nodeData);
700 // TODO: review what else might need to be updated
701 }
702
703 function updateHostState(hostData) {
704 updateHostLabel(hostData);
705 // TODO: review what else might need to be updated
706 }
707
708
Simon Hunt99c13842014-11-06 18:23:12 -0800709 function updateNodes() {
710 node = nodeG.selectAll('.node')
711 .data(network.nodes, function (d) { return d.id; });
712
713 // operate on existing nodes, if necessary
Simon Hunt7cd48f32014-11-09 23:42:50 -0800714 // update host labels
Simon Hunt99c13842014-11-06 18:23:12 -0800715 //node .foo() .bar() ...
716
717 // operate on entering nodes:
718 var entering = node.enter()
719 .append('g')
720 .attr({
721 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800722 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800723 transform: function (d) { return translate(d.x, d.y); },
724 opacity: 0
725 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800726 .call(network.drag)
Simon Hunt99c13842014-11-06 18:23:12 -0800727 //.on('mouseover', function (d) {})
728 //.on('mouseover', function (d) {})
729 .transition()
730 .attr('opacity', 1);
731
732 // augment device nodes...
733 entering.filter('.device').each(function (d) {
734 var node = d3.select(this),
735 icon = iconUrl(d),
Simon Hunt7cd48f32014-11-09 23:42:50 -0800736 label = niceLabel(deviceLabel(d)),
Simon Hunt99c13842014-11-06 18:23:12 -0800737 box;
738
Simon Hunt7cd48f32014-11-09 23:42:50 -0800739 // provide ref to element from backing data....
740 d.el = node;
741
Simon Hunt99c13842014-11-06 18:23:12 -0800742 node.append('rect')
743 .attr({
744 'rx': 5,
745 'ry': 5
746 });
747
748 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -0800749 .text(label)
Simon Hunt99c13842014-11-06 18:23:12 -0800750 .attr('dy', '1.1em');
751
752 box = adjustRectToFitText(node);
753
754 node.select('rect')
755 .attr(box);
756
757 if (icon) {
758 var cfg = config.icons;
759 node.append('svg:image')
760 .attr({
761 x: box.x + config.icons.xoff,
762 y: box.y + config.icons.yoff,
763 width: cfg.w,
764 height: cfg.h,
765 'xlink:href': icon
766 });
767 }
768
769 // debug function to show the modelled x,y coordinates of nodes...
770 if (debug('showNodeXY')) {
771 node.select('rect').attr('fill-opacity', 0.5);
772 node.append('circle')
773 .attr({
774 class: 'debug',
775 cx: 0,
776 cy: 0,
777 r: '3px'
778 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800779 }
780 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800781
Simon Hunt56d51852014-11-09 13:03:35 -0800782 // augment host nodes...
783 entering.filter('.host').each(function (d) {
784 var node = d3.select(this),
Simon Hunt56d51852014-11-09 13:03:35 -0800785 box;
786
Simon Hunt7cd48f32014-11-09 23:42:50 -0800787 // provide ref to element from backing data....
788 d.el = node;
789
Simon Hunt56d51852014-11-09 13:03:35 -0800790 node.append('circle')
791 .attr('r', 8); // TODO: define host circle radius
792
793 // TODO: are we attaching labels to hosts?
794 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -0800795 .text(hostLabel)
796 .attr('dy', '1.3em')
797 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -0800798
799 // debug function to show the modelled x,y coordinates of nodes...
800 if (debug('showNodeXY')) {
801 node.select('circle').attr('fill-opacity', 0.5);
802 node.append('circle')
803 .attr({
804 class: 'debug',
805 cx: 0,
806 cy: 0,
807 r: '3px'
808 });
809 }
810 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800811
Simon Hunt99c13842014-11-06 18:23:12 -0800812 // operate on both existing and new nodes, if necessary
813 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -0800814
Simon Hunt99c13842014-11-06 18:23:12 -0800815 // operate on exiting nodes:
816 // TODO: figure out how to remove the node 'g' AND its children
817 node.exit()
818 .transition()
819 .duration(750)
820 .attr({
821 opacity: 0,
822 cx: 0,
823 cy: 0,
824 r: 0
825 })
826 .remove();
Simon Huntc7ee0662014-11-05 16:44:37 -0800827 }
828
829
830 function tick() {
831 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800832 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -0800833 });
834
835 link.attr({
836 x1: function (d) { return d.source.x; },
837 y1: function (d) { return d.source.y; },
838 x2: function (d) { return d.target.x; },
839 y2: function (d) { return d.target.y; }
840 });
841 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800842
843 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800844 // Web-Socket for live data
845
846 function webSockUrl() {
847 return document.location.toString()
848 .replace(/\#.*/, '')
849 .replace('http://', 'ws://')
850 .replace('https://', 'wss://')
851 .replace('index2.html', config.webSockUrl);
852 }
853
854 webSock = {
855 ws : null,
856
857 connect : function() {
858 webSock.ws = new WebSocket(webSockUrl());
859
860 webSock.ws.onopen = function() {
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800861 };
862
863 webSock.ws.onmessage = function(m) {
864 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800865 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800866 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800867 }
868 };
869
870 webSock.ws.onclose = function(m) {
871 webSock.ws = null;
872 };
873 },
874
875 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800876 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800877 webSock._send(text);
878 }
879 },
880
881 _send : function(message) {
882 if (webSock.ws) {
883 webSock.ws.send(message);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800884 } else {
Simon Hunt56d51852014-11-09 13:03:35 -0800885 network.view.alert('no web socket open\n\n' + message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800886 }
887 }
888
889 };
890
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800891 var sid = 0;
892
893 function sendMessage(evType, payload) {
894 var toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -0800895 event: evType,
896 sid: ++sid,
897 payload: payload
898 },
899 asText = JSON.stringify(toSend);
900 wsTraceTx(asText);
901 webSock.send(asText);
902 }
903
904 function wsTraceTx(msg) {
905 wsTrace('tx', msg);
906 }
907 function wsTraceRx(msg) {
908 wsTrace('rx', msg);
909 }
910 function wsTrace(rxtx, msg) {
911
912 console.log('[' + rxtx + '] ' + msg);
913 // TODO: integrate with trace view
914 //if (trace) {
915 // trace.output(rxtx, msg);
916 //}
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800917 }
918
919
920 // ==============================
921 // Selection stuff
922
923 function selectObject(obj, el) {
924 var n,
925 meta = d3.event.sourceEvent.metaKey;
926
927 if (el) {
928 n = d3.select(el);
929 } else {
930 node.each(function(d) {
931 if (d == obj) {
932 n = d3.select(el = this);
933 }
934 });
935 }
936 if (!n) return;
937
938 if (meta && n.classed('selected')) {
939 deselectObject(obj.id);
940 //flyinPane(null);
941 return;
942 }
943
944 if (!meta) {
945 deselectAll();
946 }
947
Simon Hunt5f36d342014-11-08 21:33:14 -0800948 selections[obj.id] = { obj: obj, el : el};
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800949 selectOrder.push(obj.id);
950
951 n.classed('selected', true);
952 //flyinPane(obj);
953 }
954
955 function deselectObject(id) {
956 var obj = selections[id];
957 if (obj) {
958 d3.select(obj.el).classed('selected', false);
959 selections[id] = null;
960 // TODO: use splice to remove element
961 }
962 //flyinPane(null);
963 }
964
965 function deselectAll() {
966 // deselect all nodes in the network...
967 node.classed('selected', false);
968 selections = {};
969 selectOrder = [];
970 //flyinPane(null);
971 }
972
Simon Hunt56d51852014-11-09 13:03:35 -0800973 // TODO: this click handler does not get unloaded when the view does
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800974 $('#view').on('click', function(e) {
975 if (!$(e.target).closest('.node').length) {
976 if (!e.metaKey) {
977 deselectAll();
978 }
979 }
980 });
981
Simon Hunt56d51852014-11-09 13:03:35 -0800982
983 function prepareScenario(view, ctx, dbg) {
984 var sc = scenario,
985 urlSc = sc.evDir + ctx + sc.evScenario;
986
987 if (!ctx) {
988 view.alert("No scenario specified (null ctx)");
989 return;
990 }
991
992 sc.view = view;
993 sc.ctx = ctx;
994 sc.debug = dbg;
995 sc.evNumber = 0;
996
997 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800998 var p = data && data.params || {},
999 desc = data && data.description || null,
1000 intro;
1001
Simon Hunt56d51852014-11-09 13:03:35 -08001002 if (err) {
1003 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
1004 } else {
1005 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08001006 intro = "Scenario loaded: " + ctx + '\n\n' + data.title;
1007 if (desc) {
1008 intro += '\n\n ' + desc.join('\n ');
1009 }
1010 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08001011 }
1012 });
1013
1014 }
1015
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001016 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08001017 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08001018
Simon Huntf67722a2014-11-10 09:32:06 -08001019 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08001020 var w = view.width(),
1021 h = view.height(),
1022 idBg = view.uid('bg'),
Simon Huntc7ee0662014-11-05 16:44:37 -08001023 showBg = config.options.showBackground ? 'visible' : 'hidden',
1024 fcfg = config.force,
1025 fpad = fcfg.pad,
1026 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08001027
Simon Huntbb282f52014-11-10 11:08:19 -08001028 // TODO: set trace api
1029 //trace = onos.exported.webSockTrace;
1030
Simon Hunt142d0032014-11-04 20:13:09 -08001031 // NOTE: view.$div is a D3 selection of the view's div
1032 svg = view.$div.append('svg');
Simon Hunt934c3ce2014-11-05 11:45:07 -08001033 setSize(svg, view);
1034
Simon Hunt1a9eff92014-11-07 11:06:34 -08001035 // add blue glow filter to svg layer
1036 d3u.appendGlow(svg);
1037
Simon Hunt142d0032014-11-04 20:13:09 -08001038 // load the background image
1039 bgImg = svg.append('svg:image')
Simon Hunt195cb382014-11-03 17:50:51 -08001040 .attr({
Simon Hunt142d0032014-11-04 20:13:09 -08001041 id: idBg,
1042 width: w,
1043 height: h,
Simon Hunt195cb382014-11-03 17:50:51 -08001044 'xlink:href': config.backgroundUrl
1045 })
Simon Hunt142d0032014-11-04 20:13:09 -08001046 .style({
1047 visibility: showBg
Simon Hunt195cb382014-11-03 17:50:51 -08001048 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001049
1050 // group for the topology
1051 topoG = svg.append('g')
1052 .attr('transform', fcfg.translate());
1053
1054 // subgroups for links and nodes
1055 linkG = topoG.append('g').attr('id', 'links');
1056 nodeG = topoG.append('g').attr('id', 'nodes');
1057
1058 // selection of nodes and links
1059 link = linkG.selectAll('.link');
1060 node = nodeG.selectAll('.node');
1061
Simon Hunt7cd48f32014-11-09 23:42:50 -08001062 function chrg(d) {
1063 return fcfg.charge[d.class] || -12000;
1064 }
Simon Hunt99c13842014-11-06 18:23:12 -08001065 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001066 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08001067 }
1068 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001069 // 0.0 - 1.0
1070 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08001071 }
1072
Simon Hunt1a9eff92014-11-07 11:06:34 -08001073 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001074 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001075 }
1076
1077 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08001078 // once we've finished moving, pin the node in position
1079 d.fixed = true;
1080 d3.select(self).classed('fixed', true);
1081 if (config.useLiveData) {
1082 tellServerCoords(d);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001083 }
1084 }
1085
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001086 function tellServerCoords(d) {
1087 sendMessage('updateMeta', {
1088 id: d.id,
1089 'class': d.class,
Simon Hunt5f36d342014-11-08 21:33:14 -08001090 x: Math.floor(d.x),
1091 y: Math.floor(d.y)
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001092 });
1093 }
1094
Simon Huntc7ee0662014-11-05 16:44:37 -08001095 // set up the force layout
1096 network.force = d3.layout.force()
1097 .size(forceDim)
1098 .nodes(network.nodes)
1099 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001100 .gravity(0.4)
1101 .friction(0.7)
1102 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08001103 .linkDistance(ldist)
1104 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08001105 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08001106
Simon Hunt1a9eff92014-11-07 11:06:34 -08001107 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
1108 }
Simon Hunt195cb382014-11-03 17:50:51 -08001109
Simon Hunt56d51852014-11-09 13:03:35 -08001110 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08001111 // resize, in case the window was resized while we were not loaded
1112 resize(view, ctx, flags);
1113
Simon Hunt99c13842014-11-06 18:23:12 -08001114 // cache the view token, so network topo functions can access it
1115 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08001116 config.useLiveData = !flags.local;
1117
1118 if (!config.useLiveData) {
1119 prepareScenario(view, ctx, flags.debug);
1120 }
Simon Hunt99c13842014-11-06 18:23:12 -08001121
1122 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -08001123 view.setRadio(btnSet);
1124 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -08001125
Simon Hunt50128c02014-11-08 13:36:15 -08001126 if (config.useLiveData) {
1127 webSock.connect();
1128 }
Simon Hunt195cb382014-11-03 17:50:51 -08001129 }
1130
Simon Huntf67722a2014-11-10 09:32:06 -08001131 function resize(view, ctx, flags) {
Simon Hunt934c3ce2014-11-05 11:45:07 -08001132 setSize(svg, view);
1133 setSize(bgImg, view);
Simon Hunt99c13842014-11-06 18:23:12 -08001134
1135 // TODO: hook to recompute layout, perhaps? work with zoom/pan code
1136 // adjust force layout size
Simon Hunt142d0032014-11-04 20:13:09 -08001137 }
1138
1139
1140 // ==============================
1141 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08001142
Simon Hunt25248912014-11-04 11:25:48 -08001143 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08001144 preload: preload,
1145 load: load,
1146 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -08001147 });
1148
Simon Hunt195cb382014-11-03 17:50:51 -08001149}(ONOS));