blob: fff829f476b07bbb5363f4edb2d5fbef26de7375 [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
27 var d3u = onos.lib.d3util;
28
Simon Hunt195cb382014-11-03 17:50:51 -080029 // configuration data
30 var config = {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080031 useLiveData: true,
Simon Hunt195cb382014-11-03 17:50:51 -080032 debugOn: false,
33 debug: {
Simon Hunt99c13842014-11-06 18:23:12 -080034 showNodeXY: true,
35 showKeyHandler: false
Simon Hunt195cb382014-11-03 17:50:51 -080036 },
37 options: {
38 layering: true,
39 collisionPrevention: true,
Simon Hunt142d0032014-11-04 20:13:09 -080040 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080041 },
42 backgroundUrl: 'img/us-map.png',
Thomas Vachuska7d638d32014-11-07 10:24:43 -080043 webSockUrl: 'ws/topology',
Simon Hunt195cb382014-11-03 17:50:51 -080044 data: {
45 live: {
46 jsonUrl: 'rs/topology/graph',
47 detailPrefix: 'rs/topology/graph/',
48 detailSuffix: ''
49 },
50 fake: {
51 jsonUrl: 'json/network2.json',
52 detailPrefix: 'json/',
53 detailSuffix: '.json'
54 }
55 },
Simon Hunt99c13842014-11-06 18:23:12 -080056 labels: {
57 imgPad: 16,
58 padLR: 4,
59 padTB: 3,
60 marginLR: 3,
61 marginTB: 2,
62 port: {
63 gap: 3,
64 width: 18,
65 height: 14
66 }
67 },
Simon Hunt1a9eff92014-11-07 11:06:34 -080068 topo: {
69 linkInColor: '#66f',
70 linkInWidth: 14
71 },
Simon Hunt99c13842014-11-06 18:23:12 -080072 icons: {
73 w: 28,
74 h: 28,
75 xoff: -12,
76 yoff: -8
77 },
Simon Hunt195cb382014-11-03 17:50:51 -080078 iconUrl: {
79 device: 'img/device.png',
80 host: 'img/host.png',
81 pkt: 'img/pkt.png',
82 opt: 'img/opt.png'
83 },
Simon Hunt195cb382014-11-03 17:50:51 -080084 force: {
Simon Huntc7ee0662014-11-05 16:44:37 -080085 note: 'node.class or link.class is used to differentiate',
86 linkDistance: {
87 infra: 200,
88 host: 40
89 },
90 linkStrength: {
91 infra: 1.0,
92 host: 1.0
93 },
94 charge: {
95 device: -400,
96 host: -100
97 },
98 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -080099 translate: function() {
100 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -0800101 config.force.pad + ',' +
102 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -0800103 }
Simon Hunt142d0032014-11-04 20:13:09 -0800104 }
Simon Hunt195cb382014-11-03 17:50:51 -0800105 };
106
Simon Hunt142d0032014-11-04 20:13:09 -0800107 // radio buttons
108 var btnSet = [
Simon Hunt934c3ce2014-11-05 11:45:07 -0800109 { text: 'All Layers', cb: showAllLayers },
110 { text: 'Packet Only', cb: showPacketLayer },
111 { text: 'Optical Only', cb: showOpticalLayer }
112 ];
113
114 // key bindings
115 var keyDispatch = {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800116 M: testMe, // TODO: remove (testing only)
Simon Hunt50128c02014-11-08 13:36:15 -0800117 S: injectStartupEvents, // TODO: remove (testing only)
118 space: injectTestEvent, // TODO: remove (testing only)
Simon Hunt99c13842014-11-06 18:23:12 -0800119
Thomas Vachuska65368e32014-11-08 16:10:20 -0800120 B: toggleBg, // TODO: do we really need this?
Simon Hunt934c3ce2014-11-05 11:45:07 -0800121 L: cycleLabels,
122 P: togglePorts,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800123 U: unpin,
124
125 X: requestPath
Simon Hunt934c3ce2014-11-05 11:45:07 -0800126 };
Simon Hunt142d0032014-11-04 20:13:09 -0800127
Simon Hunt195cb382014-11-03 17:50:51 -0800128 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800129 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800130 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800131 nodes: [],
132 links: [],
133 lookup: {}
134 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800135 webSock,
Simon Hunt99c13842014-11-06 18:23:12 -0800136 labelIdx = 0,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800137
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800138 selectOrder = [],
139 selections = {},
140
Simon Hunt195cb382014-11-03 17:50:51 -0800141 highlighted = null,
142 hovered = null,
143 viewMode = 'showAll',
144 portLabelsOn = false;
145
Simon Hunt934c3ce2014-11-05 11:45:07 -0800146 // D3 selections
147 var svg,
148 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800149 topoG,
150 nodeG,
151 linkG,
152 node,
153 link;
Simon Hunt195cb382014-11-03 17:50:51 -0800154
Simon Hunt142d0032014-11-04 20:13:09 -0800155 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800156 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800157
Simon Hunt99c13842014-11-06 18:23:12 -0800158 var eventPrefix = 'json/eventTest_',
Simon Hunt1a9eff92014-11-07 11:06:34 -0800159 eventNumber = 0,
160 alertNumber = 0;
Simon Hunt195cb382014-11-03 17:50:51 -0800161
Simon Hunt99c13842014-11-06 18:23:12 -0800162 function note(label, msg) {
163 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800164 }
165
Simon Hunt99c13842014-11-06 18:23:12 -0800166 function debug(what) {
167 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800168 }
169
Simon Hunt99c13842014-11-06 18:23:12 -0800170
Simon Hunt934c3ce2014-11-05 11:45:07 -0800171 // ==============================
172 // Key Callbacks
173
Simon Hunt99c13842014-11-06 18:23:12 -0800174 function testMe(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800175 view.alert('test');
Simon Hunt99c13842014-11-06 18:23:12 -0800176 }
177
178 function injectTestEvent(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800179 if (config.useLiveData) {
180 view.alert("Sorry, currently using live data..");
181 return;
182 }
183
Simon Hunt99c13842014-11-06 18:23:12 -0800184 eventNumber++;
185 var eventUrl = eventPrefix + eventNumber + '.json';
186
Simon Hunt99c13842014-11-06 18:23:12 -0800187 d3.json(eventUrl, function(err, data) {
188 if (err) {
189 view.dataLoadError(err, eventUrl);
190 } else {
191 handleServerEvent(data);
192 }
193 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800194 }
195
Simon Hunt1a9eff92014-11-07 11:06:34 -0800196 function injectStartupEvents(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800197 if (config.useLiveData) {
198 view.alert("Sorry, currently using live data..");
199 return;
200 }
201
Simon Hunt1a9eff92014-11-07 11:06:34 -0800202 var lastStartupEvent = 32;
203 while (eventNumber < lastStartupEvent) {
204 injectTestEvent(view);
205 }
206 }
207
Simon Hunt934c3ce2014-11-05 11:45:07 -0800208 function toggleBg() {
209 var vis = bgImg.style('visibility');
210 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
211 }
212
Simon Hunt99c13842014-11-06 18:23:12 -0800213 function cycleLabels() {
214 labelIdx = (labelIdx === network.deviceLabelCount - 1) ? 0 : labelIdx + 1;
215 network.nodes.forEach(function (d) {
216 var idx = (labelIdx < d.labels.length) ? labelIdx : 0,
217 node = d3.select('#' + safeId(d.id)),
218 box;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800219
Simon Hunt99c13842014-11-06 18:23:12 -0800220 node.select('text')
221 .text(d.labels[idx])
222 .style('opacity', 0)
223 .transition()
224 .style('opacity', 1);
225
226 box = adjustRectToFitText(node);
227
228 node.select('rect')
229 .transition()
230 .attr(box);
231
232 node.select('image')
233 .transition()
234 .attr('x', box.x + config.icons.xoff)
235 .attr('y', box.y + config.icons.yoff);
236 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800237 }
238
239 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800240 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800241 }
242
243 function unpin(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800244 view.alert('unpin() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800245 }
246
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800247 function requestPath(view) {
248 var payload = {
249 one: selections[selectOrder[0]].obj.id,
250 two: selections[selectOrder[1]].obj.id
251 }
252 sendMessage('requestPath', payload);
253 }
254
Simon Hunt934c3ce2014-11-05 11:45:07 -0800255 // ==============================
256 // Radio Button Callbacks
257
Simon Hunt195cb382014-11-03 17:50:51 -0800258 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800259// network.node.classed('inactive', false);
260// network.link.classed('inactive', false);
261// d3.selectAll('svg .port').classed('inactive', false);
262// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800263 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800264 network.view.alert('showAllLayers() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800265 }
266
267 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800268 showAllLayers();
269 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800270 network.view.alert('showPacketLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800271 }
272
273 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800274 showAllLayers();
275 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800276 network.view.alert('showOpticalLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800277 }
278
Simon Hunt142d0032014-11-04 20:13:09 -0800279 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800280 // Private functions
281
Simon Hunt99c13842014-11-06 18:23:12 -0800282 function safeId(s) {
283 return s.replace(/[^a-z0-9]/gi, '-');
284 }
285
Simon Huntc7ee0662014-11-05 16:44:37 -0800286 // set the size of the given element to that of the view (reduced if padded)
287 function setSize(el, view, pad) {
288 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800289 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800290 width: view.width() - padding,
291 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800292 });
293 }
294
Simon Hunt934c3ce2014-11-05 11:45:07 -0800295
Simon Hunt99c13842014-11-06 18:23:12 -0800296 // ==============================
297 // Event handlers for server-pushed events
298
299 var eventDispatch = {
300 addDevice: addDevice,
301 updateDevice: updateDevice,
302 removeDevice: removeDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800303 addLink: addLink,
304 showPath: showPath
Simon Hunt99c13842014-11-06 18:23:12 -0800305 };
306
307 function addDevice(data) {
308 var device = data.payload,
309 node = createDeviceNode(device);
310 note('addDevice', device.id);
311
312 network.nodes.push(node);
313 network.lookup[node.id] = node;
314 updateNodes();
315 network.force.start();
316 }
317
318 function updateDevice(data) {
319 var device = data.payload;
320 note('updateDevice', device.id);
321
322 }
323
324 function removeDevice(data) {
325 var device = data.payload;
326 note('removeDevice', device.id);
327
328 }
329
330 function addLink(data) {
331 var link = data.payload,
332 lnk = createLink(link);
333
334 if (lnk) {
335 note('addLink', lnk.id);
336
337 network.links.push(lnk);
338 updateLinks();
339 network.force.start();
340 }
341 }
342
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800343 function showPath(data) {
344 network.view.alert(data.event + "\n" + data.payload.links.length);
345 }
346
Simon Hunt99c13842014-11-06 18:23:12 -0800347 // ....
348
349 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800350 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800351 }
352
353 function handleServerEvent(data) {
354 var fn = eventDispatch[data.event] || unknownEvent;
355 fn(data);
356 }
357
358 // ==============================
359 // force layout modification functions
360
361 function translate(x, y) {
362 return 'translate(' + x + ',' + y + ')';
363 }
364
365 function createLink(link) {
366 var type = link.type,
367 src = link.src,
368 dst = link.dst,
369 w = link.linkWidth,
370 srcNode = network.lookup[src],
371 dstNode = network.lookup[dst],
372 lnk;
373
374 if (!(srcNode && dstNode)) {
Simon Hunt50128c02014-11-08 13:36:15 -0800375 // TODO: send warning message back to server on websocket
376 network.view.alert('nodes not on map for link\n\n' +
377 'src = ' + src + '\ndst = ' + dst);
Simon Hunt99c13842014-11-06 18:23:12 -0800378 return null;
379 }
380
381 lnk = {
382 id: safeId(src) + '~' + safeId(dst),
383 source: srcNode,
384 target: dstNode,
385 class: 'link',
386 svgClass: type ? 'link ' + type : 'link',
387 x1: srcNode.x,
388 y1: srcNode.y,
389 x2: dstNode.x,
390 y2: dstNode.y,
391 width: w
392 };
393 return lnk;
394 }
395
Simon Hunt1a9eff92014-11-07 11:06:34 -0800396 function linkWidth(w) {
397 // w is number of links between nodes. Scale appropriately.
Simon Hunt50128c02014-11-08 13:36:15 -0800398 // TODO: use a d3.scale (linear, log, ... ?)
Simon Hunt1a9eff92014-11-07 11:06:34 -0800399 return w * 1.2;
400 }
401
Simon Hunt99c13842014-11-06 18:23:12 -0800402 function updateLinks() {
403 link = linkG.selectAll('.link')
404 .data(network.links, function (d) { return d.id; });
405
406 // operate on existing links, if necessary
407 // link .foo() .bar() ...
408
409 // operate on entering links:
410 var entering = link.enter()
411 .append('line')
412 .attr({
413 id: function (d) { return d.id; },
414 class: function (d) { return d.svgClass; },
415 x1: function (d) { return d.x1; },
416 y1: function (d) { return d.y1; },
417 x2: function (d) { return d.x2; },
418 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800419 stroke: config.topo.linkInColor,
420 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -0800421 })
422 .transition().duration(1000)
423 .attr({
Simon Hunt1a9eff92014-11-07 11:06:34 -0800424 'stroke-width': function (d) { return linkWidth(d.width); },
Simon Hunt99c13842014-11-06 18:23:12 -0800425 stroke: '#666' // TODO: remove explicit stroke, rather...
426 });
427
428 // augment links
429 // TODO: add src/dst port labels etc.
430
431 }
432
433 function createDeviceNode(device) {
434 // start with the object as is
435 var node = device,
436 type = device.type;
437
438 // Augment as needed...
439 node.class = 'device';
440 node.svgClass = type ? 'node device ' + type : 'node device';
441 positionNode(node);
442
443 // cache label array length
444 network.deviceLabelCount = device.labels.length;
445
446 return node;
447 }
448
449 function positionNode(node) {
450 var meta = node.metaUi,
451 x = 0,
452 y = 0;
453
454 if (meta) {
455 x = meta.x;
456 y = meta.y;
457 }
458 if (x && y) {
459 node.fixed = true;
460 }
461 node.x = x || network.view.width() / 2;
462 node.y = y || network.view.height() / 2;
463 }
464
465
466 function iconUrl(d) {
467 return 'img/' + d.type + '.png';
468 }
469
470 // returns the newly computed bounding box of the rectangle
471 function adjustRectToFitText(n) {
472 var text = n.select('text'),
473 box = text.node().getBBox(),
474 lab = config.labels;
475
476 text.attr('text-anchor', 'middle')
477 .attr('y', '-0.8em')
478 .attr('x', lab.imgPad/2);
479
480 // translate the bbox so that it is centered on [x,y]
481 box.x = -box.width / 2;
482 box.y = -box.height / 2;
483
484 // add padding
485 box.x -= (lab.padLR + lab.imgPad/2);
486 box.width += lab.padLR * 2 + lab.imgPad;
487 box.y -= lab.padTB;
488 box.height += lab.padTB * 2;
489
490 return box;
491 }
492
Simon Hunt1a9eff92014-11-07 11:06:34 -0800493 function mkSvgClass(d) {
494 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
495 }
496
Simon Hunt99c13842014-11-06 18:23:12 -0800497 function updateNodes() {
498 node = nodeG.selectAll('.node')
499 .data(network.nodes, function (d) { return d.id; });
500
501 // operate on existing nodes, if necessary
502 //node .foo() .bar() ...
503
504 // operate on entering nodes:
505 var entering = node.enter()
506 .append('g')
507 .attr({
508 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800509 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800510 transform: function (d) { return translate(d.x, d.y); },
511 opacity: 0
512 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800513 .call(network.drag)
Simon Hunt99c13842014-11-06 18:23:12 -0800514 //.on('mouseover', function (d) {})
515 //.on('mouseover', function (d) {})
516 .transition()
517 .attr('opacity', 1);
518
519 // augment device nodes...
520 entering.filter('.device').each(function (d) {
521 var node = d3.select(this),
522 icon = iconUrl(d),
523 idx = (labelIdx < d.labels.length) ? labelIdx : 0,
524 box;
525
526 node.append('rect')
527 .attr({
528 'rx': 5,
529 'ry': 5
530 });
531
532 node.append('text')
533 .text(d.labels[idx])
534 .attr('dy', '1.1em');
535
536 box = adjustRectToFitText(node);
537
538 node.select('rect')
539 .attr(box);
540
541 if (icon) {
542 var cfg = config.icons;
543 node.append('svg:image')
544 .attr({
545 x: box.x + config.icons.xoff,
546 y: box.y + config.icons.yoff,
547 width: cfg.w,
548 height: cfg.h,
549 'xlink:href': icon
550 });
551 }
552
553 // debug function to show the modelled x,y coordinates of nodes...
554 if (debug('showNodeXY')) {
555 node.select('rect').attr('fill-opacity', 0.5);
556 node.append('circle')
557 .attr({
558 class: 'debug',
559 cx: 0,
560 cy: 0,
561 r: '3px'
562 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800563 }
564 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800565
Simon Huntc7ee0662014-11-05 16:44:37 -0800566
Simon Hunt99c13842014-11-06 18:23:12 -0800567 // operate on both existing and new nodes, if necessary
568 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -0800569
Simon Hunt99c13842014-11-06 18:23:12 -0800570 // operate on exiting nodes:
571 // TODO: figure out how to remove the node 'g' AND its children
572 node.exit()
573 .transition()
574 .duration(750)
575 .attr({
576 opacity: 0,
577 cx: 0,
578 cy: 0,
579 r: 0
580 })
581 .remove();
Simon Huntc7ee0662014-11-05 16:44:37 -0800582 }
583
584
585 function tick() {
586 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800587 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -0800588 });
589
590 link.attr({
591 x1: function (d) { return d.source.x; },
592 y1: function (d) { return d.source.y; },
593 x2: function (d) { return d.target.x; },
594 y2: function (d) { return d.target.y; }
595 });
596 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800597
598 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800599 // Web-Socket for live data
600
601 function webSockUrl() {
602 return document.location.toString()
603 .replace(/\#.*/, '')
604 .replace('http://', 'ws://')
605 .replace('https://', 'wss://')
606 .replace('index2.html', config.webSockUrl);
607 }
608
609 webSock = {
610 ws : null,
611
612 connect : function() {
613 webSock.ws = new WebSocket(webSockUrl());
614
615 webSock.ws.onopen = function() {
616 webSock._send("Hi there!");
617 };
618
619 webSock.ws.onmessage = function(m) {
620 if (m.data) {
621 console.log(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800622 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800623 }
624 };
625
626 webSock.ws.onclose = function(m) {
627 webSock.ws = null;
628 };
629 },
630
631 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800632 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800633 webSock._send(text);
634 }
635 },
636
637 _send : function(message) {
638 if (webSock.ws) {
639 webSock.ws.send(message);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800640 } else {
641 network.view.alert('no web socket open');
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800642 }
643 }
644
645 };
646
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800647 var sid = 0;
648
649 function sendMessage(evType, payload) {
650 var toSend = {
651 event: evType,
652 sid: ++sid,
653 payload: payload
654 };
655 webSock.send(JSON.stringify(toSend));
656 }
657
658
659 // ==============================
660 // Selection stuff
661
662 function selectObject(obj, el) {
663 var n,
664 meta = d3.event.sourceEvent.metaKey;
665
666 if (el) {
667 n = d3.select(el);
668 } else {
669 node.each(function(d) {
670 if (d == obj) {
671 n = d3.select(el = this);
672 }
673 });
674 }
675 if (!n) return;
676
677 if (meta && n.classed('selected')) {
678 deselectObject(obj.id);
679 //flyinPane(null);
680 return;
681 }
682
683 if (!meta) {
684 deselectAll();
685 }
686
687 // TODO: allow for mutli selections
688 var selected = {
689 obj : obj,
690 el : el
691 };
692
693 selections[obj.id] = selected;
694 selectOrder.push(obj.id);
695
696 n.classed('selected', true);
697 //flyinPane(obj);
698 }
699
700 function deselectObject(id) {
701 var obj = selections[id];
702 if (obj) {
703 d3.select(obj.el).classed('selected', false);
704 selections[id] = null;
705 // TODO: use splice to remove element
706 }
707 //flyinPane(null);
708 }
709
710 function deselectAll() {
711 // deselect all nodes in the network...
712 node.classed('selected', false);
713 selections = {};
714 selectOrder = [];
715 //flyinPane(null);
716 }
717
718
719 $('#view').on('click', function(e) {
720 if (!$(e.target).closest('.node').length) {
721 if (!e.metaKey) {
722 deselectAll();
723 }
724 }
725 });
726
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800727 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -0800728 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -0800729
Simon Hunt142d0032014-11-04 20:13:09 -0800730 function preload(view, ctx) {
731 var w = view.width(),
732 h = view.height(),
733 idBg = view.uid('bg'),
Simon Huntc7ee0662014-11-05 16:44:37 -0800734 showBg = config.options.showBackground ? 'visible' : 'hidden',
735 fcfg = config.force,
736 fpad = fcfg.pad,
737 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -0800738
Simon Hunt142d0032014-11-04 20:13:09 -0800739 // NOTE: view.$div is a D3 selection of the view's div
740 svg = view.$div.append('svg');
Simon Hunt934c3ce2014-11-05 11:45:07 -0800741 setSize(svg, view);
742
Simon Hunt1a9eff92014-11-07 11:06:34 -0800743 // add blue glow filter to svg layer
744 d3u.appendGlow(svg);
745
Simon Hunt142d0032014-11-04 20:13:09 -0800746 // load the background image
747 bgImg = svg.append('svg:image')
Simon Hunt195cb382014-11-03 17:50:51 -0800748 .attr({
Simon Hunt142d0032014-11-04 20:13:09 -0800749 id: idBg,
750 width: w,
751 height: h,
Simon Hunt195cb382014-11-03 17:50:51 -0800752 'xlink:href': config.backgroundUrl
753 })
Simon Hunt142d0032014-11-04 20:13:09 -0800754 .style({
755 visibility: showBg
Simon Hunt195cb382014-11-03 17:50:51 -0800756 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800757
758 // group for the topology
759 topoG = svg.append('g')
760 .attr('transform', fcfg.translate());
761
762 // subgroups for links and nodes
763 linkG = topoG.append('g').attr('id', 'links');
764 nodeG = topoG.append('g').attr('id', 'nodes');
765
766 // selection of nodes and links
767 link = linkG.selectAll('.link');
768 node = nodeG.selectAll('.node');
769
Simon Hunt99c13842014-11-06 18:23:12 -0800770 function ldist(d) {
771 return fcfg.linkDistance[d.class] || 150;
772 }
773 function lstrg(d) {
774 return fcfg.linkStrength[d.class] || 1;
775 }
776 function lchrg(d) {
777 return fcfg.charge[d.class] || -200;
778 }
779
Simon Hunt1a9eff92014-11-07 11:06:34 -0800780 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800781 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -0800782 }
783
784 function atDragEnd(d, self) {
785 // once we've finished moving, pin the node in position,
786 // if it is a device (not a host)
787 if (d.class === 'device') {
788 d.fixed = true;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800789 d3.select(self).classed('fixed', true);
Thomas Vachuska65368e32014-11-08 16:10:20 -0800790 if (config.useLiveData) {
791 tellServerCoords(d);
792 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800793 }
794 }
795
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800796 function tellServerCoords(d) {
797 sendMessage('updateMeta', {
798 id: d.id,
799 'class': d.class,
800 x: d.x,
801 y: d.y
802 });
803 }
804
Simon Huntc7ee0662014-11-05 16:44:37 -0800805 // set up the force layout
806 network.force = d3.layout.force()
807 .size(forceDim)
808 .nodes(network.nodes)
809 .links(network.links)
Simon Hunt99c13842014-11-06 18:23:12 -0800810 .charge(lchrg)
811 .linkDistance(ldist)
812 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -0800813 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -0800814
Simon Hunt1a9eff92014-11-07 11:06:34 -0800815 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
816 }
Simon Hunt195cb382014-11-03 17:50:51 -0800817
Simon Hunt142d0032014-11-04 20:13:09 -0800818 function load(view, ctx) {
Simon Hunt99c13842014-11-06 18:23:12 -0800819 // cache the view token, so network topo functions can access it
820 network.view = view;
821
822 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -0800823 view.setRadio(btnSet);
824 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -0800825
Simon Hunt50128c02014-11-08 13:36:15 -0800826 if (config.useLiveData) {
827 webSock.connect();
828 }
Simon Hunt195cb382014-11-03 17:50:51 -0800829 }
830
Simon Hunt142d0032014-11-04 20:13:09 -0800831 function resize(view, ctx) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800832 setSize(svg, view);
833 setSize(bgImg, view);
Simon Hunt99c13842014-11-06 18:23:12 -0800834
835 // TODO: hook to recompute layout, perhaps? work with zoom/pan code
836 // adjust force layout size
Simon Hunt142d0032014-11-04 20:13:09 -0800837 }
838
839
840 // ==============================
841 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -0800842
Simon Hunt25248912014-11-04 11:25:48 -0800843 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -0800844 preload: preload,
845 load: load,
846 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -0800847 });
848
Simon Hunt195cb382014-11-03 17:50:51 -0800849}(ONOS));