Delete and launch devices on the topology view

This feature enables to delete and launch a instance or router on the network
topology view. So you can do basic actions on this view.
Also it enables to change view small or normal. You can see more networks and
devices in the small view.

implements bp editable-network-topology-view

fixes bug #1215683

Change-Id: Ie65d50d2a99f72696c8f10223f8430ad5f90b144
This commit is contained in:
Toshi Hayashi 2013-08-12 17:20:38 -07:00 committed by Toshiyuki Hayashi
parent d45413e9bf
commit 88b9335910
20 changed files with 1382 additions and 571 deletions

View File

@ -1,201 +1,447 @@
/* Namespace for core functionality related to Network Topology. */
horizon.network_topology = {
model: null,
network_margin: 270,
topologyCanvas_padding: 120,
min_network_height:500,
port_margin: 20,
device_initial_position : 40,
device_last_position : 0,
device_left_position : 90,
device_margin : 20,
device_min_height : 45,
port_initial_position: 1,
svg:'#topology_canvas',
svg_container:'#topologyCanvasContainer',
post_messages:'#topologyMessages',
network_tmpl:{
small:'#topology_template > .network_container_small',
normal:'#topology_template > .network_container_normal'
},
router_tmpl: {
small:'#topology_template > .router_small',
normal:'#topology_template > .router_normal'
},
instance_tmpl: {
small:'#topology_template > .instance_small',
normal:'#topology_template > .instance_normal'
},
balloon_tmpl : null,
balloon_device_tmpl : null,
balloon_port_tmpl : null,
network_index: {},
network_color_unit: 0,
network_saturation: 1,
network_lightness: 0.7,
balloon_id:null,
reload_duration: 10000,
spinner:null,
init:function(){
draw_mode:'normal',
network_height : 0,
previous_message : null,
element_properties:{
normal:{
network_width:270,
network_min_height:500,
top_margin:80,
default_height:50,
margin:20,
device_x:98.5,
device_width:90,
port_margin:16,
port_height:6,
port_width:82,
port_text_margin:{x:6,y:-4},
texts_bg_y:32,
type_y:46,
balloon_margin:{x:12,y:-12}
},
small :{
network_width:100,
network_min_height:400,
top_margin:50,
default_height:20,
margin:30,
device_x:47.5,
device_width:20,
port_margin:5,
port_height:3,
port_width:32.5,
port_text_margin:{x:0,y:0},
texts_bg_y:0,
type_y:0,
balloon_margin:{x:12,y:-30}
},
cidr_margin:5,
device_name_max_size:9,
device_name_suffix:'..'
},
init:function() {
var self = this;
$("#topologyCanvas").spin(horizon.conf.spinner_options.modal);
self.retrieve_network_info();
$(self.svg_container).spin(horizon.conf.spinner_options.modal);
if($('#networktopology').length === 0) {
return;
}
self.color = d3.scale.category10();
self.balloon_tmpl = Hogan.compile($('#balloon_container').html());
self.balloon_device_tmpl = Hogan.compile($('#balloon_device').html());
self.balloon_port_tmpl = Hogan.compile($('#balloon_port').html());
$(document)
.on('click', 'a.closeTopologyBalloon', function(e) {
e.preventDefault();
self.delete_balloon();
})
.on('click', '.topologyBalloon', function(e) {
e.stopPropagation();
})
.on('click', 'a.vnc_window', function(e) {
e.preventDefault();
var vnc_window = window.open($(this).attr('href'), vnc_window, 'width=760,height=560');
self.delete_balloon();
})
.click(function(){
self.delete_balloon();
});
$('.toggleView > .btn').click(function(){
self.draw_mode = $(this).data('value');
$('g.network').remove();
$.cookie('ntp_draw_mode',self.draw_mode);
self.data_convert();
});
$(window)
.on('message',function(e){
var message = JSON.parse(e.originalEvent.data);
if (self.previous_message != message.message) {
horizon.alert(message.type, message.message);
horizon.autoDismissAlerts();
self.previous_message = message.message;
self.delete_post_message(message.iframe_id);
self.load_network_info();
setTimeout(function() {
self.previous_message = null;
},10000);
}
});
self.load_network_info();
setInterval(function(){
self.retrieve_network_info();
self.load_network_info();
}, self.reload_duration);
},
retrieve_network_info: function(){
load_network_info:function(){
var self = this;
if($("#networktopology").length === 0) {
return;
if($('#networktopology').length === 0) {
return;
}
$.getJSON($("#networktopology").data("networktopology"),
$.getJSON($('#networktopology').data('networktopology') + '?' + $.now(),
function(data) {
self.draw_graph(data);
self.model = data;
self.data_convert();
}
);
},
draw_loading: function () {
$("#topologyCanvas").spin(horizon.conf.spinner_options.modal);
},
draw_graph: function(data){
var canvas = $("#topologyCanvas");
var networks = $("#topologyCanvas > .networks");
var nodata = $("#topologyCanvas > .nodata");
networks.show();
nodata.hide();
canvas.spin(false);
networks.empty();
this.model = data;
this.device_last_position = this.device_initial_position;
var network_elements = this.draw_networks();
var router_elements = this.draw_routers();
var server_elements = this.draw_servers();
if ((network_elements + router_elements + server_elements) <= 0){
networks.hide();
nodata.show();
select_draw_mode:function() {
var self = this;
var draw_mode = $.cookie('ntp_draw_mode');
if (draw_mode && (draw_mode == 'normal'| draw_mode == 'small')) {
self.draw_mode = draw_mode;
} else {
canvas.height(
Math.max(this.device_last_position + this.topologyCanvas_padding, this.min_network_height)
);
networks.width(
this.model.networks.length * this.network_margin
);
if (self.model.networks.length *
self.element_properties.normal.network_width > $('#topologyCanvas').width()) {
self.draw_mode = 'small';
} else {
self.draw_mode = 'normal';
}
$.cookie('ntp_draw_mode',self.draw_mode);
}
$('.toggleView > .btn').each(function(){
var $this = $(this);
if($this.hasClass(self.draw_mode)) {
$this.addClass('active');
}
});
},
network_color: function(network_id){
var max_hue = 360;
var num_network = this.model.networks.length;
if(num_network <= 0){
data_convert:function() {
var self = this;
var model = self.model;
$.each(model.networks, function(index, network) {
self.network_index[network.id] = index;
});
self.select_draw_mode();
var element_properties = self.element_properties[self.draw_mode];
self.network_height = element_properties.top_margin;
$.each([
{model:model.routers, type:'router'},
{model:model.servers, type:'instance'}
], function(index, devices) {
var type = devices.type;
var model = devices.model;
$.each(model, function(index, device) {
device.type = type;
device.ports = self.select_port(device.id);
var hasports = (device.ports.length <= 0) ? false : true;
device.parent_network = (hasports) ?
self.select_main_port(device.ports).network_id : self.model.networks[0].id;
var height = element_properties.port_margin*(device.ports.length - 1);
device.height =
(self.draw_mode == 'normal' && height > element_properties.default_height) ? height :
element_properties.default_height;
device.pos_y = self.network_height;
device.port_height =
(self.draw_mode == 'small' && height > device.height) ? 1 :
element_properties.port_height;
device.port_margin =
(self.draw_mode == 'small' && height > device.height) ?
device.height/device.ports.length :
element_properties.port_margin;
self.network_height += device.height + element_properties.margin;
});
});
$.each(model.networks, function(index, network) {
network.devices = [];
$.each([model.routers, model.servers],function(index, devices) {
$.each(devices,function(index, device) {
if(network.id == device.parent_network) {
network.devices.push(device);
}
});
});
});
self.network_height += element_properties.top_margin;
self.network_height = (self.network_height > element_properties.network_min_height) ?
self.network_height : element_properties.network_min_height;
self.draw_topology();
},
draw_topology:function() {
var self = this;
$(self.svg_container).spin(false);
$(self.svg_container).removeClass('noinfo');
if (self.model.networks.length <= 0) {
$('g.network').remove();
$(self.svg_container).addClass('noinfo');
return;
}
num_network ++;
var hue = Math.floor(
max_hue/num_network*(this.network_index(network_id) + 1));
return this.hsv2rgb(
hue, this.network_saturation, this.network_lightness);
},
//see http://en.wikipedia.org/wiki/HSL_and_HSV
hsv2rgb:function (h, s, v) {
var hi = Math.round(h/60) % 6;
var f = h/60 - hi;
var p = v*(1 - s);
var q = v*(1 - f*s);
var t = v*(1 - (1 - f)*s);
switch(hi){
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
return "rgb(" + Math.round(r*255) + "," + Math.round(g*255) + "," + Math.round(b*255) + ")";
},
draw_networks: function(){
var self = this;
var networks = $("#topologyCanvas > .networks");
$.each(self.model.networks, function(index, network){
var label = (network.name != "")? network.name : network.id;
if(network['router:external']){
label += " (external) ";
}
self.network_index[network.id] = index;
var network_html = $("<div class='network' />").attr("id", network.id);
var nicname_html = $("<div class='nicname'><h3>" + label +
"</h3><span class='ip'>" + self.select_cidr(network.id) + "</span></div>");
if (network.url == undefined) {
nicname_html.addClass("nourl");
} else {
nicname_html.click(function (){
window.location.href = network.url;
});
}
nicname_html
.css (
{'background-color':self.network_color(network.id)})
.appendTo(network_html);
networks.append(network_html);
});
return self.model.networks.length;
},
select_cidr:function(network_id){
var cidr = [];
$.each(this.model.subnets, function(index, subnet){
if(subnet.network_id != network_id){
return;
var svg = d3.select(self.svg);
var element_properties = self.element_properties[self.draw_mode];
svg
.attr('width',self.model.networks.length*element_properties.network_width)
.attr('height',self.network_height);
var network = svg.selectAll('g.network')
.data(self.model.networks);
var network_enter = network.enter()
.append('g')
.attr('class','network')
.each(function(d,i){
this.appendChild(d3.select(self.network_tmpl[self.draw_mode]).node().cloneNode(true));
var $this = d3.select(this).select('.network-rect');
if (d.url) {
var $this = d3.select(this).select('.network-rect');
$this
.on('mouseover',function(){
$this.transition().style('fill',
function() { return d3.rgb(self.network_color(d.id)).brighter(0.5)});
})
.on('mouseout',function(){
$this.transition().style('fill',
function() { return self.network_color(d.id)});
})
.on('click',function(){
window.location.href = d.url;
});
} else {
$this.classed('nourl', true);
}
cidr.push(subnet.cidr);
});
return cidr.join(', ');
},
draw_devices: function(type){
var self = this;
$.each(self.model[type + 's'], function(index, device){
var id = device.id;
var name = (device.name != "")? device.name : device.id;
var ports = self.select_port(id);
if(ports.length <= 0){
return;
}
var main_port = self.select_main_port(ports);
var parent_network = main_port.network_id;
var device_html = $("<div class='" + type + "'></div>");
device_html
.attr('id', device.id)
.css({top: self.device_last_position, position: 'absolute'})
.append($("<span class='devicename'><i></i>" + type + "</span>"))
.click(function (e){
e.stopPropagation();
window.location.href = device.url;
});
network
.attr('id',function(d) { return 'id_' + d.id; })
.attr('transform',function(d,i){
return 'translate(' + element_properties.network_width * i + ',' + 0 + ')'})
.select('.network-rect')
.attr('height', function(d) { return self.network_height})
.style('fill', function(d) { return self.network_color(d.id)});
network
.select('.network-name')
.attr('x', function(d) { return self.network_height/2 })
.text(function(d) { return d.name; });
network
.select('.network-cidr')
.attr('x', function(d) { return self.network_height - self.element_properties.cidr_margin })
.text(function(d) {
var cidr = $.map(d.subnets,function(n, i){
return n.cidr;
});
var name_html = $("<span class='name'></span>")
.html(device.name)
.attr('title', device.name)
.appendTo(device_html);
var port_position = self.port_initial_position;
$.each(ports, function(){
var port = this;
var port_html = self.port_html(port);
port_position += self.port_margin;
self.port_css(port_html, port_position, parent_network, port.network_id);
device_html.append(port_html);
return cidr.join(', ');
});
network.exit().remove();
var device = network.selectAll('g.device')
.data(function(d) { return d.devices; });
var device_enter = device.enter()
.append("g")
.attr('class','device')
.each(function(d,i){
var device_template = self[d.type + '_tmpl'][self.draw_mode];
this.appendChild(d3.select(device_template).node().cloneNode(true));
});
port_position += self.port_margin;
device_html.css(
{height: Math.max(self.device_min_height, port_position) + "px"});
self.device_last_position += device_html.height() + self.device_margin;
$("#" + parent_network).append(device_html);
$('div.port span.ip').each(function(i, ip){
$(ip).css('top', '-'+$(ip).height()+'px');
device_enter.on('mouseenter',function(d){
var $this = $(this);
self.show_balloon(d,$this);
})
.on('click',function(){
d3.event.stopPropagation();
});
device
.attr('id',function(d) { return 'id_' + d.id; })
.attr('transform',function(d,i){
return 'translate(' + element_properties.device_x + ',' + d.pos_y + ')';
})
.select('.frame')
.attr('height',function(d) { return d.height; });
device
.select('.texts_bg')
.attr('y',function(d) {
return element_properties.texts_bg_y + d.height - element_properties.default_height;
});
device
.select('.type')
.attr('y',function(d) {
return element_properties.type_y + d.height - element_properties.default_height;
});
device
.select('.name')
.text(function(d) { return self.string_truncate(d.name); });
device.each(function(d) {
if (d.status == 'BUILD') {
d3.select(this).classed('loading',true);
} else if (d.task == 'deleting') {
d3.select(this).classed('loading',true);
if ('bl_' + d.id == self.balloon_id) {
self.delete_balloon();
}
} else {
d3.select(this).classed('loading',false);
if ('bl_' + d.id == self.balloon_id) {
var $this = $(this);
self.show_balloon(d,$this);
}
}
});
device.exit().each(function(d){
if ('bl_' + d.id == self.balloon_id) {
self.delete_balloon();
}
}).remove();
var port = device.select('g.ports')
.selectAll('g.port')
.data(function(d) { return d.ports; });
var port_enter = port.enter()
.append('g')
.attr('class','port')
.attr('id',function(d) { return 'id_' + d.id; });
port_enter
.append('line')
.attr('class','port_line');
port_enter
.append('text')
.attr('class','port_text');
device.select('g.ports').each(function(d,i){
this._portdata = {};
this._portdata.ports_length = d.ports.length;
this._portdata.parent_network = d.parent_network;
this._portdata.device_height = d.height;
this._portdata.port_height = d.port_height;
this._portdata.port_margin = d.port_margin;
this._portdata.left = 0;
this._portdata.right = 0;
$(this).mouseenter(function(e){
e.stopPropagation();
});
});
return self.model[type + 's'].length;
port.each(function(d,i){
var index_diff = self.network_index(this.parentNode._portdata.parent_network) -
self.network_index(d.network_id);
this._index_diff = index_diff = (index_diff >= 0)? ++index_diff : index_diff;
this._direction = (this._index_diff < 0)? 'right' : 'left';
this._index = this.parentNode._portdata[this._direction] ++;
});
port.attr('transform',function(d,i){
var x = (this._direction == 'left') ? 0 : element_properties.device_width;
var ports_length = this.parentNode._portdata[this._direction];
var distance = this.parentNode._portdata.port_margin;
var y = (this.parentNode._portdata.device_height -
(ports_length -1)*distance)/2 + this._index*distance;
return 'translate(' + x + ',' + y + ')';
});
port
.select('.port_line')
.attr('stroke-width',function(d,i) {
return this.parentNode.parentNode._portdata.port_height;
})
.attr('stroke',function(d,i) {return self.network_color(d.network_id)})
.attr('x1',0).attr('y1',0).attr('y2',0)
.attr('x2',function(d,i) {
var parent = this.parentNode;
var width = (Math.abs(parent._index_diff) - 1)*element_properties.network_width +
element_properties.port_width;
return (parent._direction == 'left') ? -1*width : width;
});
port
.select('.port_text')
.attr('x',function(d) {
var parent = this.parentNode;
if (parent._direction == 'left') {
d3.select(this).classed('left',true);
return element_properties.port_text_margin.x*-1;
} else {
d3.select(this).classed('left',false);
return element_properties.port_text_margin.x;
}
})
.attr('y',function(d) { return element_properties.port_text_margin.y })
.text(function(d) {
var ip_label = [];
$.each(d.fixed_ips, function() {
ip_label.push(this.ip_address);
});
return ip_label.join(',');
});
port.exit().remove();
},
network_color: function(network_id) {
return this.color(this.network_index(network_id));
},
network_index: function(network_id) {
return this.network_index[network_id];
},
select_port: function(device_id){
return $.map(this.model.ports,function(port, index){
if (port.device_id == device_id) {
return port;
}
});
},
select_main_port: function(ports){
var _self = this;
var main_port_index = 0;
var MAX_INT = 4294967295;
var min_port_length = MAX_INT;
$.each(ports, function(index, port){
var port_length = _self.sum_port_length(port.network_id, ports);
if(port_length < min_port_length){
min_port_length = port_length;
main_port_index = index;
}
});
return ports[main_port_index];
},
sum_port_length: function(network_id, ports){
var self = this;
@ -206,75 +452,152 @@ horizon.network_topology = {
});
return sum_port_length;
},
select_main_port: function(ports){
var main_port_index = 0;
var MAX_INT = 4294967295;
var min_port_length = MAX_INT;
$.each(ports, function(index, port){
port_length = horizon.network_topology.sum_port_length(port.network_id, ports)
if(port_length < min_port_length){
min_port_length = port_length;
main_port_index = index;
string_truncate: function(string) {
var self = this;
var str = string;
var max_size = self.element_properties.device_name_max_size;
var suffix = self.element_properties.device_name_suffix;
var bytes = 0;
for (var i = 0; i < str.length; i++) {
bytes += str.charCodeAt(i) <= 255 ? 1 : 2;
if (bytes > max_size) {
str = str.substr(0, i) + suffix;
break;
}
})
return ports[main_port_index];
}
return str;
},
draw_routers: function(){
return this.draw_devices('router');
},
draw_servers: function(){
return this.draw_devices('server');
},
select_port: function(device_id){
return $.map(this.model.ports,function(port, index){
if (port.device_id == device_id) {
return port;
}
});
},
port_html: function(port){
delete_device: function(type, device_id) {
var self = this;
var port_html = $('<div class="port"><div class="dot"></div></div>');
var ip_label = "";
$.each(port.fixed_ips, function(){
ip_label += this.ip_address + "<br />";
})
var ip_html = $('<span class="ip" />').html(ip_label);
port_html
.append(ip_html)
.css({'background-color':self.network_color(port.network_id)})
.click(function (e) {
e.stopPropagation();
if(port.url != undefined) {
window.location.href = port.url;
}
var message = {id:device_id};
self.post_message(device_id,type,message);
},
delete_port: function(router_id, port_id) {
var self = this;
var message = {id:port_id};
self.post_message(port_id, 'router/' + router_id + '/', message);
},
show_balloon:function(d,element) {
var self = this;
var element_properties = self.element_properties[self.draw_mode];
if (self.balloon_id) {
self.delete_balloon();
}
var balloon_tmpl = self.balloon_tmpl;
var device_tmpl = self.balloon_device_tmpl;
var port_tmpl = self.balloon_port_tmpl;
var balloon_id = 'bl_' + d.id;
var ports = [];
$.each(d.ports,function(i, port){
var object = {};
object.id = port.id;
object.router_id = port.device_id;
object.url = port.url;
object.port_status = port.status;
object.port_status_css = (port.status == "ACTIVE")? 'active' : 'down';
var ip_address = '';
try {
ip_address = port.fixed_ips[0].ip_address;
}catch(e){
ip_address = 'no info';
}
var device_owner = '';
try {
device_owner = port.device_owner.replace('network:','');
}catch(e){
device_owner = 'no info';
}
object.ip_address = ip_address;
object.device_owner = device_owner;
object.is_interface = (device_owner == 'router_interface') ? true : false;
ports.push(object);
});
var html_data = {
balloon_id:balloon_id,
id:d.id,
url:d.url,
name:d.name,
type:d.type,
type_capital:d.type.replace(/^\w/, function($0) {return $0.toUpperCase()}),
id:d.id,
status:d.status,
status_class:(d.status == "ACTIVE")? 'active' : 'down'
};
if (d.type == 'router') {
html_data.port = ports;
html = balloon_tmpl.render(html_data,{
table1:device_tmpl,
table2:port_tmpl
});
if(port.url == undefined) {
port_html.addClass("nourl");
} else if (d.type == 'instance') {
html_data.console_id = d.id;
html = balloon_tmpl.render(html_data,{
table1:device_tmpl
});
} else {
return;
}
return port_html;
$(self.svg_container).append(html);
var device_position = element.find('.frame');
var x = device_position.position().left +
element_properties.device_width +
element_properties.balloon_margin.x;
var y = device_position.position().top +
element_properties.balloon_margin.y;
$('#' + balloon_id).css({
'left': x + 'px',
'top': y + 'px'
})
.show();
var $balloon = $('#' + balloon_id);
if ($balloon.offset().left + $balloon.outerWidth() > $(window).outerWidth()) {
$balloon
.css({
'left': 0 + 'px'
})
.css({
'left': device_position.position().left
- $balloon.outerWidth()
- element_properties.balloon_margin.x + 'px'
})
.addClass('leftPosition');
}
$balloon.find('.delete-device').click(function(e){
var $this = $(this);
$this.addClass('deleting');
d3.select('#id_' + $this.data('device-id')).classed('loading',true);
self.delete_device($this.data('type'),$this.data('device-id'));
});
$balloon.find('.delete-port').click(function(e){
var $this = $(this);
self.delete_port($this.data('router-id'),$this.data('port-id'));
});
self.balloon_id = balloon_id;
},
port_css: function(port_html, position, network_a, network_b){
delete_balloon:function() {
var self = this;
var index_diff = self.network_index(network_a) - self.network_index(network_b);
var width = self.network_margin * index_diff;
var direction = "left";
if(width < 0){
direction = "right";
width += self.network_margin;
if(self.balloon_id) {
$('#' + self.balloon_id).remove()
self.balloon_id = null;
}
width = Math.abs(width) + self.device_left_position;
var port_css = {};
port_css['width'] = width + "px";
port_css['top'] = position + "px";
port_css[direction] = (-width -3) + "px";
port_html.addClass(direction).css(port_css);
},
network_index: function(network_id){
return horizon.network_topology.network_index[network_id];
post_message: function(id,url,message) {
var self = this;
var iframe_id = 'ifr_' + id;
var iframe = $('<iframe width="500" height="300" />')
.attr('id',iframe_id)
.attr('src',url)
.appendTo(self.post_messages);
iframe.on('load',function() {
$(this).get(0).contentWindow.postMessage(
JSON.stringify(message, null, 2), '*');
});
},
delete_post_message: function(id) {
$('#' + id).remove();
}
}
};
horizon.addInitFunction(function () {
horizon.network_topology.init();
horizon.network_topology.init();
});

View File

@ -3,7 +3,7 @@
import os
import sys
from django.core.management import execute_from_command_line
from django.core.management import execute_from_command_line # noqa
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE",

View File

@ -0,0 +1,25 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 NTT Innovation Institute Inc.
#
# 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 _ # noqa
from openstack_dashboard.dashboards.project.instances import tables as i_tables
class InstancesTable(i_tables.InstancesTable):
class Meta:
name = "instances"
verbose_name = _("NT_Instances")
row_actions = (i_tables.TerminateInstance,)

View File

@ -0,0 +1,31 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 NTT Innovation Institute Inc.
#
# 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 _ # noqa
from openstack_dashboard.dashboards.project.routers.ports import\
tables as p_tables
class RemoveInterface(p_tables.RemoveInterface):
failure_url = 'horizon:project:network_topology:router'
class PortsTable(p_tables.PortsTable):
class Meta:
name = "interfaces"
verbose_name = _("NT_Interfaces")
row_actions = (RemoveInterface, )

View File

@ -0,0 +1,33 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 NTT Innovation Institute Inc.
#
# 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 _ # noqa
from openstack_dashboard.dashboards.project.routers import\
tables as r_tables
#LOG = logging.getLogger(__name__)
class DeleteRouter(r_tables.DeleteRouter):
redirect_url = "horizon:project:network_topology:router"
class RoutersTable(r_tables.RoutersTable):
class Meta:
name = "Routers"
verbose_name = _("NT_Routers")
status_columns = ["status"]
row_actions = (DeleteRouter,)

View File

@ -0,0 +1,21 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n horizon humanize %}
{% block form_id %}{% endblock %}
{% block form_action %}{% url horizon:project:network_topology:createrouter %}?{{ request.GET.urlencode }}{% endblock %}
{% block modal_id %}create_router_modal{% endblock %}
{% block modal-header %}{% trans "Create router" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create router" %}" />
<a href="{% url horizon:project:network_topology:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,27 @@
<script type='text/javascript'>
(function(){
$(window).on('message',function(e){
var message = JSON.parse(e.originalEvent.data);
var id = message.id;
$('button[id*="' + id + '"]').click();
});
$('.messages .alert').each(function(){
var $this = $(this);
var message = {};
message.action = "alert";
message.iframe_id = $(window.frameElement).attr('id');
message.type =
($this.hasClass('alert-info')) ? 'info' :
($this.hasClass('alert-warning')) ? 'warning' :
($this.hasClass('alert-success')) ? 'success' :
($this.hasClass('alert-error')) ? 'error' :
null;
message.message = $this.children('p')
.html()
.replace(/<strong>(.*?)<\/strong>/g,'');
var target = (parent.postMessage ? parent : (parent.document.postMessage ? parent.document : undefined));
target.postMessage(JSON.stringify(message, null, 2), '*');
});
})();
</script>

View File

@ -0,0 +1,213 @@
<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 .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>
</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">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">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>
</g>
</svg>

View File

@ -0,0 +1,29 @@
{% extends "horizon/client_side/template.html" %}
{% load horizon %}
{% block id %}balloon_container{% endblock %}
{% block template %}
{% jstemplate %}
<div class="topologyBalloon" id="[[balloon_id]]">
<a href="#close" class="closeTopologyBalloon">&times;</a>
<div class="contentBody">
[[> table1]]
[[> table2]]
</div>
<div class="footer">
<div class="footerInner">
<div class="cell link">
<a href="[[url]]">» view [[type]] details</a>
[[#console_id]]
<a href="/project/instances/[[console_id]]/vnc" class="vnc_window">» open console</a>
[[/console_id]]
</div>
<div class="cell delete">
<button class="delete-device btn btn-danger btn-mini [[type]]" data-type="[[type]]" data-device-id="[[id]]">[[type_capital]]</button>
</div>
</div>
</div>
</div>
{% endjstemplate %}
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends "horizon/client_side/template.html" %}
{% load horizon %}
{% block id %}balloon_device{% endblock %}
{% block template %}
{% jstemplate %}
<table class="detaiInfoTable">
<caption>[[name]]</caption>
<tbody>
<tr>
<th class="device">ID</th>
<td>[[id]]</td>
</tr>
<tr>
<th class="device">STATUS</th>
<td>
<span class="[[status_class]]">[[status]]</span>
</td>
</tr>
</tbody>
</table>
{% endjstemplate %}
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends "horizon/client_side/template.html" %}
{% load horizon %}
{% block id %}balloon_port{% endblock %}
{% block template %}
{% jstemplate %}
<table class="detaiInfoTable">
<caption>Interfaces</caption>
<tbody>
[[#port]]
<tr>
<th>
<span title="[[id]]">
<a href="[[url]]">[[id]]</a>
</span>
</th>
<td>[[ip_address]]</td>
<td>[[device_owner]]</td>
<td>
<span class="[[port_status_class]]">[[port_status]]</span>
</td>
<td class="delete">
[[#is_interface]]
<button class="delete-port btn btn-danger btn-mini" data-router-id="[[router_id]]" data-port-id="[[id]]">Interface</button>
[[/is_interface]]
</td>
</tr>
[[/port]]
</tbody>
</table>
{% endjstemplate %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Router" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create a Router") %}
{% endblock page_header %}
{% block main %}
{% include 'project/network_topology/_create_router.html' %}
{% endblock %}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
<script src='{{ STATIC_URL }}horizon/lib/jquery/jquery.min.js' type='text/javascript' charset="utf-8"></script>
</head>
<body>
{% include "horizon/_messages.html" %}
{% firstof table.render interfaces_table.render %}
{% include "project/network_topology/_post_massage.html" %}
</body>
</html>

View File

@ -8,30 +8,32 @@
{% endblock page_header %}
{% block main %}
<style>
/* TODO(nati): The following styles are not work with compress, so put it here tempolary */
div.network .router:hover div.port,
div.network .server:hover div.port,
div.network .device:hover div.port {
background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(0, 0, 0, 0.25)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(0, 0, 0, 0.25)), color-stop(0.75, rgba(0, 0, 0, 0.25)), color-stop(0.75, transparent), to(transparent));
background-image: -webkit-linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.25) 50%, rgba(0, 0, 0, 0.25) 75%, transparent 75%, transparent);
background-image: -moz-linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.25) 50%, rgba(0, 0, 0, 0.25) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.25) 50%, rgba(0, 0, 0, 0.25) 75%, transparent 75%, transparent);
background-image: linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.25) 50%, rgba(0, 0, 0, 0.25) 75%, transparent 75%, transparent);
}
</style>
<noscript>
{% trans "This pane needs javascript support." %}
</noscript>
<div class="launchButtons">
<a href="{% url 'horizon:project:instances:launch' %}" id="instances__action_launch" class="btn btn-small btn-launch ajax-modal">{%trans "Launch Instance" %}</a>
<a href="{% url 'horizon:project:networks:create' %}" id="networks__action_create" class="btn btn-small ajax-modal btn-create">{%trans "Create Network" %}</a>
<a href="{% url 'horizon:project:routers:create' %}" id="Routers__action_create" class="btn btn-small ajax-modal btn-create">{%trans "Create Router" %}</a>
{% include "project/network_topology/client_side/_balloon_container.html" %}
{% include "project/network_topology/client_side/_balloon_device.html" %}
{% include "project/network_topology/client_side/_balloon_port.html" %}
<div class="topologyNavi">
<div class="toggleView btn-group" data-toggle="buttons-radio">
<button type="button" class="btn small" data-value="small"><i class="icon-th"></i>{%trans "Small" %}</button>
<button type="button" class="btn normal" data-value="normal"><i class="icon-th-large"></i>{%trans "Normal" %}</button>
</div>
<div class="launchButtons">
<a href="{% url 'horizon:project:network_topology:launchinstance' %}" id="instances__action_launch" class="btn btn-small btn-launch ajax-modal">{%trans "Launch Instance" %}</a>
<a href="{% url 'horizon:project:network_topology:createnetwork' %}" id="networks__action_create" class="btn btn-small ajax-modal btn-create">{%trans "Create Network" %}</a>
<a href="{% url 'horizon:project:network_topology:createrouter' %}" id="Routers__action_create" class="btn btn-small ajax-modal btn-create">{%trans "Create Router" %}</a>
</div>
</div>
<div id="topologyCanvas">
<div class="networks"></div>
<div class="nodata">{% blocktrans %}There are no networks, routers, or connected instances to display. {% endblocktrans %}</div>
<div id="topologyCanvasContainer">
<div class="nodata">{% blocktrans %}There are no networks, routers, or connected instances to display.{% endblocktrans %}</div>
{% include "project/network_topology/_svg_element.html" %}
</div>
<span data-networktopology="{% url 'horizon:project:network_topology:json' %}" id="networktopology"></span>
<div id="topologyMessages"></div>
{% endblock %}

View File

@ -22,11 +22,36 @@
from django.conf.urls.defaults import patterns # noqa
from django.conf.urls.defaults import url # noqa
from openstack_dashboard.dashboards.project.network_topology import views
from openstack_dashboard.dashboards.project.network_topology.views import\
InstanceView # noqa
from openstack_dashboard.dashboards.project.network_topology.views import\
JSONView # noqa
from openstack_dashboard.dashboards.project.network_topology.views import\
NetworkTopologyView # noqa
from openstack_dashboard.dashboards.project.network_topology.views import\
NTCreateNetworkView # noqa
from openstack_dashboard.dashboards.project.network_topology.views import\
NTCreateRouterView # noqa
from openstack_dashboard.dashboards.project.network_topology.views import\
NTLaunchInstanceView # noqa
from openstack_dashboard.dashboards.project.network_topology.views import\
RouterDetailView # noqa
from openstack_dashboard.dashboards.project.network_topology.views import\
RouterView # noqa
urlpatterns = patterns(
'openstack_dashboard.dashboards.project.network_topology.views',
url(r'^$', views.NetworkTopology.as_view(), name='index'),
url(r'^json$', views.JSONView.as_view(), name='json'),
url(r'^$', NetworkTopologyView.as_view(), name='index'),
url(r'^router$', RouterView.as_view(), name='router'),
url(r'^instance$', InstanceView.as_view(), name='instance'),
url(r'^router/(?P<router_id>[^/]+)/$', RouterDetailView.as_view(),
name='detail'),
url(r'^json$', JSONView.as_view(), name='json'),
url(r'^launchinstance$', NTLaunchInstanceView.as_view(),
name='launchinstance'),
url(r'^createnetwork$', NTCreateNetworkView.as_view(),
name='createnetwork'),
url(r'^createrouter$', NTCreateRouterView.as_view(),
name='createrouter'),
)

View File

@ -21,14 +21,73 @@
import json
from django.core.urlresolvers import reverse # noqa
from django.core.urlresolvers import reverse_lazy # noqa
from django.http import HttpResponse # noqa
from django.views.generic import TemplateView # noqa
from django.views.generic import View # noqa
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.network_topology.instances.tables\
import InstancesTable # noqa
from openstack_dashboard.dashboards.project.network_topology.ports.tables\
import PortsTable # noqa
from openstack_dashboard.dashboards.project.network_topology.routers.tables\
import RoutersTable # noqa
class NetworkTopology(TemplateView):
from openstack_dashboard.dashboards.project.instances import\
views as i_views
from openstack_dashboard.dashboards.project.instances.workflows import\
create_instance as i_workflows
from openstack_dashboard.dashboards.project.networks import\
views as n_views
from openstack_dashboard.dashboards.project.networks import\
workflows as n_workflows
from openstack_dashboard.dashboards.project.routers import\
views as r_views
class NTCreateRouterView (r_views.CreateView):
template_name = 'project/network_topology/create_router.html'
success_url = reverse_lazy("horizon:project:network_topology:index")
class NTCreateNetwork (n_workflows.CreateNetwork):
def get_success_url(self):
return reverse("horizon:project:network_topology:index")
def get_failure_url(self):
return reverse("horizon:project:network_topology:index")
class NTCreateNetworkView (n_views.CreateView):
workflow_class = NTCreateNetwork
class NTLaunchInstance (i_workflows.LaunchInstance):
success_url = "horizon:project:network_topology:index"
class NTLaunchInstanceView (i_views.LaunchInstanceView):
workflow_class = NTLaunchInstance
class InstanceView (i_views.IndexView):
table_class = InstancesTable
template_name = 'project/network_topology/iframe.html'
class RouterView (r_views.IndexView):
table_class = RoutersTable
template_name = 'project/network_topology/iframe.html'
class RouterDetailView (r_views.DetailView):
table_classes = (PortsTable, )
template_name = 'project/network_topology/iframe.html'
class NetworkTopologyView (TemplateView):
template_name = 'project/network_topology/index.html'
@ -57,33 +116,39 @@ class JSONView(View):
servers = []
data['servers'] = [{'name': server.name,
'status': server.status,
'task': getattr(server, 'OS-EXT-STS:task_state'),
'id': server.id} for server in servers]
self.add_resource_url('horizon:project:instances:detail',
data['servers'])
# Get neutron data
# if we didn't specify tenant_id, all networks shown as admin user.
# so it is need to specify the networks. However there is no need to
# specify tenant_id for subnet. The subnet which belongs to the public
# network is needed to draw subnet information on public network.
try:
neutron_public_networks = api.neutron.network_list(request,
**{'router:external': True})
neutron_networks = api.neutron.network_list_for_tenant(request,
request.user.tenant_id)
neutron_subnets = api.neutron.subnet_list(request,
tenant_id=request.user.tenant_id)
neutron_ports = api.neutron.port_list(request,
tenant_id=request.user.tenant_id)
neutron_routers = api.neutron.router_list(request,
tenant_id=request.user.tenant_id)
neutron_public_networks = api.neutron.network_list(
request,
**{'router:external': True})
neutron_networks = api.neutron.network_list_for_tenant(
request,
request.user.tenant_id)
neutron_ports = api.neutron.port_list(request)
neutron_routers = api.neutron.router_list(
request,
tenant_id=request.user.tenant_id)
except Exception:
neutron_public_networks = []
neutron_networks = []
neutron_subnets = []
neutron_ports = []
neutron_routers = []
networks = [{'name': network.name,
'id': network.id,
'router:external': network['router:external']}
for network in neutron_networks]
'id': network.id,
'subnets': [{'cidr': subnet.cidr}
for subnet in network.subnets],
'router:external': network['router:external']}
for network in neutron_networks]
self.add_resource_url('horizon:project:networks:detail',
networks)
# Add public networks to the networks list
@ -93,29 +158,37 @@ class JSONView(View):
if publicnet.id == network['id']:
found = True
if not found:
networks.append({'name': publicnet.name,
'id': publicnet.id,
'router:external': publicnet['router:external']})
try:
subnets = [{'cidr': subnet.cidr}
for subnet in publicnet.subnets]
except Exception:
subnets = []
networks.append({
'name': publicnet.name,
'id': publicnet.id,
'subnets': subnets,
'router:external': publicnet['router:external']})
data['networks'] = sorted(networks,
key=lambda x: x.get('router:external'),
reverse=True)
data['subnets'] = [{'id': subnet.id,
'cidr': subnet.cidr,
'network_id': subnet.network_id}
for subnet in neutron_subnets]
data['ports'] = [{'id': port.id,
'network_id': port.network_id,
'device_id': port.device_id,
'fixed_ips': port.fixed_ips} for port in neutron_ports]
'network_id': port.network_id,
'device_id': port.device_id,
'fixed_ips': port.fixed_ips,
'device_owner': port.device_owner,
'status': port.status
}
for port in neutron_ports]
self.add_resource_url('horizon:project:networks:ports:detail',
data['ports'])
data['routers'] = [{'id': router.id,
'name': router.name,
'external_gateway_info': router.external_gateway_info}
for router in neutron_routers]
data['routers'] = [{
'id': router.id,
'name': router.name,
'status': router.status,
'external_gateway_info': router.external_gateway_info}
for router in neutron_routers]
# user can't see port on external network. so we are
# adding fake port based on router information

View File

@ -1701,9 +1701,7 @@ label.log-length {
border: 1px solid #ccc;
min-height: 2em;
width: auto !important;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
.box-sizing(border-box);
li {
width: 226px;
list-style-type: none;
@ -1728,16 +1726,13 @@ label.log-length {
vertical-align: middle;
}
a.btn {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
.box-sizing(border-box);
font-size: 11px;
line-height: 12px;
padding: 2px 5px 3px;
margin-right: 1px;
width: 18px;
text-align: center;
//position: absolute;
right:5px;
vertical-align: middle;
float: right;
@ -1804,302 +1799,236 @@ label.log-length {
}
/* Styling for network topology */
.box-sizing(@box: border-box) {
-webkit-box-sizing: @box;
-moz-box-sizing: @box;
-ms-box-sizing: @box;
-o-box-sizing: @box;
box-sizing: @box;
}
@-webkit-keyframes progress-bar-stripes {
from {
background-position: 20px 0;
}
to {
background-position: 0 0;
}
}
@-moz-keyframes progress-bar-stripes {
from {
background-position: 20px 0;
}
to {
background-position: 0 0;
}
}
@-ms-keyframes progress-bar-stripes {
from {
background-position: 20px 0;
}
to {
background-position: 0 0;
}
}
@-o-keyframes progress-bar-stripes {
from {
background-position: 0 0;
}
to {
background-position: 20px 0;
}
}
@keyframes progress-bar-stripes {
from {
background-position: 20px 0;
}
to {
background-position: 0 0;
}
}
#topologyCanvas {
.box-sizing();
/**** Network Topology CSS ****/
#topologyCanvasContainer {
.box-sizing(border-box);
width: 100%;
height: 500px;
height: auto;
padding: 25px;
padding-left: 50px;
background: #efefef;
min-height: 400px;
div.nodata {
font-size: 150%;
font-weight: bold;
text-align: center;
padding-top: 200px;
padding-top: 150px;
display: none;
}
}
div.networks {
height: 100%;
}
div.network {
.box-sizing();
float: left;
width: 270px;
height: 100%;
position: relative;
.nicname {
.box-sizing();
height: 100%;
width: 17px;
border-radius: 17px;
z-index: 200;
color:#fff;
position: absolute;
left: -8px;
top: 0px;
cursor: pointer;
&:hover {
background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15));
background-image: -moz-linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15));
background-image: -ms-linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15));
background-image: -o-linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15));
background-image: linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15));
background-size: 10px 10px;
}
&.nourl {
cursor: auto;
&:hover {
background-image:none;
}
}
h3 {
font-size: 12px;
line-height: 1;
position: relative;
font-weight: normal;
top:55%;
color:#fff;
left:-1px;
letter-spacing: 0.2em;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
white-space: nowrap;
text-shadow: 0px 0px 5px #000;
}
span.ip {
position: absolute;
bottom:-10px;
left:20px;
color: #000;
&.noinfo {
div.nodata {
display: block;
font-weight: normal;
font-size: 90%;
letter-spacing: 0.2em;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
-webkit-transform-origin: 0% 0%;
-moz-transform-origin: 0% 0%;
-ms-transform-origin: 0% 0%;
-o-transform-origin: 0% 0%;
transform-origin: 0% 0%;
white-space: nowrap;
text-shadow: 0px 0px 2px #fff,0px 0px 2px #fff;
}
}
.router, .server, .device {
.box-sizing();
cursor: pointer;
width: 90px;
border: 3px solid #444;
position: absolute;
top:30px;
left:90px;
color:#fff;
padding: 0 3px;
background: #666;
margin-bottom: 20px;
border-radius: 8px;
&:before {
content: "";
width: 20px;
height: 20px;
border: 2px solid #444;
line-height: 1.2;
position: absolute;
border-radius: 20px;
top:-10px;
left:-10px;
background:#fff url(/static/dashboard/img/router.png) no-repeat center center;
background-size: 16px 16px;
#topology_canvas {
display: none;
}
&:after {
content: "";
width: 100%;
line-height: 1.2;
position: absolute;
text-align: center;
border-radius: 0;
background: #444;
color: #fff;
font-size: 11px;
height: 1.5em;
bottom:0px;
left: 0px;
}
span.devicename {
position: absolute;
color: #fff;
bottom: 0px;
font-size: 12px;
line-height: 14px;
width: 100%;
text-align: center;
z-index:300;
left:-2px;
}
span.name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: block;
font-size : 12px;
position: relative;
z-index:10;
text-align: center;
top:4px;
padding: 0 3px;
}
div.port {
text-align: right;
min-width: 90px;
height: 10px;
font:0px/0px sans-serif;
position: absolute;
left:-91px;
top:8px;
background-color: #37a9e3;
background-image: none;
-webkit-background-size: 20px 20px;
-moz-background-size: 20px 20px;
-o-background-size: 20px 20px;
background-size: 20px 20px;
z-index:100;
span.ip {
.box-sizing();
color: #333;
font-size: 9px;
line-height: 1;
text-shadow: 0px -1px #fff;
position: relative;
width: 90px;
display: inline-block;
padding-right:8px;
padding-left: 8px;
word-wrap:break-word;
word-break:break-all;
}
&.right {
left:auto;
right:-92px;
width: 92px;
text-align: left;
span.ip {
}
}
}
&:hover {
div.port {
cursor:pointer;
background-color: #2688c0;
-webkit-animation: progress-bar-stripes 1s linear infinite;
-moz-animation: progress-bar-stripes 1s linear infinite;
-ms-animation: progress-bar-stripes 1s linear infinite;
-o-animation: progress-bar-stripes 1s linear infinite;
animation: progress-bar-stripes 1s linear infinite;
&:hover {
-webkit-animation: progress-bar-stripes 0.3s linear infinite;
-moz-animation: progress-bar-stripes 0.3s linear infinite;
-ms-animation: progress-bar-stripes 0.3s linear infinite;
-o-animation: progress-bar-stripes 0.3s linear infinite;
animation: progress-bar-stripes 0.3s linear infinite;
}
&.nourl {
cursor: auto;
background-image:none;
&:hover {
background-image:none;
}
}
}
border-color: #222;
&:after {
background-color: #222;
border-color: #222;
}
}
}
.device {
border:none;
background: transparent;
}
.server {
&:before {
background:#fff url(/static/dashboard/img/server.png) no-repeat center center;
background-size: 14px 14px;
}
background: #fff;
color:#333;
}
}
.launchButtons {
text-align: right;
margin: 10px 0px 15px 10px;
a.btn {
.topologyNavi {
overflow: hidden;
i {margin-right: 3px;}
margin: 10px 0 20px;
.toggleView {
float: left;
}
.launchButtons {
float: right;
text-align: right;
a.btn {
margin-left: 10px;
}
}
}
.topologyBalloon {
display: none;
background: #fff;
position: absolute;
left:100px;
top:20px;
z-index: 600;
border-radius: 5px;
color:#333;
min-width: 200px;
&.on {
display: block;
}
line-height: 1.2;
.vnc_window {
margin-left: 10px;
}
.closeTopologyBalloon {
font-size: 16px;
line-height: 1;
display: block;
position: absolute;
font-weight: bold;
right: 6px;
top: 0px;
cursor: pointer;
padding: 3px;
color:#aaa;
&:hover {
color:#777;
text-decoration: none;
}
}
.contentBody {
padding: 8px 8px 0;
}
span.active, span.down {
&:before {
content: "";
width: 9px;
height: 9px;
display: inline-block;
background: #0d925b;
margin-right: 3px;
border-radius: 10px;
vertical-align: middle;
}
}
span.down {
&:before {
background: #e64b41;
}
}
.footer {
background: #efefef;
border-top: 1px solid #d9d9d9;
padding: 8px;
border-radius: 0px 0px 7px 7px;
.footerInner {
display: table;
width: 100%;
}
.cell {
display: table-cell;
padding-right: 10px;
}
.link {
font-size: 12px;
}
.delete {
padding-right: 0;
text-align: right;
.btn {
&:before {
content:"Delete ";
}
&.deleting:before {
content:"Deleting ";
}
}
.btn.instance {
&:before {
content:"Terminate ";
}
&.deleting:before {
content:"Terminating ";
}
}
}
}
table.detaiInfoTable {
margin-bottom: 5px;
caption {
text-align: left;
font-size: 12px;
font-weight: bold;
margin-bottom: 0px;
}
th,td {
text-align: left;
vertical-align: middle;
padding-bottom: 3px;
background: transparent;
}
th {
color:#999;
padding-right: 8px;
width: 80px;
span {
vertical-align: middle;
width:80px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
&.device {
text-align: right;
}
}
td {
padding-right: 5px;
white-space: nowrap;
}
td.delete {
padding-right: 0;
text-align: right;
}
.btn {
line-height: 1.4;
}
.btn:before {
content:"Delete ";
}
.btn.deleting:before {
content:"Deleting ";
}
}
font-size: 11px;
.box-shadow(0px 1px 6px #777);
&:before {
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-right: 9px solid #bbb;
display: block;
position: absolute;
top: 30px;
left: -9px;
width: 0;
height: 0;
content: "";
}
&:after {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-right: 8px solid #fff;
display: block;
position: absolute;
top: 31px;
left: -8px;
width: 0;
height: 0;
content: "";
}
&.leftPosition {
&:before {
border-right: none;
border-left: 9px solid #bbb;
right: -9px;
top: 30px;
left:auto;
}
&:after {
border-right: none;
border-left: 8px solid #fff;
right: -8px;
top: 31px;
left:auto;
}
}
}
#topologyMessages {
width:1px;
height:1px;
visibility: hidden;
position: absolute;
top: -100px;
}
/**** Resource Topology CSS ****/