blob: 201c99cf696268c7438998564648a0ec46f5caa3 [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 Hunt195cb382014-11-03 17:50:51 -080026 // configuration data
27 var config = {
Simon Hunt929f77b2014-11-06 10:39:51 -080028 useLiveData: true,
Simon Hunt195cb382014-11-03 17:50:51 -080029 debugOn: false,
30 debug: {
31 showNodeXY: false,
32 showKeyHandler: true
33 },
34 options: {
35 layering: true,
36 collisionPrevention: true,
Simon Hunt142d0032014-11-04 20:13:09 -080037 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080038 },
39 backgroundUrl: 'img/us-map.png',
40 data: {
41 live: {
42 jsonUrl: 'rs/topology/graph',
43 detailPrefix: 'rs/topology/graph/',
44 detailSuffix: ''
45 },
46 fake: {
47 jsonUrl: 'json/network2.json',
48 detailPrefix: 'json/',
49 detailSuffix: '.json'
50 }
51 },
52 iconUrl: {
53 device: 'img/device.png',
54 host: 'img/host.png',
55 pkt: 'img/pkt.png',
56 opt: 'img/opt.png'
57 },
Simon Hunt195cb382014-11-03 17:50:51 -080058 force: {
Simon Huntc7ee0662014-11-05 16:44:37 -080059 note: 'node.class or link.class is used to differentiate',
60 linkDistance: {
61 infra: 200,
62 host: 40
63 },
64 linkStrength: {
65 infra: 1.0,
66 host: 1.0
67 },
68 charge: {
69 device: -400,
70 host: -100
71 },
72 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -080073 translate: function() {
74 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -080075 config.force.pad + ',' +
76 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -080077 }
Simon Hunt142d0032014-11-04 20:13:09 -080078 }
Simon Hunt195cb382014-11-03 17:50:51 -080079 };
80
Simon Hunt142d0032014-11-04 20:13:09 -080081 // radio buttons
82 var btnSet = [
Simon Hunt934c3ce2014-11-05 11:45:07 -080083 { text: 'All Layers', cb: showAllLayers },
84 { text: 'Packet Only', cb: showPacketLayer },
85 { text: 'Optical Only', cb: showOpticalLayer }
86 ];
87
88 // key bindings
89 var keyDispatch = {
90 Q: getUpdatedNetworkData,
91 B: toggleBg,
92 G: toggleLayout,
93 L: cycleLabels,
94 P: togglePorts,
95 U: unpin
96 };
Simon Hunt142d0032014-11-04 20:13:09 -080097
Simon Hunt195cb382014-11-03 17:50:51 -080098 // state variables
Simon Hunt934c3ce2014-11-05 11:45:07 -080099 var network = {},
Simon Hunt195cb382014-11-03 17:50:51 -0800100 selected = {},
101 highlighted = null,
102 hovered = null,
103 viewMode = 'showAll',
104 portLabelsOn = false;
105
Simon Hunt934c3ce2014-11-05 11:45:07 -0800106 // D3 selections
107 var svg,
108 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800109 topoG,
110 nodeG,
111 linkG,
112 node,
113 link;
Simon Hunt195cb382014-11-03 17:50:51 -0800114
Simon Hunt142d0032014-11-04 20:13:09 -0800115 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800116 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800117
Simon Hunt934c3ce2014-11-05 11:45:07 -0800118 var topoPrefix = 'json/topoTest_',
119 lastFlavor = 4,
120 topoBase = true,
121 topoFlavor = 1;
Simon Hunt195cb382014-11-03 17:50:51 -0800122
Simon Hunt934c3ce2014-11-05 11:45:07 -0800123 function nextTopo() {
124 if (topoBase) {
125 topoBase = false;
126 } else {
127 topoBase = true;
128 topoFlavor = (topoFlavor === lastFlavor) ? 1 : topoFlavor + 1
Simon Hunt195cb382014-11-03 17:50:51 -0800129 }
130 }
131
Simon Hunt934c3ce2014-11-05 11:45:07 -0800132 // TODO change this to return the live data URL
133 function getTopoUrl() {
134 var suffix = topoBase ? 'base' : topoFlavor;
135 return topoPrefix + suffix + '.json';
136 }
137
138 // ==============================
139 // Key Callbacks
140
141 function getUpdatedNetworkData(view) {
142 nextTopo();
143 getNetworkData(view);
144 }
145
146 function toggleBg() {
147 var vis = bgImg.style('visibility');
148 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
149 }
150
151 function toggleLayout(view) {
152
153 }
154
155 function cycleLabels(view) {
156
157 }
158
159 function togglePorts(view) {
160
161 }
162
163 function unpin(view) {
164
165 }
166
167 // ==============================
168 // Radio Button Callbacks
169
Simon Hunt195cb382014-11-03 17:50:51 -0800170 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800171// network.node.classed('inactive', false);
172// network.link.classed('inactive', false);
173// d3.selectAll('svg .port').classed('inactive', false);
174// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800175 // TODO ...
176 console.log('showAllLayers()');
Simon Hunt195cb382014-11-03 17:50:51 -0800177 }
178
179 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800180 showAllLayers();
181 // TODO ...
182 console.log('showPacketLayer()');
Simon Hunt195cb382014-11-03 17:50:51 -0800183 }
184
185 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800186 showAllLayers();
187 // TODO ...
188 console.log('showOpticalLayer()');
Simon Hunt195cb382014-11-03 17:50:51 -0800189 }
190
Simon Hunt142d0032014-11-04 20:13:09 -0800191 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800192 // Private functions
193
Simon Huntc7ee0662014-11-05 16:44:37 -0800194 // set the size of the given element to that of the view (reduced if padded)
195 function setSize(el, view, pad) {
196 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800197 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800198 width: view.width() - padding,
199 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800200 });
201 }
202
Simon Hunt934c3ce2014-11-05 11:45:07 -0800203 function getNetworkData(view) {
204 var url = getTopoUrl();
205
Simon Huntc7ee0662014-11-05 16:44:37 -0800206 console.log('Fetching JSON: ' + url);
207 d3.json(url, function(err, data) {
208 if (err) {
209 view.dataLoadError(err, url);
210 } else {
211 network.data = data;
212 drawNetwork(view);
213 }
214 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800215 }
216
Simon Huntc7ee0662014-11-05 16:44:37 -0800217 function drawNetwork(view) {
218 preprocessData(view);
219 updateLayout(view);
220 }
221
222 function preprocessData(view) {
223 var w = view.width(),
224 h = view.height(),
225 hDevice = h * 0.6,
226 hHost = h * 0.3,
227 data = network.data,
228 deviceLayout = computeInitLayout(w, hDevice, data.devices.length),
229 hostLayout = computeInitLayout(w, hHost, data.hosts.length);
230
231 network.lookup = {};
232 network.nodes = [];
233 network.links = [];
234 // we created new arrays, so need to set the refs in the force layout
235 network.force.nodes(network.nodes);
236 network.force.links(network.links);
237
238 // let's just start with the nodes
239
240 // note that both 'devices' and 'hosts' get mapped into the nodes array
241 function makeNode(d, cls, layout) {
242 var node = {
243 id: d.id,
244 labels: d.labels,
245 class: cls,
246 icon: cls,
247 type: d.type,
248 x: layout.x(),
249 y: layout.y()
250 };
251 network.lookup[d.id] = node;
252 network.nodes.push(node);
253 }
254
255 // first the devices...
256 network.data.devices.forEach(function (d) {
257 makeNode(d, 'device', deviceLayout);
258 });
259
260 // then the hosts...
261 network.data.hosts.forEach(function (d) {
262 makeNode(d, 'host', hostLayout);
263 });
264
265 // TODO: process links
266 }
267
268 function computeInitLayout(w, h, n) {
269 var maxdw = 60,
270 compdw, dw, ox, layout;
271
272 if (n < 2) {
273 layout = { ox: w/2, dw: 0 }
274 } else {
275 compdw = (0.8 * w) / (n - 1);
276 dw = Math.min(maxdw, compdw);
277 ox = w/2 - ((n - 1)/2 * dw);
278 layout = { ox: ox, dw: dw }
279 }
280
281 layout.i = 0;
282
283 layout.x = function () {
284 var x = layout.ox + layout.i*layout.dw;
285 layout.i++;
286 return x;
287 };
288
289 layout.y = function () {
290 return h;
291 };
292
293 return layout;
294 }
295
296 function linkId(d) {
297 return d.source.id + '~' + d.target.id;
298 }
299
300 function nodeId(d) {
301 return d.id;
302 }
303
304 function updateLayout(view) {
305 link = link.data(network.force.links(), linkId);
306 link.enter().append('line')
307 .attr('class', 'link');
308 link.exit().remove();
309
310 node = node.data(network.force.nodes(), nodeId);
311 node.enter().append('circle')
312 .attr('id', function (d) { return 'nodeId-' + d.id; })
313 .attr('class', function (d) { return 'node'; })
314 .attr('r', 12);
315
316 network.force.start();
317 }
318
319
320 function tick() {
321 node.attr({
322 cx: function(d) { return d.x; },
323 cy: function(d) { return d.y; }
324 });
325
326 link.attr({
327 x1: function (d) { return d.source.x; },
328 y1: function (d) { return d.source.y; },
329 x2: function (d) { return d.target.x; },
330 y2: function (d) { return d.target.y; }
331 });
332 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800333
334 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -0800335 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -0800336
Simon Hunt142d0032014-11-04 20:13:09 -0800337 function preload(view, ctx) {
338 var w = view.width(),
339 h = view.height(),
340 idBg = view.uid('bg'),
Simon Huntc7ee0662014-11-05 16:44:37 -0800341 showBg = config.options.showBackground ? 'visible' : 'hidden',
342 fcfg = config.force,
343 fpad = fcfg.pad,
344 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -0800345
Simon Hunt142d0032014-11-04 20:13:09 -0800346 // NOTE: view.$div is a D3 selection of the view's div
347 svg = view.$div.append('svg');
Simon Hunt934c3ce2014-11-05 11:45:07 -0800348 setSize(svg, view);
349
Simon Hunt142d0032014-11-04 20:13:09 -0800350 // load the background image
351 bgImg = svg.append('svg:image')
Simon Hunt195cb382014-11-03 17:50:51 -0800352 .attr({
Simon Hunt142d0032014-11-04 20:13:09 -0800353 id: idBg,
354 width: w,
355 height: h,
Simon Hunt195cb382014-11-03 17:50:51 -0800356 'xlink:href': config.backgroundUrl
357 })
Simon Hunt142d0032014-11-04 20:13:09 -0800358 .style({
359 visibility: showBg
Simon Hunt195cb382014-11-03 17:50:51 -0800360 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800361
362 // group for the topology
363 topoG = svg.append('g')
364 .attr('transform', fcfg.translate());
365
366 // subgroups for links and nodes
367 linkG = topoG.append('g').attr('id', 'links');
368 nodeG = topoG.append('g').attr('id', 'nodes');
369
370 // selection of nodes and links
371 link = linkG.selectAll('.link');
372 node = nodeG.selectAll('.node');
373
374 // set up the force layout
375 network.force = d3.layout.force()
376 .size(forceDim)
377 .nodes(network.nodes)
378 .links(network.links)
379 .charge(function (d) { return fcfg.charge[d.class]; })
380 .linkDistance(function (d) { return fcfg.linkDistance[d.class]; })
381 .linkStrength(function (d) { return fcfg.linkStrength[d.class]; })
382 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -0800383 }
384
385
Simon Hunt142d0032014-11-04 20:13:09 -0800386 function load(view, ctx) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800387 view.setRadio(btnSet);
388 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -0800389
Simon Hunt934c3ce2014-11-05 11:45:07 -0800390 getNetworkData(view);
Simon Hunt195cb382014-11-03 17:50:51 -0800391 }
392
Simon Hunt142d0032014-11-04 20:13:09 -0800393 function resize(view, ctx) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800394 setSize(svg, view);
395 setSize(bgImg, view);
Simon Hunt142d0032014-11-04 20:13:09 -0800396 }
397
398
399 // ==============================
400 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -0800401
Simon Hunt25248912014-11-04 11:25:48 -0800402 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -0800403 preload: preload,
404 load: load,
405 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -0800406 });
407
Simon Hunt195cb382014-11-03 17:50:51 -0800408}(ONOS));