Restructure navigation and rename base directories

* overview is gone
* deployment has been renamed overcloud
   * controller/compute/storage have been removed,
     as different tabs will be developed in a
     future patch
* nodes now have tabbed navigation

Change-Id: I7db58725dacb3d535be60be2cb31578a636d962d
This commit is contained in:
Tzu-Mainn Chen 2014-01-14 16:19:16 -05:00
parent ae4821b26b
commit 61f6b4e620
59 changed files with 287 additions and 927 deletions

View File

@ -17,33 +17,12 @@ from django.utils.translation import ugettext_lazy as _
import horizon
class InfrastructureOverview(horizon.PanelGroup):
slug = "infrastructure_overview"
name = _("Overview")
class BasePanels(horizon.PanelGroup):
slug = "infrastructure"
name = _("Infrastructure")
panels = (
'overview',
)
class Deployment(horizon.PanelGroup):
slug = "deployment"
name = _("Deployment")
panels = (
'deployment.overview',
'deployment.controller',
'deployment.compute',
'deployment.object_storage',
'deployment.block_storage',
)
class Nodes(horizon.PanelGroup):
slug = "nodes"
name = _("Nodes")
panels = (
'nodes.overview',
'nodes.resource',
'nodes.free',
'nodes',
'overcloud',
)
@ -51,11 +30,9 @@ class Infrastructure(horizon.Dashboard):
name = _("Infrastructure")
slug = "infrastructure"
panels = (
InfrastructureOverview,
Deployment,
Nodes,
BasePanels,
)
default_panel = 'overview'
default_panel = 'overcloud'
permissions = ('openstack.roles.admin',)

View File

@ -1,27 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 tuskar_ui.infrastructure import dashboard
class BlockStorage(horizon.Panel):
name = _("Block Storage")
slug = "deployment.block_storage"
dashboard.Infrastructure.register(BlockStorage)

View File

