Merge "Add back the proper NetvirtSfc flows"
[netvirt.git] / ovsdb-ui / module / src / main / resources / ovsdb / LogicalGraph.js
1 define(['app/ovsdb/lib/d3.min', 'app/ovsdb/OvsCore', 'underscore'], function (d3, OvsCore, _) {
2   'use strict';
3
4   var root = null,
5     canvasWidth = -1,
6     canvasHeight = -1,
7     bbox = {
8       x: 0,
9       y: 15,
10       width: 0,
11       height: 0
12     },
13     // config
14     nodeWidth = 15,
15     nodeHeight = -1,
16     defaultRouterWidth = 52,
17     defaultRouterHeight = 52,
18     networkMargin = {
19       width: 120,
20       height: 15
21     },
22     routerMargin = {
23       width: 120,
24       height: 40
25     },
26     vmMargin = {
27       width: 90,
28       height: 30
29     },
30     defaultVmsWidth = 48,
31     defaultVmsHeight = 48,
32     ipNetworkTextMaxLength = 60,
33     networkOffset = 15,
34     linkHeight = 5,
35     // datas
36     networkData = [],
37     routerData = [],
38     vmData = [],
39     linkData = [],
40     tmpNetHolder = {},
41     // d3 layer over datas
42     d3Node = null,
43     d3Link = null,
44     d3Vm = null,
45     d3Router = null,
46     randomize = OvsCore.Util.Math.Random(42);
47
48   function LogicalGraph(id, width, height) {
49     canvasWidth = width;
50     canvasHeight = height;
51
52     nodeHeight = height - 15;
53
54     var tmp = d3.select(id).append("svg")
55       .attr('width', width)
56       .attr('height', height)
57       .append("svg:g")
58       .attr('class', 'layer_0');
59
60     tmp.append('svg:rect')
61       .attr('width', width)
62       .attr('height', height)
63       .attr('fill', 'white');
64
65     root = tmp.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
66       .append("g");
67     tmp.on("dblclick.zoom", null);
68     addDefs();
69   }
70
71   // Define reusable svg item like box-shadow
72   function addDefs() {
73     // box-shadow
74     var defs = d3.select('svg').insert('svg:defs', ':first-child');
75     var filter = defs.append('svg:filter').attr('id', 'boxShadow').attr('x', '0').attr('y', '0').attr('width', '200%').attr('height', '200%');
76     filter.append('feOffset').attr('in', 'SourceAlpha').attr('result', 'offOut').attr('dx', 0).attr('dy', 0);
77     filter.append('feGaussianBlur').attr('stdDeviation', '5').attr('in', 'offOut').attr('result', 'blurOut');
78     filter.append('feOffset').attr('in', 'SourceGraphic').attr('in2', 'blurOut').attr('mode', 'normal');
79   }
80
81   function zoom() {
82     root.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
83   }
84
85   Object.defineProperties(LogicalGraph.prototype, {
86     networks: {
87       set: function (value) {
88         networkData = value;
89         value.forEach(function (net) {
90           routerData = routerData.concat(net.routers);
91         });
92         value.forEach(function (net) {
93           vmData = vmData.concat(net.instances);
94         });
95       }
96     }
97   });
98
99   LogicalGraph.prototype.start = function () {
100     setTopologyPosition.call(this, networkData);
101     addLinksToDom.call(this, linkData);
102     addNetWorkRouterVmsToDom.call(this, networkData, routerData, vmData);
103     update.call(this);
104   };
105
106   LogicalGraph.prototype.freeDOM = function () {
107     d3Node.remove();
108     d3Link.remove();
109     d3Vm.remove();
110     d3Router.remove();
111
112     networkData = [];
113     routerData = [];
114     vmData = [];
115     linkData = [];
116     bbox = {
117       x: 0,
118       y: 15,
119       width: 0,
120       height: 0
121     };
122   };
123
124   function addLinksToDom(linksData) {
125     d3Link = root.selectAll('.llink')
126       .data(linksData).enter().append('svg:g');
127
128     d3Link.append('rect')
129       .attr('width', function (d) {
130         return d.target.x - d.source.x;
131       })
132       .attr('height', linkHeight)
133       .style('fill', function (d) {
134         return d.color;
135       });
136
137     d3Link.append('text')
138       .attr('x', 40)
139       .attr('y', -3)
140       .text(function (d) {
141         return d.text;
142       });
143   }
144
145   function addNetWorkRouterVmsToDom(networks, routers, vms) {
146     var ctx = this,
147       timer = null;
148
149     d3Node = root.selectAll('.network')
150       .data(networks).enter()
151       .append('svg:g');
152     d3Router = root.selectAll('.routers')
153       .data(routers).enter()
154       .append('svg:g');
155     d3Vm = root.selectAll('.vm')
156       .data(vms).enter()
157       .append('svg:g');
158
159     // append coresponding form
160     d3Node.append('svg:rect')
161       .attr('width', nodeWidth)
162       .attr('height', nodeHeight)
163       .attr('rx', 10)
164       .attr('ry', 10)
165       .style('fill', function (d) {
166         return d.color;
167       }).on('click', function (d) {
168         if (d3.event.defaultPrevented) return;
169         timer = setTimeout(function () {
170           ctx.onClick(d);
171         }.bind(this), 150);
172       }).on('dblclick', function (d) {
173         clearTimeout(timer);
174         ctx.dblClick(d);
175       });
176
177     // append the network name text
178     d3Node.append('text')
179       .attr('x', nodeWidth / 2)
180       .attr('y', nodeHeight / 2)
181       .style('text-anchor', 'middle')
182       .style('writing-mode', 'tb')
183       .style('font-size', '12px')
184       .style('glyph-orientation-vertical', '0')
185       .text(function (d) {
186         return d.name;
187       });
188
189     // text info for the network ip
190     d3Node.append('text')
191       .attr('x', nodeWidth + 10)
192       .attr('y', nodeHeight - 15)
193       .attr('transform',
194         OvsCore.Util.String.Format('translate({0} {1}) rotate(-90) translate(-{0} -{1})', nodeWidth + 10, nodeHeight - 15))
195       .attr('class', 'linfolabel')
196       .text(function (d) {
197         return d.ip;
198       });
199
200     // vm
201     d3Vm.append('svg:image')
202       .attr('width', defaultVmsWidth)
203       .attr('height', defaultVmsHeight)
204       .attr('filter', 'url(#boxShadow)')
205       .attr('xlink:href', function (d) {
206         return d.type === 'network:dhcp' ?
207           'src/app/ovsdb/assets/dhcp.png' : 'src/app/ovsdb/assets/vm.png';
208       })
209       .on('click', function (d) {
210         if (d3.event.defaultPrevented) return;
211         timer = setTimeout(function () {
212           ctx.onClick(d);
213         }.bind(this), 150);
214       }).on('dblclick', function (d) {
215         clearTimeout(timer);
216         ctx.dblClick(d);
217       });
218
219     // router
220     d3Router.append('svg:image')
221       .attr('width', defaultRouterWidth)
222       .attr('height', defaultRouterHeight)
223       .attr('xlink:href', 'src/app/ovsdb/assets/router.png')
224       .on('click', function (d) {
225         if (d3.event.defaultPrevented) return;
226         timer = setTimeout(function () {
227           ctx.onClick(d);
228         }.bind(this), 150);
229       }).on('dblclick', function (d) {
230         clearTimeout(timer);
231         ctx.dblClick(d);
232       });
233
234     // router name label
235     d3Router.append('text')
236       .attr('x', defaultRouterWidth * 0.5)
237       .attr('y', defaultRouterHeight + 15)
238       .attr('text-anchor', 'middle')
239       .attr('class', 'linfolabel')
240       .text(function (d) {
241         return d.name;
242       });
243
244     // vm name label
245     d3Vm.append('text')
246       .attr('x', defaultVmsWidth * 0.5)
247       .attr('y', defaultVmsHeight + 15)
248       .attr('text-anchor', 'middle')
249       .attr('class', 'linfolabel')
250       .text(function (d) {
251         return d.name;
252       });
253
254     // vm floating ip label
255     d3Vm.append('text')
256       .attr('x', -35)
257       .attr('y', 40)
258       .attr('text-anchor', 'middle')
259       .text(function (d) {
260         return (d.floatingIp) ? d.floatingIp.ip : '';
261       });
262
263   }
264
265   function findNetworkWithRouter(router) {
266     var result = [];
267     _.each(router.interfaces, function (inter) {
268       if (inter.type === 'router_interface') {
269         var net = tmpNetHolder[inter.networkId] || null;
270
271         if (net) {
272           result.push({
273             network: net,
274             interface: inter
275           });
276         }
277       }
278     });
279
280     return result;
281   }
282
283   function positionateNetwork(network, x, y, margin) {
284     network.x = x;
285     network.y = y;
286     margin = margin || 0;
287     network.color = d3.hsl(randomize.next() * 360, 1, 0.6).toString();
288
289     // look is the network is the highest
290     bbox.height = network.y > bbox.height ? network.y : bbox.height;
291     bbox.width = network.x > bbox.width ? network.x : bbox.width;
292
293     // get the number of "childs" (router, vm)
294     var nbRouter = network.routers.length;
295     var nbVm = network.instances.length;
296
297     if (!network.external) {
298       _.each(network.subnets, function (subnet, i) {
299         network.ip += subnet.cidr;
300         if (i < network.subnets.length - 1) {
301           network.ip += ', ';
302         }
303       });
304     }
305
306     // if needed, ajust the height of the network
307     // to be able to display all children
308     ajustHeighBaseOnChilds(nbRouter, nbVm);
309
310     var py = positionateRouter(network, x + routerMargin.width, y + margin);
311
312     positionateVm(network, x + vmMargin.width, py + 35 + margin);
313     delete tmpNetHolder[network.id];
314   }
315
316   function positionateRouter(network, x, y) {
317     var px = x,
318       py = y;
319
320     // loop over all routers
321     _.each(network.routers, function (router, i) {
322       router.x = getRouterCentroid(x, py).x;
323       router.y = py;
324       py += getRouterMarginHeight();
325
326       if (network.external) {
327         // find network ip with the gateway ip
328         var gateway = router.externalGateway.external_fixed_ips[0].ip_address;
329         var netIp = gateway.slice(0, gateway.lastIndexOf('.')) + '.0';
330         network.ip = netIp;
331       }
332
333       // look is the router is the highest
334       bbox.height = router.y > bbox.height ? router.y : bbox.height;
335       bbox.width = router.x > bbox.width ? router.x : bbox.width;
336
337       linkData.push({
338         source: {
339           x: network.x + (nodeWidth * 0.5),
340           y: router.y + (defaultRouterHeight * 0.5)
341         },
342         target: {
343           x: router.x + (defaultRouterWidth * 0.5),
344           y: router.y + (nodeWidth * 0.5)
345         },
346         color: network.color,
347         text: router.externalGateway.external_fixed_ips[0].ip_address
348       });
349
350       // go to the next layer
351       var nets = findNetworkWithRouter(router),
352         step = defaultRouterHeight / (nets.length + 1);
353
354       _.forEach(nets, function (net, i) {
355         var netPos = getNetworkLayerPosition(bbox.width + defaultRouterWidth);
356
357         positionateNetwork(net.network, netPos.x, netPos.y);
358         linkData.push({
359           source: {
360             x: router.x + (2 * nodeWidth),
361             y: router.y + step * (i + 1)
362           },
363           target: {
364             x: net.network.x + (nodeWidth * 0.5),
365             y: router.y + (nodeWidth * 0.5)
366           },
367           color: net.network.color,
368           text: net.interface.ip.ip_address
369         });
370       });
371     });
372     return py;
373   }
374
375   function positionateVm(network, x, y) {
376
377     // I do vm before router because router
378     // will step to another BUS
379     _.each(network.instances, function (vm) {
380       vm.x = x;
381       vm.y = y;
382
383       // look is the network is the highest
384       bbox.height = vm.y > bbox.height ? vm.y : bbox.height;
385       bbox.width = vm.x > bbox.width ? vm.x : bbox.width;
386
387       y += getVmMarginHeight();
388       linkData.push({
389         source: {
390           x: network.x + (nodeWidth * 0.5),
391           y: vm.y + (defaultVmsHeight * 0.5)
392         },
393         target: {
394           x: vm.x + (defaultVmsWidth * 0.5),
395           y: vm.y + (nodeWidth * 0.5)
396         },
397         color: network.color,
398         text: vm.ip
399       });
400     });
401   }
402
403   /*
404    *  Scan the whole "BUS" to display it properly
405    * ------------------------------------------------
406    *  I build it in a virtual space, if it need to be
407    *  resize it at the end when the overal bounding
408    *  box is known
409    */
410   function setTopologyPosition(networks) {
411     _.each(networks, function (net) {
412       tmpNetHolder[net.id] = net;
413     });
414
415     var i = 0;
416     for (var key in tmpNetHolder) {
417       var margin = (i === 0) ? 5 : networkMargin.width,
418         net = tmpNetHolder[key];
419       if (net.routers.length > 0) {
420         positionateNetwork(net, bbox.x + bbox.width + margin, bbox.y);
421         ++i;
422       }
423     }
424
425     for (var key in tmpNetHolder) {
426       var margin = networkMargin.width,
427         net = tmpNetHolder[key];
428       positionateNetwork(net, bbox.x + bbox.width + margin, bbox.y);
429     }
430   }
431
432   /*
433    * Check and ajust the height for a network.
434    */
435   function ajustHeighBaseOnChilds(nbRouter, nbVm) {
436     // calculate the height for the number of childs
437     var childHeight = nbRouter * (getRouterMarginHeight()) +
438       nbVm * (getVmMarginHeight()) + ipNetworkTextMaxLength;
439
440     // if heigh bigger than the default network height resize it
441     if (childHeight > nodeHeight) {
442       nodeHeight = childHeight + networkOffset;
443     }
444   }
445
446   /*
447    * Set the view to the modal position
448    */
449   function update() {
450
451     d3Node.attr('transform', function (d) {
452       return OvsCore.Util.String.Format("translate({0}, {1})",
453         d.x, d.y
454       );
455     });
456
457     d3Router.attr('transform', function (d, i) {
458       return OvsCore.Util.String.Format("translate({0}, {1})",
459         d.x, d.y
460       );
461     });
462
463     d3Vm.attr('transform', function (d) {
464       return OvsCore.Util.String.Format("translate({0}, {1})",
465         d.x, d.y
466       );
467     });
468
469     d3Link.attr('transform', function (d) {
470       return OvsCore.Util.String.Format("translate({0}, {1})",
471         d.source.x, d.source.y
472       );
473     });
474
475     // resize the graph if bigger than the canvas
476     var bbox = root.node().getBBox();
477     if (bbox.width > canvasWidth || bbox.height > canvasHeight) {
478       var sx = (canvasWidth - 30) / bbox.width,
479         sy = (canvasHeight - 30) / bbox.height,
480         s = sx < sy ? sx : sy;
481       d3.select('.layer_0').attr('transform', 'scale(' + s + ')');
482       console.log(root.node().getBBox());
483     }
484   }
485
486   function getRouterCentroid(x, y) {
487     return {
488       x: x + defaultRouterWidth * 0.5,
489       y: y + defaultRouterHeight * 0.5
490     };
491   }
492
493   function getRouterMarginHeight() {
494     return (defaultRouterHeight + routerMargin.height);
495   }
496
497   function getVmMarginHeight() {
498     return (defaultVmsHeight + vmMargin.height);
499   }
500
501   function getNetworkLayerPosition(x) {
502     return {
503       x: x + networkMargin.width,
504       y: networkMargin.height
505     };
506   }
507
508   function getVmLayerPosition(nbRouter, x) {
509     var t = {
510       x: x + vmMargin.width * 2,
511       y: getRoutersDim(nbRouter).height + getVmMarginHeight()
512     };
513     return t;
514   }
515
516   LogicalGraph.prototype.onClick = _.noop;
517   LogicalGraph.prototype.dblClick = _.noop;
518
519   return LogicalGraph;
520 });