Add sexy boxes to the deployment status page
For now, they are statically generated and don't update with the progress bar -- that will be done as the next step. Change-Id: I15701bf8523240f9429304f315386a8d3bd2cd7a
This commit is contained in:
@@ -17,6 +17,20 @@ from tuskar_ui.infrastructure.overview import views
|
|||||||
from tuskar_boxes.overview import forms
|
from tuskar_boxes.overview import forms
|
||||||
|
|
||||||
|
|
||||||
|
NODE_STATE_ICON = {
|
||||||
|
api.node.DISCOVERING_STATE: 'fa-search',
|
||||||
|
api.node.DISCOVERED_STATE: 'fa-search-plus',
|
||||||
|
api.node.DISCOVERY_FAILED_STATE: 'fa-search-minus',
|
||||||
|
api.node.MAINTENANCE_STATE: 'fa-exclamation-triangle',
|
||||||
|
api.node.FREE_STATE: 'fa-minus',
|
||||||
|
api.node.PROVISIONING_STATE: 'fa-spinner fa-spin',
|
||||||
|
api.node.PROVISIONED_STATE: 'fa-check',
|
||||||
|
api.node.DELETING_STATE: 'fa-spinner fa-spin',
|
||||||
|
api.node.PROVISIONING_FAILED_STATE: 'fa-exclamation-circle',
|
||||||
|
None: 'fa-question',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def flavor_nodes(request, flavor):
|
def flavor_nodes(request, flavor):
|
||||||
"""Lists all nodes that match the given flavor exactly."""
|
"""Lists all nodes that match the given flavor exactly."""
|
||||||
for node in api.node.Node.list(request, maintenance=False):
|
for node in api.node.Node.list(request, maintenance=False):
|
||||||
@@ -29,6 +43,14 @@ def flavor_nodes(request, flavor):
|
|||||||
yield node
|
yield node
|
||||||
|
|
||||||
|
|
||||||
|
def node_role(request, node):
|
||||||
|
try:
|
||||||
|
resource = api.heat.Resource.get_by_node(request, node)
|
||||||
|
except LookupError:
|
||||||
|
return None
|
||||||
|
return resource.role
|
||||||
|
|
||||||
|
|
||||||
class IndexView(views.IndexView):
|
class IndexView(views.IndexView):
|
||||||
template_name = "tuskar_boxes/overview/index.html"
|
template_name = "tuskar_boxes/overview/index.html"
|
||||||
form_class = forms.EditPlan
|
form_class = forms.EditPlan
|
||||||
@@ -40,27 +62,35 @@ class IndexView(views.IndexView):
|
|||||||
for role in context['roles']:
|
for role in context['roles']:
|
||||||
flavor = role['role'].flavor(context['plan'])
|
flavor = role['role'].flavor(context['plan'])
|
||||||
role['flavor_name'] = flavor.name if flavor else ''
|
role['flavor_name'] = flavor.name if flavor else ''
|
||||||
context['flavors'] = []
|
|
||||||
for flavor in flavors:
|
|
||||||
nodes = [{
|
|
||||||
'role': '',
|
|
||||||
} for node in flavor_nodes(self.request, flavor)]
|
|
||||||
roles = [role for role in context['roles']
|
|
||||||
if role['flavor_name'] == flavor.name]
|
|
||||||
flavor = {
|
|
||||||
'name': flavor.name,
|
|
||||||
'vcpus': flavor.vcpus,
|
|
||||||
'ram': flavor.ram,
|
|
||||||
'disk': flavor.disk,
|
|
||||||
'cpu_arch': flavor.cpu_arch,
|
|
||||||
'nodes': nodes,
|
|
||||||
'roles': roles,
|
|
||||||
}
|
|
||||||
if nodes or roles: # Don't list empty flavors
|
|
||||||
context['flavors'].append(flavor)
|
|
||||||
context['free_roles'] = [role for role in context['roles']
|
|
||||||
if not role['flavor_name']]
|
|
||||||
if not context['stack']:
|
if not context['stack']:
|
||||||
|
context['flavors'] = []
|
||||||
|
for flavor in flavors:
|
||||||
|
nodes = [{
|
||||||
|
'role': '',
|
||||||
|
} for node in flavor_nodes(self.request, flavor)]
|
||||||
|
roles = [role for role in context['roles']
|
||||||
|
if role['flavor_name'] == flavor.name]
|
||||||
|
flavor = {
|
||||||
|
'name': flavor.name,
|
||||||
|
'vcpus': flavor.vcpus,
|
||||||
|
'ram': flavor.ram,
|
||||||
|
'disk': flavor.disk,
|
||||||
|
'cpu_arch': flavor.cpu_arch,
|
||||||
|
'nodes': nodes,
|
||||||
|
'roles': roles,
|
||||||
|
}
|
||||||
|
if nodes or roles: # Don't list empty flavors
|
||||||
|
context['flavors'].append(flavor)
|
||||||
|
context['free_roles'] = [role for role in context['roles']
|
||||||
|
if not role['flavor_name']]
|
||||||
for role in context['roles']:
|
for role in context['roles']:
|
||||||
role['flavor_field'] = context['form'][role['id'] + '-flavor']
|
role['flavor_field'] = context['form'][role['id'] + '-flavor']
|
||||||
|
else:
|
||||||
|
context['nodes'] = [{
|
||||||
|
'uuid': node.uuid,
|
||||||
|
'role': node_role(self.request, node),
|
||||||
|
'state': node.state,
|
||||||
|
'state_icon': NODE_STATE_ICON.get(node.state,
|
||||||
|
NODE_STATE_ICON[None]),
|
||||||
|
} for node in api.node.Node.list(self.request, maintenance=False)]
|
||||||
return context
|
return context
|
||||||
|
@@ -29,12 +29,15 @@
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
cursor: move;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: #fce94f;
|
background-color: #fce94f;
|
||||||
border-color: #edd400;
|
border-color: #edd400;
|
||||||
color: #c4a000;
|
color: #c4a000;
|
||||||
margin: 0 0 4px 0;
|
margin: 0 0 4px 0;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
.deploy-role-status .boxes-role {
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
.boxes-available-roles .boxes-role {
|
.boxes-available-roles .boxes-role {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@@ -42,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-8">
|
<div class="col-xs-8">
|
||||||
{% if stack %}
|
{% if stack %}
|
||||||
{% include "infrastructure/overview/role_nodes_status.html" %}
|
{% include "tuskar_boxes/overview/role_nodes_status.html" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include "tuskar_boxes/overview/role_nodes_edit.html" %}
|
{% include "tuskar_boxes/overview/role_nodes_edit.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load url from future %}
|
||||||
|
{% load horizon %}
|
||||||
|
|
||||||
|
<h4>{% trans "Deployment Roles" %}</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="deploy-role-status col-xs-5">
|
||||||
|
{% for role in roles %}
|
||||||
|
<div class="boxes-role boxes-role-{{ role.name|slugify }} clearfix">
|
||||||
|
<div class="col-xs-2 deploy-role-count">
|
||||||
|
{% if role.finished %}
|
||||||
|
{{ role.deployed_node_count }}
|
||||||
|
{% else %}
|
||||||
|
{{ role.deployed_node_count }}<small class="text-muted">/{{ role.planned_node_count }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-10 deploy-role-name">
|
||||||
|
<strong class="deployment-roles-label">{{ role.name|capfirst }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-7 boxes-nodes">
|
||||||
|
{% for node in nodes %}{% spaceless %}
|
||||||
|
<div class="boxes-node boxes-role-{{ node.role.name|slugify }} status-{{ node.status|slugify }}" title="{{ node.uuid }}">
|
||||||
|
<i class="fa fa-lg {{ node.state_icon }}"></i>
|
||||||
|
</div>
|
||||||
|
{% endspaceless %}{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/html" id="roles-template">{% spaceless %}{% jstemplate %}
|
||||||
|
[[#roles]]
|
||||||
|
<div class="boxes-role boxes-role boxes-role[[ slug ]] clearfix">
|
||||||
|
<div class="col-xs-2 deploy-role-count">
|
||||||
|
[[#finished]]
|
||||||
|
[[ deployed_node_count ]]
|
||||||
|
[[/finished]]
|
||||||
|
[[^finished]]
|
||||||
|
[[ deployed_node_count ]]<small class="text-muted">/[[ planned_node_count ]]</small>
|
||||||
|
[[/finished]]
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-10 deploy-role-name">
|
||||||
|
<strong class="deployment-roles-label" >[[ name ]]</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
[[/roles]]
|
||||||
|
{% endjstemplate %}{% endspaceless %}</script>
|
Reference in New Issue
Block a user