Updated Nodes Overview page
Displays node counts, mocked Health Status table, defined template structure to resemble wireframes Change-Id: Ia042bf414d48eaf0254df92db2b1557b9ab63ea3
This commit is contained in:
@@ -66,10 +66,10 @@ class FreeNodesTable(NodesTable):
|
||||
row_actions = ()
|
||||
|
||||
|
||||
class ResourceNodesTable(NodesTable):
|
||||
class DeployedNodesTable(NodesTable):
|
||||
|
||||
class Meta:
|
||||
name = "resource_nodes"
|
||||
verbose_name = _("Resource Nodes")
|
||||
name = "deployed_nodes"
|
||||
verbose_name = _("Deployed Nodes")
|
||||
table_actions = ()
|
||||
row_actions = ()
|
||||
|
||||
@@ -31,41 +31,54 @@ class OverviewTab(tabs.Tab):
|
||||
|
||||
def get_context_data(self, request):
|
||||
try:
|
||||
free_nodes = len(api.Node.list(request, associated=False))
|
||||
free_nodes = api.Node.list(request, associated=False)
|
||||
deployed_nodes = api.Node.list(request, associated=True)
|
||||
except Exception:
|
||||
free_nodes = 0
|
||||
free_nodes = []
|
||||
deployed_nodes = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve free nodes.'))
|
||||
try:
|
||||
resource_nodes = len(api.Node.list(request, associated=True))
|
||||
except Exception:
|
||||
resource_nodes = 0
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve resource nodes.'))
|
||||
_('Unable to retrieve nodes.'))
|
||||
|
||||
free_nodes_down = [node for node in free_nodes
|
||||
if node.power_state != 'on']
|
||||
deployed_nodes_down = [node for node in deployed_nodes
|
||||
if node.power_state != 'on']
|
||||
|
||||
return {
|
||||
'nodes_total': free_nodes + resource_nodes,
|
||||
'nodes_resources': resource_nodes,
|
||||
'nodes_free': free_nodes,
|
||||
'deployed_nodes': deployed_nodes,
|
||||
'deployed_nodes_down': deployed_nodes_down,
|
||||
'free_nodes': free_nodes,
|
||||
'free_nodes_down': free_nodes_down,
|
||||
}
|
||||
|
||||
|
||||
class ResourceTab(tabs.TableTab):
|
||||
table_classes = (tables.ResourceNodesTable,)
|
||||
name = _("Resource")
|
||||
slug = "resource"
|
||||
class DeployedTab(tabs.TableTab):
|
||||
table_classes = (tables.DeployedNodesTable,)
|
||||
name = _("Deployed")
|
||||
slug = "deployed"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
|
||||
def get_resource_nodes_data(self):
|
||||
def get_items_count(self):
|
||||
try:
|
||||
resource_nodes = api.Node.list(self.request, associated=True)
|
||||
deployed_nodes_count = len(api.Node.list(self.request,
|
||||
associated=True))
|
||||
except Exception:
|
||||
resource_nodes = []
|
||||
deployed_nodes_count = 0
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve deployed nodes count.'))
|
||||
return deployed_nodes_count
|
||||
|
||||
def get_deployed_nodes_data(self):
|
||||
try:
|
||||
deployed_nodes = api.Node.list(self.request, associated=True)
|
||||
except Exception:
|
||||
deployed_nodes = []
|
||||
redirect = urlresolvers.reverse(
|
||||
'horizon:infrastructure:nodes:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve resource nodes.'),
|
||||
_('Unable to retrieve deployed nodes.'),
|
||||
redirect=redirect)
|
||||
return resource_nodes
|
||||
return deployed_nodes
|
||||
|
||||
|
||||
class FreeTab(tabs.TableTab):
|
||||
@@ -74,6 +87,16 @@ class FreeTab(tabs.TableTab):
|
||||
slug = "free"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
|
||||
def get_items_count(self):
|
||||
try:
|
||||
free_nodes_count = len(api.Node.list(self.request,
|
||||
associated=False))
|
||||
except Exception:
|
||||
free_nodes_count = "0"
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve free nodes count.'))
|
||||
return free_nodes_count
|
||||
|
||||
def get_free_nodes_data(self):
|
||||
try:
|
||||
free_nodes = api.Node.list(self.request, associated=False)
|
||||
@@ -89,5 +112,6 @@ class FreeTab(tabs.TableTab):
|
||||
|
||||
class NodeTabs(tabs.TabGroup):
|
||||
slug = "nodes"
|
||||
tabs = (OverviewTab, ResourceTab, FreeTab)
|
||||
tabs = (OverviewTab, DeployedTab, FreeTab)
|
||||
sticky = True
|
||||
template_name = "horizon/common/_items_count_tab_group.html"
|
||||
|
||||
@@ -2,23 +2,78 @@
|
||||
{% load url from future%}
|
||||
|
||||
|
||||
<div class="row-fluid"><div class="span12">
|
||||
<p>
|
||||
<h4><span class="big-number">{{ nodes_total|default:0 }}</span> {% trans 'nodes in total' %}</h4>
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
<a href="{% url 'horizon:infrastructure:nodes:index' %}?tab=nodes__resource"
|
||||
><span class="big-number">{{ nodes_resources|default:0 }}</span> {% trans 'Resource Nodes' %}</a>
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
<a href="{% url 'horizon:infrastructure:nodes:register' %}"
|
||||
class="btn ajax-modal pull-right">{% trans 'Register Nodes' %}</a>
|
||||
<a href="{% url 'horizon:infrastructure:nodes:index' %}?tab=nodes__free"
|
||||
><span class="big-number">{{ nodes_free|default:0 }}</span> {% trans 'Free Nodes' %}</a>
|
||||
</p>
|
||||
<hr>
|
||||
<h4>{% trans 'Statistics' %}</h4>
|
||||
<p>{% trans 'No statistics available' %}</p>
|
||||
</div></div>
|
||||
<div class="row-fluid">
|
||||
<div class="span8">
|
||||
<div class="widget">
|
||||
<h2>{% trans 'Health Status' %}</h2>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'horizon:infrastructure:nodes:index' %}?tab=nodes__deployed">
|
||||
{% trans 'Deployed Nodes' %}
|
||||
({{ deployed_nodes|length|default:0 }})
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if deployed_nodes_down %}
|
||||
<i class="icon-exclamation-sign"></i>
|
||||
{% if deployed_nodes_down|length == 1 %}
|
||||
{% url 'horizon:infrastructure:nodes:detail' deployed_nodes_down.0.uuid as node_detail_url %}
|
||||
{% else %}
|
||||
{% url 'horizon:infrastructure:nodes:index' as nodes_index_url %}
|
||||
{% endif %}
|
||||
|
||||
{% blocktrans count down_count=deployed_nodes_down|length %}
|
||||
<a href="{{ node_detail_url }}">{{ down_count }} node</a> is down
|
||||
{% plural %}
|
||||
<a href="{{ nodes_index_url }}?tab=nodes__deployed">{{ down_count }} nodes</a> are down
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
<i class="icon-ok"></i>
|
||||
{% trans 'All nodes are performing correctly' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'horizon:infrastructure:nodes:index' %}?tab=nodes__free">
|
||||
{% trans 'Free Nodes' %}
|
||||
({{ free_nodes|length|default:0 }})
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if free_nodes_down %}
|
||||
<i class="icon-exclamation-sign"></i>
|
||||
{% if free_nodes_down|length == 1 %}
|
||||
{% url 'horizon:infrastructure:nodes:detail' free_nodes_down.0.uuid as node_detail_url %}
|
||||
{% else %}
|
||||
{% url 'horizon:infrastructure:nodes:index' as nodes_index_url %}
|
||||
{% endif %}
|
||||
|
||||
{% blocktrans count down_count=free_nodes_down|length %}
|
||||
<a href="{{ node_detail_url }}">{{ down_count }} node</a> is down
|
||||
{% plural %}
|
||||
<a href="{{ nodes_index_url }}?tab=nodes__free">{{ down_count }} nodes</a> are down
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
<i class="icon-ok"></i>
|
||||
{% trans 'All nodes are performing correctly' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="widget">
|
||||
<h2>{% trans 'Provisioning Status' %}</h2>
|
||||
<p>{% trans 'No statistics available' %}</p>
|
||||
</div>
|
||||
<div class="widget">
|
||||
<h2>{% trans 'Power Status' %}</h2>
|
||||
<p>{% trans 'No statistics available' %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
{% extends 'infrastructure/base.html' %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
{% block title %}{% trans 'Nodes' %}{% endblock %}
|
||||
{% block title %}{% trans 'Registered Nodes' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_domain_page_header.html' with title=_('Nodes') %}
|
||||
{% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Registered Nodes') items_count=nodes_count %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="actions pull-right">
|
||||
<a href="{% url 'horizon:infrastructure:nodes:register' %}" class="btn ajax-modal">
|
||||
{% trans 'Register Nodes' %}
|
||||
</a>
|
||||
</div>
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ class NodesTests(test.BaseAdminViewTests):
|
||||
'list.return_value': free_nodes,
|
||||
}) as mock:
|
||||
res = self.client.get(INDEX_URL + '?tab=nodes__free')
|
||||
self.assertEqual(mock.list.call_count, 4)
|
||||
self.assertEqual(mock.list.call_count, 10)
|
||||
|
||||
self.assertTemplateUsed(res,
|
||||
'infrastructure/nodes/index.html')
|
||||
@@ -59,34 +59,34 @@ class NodesTests(test.BaseAdminViewTests):
|
||||
'list.side_effect': self.exceptions.tuskar,
|
||||
}) as mock:
|
||||
res = self.client.get(INDEX_URL + '?tab=nodes__free')
|
||||
self.assertEqual(mock.list.call_count, 3)
|
||||
self.assertEqual(mock.list.call_count, 2)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
def test_resource_nodes(self):
|
||||
resource_nodes = [api.Node(node)
|
||||
def test_deployed_nodes(self):
|
||||
deployed_nodes = [api.Node(node)
|
||||
for node in self.ironicclient_nodes.list()]
|
||||
|
||||
with patch('tuskar_ui.api.Node', **{
|
||||
'spec_set': ['list'], # Only allow these attributes
|
||||
'list.return_value': resource_nodes,
|
||||
'list.return_value': deployed_nodes,
|
||||
}) as mock:
|
||||
res = self.client.get(INDEX_URL + '?tab=nodes__resource')
|
||||
self.assertEqual(mock.list.call_count, 4)
|
||||
res = self.client.get(INDEX_URL + '?tab=nodes__deployed')
|
||||
self.assertEqual(mock.list.call_count, 10)
|
||||
|
||||
self.assertTemplateUsed(
|
||||
res, 'infrastructure/nodes/index.html')
|
||||
self.assertTemplateUsed(res, 'horizon/common/_detail_table.html')
|
||||
self.assertItemsEqual(res.context['resource_nodes_table'].data,
|
||||
resource_nodes)
|
||||
self.assertItemsEqual(res.context['deployed_nodes_table'].data,
|
||||
deployed_nodes)
|
||||
|
||||
def test_resource_nodes_list_exception(self):
|
||||
def test_deployed_nodes_list_exception(self):
|
||||
with patch('tuskar_ui.api.Node', **{
|
||||
'spec_set': ['list'],
|
||||
'list.side_effect': self.exceptions.tuskar,
|
||||
}) as mock:
|
||||
res = self.client.get(INDEX_URL + '?tab=nodes__resource')
|
||||
self.assertEqual(mock.list.call_count, 3)
|
||||
res = self.client.get(INDEX_URL + '?tab=nodes__deployed')
|
||||
self.assertEqual(mock.list.call_count, 2)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
|
||||
@@ -29,6 +29,36 @@ class IndexView(horizon_tabs.TabbedTableView):
|
||||
tab_group_class = tabs.NodeTabs
|
||||
template_name = 'infrastructure/nodes/index.html'
|
||||
|
||||
def get_free_nodes_count(self):
|
||||
try:
|
||||
free_nodes_count = len(api.Node.list(self.request,
|
||||
associated=False))
|
||||
except Exception:
|
||||
free_nodes_count = 0
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve free nodes.'))
|
||||
return free_nodes_count
|
||||
|
||||
def get_deployed_nodes_count(self):
|
||||
try:
|
||||
deployed_nodes_count = len(api.Node.list(self.request,
|
||||
associated=True))
|
||||
except Exception:
|
||||
deployed_nodes_count = 0
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve deployed nodes.'))
|
||||
return deployed_nodes_count
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IndexView, self).get_context_data(**kwargs)
|
||||
|
||||
context['free_nodes_count'] = self.get_free_nodes_count()
|
||||
context['deployed_nodes_count'] = self.get_deployed_nodes_count()
|
||||
context['nodes_count'] = (context['free_nodes_count'] +
|
||||
context['deployed_nodes_count'])
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class RegisterView(horizon_forms.ModalFormView):
|
||||
form_class = forms.NodeFormset
|
||||
|
||||
@@ -522,10 +522,6 @@ input {
|
||||
}
|
||||
}
|
||||
|
||||
.big-number {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
#nodes-formset-datatable .datatable tbody {
|
||||
input {
|
||||
padding: 2px 1px;
|
||||
@@ -587,6 +583,6 @@ input {
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen-workflow-buttons {
|
||||
.actions {
|
||||
margin: @baseLineHeight / 3;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{% load i18n %}
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
<h2>
|
||||
{% if request.session.domain_context_name %}
|
||||
<em>{{ request.session.domain_context_name }}:</em>
|
||||
{% endif %}
|
||||
{% if items_count %}
|
||||
{{ items_count }}
|
||||
{% endif %}
|
||||
{{ title }}
|
||||
</h2>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,25 @@
|
||||
{% with tab_group.get_tabs as tabs %}
|
||||
{% if tabs %}
|
||||
|
||||
{# Tab Navigation #}
|
||||
<ul id="{{ tab_group.get_id }}" {{ tab_group.attr_string|safe }}>
|
||||
{% for tab in tabs %}
|
||||
<li {{ tab.attr_string|safe }}>
|
||||
<a href="?{{ tab_group.param_name}}={{ tab.get_id }}" data-toggle="tab" data-target="#{{ tab.get_id }}" data-loaded='{{ tab.load|yesno:"true,false" }}'>
|
||||
{{ tab.name }} {% if tab.get_items_count %} ({{ tab.get_items_count }}) {% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{# Tab Content #}
|
||||
<div class="tab-content">
|
||||
{% for tab in tabs %}
|
||||
<div id="{{ tab.get_id }}" class="tab-pane{% if tab.is_active %} active{% endif %}">
|
||||
{{ tab.render }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
@@ -4,7 +4,7 @@
|
||||
<form class="form form-horizontal" {{ workflow.attr_string|safe }} action="{{ workflow.get_absolute_url }}" {% if add_to_field %}data-add-to-field="{{ add_to_field }}"{% endif %} method="POST"{% if workflow.multipart %} enctype="multipart/form-data"{% endif %}>
|
||||
{% csrf_token %}
|
||||
{% if REDIRECT_URL %}<input type="hidden" name="{{ workflow.redirect_param_name }}" value="{{ REDIRECT_URL }}"/>{% endif %}
|
||||
<div class="fullscreen-workflow-buttons pull-right">
|
||||
<div class="actions pull-right">
|
||||
{% block workflow-buttons %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{{ workflow.finalize_button_name }}">
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user