/** * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ /* global Hogan */ /* Namespace for core functionality related to Network Topology. */ function Network(data) { for (var key in data) { if ({}.hasOwnProperty.call(data, key)) { this[key] = data[key]; } } this.iconType = 'text'; this.icon = '\uf0c2'; // Cloud this.collapsed = false; this.type = 'network'; this.instances = 0; } function ExternalNetwork(data) { for (var key in data) { if ({}.hasOwnProperty.call(data, key)) { this[key] = data[key]; } } this.collapsed = false; this.iconType = 'text'; this.icon = '\uf0ac'; // Globe this.instances = 0; } function Router(data) { for (var key in data) { if ({}.hasOwnProperty.call(data, key)) { this[key] = data[key]; } } this.iconType = 'path'; this.svg = 'router'; this.networks = []; this.ports = []; this.type = 'router'; } function Server(data) { for (var key in data) { if ({}.hasOwnProperty.call(data, key)) { this[key] = data[key]; } } this.iconType = 'text'; this.icon = '\uf108'; // Server this.networks = []; this.type = 'instance'; this.ip_addresses = []; } function listContains(obj, list) { // Function to help checking if an object is present on a list for (var i = 0; i < list.length; i++) { if (angular.equals(list[i], obj)) { return true; } } return false; } horizon.network_topology = { fa_globe_glyph: '\uf0ac', fa_globe_glyph_width: 15, svg:'#topology_canvas', nodes: [], links: [], data: [], zoom: d3.behavior.zoom(), data_loaded: false, svg_container:'#topologyCanvasContainer', balloonTmpl : null, balloon_deviceTmpl : null, balloon_portTmpl : null, balloon_netTmpl : null, balloon_instanceTmpl : null, network_index: {}, balloonID:null, network_height : 0, init:function() { var self = this; self.$loading_template = horizon.networktopologyloader.setup_loader($(self.svg_container)); if (angular.element('#networktopology').length === 0) { return; } self.data = {}; self.data.networks = {}; self.data.routers = {}; self.data.servers = {}; self.data.ports = {}; // Setup balloon popups self.balloonTmpl = Hogan.compile(angular.element('#balloon_container').html()); self.balloon_deviceTmpl = Hogan.compile(angular.element('#balloon_device').html()); self.balloon_portTmpl = Hogan.compile(angular.element('#balloon_port').html()); self.balloon_netTmpl = Hogan.compile(angular.element('#balloon_net').html()); self.balloon_instanceTmpl = Hogan.compile(angular.element('#balloon_instance').html()); angular.element(document) .on('click', 'a.closeTopologyBalloon', function(e) { e.preventDefault(); self.delete_balloon(); }) .on('click', '.topologyBalloon', function(e) { e.stopPropagation(); }) .on('click', 'a.vnc_window', function(e) { e.preventDefault(); var vncWindow = window.open(angular.element(this).attr('href'), vncWindow, 'width=760,height=560'); self.delete_balloon(); }); angular.element('#toggle_labels').change(function() { horizon.cookies.put('show_labels', this.checked); self.refresh_labels(); }); angular.element('#toggle_networks').change(function() { horizon.cookies.put('are_networks_collapsed', this.checked); self.refresh_networks(); self.refresh_labels(); }); angular.element('#center_topology').on('click', function() { this.blur(); // remove btn focus after click self.delete_balloon(); // move visualization to the center and reset scale self.vis.transition() .duration(1500) .attr('transform', 'translate(0,0)scale(1)'); // reset internal zoom translate and scale parameters so on next // move the objects do not jump to the old position self.zoom.translate([0,0]); self.zoom.scale(1); self.translate = null; }); angular.element(window).on('message', function(e) { var message = JSON.parse(e.originalEvent.data); if (self.previous_message !== message.message) { horizon.alert(message.type, message.message); self.previous_message = message.message; self.delete_post_message(message.iframe_id); if (message.type == 'success' && self.deleting_device) { self.remove_node_on_delete(); } self.retrieve_network_info(); setTimeout(function() { self.previous_message = null; },10000); } }); // set up loader first thing self.$loading_template.show(); self.create_vis(); self.force_direction(0.05,70,-700); if(horizon.networktopologyloader.model !== null) { self.retrieve_network_info(true); } d3.select(window).on('resize', function() { var width = angular.element('#topologyCanvasContainer').width(); var height = angular.element('#topologyCanvasContainer').height(); self.force.size([width, height]).resume(); }); angular.element('#networktopology').on('change', function() { self.retrieve_network_info(true); if(angular.equals(self.data.networks,{}) && angular.equals(self.data.routers,{}) && angular.equals(self.data.servers,{})){ $('.loader-inline').remove(); angular.element('#topologyCanvasContainer').find('svg').remove(); $(self.svg_container).addClass('noinfo'); return; } }); // register for message notifications horizon.networktopologymessager.addMessageHandler( this.handleMessage, this ); }, // Shows/Hides graph labels refresh_labels: function() { var show_labels = horizon.cookies.get('show_labels') == 'true'; angular.element('.nodeLabel').toggle(show_labels); }, // Collapses/Uncollapses networks in the graph refresh_networks: function() { var self = this; var are_collapsed = horizon.cookies.get('are_networks_collapsed') == 'true'; for (var n in self.nodes) { if ({}.hasOwnProperty.call(self.nodes, n)) { if (self.nodes[n].data instanceof Network || self.nodes[n].data instanceof ExternalNetwork) { self.collapse_network(self.nodes[n], are_collapsed); } } } }, // Load config from cookie load_config: function() { var self = this; var labels = horizon.cookies.get('show_labels') == 'true'; var networks = horizon.cookies.get('are_networks_collapsed') == 'true'; if(networks) { angular.element('#toggle_networks_label').addClass('active'); angular.element('#toggle_networks').prop('checked', networks); self.refresh_networks(); } if(labels) { angular.element('#toggle_labels_label').addClass('active'); angular.element('#toggle_labels').prop('checked', labels); self.refresh_labels(); } }, handleMessage:function(message) { var self = this; var deleteData = horizon.networktopologymessager.delete_data; horizon.modals.spinner.modal('hide'); if (message.type == 'success') { self.remove_node_on_delete(deleteData); } }, // Get the json data about the current deployment retrieve_network_info: function(force_start) { var self = this; self.data_loaded = true; self.load_topology(horizon.networktopologyloader.model); if (force_start) { var i = 0; self.force.start(); while (i <= 100) { self.force.tick(); i++; } } }, getScreenCoords: function(x, y) { var self = this; if (self.translate) { var xn = self.translate[0] + x * self.zoom.scale(); var yn = self.translate[1] + y * self.zoom.scale(); return { x: xn, y: yn }; } else { return { x: x, y: y }; } }, // Setup the main visualisation create_vis: function() { var self = this; angular.element('#topologyCanvasContainer').find('svg').remove(); // Main svg self.outer_group = d3.select('#topologyCanvasContainer').append('svg') .attr('width', '100%') .attr('height', angular.element(document).height() - 270 + "px") .attr('pointer-events', 'all') .append('g') .call(self.zoom .scaleExtent([0.1,1.5]) .on('zoom', function() { self.delete_balloon(); self.vis.attr('transform', 'translate(' + d3.event.translate + ')scale(' + self.zoom.scale() + ')'); self.translate = d3.event.translate; }) ) .on('dblclick.zoom', null); // Background for capturing mouse events self.outer_group.append('rect') .attr('width', '100%') .attr('height', '100%') .attr('fill', 'white') .on('click', function() { self.delete_balloon(); }); // svg wrapper for nodes to sit on self.vis = self.outer_group.append('g'); }, // Calculate the hulls that surround networks convex_hulls: function(nodes) { var net, _i, _len, _ref, _h, i; var hulls = {}; var networkids = {}; var k = 0; var offset = 40; while (k < nodes.length) { var n = nodes[k]; if (n.data !== undefined) { if (n.data instanceof Server) { _ref = n.data.networks; for (_i = 0, _len = _ref.length; _i < _len; _i++) { net = _ref[_i]; if (net instanceof Network) { _h = hulls[net.id] || (hulls[net.id] = []); _h.push([n.x - offset, n.y - offset]); _h.push([n.x - offset, n.y + offset]); _h.push([n.x + offset, n.y - offset]); _h.push([n.x + offset, n.y + offset]); } } } else if (n.data instanceof Network) { net = n.data; networkids[net.id] = n; _h = hulls[net.id] || (hulls[net.id] = []); _h.push([n.x - offset, n.y - offset]); _h.push([n.x - offset, n.y + offset]); _h.push([n.x + offset, n.y - offset]); _h.push([n.x + offset, n.y + offset]); } } ++k; } var hullset = []; for (i in hulls) { if ({}.hasOwnProperty.call(hulls, i)) { hullset.push({group: i, network: networkids[i], path: d3.geom.hull(hulls[i])}); } } return hullset; }, // Setup the force direction force_direction: function(grav, dist, ch) { var self = this; angular.element('[data-toggle="tooltip"]').tooltip({container: 'body'}); self.curve = d3.svg.line() .interpolate('cardinal-closed') .tension(0.85); self.fill = d3.scale.category10(); self.force = d3.layout.force() .gravity(grav) .linkDistance(function(d) { if (d.source.data instanceof Server || d.target.data instanceof Server) { if (d.source.data.networks) { return (dist * d.source.data.networks.length) + (5 * d.target.data.instances) + 20; } else if (d.target.data.networks) { return (dist * d.target.data.networks.length) + (5 * d.source.data.instances) + 20; } } else if (d.source.data instanceof Router || d.target.data instanceof Router) { if (d.source.data.networks) { if (d.source.data.networks.length === 0) { return dist + 20; } else if (d.target.data.instances) { return dist * d.source.data.networks.length + (10 * d.target.data.instances) + 20; } return dist * d.source.data.networks.length + 20; } else if (d.target.data.networks) { if (d.target.data.networks.length === 0) { return dist + 20; } else if (d.source.data.instances) { return dist * d.target.data.networks.length + (10 * d.source.data.instances) + 20; } return dist * d.source.data.networks.length + 20; } } else { return dist; } }) .charge(ch) .size([angular.element('#topologyCanvasContainer').width(), angular.element('#topologyCanvasContainer').height()]) .nodes(self.nodes) .links(self.links) .on('tick', function() { self.vis.selectAll('g.node') .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; }); self.vis.selectAll('line.link') .attr('x1', function(d) { return d.source.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }); self.vis.selectAll('path.hulls') .data(self.convex_hulls(self.vis.selectAll('g.node').data())) .attr('d', function(d) { return self.curve(d.path); }) .enter().insert('path', 'g') .attr('class', 'hulls') .style('fill', function(d) { return self.fill(d.group); }) .style('stroke', function(d) { return self.fill(d.group); }) .style('stroke-linejoin', 'round') .style('stroke-width', 10) .style('opacity', 0.2); }); }, // Create a new node new_node: function(data, x, y) { var self = this; data = {data: data}; if (x && y) { data.x = x; data.y = y; } self.nodes.push(data); var node = self.vis.selectAll('g.node').data(self.nodes); var nodeEnter = node.enter().append('g') .attr('class', 'node') .style('fill', 'white') .call(self.force.drag); nodeEnter.append('circle') .attr('class', 'frame') .attr('r', function(d) { switch (Object.getPrototypeOf(d.data)) { case ExternalNetwork.prototype: return 35; case Network.prototype: return 30; case Router.prototype: return 25; case Server.prototype: return 20; } }) .style('fill', 'white') .style('stroke', 'black') .style('stroke-width', 3); switch (data.data.iconType) { case 'text': nodeEnter.append('text') .style('fill', 'black') .style('font', '20px FontAwesome') .attr('text-anchor', 'middle') .attr('dominant-baseline', 'central') .text(function(d) { return d.data.icon; }) .attr('transform', function(d) { switch (Object.getPrototypeOf(d.data)) { case ExternalNetwork.prototype: return 'scale(2.5)'; case Network.prototype: return 'scale(1.5)'; case Server.prototype: return 'scale(1)'; } }); break; case 'path': nodeEnter.append('path') .attr('class', 'svgpath') .style('fill', 'black') .attr('d', function(d) { return self.svgs(d.data.svg); }) .attr('transform', function() { return 'scale(1.2)translate(-16,-15)'; }); break; } nodeEnter.append('text') .attr('class', 'nodeLabel') .style('display',function() { var labels = horizon.cookies.get('topology_labels'); if (labels) { return 'inline'; } else { return 'none'; } }) .style('fill','black') .text(function(d) { return d.data.name; }) .attr('transform', function(d) { switch (Object.getPrototypeOf(d.data)) { case ExternalNetwork.prototype: return 'translate(40,3)'; case Network.prototype: return 'translate(35,3)'; case Router.prototype: return 'translate(30,3)'; case Server.prototype: return 'translate(25,3)'; } }); if (data.data instanceof Network || data.data instanceof ExternalNetwork) { nodeEnter.append('svg:text') .attr('class','vmCount') .style('fill', 'black') .style('font-size','20') .text('') .attr('transform', 'translate(26,38)'); } nodeEnter.on('click', function(d) { self.show_balloon(d.data, d, angular.element(this)); }); // Highlight the links for currently selected node nodeEnter.on('mouseover', function(d) { self.vis.selectAll('line.link').filter(function(z) { if (z.source === d || z.target === d) { return true; } else { return false; } }).style('stroke-width', '3px'); }); // Remove the highlight on the links nodeEnter.on('mouseout', function() { self.vis.selectAll('line.link').style('stroke-width','1px'); }); }, collapse_network: function(d, only_collapse) { var self = this; var server, vm; var filterNode = function(obj) { return function(d) { return obj == d.data; }; }; if (!d.data.collapsed) { var vmCount = 0; for (vm in self.data.servers) { if (self.data.servers[vm] !== undefined) { if (self.data.servers[vm].networks.length == 1) { if (self.data.servers[vm].networks[0].id == d.data.id) { vmCount += 1; self.removeNode(self.data.servers[vm]); } } } } d.data.collapsed = true; if (vmCount > 0) { self.vis.selectAll('.vmCount').filter(filterNode(d.data))[0][0].textContent = vmCount; } } else if (!only_collapse) { for (server in self.data.servers) { if ({}.hasOwnProperty.call(self.data.servers, server)) { var _vm = self.data.servers[server]; if (_vm !== undefined) { if (_vm.networks.length === 1) { if (_vm.networks[0].id == d.data.id) { self.new_node(_vm, d.x, d.y); self.new_link(self.find_by_id(_vm.id), self.find_by_id(d.data.id)); self.force.start(); } } } } } d.data.collapsed = false; self.vis.selectAll('.vmCount').filter(filterNode(d.data))[0][0].textContent = ''; var i = 0; while (i <= 100) { self.force.tick(); i++; } } }, new_link: function(source, target) { var self = this; self.links.push({source: source, target: target}); var line = self.vis.selectAll('line.link').data(self.links); line.enter().insert('line', 'g.node') .attr('class', 'link') .attr('x1', function(d) { return d.source.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }) .style('stroke', 'black') .style('stroke-width', 2); }, find_by_id: function(id) { var self = this; var obj, _i, _len, _ref; _ref = self.vis.selectAll('g.node').data(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { obj = _ref[_i]; if (obj.data.id == id) { return obj; } } return undefined; }, already_in_graph: function(data, node) { // Check for gateway that may not have unique id if (data == this.data.ports) { for (var p in data) { if (JSON.stringify(data[p]) == JSON.stringify(node)) { return true; } } return false; } // All other node types have UUIDs for (var n in data) { if (n == node.id) { return true; } } return false; }, load_topology: function(data) { var self = this; var net, _i, _netlen, _netref, rou, _j, _roulen, _rouref, port, _l, _portlen, _portref, ser, _k, _serlen, _serref, obj, vmCount; var change = false; var filterNode = function(obj) { return function(d) { return obj == d.data; }; }; // Networks _netref = data.networks; for (_i = 0, _netlen = _netref.length; _i < _netlen; _i++) { net = _netref[_i]; var network = null; if (net['router:external'] === true) { network = new ExternalNetwork(net); } else { network = new Network(net); } if (!self.already_in_graph(self.data.networks, network)) { self.new_node(network); change = true; } else { obj = self.find_by_id(network.id); if (obj) { network.collapsed = obj.data.collapsed; network.instances = obj.data.instances; obj.data = network; } } self.data.networks[network.id] = network; } // Routers _rouref = data.routers; for (_j = 0, _roulen = _rouref.length; _j < _roulen; _j++) { rou = _rouref[_j]; var router = new Router(rou); if (!self.already_in_graph(self.data.routers, router)) { self.new_node(router); change = true; } else { obj = self.find_by_id(router.id); if (obj) { // Keep networks list router.networks = obj.data.networks; // Keep ports list router.ports = obj.data.ports; obj.data = router; } } self.data.routers[router.id] = router; } // Servers _serref = data.servers; for (_k = 0, _serlen = _serref.length; _k < _serlen; _k++) { ser = _serref[_k]; var server = new Server(ser); if (!self.already_in_graph(self.data.servers, server)) { self.new_node(server); change = true; } else { obj = self.find_by_id(server.id); if (obj) { // Keep networks list server.networks = obj.data.networks; // Keep ip address list server.ip_addresses = obj.data.ip_addresses; obj.data = server; } else if (self.data.servers[server.id] !== undefined) { // This is used when servers are hidden because the network is // collapsed server.networks = self.data.servers[server.id].networks; server.ip_addresses = self.data.servers[server.id].ip_addresses; } } self.data.servers[server.id] = server; } // Ports _portref = data.ports; for (_l = 0, _portlen = _portref.length; _l < _portlen; _l++) { port = _portref[_l]; if (!self.already_in_graph(self.data.ports, port)) { var device = self.find_by_id(port.device_id); var _network = self.find_by_id(port.network_id); if (angular.isDefined(device) && angular.isDefined(_network)) { if (port.device_owner && port.device_owner.startsWith('compute:')) { _network.data.instances++; device.data.networks.push(_network.data); if (port.fixed_ips) { for(var ip in port.fixed_ips) { if (!listContains(port.fixed_ips[ip], device.data.ip_addresses)) { device.data.ip_addresses.push(port.fixed_ips[ip]); } } } // Remove the recently added node if connected to a network which is // currently collapsed if (_network.data.collapsed) { if (device.data.networks.length == 1) { self.data.servers[device.data.id].networks = device.data.networks; self.data.servers[device.data.id].ip_addresses = device.data.ip_addresses; self.removeNode(self.data.servers[port.device_id]); vmCount = Number(self.vis.selectAll('.vmCount').filter(filterNode(_network.data))[0][0].textContent); self.vis.selectAll('.vmCount').filter(filterNode(_network.data))[0][0].textContent = vmCount + 1; continue; } } } else if (port.device_owner == 'network:router_interface') { device.data.networks.push(_network.data); device.data.ports.push(port); } else if (device.data.ports) { device.data.ports.push(port); } self.new_link(self.find_by_id(port.device_id), self.find_by_id(port.network_id)); change = true; } else if (angular.isDefined(_network) && port.device_owner && port.device_owner.startsWith('compute:')) { // Need to add a previously hidden node to the graph because it is // connected to more than 1 network if (_network.data.collapsed) { server = self.data.servers[port.device_id]; server.networks.push(_network.data); if (port.fixed_ips) { for(var ip in port.fixed_ips) { server.ip_addresses.push(port.fixed_ips[ip]); } } self.new_node(server); // decrease collapsed vm count on network vmCount = Number(self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent); if (vmCount == 1) { self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent = ''; } else { self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent = vmCount - 1; } // Add back in first network link self.new_link(self.find_by_id(port.device_id), self.find_by_id(server.networks[0].id)); // Add new link self.new_link(self.find_by_id(port.device_id), self.find_by_id(port.network_id)); change = true; } } } self.data.ports[port.id+port.device_id+port.network_id] = port; } if (change) { self.force.start(); } self.load_config(); self.$loading_template.hide(); }, removeNode: function(obj) { var filterNetwork, filterNode, n, node, _i, _len, _ref; _ref = this.nodes; for (_i = 0, _len = _ref.length; _i < _len; _i++) { n = _ref[_i]; if (n.data === obj) { node = n; break; } } if (node) { this.nodes.splice(this.nodes.indexOf(node), 1); filterNode = function(obj) { return function(d) { return obj === d.data; }; }; filterNetwork = function(obj) { return function(d) { return obj === d.network.data; }; }; if (obj instanceof Network) { this.vis.selectAll('.hulls').filter(filterNetwork(obj)).remove(); } this.vis.selectAll('g.node').filter(filterNode(obj)).remove(); return this.removeNodesLinks(obj); } }, removeNodesLinks: function(node) { var l, linksToRemove, _i, _j, _len, _len1, _ref; linksToRemove = []; _ref = this.links; for (_i = 0, _len = _ref.length; _i < _len; _i++) { l = _ref[_i]; if (l.source.data === node) { linksToRemove.push(l); } else if (l.target.data === node) { linksToRemove.push(l); } } for (_j = 0, _len1 = linksToRemove.length; _j < _len1; _j++) { l = linksToRemove[_j]; this.removeLink(l); } return this.force.resume(); }, removeLink: function(link) { var i, index, l, _i, _len, _ref; index = -1; _ref = this.links; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { l = _ref[i]; if (l === link) { index = i; break; } } if (index !== -1) { this.links.splice(index, 1); } return this.vis.selectAll('line.link').data(this.links).exit().remove(); }, delete_device: function(device_type, deviceId) { var message = {id:deviceId}; var target = device_type === 'instance' ? 'instance?id=' + deviceId : device_type; horizon.networktopologymessager.post_message(deviceId, target, message, device_type, 'delete', data={}); }, remove_node_on_delete: function(deleteData) { var self = this; var deviceId = deleteData.device_id; switch (deleteData.device_type) { case 'router': self.removeNode(self.data.routers[deviceId]); break; case 'instance': self.removeNode(self.data.servers[deviceId]); this.data.servers[deviceId] = undefined; break; case 'network': self.removeNode(self.data.networks[deviceId]); break; case 'port': self.removePortOrSubnet(deviceId, deleteData.device_data); break; } self.delete_balloon(); }, removePortOrSubnet: function(portId, deviceData) { var self = this; var routerId = deviceData.router_id; var networkId = deviceData.network_id; if (routerId) { for (var l in self.links) { var data = null; if(self.links[l].source.data.id == routerId && self.links[l].target.data.id == networkId) { data = self.links[l].source.data; } else if (self.links[l].target.data.id == routerId && self.links[l].source.data.id == networkId) { data = self.links[l].target.data; } if (data) { for (var p in data.ports) { if ((data.ports[p].id == portId) && (data.ports[p].network_id == networkId)) { self.removeLink(self.links[l]); // Update Router to remove deleted port var router = self.find_by_id(routerId); router.data.ports.splice(router.data.ports.indexOf(data.ports[p]), 1); self.force.start(); return; } } } } } else { var networkData = self.find_by_id(networkId).data; var subnets = networkData.subnets; for (var subnet in subnets) { if (subnets[subnet].id === portId) { if (subnets.length == 1) { delete(networkData.subnets); } else { subnets.splice(subnet, 1); } self.force.start(); break; } } } }, delete_port: function(routerId, portId, networkId) { var message = {id:portId}; var data = {network_id:networkId,router_id:routerId}; if (routerId) { horizon.networktopologymessager.post_message(portId, 'router/' + routerId + '/', message, 'port', 'delete', data); } else { horizon.networktopologymessager.post_message(portId, 'network/' + networkId + '/?tab=network_tabs__subnets_tab', message, 'port', 'delete', data); } }, show_balloon: function(d,d2,element) { var self = this; var balloonTmpl = self.balloonTmpl; var deviceTmpl = self.balloon_deviceTmpl; var portTmpl = self.balloon_portTmpl; var netTmpl = self.balloon_netTmpl; var instanceTmpl = self.balloon_instanceTmpl; var balloonID = 'bl_' + d.id; var ports = []; var subnets = []; if (self.balloonID) { if (self.balloonID == balloonID) { self.delete_balloon(); return; } self.delete_balloon(); } self.force.stop(); if (d.hasOwnProperty('ports')) { angular.element.each(d.ports, function(i, port) { var object = {}; object.id = port.id; object.router_id = port.device_id; object.url = port.url; object.port_status = port.status; object.port_status_class = (port.original_status === 'ACTIVE') ? 'active' : 'down'; var ipAddress = ''; try { for (var ip in port.fixed_ips) { ipAddress += port.fixed_ips[ip].ip_address + ' '; } }catch(e) { ipAddress = gettext('None'); } var deviceOwner = ''; try { deviceOwner = port.device_owner.replace('network:',''); }catch(e) { deviceOwner = gettext('None'); } var networkId = ''; try { networkId = port.network_id; }catch(e) { networkId = gettext('None'); } object.ip_address = ipAddress; object.device_owner = deviceOwner; object.network_id = networkId; object.is_interface = ( deviceOwner === 'router_interface' || deviceOwner === 'router_gateway' || deviceOwner === 'ha_router_replicated_interface' ); ports.push(object); }); } else if (d.hasOwnProperty('subnets')) { angular.element.each(d.subnets, function(i, snet) { var object = {}; object.id = snet.id; object.cidr = snet.cidr; object.url = snet.url; subnets.push(object); }); } var htmlData = { balloon_id:balloonID, id:d.id, url:d.url, name:d.name, type:d.type, delete_label: gettext('Delete'), status:d.status, status_class: (d.original_status === 'ACTIVE') ? 'active' : 'down', status_label: gettext('STATUS'), id_label: gettext('ID'), interfaces_label: gettext('Interfaces'), subnets_label: gettext('Subnets'), delete_interface_label: gettext('Delete Interface'), delete_subnet_label: gettext('Delete Subnet'), open_console_label: gettext('Open Console'), view_details_label: gettext('View Details'), ips_label: gettext('IP Addresses') }; var html; if (d instanceof Router) { htmlData.delete_label = gettext('Delete Router'); htmlData.view_details_label = gettext('View Router Details'); htmlData.port = ports; htmlData.add_interface_url = 'router/' + d.id + '/addinterface'; htmlData.add_interface_label = gettext('Add Interface'); html = balloonTmpl.render(htmlData,{ table1:deviceTmpl, table2:portTmpl }); } else if (d instanceof Server) { htmlData.delete_label = gettext('Delete Instance'); htmlData.view_details_label = gettext('View Instance Details'); htmlData.console_id = d.id; htmlData.ips = d.ip_addresses; htmlData.console = d.console; html = balloonTmpl.render(htmlData,{ table1:deviceTmpl, table2:instanceTmpl }); } else if (d instanceof Network || d instanceof ExternalNetwork) { for (var s in subnets) { subnets[s].network_id = d.id; } htmlData.subnet = subnets; if (d instanceof Network) { htmlData.delete_label = gettext('Delete Network'); if (d.allow_delete_subnet){ htmlData.allow_delete_subnet = d.allow_delete_subnet; } } htmlData.add_subnet_url = 'network/' + d.id + '/subnet/create'; htmlData.add_subnet_label = gettext('Create Subnet'); html = balloonTmpl.render(htmlData,{ table1:deviceTmpl, table2:netTmpl }); } else { return; } angular.element(self.svg_container).append(html); var devicePosition = self.getScreenCoords(d2.x, d2.y); var x = devicePosition.x; var y = devicePosition.y; var xoffset = 100; var yoffset = 95; angular.element('#' + balloonID).css({ 'left': x + xoffset + 'px', 'top': y + yoffset + 'px' }).show(); var _balloon = angular.element('#' + balloonID); if (element.x + _balloon.outerWidth() > angular.element(window).outerWidth()) { _balloon .css({ 'left': 0 + 'px' }) .css({ 'left': (x - _balloon.outerWidth() + 'px') }) .addClass('leftPosition'); } _balloon.find('.delete-device').on('click', function() { var _this = angular.element(this); var delete_modal = horizon.datatables.confirm(_this); delete_modal.find('.btn.btn-danger').on('click', function () { _this.prop('disabled', true); d3.select('#id_' + _this.data('device-id')).classed('loading',true); self.delete_device(_this.data('type'),_this.data('device-id')); }); }); _balloon.find('.delete-port').on('click', function() { var _this = angular.element(this); var delete_modal = horizon.datatables.confirm(_this); delete_modal.find('.btn.btn-danger').on('click', function () { _this.prop('disabled', true); self.delete_port(_this.data('router-id'),_this.data('port-id'),_this.data('network-id')); }); }); self.balloonID = balloonID; }, delete_balloon:function() { var self = this; if (self.balloonID) { angular.element('#' + self.balloonID).remove(); self.balloonID = null; self.force.start(); } }, svgs: function(name) { switch (name) { case 'router': return 'm 26.628571,16.08 -8.548572,0 0,8.548571 2.08,-2.079998 6.308572,6.30857 4.38857,-4.388572 -6.308571,-6.30857 z m -21.2571429,-4.159999 8.5485709,0 0,-8.5485723 -2.08,2.08 L 5.5314281,-0.85714307 1.1428571,3.5314287 7.4514281,9.84 z m -3.108571,7.268571 0,8.548571 8.5485709,0 L 8.7314281,25.657144 15.039999,19.325715 10.674285,14.96 4.3428571,21.268573 z M 29.737142,8.8114288 l 0,-8.54857147 -8.548572,0 2.08,2.07999987 -6.308571,6.3085716 4.388572,4.3885722 6.308571,-6.3085723 z'; default: return ''; } } };