@ -1,23 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import defaults
from tuskar_ui.infrastructure.deployment.block_storage import views
urlpatterns = defaults.patterns(
'',
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,18 +0,0 @@
# -*- coding: utf8 -*-
#
# 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.views import generic
class IndexView(generic.TemplateView):
template_name = 'infrastructure/base.html'

View File

@ -1,27 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 tuskar_ui.infrastructure import dashboard
class Controller(horizon.Panel):
name = _("Controller")
slug = "deployment.controller"
dashboard.Infrastructure.register(Controller)

View File

@ -1,23 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import defaults
from tuskar_ui.infrastructure.deployment.controller import views
urlpatterns = defaults.patterns(
'',
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,18 +0,0 @@
# -*- coding: utf8 -*-
#
# 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.views import generic
class IndexView(generic.TemplateView):
template_name = 'infrastructure/base.html'

View File

@ -1,27 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 tuskar_ui.infrastructure import dashboard
class ObjectStorage(horizon.Panel):
name = _("Object Storage")
slug = "deployment.object_storage"
dashboard.Infrastructure.register(ObjectStorage)

View File

@ -1,23 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import defaults
from tuskar_ui.infrastructure.deployment.object_storage import views
urlpatterns = defaults.patterns(
'',
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,18 +0,0 @@
# -*- coding: utf8 -*-
#
# 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.views import generic
class IndexView(generic.TemplateView):
template_name = 'infrastructure/base.html'

View File

@ -1,27 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 tuskar_ui.infrastructure import dashboard
class DeploymentOverview(horizon.Panel):
name = _("Overview")
slug = "deployment.overview"
dashboard.Infrastructure.register(DeploymentOverview)

View File

@ -1,23 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import defaults
from tuskar_ui.infrastructure.deployment.overview import views
urlpatterns = defaults.patterns(
'',
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,18 +0,0 @@
# -*- coding: utf8 -*-
#
# 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.views import generic
class IndexView(generic.TemplateView):
template_name = 'infrastructure/base.html'

View File

@ -1,26 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 _
from tuskar_ui.infrastructure.nodes import tables
class FreeNodesTable(tables.NodesTable):
class Meta:
name = "free_nodes"
verbose_name = _("Free Nodes")
table_actions = ()
row_actions = ()

View File

@ -1,12 +0,0 @@
{% extends 'infrastructure/base_nodes_table.html' %}
{% load i18n %}
{% block title %}{% trans "Free Nodes" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Free Nodes") %}
{% endblock page_header %}
{% block name %}
{% trans "Free Nodes" %}
{% endblock %}

View File

@ -1,58 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import urlresolvers
from mock import patch # noqa
from tuskar_ui import api
from tuskar_ui.test import helpers as test
INDEX_URL = urlresolvers.reverse('horizon:infrastructure:nodes.free'
':index')
NODES_OVERVIEW_URL = urlresolvers.reverse('horizon:infrastructure:'
'nodes.overview:index')
class FreeNodesTests(test.BaseAdminViewTests):
def setUp(self):
super(FreeNodesTests, self).setUp()
def test_index(self):
free_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': free_nodes,
}) as mock:
res = self.client.get(INDEX_URL)
self.assertEqual(mock.list.call_count, 1)
self.maxDiff = None
self.assertTemplateUsed(res,
'infrastructure/nodes.free/index.html')
self.assertItemsEqual(res.context['free_nodes_table'].data,
free_nodes)
def test_index_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)
self.assertEqual(mock.list.call_count, 1)
self.assertRedirectsNoFollow(res, NODES_OVERVIEW_URL)

View File

@ -1,23 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 import defaults
from tuskar_ui.infrastructure.nodes.free import views
urlpatterns = defaults.patterns(
'',
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,39 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import urlresolvers
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tables as horizon_tables
from tuskar_ui import api as tuskar
from tuskar_ui.infrastructure.nodes.free import tables
class IndexView(horizon_tables.DataTableView):
table_class = tables.FreeNodesTable
template_name = 'infrastructure/nodes.free/index.html'
def get_data(self):
try:
free_nodes = tuskar.Node.list(self.request, associated=False)
except Exception:
free_nodes = []
redirect = urlresolvers.reverse(
'horizon:infrastructure:nodes.overview:index')
exceptions.handle(self.request,
_('Unable to retrieve free nodes.'),
redirect=redirect)
return free_nodes

View File

@ -1,27 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 tuskar_ui.infrastructure import dashboard
class NodesOverview(horizon.Panel):
name = _("Overview")
slug = "nodes.overview"
dashboard.Infrastructure.register(NodesOverview)

View File

@ -1,36 +0,0 @@
{% extends 'infrastructure/base.html' %}
{% load i18n %}
{% load url from future %}
{% block title %}{% trans 'Nodes Overview' %}{% endblock %}
{% block page_header %}
{% include 'horizon/common/_domain_page_header.html' with title=_('Nodes Overview') %}
<div class="breadcrumbs">
<a href="{% url 'horizon:infrastructure:overview:index' %}"
>{% trans "Infrastructure" %}</a>
<span class="separator"></span>
</div>
{% endblock page_header %}
{% block main %}
<div class="row-fluid"><div class="span12">
<p>
<h4><span class="big-number">{{ nodes_total|default:0 }}</span> nodes in total</h4>
</p>
<hr>
<p>
<a href="{% url 'horizon:infrastructure:nodes.resource:index' %}"
><span class="big-number">{{ nodes_allocated_resources|default:0 }}</span> Allocated — Resource Nodes</a>
</p>
<hr>
<p>
<a href="{% url 'horizon:infrastructure:nodes.overview:register' %}"
class="btn ajax-modal pull-right">Register Nodes</a>
<a href="{% url 'horizon:infrastructure:nodes.free:index' %}"
><span class="big-number">{{ nodes_unallocated|default:0 }}</span> Unallocated Nodes</a>
</p>
<hr>
<h4>Statistics</h4>
<p>No statistics available</p>
</div></div>
{% endblock %}

View File

@ -1,63 +0,0 @@
# -*- coding: utf8 -*-
#
# 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_lazy
from django.utils.translation import ugettext_lazy as _
from django.views import generic
from horizon import exceptions
import horizon.forms
from tuskar_ui import api
from tuskar_ui.infrastructure.nodes import forms
class IndexView(generic.TemplateView):
template_name = 'infrastructure/nodes.overview/index.html'
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
try:
free_nodes = len(api.Node.list(self.request, associated=False))
except Exception:
free_nodes = 0
exceptions.handle(self.request,
_('Unable to retrieve free nodes.'))
try:
allocated_nodes = len(api.Node.list(self.request, associated=True))
except Exception:
allocated_nodes = 0
exceptions.handle(self.request,
_('Unable to retrieve allocated nodes.'))
context.update({
'nodes_total': free_nodes + allocated_nodes,
'nodes_allocated_resources': allocated_nodes,
'nodes_unallocated': free_nodes,
})
return context
class RegisterView(horizon.forms.ModalFormView):
form_class = forms.NodeFormset
form_prefix = 'register_nodes'
template_name = 'infrastructure/nodes.overview/register.html'
success_url = reverse_lazy(
'horizon:infrastructure:nodes.overview:index')
def get_data(self):
return []
def get_form(self, form_class):
return form_class(self.request.POST or None,
initial=self.get_data(),
prefix=self.form_prefix)

View File

@ -19,9 +19,9 @@ import horizon
from tuskar_ui.infrastructure import dashboard
class NodesFree(horizon.Panel):
name = _("Free Nodes")
slug = "nodes.free"
class Nodes(horizon.Panel):
name = _("Nodes")
slug = "nodes"
dashboard.Infrastructure.register(NodesFree)
dashboard.Infrastructure.register(Nodes)

View File

@ -1,27 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 tuskar_ui.infrastructure import dashboard
class NodesResource(horizon.Panel):
name = _("Resource Nodes")
slug = "nodes.resource"
dashboard.Infrastructure.register(NodesResource)

View File

@ -1,26 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 _
from tuskar_ui.infrastructure.nodes import tables
class ResourceNodesTable(tables.NodesTable):
class Meta:
name = "resource_nodes"
verbose_name = _("Resource Nodes")
table_actions = ()
row_actions = ()

View File

@ -1,12 +0,0 @@
{% extends 'infrastructure/base_nodes_table.html' %}
{% load i18n %}
{% block title %}{% trans "Resource Nodes" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Resource Nodes") %}
{% endblock page_header %}
{% block name %}
{% trans "Resource Nodes" %}
{% endblock %}

View File

@ -1,59 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import urlresolvers
from mock import patch # noqa
from tuskar_ui import api
from tuskar_ui.test import helpers as test
INDEX_URL = urlresolvers.reverse('horizon:infrastructure:nodes.resource'
':index')
NODES_OVERVIEW_URL = urlresolvers.reverse('horizon:infrastructure:'
'nodes.overview:index')
class ResourceNodesTests(test.BaseAdminViewTests):
def setUp(self):
super(ResourceNodesTests, self).setUp()
def test_index(self):
resource_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,
}) as mock:
res = self.client.get(INDEX_URL)
self.assertEqual(mock.list.call_count, 1)
self.maxDiff = None
self.assertTemplateUsed(
res, 'infrastructure/nodes.resource/index.html')
self.assertItemsEqual(res.context['resource_nodes_table'].data,
resource_nodes)
def test_index_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)
self.assertEqual(mock.list.call_count, 1)
self.assertRedirectsNoFollow(res, NODES_OVERVIEW_URL)

View File

@ -1,23 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 import defaults
from tuskar_ui.infrastructure.nodes.resource import views
urlpatterns = defaults.patterns(
'',
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,39 +0,0 @@
# -*- coding: utf8 -*-
#
# 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 import urlresolvers
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tables as horizon_tables
from tuskar_ui import api as tuskar
from tuskar_ui.infrastructure.nodes.resource import tables
class IndexView(horizon_tables.DataTableView):
table_class = tables.ResourceNodesTable
template_name = 'infrastructure/nodes.resource/index.html'
def get_data(self):
try:
resource_nodes = tuskar.Node.list(self.request, associated=True)
except Exception:
resource_nodes = []
redirect = urlresolvers.reverse(
'horizon:infrastructure:nodes.overview:index')
exceptions.handle(self.request,
_('Unable to retrieve resource nodes.'),
redirect=redirect)
return resource_nodes

View File

@ -54,3 +54,21 @@ class NodesTable(tables.DataTable):
def get_object_display(self, datum):
return datum.uuid
class FreeNodesTable(NodesTable):
class Meta:
name = "free_nodes"
verbose_name = _("Free Nodes")
table_actions = ()
row_actions = ()
class ResourceNodesTable(NodesTable):
class Meta:
name = "resource_nodes"
verbose_name = _("Resource Nodes")
table_actions = ()
row_actions = ()

View File

@ -0,0 +1,93 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, 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 import urlresolvers
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from tuskar_ui import api
from tuskar_ui.infrastructure.nodes import tables
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = ("infrastructure/nodes/_overview.html")
def get_context_data(self, request):
try:
free_nodes = len(api.Node.list(request, associated=False))
except Exception:
free_nodes = 0
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.'))
return {
'nodes_total': free_nodes + resource_nodes,
'nodes_resources': resource_nodes,
'nodes_free': free_nodes,
}
class ResourceTab(tabs.TableTab):
table_classes = (tables.ResourceNodesTable,)
name = _("Resource")
slug = "resource"
template_name = ("horizon/common/_detail_table.html")
def get_resource_nodes_data(self):
try:
resource_nodes = api.Node.list(self.request, associated=True)
except Exception:
resource_nodes = []
redirect = urlresolvers.reverse(
'horizon:infrastructure:nodes:index')
exceptions.handle(self.request,
_('Unable to retrieve resource nodes.'),
redirect=redirect)
return resource_nodes
class FreeTab(tabs.TableTab):
table_classes = (tables.FreeNodesTable,)
name = _("Free")
slug = "free"
template_name = ("horizon/common/_detail_table.html")
def get_free_nodes_data(self):
try:
free_nodes = api.Node.list(self.request, associated=False)
except Exception:
free_nodes = []
redirect = urlresolvers.reverse(
'horizon:infrastructure:nodes:index')
exceptions.handle(self.request,
_('Unable to retrieve free nodes.'),
redirect=redirect)
return free_nodes
class NodeTabs(tabs.TabGroup):
slug = "nodes"
tabs = (OverviewTab, ResourceTab, FreeTab)
sticky = True

View File

@ -3,17 +3,17 @@
<div class="form form-inline">
<div class="row-fluid">
<h3 style="margin-bottom:16px">Node Detail</h3>
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.node_tags %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.node_tags %}
</div>
<div class="row-fluid">
<h4>Power Management</h4>
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.ip_address required=True %}
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.ipmi_user %}
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.ipmi_password %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ip_address required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_user %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_password %}
</div>
<div class="row-fluid">
<h4>Networking</h4>
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.mac_address required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_address required=True %}
</div>
<div class="row-fluid">
<div class="span4">
@ -24,9 +24,9 @@
</label>
</div>
<div class="row-fluid" id="register-hardware-fields">
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.cpus extra_text=_('units') required=True %}
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.memory extra_text=_('MB') required=True %}
{% include 'infrastructure/nodes.overview/_nodes_formset_field.html' with field=form.local_disk extra_text=_('GB') required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpus extra_text=_('units') required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.memory extra_text=_('MB') required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.local_disk extra_text=_('GB') required=True %}
</div>
</div>
</div>

