summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToshi Hayashi <hayashi@ntti3.com>2013-08-12 17:20:38 -0700
committerToshiyuki Hayashi <hayashi@ntti3.com>2013-08-29 00:07:27 -0700
commit88b9335910771b52979735ce90c9d9b1e35ba5f6 (patch)
tree6949bef71efa0d3d2ae40aa671bd132a71ee9468
parentd45413e9bfc5c0730ce10daa4251cfbff086f3b4 (diff)
downloadhorizon-88b9335910771b52979735ce90c9d9b1e35ba5f6.tar.gz
Delete and launch devices on the topology view
This feature enables to delete and launch a instance or router on the network topology view. So you can do basic actions on this view. Also it enables to change view small or normal. You can see more networks and devices in the small view. implements bp editable-network-topology-view fixes bug #1215683 Change-Id: Ie65d50d2a99f72696c8f10223f8430ad5f90b144
-rw-r--r--horizon/static/horizon/js/horizon.networktopology.js771
-rwxr-xr-xmanage.py2
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/instances/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/instances/tables.py25
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/ports/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/ports/tables.py31
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/routers/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/routers/tables.py33
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_create_router.html21
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_post_massage.html27
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_svg_element.html213
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_container.html29
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_device.html24
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_port.html33
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/create_router.html11
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/iframe.html12
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/templates/network_topology/index.html40
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/urls.py31
-rw-r--r--openstack_dashboard/dashboards/project/network_topology/views.py133
-rw-r--r--openstack_dashboard/static/dashboard/less/horizon.less457
20 files changed, 1352 insertions, 541 deletions
diff --git a/horizon/static/horizon/js/horizon.networktopology.js b/horizon/static/horizon/js/horizon.networktopology.js
index dd4bd7e2e..8e6c3612d 100644
--- a/horizon/static/horizon/js/horizon.networktopology.js
+++ b/horizon/static/horizon/js/horizon.networktopology.js
@@ -1,280 +1,603 @@
/* 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
- );
- }
- },
- network_color: function(network_id){
- var max_hue = 360;
- var num_network = this.model.networks.length;
- if(num_network <= 0){
- return;
+ 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);
}
- 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;
+ $('.toggleView > .btn').each(function(){
+ var $this = $(this);
+ if($this.hasClass(self.draw_mode)) {
+ $this.addClass('active');
}
- return "rgb(" + Math.round(r*255) + "," + Math.round(g*255) + "," + Math.round(b*255) + ")";
+ });
},
- draw_networks: function(){
+ data_convert: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) ";
- }
+ var model = self.model;
+ $.each(model.networks, function(index, network) {
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;
- }
- cidr.push(subnet.cidr);
+ 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);
+ }
+ });
+ });
});
- return cidr.join(', ');
+ 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_devices: function(type){
+ draw_topology:function() {
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;
+ $(self.svg_container).spin(false);
+ $(self.svg_container).removeClass('noinfo');
+ if (self.model.networks.length <= 0) {
+ $('g.network').remove();
+ $(self.svg_container).addClass('noinfo');
+ return;
+ }
+ var svg = d3.select(self.svg);
+ var element_properties = self.element_properties[self.draw_mode];
+ svg
+ .attr('width',self.model.networks.length*element_properties.network_width)
+ .attr('height',self.network_height);
+
+ var network = svg.selectAll('g.network')
+ .data(self.model.networks);
+
+ 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);
+ }
+ });
+
+ 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));
+ });
+
+ 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;
});
- 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
+ .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);
+ }
+ }
});
- return self.model[type + 's'].length;
- },
- sum_port_length: function(network_id, ports){
- var self = this;
- var sum_port_length = 0;
- var base_index = self.network_index(network_id);
- $.each(ports, function(index, port){
- sum_port_length += base_index - self.network_index(port.network_id);
+
+ 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 sum_port_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){
- port_length = horizon.network_topology.sum_port_length(port.network_id, ports)
+ 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];
},
- draw_routers: function(){
- return this.draw_devices('router');
+ sum_port_length: function(network_id, ports){
+ var self = this;
+ var sum_port_length = 0;
+ var base_index = self.network_index(network_id);
+ $.each(ports, function(index, port){
+ sum_port_length += base_index - self.network_index(port.network_id);
+ });
+ return sum_port_length;
},
- draw_servers: function(){
- return this.draw_devices('server');
+ string_truncate: function(string) {
+ var self = this;
+ var str = string;
+ var max_size = self.element_properties.device_name_max_size;
+ var suffix = self.element_properties.device_name_suffix;
+ var bytes = 0;
+ for (var i = 0; i < str.length; i++) {
+ bytes += str.charCodeAt(i) <= 255 ? 1 : 2;
+ if (bytes > max_size) {
+ str = str.substr(0, i) + suffix;
+ break;
+ }
+ }
+ return str;
},
- select_port: function(device_id){
- return $.map(this.model.ports,function(port, index){
- if (port.device_id == device_id) {
- return port;
- }
- });
+ delete_device: function(type, device_id) {
+ var self = this;
+ var message = {id:device_id};
+ self.post_message(device_id,type,message);
},
- port_html: function(port){
+ delete_port: function(router_id, port_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: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
+ });
+ } else if (d.type == 'instance') {
+ html_data.console_id = d.id;
+ html = balloon_tmpl.render(html_data,{
+ table1:device_tmpl
});
- if(port.url == undefined) {
- port_html.addClass("nourl");
+ } 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();
});
diff --git a/manage.py b/manage.py
index 256981c54..fe16377ab 100755
--- a/manage.py
+++ b/manage.py
@@ -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",
diff --git a/openstack_dashboard/dashboards/project/network_topology/instances/__init__.py b/openstack_dashboard/dashboards/project/network_topology/instances/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/instances/__init__.py
diff --git a/openstack_dashboard/dashboards/project/network_topology/instances/tables.py b/openstack_dashboard/dashboards/project/network_topology/instances/tables.py
new file mode 100644
index 000000000..90e37f9e4
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/instances/tables.py
@@ -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,)
diff --git a/openstack_dashboard/dashboards/project/network_topology/ports/__init__.py b/openstack_dashboard/dashboards/project/network_topology/ports/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/ports/__init__.py
diff --git a/openstack_dashboard/dashboards/project/network_topology/ports/tables.py b/openstack_dashboard/dashboards/project/network_topology/ports/tables.py
new file mode 100644
index 000000000..77f36511c
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/ports/tables.py
@@ -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, )
diff --git a/openstack_dashboard/dashboards/project/network_topology/routers/__init__.py b/openstack_dashboard/dashboards/project/network_topology/routers/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/routers/__init__.py
diff --git a/openstack_dashboard/dashboards/project/network_topology/routers/tables.py b/openstack_dashboard/dashboards/project/network_topology/routers/tables.py
new file mode 100644
index 000000000..eedff2d78
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/routers/tables.py
@@ -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,)
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_create_router.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_create_router.html
new file mode 100644
index 000000000..cb0c8deb4
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_create_router.html
@@ -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 %}
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_post_massage.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_post_massage.html
new file mode 100644
index 000000000..be2582dc0
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_post_massage.html
@@ -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>
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_svg_element.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_svg_element.html
new file mode 100644
index 000000000..97f076ba2
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_svg_element.html
@@ -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>
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_container.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_container.html
new file mode 100644
index 000000000..4493d7223
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_container.html
@@ -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 %}
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_device.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_device.html
new file mode 100644
index 000000000..b7397d718
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_device.html
@@ -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 %}
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_port.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_port.html
new file mode 100644
index 000000000..4deb818e7
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_port.html
@@ -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 %}
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/create_router.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/create_router.html
new file mode 100644
index 000000000..3a2f384a1
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/create_router.html
@@ -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 %}
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/iframe.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/iframe.html
new file mode 100644
index 000000000..a7ffbf387
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/iframe.html
@@ -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>
diff --git a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/index.html b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/index.html
index 75868d75c..633abfa23 100644
--- a/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/index.html
+++ b/openstack_dashboard/dashboards/project/network_topology/templates/network_topology/index.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 %}
diff --git a/openstack_dashboard/dashboards/project/network_topology/urls.py b/openstack_dashboard/dashboards/project/network_topology/urls.py
index 0f51a1a31..eec9a88a8 100644
--- a/openstack_dashboard/dashboards/project/network_topology/urls.py
+++ b/openstack_dashboard/dashboards/project/network_topology/urls.py
@@ -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'),
)
diff --git a/openstack_dashboard/dashboards/project/network_topology/views.py b/openstack_dashboard/dashboards/project/network_topology/views.py
index 4d1c305b1..eceedcd60 100644
--- a/openstack_dashboard/dashboards/project/network_topology/views.py
+++ b/openstack_dashboard/dashboards/project/network_topology/views.py
@@ -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
diff --git a/openstack_dashboard/static/dashboard/less/horizon.less b/openstack_dashboard/static/dashboard/less/horizon.less
index bc9415c6a..2c8fbffdf 100644
--- a/openstack_dashboard/static/dashboard/less/horizon.less
+++ b/openstack_dashboard/static/dashboard/less/horizon.less
@@ -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;
}
+ &.noinfo {
+ div.nodata {
+ display: block;
+
+ }
+ #topology_canvas {
+ display: none;
+ }
+ }
}
-div.networks {
- height: 100%;
+
+.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;
+ }
+ }
}
-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;
+
+.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;
- left: -8px;
+ font-weight: bold;
+ right: 6px;
top: 0px;
cursor: pointer;
+ padding: 3px;
+ color:#aaa;
&: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;
- 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;
+ color:#777;
+ text-decoration: 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;
+ .contentBody {
+ padding: 8px 8px 0;
+ }
+ span.active, span.down {
&: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;
+ width: 9px;
+ height: 9px;
+ display: inline-block;
+ background: #0d925b;
+ margin-right: 3px;
+ border-radius: 10px;
+ vertical-align: middle;
}
- &: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.down {
+ &:before {
+ background: #e64b41;
}
- span.devicename {
- position: absolute;
- color: #fff;
- bottom: 0px;
- font-size: 12px;
- line-height: 14px;
+ }
+ .footer {
+ background: #efefef;
+ border-top: 1px solid #d9d9d9;
+ padding: 8px;
+ border-radius: 0px 0px 7px 7px;
+ .footerInner {
+ display: table;
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;
+ .cell {
+ display: table-cell;
+ padding-right: 10px;
+ }
+ .link {
+ font-size: 12px;
}
- div.port {
+ .delete {
+ padding-right: 0;
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 {
+ .btn {
+ &:before {
+ content:"Delete ";
+ }
+ &.deleting:before {
+ content:"Deleting ";
}
}
- }
- &: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;
+ .btn.instance {
+ &:before {
+ content:"Terminate ";
}
- &.nourl {
- cursor: auto;
- background-image:none;
- &:hover {
- background-image:none;
- }
+ &.deleting:before {
+ content:"Terminating ";
}
}
- border-color: #222;
- &:after {
- background-color: #222;
- border-color: #222;
+ }
+ }
+ 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: "";
}
- .device {
- border:none;
- background: transparent;
+ &: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: "";
}
- .server {
+ &.leftPosition {
&:before {
- background:#fff url(/static/dashboard/img/server.png) no-repeat center center;
- background-size: 14px 14px;
+ 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;
}
- background: #fff;
- color:#333;
}
}
-.launchButtons {
- text-align: right;
- margin: 10px 0px 15px 10px;
- a.btn {
- margin-left: 10px;
- }
+#topologyMessages {
+ width:1px;
+ height:1px;
+ visibility: hidden;
+ position: absolute;
+ top: -100px;
}
/**** Resource Topology CSS ****/