Adding old topology to compliment new
There has been user/operator feedback that the new network topology satisfies different needs than the old did. The two are actually complimentary rather than mutually exclusive. This patch allows for both topologies to be visible on separate tabs. Both views share the same data model, but renders that data differently. One interesting inclusion is triggering a resize event if an HTML element with the d3-container CSS class is present. The reason for this is that the svg content in a non-visible tab is rendered into a container with 0 height and width. This causes the contents to all sit in the top left corner of the container. Without the resize event which is used to trigger a redo of the force layout. The plus side is now this d3 based network topology handles window resize events. I am open to suggestions so that a resize event is not necessary on the tab show event. An additional area for improvement is the inline CSS in _svg_element.html Implements blueprint: restore-old-net-topology Change-Id: Iba6e6ad07b9ff7705f62cdb0281904880df6e4ba
This commit is contained in:
parent
1554e281bc
commit
dd80909edf
619
horizon/static/horizon/js/horizon.flatnetworktopology.js
Normal file
619
horizon/static/horizon/js/horizon.flatnetworktopology.js
Normal file
@ -0,0 +1,619 @@
|
|||||||
|
/* 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) ? 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;
|
||||||
|
}
|
||||||
|
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("Terminate 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -53,7 +53,6 @@ function Server(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
horizon.network_topology = {
|
horizon.network_topology = {
|
||||||
model: null,
|
|
||||||
fa_globe_glyph: '\uf0ac',
|
fa_globe_glyph: '\uf0ac',
|
||||||
fa_globe_glyph_width: 15,
|
fa_globe_glyph_width: 15,
|
||||||
svg:'#topology_canvas',
|
svg:'#topology_canvas',
|
||||||
@ -63,7 +62,6 @@ horizon.network_topology = {
|
|||||||
zoom: d3.behavior.zoom(),
|
zoom: d3.behavior.zoom(),
|
||||||
data_loaded: false,
|
data_loaded: false,
|
||||||
svg_container:'#topologyCanvasContainer',
|
svg_container:'#topologyCanvasContainer',
|
||||||
post_messages:'#topologyMessages',
|
|
||||||
balloonTmpl : null,
|
balloonTmpl : null,
|
||||||
balloon_deviceTmpl : null,
|
balloon_deviceTmpl : null,
|
||||||
balloon_portTmpl : null,
|
balloon_portTmpl : null,
|
||||||
@ -71,10 +69,7 @@ horizon.network_topology = {
|
|||||||
balloon_instanceTmpl : null,
|
balloon_instanceTmpl : null,
|
||||||
network_index: {},
|
network_index: {},
|
||||||
balloonID:null,
|
balloonID:null,
|
||||||
reload_duration: 10000,
|
|
||||||
network_height : 0,
|
network_height : 0,
|
||||||
previous_message : null,
|
|
||||||
deleting_device : null,
|
|
||||||
|
|
||||||
init:function() {
|
init:function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -136,40 +131,41 @@ horizon.network_topology = {
|
|||||||
horizon.cookies.put('are_networks_collapsed', !current);
|
horizon.cookies.put('are_networks_collapsed', !current);
|
||||||
});
|
});
|
||||||
|
|
||||||
angular.element(window).on('message', function(e) {
|
|
||||||
var message = angular.element.parseJSON(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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.element('#topologyCanvasContainer').spin(horizon.conf.spinner_options.modal);
|
angular.element('#topologyCanvasContainer').spin(horizon.conf.spinner_options.modal);
|
||||||
self.create_vis();
|
self.create_vis();
|
||||||
self.loading();
|
self.loading();
|
||||||
self.force_direction(0.05,70,-700);
|
self.force_direction(0.05,70,-700);
|
||||||
|
if(horizon.networktopologyloader.model !== null) {
|
||||||
self.retrieve_network_info(true);
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// register for message notifications
|
||||||
|
horizon.networktopologymessager.addMessageHandler(this.handleMessage, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleMessage:function(message) {
|
||||||
|
var self = this;
|
||||||
|
var deleteData = horizon.networktopologymessager.delete_data;
|
||||||
|
if (message.type == 'success') {
|
||||||
|
self.remove_node_on_delete(deleteData);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Get the json data about the current deployment
|
// Get the json data about the current deployment
|
||||||
retrieve_network_info: function(force_start) {
|
retrieve_network_info: function(force_start) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (angular.element('#networktopology').length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
angular.element.getJSON(
|
|
||||||
angular.element('#networktopology').data('networktopology') + '?' + angular.element.now(),
|
|
||||||
function(data) {
|
|
||||||
self.data_loaded = true;
|
self.data_loaded = true;
|
||||||
self.load_topology(data);
|
self.load_topology(horizon.networktopologyloader.model);
|
||||||
if (force_start) {
|
if (force_start) {
|
||||||
var i = 0;
|
var i = 0;
|
||||||
self.force.start();
|
self.force.start();
|
||||||
@ -178,11 +174,6 @@ horizon.network_topology = {
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(function() {
|
|
||||||
self.retrieve_network_info();
|
|
||||||
}, self.reload_duration);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load config from cookie
|
// Load config from cookie
|
||||||
@ -222,7 +213,7 @@ horizon.network_topology = {
|
|||||||
// Main svg
|
// Main svg
|
||||||
self.outer_group = d3.select('#topologyCanvasContainer').append('svg')
|
self.outer_group = d3.select('#topologyCanvasContainer').append('svg')
|
||||||
.attr('width', '100%')
|
.attr('width', '100%')
|
||||||
.attr('height', angular.element(document).height() - 200 + "px")
|
.attr('height', angular.element(document).height() - 270 + "px")
|
||||||
.attr('pointer-events', 'all')
|
.attr('pointer-events', 'all')
|
||||||
.append('g')
|
.append('g')
|
||||||
.call(self.zoom
|
.call(self.zoom
|
||||||
@ -837,17 +828,14 @@ horizon.network_topology = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
delete_device: function(type, deviceId) {
|
delete_device: function(type, deviceId) {
|
||||||
var self = this;
|
|
||||||
var message = {id:deviceId};
|
var message = {id:deviceId};
|
||||||
self.post_message(deviceId,type,message);
|
horizon.networktopologymessager.post_message(deviceId,type,message,type,'delete',data={});
|
||||||
self.deleting_device = {type: type, deviceId: deviceId};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
remove_node_on_delete: function () {
|
remove_node_on_delete: function(deleteData) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var type = self.deleting_device.type;
|
var deviceId = deleteData.device_id;
|
||||||
var deviceId = self.deleting_device.deviceId;
|
switch (deleteData.device_type) {
|
||||||
switch (type) {
|
|
||||||
case 'router':
|
case 'router':
|
||||||
self.removeNode(self.data.routers[deviceId]);
|
self.removeNode(self.data.routers[deviceId]);
|
||||||
break;
|
break;
|
||||||
@ -858,15 +846,18 @@ horizon.network_topology = {
|
|||||||
case 'network':
|
case 'network':
|
||||||
self.removeNode(self.data.networks[deviceId]);
|
self.removeNode(self.data.networks[deviceId]);
|
||||||
break;
|
break;
|
||||||
|
case 'port':
|
||||||
|
self.removePort(deviceId, deleteData.device_data);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
self.delete_balloon();
|
self.delete_balloon();
|
||||||
},
|
},
|
||||||
|
|
||||||
delete_port: function(routerId, portId, networkId) {
|
removePort: function(portId, deviceData) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var message = {id:portId};
|
var routerId = deviceData.router_id;
|
||||||
|
var networkId = deviceData.network_id;
|
||||||
if (routerId) {
|
if (routerId) {
|
||||||
self.post_message(portId, 'router/' + routerId + '/', message);
|
|
||||||
for (var l in self.links) {
|
for (var l in self.links) {
|
||||||
var data = null;
|
var data = null;
|
||||||
if(self.links[l].source.data.id == routerId && self.links[l].target.data.id == networkId) {
|
if(self.links[l].source.data.id == routerId && self.links[l].target.data.id == networkId) {
|
||||||
@ -874,7 +865,6 @@ horizon.network_topology = {
|
|||||||
} else if (self.links[l].target.data.id == routerId && self.links[l].source.data.id == networkId) {
|
} else if (self.links[l].target.data.id == routerId && self.links[l].source.data.id == networkId) {
|
||||||
data = self.links[l].target.data;
|
data = self.links[l].target.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
for (var p in data.ports) {
|
for (var p in data.ports) {
|
||||||
if ((data.ports[p].id == portId) && (data.ports[p].network_id == networkId)) {
|
if ((data.ports[p].id == portId) && (data.ports[p].network_id == networkId)) {
|
||||||
@ -888,8 +878,16 @@ horizon.network_topology = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
delete_port: function(routerId, portId, networkId) {
|
||||||
|
var message = {id:portId};
|
||||||
|
var data = {network_id:networkId,routerId:routerId};
|
||||||
|
if (routerId) {
|
||||||
|
horizon.networktopologymessager.post_message(portId, 'router/' + routerId + '/', message, 'port', 'delete', data);
|
||||||
} else {
|
} else {
|
||||||
self.post_message(portId, 'network/' + networkId + '/', message);
|
horizon.networktopologymessager.post_message(portId, 'network/' + networkId + '/', message, 'port', 'delete', data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1062,21 +1060,5 @@ horizon.network_topology = {
|
|||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
post_message: function(id,url,message) {
|
|
||||||
var self = this;
|
|
||||||
var iframeID = 'ifr_' + id;
|
|
||||||
var iframe = angular.element('<iframe width="500" height="300" />')
|
|
||||||
.attr('id',iframeID)
|
|
||||||
.attr('src',url)
|
|
||||||
.appendTo(self.post_messages);
|
|
||||||
iframe.on('load',function() {
|
|
||||||
angular.element(this).get(0).contentWindow.postMessage(
|
|
||||||
JSON.stringify(message, null, 2), '*');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
delete_post_message: function(id) {
|
|
||||||
angular.element('#' + id).remove();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
149
horizon/static/horizon/js/horizon.networktopologycommon.js
Normal file
149
horizon/static/horizon/js/horizon.networktopologycommon.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// Aggregate for common network topology functionality
|
||||||
|
horizon.networktopologycommon = {
|
||||||
|
init:function() {
|
||||||
|
horizon.networktopologyloader.init();
|
||||||
|
horizon.networktopologymessager.init();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common data loader for network topology views
|
||||||
|
*/
|
||||||
|
horizon.networktopologyloader = {
|
||||||
|
// data for the network topology views
|
||||||
|
model: null,
|
||||||
|
// timeout length
|
||||||
|
reload_duration: 10000,
|
||||||
|
// timer controlling update intervals
|
||||||
|
update_timer: null,
|
||||||
|
|
||||||
|
init:function() {
|
||||||
|
var self = this;
|
||||||
|
if($('#networktopology').length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* makes the data reqeuest and populates the 'model'
|
||||||
|
*/
|
||||||
|
update:function() {
|
||||||
|
var self = this;
|
||||||
|
angular.element.getJSON(
|
||||||
|
angular.element('#networktopology').data('networktopology') + '?' + angular.element.now(),
|
||||||
|
function(data) {
|
||||||
|
self.model = data;
|
||||||
|
$('#networktopology').trigger('change');
|
||||||
|
self.update_timer = setTimeout(function(){
|
||||||
|
self.update();
|
||||||
|
}, self.reload_duration);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stops the data update sequences
|
||||||
|
*/
|
||||||
|
stop_update:function() {
|
||||||
|
var self = this;
|
||||||
|
clearTimeout(self.update_timer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* common utility for network topology view to create iframes and pass post
|
||||||
|
* messages from those iframes
|
||||||
|
*/
|
||||||
|
horizon.networktopologymessager = {
|
||||||
|
previous_message : null,
|
||||||
|
// element to attach messages to
|
||||||
|
post_messages:'#topologyMessages',
|
||||||
|
// Array of functions to call when a message event is received
|
||||||
|
messaging_functions: [],
|
||||||
|
// data stored when a delete operation is finalizing
|
||||||
|
delete_data: {},
|
||||||
|
|
||||||
|
init:function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// listens for message events
|
||||||
|
angular.element(window).on('message', function(e) {
|
||||||
|
var message = angular.element.parseJSON(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);
|
||||||
|
self.messageNotify(message);
|
||||||
|
horizon.networktopologyloader.update();
|
||||||
|
setTimeout(function() {
|
||||||
|
self.previous_message = null;
|
||||||
|
self.delete_data = {};
|
||||||
|
},self.reload_duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add method to be called when a message is received
|
||||||
|
*
|
||||||
|
* @param {function} fn method to be called
|
||||||
|
*
|
||||||
|
* @param {Object} fnObj object the method is being called from this make
|
||||||
|
* sure the scope of 'this' is correct
|
||||||
|
*/
|
||||||
|
addMessageHandler:function(fn, fnObj) {
|
||||||
|
var self = this;
|
||||||
|
self.messaging_functions.push({obj:fnObj, func:fn});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calls the methods that subscribed to message notifications
|
||||||
|
*
|
||||||
|
* @param {Object} message iframe message content
|
||||||
|
*/
|
||||||
|
messageNotify:function(message) {
|
||||||
|
var self = this;
|
||||||
|
for (var i = 0; i < self.messaging_functions.length; i += 1) {
|
||||||
|
func = self.messaging_functions[i].func;
|
||||||
|
fnObj = self.messaging_functions[i].obj;
|
||||||
|
func.call(fnObj, message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* posts a message from the iframe
|
||||||
|
*
|
||||||
|
* @param {String} id target device id
|
||||||
|
* @param {String} url URL for action
|
||||||
|
* @param {Object} message object containing message value
|
||||||
|
* @param {String} action value of action, e.g., "delete"
|
||||||
|
* @param {Object} data of extra data that should be relayed with the message
|
||||||
|
* notification
|
||||||
|
*/
|
||||||
|
post_message: function(id,url,message,type,action,data) {
|
||||||
|
var self = this;
|
||||||
|
if(action == "delete") {
|
||||||
|
self.delete_data.device_id = id;
|
||||||
|
self.delete_data.device_type = type;
|
||||||
|
self.delete_data.device_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop the update refesh cycle while action takes place
|
||||||
|
horizon.networktopologyloader.stop_update();
|
||||||
|
var iframeID = 'ifr_' + id;
|
||||||
|
var iframe = $('<iframe width="500" height="300" />')
|
||||||
|
.attr('id',iframeID)
|
||||||
|
.attr('src',url)
|
||||||
|
.appendTo('#topologyMessages');
|
||||||
|
iframe.on('load',function() {
|
||||||
|
angular.element(this).get(0).contentWindow.postMessage(
|
||||||
|
JSON.stringify(message, null, 2), '*');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// delete the iframe
|
||||||
|
delete_post_message: function(id) {
|
||||||
|
angular.element('#' + id).remove();
|
||||||
|
}
|
||||||
|
};
|
@ -56,6 +56,12 @@ horizon.addInitFunction(horizon.tabs.init = function () {
|
|||||||
$content.find("table.datatable").each(function () {
|
$content.find("table.datatable").each(function () {
|
||||||
horizon.datatables.update_footer_count($(this));
|
horizon.datatables.update_footer_count($(this));
|
||||||
});
|
});
|
||||||
|
// d3 renders incorrectly in a hidden tab, this forces a rerender when the
|
||||||
|
// container size is not 0 from display:none
|
||||||
|
if($content.find(".d3-container").length) {
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
}
|
||||||
|
|
||||||
data[$tab.closest(".nav-tabs").attr("id")] = $tab.attr('data-target');
|
data[$tab.closest(".nav-tabs").attr("id")] = $tab.attr('data-target');
|
||||||
horizon.cookies.put("tabs", data);
|
horizon.cookies.put("tabs", data);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tabs
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project.network_topology import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyBaseTab(tabs.Tab):
|
||||||
|
def get_context_data(self, request):
|
||||||
|
return utils.get_context(request)
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyTab(TopologyBaseTab):
|
||||||
|
name = _("Topology")
|
||||||
|
slug = "topology"
|
||||||
|
preload = False
|
||||||
|
template_name = ("project/network_topology/_topology_view.html")
|
||||||
|
|
||||||
|
|
||||||
|
class GraphTab(TopologyBaseTab):
|
||||||
|
name = _("Graph")
|
||||||
|
slug = "graph"
|
||||||
|
preload = True
|
||||||
|
template_name = ("project/network_topology/_graph_view.html")
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyTabs(tabs.TabGroup):
|
||||||
|
slug = "topology_tabs"
|
||||||
|
tabs = (TopologyTab, GraphTab)
|
||||||
|
sticky = True
|
@ -0,0 +1,53 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="launchButtons pull-right">
|
||||||
|
{% if launch_instance_allowed %}
|
||||||
|
{% if show_ng_launch %}
|
||||||
|
{% url 'horizon:project:network_topology:index' as networkUrl %}
|
||||||
|
<a href="javascript:void(0);" ng-controller="LaunchInstanceModalController as modal"
|
||||||
|
ng-click="modal.openLaunchInstanceWizard({successUrl: '{{networkUrl}}'})"
|
||||||
|
id="instances__action_launch-ng" class="btn btn-default btn-launch
|
||||||
|
{% if instance_quota_exceeded %}disabled{% endif %}">
|
||||||
|
<span class="fa fa-cloud-upload"></span>
|
||||||
|
{% if instance_quota_exceeded %}
|
||||||
|
{% trans "Launch Instance (Quota exceeded)" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Launch Instance" %}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_legacy_launch %}
|
||||||
|
<a href="{% url 'horizon:project:network_topology:launchinstance' %}"
|
||||||
|
id="instances__action_launch" class="btn btn-default btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}">
|
||||||
|
<span class="fa fa-cloud-upload"></span>
|
||||||
|
{% if instance_quota_exceeded %}
|
||||||
|
{% trans "Launch Instance (Quota exceeded)" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Launch Instance" %}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if create_network_allowed %}
|
||||||
|
<a href="{% url 'horizon:project:network_topology:createnetwork' %}"
|
||||||
|
id="networks__action_create" class="btn btn-default ajax-modal {% if network_quota_exceeded %}disabled{% endif %}">
|
||||||
|
<span class="fa fa-plus"></span>
|
||||||
|
{% if network_quota_exceeded %}
|
||||||
|
{% trans "Create Network (Quota exceeded)" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Create Network" %}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if create_router_allowed %}
|
||||||
|
<a href="{% url 'horizon:project:network_topology:createrouter' %}"
|
||||||
|
id="Routers__action_create" class="btn btn-default ajax-modal {% if router_quota_exceeded %}disabled{% endif %}">
|
||||||
|
<span class="fa fa-plus"></span>
|
||||||
|
{% if router_quota_exceeded %}
|
||||||
|
{% trans "Create Router (Quota exceeded)" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Create Router" %}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
@ -0,0 +1,21 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class='description'>
|
||||||
|
{% blocktrans %}
|
||||||
|
Resize the canvas by scrolling up/down with your mouse/trackpad on the topology.
|
||||||
|
Pan around the canvas by clicking and dragging the space behind the topology.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<div class="topology-navi">
|
||||||
|
<div class="toggle-view btn-group" data-toggle="buttons-radio">
|
||||||
|
<button type="button" class="btn btn-default" id="toggle_labels">
|
||||||
|
<span class="fa fa-th-large"></span> {% trans "Toggle Labels" %}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default" id="toggle_networks">
|
||||||
|
<span class="fa fa-th"></span> {% trans "Toggle Network Collapse" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="topologyCanvasContainer" class="d3-container">
|
||||||
|
<div class="nodata">{% blocktrans %}There are no networks, routers, or connected instances to display.{% endblocktrans %}</div>
|
||||||
|
</div>
|
@ -0,0 +1,222 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<style type="text/css">
|
||||||
|
svg#topology_canvas {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .network-rect {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .network-rect.nourl {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .network-name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
fill: #fff;
|
||||||
|
text-anchor: middle;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .network-cidr {
|
||||||
|
font-size: 11px;
|
||||||
|
text-anchor: end;
|
||||||
|
}
|
||||||
|
svg#topology_canvas text.network-type {
|
||||||
|
font-family: FontAwesome;
|
||||||
|
text-anchor: end;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .port_text {
|
||||||
|
font-size: 9px;
|
||||||
|
fill: #666;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .port_text.left {
|
||||||
|
text-anchor: end;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .base_bg_normal {
|
||||||
|
fill: #333;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .loading_bg_normal {
|
||||||
|
fill: #555;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .base_bg_small,
|
||||||
|
svg#topology_canvas .loading_bg_small {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .active {
|
||||||
|
fill: #45B035;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .icon polygon {
|
||||||
|
fill: #333;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .instance_small .frame,
|
||||||
|
svg#topology_canvas .router_small .frame {
|
||||||
|
fill: url(#device_small_bg);
|
||||||
|
stroke: #333;
|
||||||
|
stroke-width: 3;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .instance_small .port_text,
|
||||||
|
svg#topology_canvas .router_small .port_text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .router_normal .frame,
|
||||||
|
svg#topology_canvas .instance_normal .frame {
|
||||||
|
fill: #fff;
|
||||||
|
stroke: #333;
|
||||||
|
stroke-width: 4;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .router_normal .icon_bg,
|
||||||
|
svg#topology_canvas .instance_normal .icon_bg {
|
||||||
|
fill: #fff;
|
||||||
|
stroke: #333;
|
||||||
|
stroke-width: 4;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .router_normal .texts_bg,
|
||||||
|
svg#topology_canvas .instance_normal .texts_bg {
|
||||||
|
fill: url('#device_normal_bg');
|
||||||
|
}
|
||||||
|
svg#topology_canvas .router_normal .texts .name,
|
||||||
|
svg#topology_canvas .instance_normal .texts .name {
|
||||||
|
text-anchor: middle;
|
||||||
|
fill: #333;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .router_normal .texts .type,
|
||||||
|
svg#topology_canvas .instance_normal .texts .type {
|
||||||
|
text-anchor: middle;
|
||||||
|
fill: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
svg#topology_canvas .router_normal .instance_bg,
|
||||||
|
svg#topology_canvas .instance_normal .instance_bg {
|
||||||
|
fill: #333;
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .active {
|
||||||
|
fill: #555;
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .icon polygon {
|
||||||
|
fill: #555;
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .instance_bg {
|
||||||
|
fill: #555;
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .instance_small .frame,
|
||||||
|
svg#topology_canvas g.loading .router_small .frame {
|
||||||
|
stroke: #555;
|
||||||
|
fill: url(#device_small_bg_loading);
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .router_normal .frame,
|
||||||
|
svg#topology_canvas g.loading .instance_normal .frame {
|
||||||
|
stroke: #555;
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .router_normal .name,
|
||||||
|
svg#topology_canvas g.loading .instance_normal .name {
|
||||||
|
fill: #999;
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .router_normal .texts_bg,
|
||||||
|
svg#topology_canvas g.loading .instance_normal .texts_bg {
|
||||||
|
fill: url(#device_normal_bg_loading);
|
||||||
|
}
|
||||||
|
svg#topology_canvas g.loading .router_normal .icon_bg,
|
||||||
|
svg#topology_canvas g.loading .instance_normal .icon_bg {
|
||||||
|
stroke: #555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<svg width="400" height="400" id="topology_canvas">
|
||||||
|
<defs>
|
||||||
|
<pattern id="device_normal_bg" patternUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||||
|
<g>
|
||||||
|
<rect width="20" height="20" class="base_bg_normal"></rect>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="device_normal_bg_loading" patternUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||||
|
<g>
|
||||||
|
<rect width="20" height="20" class="loading_bg_normal"></rect>
|
||||||
|
<path d='M0 20L20 0ZM22 18L18 22ZM-2 2L2 -2Z' stroke-linecap="square" stroke='rgba(0, 0, 0, 0.3)' stroke-width="7"></path>
|
||||||
|
</g>
|
||||||
|
<animate attributeName="x" attributeType="XML" begin="0s" dur="0.5s" from="0" to="-20" repeatCount="indefinite"></animate>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="device_small_bg" patternUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||||
|
<g>
|
||||||
|
<rect width="20" height="20" class="base_bg_small"></rect>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="device_small_bg_loading" patternUnits="userSpaceOnUse" x="0" y="0" width="5" height="5">
|
||||||
|
<g>
|
||||||
|
<rect width="5" height="5" class="loading_bg_small"></rect>
|
||||||
|
<path d='M0 5L5 0ZM6 4L4 6ZM-1 1L1 -1Z' stroke-linecap="square" stroke='rgba(0, 0, 0, 0.4)' stroke-width="1.5"></path>
|
||||||
|
</g>
|
||||||
|
<animate attributeName="x" attributeType="XML" begin="0s" dur="0.5s" from="0" to="-5" repeatCount="indefinite"></animate>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
<svg id="topology_template" display="none">
|
||||||
|
<g class="router_small device_body">
|
||||||
|
<g class="ports" pointer-events="none"></g>
|
||||||
|
<rect rx="3" ry="3" width="20" height="20" class="frame"></rect>
|
||||||
|
<g transform="translate(3.5,3)" class="icon">
|
||||||
|
<polygon points="12.51,4.23 12.51,0.49 8.77,0.49 9.68,1.4 6.92,4.16 8.84,6.08 11.6,3.32 "></polygon>
|
||||||
|
<polygon points="0.49,8.77 0.49,12.51 4.23,12.51 3.32,11.6 6.08,8.83 4.17,6.92 1.4,9.68 "></polygon>
|
||||||
|
<polygon points="1.85,5.59 5.59,5.59 5.59,1.85 4.68,2.76 1.92,0 0,1.92 2.76,4.68 "></polygon>
|
||||||
|
<polygon points="11.15,7.41 7.41,7.41 7.41,11.15 8.32,10.24 11.08,13 13,11.08 10.24,8.32 "></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g class="instance_small device_body">
|
||||||
|
<g class="ports" pointer-events="none"></g>
|
||||||
|
<rect rx="3" ry="3" width="20" height="20" class="frame"></rect>
|
||||||
|
<g transform="translate(5,3)" class="icon">
|
||||||
|
<rect class="instance_bg" width="10" height="13"></rect>
|
||||||
|
<rect x="2" y="1" fill="#FFFFFF" width="6" height="2"></rect>
|
||||||
|
<rect x="2" y="4" fill="#FFFFFF" width="6" height="2"></rect>
|
||||||
|
<circle class="active" cx="3" cy="10" r="1.3"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g class="network_container_small">
|
||||||
|
<rect rx="7" ry="7" width="15" height="200" style="fill: #8541B5;" class="network-rect"></rect>
|
||||||
|
<text x="250" y="-3" class="network-name" transform="rotate(90 0 0)" pointer-events="none">Network</text>
|
||||||
|
<text x="0" y="-20" class="network-cidr" transform="rotate(90 0 0)">0.0.0.0</text>
|
||||||
|
<text x="0" y="-20" class="network-type" transform="rotate(90 0 0)"
|
||||||
|
data-toggle="tooltip" data-placement="bottom" title="{% trans 'External Network' %}"></text>
|
||||||
|
</g>
|
||||||
|
<g class="router_normal device_body">
|
||||||
|
<g class="ports" pointer-events="none"></g>
|
||||||
|
<rect class="frame" x="0" y="0" rx="6" ry="6" width="90" height="50"></rect>
|
||||||
|
<g class="texts" pointer-events="none">
|
||||||
|
<rect class="texts_bg" x="1.5" y="32" width="87" height="17"></rect>
|
||||||
|
<text x="45" y="46" class="type">{% trans "Router" %}</text>
|
||||||
|
<text x="45" y="22" class="name">router</text>
|
||||||
|
</g>
|
||||||
|
<g class="icon" transform="translate(6,6)" pointer-events="none">
|
||||||
|
<circle class="icon_bg" cx="0" cy="0" r="12"></circle>
|
||||||
|
<g transform="translate(-6.5,-6.5)">
|
||||||
|
<polygon points="12.51,4.23 12.51,0.49 8.77,0.49 9.68,1.4 6.92,4.16 8.84,6.08 11.6,3.32 "></polygon>
|
||||||
|
<polygon points="0.49,8.77 0.49,12.51 4.23,12.51 3.32,11.6 6.08,8.83 4.17,6.92 1.4,9.68 "></polygon>
|
||||||
|
<polygon points="1.85,5.59 5.59,5.59 5.59,1.85 4.68,2.76 1.92,0 0,1.92 2.76,4.68 "></polygon>
|
||||||
|
<polygon points="11.15,7.41 7.41,7.41 7.41,11.15 8.32,10.24 11.08,13 13,11.08 10.24,8.32 "></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g class="instance_normal device_body">
|
||||||
|
<g class="ports" pointer-events="none"></g>
|
||||||
|
<rect class="frame" x="0" y="0" rx="6" ry="6" width="90" height="50"></rect>
|
||||||
|
<g class="texts">
|
||||||
|
<rect class="texts_bg" x="1.5" y="32" width="87" height="17"></rect>
|
||||||
|
<text x="45" y="46" class="type">{% trans "Instance" %}</text>
|
||||||
|
<text x="45" y="22" class="name">instance</text>
|
||||||
|
</g>
|
||||||
|
<g class="icon" transform="translate(6,6)">
|
||||||
|
<circle class="icon_bg" cx="0" cy="0" r="12"></circle>
|
||||||
|
<g transform="translate(-5,-6.5)">
|
||||||
|
<rect class="instance_bg" width="10" height="13"></rect>
|
||||||
|
<rect x="2" y="1" fill="#FFFFFF" width="6" height="2"></rect>
|
||||||
|
<rect x="2" y="4" fill="#FFFFFF" width="6" height="2"></rect>
|
||||||
|
<circle class="active" cx="3" cy="10" r="1.3"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g class="network_container_normal">
|
||||||
|
<rect rx="9" ry="9" width="17" height="500" style="fill: #8541B5;" class="network-rect"></rect>
|
||||||
|
<text x="250" y="-4" class="network-name" transform="rotate(90 0 0)" pointer-events="none">Network</text>
|
||||||
|
<text x="490" y="-20" class="network-cidr" transform="rotate(90 0 0)">0.0.0.0</text>
|
||||||
|
<text x="490" y="-20" class="network-type" transform="rotate(90 0 0)"
|
||||||
|
data-toggle="tooltip" data-placement="bottom" title="{% trans 'External Network' %}"></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
@ -0,0 +1,20 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="topology-navi">
|
||||||
|
<div class="toggle-view btn-group" data-toggle="buttons">
|
||||||
|
<label class="btn btn-default" data-value="small">
|
||||||
|
<input type="radio" name="options" id="option1" checked>
|
||||||
|
<span class="fa fa-th"></span>
|
||||||
|
{% trans "Small" %}
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-default" data-value="normal">
|
||||||
|
<input type="radio" name="options" id="option2">
|
||||||
|
<span class="fa fa-th-large"></span>
|
||||||
|
{% trans "Normal" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="flatTopologyCanvasContainer">
|
||||||
|
<div class="nodata">{% blocktrans %}There are no networks, routers, or connected instances to display.{% endblocktrans %}</div>
|
||||||
|
{% include "project/network_topology/_svg_element.html" %}
|
||||||
|
</div>
|
@ -13,41 +13,14 @@
|
|||||||
{% include "project/network_topology/client_side/_balloon_port.html" %}
|
{% include "project/network_topology/client_side/_balloon_port.html" %}
|
||||||
{% include "project/network_topology/client_side/_balloon_net.html" %}
|
{% include "project/network_topology/client_side/_balloon_net.html" %}
|
||||||
{% include "project/network_topology/client_side/_balloon_instance.html" %}
|
{% include "project/network_topology/client_side/_balloon_instance.html" %}
|
||||||
<div class='description'>
|
|
||||||
{% blocktrans %}
|
{% include 'project/network_topology/_actions_list.html' %}
|
||||||
Resize the canvas by scrolling up/down with your mouse/trackpad on the topology.
|
<div class="row">
|
||||||
Pan around the canvas by clicking and dragging the space behind the topology.
|
<div class="col-sm-12">
|
||||||
{% endblocktrans %}
|
{{ tab_group.render }}
|
||||||
</div>
|
|
||||||
<div class="topologyNavi">
|
|
||||||
<div class="toggleView btn-group" data-toggle="buttons-radio">
|
|
||||||
<button type="button" class="btn btn-default" id="toggle_labels"><span class="fa
|
|
||||||
fa-th-large"></span> {%trans "Toggle labels" %}</button>
|
|
||||||
<button type="button" class="btn btn-default" id="toggle_networks"><span class="fa
|
|
||||||
fa-th"></span> {%trans "Toggle Network Collapse" %}</button>
|
|
||||||
</div>
|
|
||||||
<div class="launchButtons">
|
|
||||||
{% if launch_instance_allowed %}
|
|
||||||
{% if show_ng_launch %}
|
|
||||||
{% url 'horizon:project:network_topology:index' as networkUrl %}
|
|
||||||
<a href="javascript:void(0);" ng-controller="LaunchInstanceModalController as modal" ng-click="modal.openLaunchInstanceWizard({successUrl: '{{networkUrl}}'})" id="instances__action_launch-ng" class="btn btn-default btn-sm btn-launch {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if show_legacy_launch %}
|
|
||||||
<a href="{% url 'horizon:project:network_topology:launchinstance' %}" id="instances__action_launch" class="btn btn-default btn-sm btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if create_network_allowed %}
|
|
||||||
<a href="{% url 'horizon:project:network_topology:createnetwork' %}" id="networks__action_create" class="btn btn-default btn-sm ajax-modal {% if network_quota_exceeded %}disabled{% endif %}"><span class="fa fa-plus"></span> {% if network_quota_exceeded %}{% trans "Create Network (Quota exceeded)"%}{% else %}{% trans "Create Network"%}{% endif %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if create_router_allowed %}
|
|
||||||
<a href="{% url 'horizon:project:network_topology:createrouter' %}" id="Routers__action_create" class="btn btn-default btn-sm ajax-modal {% if router_quota_exceeded %}disabled{% endif %}"><span class="fa fa-plus"></span> {% if router_quota_exceeded %}{% trans "Create Router (Quota exceeded)"%}{% else %}{% trans "Create Router"%}{% endif %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="topologyCanvasContainer">
|
|
||||||
<div class="nodata">{% blocktrans %}There are no networks, routers, or connected instances to display.{% endblocktrans %}</div>
|
|
||||||
</div>
|
|
||||||
<span data-networktopology="{% url 'horizon:project:network_topology:json' %}" id="networktopology"></span>
|
<span data-networktopology="{% url 'horizon:project:network_topology:json' %}" id="networktopology"></span>
|
||||||
<div id="topologyMessages"></div>
|
<div id="topologyMessages"></div>
|
||||||
|
|
||||||
@ -56,6 +29,8 @@ Pan around the canvas by clicking and dragging the space behind the topology.
|
|||||||
horizon.network_topology.init();
|
horizon.network_topology.init();
|
||||||
} else {
|
} else {
|
||||||
addHorizonLoadEvent(function () {
|
addHorizonLoadEvent(function () {
|
||||||
|
horizon.networktopologycommon.init();
|
||||||
|
horizon.flat_network_topology.init();
|
||||||
horizon.network_topology.init();
|
horizon.network_topology.init();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ class NetworkTopologyCreateTests(test.TestCase):
|
|||||||
@test.create_stubs({quotas: ('tenant_quota_usages',)})
|
@test.create_stubs({quotas: ('tenant_quota_usages',)})
|
||||||
def test_create_network_button_disabled_when_quota_exceeded(self):
|
def test_create_network_button_disabled_when_quota_exceeded(self):
|
||||||
url = reverse('horizon:project:network_topology:createnetwork')
|
url = reverse('horizon:project:network_topology:createnetwork')
|
||||||
classes = 'btn btn-default btn-sm ajax-modal'
|
classes = 'btn btn-default ajax-modal'
|
||||||
link_name = "Create Network (Quota exceeded)"
|
link_name = "Create Network (Quota exceeded)"
|
||||||
expected_string = "<a href='%s' class='%s disabled' "\
|
expected_string = "<a href='%s' class='%s disabled' "\
|
||||||
"id='networks__action_create'>" \
|
"id='networks__action_create'>" \
|
||||||
@ -222,7 +222,7 @@ class NetworkTopologyCreateTests(test.TestCase):
|
|||||||
@test.create_stubs({quotas: ('tenant_quota_usages',)})
|
@test.create_stubs({quotas: ('tenant_quota_usages',)})
|
||||||
def test_create_router_button_disabled_when_quota_exceeded(self):
|
def test_create_router_button_disabled_when_quota_exceeded(self):
|
||||||
url = reverse('horizon:project:network_topology:createrouter')
|
url = reverse('horizon:project:network_topology:createrouter')
|
||||||
classes = 'btn btn-default btn-sm ajax-modal'
|
classes = 'btn btn-default ajax-modal'
|
||||||
link_name = "Create Router (Quota exceeded)"
|
link_name = "Create Router (Quota exceeded)"
|
||||||
expected_string = "<a href='%s' class='%s disabled' "\
|
expected_string = "<a href='%s' class='%s disabled' "\
|
||||||
"id='Routers__action_create'>" \
|
"id='Routers__action_create'>" \
|
||||||
@ -236,7 +236,7 @@ class NetworkTopologyCreateTests(test.TestCase):
|
|||||||
@test.create_stubs({quotas: ('tenant_quota_usages',)})
|
@test.create_stubs({quotas: ('tenant_quota_usages',)})
|
||||||
def test_launch_instance_button_disabled_when_quota_exceeded(self):
|
def test_launch_instance_button_disabled_when_quota_exceeded(self):
|
||||||
url = reverse('horizon:project:network_topology:launchinstance')
|
url = reverse('horizon:project:network_topology:launchinstance')
|
||||||
classes = 'btn btn-default btn-sm btn-launch ajax-modal'
|
classes = 'btn btn-default btn-launch ajax-modal'
|
||||||
link_name = "Launch Instance (Quota exceeded)"
|
link_name = "Launch Instance (Quota exceeded)"
|
||||||
expected_string = "<a href='%s' class='%s disabled' "\
|
expected_string = "<a href='%s' class='%s disabled' "\
|
||||||
"id='instances__action_launch'>" \
|
"id='instances__action_launch'>" \
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from openstack_dashboard.usage import quotas
|
||||||
|
|
||||||
|
|
||||||
|
def _has_permission(request, policy):
|
||||||
|
has_permission = True
|
||||||
|
policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
|
||||||
|
|
||||||
|
if policy_check:
|
||||||
|
has_permission = policy_check(policy, request)
|
||||||
|
|
||||||
|
return has_permission
|
||||||
|
|
||||||
|
|
||||||
|
def _quota_exceeded(request, quota):
|
||||||
|
usages = quotas.tenant_quota_usages(request)
|
||||||
|
available = usages.get(quota, {}).get('available', 1)
|
||||||
|
return available <= 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_context(request, context=None):
|
||||||
|
"""Returns common context data for network topology views."""
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
|
||||||
|
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
|
||||||
|
|
||||||
|
context['launch_instance_allowed'] = _has_permission(
|
||||||
|
request, (("compute", "compute:create"),))
|
||||||
|
context['instance_quota_exceeded'] = _quota_exceeded(request, 'instances')
|
||||||
|
context['create_network_allowed'] = _has_permission(
|
||||||
|
request, (("network", "create_network"),))
|
||||||
|
context['network_quota_exceeded'] = _quota_exceeded(request, 'networks')
|
||||||
|
context['create_router_allowed'] = (
|
||||||
|
network_config.get('enable_router', True) and
|
||||||
|
_has_permission(request, (("network", "create_router"),)))
|
||||||
|
context['router_quota_exceeded'] = _quota_exceeded(request, 'routers')
|
||||||
|
context['console_type'] = getattr(settings, 'CONSOLE_TYPE', 'AUTO')
|
||||||
|
context['show_ng_launch'] = getattr(
|
||||||
|
settings, 'LAUNCH_INSTANCE_NG_ENABLED', True)
|
||||||
|
context['show_legacy_launch'] = getattr(
|
||||||
|
settings, 'LAUNCH_INSTANCE_LEGACY_ENABLED', False)
|
||||||
|
return context
|
@ -27,12 +27,10 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from django.views.generic import View # noqa
|
from django.views.generic import View # noqa
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
|
from horizon import tabs
|
||||||
from horizon.utils.lazy_encoder import LazyTranslationEncoder
|
from horizon.utils.lazy_encoder import LazyTranslationEncoder
|
||||||
from horizon import views
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.usage import quotas
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.network_topology.instances \
|
from openstack_dashboard.dashboards.project.network_topology.instances \
|
||||||
import tables as instances_tables
|
import tables as instances_tables
|
||||||
from openstack_dashboard.dashboards.project.network_topology.networks \
|
from openstack_dashboard.dashboards.project.network_topology.networks \
|
||||||
@ -43,6 +41,9 @@ from openstack_dashboard.dashboards.project.network_topology.routers \
|
|||||||
import tables as routers_tables
|
import tables as routers_tables
|
||||||
from openstack_dashboard.dashboards.project.network_topology.subnets \
|
from openstack_dashboard.dashboards.project.network_topology.subnets \
|
||||||
import tables as subnets_tables
|
import tables as subnets_tables
|
||||||
|
from openstack_dashboard.dashboards.project.network_topology \
|
||||||
|
import tabs as topology_tabs
|
||||||
|
from openstack_dashboard.dashboards.project.network_topology import utils
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.instances import\
|
from openstack_dashboard.dashboards.project.instances import\
|
||||||
console as i_console
|
console as i_console
|
||||||
@ -183,45 +184,14 @@ class NetworkDetailView(n_views.DetailView):
|
|||||||
template_name = 'project/network_topology/iframe.html'
|
template_name = 'project/network_topology/iframe.html'
|
||||||
|
|
||||||
|
|
||||||
class NetworkTopologyView(views.HorizonTemplateView):
|
class NetworkTopologyView(tabs.TabView):
|
||||||
|
tab_group_class = topology_tabs.TopologyTabs
|
||||||
template_name = 'project/network_topology/index.html'
|
template_name = 'project/network_topology/index.html'
|
||||||
page_title = _("Network Topology")
|
page_title = _("Network Topology")
|
||||||
|
|
||||||
def _has_permission(self, policy):
|
|
||||||
has_permission = True
|
|
||||||
policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
|
|
||||||
|
|
||||||
if policy_check:
|
|
||||||
has_permission = policy_check(policy, self.request)
|
|
||||||
|
|
||||||
return has_permission
|
|
||||||
|
|
||||||
def _quota_exceeded(self, quota):
|
|
||||||
usages = quotas.tenant_quota_usages(self.request)
|
|
||||||
available = usages.get(quota, {}).get('available', 1)
|
|
||||||
return available <= 0
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(NetworkTopologyView, self).get_context_data(**kwargs)
|
context = super(NetworkTopologyView, self).get_context_data(**kwargs)
|
||||||
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
|
return utils.get_context(self.request, context)
|
||||||
|
|
||||||
context['launch_instance_allowed'] = self._has_permission(
|
|
||||||
(("compute", "compute:create"),))
|
|
||||||
context['instance_quota_exceeded'] = self._quota_exceeded('instances')
|
|
||||||
context['create_network_allowed'] = self._has_permission(
|
|
||||||
(("network", "create_network"),))
|
|
||||||
context['network_quota_exceeded'] = self._quota_exceeded('networks')
|
|
||||||
context['create_router_allowed'] = (
|
|
||||||
network_config.get('enable_router', True) and
|
|
||||||
self._has_permission((("network", "create_router"),)))
|
|
||||||
context['router_quota_exceeded'] = self._quota_exceeded('routers')
|
|
||||||
context['console_type'] = getattr(
|
|
||||||
settings, 'CONSOLE_TYPE', 'AUTO')
|
|
||||||
context['show_ng_launch'] = getattr(
|
|
||||||
settings, 'LAUNCH_INSTANCE_NG_ENABLED', True)
|
|
||||||
context['show_legacy_launch'] = getattr(
|
|
||||||
settings, 'LAUNCH_INSTANCE_LEGACY_ENABLED', False)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class JSONView(View):
|
class JSONView(View):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#topologyCanvasContainer {
|
#topologyCanvasContainer, #flatTopologyCanvasContainer {
|
||||||
@include box-sizing(border-box);
|
@include box-sizing(border-box);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
@ -24,10 +24,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topologyNavi {
|
.topology-navi {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
.toggleView {
|
.toggle-view {
|
||||||
float: left;
|
float: left;
|
||||||
span.glyphicon {
|
span.glyphicon {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
@ -46,6 +46,8 @@
|
|||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.users.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.users.js'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.membership.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.membership.js'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.metering.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.metering.js'></script>
|
||||||
|
<script src='{{ STATIC_URL }}horizon/js/horizon.networktopologycommon.js'></script>
|
||||||
|
<script src='{{ STATIC_URL }}horizon/js/horizon.flatnetworktopology.js'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.networktopology.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.networktopology.js'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3piechart.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.d3piechart.js'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.heattop.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.heattop.js'></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user