blob: 94b2e9ed0e2ba8c4f83358c6b789829c6857d242 [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 Hunt99c13842014-11-06 18:23:12 -0800263 function cycleLabels() {
Simon Huntbb282f52014-11-10 11:08:19 -0800264 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1)
265 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800266
Simon Hunt99c13842014-11-06 18:23:12 -0800267 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800268 if (d.class === 'device') {
269 updateDeviceLabel(d);
270 }
Simon Hunt99c13842014-11-06 18:23:12 -0800271 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800272 }
273
274 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800275 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800276 }
277
278 function unpin(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800279 view.alert('unpin() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800280 }
281
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800282 function requestPath(view) {
283 var payload = {
284 one: selections[selectOrder[0]].obj.id,
285 two: selections[selectOrder[1]].obj.id
286 }
287 sendMessage('requestPath', payload);
288 }
289
Simon Hunt934c3ce2014-11-05 11:45:07 -0800290 // ==============================
291 // Radio Button Callbacks
292
Simon Hunt195cb382014-11-03 17:50:51 -0800293 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800294// network.node.classed('inactive', false);
295// network.link.classed('inactive', false);
296// d3.selectAll('svg .port').classed('inactive', false);
297// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800298 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800299 network.view.alert('showAllLayers() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800300 }
301
302 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800303 showAllLayers();
304 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800305 network.view.alert('showPacketLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800306 }
307
308 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800309 showAllLayers();
310 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800311 network.view.alert('showOpticalLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800312 }
313
Simon Hunt142d0032014-11-04 20:13:09 -0800314 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800315 // Private functions
316
Simon Hunt99c13842014-11-06 18:23:12 -0800317 function safeId(s) {
318 return s.replace(/[^a-z0-9]/gi, '-');
319 }
320
Simon Huntc7ee0662014-11-05 16:44:37 -0800321 // set the size of the given element to that of the view (reduced if padded)
322 function setSize(el, view, pad) {
323 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800324 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800325 width: view.width() - padding,
326 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800327 });
328 }
329
Simon Hunt934c3ce2014-11-05 11:45:07 -0800330
Simon Hunt99c13842014-11-06 18:23:12 -0800331 // ==============================
332 // Event handlers for server-pushed events
333
Simon Huntbb282f52014-11-10 11:08:19 -0800334 function logicError(msg) {
335 // TODO, report logic error to server, via websock, so it can be logged
336 network.view.alert('Logic Error:\n\n' + msg);
337 }
338
Simon Hunt99c13842014-11-06 18:23:12 -0800339 var eventDispatch = {
340 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800341 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800342 addHost: addHost,
Simon Huntbb282f52014-11-10 11:08:19 -0800343 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800344 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800345 updateHost: updateHost,
Simon Huntbb282f52014-11-10 11:08:19 -0800346 removeDevice: stillToImplement,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800347 removeLink: removeLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800348 removeHost: stillToImplement,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800349 showPath: showPath
Simon Hunt99c13842014-11-06 18:23:12 -0800350 };
351
352 function addDevice(data) {
353 var device = data.payload,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800354 nodeData = createDeviceNode(device);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800355 network.nodes.push(nodeData);
356 network.lookup[nodeData.id] = nodeData;
Simon Hunt99c13842014-11-06 18:23:12 -0800357 updateNodes();
358 network.force.start();
359 }
360
Simon Hunt99c13842014-11-06 18:23:12 -0800361 function addLink(data) {
362 var link = data.payload,
363 lnk = createLink(link);
Simon Hunt99c13842014-11-06 18:23:12 -0800364 if (lnk) {
Simon Hunt99c13842014-11-06 18:23:12 -0800365 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800366 network.lookup[lnk.id] = lnk;
Simon Hunt99c13842014-11-06 18:23:12 -0800367 updateLinks();
368 network.force.start();
369 }
370 }
371
Simon Hunt56d51852014-11-09 13:03:35 -0800372 function addHost(data) {
373 var host = data.payload,
374 node = createHostNode(host),
375 lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800376 network.nodes.push(node);
377 network.lookup[host.id] = node;
378 updateNodes();
379
380 lnk = createHostLink(host);
381 if (lnk) {
382 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800383 network.lookup[host.ingress] = lnk;
384 network.lookup[host.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800385 updateLinks();
386 }
387 network.force.start();
388 }
389
Simon Huntbb282f52014-11-10 11:08:19 -0800390 function updateDevice(data) {
391 var device = data.payload,
392 id = device.id,
393 nodeData = network.lookup[id];
394 if (nodeData) {
395 $.extend(nodeData, device);
396 updateDeviceState(nodeData);
397 } else {
398 logicError('updateDevice lookup fail. ID = "' + id + '"');
399 }
400 }
401
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800402 function updateLink(data) {
403 var link = data.payload,
404 id = link.id,
405 linkData = network.lookup[id];
406 if (linkData) {
407 $.extend(linkData, link);
408 updateLinkState(linkData);
409 } else {
410 logicError('updateLink lookup fail. ID = "' + id + '"');
411 }
412 }
413
Simon Hunt7cd48f32014-11-09 23:42:50 -0800414 function updateHost(data) {
415 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800416 id = host.id,
417 hostData = network.lookup[id];
418 if (hostData) {
419 $.extend(hostData, host);
420 updateHostState(hostData);
421 } else {
422 logicError('updateHost lookup fail. ID = "' + id + '"');
423 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800424 }
425
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800426 function removeLink(data) {
427 var link = data.payload,
428 id = link.id,
429 linkData = network.lookup[id];
430 if (linkData) {
431 removeLinkElement(linkData);
432 } else {
433 logicError('removeLink lookup fail. ID = "' + id + '"');
434 }
435 }
436
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800437 function showPath(data) {
Thomas Vachuska4830d392014-11-09 17:09:56 -0800438 var links = data.payload.links,
439 s = [ data.event + "\n" + links.length ];
440 links.forEach(function (d, i) {
441 s.push(d);
442 });
443 network.view.alert(s.join('\n'));
444
445 links.forEach(function (d, i) {
446 var link = network.lookup[d];
447 if (link) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800448 link.el.classed('showPath', true);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800449 }
450 });
451
452 // TODO: add selection-highlite lines to links
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800453 }
454
Simon Hunt56d51852014-11-09 13:03:35 -0800455 // ...............................
456
457 function stillToImplement(data) {
458 var p = data.payload;
459 note(data.event, p.id);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800460 network.view.alert('Not yet implemented: "' + data.event + '"');
Simon Hunt56d51852014-11-09 13:03:35 -0800461 }
Simon Hunt99c13842014-11-06 18:23:12 -0800462
463 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800464 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800465 }
466
467 function handleServerEvent(data) {
468 var fn = eventDispatch[data.event] || unknownEvent;
469 fn(data);
470 }
471
472 // ==============================
473 // force layout modification functions
474
475 function translate(x, y) {
476 return 'translate(' + x + ',' + y + ')';
477 }
478
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800479 function missMsg(what, id) {
480 return '\n[' + what + '] "' + id + '" missing ';
481 }
482
483 function linkEndPoints(srcId, dstId) {
484 var srcNode = network.lookup[srcId],
485 dstNode = network.lookup[dstId],
486 sMiss = !srcNode ? missMsg('src', srcId) : '',
487 dMiss = !dstNode ? missMsg('dst', dstId) : '';
488
489 if (sMiss || dMiss) {
490 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
491 return null;
492 }
493 return {
494 source: srcNode,
495 target: dstNode,
496 x1: srcNode.x,
497 y1: srcNode.y,
498 x2: dstNode.x,
499 y2: dstNode.y
500 };
501 }
502
Simon Hunt56d51852014-11-09 13:03:35 -0800503 function createHostLink(host) {
504 var src = host.id,
505 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800506 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800507 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -0800508
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800509 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -0800510 return null;
511 }
512
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800513 // Synthesize link ...
514 $.extend(lnk, {
Thomas Vachuska4830d392014-11-09 17:09:56 -0800515 id: id,
Simon Hunt56d51852014-11-09 13:03:35 -0800516 class: 'link',
Simon Hunt7cd48f32014-11-09 23:42:50 -0800517 type: 'hostLink',
Simon Hunt56d51852014-11-09 13:03:35 -0800518 svgClass: 'link hostLink',
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800519 linkWidth: 1
Simon Hunt7cd48f32014-11-09 23:42:50 -0800520 });
Simon Hunt99c13842014-11-06 18:23:12 -0800521 return lnk;
522 }
523
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800524 function createLink(link) {
525 var lnk = linkEndPoints(link.src, link.dst),
526 type = link.type;
527
528 if (!lnk) {
529 return null;
530 }
531
532 // merge in remaining data
533 $.extend(lnk, link, {
534 class: 'link',
535 svgClass: type ? 'link ' + type : 'link'
536 });
537 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -0800538 }
539
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800540 var widthRatio = 1.4,
541 linkScale = d3.scale.linear()
542 .domain([1, 12])
543 .range([widthRatio, 12 * widthRatio])
544 .clamp(true);
545
546 function updateLinkWidth (d) {
547 // TODO: watch out for .showPath/.showTraffic classes
548 d.el.transition()
549 .duration(1000)
550 .attr('stroke-width', linkScale(d.linkWidth));
551 }
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 Hunt3f03d4a2014-11-10 20:14:37 -0800575 'stroke-width': function (d) { return linkScale(d.linkWidth); },
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:
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800592 // TODO: better transition (longer as a dashed, grey line)
Thomas Vachuska4830d392014-11-09 17:09:56 -0800593 link.exit()
Thomas Vachuska4830d392014-11-09 17:09:56 -0800594 .attr({
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800595 'stroke-dasharray': '3, 3'
Thomas Vachuska4830d392014-11-09 17:09:56 -0800596 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800597 .style('opacity', 0.4)
598 .transition()
599 .duration(2000)
600 .attr({
601 'stroke-dasharray': '3, 12'
602 })
603 .transition()
604 .duration(1000)
605 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -0800606 .remove();
Simon Hunt99c13842014-11-06 18:23:12 -0800607 }
608
609 function createDeviceNode(device) {
610 // start with the object as is
611 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -0800612 type = device.type,
613 svgCls = type ? 'node device ' + type : 'node device';
Simon Hunt99c13842014-11-06 18:23:12 -0800614
615 // Augment as needed...
616 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -0800617 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -0800618 positionNode(node);
619
620 // cache label array length
621 network.deviceLabelCount = device.labels.length;
Simon Hunt99c13842014-11-06 18:23:12 -0800622 return node;
623 }
624
Simon Hunt56d51852014-11-09 13:03:35 -0800625 function createHostNode(host) {
626 // start with the object as is
627 var node = host;
628
629 // Augment as needed...
630 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -0800631 if (!node.type) {
632 // TODO: perhaps type would be: {phone, tablet, laptop, endstation} ?
633 node.type = 'endstation';
634 }
Simon Hunt56d51852014-11-09 13:03:35 -0800635 node.svgClass = 'node host';
636 // TODO: consider placing near its switch, if [x,y] not defined
637 positionNode(node);
638
639 // cache label array length
640 network.hostLabelCount = host.labels.length;
Simon Hunt56d51852014-11-09 13:03:35 -0800641 return node;
642 }
643
Simon Hunt99c13842014-11-06 18:23:12 -0800644 function positionNode(node) {
645 var meta = node.metaUi,
646 x = 0,
647 y = 0;
648
649 if (meta) {
650 x = meta.x;
651 y = meta.y;
652 }
653 if (x && y) {
654 node.fixed = true;
655 }
656 node.x = x || network.view.width() / 2;
657 node.y = y || network.view.height() / 2;
658 }
659
Simon Hunt99c13842014-11-06 18:23:12 -0800660 function iconUrl(d) {
661 return 'img/' + d.type + '.png';
662 }
663
664 // returns the newly computed bounding box of the rectangle
665 function adjustRectToFitText(n) {
666 var text = n.select('text'),
667 box = text.node().getBBox(),
668 lab = config.labels;
669
670 text.attr('text-anchor', 'middle')
671 .attr('y', '-0.8em')
672 .attr('x', lab.imgPad/2);
673
674 // translate the bbox so that it is centered on [x,y]
675 box.x = -box.width / 2;
676 box.y = -box.height / 2;
677
678 // add padding
679 box.x -= (lab.padLR + lab.imgPad/2);
680 box.width += lab.padLR * 2 + lab.imgPad;
681 box.y -= lab.padTB;
682 box.height += lab.padTB * 2;
683
684 return box;
685 }
686
Simon Hunt1a9eff92014-11-07 11:06:34 -0800687 function mkSvgClass(d) {
688 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
689 }
690
Simon Hunt7cd48f32014-11-09 23:42:50 -0800691 function hostLabel(d) {
692 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
693 return d.labels[idx];
694 }
695 function deviceLabel(d) {
696 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
697 return d.labels[idx];
698 }
699 function niceLabel(label) {
700 return (label && label.trim()) ? label : '.';
701 }
702
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800703 function updateDeviceLabel(d) {
704 var label = niceLabel(deviceLabel(d)),
705 node = d.el,
706 box;
707
708 node.select('text')
709 .text(label)
710 .style('opacity', 0)
711 .transition()
712 .style('opacity', 1);
713
714 box = adjustRectToFitText(node);
715
716 node.select('rect')
717 .transition()
718 .attr(box);
719
720 node.select('image')
721 .transition()
722 .attr('x', box.x + config.icons.xoff)
723 .attr('y', box.y + config.icons.yoff);
724 }
725
726 function updateHostLabel(d) {
727 var label = hostLabel(d),
728 host = d.el;
729
730 host.select('text').text(label);
731 }
732
Simon Huntbb282f52014-11-10 11:08:19 -0800733 function updateDeviceState(nodeData) {
734 nodeData.el.classed('online', nodeData.online);
735 updateDeviceLabel(nodeData);
736 // TODO: review what else might need to be updated
737 }
738
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800739 function updateLinkState(linkData) {
740 updateLinkWidth(linkData);
741 // TODO: review what else might need to be updated
742 // update label, if showing
743 }
744
Simon Huntbb282f52014-11-10 11:08:19 -0800745 function updateHostState(hostData) {
746 updateHostLabel(hostData);
747 // TODO: review what else might need to be updated
748 }
749
750
Simon Hunt99c13842014-11-06 18:23:12 -0800751 function updateNodes() {
752 node = nodeG.selectAll('.node')
753 .data(network.nodes, function (d) { return d.id; });
754
755 // operate on existing nodes, if necessary
Simon Hunt7cd48f32014-11-09 23:42:50 -0800756 // update host labels
Simon Hunt99c13842014-11-06 18:23:12 -0800757 //node .foo() .bar() ...
758
759 // operate on entering nodes:
760 var entering = node.enter()
761 .append('g')
762 .attr({
763 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800764 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800765 transform: function (d) { return translate(d.x, d.y); },
766 opacity: 0
767 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800768 .call(network.drag)
Simon Hunt99c13842014-11-06 18:23:12 -0800769 //.on('mouseover', function (d) {})
770 //.on('mouseover', function (d) {})
771 .transition()
772 .attr('opacity', 1);
773
774 // augment device nodes...
775 entering.filter('.device').each(function (d) {
776 var node = d3.select(this),
777 icon = iconUrl(d),
Simon Hunt7cd48f32014-11-09 23:42:50 -0800778 label = niceLabel(deviceLabel(d)),
Simon Hunt99c13842014-11-06 18:23:12 -0800779 box;
780
Simon Hunt7cd48f32014-11-09 23:42:50 -0800781 // provide ref to element from backing data....
782 d.el = node;
783
Simon Hunt99c13842014-11-06 18:23:12 -0800784 node.append('rect')
785 .attr({
786 'rx': 5,
787 'ry': 5
788 });
789
790 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -0800791 .text(label)
Simon Hunt99c13842014-11-06 18:23:12 -0800792 .attr('dy', '1.1em');
793
794 box = adjustRectToFitText(node);
795
796 node.select('rect')
797 .attr(box);
798
799 if (icon) {
800 var cfg = config.icons;
801 node.append('svg:image')
802 .attr({
803 x: box.x + config.icons.xoff,
804 y: box.y + config.icons.yoff,
805 width: cfg.w,
806 height: cfg.h,
807 'xlink:href': icon
808 });
809 }
810
811 // debug function to show the modelled x,y coordinates of nodes...
812 if (debug('showNodeXY')) {
813 node.select('rect').attr('fill-opacity', 0.5);
814 node.append('circle')
815 .attr({
816 class: 'debug',
817 cx: 0,
818 cy: 0,
819 r: '3px'
820 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800821 }
822 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800823
Simon Hunt56d51852014-11-09 13:03:35 -0800824 // augment host nodes...
825 entering.filter('.host').each(function (d) {
826 var node = d3.select(this),
Simon Hunt56d51852014-11-09 13:03:35 -0800827 box;
828
Simon Hunt7cd48f32014-11-09 23:42:50 -0800829 // provide ref to element from backing data....
830 d.el = node;
831
Simon Hunt56d51852014-11-09 13:03:35 -0800832 node.append('circle')
833 .attr('r', 8); // TODO: define host circle radius
834
835 // TODO: are we attaching labels to hosts?
836 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -0800837 .text(hostLabel)
838 .attr('dy', '1.3em')
839 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -0800840
841 // debug function to show the modelled x,y coordinates of nodes...
842 if (debug('showNodeXY')) {
843 node.select('circle').attr('fill-opacity', 0.5);
844 node.append('circle')
845 .attr({
846 class: 'debug',
847 cx: 0,
848 cy: 0,
849 r: '3px'
850 });
851 }
852 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800853
Simon Hunt99c13842014-11-06 18:23:12 -0800854 // operate on both existing and new nodes, if necessary
855 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -0800856
Simon Hunt99c13842014-11-06 18:23:12 -0800857 // operate on exiting nodes:
858 // TODO: figure out how to remove the node 'g' AND its children
859 node.exit()
860 .transition()
861 .duration(750)
862 .attr({
863 opacity: 0,
864 cx: 0,
865 cy: 0,
866 r: 0
867 })
868 .remove();
Simon Huntc7ee0662014-11-05 16:44:37 -0800869 }
870
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800871 function find(id, array) {
872 for (var idx = 0, n = array.length; idx < n; idx++) {
873 if (array[idx].id === id) {
874 return idx;
875 }
876 }
877 return -1;
878 }
879
880 function removeLinkElement(linkData) {
881 // remove from lookup cache
882 delete network.lookup[linkData.id];
883 // remove from links array
884 var idx = find(linkData.id, network.links);
885
886 network.links.splice(linkData.index, 1);
887 // remove from SVG
888 updateLinks();
889 }
Simon Huntc7ee0662014-11-05 16:44:37 -0800890
891 function tick() {
892 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800893 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -0800894 });
895
896 link.attr({
897 x1: function (d) { return d.source.x; },
898 y1: function (d) { return d.source.y; },
899 x2: function (d) { return d.target.x; },
900 y2: function (d) { return d.target.y; }
901 });
902 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800903
904 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800905 // Web-Socket for live data
906
907 function webSockUrl() {
908 return document.location.toString()
909 .replace(/\#.*/, '')
910 .replace('http://', 'ws://')
911 .replace('https://', 'wss://')
912 .replace('index2.html', config.webSockUrl);
913 }
914
915 webSock = {
916 ws : null,
917
918 connect : function() {
919 webSock.ws = new WebSocket(webSockUrl());
920
921 webSock.ws.onopen = function() {
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800922 };
923
924 webSock.ws.onmessage = function(m) {
925 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800926 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800927 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800928 }
929 };
930
931 webSock.ws.onclose = function(m) {
932 webSock.ws = null;
933 };
934 },
935
936 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800937 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800938 webSock._send(text);
939 }
940 },
941
942 _send : function(message) {
943 if (webSock.ws) {
944 webSock.ws.send(message);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800945 } else {
Simon Hunt56d51852014-11-09 13:03:35 -0800946 network.view.alert('no web socket open\n\n' + message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800947 }
948 }
949
950 };
951
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800952 var sid = 0;
953
954 function sendMessage(evType, payload) {
955 var toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -0800956 event: evType,
957 sid: ++sid,
958 payload: payload
959 },
960 asText = JSON.stringify(toSend);
961 wsTraceTx(asText);
962 webSock.send(asText);
963 }
964
965 function wsTraceTx(msg) {
966 wsTrace('tx', msg);
967 }
968 function wsTraceRx(msg) {
969 wsTrace('rx', msg);
970 }
971 function wsTrace(rxtx, msg) {
972
973 console.log('[' + rxtx + '] ' + msg);
974 // TODO: integrate with trace view
975 //if (trace) {
976 // trace.output(rxtx, msg);
977 //}
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800978 }
979
980
981 // ==============================
982 // Selection stuff
983
984 function selectObject(obj, el) {
985 var n,
986 meta = d3.event.sourceEvent.metaKey;
987
988 if (el) {
989 n = d3.select(el);
990 } else {
991 node.each(function(d) {
992 if (d == obj) {
993 n = d3.select(el = this);
994 }
995 });
996 }
997 if (!n) return;
998
999 if (meta && n.classed('selected')) {
1000 deselectObject(obj.id);
1001 //flyinPane(null);
1002 return;
1003 }
1004
1005 if (!meta) {
1006 deselectAll();
1007 }
1008
Simon Hunt5f36d342014-11-08 21:33:14 -08001009 selections[obj.id] = { obj: obj, el : el};
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001010 selectOrder.push(obj.id);
1011
1012 n.classed('selected', true);
1013 //flyinPane(obj);
1014 }
1015
1016 function deselectObject(id) {
1017 var obj = selections[id];
1018 if (obj) {
1019 d3.select(obj.el).classed('selected', false);
1020 selections[id] = null;
1021 // TODO: use splice to remove element
1022 }
1023 //flyinPane(null);
1024 }
1025
1026 function deselectAll() {
1027 // deselect all nodes in the network...
1028 node.classed('selected', false);
1029 selections = {};
1030 selectOrder = [];
1031 //flyinPane(null);
1032 }
1033
Simon Hunt56d51852014-11-09 13:03:35 -08001034 // TODO: this click handler does not get unloaded when the view does
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001035 $('#view').on('click', function(e) {
1036 if (!$(e.target).closest('.node').length) {
1037 if (!e.metaKey) {
1038 deselectAll();
1039 }
1040 }
1041 });
1042
Simon Hunt56d51852014-11-09 13:03:35 -08001043
1044 function prepareScenario(view, ctx, dbg) {
1045 var sc = scenario,
1046 urlSc = sc.evDir + ctx + sc.evScenario;
1047
1048 if (!ctx) {
1049 view.alert("No scenario specified (null ctx)");
1050 return;
1051 }
1052
1053 sc.view = view;
1054 sc.ctx = ctx;
1055 sc.debug = dbg;
1056 sc.evNumber = 0;
1057
1058 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08001059 var p = data && data.params || {},
1060 desc = data && data.description || null,
1061 intro;
1062
Simon Hunt56d51852014-11-09 13:03:35 -08001063 if (err) {
1064 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
1065 } else {
1066 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08001067 intro = "Scenario loaded: " + ctx + '\n\n' + data.title;
1068 if (desc) {
1069 intro += '\n\n ' + desc.join('\n ');
1070 }
1071 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08001072 }
1073 });
1074
1075 }
1076
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001077 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08001078 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08001079
Simon Huntf67722a2014-11-10 09:32:06 -08001080 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08001081 var w = view.width(),
1082 h = view.height(),
1083 idBg = view.uid('bg'),
Simon Huntc7ee0662014-11-05 16:44:37 -08001084 showBg = config.options.showBackground ? 'visible' : 'hidden',
1085 fcfg = config.force,
1086 fpad = fcfg.pad,
1087 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08001088
Simon Huntbb282f52014-11-10 11:08:19 -08001089 // TODO: set trace api
1090 //trace = onos.exported.webSockTrace;
1091
Simon Hunt142d0032014-11-04 20:13:09 -08001092 // NOTE: view.$div is a D3 selection of the view's div
1093 svg = view.$div.append('svg');
Simon Hunt934c3ce2014-11-05 11:45:07 -08001094 setSize(svg, view);
1095
Simon Hunt1a9eff92014-11-07 11:06:34 -08001096 // add blue glow filter to svg layer
1097 d3u.appendGlow(svg);
1098
Simon Hunt142d0032014-11-04 20:13:09 -08001099 // load the background image
1100 bgImg = svg.append('svg:image')
Simon Hunt195cb382014-11-03 17:50:51 -08001101 .attr({
Simon Hunt142d0032014-11-04 20:13:09 -08001102 id: idBg,
1103 width: w,
1104 height: h,
Simon Hunt195cb382014-11-03 17:50:51 -08001105 'xlink:href': config.backgroundUrl
1106 })
Simon Hunt142d0032014-11-04 20:13:09 -08001107 .style({
1108 visibility: showBg
Simon Hunt195cb382014-11-03 17:50:51 -08001109 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001110
1111 // group for the topology
1112 topoG = svg.append('g')
1113 .attr('transform', fcfg.translate());
1114
1115 // subgroups for links and nodes
1116 linkG = topoG.append('g').attr('id', 'links');
1117 nodeG = topoG.append('g').attr('id', 'nodes');
1118
1119 // selection of nodes and links
1120 link = linkG.selectAll('.link');
1121 node = nodeG.selectAll('.node');
1122
Simon Hunt7cd48f32014-11-09 23:42:50 -08001123 function chrg(d) {
1124 return fcfg.charge[d.class] || -12000;
1125 }
Simon Hunt99c13842014-11-06 18:23:12 -08001126 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001127 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08001128 }
1129 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001130 // 0.0 - 1.0
1131 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08001132 }
1133
Simon Hunt1a9eff92014-11-07 11:06:34 -08001134 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001135 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001136 }
1137
1138 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08001139 // once we've finished moving, pin the node in position
1140 d.fixed = true;
1141 d3.select(self).classed('fixed', true);
1142 if (config.useLiveData) {
1143 tellServerCoords(d);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001144 }
1145 }
1146
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001147 function tellServerCoords(d) {
1148 sendMessage('updateMeta', {
1149 id: d.id,
1150 'class': d.class,
Simon Hunt5f36d342014-11-08 21:33:14 -08001151 x: Math.floor(d.x),
1152 y: Math.floor(d.y)
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001153 });
1154 }
1155
Simon Huntc7ee0662014-11-05 16:44:37 -08001156 // set up the force layout
1157 network.force = d3.layout.force()
1158 .size(forceDim)
1159 .nodes(network.nodes)
1160 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001161 .gravity(0.4)
1162 .friction(0.7)
1163 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08001164 .linkDistance(ldist)
1165 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08001166 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08001167
Simon Hunt1a9eff92014-11-07 11:06:34 -08001168 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
1169 }
Simon Hunt195cb382014-11-03 17:50:51 -08001170
Simon Hunt56d51852014-11-09 13:03:35 -08001171 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08001172 // resize, in case the window was resized while we were not loaded
1173 resize(view, ctx, flags);
1174
Simon Hunt99c13842014-11-06 18:23:12 -08001175 // cache the view token, so network topo functions can access it
1176 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08001177 config.useLiveData = !flags.local;
1178
1179 if (!config.useLiveData) {
1180 prepareScenario(view, ctx, flags.debug);
1181 }
Simon Hunt99c13842014-11-06 18:23:12 -08001182
1183 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -08001184 view.setRadio(btnSet);
1185 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -08001186
Simon Hunt50128c02014-11-08 13:36:15 -08001187 if (config.useLiveData) {
1188 webSock.connect();
1189 }
Simon Hunt195cb382014-11-03 17:50:51 -08001190 }
1191
Simon Huntf67722a2014-11-10 09:32:06 -08001192 function resize(view, ctx, flags) {
Simon Hunt934c3ce2014-11-05 11:45:07 -08001193 setSize(svg, view);
1194 setSize(bgImg, view);
Simon Hunt99c13842014-11-06 18:23:12 -08001195
1196 // TODO: hook to recompute layout, perhaps? work with zoom/pan code
1197 // adjust force layout size
Simon Hunt142d0032014-11-04 20:13:09 -08001198 }
1199
1200
1201 // ==============================
1202 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08001203
Simon Hunt25248912014-11-04 11:25:48 -08001204 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08001205 preload: preload,
1206 load: load,
1207 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -08001208 });
1209
Simon Hunt195cb382014-11-03 17:50:51 -08001210}(ONOS));