View File

@ -0,0 +1,24 @@
{% load i18n %}
{% 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>

View File

@ -3,18 +3,18 @@
{% load url from future %}
{% block form_id %}register_nodes_form{% endblock %}
{% block form_action %}{% url 'horizon:infrastructure:nodes.overview:register' %}{% endblock %}
{% block form_action %}{% url 'horizon:infrastructure:nodes:register' %}{% endblock %}
{% block modal_id %}register_nodes_modal{% endblock %}
{% block modal-header %}{% trans "Register Nodes" %}{% endblock %}
{% block modal-body %}
{% include "formset_table/menu_formset.html" with formset=form form_template="infrastructure/nodes.overview/_nodes_formset_form.html" %}
{% include "formset_table/menu_formset.html" with formset=form form_template="infrastructure/nodes/_nodes_formset_form.html" %}
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit"
value="{% trans "Register Nodes" %}" />
<a href="{% url 'horizon:infrastructure:nodes.overview:index' %}"
<a href="{% url 'horizon:infrastructure:nodes:index' %}"
class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends 'infrastructure/base.html' %}
{% load i18n %}
{% load url from future %}
{% block title %}{% trans 'Nodes' %}{% endblock %}
{% block page_header %}
{% include 'horizon/common/_domain_page_header.html' with title=_('Nodes') %}
{% endblock page_header %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -7,5 +7,5 @@
{% endblock %}
{% block main %}
{% include "infrastructure/nodes.overview/_register.html" %}
{% include "infrastructure/nodes/_register.html" %}
{% endblock %}

View File

@ -17,28 +17,86 @@ from django.core import urlresolvers
from mock import patch, call # noqa
from openstack_dashboard.test.test_data import utils
from tuskar_ui import api as api
from tuskar_ui.test import helpers as test
from tuskar_ui.test.test_data import tuskar_data
INDEX_URL = urlresolvers.reverse(
'horizon:infrastructure:nodes.overview:index')
'horizon:infrastructure:nodes:index')
REGISTER_URL = urlresolvers.reverse(
'horizon:infrastructure:nodes.overview:register')
'horizon:infrastructure:nodes:register')
TEST_DATA = utils.TestDataContainer()
tuskar_data.data(TEST_DATA)
class RegisterNodesTests(test.BaseAdminViewTests):
class NodesTests(test.BaseAdminViewTests):
def test_index_get(self):
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(
res, 'infrastructure/nodes.overview/index.html')
res, 'infrastructure/nodes/index.html')
self.assertTemplateUsed(res, 'infrastructure/nodes/_overview.html')
def test_free_nodes(self):
free_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': free_nodes,
}) as mock:
res = self.client.get(INDEX_URL + '?tab=nodes__free')
self.assertEqual(mock.list.call_count, 4)
self.maxDiff = None
self.assertTemplateUsed(res,
'infrastructure/nodes/index.html')
self.assertTemplateUsed(res, 'horizon/common/_detail_table.html')
self.assertItemsEqual(res.context['free_nodes_table'].data,
free_nodes)
def test_free_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__free')
self.assertEqual(mock.list.call_count, 3)
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_resource_nodes(self):
resource_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,
}) as mock:
res = self.client.get(INDEX_URL + '?tab=nodes__resource')
self.assertEqual(mock.list.call_count, 4)
self.maxDiff = None
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)
def test_resource_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)
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_register_get(self):
res = self.client.get(REGISTER_URL)
self.assertTemplateUsed(
res, 'infrastructure/nodes.overview/register.html')
res, 'infrastructure/nodes/register.html')
def test_register_post(self):
node = TEST_DATA.ironicclient_nodes.first
@ -104,4 +162,4 @@ class RegisterNodesTests(test.BaseAdminViewTests):
['DE:AD:BE:EF:CA:FF'], None, u''),
])
self.assertTemplateUsed(
res, 'infrastructure/nodes.overview/register.html')
res, 'infrastructure/nodes/register.html')

