Adding network-topology view for quantum
Implements bp quantum-network-topology - network-topology pane shows the topology of quantum network - supports network, router , instances now - support of load balancer is future work Change-Id: Idb1bf2d3add43cf51ed23ebaac3ee740b03498aa
This commit is contained in:
parent
f2fb22cf59
commit
250d21a562
|
@ -0,0 +1,255 @@
|
||||||
|
/* Namespace for core functionality related to Network Topology. */
|
||||||
|
horizon.network_topology = {
|
||||||
|
model: null,
|
||||||
|
network_margin: 270,
|
||||||
|
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,
|
||||||
|
network_index: {},
|
||||||
|
network_color_unit: 0,
|
||||||
|
network_saturation: 1,
|
||||||
|
network_lightness: 0.7,
|
||||||
|
reload_duration: 10000,
|
||||||
|
spinner:null,
|
||||||
|
init:function(){
|
||||||
|
var self = this;
|
||||||
|
$("#topologyCanvas").spin(horizon.conf.spinner_options.modal);
|
||||||
|
self.retrieve_network_info();
|
||||||
|
setInterval(horizon.network_topology.retrieve_network_info,
|
||||||
|
horizon.network_topology.reload_duration);
|
||||||
|
},
|
||||||
|
retrieve_network_info: function(){
|
||||||
|
var self = this;
|
||||||
|
if(!$("#networktopology")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.getJSON($("#networktopology").data('networktopology'),
|
||||||
|
function(data) {
|
||||||
|
self.draw_graph(data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
draw_loading: function () {
|
||||||
|
$("#topologyCanvas").spin(horizon.conf.spinner_options.modal);
|
||||||
|
},
|
||||||
|
draw_graph: function(data){
|
||||||
|
var canvas = $("#topologyCanvas");
|
||||||
|
var networks = $("#topologyCanvas > .networks");
|
||||||
|
canvas.spin(false);
|
||||||
|
networks.empty();
|
||||||
|
this.model = data;
|
||||||
|
this.device_last_position = this.device_initial_position;
|
||||||
|
this.draw_networks();
|
||||||
|
this.draw_routers();
|
||||||
|
this.draw_servers();
|
||||||
|
canvas.height(
|
||||||
|
Math.max(this.device_last_position,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;
|
||||||
|
}
|
||||||
|
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) ";
|
||||||
|
}
|
||||||
|
label += self.select_cidr(network.id);
|
||||||
|
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></div>");
|
||||||
|
nicname_html
|
||||||
|
.click(function (){
|
||||||
|
window.location.href = network.url;})
|
||||||
|
.css (
|
||||||
|
{'background-color':self.network_color(network.id)})
|
||||||
|
.appendTo(network_html);
|
||||||
|
networks.append(network_html);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
select_cidr:function(network_id){
|
||||||
|
var cidr = "";
|
||||||
|
$.each(this.model.subnets, function(index, subnet){
|
||||||
|
if(subnet.network_id != network_id){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cidr += " <span class=\"ip\">[ " + subnet.cidr + " ]</span>";
|
||||||
|
});
|
||||||
|
return cidr;
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ports[main_port_index];
|
||||||
|
},
|
||||||
|
draw_routers: function(){
|
||||||
|
this.draw_devices('router');
|
||||||
|
},
|
||||||
|
draw_servers: function(){
|
||||||
|
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){
|
||||||
|
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 + " ";
|
||||||
|
})
|
||||||
|
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();
|
||||||
|
window.location.href = port.url;
|
||||||
|
});
|
||||||
|
return port_html;
|
||||||
|
},
|
||||||
|
port_css: function(port_html, position, network_a, network_b){
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
horizon.addInitFunction(function () {
|
||||||
|
horizon.network_topology.init();
|
||||||
|
});
|
|
@ -32,6 +32,7 @@
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.users.js' type='text/javascript' charset='utf-8'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.users.js' type='text/javascript' charset='utf-8'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.utils.js' type='text/javascript' charset='utf-8'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.utils.js' type='text/javascript' charset='utf-8'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.projects.js' type='text/javascript' charset='utf-8'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.projects.js' type='text/javascript' charset='utf-8'></script>
|
||||||
|
<script src='{{ STATIC_URL }}horizon/js/horizon.networktopology.js' type='text/javascript' charset='utf-8'></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
|
||||||
{% comment %} Client-side Templates (These should *not* be inside the "compress" tag.) {% endcomment %}
|
{% comment %} Client-side Templates (These should *not* be inside the "compress" tag.) {% endcomment %}
|
||||||
|
|
|
@ -28,7 +28,8 @@ class BasePanels(horizon.PanelGroup):
|
||||||
'images_and_snapshots',
|
'images_and_snapshots',
|
||||||
'access_and_security',
|
'access_and_security',
|
||||||
'networks',
|
'networks',
|
||||||
'routers')
|
'routers',
|
||||||
|
'network_topology')
|
||||||
|
|
||||||
|
|
||||||
class ObjectStorePanels(horizon.PanelGroup):
|
class ObjectStorePanels(horizon.PanelGroup):
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2013 NTT MCL, 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.
|
|
@ -0,0 +1,34 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2013 NTT MCL, 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 _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project import dashboard
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkTopology(horizon.Panel):
|
||||||
|
name = _("Network Topology")
|
||||||
|
slug = 'network_topology'
|
||||||
|
permissions = ('openstack.services.network', )
|
||||||
|
|
||||||
|
|
||||||
|
dashboard.Project.register(NetworkTopology)
|
|
@ -0,0 +1,35 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Network Topology" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("Network Topology") %}
|
||||||
|
{% 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="topologyCanvas">
|
||||||
|
<div class="networks"></div>
|
||||||
|
</div>
|
||||||
|
<span data-networktopology="{% url horizon:project:network_topology:json %}" id="networktopology"></span>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,31 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2013 NTT MCL, 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.conf.urls.defaults import url, patterns
|
||||||
|
|
||||||
|
from .views import (NetworkTopology, JSONView)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'openstack_dashboard.dashboards.project.network_topology.views',
|
||||||
|
url(r'^$', NetworkTopology.as_view(), name='index'),
|
||||||
|
url(r'^json$', JSONView.as_view(), name='json'),
|
||||||
|
)
|
|
@ -0,0 +1,97 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2013 NTT MCL 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.core.urlresolvers import reverse
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils import simplejson
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
from django.views.generic import View
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkTopology(TemplateView):
|
||||||
|
template_name = 'project/network_topology/index.html'
|
||||||
|
|
||||||
|
|
||||||
|
class JSONView(View):
|
||||||
|
def add_resource_url(self, view, resources):
|
||||||
|
for resource in resources:
|
||||||
|
resource['url'] = reverse(view, None, [str(resource['id'])])
|
||||||
|
|
||||||
|
def _select_port_by_network_id(self, ports, network_id):
|
||||||
|
for port in ports:
|
||||||
|
if port['network_id'] == network_id:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
data = {}
|
||||||
|
# Get nova data
|
||||||
|
novaclient = api.nova.novaclient(request)
|
||||||
|
servers = novaclient.servers.list()
|
||||||
|
data['servers'] = [{'name': server.name,
|
||||||
|
'status': server.status,
|
||||||
|
'id': server.id} for server in servers]
|
||||||
|
self.add_resource_url('horizon:project:instances:detail',
|
||||||
|
data['servers'])
|
||||||
|
# Get quantum data
|
||||||
|
quantumclient = api.quantum.quantumclient(request)
|
||||||
|
networks = quantumclient.list_networks()
|
||||||
|
subnets = quantumclient.list_subnets()
|
||||||
|
ports = quantumclient.list_ports()
|
||||||
|
routers = quantumclient.list_routers()
|
||||||
|
data['networks'] = sorted(networks.get('networks', []),
|
||||||
|
key=lambda x: x.get('router:external'),
|
||||||
|
reverse=True)
|
||||||
|
self.add_resource_url('horizon:project:networks:detail',
|
||||||
|
data['networks'])
|
||||||
|
data['subnets'] = subnets.get('subnets', [])
|
||||||
|
data['ports'] = ports.get('ports', [])
|
||||||
|
self.add_resource_url('horizon:project:networks:ports:detail',
|
||||||
|
data['ports'])
|
||||||
|
data['routers'] = routers.get('routers', [])
|
||||||
|
# user can't see port on shared network. so we are
|
||||||
|
# adding fake port based on router information
|
||||||
|
for router in data['routers']:
|
||||||
|
external_gateway_info = router.get('external_gateway_info')
|
||||||
|
if not external_gateway_info:
|
||||||
|
continue
|
||||||
|
external_network = external_gateway_info.get(
|
||||||
|
'network_id')
|
||||||
|
if not external_network:
|
||||||
|
continue
|
||||||
|
if self._select_port_by_network_id(data['ports'],
|
||||||
|
external_network):
|
||||||
|
continue
|
||||||
|
fake_port = {'id': 'fake%s' % external_network,
|
||||||
|
'network_id': external_network,
|
||||||
|
'url': reverse(
|
||||||
|
'horizon:project:networks:detail',
|
||||||
|
None,
|
||||||
|
[external_network]),
|
||||||
|
'device_id': router['id'],
|
||||||
|
'fixed_ips': []}
|
||||||
|
data['ports'].append(fake_port)
|
||||||
|
|
||||||
|
self.add_resource_url('horizon:project:routers:detail',
|
||||||
|
data['routers'])
|
||||||
|
json_string = simplejson.dumps(data, ensure_ascii=False)
|
||||||
|
return HttpResponse(json_string, mimetype='text/json')
|
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -1744,6 +1744,311 @@ 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();
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
padding: 25px;
|
||||||
|
padding-left: 50px;
|
||||||
|
background: #efefef;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
position: relative;
|
||||||
|
font-weight: normal;
|
||||||
|
top:60%;
|
||||||
|
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 {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
color: #000;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 90%;
|
||||||
|
text-shadow: 0px 0px 0px #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.router, .server, .device {
|
||||||
|
.box-sizing();
|
||||||
|
cursor: pointer;
|
||||||
|
width: 90px;
|
||||||
|
border: 3px solid #666;
|
||||||
|
position: absolute;
|
||||||
|
top:30px;
|
||||||
|
left:90px;
|
||||||
|
padding: 0 3px;
|
||||||
|
background: #666;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
&:before,&:after {
|
||||||
|
content: "";
|
||||||
|
width: 90px;
|
||||||
|
height: 34px;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
border: 3px solid #666;
|
||||||
|
.box-sizing();
|
||||||
|
background: #fff;
|
||||||
|
border-radius:50%;
|
||||||
|
top:-19px;
|
||||||
|
left:-3px;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content:"";
|
||||||
|
color: #fff;
|
||||||
|
background:#666;
|
||||||
|
border-radius:50%;
|
||||||
|
top:auto;
|
||||||
|
bottom:-19px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
span.devicename {
|
||||||
|
position: absolute;
|
||||||
|
color: #fff;
|
||||||
|
bottom: -10px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
left:-4px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
z-index:300;
|
||||||
|
i {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height:14px;
|
||||||
|
background: #fff url(/static/dashboard/img/router.png) no-repeat center center;
|
||||||
|
background-size: 12px 12px;
|
||||||
|
margin-right: 3px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.name {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: block;
|
||||||
|
font-size : 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index:10;
|
||||||
|
text-align: center;
|
||||||
|
top:-10px;
|
||||||
|
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();
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0px -1px #fff;
|
||||||
|
position: relative;
|
||||||
|
top:-1em;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
background-color: #444;
|
||||||
|
border-color: #444;
|
||||||
|
&:before {
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
background-color: #444;
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.device {
|
||||||
|
border:none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.server {
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #fff;
|
||||||
|
span.devicename {
|
||||||
|
bottom: 0px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
left:-4px;
|
||||||
|
i {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height:14px;
|
||||||
|
background: url(/static/dashboard/img/server.png) no-repeat center center;
|
||||||
|
background-size: 12px 12px;
|
||||||
|
margin-right: 3px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.name {
|
||||||
|
top:5px;
|
||||||
|
padding: 0 3px;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
border:none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
line-height: 1.2;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0;
|
||||||
|
background: #666;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
height: 1.5em;
|
||||||
|
bottom:0px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.launchButtons {
|
||||||
|
text-align: right;
|
||||||
|
margin: 10px 0px 15px 10px;
|
||||||
|
a.btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue