Merge "Delete and launch devices on the topology view"
This commit is contained in:
commit
60acb67e18
@ -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();
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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,)
|
@ -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, )
|
@ -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,)
|
@ -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 %}
|
@ -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>
|
@ -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>
|
@ -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">×</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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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>
|
@ -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 %}
|
||||
|
@ -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'),
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
&.noinfo {
|
||||
div.nodata {
|
||||
display: block;
|
||||
|
||||
white-space: nowrap;
|
||||
text-shadow: 0px 0px 5px #000;
|
||||
}
|
||||
span.ip {
|
||||
position: absolute;
|
||||
bottom:-10px;
|
||||
left:20px;
|
||||
color: #000;
|
||||
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;
|
||||
#topology_canvas {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
&: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 ****/
|
||||
|
Loading…
Reference in New Issue
Block a user