View File

@ -14,7 +14,7 @@
from django.conf import urls
from tuskar_ui.infrastructure.nodes.overview import views
from tuskar_ui.infrastructure.nodes import views
urlpatterns = urls.patterns(

View File

@ -0,0 +1,42 @@
# -*- coding: utf8 -*-
#
# 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_lazy
import horizon.forms
from horizon import tabs as horizon_tabs
from tuskar_ui.infrastructure.nodes import forms
from tuskar_ui.infrastructure.nodes import tabs
class IndexView(horizon_tabs.TabbedTableView):
tab_group_class = tabs.NodeTabs
template_name = 'infrastructure/nodes/index.html'
class RegisterView(horizon.forms.ModalFormView):
form_class = forms.NodeFormset
form_prefix = 'register_nodes'
template_name = 'infrastructure/nodes/register.html'
success_url = reverse_lazy(
'horizon:infrastructure:nodes:index')
def get_data(self):
return []
def get_form(self, form_class):
return form_class(self.request.POST or None,
initial=self.get_data(),
prefix=self.form_prefix)

View File

@ -19,9 +19,9 @@ import horizon
from tuskar_ui.infrastructure import dashboard
class Compute(horizon.Panel):
name = _("Compute")
slug = "deployment.compute"
class Overcloud(horizon.Panel):
name = _("Overcloud")
slug = "overcloud"
dashboard.Infrastructure.register(Compute)
dashboard.Infrastructure.register(Overcloud)

View File

@ -14,7 +14,7 @@
from django.conf.urls import defaults
from tuskar_ui.infrastructure.deployment.compute import views
from tuskar_ui.infrastructure.overcloud import views
urlpatterns = defaults.patterns(

View File

@ -1,17 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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.
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -1,27 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 tuskar_ui.infrastructure import dashboard
class Overview(horizon.Panel):
name = _("Overview")
slug = "overview"
dashboard.Infrastructure.register(Overview)

View File

@ -1,12 +0,0 @@
{% extends 'infrastructure/base.html' %}
{% load i18n %}
{% block title %}{% trans "Overview" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Overview") %}
{% endblock page_header %}
{% block infrastructure_main %}
{% endblock %}

View File

@ -1,21 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 openstack_dashboard.test import helpers as test
class OverviewTests(test.TestCase):
# Unit tests for overview.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -1,23 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 import defaults
from tuskar_ui.infrastructure.overview import views
urlpatterns = defaults.patterns(
'',
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,24 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 horizon import views
class IndexView(views.APIView):
# A very simple class-based view...
template_name = 'infrastructure/overview/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context

View File

@ -5,9 +5,7 @@
<div class="row-fluid">
<div class="span12">
<div class="breadcrumbs">
<a href="{% url 'horizon:infrastructure:overview:index' %}">{% trans "Infrastructure" %}</a>
<span class="separator"></span>
<a href="{% url 'horizon:infrastructure:nodes.overview:index' %}">{% trans "Nodes" %}</a>
<a href="{% url 'horizon:infrastructure:nodes:index' %}">{% trans "Nodes" %}</a>
<span class="separator"></span>
</div>