From 5fdf903e249e6857c897a00b853d9d9f26d2df30 Mon Sep 17 00:00:00 2001 From: Radomir Dopieralski <openstack@sheep.art.pl> Date: Fri, 14 Nov 2014 16:38:44 +0100 Subject: [PATCH] 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 --- tuskar_boxes/overview/views.py | 70 +++++++++++++------ .../tuskar_boxes/scss/tuskar_boxes.scss | 5 +- .../tuskar_boxes/overview/index.html | 2 +- .../overview/role_nodes_status.html | 48 +++++++++++++ 4 files changed, 103 insertions(+), 22 deletions(-) create mode 100644 tuskar_boxes/templates/tuskar_boxes/overview/role_nodes_status.html diff --git a/tuskar_boxes/overview/views.py b/tuskar_boxes/overview/views.py index baca1f1..cae0a40 100644 --- a/tuskar_boxes/overview/views.py +++ b/tuskar_boxes/overview/views.py @@ -17,6 +17,20 @@ from tuskar_ui.infrastructure.overview import views 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): """Lists all nodes that match the given flavor exactly.""" for node in api.node.Node.list(request, maintenance=False): @@ -29,6 +43,14 @@ def flavor_nodes(request, flavor): 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): template_name = "tuskar_boxes/overview/index.html" form_class = forms.EditPlan @@ -40,27 +62,35 @@ class IndexView(views.IndexView): for role in context['roles']: flavor = role['role'].flavor(context['plan']) 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']: + 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']: 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 diff --git a/tuskar_boxes/static/tuskar_boxes/scss/tuskar_boxes.scss b/tuskar_boxes/static/tuskar_boxes/scss/tuskar_boxes.scss index 428cb19..820203c 100644 --- a/tuskar_boxes/static/tuskar_boxes/scss/tuskar_boxes.scss +++ b/tuskar_boxes/static/tuskar_boxes/scss/tuskar_boxes.scss @@ -29,12 +29,15 @@ opacity: 0.75; padding: 6px; border: 1px solid; - cursor: move; border-radius: 2px; background-color: #fce94f; border-color: #edd400; color: #c4a000; margin: 0 0 4px 0; + cursor: move; +} +.deploy-role-status .boxes-role { + cursor: default; } .boxes-available-roles .boxes-role { display: inline-block; diff --git a/tuskar_boxes/templates/tuskar_boxes/overview/index.html b/tuskar_boxes/templates/tuskar_boxes/overview/index.html index 0ad1c29..2d3b1b0 100644 --- a/tuskar_boxes/templates/tuskar_boxes/overview/index.html +++ b/tuskar_boxes/templates/tuskar_boxes/overview/index.html @@ -42,7 +42,7 @@ </div> <div class="col-xs-8"> {% if stack %} - {% include "infrastructure/overview/role_nodes_status.html" %} + {% include "tuskar_boxes/overview/role_nodes_status.html" %} {% else %} {% include "tuskar_boxes/overview/role_nodes_edit.html" %} {% endif %} diff --git a/tuskar_boxes/templates/tuskar_boxes/overview/role_nodes_status.html b/tuskar_boxes/templates/tuskar_boxes/overview/role_nodes_status.html new file mode 100644 index 0000000..2ac2da7 --- /dev/null +++ b/tuskar_boxes/templates/tuskar_boxes/overview/role_nodes_status.html @@ -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>