Merge "Delete and launch devices on the topology view"

This commit is contained in:
Jenkins 2013-08-30 20:22:50 +00:00 committed by Gerrit Code Review
commit 60acb67e18
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 ****/