From 88b9335910771b52979735ce90c9d9b1e35ba5f6 Mon Sep 17 00:00:00 2001 From: Toshi Hayashi Date: Mon, 12 Aug 2013 17:20:38 -0700 Subject: [PATCH] Delete and launch devices on the topology view This feature enables to delete and launch a instance or router on the network topology view. So you can do basic actions on this view. Also it enables to change view small or normal. You can see more networks and devices in the small view. implements bp editable-network-topology-view fixes bug #1215683 Change-Id: Ie65d50d2a99f72696c8f10223f8430ad5f90b144 --- .../horizon/js/horizon.networktopology.js | 787 ++++++++++++------ manage.py | 2 +- .../network_topology/instances/__init__.py | 0 .../network_topology/instances/tables.py | 25 + .../network_topology/ports/__init__.py | 0 .../project/network_topology/ports/tables.py | 31 + .../network_topology/routers/__init__.py | 0 .../network_topology/routers/tables.py | 33 + .../network_topology/_create_router.html | 21 + .../network_topology/_post_massage.html | 27 + .../network_topology/_svg_element.html | 213 +++++ .../client_side/_balloon_container.html | 29 + .../client_side/_balloon_device.html | 24 + .../client_side/_balloon_port.html | 33 + .../network_topology/create_router.html | 11 + .../templates/network_topology/iframe.html | 12 + .../templates/network_topology/index.html | 40 +- .../project/network_topology/urls.py | 31 +- .../project/network_topology/views.py | 133 ++- .../static/dashboard/less/horizon.less | 501 +++++------ 20 files changed, 1382 insertions(+), 571 deletions(-) create mode 100644 openstack_dashboard/dashboards/project/network_topology/instances/__init__.py create mode 100644 openstack_dashboard/dashboards/project/network_topology/instances/tables.py create mode 100644 openstack_dashboard/dashboards/project/network_topology/ports/__init__.py create mode 100644 openstack_dashboard/dashboards/project/network_topology/ports/tables.py create mode 100644 openstack_dashboard/dashboards/project/network_topology/routers/__init__.py create mode 100644 openstack_dashboard/dashboards/project/network_topology/routers/tables.py create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_create_router.html create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_post_massage.html create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_svg_element.html create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_container.html create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_device.html create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_port.html create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/create_router.html create mode 100644 openstack_dashboard/dashboards/project/network_topology/templates/network_topology/iframe.html diff --git a/horizon/static/horizon/js/horizon.networktopology.js b/horizon/static/horizon/js/horizon.networktopology.js index dd4bd7e2e8..8e6c3612d1 100644 --- a/horizon/static/horizon/js/horizon.networktopology.js +++ b/horizon/static/horizon/js/horizon.networktopology.js @@ -1,201 +1,447 @@ /* Namespace for core functionality related to Network Topology. */ + horizon.network_topology = { model: null, - network_margin: 270, - topologyCanvas_padding: 120, - min_network_height:500, - port_margin: 20, - device_initial_position : 40, - device_last_position : 0, - device_left_position : 90, - device_margin : 20, - device_min_height : 45, - port_initial_position: 1, + svg:'#topology_canvas', + svg_container:'#topologyCanvasContainer', + post_messages:'#topologyMessages', + network_tmpl:{ + small:'#topology_template > .network_container_small', + normal:'#topology_template > .network_container_normal' + }, + router_tmpl: { + small:'#topology_template > .router_small', + normal:'#topology_template > .router_normal' + }, + instance_tmpl: { + small:'#topology_template > .instance_small', + normal:'#topology_template > .instance_normal' + }, + balloon_tmpl : null, + balloon_device_tmpl : null, + balloon_port_tmpl : null, network_index: {}, - network_color_unit: 0, - network_saturation: 1, - network_lightness: 0.7, + balloon_id:null, reload_duration: 10000, - spinner:null, - init:function(){ + draw_mode:'normal', + network_height : 0, + previous_message : null, + element_properties:{ + normal:{ + network_width:270, + network_min_height:500, + top_margin:80, + default_height:50, + margin:20, + device_x:98.5, + device_width:90, + port_margin:16, + port_height:6, + port_width:82, + port_text_margin:{x:6,y:-4}, + texts_bg_y:32, + type_y:46, + balloon_margin:{x:12,y:-12} + }, + small :{ + network_width:100, + network_min_height:400, + top_margin:50, + default_height:20, + margin:30, + device_x:47.5, + device_width:20, + port_margin:5, + port_height:3, + port_width:32.5, + port_text_margin:{x:0,y:0}, + texts_bg_y:0, + type_y:0, + balloon_margin:{x:12,y:-30} + }, + cidr_margin:5, + device_name_max_size:9, + device_name_suffix:'..' + }, + init:function() { var self = this; - $("#topologyCanvas").spin(horizon.conf.spinner_options.modal); - self.retrieve_network_info(); + $(self.svg_container).spin(horizon.conf.spinner_options.modal); + if($('#networktopology').length === 0) { + return; + } + self.color = d3.scale.category10(); + self.balloon_tmpl = Hogan.compile($('#balloon_container').html()); + self.balloon_device_tmpl = Hogan.compile($('#balloon_device').html()); + self.balloon_port_tmpl = Hogan.compile($('#balloon_port').html()); + + $(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 vnc_window = window.open($(this).attr('href'), vnc_window, 'width=760,height=560'); + self.delete_balloon(); + }) + .click(function(){ + self.delete_balloon(); + }); + + $('.toggleView > .btn').click(function(){ + self.draw_mode = $(this).data('value'); + $('g.network').remove(); + $.cookie('ntp_draw_mode',self.draw_mode); + self.data_convert(); + }); + + $(window) + .on('message',function(e){ + var message = JSON.parse(e.originalEvent.data); + if (self.previous_message != message.message) { + horizon.alert(message.type, message.message); + horizon.autoDismissAlerts(); + self.previous_message = message.message; + self.delete_post_message(message.iframe_id); + self.load_network_info(); + setTimeout(function() { + self.previous_message = null; + },10000); + } + }); + + self.load_network_info(); setInterval(function(){ - self.retrieve_network_info(); + self.load_network_info(); }, self.reload_duration); }, - retrieve_network_info: function(){ + load_network_info:function(){ var self = this; - if($("#networktopology").length === 0) { - return; + if($('#networktopology').length === 0) { + return; } - $.getJSON($("#networktopology").data("networktopology"), + $.getJSON($('#networktopology').data('networktopology') + '?' + $.now(), function(data) { - self.draw_graph(data); + self.model = data; + self.data_convert(); } ); }, - draw_loading: function () { - $("#topologyCanvas").spin(horizon.conf.spinner_options.modal); - }, - draw_graph: function(data){ - var canvas = $("#topologyCanvas"); - var networks = $("#topologyCanvas > .networks"); - var nodata = $("#topologyCanvas > .nodata"); - networks.show(); - nodata.hide(); - canvas.spin(false); - networks.empty(); - this.model = data; - this.device_last_position = this.device_initial_position; - var network_elements = this.draw_networks(); - var router_elements = this.draw_routers(); - var server_elements = this.draw_servers(); - if ((network_elements + router_elements + server_elements) <= 0){ - networks.hide(); - nodata.show(); + select_draw_mode:function() { + var self = this; + var draw_mode = $.cookie('ntp_draw_mode'); + if (draw_mode && (draw_mode == 'normal'| draw_mode == 'small')) { + self.draw_mode = draw_mode; } else { - canvas.height( - Math.max(this.device_last_position + this.topologyCanvas_padding, this.min_network_height) - ); - networks.width( - this.model.networks.length * this.network_margin - ); + if (self.model.networks.length * + self.element_properties.normal.network_width > $('#topologyCanvas').width()) { + self.draw_mode = 'small'; + } else { + self.draw_mode = 'normal'; + } + $.cookie('ntp_draw_mode',self.draw_mode); } + $('.toggleView > .btn').each(function(){ + var $this = $(this); + if($this.hasClass(self.draw_mode)) { + $this.addClass('active'); + } + }); }, - network_color: function(network_id){ - var max_hue = 360; - var num_network = this.model.networks.length; - if(num_network <= 0){ + data_convert:function() { + var self = this; + var model = self.model; + $.each(model.networks, function(index, network) { + self.network_index[network.id] = index; + }); + self.select_draw_mode(); + var element_properties = self.element_properties[self.draw_mode]; + self.network_height = element_properties.top_margin; + $.each([ + {model:model.routers, type:'router'}, + {model:model.servers, type:'instance'} + ], function(index, devices) { + var type = devices.type; + var model = devices.model; + $.each(model, function(index, device) { + device.type = type; + device.ports = self.select_port(device.id); + var hasports = (device.ports.length <= 0) ? false : true; + device.parent_network = (hasports) ? + self.select_main_port(device.ports).network_id : self.model.networks[0].id; + var height = element_properties.port_margin*(device.ports.length - 1); + device.height = + (self.draw_mode == 'normal' && height > element_properties.default_height) ? height : + element_properties.default_height; + device.pos_y = self.network_height; + device.port_height = + (self.draw_mode == 'small' && height > device.height) ? 1 : + element_properties.port_height; + device.port_margin = + (self.draw_mode == 'small' && height > device.height) ? + device.height/device.ports.length : + element_properties.port_margin; + self.network_height += device.height + element_properties.margin; + }); + }); + $.each(model.networks, function(index, network) { + network.devices = []; + $.each([model.routers, model.servers],function(index, devices) { + $.each(devices,function(index, device) { + if(network.id == device.parent_network) { + network.devices.push(device); + } + }); + }); + }); + self.network_height += element_properties.top_margin; + self.network_height = (self.network_height > element_properties.network_min_height) ? + self.network_height : element_properties.network_min_height; + self.draw_topology(); + }, + draw_topology:function() { + var self = this; + $(self.svg_container).spin(false); + $(self.svg_container).removeClass('noinfo'); + if (self.model.networks.length <= 0) { + $('g.network').remove(); + $(self.svg_container).addClass('noinfo'); return; } - num_network ++; - var hue = Math.floor( - max_hue/num_network*(this.network_index(network_id) + 1)); - return this.hsv2rgb( - hue, this.network_saturation, this.network_lightness); - }, - //see http://en.wikipedia.org/wiki/HSL_and_HSV - hsv2rgb:function (h, s, v) { - var hi = Math.round(h/60) % 6; - var f = h/60 - hi; - var p = v*(1 - s); - var q = v*(1 - f*s); - var t = v*(1 - (1 - f)*s); - switch(hi){ - case 0: - r = v; - g = t; - b = p; - break; - case 1: - r = q; - g = v; - b = p; - break; - case 2: - r = p; - g = v; - b = t; - break; - case 3: - r = p; - g = q; - b = v; - break; - case 4: - r = t; - g = p; - b = v; - break; - case 5: - r = v; - g = p; - b = q; - break; - } - return "rgb(" + Math.round(r*255) + "," + Math.round(g*255) + "," + Math.round(b*255) + ")"; - }, - draw_networks: function(){ - var self = this; - var networks = $("#topologyCanvas > .networks"); - $.each(self.model.networks, function(index, network){ - var label = (network.name != "")? network.name : network.id; - if(network['router:external']){ - label += " (external) "; - } - self.network_index[network.id] = index; - var network_html = $("
").attr("id", network.id); - var nicname_html = $("

" + label + - "

" + self.select_cidr(network.id) + "
"); - if (network.url == undefined) { - nicname_html.addClass("nourl"); - } else { - nicname_html.click(function (){ - window.location.href = network.url; - }); - } - nicname_html - .css ( - {'background-color':self.network_color(network.id)}) - .appendTo(network_html); - networks.append(network_html); - }); - return self.model.networks.length; - }, - select_cidr:function(network_id){ - var cidr = []; - $.each(this.model.subnets, function(index, subnet){ - if(subnet.network_id != network_id){ - return; + var svg = d3.select(self.svg); + var element_properties = self.element_properties[self.draw_mode]; + svg + .attr('width',self.model.networks.length*element_properties.network_width) + .attr('height',self.network_height); + + var network = svg.selectAll('g.network') + .data(self.model.networks); + + var network_enter = network.enter() + .append('g') + .attr('class','network') + .each(function(d,i){ + this.appendChild(d3.select(self.network_tmpl[self.draw_mode]).node().cloneNode(true)); + var $this = d3.select(this).select('.network-rect'); + if (d.url) { + var $this = d3.select(this).select('.network-rect'); + $this + .on('mouseover',function(){ + $this.transition().style('fill', + function() { return d3.rgb(self.network_color(d.id)).brighter(0.5)}); + }) + .on('mouseout',function(){ + $this.transition().style('fill', + function() { return self.network_color(d.id)}); + }) + .on('click',function(){ + window.location.href = d.url; + }); + } else { + $this.classed('nourl', true); } - cidr.push(subnet.cidr); - }); - return cidr.join(', '); - }, - draw_devices: function(type){ - var self = this; - $.each(self.model[type + 's'], function(index, device){ - var id = device.id; - var name = (device.name != "")? device.name : device.id; - var ports = self.select_port(id); - if(ports.length <= 0){ - return; - } - var main_port = self.select_main_port(ports); - var parent_network = main_port.network_id; - var device_html = $("
"); - device_html - .attr('id', device.id) - .css({top: self.device_last_position, position: 'absolute'}) - .append($("" + type + "")) - .click(function (e){ - e.stopPropagation(); - window.location.href = device.url; + }); + + network + .attr('id',function(d) { return 'id_' + d.id; }) + .attr('transform',function(d,i){ + return 'translate(' + element_properties.network_width * i + ',' + 0 + ')'}) + .select('.network-rect') + .attr('height', function(d) { return self.network_height}) + .style('fill', function(d) { return self.network_color(d.id)}); + network + .select('.network-name') + .attr('x', function(d) { return self.network_height/2 }) + .text(function(d) { return d.name; }); + network + .select('.network-cidr') + .attr('x', function(d) { return self.network_height - self.element_properties.cidr_margin }) + .text(function(d) { + var cidr = $.map(d.subnets,function(n, i){ + return n.cidr; }); - var name_html = $("") - .html(device.name) - .attr('title', device.name) - .appendTo(device_html); - var port_position = self.port_initial_position; - $.each(ports, function(){ - var port = this; - var port_html = self.port_html(port); - port_position += self.port_margin; - self.port_css(port_html, port_position, parent_network, port.network_id); - device_html.append(port_html); + return cidr.join(', '); + }); + + network.exit().remove(); + + var device = network.selectAll('g.device') + .data(function(d) { return d.devices; }); + + var device_enter = device.enter() + .append("g") + .attr('class','device') + .each(function(d,i){ + var device_template = self[d.type + '_tmpl'][self.draw_mode]; + this.appendChild(d3.select(device_template).node().cloneNode(true)); }); - port_position += self.port_margin; - device_html.css( - {height: Math.max(self.device_min_height, port_position) + "px"}); - self.device_last_position += device_html.height() + self.device_margin; - $("#" + parent_network).append(device_html); - $('div.port span.ip').each(function(i, ip){ - $(ip).css('top', '-'+$(ip).height()+'px'); + + device_enter.on('mouseenter',function(d){ + var $this = $(this); + self.show_balloon(d,$this); + }) + .on('click',function(){ + d3.event.stopPropagation(); + }); + + device + .attr('id',function(d) { return 'id_' + d.id; }) + .attr('transform',function(d,i){ + return 'translate(' + element_properties.device_x + ',' + d.pos_y + ')'; + }) + .select('.frame') + .attr('height',function(d) { return d.height; }); + device + .select('.texts_bg') + .attr('y',function(d) { + return element_properties.texts_bg_y + d.height - element_properties.default_height; + }); + device + .select('.type') + .attr('y',function(d) { + return element_properties.type_y + d.height - element_properties.default_height; + }); + device + .select('.name') + .text(function(d) { return self.string_truncate(d.name); }); + device.each(function(d) { + if (d.status == 'BUILD') { + d3.select(this).classed('loading',true); + } else if (d.task == 'deleting') { + d3.select(this).classed('loading',true); + if ('bl_' + d.id == self.balloon_id) { + self.delete_balloon(); + } + } else { + d3.select(this).classed('loading',false); + if ('bl_' + d.id == self.balloon_id) { + var $this = $(this); + self.show_balloon(d,$this); + } + } + }); + + device.exit().each(function(d){ + if ('bl_' + d.id == self.balloon_id) { + self.delete_balloon(); + } + }).remove(); + + var port = device.select('g.ports') + .selectAll('g.port') + .data(function(d) { return d.ports; }); + + var port_enter = port.enter() + .append('g') + .attr('class','port') + .attr('id',function(d) { return 'id_' + d.id; }); + + port_enter + .append('line') + .attr('class','port_line'); + + port_enter + .append('text') + .attr('class','port_text'); + + device.select('g.ports').each(function(d,i){ + this._portdata = {}; + this._portdata.ports_length = d.ports.length; + this._portdata.parent_network = d.parent_network; + this._portdata.device_height = d.height; + this._portdata.port_height = d.port_height; + this._portdata.port_margin = d.port_margin; + this._portdata.left = 0; + this._portdata.right = 0; + $(this).mouseenter(function(e){ + e.stopPropagation(); }); }); - return self.model[type + 's'].length; + + port.each(function(d,i){ + var index_diff = self.network_index(this.parentNode._portdata.parent_network) - + self.network_index(d.network_id); + this._index_diff = index_diff = (index_diff >= 0)? ++index_diff : index_diff; + this._direction = (this._index_diff < 0)? 'right' : 'left'; + this._index = this.parentNode._portdata[this._direction] ++; + + }); + + port.attr('transform',function(d,i){ + var x = (this._direction == 'left') ? 0 : element_properties.device_width; + var ports_length = this.parentNode._portdata[this._direction]; + var distance = this.parentNode._portdata.port_margin; + var y = (this.parentNode._portdata.device_height - + (ports_length -1)*distance)/2 + this._index*distance; + return 'translate(' + x + ',' + y + ')'; + }); + + port + .select('.port_line') + .attr('stroke-width',function(d,i) { + return this.parentNode.parentNode._portdata.port_height; + }) + .attr('stroke',function(d,i) {return self.network_color(d.network_id)}) + .attr('x1',0).attr('y1',0).attr('y2',0) + .attr('x2',function(d,i) { + var parent = this.parentNode; + var width = (Math.abs(parent._index_diff) - 1)*element_properties.network_width + + element_properties.port_width; + return (parent._direction == 'left') ? -1*width : width; + }); + + port + .select('.port_text') + .attr('x',function(d) { + var parent = this.parentNode; + if (parent._direction == 'left') { + d3.select(this).classed('left',true); + return element_properties.port_text_margin.x*-1; + } else { + d3.select(this).classed('left',false); + return element_properties.port_text_margin.x; + } + }) + .attr('y',function(d) { return element_properties.port_text_margin.y }) + .text(function(d) { + var ip_label = []; + $.each(d.fixed_ips, function() { + ip_label.push(this.ip_address); + }); + return ip_label.join(','); + }); + + port.exit().remove(); + }, + network_color: function(network_id) { + return this.color(this.network_index(network_id)); + }, + network_index: function(network_id) { + return this.network_index[network_id]; + }, + select_port: function(device_id){ + return $.map(this.model.ports,function(port, index){ + if (port.device_id == device_id) { + return port; + } + }); + }, + select_main_port: function(ports){ + var _self = this; + var main_port_index = 0; + var MAX_INT = 4294967295; + var min_port_length = MAX_INT; + $.each(ports, function(index, port){ + var port_length = _self.sum_port_length(port.network_id, ports); + if(port_length < min_port_length){ + min_port_length = port_length; + main_port_index = index; + } + }); + return ports[main_port_index]; }, sum_port_length: function(network_id, ports){ var self = this; @@ -206,75 +452,152 @@ horizon.network_topology = { }); return sum_port_length; }, - select_main_port: function(ports){ - var main_port_index = 0; - var MAX_INT = 4294967295; - var min_port_length = MAX_INT; - $.each(ports, function(index, port){ - port_length = horizon.network_topology.sum_port_length(port.network_id, ports) - if(port_length < min_port_length){ - min_port_length = port_length; - main_port_index = index; + string_truncate: function(string) { + var self = this; + var str = string; + var max_size = self.element_properties.device_name_max_size; + var suffix = self.element_properties.device_name_suffix; + var bytes = 0; + for (var i = 0; i < str.length; i++) { + bytes += str.charCodeAt(i) <= 255 ? 1 : 2; + if (bytes > max_size) { + str = str.substr(0, i) + suffix; + break; } - }) - return ports[main_port_index]; + } + return str; }, - draw_routers: function(){ - return this.draw_devices('router'); - }, - draw_servers: function(){ - return this.draw_devices('server'); - }, - select_port: function(device_id){ - return $.map(this.model.ports,function(port, index){ - if (port.device_id == device_id) { - return port; - } - }); - }, - port_html: function(port){ + delete_device: function(type, device_id) { var self = this; - var port_html = $('
'); - var ip_label = ""; - $.each(port.fixed_ips, function(){ - ip_label += this.ip_address + "
"; - }) - var ip_html = $('').html(ip_label); - port_html - .append(ip_html) - .css({'background-color':self.network_color(port.network_id)}) - .click(function (e) { - e.stopPropagation(); - if(port.url != undefined) { - window.location.href = port.url; - } + var message = {id:device_id}; + self.post_message(device_id,type,message); + }, + delete_port: function(router_id, port_id) { + var self = this; + var message = {id:port_id}; + self.post_message(port_id, 'router/' + router_id + '/', message); + }, + show_balloon:function(d,element) { + var self = this; + var element_properties = self.element_properties[self.draw_mode]; + if (self.balloon_id) { + self.delete_balloon(); + } + var balloon_tmpl = self.balloon_tmpl; + var device_tmpl = self.balloon_device_tmpl; + var port_tmpl = self.balloon_port_tmpl; + var balloon_id = 'bl_' + d.id; + var ports = []; + $.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_css = (port.status == "ACTIVE")? 'active' : 'down'; + var ip_address = ''; + try { + ip_address = port.fixed_ips[0].ip_address; + }catch(e){ + ip_address = 'no info'; + } + var device_owner = ''; + try { + device_owner = port.device_owner.replace('network:',''); + }catch(e){ + device_owner = 'no info'; + } + object.ip_address = ip_address; + object.device_owner = device_owner; + object.is_interface = (device_owner == 'router_interface') ? true : false; + ports.push(object); + }); + var html_data = { + balloon_id:balloon_id, + id:d.id, + url:d.url, + name:d.name, + type:d.type, + type_capital:d.type.replace(/^\w/, function($0) {return $0.toUpperCase()}), + id:d.id, + status:d.status, + status_class:(d.status == "ACTIVE")? 'active' : 'down' + }; + if (d.type == 'router') { + html_data.port = ports; + html = balloon_tmpl.render(html_data,{ + table1:device_tmpl, + table2:port_tmpl }); - if(port.url == undefined) { - port_html.addClass("nourl"); + } else if (d.type == 'instance') { + html_data.console_id = d.id; + html = balloon_tmpl.render(html_data,{ + table1:device_tmpl + }); + } else { + return; } - return port_html; + $(self.svg_container).append(html); + var device_position = element.find('.frame'); + var x = device_position.position().left + + element_properties.device_width + + element_properties.balloon_margin.x; + var y = device_position.position().top + + element_properties.balloon_margin.y; + $('#' + balloon_id).css({ + 'left': x + 'px', + 'top': y + 'px' + }) + .show(); + var $balloon = $('#' + balloon_id); + if ($balloon.offset().left + $balloon.outerWidth() > $(window).outerWidth()) { + $balloon + .css({ + 'left': 0 + 'px' + }) + .css({ + 'left': device_position.position().left + - $balloon.outerWidth() + - element_properties.balloon_margin.x + 'px' + }) + .addClass('leftPosition'); + } + $balloon.find('.delete-device').click(function(e){ + var $this = $(this); + $this.addClass('deleting'); + d3.select('#id_' + $this.data('device-id')).classed('loading',true); + self.delete_device($this.data('type'),$this.data('device-id')); + }); + $balloon.find('.delete-port').click(function(e){ + var $this = $(this); + self.delete_port($this.data('router-id'),$this.data('port-id')); + }); + self.balloon_id = balloon_id; }, - port_css: function(port_html, position, network_a, network_b){ + delete_balloon:function() { var self = this; - var index_diff = self.network_index(network_a) - self.network_index(network_b); - var width = self.network_margin * index_diff; - var direction = "left"; - if(width < 0){ - direction = "right"; - width += self.network_margin; + if(self.balloon_id) { + $('#' + self.balloon_id).remove() + self.balloon_id = null; } - width = Math.abs(width) + self.device_left_position; - var port_css = {}; - port_css['width'] = width + "px"; - port_css['top'] = position + "px"; - port_css[direction] = (-width -3) + "px"; - port_html.addClass(direction).css(port_css); }, - network_index: function(network_id){ - return horizon.network_topology.network_index[network_id]; + post_message: function(id,url,message) { + var self = this; + var iframe_id = 'ifr_' + id; + var iframe = $('