horizon/openstack_dashboard/static/js/horizon.flatnetworktopology.js
MinSun 73f8675148 Support to delete instance from network topology view
When try to delete an instance in network topology view, horizon would
try to load project/instance table in background, then do the delete
row action to the instance.
But the project instance table was paginated, it only load the first
page instances. The delete action would not work if you try to delete
an instance that are not in the first page.
The patch try to fix the bug.

Change-Id: I317bd1ee418d19c075ae3ac8d39563a0514b1795
Closes-Bug: #1597677
2017-01-20 21:19:54 +08:00

635 lines
21 KiB
JavaScript

/**
* 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. */
horizon.flat_network_topology = {
model: null,
fa_globe_glyph: '\uf0ac',
fa_globe_glyph_width: 15,
svg:'#topology_canvas',
svg_container:'#flatTopologyCanvasContainer',
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: {},
balloon_id:null,
reload_duration: 10000,
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;
$(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();
});
$('.toggle-view > .btn').click(function(){
self.draw_mode = $(this).data('value');
$('g.network').remove();
horizon.cookies.put('ntp_draw_mode',self.draw_mode);
self.data_convert();
});
$('#networktopology').on('change', function() {
self.load_network_info();
});
// register for message notifications
//horizon.networktopologymessager.addMessageHandler(self.handleMessage, this);
},
/*handleMessage:function(message) {
// noop
},*/
load_network_info:function(){
var self = this;
self.model = horizon.networktopologyloader.model;
self.data_convert();
},
select_draw_mode:function() {
var self = this;
var draw_mode = 'normal';
try {
draw_mode = horizon.cookies.get('ntp_draw_mode');
}
catch(e) {
// if the cookie does not exist, angular-cookie passes "undefined" to
// JSON.parse which throws an exception
}
if (draw_mode && (draw_mode === 'normal' || draw_mode === 'small')) {
self.draw_mode = draw_mode;
} else {
if (self.model.networks.length *
self.element_properties.normal.network_width > $('#topologyCanvas').width()) {
self.draw_mode = 'small';
} else {
self.draw_mode = 'normal';
}
horizon.cookies.put('ntp_draw_mode',self.draw_mode);
}
$('.toggle-view > .btn').each(function(){
var $this = $(this);
if($this.data('value') === self.draw_mode) {
$this.addClass('active');
} else {
$this.removeClass('active');
}
});
},
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;
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;
}
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);
network.enter()
.append('g')
.attr('class','network')
.each(function(d){
this.appendChild(d3.select(self.network_tmpl[self.draw_mode]).node().cloneNode(true));
var $this = d3.select(this).select('.network-rect');
if (d.url) {
$this
.on('mouseover',function(){
$this.transition().style('fill', function() {
return d3.rgb(self.get_network_color(d.id)).brighter(0.5);
});
})
.on('mouseout',function(){
$this.transition().style('fill', function() {
return self.get_network_color(d.id);
});
})
.on('click',function(){
window.location.href = d.url;
});
} else {
$this.classed('nourl', true);
}
});
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() { return self.network_height; })
.style('fill', function(d) { return self.get_network_color(d.id); });
network
.select('.network-name')
.attr('x', function() { return self.network_height/2; })
.text(function(d) { return d.name; });
network
.select('.network-cidr')
.attr('x', function(d) {
var padding = isExternalNetwork(d) ? self.fa_globe_glyph_width : 0;
return self.network_height - self.element_properties.cidr_margin -
padding;
})
.text(function(d) {
var cidr = $.map(d.subnets,function(n){
return n.cidr;
});
return cidr.join(', ');
});
function isExternalNetwork(d) {
return d['router:external'];
}
network
.select('.network-type')
.text(function(d) {
return isExternalNetwork(d) ? self.fa_globe_glyph : '';
})
.attr('x', function() {
return self.network_height - self.element_properties.cidr_margin;
});
$('[data-toggle="tooltip"]').tooltip({container: 'body'});
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){
var device_template = self[d.type + '_tmpl'][self.draw_mode];
this.appendChild(d3.select(device_template).node().cloneNode(true));
});
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){
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){
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();
});
});
port.each(function(d){
var index_diff = self.get_network_index(this.parentNode._portdata.parent_network) -
self.get_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(){
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() {
return this.parentNode.parentNode._portdata.port_height;
})
.attr('stroke', function(d) {
return self.get_network_color(d.network_id);
})
.attr('x1',0).attr('y1',0).attr('y2',0)
.attr('x2',function() {
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() {
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() {
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();
},
get_network_color: function(network_id) {
return this.color(this.get_network_index(network_id));
},
get_network_index: function(network_id) {
return this.network_index[network_id];
},
select_port: function(device_id){
return $.map(this.model.ports,function(port){
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;
var sum_port_length = 0;
var base_index = self.get_network_index(network_id);
$.each(ports, function(index, port){
sum_port_length += base_index - self.get_network_index(port.network_id);
});
return sum_port_length;
},
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 str;
},
delete_device: function(device_type, device_id) {
var message = {id:device_id};
var target = device_type === 'instance' ? 'instance?id=' + device_id : device_type;
horizon.networktopologymessager.post_message(device_id, target, message, device_type, 'delete', data={});
},
delete_port: function(router_id, port_id, network_id) {
var message = {id:port_id};
var data = {router_id: router_id, network_id: network_id};
horizon.networktopologymessager.post_message(port_id, 'router/' + router_id + '/', message, 'port', 'delete', data);
},
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 = gettext('None');
}
var device_owner = '';
try {
device_owner = port.device_owner.replace('network:','');
}catch(e){
device_owner = gettext('None');
}
var network_id = '';
try {
network_id = port.network_id;
}catch(e) {
network_id = gettext('None');
}
object.network_id = network_id;
object.ip_address = ip_address;
object.device_owner = device_owner;
object.is_interface = (device_owner === 'router_interface' || device_owner === 'router_gateway');
ports.push(object);
});
var html;
var html_data = {
balloon_id:balloon_id,
id:d.id,
url:d.url,
name:d.name,
type:d.type,
delete_label: gettext("Delete"),
status:d.status,
status_class:(d.status === "ACTIVE")? 'active' : 'down',
status_label: gettext("STATUS"),
id_label: gettext("ID"),
interfaces_label: gettext("Interfaces"),
delete_interface_label: gettext("Delete Interface"),
open_console_label: gettext("Open Console"),
view_details_label: gettext("View Details")
};
if (d.type === 'router') {
html_data.delete_label = gettext("Delete Router");
html_data.view_details_label = gettext("View Router Details");
html_data.port = ports;
html_data.add_interface_url = d.url + 'addinterface';
html_data.add_interface_label = gettext("Add Interface");
html = balloon_tmpl.render(html_data,{
table1:device_tmpl,
table2:(ports.length > 0) ? port_tmpl : null
});
} else if (d.type === 'instance') {
html_data.delete_label = gettext("Delete Instance");
html_data.view_details_label = gettext("View Instance Details");
html_data.console_id = d.id;
html_data.console = d.console;
html = balloon_tmpl.render(html_data,{
table1:device_tmpl
});
} else {
return;
}
$(self.svg_container).append(html);
var device_position = element.find('.frame');
var sidebar_width = $("#sidebar").width();
var navbar_height = $(".navbar").height();
var breadcrumb_height = $(".breadcrumb").outerHeight(true);
var pageheader_height = $(".page-header").outerHeight(true);
var launchbuttons_height = $(".launchButtons").height();
var height_offset = navbar_height + breadcrumb_height + pageheader_height + launchbuttons_height;
var device_offset = device_position.offset();
var x = Math.round(device_offset.left + element_properties.device_width + element_properties.balloon_margin.x - sidebar_width);
// 15 is magic pixel value that seems to make things line up
var y = Math.round(device_offset.top + element_properties.balloon_margin.y - height_offset + 15);
var $balloon = $('#' + balloon_id);
$balloon.css({
'left': '0px',
'top': y + 'px'
});
var balloon_width = $balloon.outerWidth();
var left_x = device_offset.left - balloon_width - element_properties.balloon_margin.x - sidebar_width;
var right_x = x + balloon_width + element_properties.balloon_margin.x + sidebar_width;
if (left_x > 0 && right_x > $(window).outerWidth()) {
x = left_x;
$balloon.addClass('leftPosition');
}
$balloon.css({
'left': x + 'px'
}).show();
$balloon.find('.delete-device').click(function(){
var $this = $(this);
$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').click(function(){
var $this = $(this);
self.delete_port($this.data('router-id'),$this.data('port-id'),$this.data('network-id'));
});
self.balloon_id = balloon_id;
},
delete_balloon:function() {
var self = this;
if(self.balloon_id) {
$('#' + self.balloon_id).remove();
self.balloon_id = null;
}
}
};