Add Topology Tab for Services
This makes the following changes on Environment->Service page: * Adds Tab for Services List inside Environment * Adds a new Tab for Topology Graph Added new API function to render d3 data for topology graph. Currently heat images are used. Change-Id: Icae70a3d2668a9b18d1c716d7a60ebdcf2faf5ea Implements: blueprint environment-topology-view
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
import logging
|
||||
import bunch
|
||||
import json
|
||||
from django.conf import settings
|
||||
from horizon.exceptions import ServiceCatalogException
|
||||
from openstack_dashboard.api.base import url_for
|
||||
@@ -23,6 +24,8 @@ from muranoclient.common.exceptions import HTTPForbidden, HTTPNotFound
|
||||
from consts import STATUS_ID_READY, STATUS_ID_NEW
|
||||
from .network import get_network_params
|
||||
|
||||
from muranodashboard.environments import format
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -330,3 +333,68 @@ def get_deployment_descr(request, environment_id, deployment_id):
|
||||
request, service['type'])
|
||||
return descr
|
||||
return None
|
||||
|
||||
|
||||
def load_environment_data(request, environment_id):
|
||||
environment = environment_get(request, environment_id)
|
||||
return render_d3_data(environment, environment.services)
|
||||
|
||||
|
||||
def render_d3_data(environment, services):
|
||||
ext_net_name = None
|
||||
d3_data = {"nodes": [], "environment": {}}
|
||||
if environment:
|
||||
environment_image = '/static/dashboard/img/stack-green.svg'
|
||||
in_progress, status_message = \
|
||||
format.get_environment_status_message(environment)
|
||||
environment_node = format.create_empty_node()
|
||||
environment_node['id'] = environment.id
|
||||
environment_node['name'] = environment.name
|
||||
environment_node['status'] = status_message
|
||||
environment_node['image'] = environment_image
|
||||
environment_node['in_progress'] = in_progress
|
||||
environment_node['info_box'] = \
|
||||
format.environment_info(environment, status_message)
|
||||
d3_data['environment'] = environment_node
|
||||
|
||||
if services:
|
||||
for service in services:
|
||||
service_image = '/static/dashboard/img/stack-green.svg'
|
||||
in_progress, status_message = \
|
||||
format.get_environment_status_message(service)
|
||||
required_by = None
|
||||
if service.get('assignFloatingIP', False):
|
||||
if ext_net_name:
|
||||
required_by = ext_net_name
|
||||
else:
|
||||
ext_net_name = 'External_Network'
|
||||
d3_data['nodes'].append(format.create_ext_network_node(
|
||||
ext_net_name))
|
||||
required_by = ext_net_name
|
||||
service_node = format.create_empty_node()
|
||||
service_node['name'] = service['name']
|
||||
service_node['status'] = status_message
|
||||
service_node['image'] = service_image
|
||||
service_node['link_type'] = "unit"
|
||||
service_node['in_progress'] = in_progress
|
||||
service_node['info_box'] = format.appication_info(service,
|
||||
service_image,
|
||||
status_message)
|
||||
if required_by:
|
||||
service_node['required_by'] = [required_by]
|
||||
d3_data['nodes'].append(service_node)
|
||||
|
||||
for unit in service['units']:
|
||||
unit_image = '/static/dashboard/img/server-green.svg'
|
||||
node = format.create_empty_node()
|
||||
node['name'] = unit['name']
|
||||
node['id'] = unit['id']
|
||||
node['required_by'] = [service['name']]
|
||||
node['flavor'] = service['flavor']
|
||||
node['info_box'] = \
|
||||
format.unit_info(service, unit, unit_image)
|
||||
node['image'] = unit_image
|
||||
node['link_type'] = "unit"
|
||||
node['in_progress'] = in_progress
|
||||
d3_data['nodes'].append(node)
|
||||
return json.dumps(d3_data)
|
||||
|
86
muranodashboard/environments/format.py
Normal file
86
muranodashboard/environments/format.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# 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.template.loader import render_to_string
|
||||
|
||||
|
||||
def get_environment_status_message(entity):
|
||||
try:
|
||||
status = entity['status']
|
||||
except TypeError:
|
||||
status = entity.status
|
||||
|
||||
in_progress = True
|
||||
if status in ('pending', 'ready'):
|
||||
in_progress = False
|
||||
if status == 'pending':
|
||||
status_message = 'Waiting for deployment'
|
||||
elif status == 'ready':
|
||||
status_message = 'Deployed'
|
||||
elif status == 'deploying':
|
||||
status_message = 'Deployment is in progress'
|
||||
return in_progress, status_message
|
||||
|
||||
|
||||
def appication_info(application, app_image, status):
|
||||
context = {}
|
||||
context['name'] = application['name']
|
||||
context['type'] = application['type']
|
||||
context['status'] = status
|
||||
context['app_image'] = app_image
|
||||
return render_to_string('services/_application_info.html',
|
||||
context)
|
||||
|
||||
|
||||
def unit_info(service, unit, unit_image):
|
||||
context = {}
|
||||
context['name'] = unit['name']
|
||||
context['os'] = service['osImage']['type']
|
||||
context['image'] = service['osImage']['name']
|
||||
context['flavor'] = service['flavor']
|
||||
context['unit_image'] = unit_image
|
||||
return render_to_string('services/_unit_info.html',
|
||||
context)
|
||||
|
||||
|
||||
def environment_info(environment, status):
|
||||
context = {}
|
||||
context['name'] = environment.name
|
||||
context['status'] = status
|
||||
return render_to_string('services/_environment_info.html',
|
||||
context)
|
||||
|
||||
|
||||
def create_empty_node():
|
||||
node = {
|
||||
'name': '',
|
||||
'status': 'ready',
|
||||
'image': '',
|
||||
'image_size': 60,
|
||||
'required_by': [],
|
||||
'image_x': -30,
|
||||
'image_y': -30,
|
||||
'text_x': 40,
|
||||
'text_y': ".35em",
|
||||
'link_type': "relation",
|
||||
'in_progress': False,
|
||||
'info_box': ''
|
||||
}
|
||||
return node
|
||||
|
||||
|
||||
def create_ext_network_node(name):
|
||||
node = create_empty_node()
|
||||
node['name'] = name
|
||||
node['image'] = '/static/dashboard/img/lb-green.svg'
|
||||
node['link_type'] = "relation"
|
||||
return node
|
@@ -14,17 +14,21 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.datastructures import SortedDict
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from muranodashboard.environments.consts import LOG_LEVEL_TO_COLOR
|
||||
from muranodashboard.environments.consts import LOG_LEVEL_TO_TEXT
|
||||
|
||||
from openstack_dashboard.api import nova as nova_api
|
||||
from openstack_dashboard.api import heat as heat_api
|
||||
|
||||
from muranoclient.common import exceptions as exc
|
||||
from muranodashboard.environments import api
|
||||
from muranodashboard.environments.consts import LOG_LEVEL_TO_COLOR
|
||||
from muranodashboard.environments.consts import LOG_LEVEL_TO_TEXT
|
||||
from muranodashboard.environments.tables import STATUS_DISPLAY_CHOICES
|
||||
from muranodashboard.environments.tables import EnvConfigTable
|
||||
from muranodashboard.environments.tables import EnvConfigTable, ServicesTable
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@@ -166,6 +170,54 @@ class EnvConfigTab(tabs.TableTab):
|
||||
return deployment.get('services')
|
||||
|
||||
|
||||
class EnvironmentTopologyTab(tabs.Tab):
|
||||
name = _("Topology")
|
||||
slug = "topology"
|
||||
template_name = "services/_detail_topology.html"
|
||||
preload = False
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = {}
|
||||
environment_id = self.tab_group.kwargs['environment_id']
|
||||
context['environment_id'] = environment_id
|
||||
d3_data = api.load_environment_data(self.request, environment_id)
|
||||
context['d3_data'] = d3_data
|
||||
return context
|
||||
|
||||
|
||||
class EnvironmentServicesTab(tabs.TableTab):
|
||||
name = _("Services")
|
||||
slug = "serviceslist"
|
||||
table_classes = (ServicesTable,)
|
||||
template_name = "services/_service_list.html"
|
||||
preload = False
|
||||
|
||||
def get_services_data(self):
|
||||
services = []
|
||||
self.environment_id = self.tab_group.kwargs['environment_id']
|
||||
ns_url = "horizon:murano:environments:index"
|
||||
try:
|
||||
services = api.services_list(self.request, self.environment_id)
|
||||
except exc.HTTPForbidden:
|
||||
msg = _('Unable to retrieve list of services. This environment '
|
||||
'is deploying or already deployed by other user.')
|
||||
exceptions.handle(self.request, msg, redirect=reverse(ns_url))
|
||||
|
||||
except (exc.HTTPInternalServerError, exc.HTTPNotFound):
|
||||
msg = _("Environment with id %s doesn't exist anymore"
|
||||
% self.environment_id)
|
||||
exceptions.handle(self.request, msg, redirect=reverse(ns_url))
|
||||
except exc.HTTPUnauthorized:
|
||||
exceptions.handle(self.request)
|
||||
|
||||
return services
|
||||
|
||||
|
||||
class EnvironmentDetailsTabs(tabs.TabGroup):
|
||||
slug = "environemnt_details"
|
||||
tabs = (EnvironmentServicesTab, EnvironmentTopologyTab)
|
||||
|
||||
|
||||
class ServicesTabs(tabs.TabGroup):
|
||||
slug = "services_details"
|
||||
tabs = (OverviewTab, ServiceLogsTab)
|
||||
|
@@ -15,7 +15,7 @@
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from views import IndexView, DeploymentDetailsView
|
||||
from views import Services
|
||||
from views import JSONView, EnvironmentDetails
|
||||
from views import CreateEnvironmentView
|
||||
from views import DetailServiceView
|
||||
from views import DeploymentsView
|
||||
@@ -50,9 +50,12 @@ urlpatterns = patterns(
|
||||
EditEnvironmentView.as_view(),
|
||||
name='update_environment'),
|
||||
|
||||
url(ENVIRONMENT_ID + r'/services$', Services.as_view(),
|
||||
url(ENVIRONMENT_ID + r'/services$', EnvironmentDetails.as_view(),
|
||||
name='services'),
|
||||
|
||||
url(ENVIRONMENT_ID + r'/services/get_d3_data$',
|
||||
JSONView.as_view(), name='d3_data'),
|
||||
|
||||
url(ENVIRONMENT_ID + r'/(?P<service_id>[^/]+)/$',
|
||||
DetailServiceView.as_view(),
|
||||
name='service_details'),
|
||||
|
@@ -21,7 +21,9 @@ from functools import update_wrapper
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.formtools.wizard.views import SessionWizardView
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import HttpResponseRedirect # noqa
|
||||
from django.http import HttpResponse # noqa
|
||||
from django.views import generic
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from horizon import tables
|
||||
@@ -33,7 +35,7 @@ from tables import ServicesTable
|
||||
from tables import DeploymentsTable
|
||||
from tables import EnvConfigTable
|
||||
from workflows import CreateEnvironment, UpdateEnvironment
|
||||
from tabs import ServicesTabs, DeploymentTabs
|
||||
from tabs import ServicesTabs, DeploymentTabs, EnvironmentDetailsTabs
|
||||
from . import api
|
||||
from muranoclient.common.exceptions import HTTPUnauthorized, \
|
||||
CommunicationError, HTTPInternalServerError, HTTPForbidden, HTTPNotFound
|
||||
@@ -193,46 +195,24 @@ class IndexView(tables.DataTableView):
|
||||
return environments
|
||||
|
||||
|
||||
class Services(tables.DataTableView):
|
||||
table_class = ServicesTable
|
||||
class EnvironmentDetails(tabs.TabbedTableView):
|
||||
tab_group_class = EnvironmentDetailsTabs
|
||||
template_name = 'services/index.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(Services, self).get_context_data(**kwargs)
|
||||
context = super(EnvironmentDetails, self).get_context_data(**kwargs)
|
||||
|
||||
try:
|
||||
self.environment_id = self.kwargs['environment_id']
|
||||
env = api.environment_get(self.request, self.environment_id)
|
||||
context['environment_name'] = env.name
|
||||
|
||||
except:
|
||||
msg = _("Sorry, this environment does't exist anymore")
|
||||
msg = _("Sorry, this environment doesn't exist anymore")
|
||||
redirect = reverse("horizon:murano:environments:index")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
services = []
|
||||
self.environment_id = self.kwargs['environment_id']
|
||||
ns_url = "horizon:murano:environments:index"
|
||||
try:
|
||||
services = api.services_list(self.request, self.environment_id)
|
||||
except HTTPForbidden:
|
||||
msg = _('Unable to retrieve list of services. This environment '
|
||||
'is deploying or already deployed by other user.')
|
||||
exceptions.handle(self.request, msg, redirect=reverse(ns_url))
|
||||
|
||||
except HTTPInternalServerError:
|
||||
msg = _("Environment with id %s doesn't exist anymore"
|
||||
% self.environment_id)
|
||||
exceptions.handle(self.request, msg, redirect=reverse(ns_url))
|
||||
except HTTPUnauthorized:
|
||||
exceptions.handle(self.request)
|
||||
except HTTPNotFound:
|
||||
msg = _("Environment with id %s doesn't exist anymore"
|
||||
% self.environment_id)
|
||||
exceptions.handle(self.request, msg, redirect=reverse(ns_url))
|
||||
return services
|
||||
|
||||
|
||||
class DetailServiceView(tabs.TabView):
|
||||
tab_group_class = ServicesTabs
|
||||
@@ -396,3 +376,12 @@ class DeploymentDetailsView(tabs.TabbedTableView):
|
||||
|
||||
return self.tab_group_class(request, deployment=deployment, logs=logs,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class JSONView(generic.View):
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
self.environment_id = kwargs['environment_id']
|
||||
data = api.load_environment_data(request, self.environment_id)
|
||||
return HttpResponse(data,
|
||||
content_type="application/json")
|
||||
|
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Adapted for Murano js topology generator.
|
||||
* Based on:
|
||||
* HeatTop JS Framework
|
||||
* Dependencies: jQuery 1.7.1 or later, d3 v3 or later
|
||||
* Date: June 2013
|
||||
* Description: JS Framework that subclasses the D3 Force Directed Graph library to create
|
||||
* Heat-specific objects and relationships with the purpose of displaying
|
||||
* Stacks, Resources, and related Properties in a Resource Topology Graph.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
var murano_container = "#murano_application_topology";
|
||||
|
||||
var diagonal = d3.svg.diagonal()
|
||||
.projection(function(d) { return [d.y, d.x]; });
|
||||
|
||||
function update(){
|
||||
node = node.data(nodes, function(d) { return d.name; });
|
||||
link = link.data(links);
|
||||
|
||||
var nodeEnter = node.enter().append("g")
|
||||
.attr("class", "node")
|
||||
.attr("node_name", function(d) { return d.name; })
|
||||
.attr("node_id", function(d) { return d.instance; })
|
||||
.call(force.drag);
|
||||
|
||||
nodeEnter.append("image")
|
||||
.attr("xlink:href", function(d) { return d.image; })
|
||||
.attr("id", function(d){ return "image_"+ d.name; })
|
||||
.attr("x", function(d) { return d.image_x; })
|
||||
.attr("y", function(d) { return d.image_y; })
|
||||
.attr("width", function(d) { return d.image_size; })
|
||||
.attr("height", function(d) { return d.image_size; });
|
||||
node.exit().remove();
|
||||
|
||||
link.enter().insert("path", "g.node")
|
||||
.attr("class", function(d) { return "link " + d.link_type; });
|
||||
|
||||
link.exit().remove();
|
||||
//Setup click action for all nodes
|
||||
node.on("mouseover", function(d) {
|
||||
$("#info_box").html(d.info_box);
|
||||
current_info = d.name;
|
||||
});
|
||||
node.on("mouseout", function(d) {
|
||||
$("#info_box").html('');
|
||||
});
|
||||
|
||||
force.start();
|
||||
}
|
||||
|
||||
function tick() {
|
||||
link.attr("d", linkArc);
|
||||
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
||||
}
|
||||
|
||||
function linkArc(d) {
|
||||
var dx = d.target.x - d.source.x,
|
||||
dy = d.target.y - d.source.y,
|
||||
dr = Math.sqrt(dx * dx + dy * dy);
|
||||
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
|
||||
}
|
||||
|
||||
|
||||
function set_in_progress(stack, nodes) {
|
||||
if (stack.in_progress === true) { in_progress = true; }
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var d = nodes[i];
|
||||
if (d.in_progress === true){ in_progress = true; return false; }
|
||||
}
|
||||
}
|
||||
|
||||
function findNode(name) {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].name === name){ return nodes[i]; }
|
||||
}
|
||||
}
|
||||
|
||||
function findNodeIndex(name) {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].name === name){ return i; }
|
||||
}
|
||||
}
|
||||
|
||||
function addNode (node) {
|
||||
nodes.push(node);
|
||||
needs_update = true;
|
||||
}
|
||||
|
||||
function removeNode (name) {
|
||||
var i = 0;
|
||||
var n = findNode(name);
|
||||
while (i < links.length) {
|
||||
if (links[i].source === n || links[i].target === n) {
|
||||
links.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
nodes.splice(findNodeIndex(name),1);
|
||||
needs_update = true;
|
||||
}
|
||||
|
||||
function remove_nodes(old_nodes, new_nodes){
|
||||
//Check for removed nodes
|
||||
for (var i=0;i<old_nodes.length;i++) {
|
||||
var remove_node = true;
|
||||
for (var j=0;j<new_nodes.length;j++) {
|
||||
if (old_nodes[i].name === new_nodes[j].name){
|
||||
remove_node = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remove_node === true){
|
||||
removeNode(old_nodes[i].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function build_links(){
|
||||
for (var i=0;i<nodes.length;i++){
|
||||
build_node_links(nodes[i]);
|
||||
build_reverse_links(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function build_node_links(node){
|
||||
for (var j=0;j<node.required_by.length;j++){
|
||||
var push_link = true;
|
||||
var target_idx = '';
|
||||
var source_idx = findNodeIndex(node.name);
|
||||
//make sure target node exists
|
||||
try {
|
||||
target_idx = findNodeIndex(node.required_by[j]);
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
push_link =false;
|
||||
}
|
||||
//check for duplicates
|
||||
for (var lidx=0;lidx<links.length;lidx++) {
|
||||
if (links[lidx].source === source_idx && links[lidx].target === target_idx) {
|
||||
push_link=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (push_link === true && (source_idx && target_idx)){
|
||||
links.push({
|
||||
'source':source_idx,
|
||||
'target':target_idx,
|
||||
'value':1,
|
||||
'link_type': node.link_type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function build_reverse_links(node){
|
||||
for (var i=0;i<nodes.length;i++){
|
||||
if(nodes[i].required_by){
|
||||
for (var j=0;j<nodes[i].required_by.length;j++){
|
||||
var dependency = nodes[i].required_by[j];
|
||||
//if new node is required by existing node, push new link
|
||||
if(node.name === dependency){
|
||||
links.push({
|
||||
'source':findNodeIndex(nodes[i].name),
|
||||
'target':findNodeIndex(node.name),
|
||||
'value':1,
|
||||
'link_type': node.link_type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ajax_poll(poll_time){
|
||||
setTimeout(function() {
|
||||
$.getJSON(ajax_url, function(json) {
|
||||
//update d3 data element
|
||||
$("#d3_data").attr("data-d3_data", JSON.stringify(json));
|
||||
|
||||
//update stack
|
||||
$("#stack_box").html(json.environment.info_box);
|
||||
set_in_progress(json.environment, json.nodes);
|
||||
needs_update = false;
|
||||
|
||||
//Check Remove nodes
|
||||
remove_nodes(nodes, json.nodes);
|
||||
|
||||
//Check for updates and new nodes
|
||||
json.nodes.forEach(function(d){
|
||||
current_node = findNode(d.name);
|
||||
//Check if node already exists
|
||||
if (current_node) {
|
||||
//Node already exists, just update it
|
||||
current_node.status = d.status;
|
||||
|
||||
//Status has changed, image should be updated
|
||||
if (current_node.image !== d.image){
|
||||
current_node.image = d.image;
|
||||
var this_image = d3.select("#image_"+current_node.name);
|
||||
this_image
|
||||
.transition()
|
||||
.attr("x", function(d) { return d.image_x + 5; })
|
||||
.duration(100)
|
||||
.transition()
|
||||
.attr("x", function(d) { return d.image_x - 5; })
|
||||
.duration(100)
|
||||
.transition()
|
||||
.attr("x", function(d) { return d.image_x + 5; })
|
||||
.duration(100)
|
||||
.transition()
|
||||
.attr("x", function(d) { return d.image_x - 5; })
|
||||
.duration(100)
|
||||
.transition()
|
||||
.attr("xlink:href", d.image)
|
||||
.transition()
|
||||
.attr("x", function(d) { return d.image_x; })
|
||||
.duration(100)
|
||||
.ease("bounce");
|
||||
}
|
||||
|
||||
//Status has changed, update info_box
|
||||
current_node.info_box = d.info_box;
|
||||
|
||||
} else {
|
||||
addNode(d);
|
||||
build_links();
|
||||
}
|
||||
});
|
||||
|
||||
//if any updates needed, do update now
|
||||
if (needs_update === true){
|
||||
update();
|
||||
}
|
||||
});
|
||||
//if no nodes still in progress, slow AJAX polling
|
||||
if (in_progress === false) { poll_time = 30000; }
|
||||
else { poll_time = 3000; }
|
||||
ajax_poll(poll_time);
|
||||
}, poll_time);
|
||||
}
|
||||
|
||||
if ($(murano_container).length){
|
||||
var width = $(murano_container).width(),
|
||||
height = 500,
|
||||
environment_id = $("#environment_id").data("environment_id"),
|
||||
ajax_url = '/murano/'+environment_id+'/services/get_d3_data',
|
||||
graph = $("#d3_data").data("d3_data"),
|
||||
force = d3.layout.force()
|
||||
.nodes(graph.nodes)
|
||||
.links([])
|
||||
.gravity(0.3)
|
||||
.charge(-2000)
|
||||
.linkDistance(100)
|
||||
.size([width, height])
|
||||
.on("tick", tick),
|
||||
svg = d3.select(murano_container).append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height),
|
||||
node = svg.selectAll(".node"),
|
||||
link = svg.selectAll(".link"),
|
||||
needs_update = false,
|
||||
nodes = force.nodes(),
|
||||
links = force.links();
|
||||
|
||||
build_links();
|
||||
update();
|
||||
|
||||
//Load initial Stack box
|
||||
$("#stack_box").html(graph.environment.info_box);
|
||||
//On Page load, set Action In Progress
|
||||
var in_progress = false;
|
||||
set_in_progress(graph.environment, node);
|
||||
|
||||
//If status is In Progress, start AJAX polling
|
||||
var poll_time = 0;
|
||||
if (in_progress === true) { poll_time = 3000; }
|
||||
else { poll_time = 30000; }
|
||||
ajax_poll(poll_time);
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
<img src="{{ app_image }}" width="35px" height="35px" />
|
||||
<h3>Name: {{ name }}</h3>
|
||||
<p>Type: {{ type }}</p>
|
||||
<p>Status: {{ status }}</p>
|
10
muranodashboard/templates/services/_detail_topology.html
Normal file
10
muranodashboard/templates/services/_detail_topology.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% load i18n sizeformat %}
|
||||
{% load static %}
|
||||
<div id="resource_container">
|
||||
<script type="text/javascript" src="{% static 'muranodashboard/js/horizon.muranotopology.js' %}"></script>
|
||||
<div id="info_box"></div>
|
||||
<div id="stack_box"></div>
|
||||
<div id="murano_application_topology"></div>
|
||||
<div id="environment_id" data-environment_id="{{ environment_id }}"></div>
|
||||
<div id="d3_data" data-d3_data="{{ d3_data }}"></div>
|
||||
</div>
|
@@ -0,0 +1,2 @@
|
||||
<h3>Environment: {{ name }}</h3>
|
||||
<p>Status: {{ status }}</p>
|
@@ -1,6 +1,8 @@
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
{% load static %}
|
||||
{% block page_header %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'muranodashboard/css/topology.css' %}" />
|
||||
<div class='page-header'>
|
||||
<h2>
|
||||
{% blocktrans %}Environment: {{ environment_name }}{% endblocktrans %}
|
||||
|
5
muranodashboard/templates/services/_service_list.html
Normal file
5
muranodashboard/templates/services/_service_list.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
5
muranodashboard/templates/services/_unit_info.html
Normal file
5
muranodashboard/templates/services/_unit_info.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<img src="{{ unit_image }}" width="35px" height="35px" />
|
||||
<h3>Name: {{ name }}</h3>
|
||||
<p>OS: {{ os }}</p>
|
||||
<p>Image: {{ image }}</p>
|
||||
<p>Flavor: {{ flavor }}</p>
|
@@ -5,7 +5,11 @@
|
||||
{% block page_header %}
|
||||
{% include "services/_page_header.html" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
Reference in New Issue
Block a user