horizon/openstack_dashboard/static/js/horizon.flatnetworktopology.js
Kenji Ishii 295175826b Move js files into openstack_dashboard folder.
hozoin doc says that the horizon directory holds the generic
libraries and components that can be used in any Django project.

So, this patch move js files related with Openstack components into
openstack_dashboard folder.

Change-Id: Ib4ed05e1136ee3f70a201139e456d4c3b93519f3
Closes-bug: #1635519
2016-12-18 15:27:42 +09:00

634 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(type, device_id) {
var message = {id:device_id};
horizon.networktopologymessager.post_message(device_id,type,message,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;
}
}
};