blob: acd2464847d74be67cc984c10332b5245ad2c58f [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;
Simon Hunt5f36d342014-11-08 21:33:14 -0800215
216 function niceLabel(label) {
217 return (label && label.trim()) ? label : '.';
218 }
219
Simon Hunt99c13842014-11-06 18:23:12 -0800220 network.nodes.forEach(function (d) {
221 var idx = (labelIdx < d.labels.length) ? labelIdx : 0,
222 node = d3.select('#' + safeId(d.id)),
223 box;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800224
Simon Hunt99c13842014-11-06 18:23:12 -0800225 node.select('text')
Simon Hunt5f36d342014-11-08 21:33:14 -0800226 .text(niceLabel(d.labels[idx]))
Simon Hunt99c13842014-11-06 18:23:12 -0800227 .style('opacity', 0)
228 .transition()
229 .style('opacity', 1);
230
231 box = adjustRectToFitText(node);
232
233 node.select('rect')
234 .transition()
235 .attr(box);
236
237 node.select('image')
238 .transition()
239 .attr('x', box.x + config.icons.xoff)
240 .attr('y', box.y + config.icons.yoff);
241 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800242 }
243
244 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800245 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800246 }
247
248 function unpin(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800249 view.alert('unpin() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800250 }
251
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800252 function requestPath(view) {
253 var payload = {
254 one: selections[selectOrder[0]].obj.id,
255 two: selections[selectOrder[1]].obj.id
256 }
257 sendMessage('requestPath', payload);
258 }
259
Simon Hunt934c3ce2014-11-05 11:45:07 -0800260 // ==============================
261 // Radio Button Callbacks
262
Simon Hunt195cb382014-11-03 17:50:51 -0800263 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800264// network.node.classed('inactive', false);
265// network.link.classed('inactive', false);
266// d3.selectAll('svg .port').classed('inactive', false);
267// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800268 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800269 network.view.alert('showAllLayers() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800270 }
271
272 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800273 showAllLayers();
274 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800275 network.view.alert('showPacketLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800276 }
277
278 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800279 showAllLayers();
280 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800281 network.view.alert('showOpticalLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800282 }
283
Simon Hunt142d0032014-11-04 20:13:09 -0800284 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800285 // Private functions
286
Simon Hunt99c13842014-11-06 18:23:12 -0800287 function safeId(s) {
288 return s.replace(/[^a-z0-9]/gi, '-');
289 }
290
Simon Huntc7ee0662014-11-05 16:44:37 -0800291 // set the size of the given element to that of the view (reduced if padded)
292 function setSize(el, view, pad) {
293 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800294 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800295 width: view.width() - padding,
296 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800297 });
298 }
299
Simon Hunt934c3ce2014-11-05 11:45:07 -0800300
Simon Hunt99c13842014-11-06 18:23:12 -0800301 // ==============================
302 // Event handlers for server-pushed events
303
304 var eventDispatch = {
305 addDevice: addDevice,
306 updateDevice: updateDevice,
307 removeDevice: removeDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800308 addLink: addLink,
309 showPath: showPath
Simon Hunt99c13842014-11-06 18:23:12 -0800310 };
311
312 function addDevice(data) {
313 var device = data.payload,
314 node = createDeviceNode(device);
315 note('addDevice', device.id);
316
317 network.nodes.push(node);
318 network.lookup[node.id] = node;
319 updateNodes();
320 network.force.start();
321 }
322
323 function updateDevice(data) {
324 var device = data.payload;
325 note('updateDevice', device.id);
326
327 }
328
329 function removeDevice(data) {
330 var device = data.payload;
331 note('removeDevice', device.id);
332
333 }
334
335 function addLink(data) {
336 var link = data.payload,
337 lnk = createLink(link);
338
339 if (lnk) {
340 note('addLink', lnk.id);
341
342 network.links.push(lnk);
343 updateLinks();
344 network.force.start();
345 }
346 }
347
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800348 function showPath(data) {
349 network.view.alert(data.event + "\n" + data.payload.links.length);
350 }
351
Simon Hunt99c13842014-11-06 18:23:12 -0800352 // ....
353
354 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800355 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800356 }
357
358 function handleServerEvent(data) {
359 var fn = eventDispatch[data.event] || unknownEvent;
360 fn(data);
361 }
362
363 // ==============================
364 // force layout modification functions
365
366 function translate(x, y) {
367 return 'translate(' + x + ',' + y + ')';
368 }
369
370 function createLink(link) {
371 var type = link.type,
372 src = link.src,
373 dst = link.dst,
374 w = link.linkWidth,
375 srcNode = network.lookup[src],
376 dstNode = network.lookup[dst],
377 lnk;
378
379 if (!(srcNode && dstNode)) {
Simon Hunt50128c02014-11-08 13:36:15 -0800380 // TODO: send warning message back to server on websocket
381 network.view.alert('nodes not on map for link\n\n' +
382 'src = ' + src + '\ndst = ' + dst);
Simon Hunt99c13842014-11-06 18:23:12 -0800383 return null;
384 }
385
386 lnk = {
387 id: safeId(src) + '~' + safeId(dst),
388 source: srcNode,
389 target: dstNode,
390 class: 'link',
391 svgClass: type ? 'link ' + type : 'link',
392 x1: srcNode.x,
393 y1: srcNode.y,
394 x2: dstNode.x,
395 y2: dstNode.y,
396 width: w
397 };
398 return lnk;
399 }
400
Simon Hunt1a9eff92014-11-07 11:06:34 -0800401 function linkWidth(w) {
402 // w is number of links between nodes. Scale appropriately.
Simon Hunt50128c02014-11-08 13:36:15 -0800403 // TODO: use a d3.scale (linear, log, ... ?)
Simon Hunt1a9eff92014-11-07 11:06:34 -0800404 return w * 1.2;
405 }
406
Simon Hunt99c13842014-11-06 18:23:12 -0800407 function updateLinks() {
408 link = linkG.selectAll('.link')
409 .data(network.links, function (d) { return d.id; });
410
411 // operate on existing links, if necessary
412 // link .foo() .bar() ...
413
414 // operate on entering links:
415 var entering = link.enter()
416 .append('line')
417 .attr({
418 id: function (d) { return d.id; },
419 class: function (d) { return d.svgClass; },
420 x1: function (d) { return d.x1; },
421 y1: function (d) { return d.y1; },
422 x2: function (d) { return d.x2; },
423 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800424 stroke: config.topo.linkInColor,
425 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -0800426 })
427 .transition().duration(1000)
428 .attr({
Simon Hunt1a9eff92014-11-07 11:06:34 -0800429 'stroke-width': function (d) { return linkWidth(d.width); },
Simon Hunt99c13842014-11-06 18:23:12 -0800430 stroke: '#666' // TODO: remove explicit stroke, rather...
431 });
432
433 // augment links
434 // TODO: add src/dst port labels etc.
435
436 }
437
438 function createDeviceNode(device) {
439 // start with the object as is
440 var node = device,
441 type = device.type;
442
443 // Augment as needed...
444 node.class = 'device';
445 node.svgClass = type ? 'node device ' + type : 'node device';
446 positionNode(node);
447
448 // cache label array length
449 network.deviceLabelCount = device.labels.length;
450
451 return node;
452 }
453
454 function positionNode(node) {
455 var meta = node.metaUi,
456 x = 0,
457 y = 0;
458
459 if (meta) {
460 x = meta.x;
461 y = meta.y;
462 }
463 if (x && y) {
464 node.fixed = true;
465 }
466 node.x = x || network.view.width() / 2;
467 node.y = y || network.view.height() / 2;
468 }
469
470
471 function iconUrl(d) {
472 return 'img/' + d.type + '.png';
473 }
474
475 // returns the newly computed bounding box of the rectangle
476 function adjustRectToFitText(n) {
477 var text = n.select('text'),
478 box = text.node().getBBox(),
479 lab = config.labels;
480
481 text.attr('text-anchor', 'middle')
482 .attr('y', '-0.8em')
483 .attr('x', lab.imgPad/2);
484
485 // translate the bbox so that it is centered on [x,y]
486 box.x = -box.width / 2;
487 box.y = -box.height / 2;
488
489 // add padding
490 box.x -= (lab.padLR + lab.imgPad/2);
491 box.width += lab.padLR * 2 + lab.imgPad;
492 box.y -= lab.padTB;
493 box.height += lab.padTB * 2;
494
495 return box;
496 }
497
Simon Hunt1a9eff92014-11-07 11:06:34 -0800498 function mkSvgClass(d) {
499 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
500 }
501
Simon Hunt99c13842014-11-06 18:23:12 -0800502 function updateNodes() {
503 node = nodeG.selectAll('.node')
504 .data(network.nodes, function (d) { return d.id; });
505
506 // operate on existing nodes, if necessary
507 //node .foo() .bar() ...
508
509 // operate on entering nodes:
510 var entering = node.enter()
511 .append('g')
512 .attr({
513 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800514 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800515 transform: function (d) { return translate(d.x, d.y); },
516 opacity: 0
517 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800518 .call(network.drag)
Simon Hunt99c13842014-11-06 18:23:12 -0800519 //.on('mouseover', function (d) {})
520 //.on('mouseover', function (d) {})
521 .transition()
522 .attr('opacity', 1);
523
524 // augment device nodes...
525 entering.filter('.device').each(function (d) {
526 var node = d3.select(this),
527 icon = iconUrl(d),
528 idx = (labelIdx < d.labels.length) ? labelIdx : 0,
529 box;
530
531 node.append('rect')
532 .attr({
533 'rx': 5,
534 'ry': 5
535 });
536
537 node.append('text')
538 .text(d.labels[idx])
539 .attr('dy', '1.1em');
540
541 box = adjustRectToFitText(node);
542
543 node.select('rect')
544 .attr(box);
545
546 if (icon) {
547 var cfg = config.icons;
548 node.append('svg:image')
549 .attr({
550 x: box.x + config.icons.xoff,
551 y: box.y + config.icons.yoff,
552 width: cfg.w,
553 height: cfg.h,
554 'xlink:href': icon
555 });
556 }
557
558 // debug function to show the modelled x,y coordinates of nodes...
559 if (debug('showNodeXY')) {
560 node.select('rect').attr('fill-opacity', 0.5);
561 node.append('circle')
562 .attr({
563 class: 'debug',
564 cx: 0,
565 cy: 0,
566 r: '3px'
567 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800568 }
569 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800570
Simon Huntc7ee0662014-11-05 16:44:37 -0800571
Simon Hunt99c13842014-11-06 18:23:12 -0800572 // operate on both existing and new nodes, if necessary
573 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -0800574
Simon Hunt99c13842014-11-06 18:23:12 -0800575 // operate on exiting nodes:
576 // TODO: figure out how to remove the node 'g' AND its children
577 node.exit()
578 .transition()
579 .duration(750)
580 .attr({
581 opacity: 0,
582 cx: 0,
583 cy: 0,
584 r: 0
585 })
586 .remove();
Simon Huntc7ee0662014-11-05 16:44:37 -0800587 }
588
589
590 function tick() {
591 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800592 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -0800593 });
594
595 link.attr({
596 x1: function (d) { return d.source.x; },
597 y1: function (d) { return d.source.y; },
598 x2: function (d) { return d.target.x; },
599 y2: function (d) { return d.target.y; }
600 });
601 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800602
603 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800604 // Web-Socket for live data
605
606 function webSockUrl() {
607 return document.location.toString()
608 .replace(/\#.*/, '')
609 .replace('http://', 'ws://')
610 .replace('https://', 'wss://')
611 .replace('index2.html', config.webSockUrl);
612 }
613
614 webSock = {
615 ws : null,
616
617 connect : function() {
618 webSock.ws = new WebSocket(webSockUrl());
619
620 webSock.ws.onopen = function() {
621 webSock._send("Hi there!");
622 };
623
624 webSock.ws.onmessage = function(m) {
625 if (m.data) {
626 console.log(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800627 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800628 }
629 };
630
631 webSock.ws.onclose = function(m) {
632 webSock.ws = null;
633 };
634 },
635
636 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800637 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800638 webSock._send(text);
639 }
640 },
641
642 _send : function(message) {
643 if (webSock.ws) {
644 webSock.ws.send(message);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800645 } else {
646 network.view.alert('no web socket open');
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800647 }
648 }
649
650 };
651
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800652 var sid = 0;
653
654 function sendMessage(evType, payload) {
655 var toSend = {
656 event: evType,
657 sid: ++sid,
658 payload: payload
659 };
660 webSock.send(JSON.stringify(toSend));
661 }
662
663
664 // ==============================
665 // Selection stuff
666
667 function selectObject(obj, el) {
668 var n,
669 meta = d3.event.sourceEvent.metaKey;
670
671 if (el) {
672 n = d3.select(el);
673 } else {
674 node.each(function(d) {
675 if (d == obj) {
676 n = d3.select(el = this);
677 }
678 });
679 }
680 if (!n) return;
681
682 if (meta && n.classed('selected')) {
683 deselectObject(obj.id);
684 //flyinPane(null);
685 return;
686 }
687
688 if (!meta) {
689 deselectAll();
690 }
691
Simon Hunt5f36d342014-11-08 21:33:14 -0800692 selections[obj.id] = { obj: obj, el : el};
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800693 selectOrder.push(obj.id);
694
695 n.classed('selected', true);
696 //flyinPane(obj);
697 }
698
699 function deselectObject(id) {
700 var obj = selections[id];
701 if (obj) {
702 d3.select(obj.el).classed('selected', false);
703 selections[id] = null;
704 // TODO: use splice to remove element
705 }
706 //flyinPane(null);
707 }
708
709 function deselectAll() {
710 // deselect all nodes in the network...
711 node.classed('selected', false);
712 selections = {};
713 selectOrder = [];
714 //flyinPane(null);
715 }
716
717
718 $('#view').on('click', function(e) {
719 if (!$(e.target).closest('.node').length) {
720 if (!e.metaKey) {
721 deselectAll();
722 }
723 }
724 });
725
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800726 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -0800727 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -0800728
Simon Hunt142d0032014-11-04 20:13:09 -0800729 function preload(view, ctx) {
730 var w = view.width(),
731 h = view.height(),
732 idBg = view.uid('bg'),
Simon Huntc7ee0662014-11-05 16:44:37 -0800733 showBg = config.options.showBackground ? 'visible' : 'hidden',
734 fcfg = config.force,
735 fpad = fcfg.pad,
736 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -0800737
Simon Hunt142d0032014-11-04 20:13:09 -0800738 // NOTE: view.$div is a D3 selection of the view's div
739 svg = view.$div.append('svg');
Simon Hunt934c3ce2014-11-05 11:45:07 -0800740 setSize(svg, view);
741
Simon Hunt1a9eff92014-11-07 11:06:34 -0800742 // add blue glow filter to svg layer
743 d3u.appendGlow(svg);
744
Simon Hunt142d0032014-11-04 20:13:09 -0800745 // load the background image
746 bgImg = svg.append('svg:image')
Simon Hunt195cb382014-11-03 17:50:51 -0800747 .attr({
Simon Hunt142d0032014-11-04 20:13:09 -0800748 id: idBg,
749 width: w,
750 height: h,
Simon Hunt195cb382014-11-03 17:50:51 -0800751 'xlink:href': config.backgroundUrl
752 })
Simon Hunt142d0032014-11-04 20:13:09 -0800753 .style({
754 visibility: showBg
Simon Hunt195cb382014-11-03 17:50:51 -0800755 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800756
757 // group for the topology
758 topoG = svg.append('g')
759 .attr('transform', fcfg.translate());
760
761 // subgroups for links and nodes
762 linkG = topoG.append('g').attr('id', 'links');
763 nodeG = topoG.append('g').attr('id', 'nodes');
764
765 // selection of nodes and links
766 link = linkG.selectAll('.link');
767 node = nodeG.selectAll('.node');
768
Simon Hunt99c13842014-11-06 18:23:12 -0800769 function ldist(d) {
770 return fcfg.linkDistance[d.class] || 150;
771 }
772 function lstrg(d) {
773 return fcfg.linkStrength[d.class] || 1;
774 }
775 function lchrg(d) {
776 return fcfg.charge[d.class] || -200;
777 }
778
Simon Hunt1a9eff92014-11-07 11:06:34 -0800779 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800780 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -0800781 }
782
783 function atDragEnd(d, self) {
784 // once we've finished moving, pin the node in position,
785 // if it is a device (not a host)
786 if (d.class === 'device') {
787 d.fixed = true;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800788 d3.select(self).classed('fixed', true);
Thomas Vachuska65368e32014-11-08 16:10:20 -0800789 if (config.useLiveData) {
790 tellServerCoords(d);
791 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800792 }
793 }
794
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800795 function tellServerCoords(d) {
796 sendMessage('updateMeta', {
797 id: d.id,
798 'class': d.class,
Simon Hunt5f36d342014-11-08 21:33:14 -0800799 x: Math.floor(d.x),
800 y: Math.floor(d.y)
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800801 });
802 }
803
Simon Huntc7ee0662014-11-05 16:44:37 -0800804 // set up the force layout
805 network.force = d3.layout.force()
806 .size(forceDim)
807 .nodes(network.nodes)
808 .links(network.links)
Simon Hunt99c13842014-11-06 18:23:12 -0800809 .charge(lchrg)
810 .linkDistance(ldist)
811 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -0800812 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -0800813
Simon Hunt1a9eff92014-11-07 11:06:34 -0800814 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
815 }
Simon Hunt195cb382014-11-03 17:50:51 -0800816
Simon Hunt142d0032014-11-04 20:13:09 -0800817 function load(view, ctx) {
Simon Hunt99c13842014-11-06 18:23:12 -0800818 // cache the view token, so network topo functions can access it
819 network.view = view;
820
821 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -0800822 view.setRadio(btnSet);
823 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -0800824
Simon Hunt50128c02014-11-08 13:36:15 -0800825 if (config.useLiveData) {
826 webSock.connect();
827 }
Simon Hunt195cb382014-11-03 17:50:51 -0800828 }
829
Simon Hunt142d0032014-11-04 20:13:09 -0800830 function resize(view, ctx) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800831 setSize(svg, view);
832 setSize(bgImg, view);
Simon Hunt99c13842014-11-06 18:23:12 -0800833
834 // TODO: hook to recompute layout, perhaps? work with zoom/pan code
835 // adjust force layout size
Simon Hunt142d0032014-11-04 20:13:09 -0800836 }
837
838
839 // ==============================
840 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -0800841
Simon Hunt25248912014-11-04 11:25:48 -0800842 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -0800843 preload: preload,
844 load: load,
845 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -0800846 });
847
Simon Hunt195cb382014-11-03 17:50:51 -0800848}(ONOS));