summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Cresswell <robert.cresswell@outlook.com>2017-02-01 19:05:49 +0000
committerRichard Jones <r1chardj0n3s@gmail.com>2017-02-02 13:19:21 +1100
commite2698063e27698901d77f9422697fd2ebc261655 (patch)
tree395a509b0afe36a8b0b91c4f0fd6ea995ebd074d
parent99849ad88fef36ed978d16f1d20a5e3b66200da1 (diff)
downloadhorizon-e2698063e27698901d77f9422697fd2ebc261655.tar.gz
Move Security Groups into its own panel
This patch moves the Security Groups tab from the Access and Security panel into its own panel under the Network panel group. As this is the last tab in Access and Security, that panel is also removed by this patch. Change-Id: Id29c7ce635d46383742aec140def265d4b249aa5 Implements: blueprint reorganise-access-and-security
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/security_groups/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/tabs.py55
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html11
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html8
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html7
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/tests.py155
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/urls.py31
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/views.py35
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/__init__.py (renamed from openstack_dashboard/dashboards/project/access_and_security/__init__.py)0
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/forms.py (renamed from openstack_dashboard/dashboards/project/access_and_security/security_groups/forms.py)6
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/panel.py (renamed from openstack_dashboard/dashboards/project/access_and_security/panel.py)10
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/tables.py (renamed from openstack_dashboard/dashboards/project/access_and_security/security_groups/tables.py)11
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_add_rule.html (renamed from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_add_rule.html)0
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_create.html (renamed from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_create.html)0
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_update.html (renamed from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_update.html)0
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html6
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/templates/security_groups/create.html (renamed from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/create.html)2
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/templates/security_groups/detail.html (renamed from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/detail.html)3
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html5
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/tests.py (renamed from openstack_dashboard/dashboards/project/access_and_security/security_groups/tests.py)124
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/urls.py (renamed from openstack_dashboard/dashboards/project/access_and_security/security_groups/urls.py)6
-rw-r--r--openstack_dashboard/dashboards/project/security_groups/views.py (renamed from openstack_dashboard/dashboards/project/access_and_security/security_groups/views.py)50
-rw-r--r--openstack_dashboard/enabled/_1060_project_access_panel.py10
-rw-r--r--openstack_dashboard/enabled/_1480_security_groups_panel.py6
-rw-r--r--releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml5
25 files changed, 186 insertions, 360 deletions
diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/__init__.py b/openstack_dashboard/dashboards/project/access_and_security/security_groups/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/__init__.py
+++ /dev/null
diff --git a/openstack_dashboard/dashboards/project/access_and_security/tabs.py b/openstack_dashboard/dashboards/project/access_and_security/tabs.py
deleted file mode 100644
index aa1ac0ccf..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/tabs.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2012 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Copyright 2012 Nebula, Inc.
-# Copyright 2012 OpenStack Foundation
-#
-# 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 horizon import exceptions
-from horizon import tabs
-
-from neutronclient.common import exceptions as neutron_exc
-
-from openstack_dashboard.api import network
-from openstack_dashboard.dashboards.project.access_and_security.\
- security_groups.tables import SecurityGroupsTable
-
-
-class SecurityGroupsTab(tabs.TableTab):
- table_classes = (SecurityGroupsTable,)
- name = _("Security Groups")
- slug = "security_groups_tab"
- template_name = "horizon/common/_detail_table.html"
- permissions = ('openstack.services.compute',)
-
- def get_security_groups_data(self):
- try:
- security_groups = network.security_group_list(self.request)
- except neutron_exc.ConnectionFailed:
- security_groups = []
- exceptions.handle(self.request)
- except Exception:
- security_groups = []
- exceptions.handle(self.request,
- _('Unable to retrieve security groups.'))
- return sorted(security_groups, key=lambda group: group.name)
-
-
-class AccessAndSecurityTabs(tabs.TabGroup):
- slug = "access_security_tabs"
- tabs = (SecurityGroupsTab,)
- sticky = True
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html b/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html
deleted file mode 100644
index 87b81a797..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n %}
-{% block title %}{% trans "Access &amp; Security" %}{% endblock %}
-
-{% block main %}
-<div class="row">
- <div class="col-sm-12">
- {{ tab_group.render }}
- </div>
-</div>
-{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html b/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html
deleted file mode 100644
index bcfa046dc..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n %}
-{% block title %}{% trans "Add Rule" %}{% endblock %}
-
-{% block main %}
- {% include 'project/access_and_security/security_groups/_add_rule.html' %}
-{% endblock %}
-
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html b/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html
deleted file mode 100644
index b07f5cdc0..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n %}
-{% block title %}{% trans "Edit Security Group" %}{% endblock %}
-
-{% block main %}
- {% include 'project/access_and_security/security_groups/_update.html' %}
-{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/access_and_security/tests.py b/openstack_dashboard/dashboards/project/access_and_security/tests.py
deleted file mode 100644
index c525d160c..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/tests.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright 2012 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# 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 copy import deepcopy # noqa
-
-from django.core.urlresolvers import reverse
-from django import http
-from mox3.mox import IsA # noqa
-import six
-
-from openstack_dashboard import api
-from openstack_dashboard.test import helpers as test
-from openstack_dashboard.usage import quotas
-
-INDEX_URL = reverse('horizon:project:access_and_security:index')
-
-
-class AccessAndSecurityTests(test.TestCase):
- def setUp(self):
- super(AccessAndSecurityTests, self).setUp()
-
- @test.create_stubs({api.network: ('security_group_list',),
- api.base: ('is_service_enabled',),
- quotas: ('tenant_quota_usages',)})
- def _test_index(self):
- sec_groups = self.security_groups.list()
- quota_data = self.quota_usages.first()
- quota_data['security_groups']['available'] = 10
-
- api.network.security_group_list(IsA(http.HttpRequest)) \
- .AndReturn(sec_groups)
- quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \
- .AndReturn(quota_data)
-
- api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
- .MultipleTimes().AndReturn(True)
-
- self.mox.ReplayAll()
-
- res = self.client.get(INDEX_URL)
-
- self.assertTemplateUsed(res, 'project/access_and_security/index.html')
-
- # Security groups
- sec_groups_from_ctx = res.context['security_groups_table'].data
- # Context data needs to contains all items from the test data.
- self.assertItemsEqual(sec_groups_from_ctx,
- sec_groups)
- # Sec groups in context need to be sorted by their ``name`` attribute.
- # This assertion is somewhat weak since it's only meaningful as long as
- # the sec groups in the test data are *not* sorted by name (which is
- # the case as of the time of this addition).
- self.assertTrue(
- all([sec_groups_from_ctx[i].name <= sec_groups_from_ctx[i + 1].name
- for i in range(len(sec_groups_from_ctx) - 1)]))
-
- def test_index(self):
- self._test_index()
-
-
-class SecurityGroupTabTests(test.TestCase):
- def setUp(self):
- super(SecurityGroupTabTests, self).setUp()
-
- @test.create_stubs({api.network: ('security_group_list',),
- quotas: ('tenant_quota_usages',),
- api.base: ('is_service_enabled',)})
- def test_create_button_attributes(self):
- sec_groups = self.security_groups.list()
- quota_data = self.quota_usages.first()
- quota_data['security_groups']['available'] = 10
-
- api.network.security_group_list(
- IsA(http.HttpRequest)) \
- .AndReturn(sec_groups)
- quotas.tenant_quota_usages(
- IsA(http.HttpRequest)).MultipleTimes() \
- .AndReturn(quota_data)
-
- api.base.is_service_enabled(
- IsA(http.HttpRequest), 'network').MultipleTimes() \
- .AndReturn(True)
-
- self.mox.ReplayAll()
-
- res = self.client.get(INDEX_URL +
- "?tab=access_security_tabs__security_groups_tab")
-
- security_groups = res.context['security_groups_table'].data
- self.assertItemsEqual(security_groups, self.security_groups.list())
-
- create_action = self.getAndAssertTableAction(res, 'security_groups',
- 'create')
-
- self.assertEqual('Create Security Group',
- six.text_type(create_action.verbose_name))
- self.assertIsNone(create_action.policy_rules)
- self.assertEqual(set(['ajax-modal']), set(create_action.classes))
-
- url = 'horizon:project:access_and_security:security_groups:create'
- self.assertEqual(url, create_action.url)
-
- @test.create_stubs({api.network: ('security_group_list',),
- quotas: ('tenant_quota_usages',),
- api.base: ('is_service_enabled',)})
- def _test_create_button_disabled_when_quota_exceeded(self,
- network_enabled):
- sec_groups = self.security_groups.list()
- quota_data = self.quota_usages.first()
- quota_data['security_groups']['available'] = 0
-
- api.network.security_group_list(
- IsA(http.HttpRequest)) \
- .AndReturn(sec_groups)
- quotas.tenant_quota_usages(
- IsA(http.HttpRequest)).MultipleTimes() \
- .AndReturn(quota_data)
-
- api.base.is_service_enabled(
- IsA(http.HttpRequest), 'network').MultipleTimes() \
- .AndReturn(network_enabled)
-
- self.mox.ReplayAll()
-
- res = self.client.get(INDEX_URL +
- "?tab=access_security_tabs__security_groups_tab")
-
- security_groups = res.context['security_groups_table'].data
- self.assertItemsEqual(security_groups, self.security_groups.list())
-
- create_action = self.getAndAssertTableAction(res, 'security_groups',
- 'create')
- self.assertIn('disabled', create_action.classes,
- 'The create button should be disabled')
-
- def test_create_button_disabled_when_quota_exceeded_neutron_disabled(self):
- self._test_create_button_disabled_when_quota_exceeded(False)
-
- def test_create_button_disabled_when_quota_exceeded_neutron_enabled(self):
- self._test_create_button_disabled_when_quota_exceeded(True)
diff --git a/openstack_dashboard/dashboards/project/access_and_security/urls.py b/openstack_dashboard/dashboards/project/access_and_security/urls.py
deleted file mode 100644
index 81f452fcd..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/urls.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2012 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# 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.conf.urls import include
-from django.conf.urls import url
-
-from openstack_dashboard.dashboards.project.access_and_security.\
- security_groups import urls as sec_group_urls
-from openstack_dashboard.dashboards.project.access_and_security import views
-
-
-urlpatterns = [
- url(r'^$', views.IndexView.as_view(), name='index'),
- url(r'security_groups/',
- include(sec_group_urls, namespace='security_groups')),
-]
diff --git a/openstack_dashboard/dashboards/project/access_and_security/views.py b/openstack_dashboard/dashboards/project/access_and_security/views.py
deleted file mode 100644
index 5827ab6f1..000000000
--- a/openstack_dashboard/dashboards/project/access_and_security/views.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2012 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Copyright 2012 Nebula, Inc.
-# Copyright 2012 OpenStack Foundation
-#
-# 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.
-
-"""
-Views for Instances and Volumes.
-"""
-
-from django.utils.translation import ugettext_lazy as _
-
-from horizon import tabs
-
-from openstack_dashboard.dashboards.project.access_and_security \
- import tabs as project_tabs
-
-
-class IndexView(tabs.TabbedTableView):
- tab_group_class = project_tabs.AccessAndSecurityTabs
- template_name = 'project/access_and_security/index.html'
- page_title = _("Access & Security")
diff --git a/openstack_dashboard/dashboards/project/access_and_security/__init__.py b/openstack_dashboard/dashboards/project/security_groups/__init__.py
index e69de29bb..e69de29bb 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/__init__.py
+++ b/openstack_dashboard/dashboards/project/security_groups/__init__.py
diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/forms.py b/openstack_dashboard/dashboards/project/security_groups/forms.py
index cfdaabb36..7ef548847 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/forms.py
+++ b/openstack_dashboard/dashboards/project/security_groups/forms.py
@@ -73,7 +73,7 @@ class GroupBase(forms.SelfHandlingForm):
messages.success(request, self.success_message % sg.name)
return sg
except Exception as e:
- redirect = reverse("horizon:project:access_and_security:index")
+ redirect = reverse("horizon:project:security_groups:index")
error_msg = self.error_message % e
exceptions.handle(request, error_msg, redirect=redirect)
@@ -407,8 +407,8 @@ class AddRule(forms.SelfHandlingForm):
return cleaned_data
def handle(self, request, data):
- redirect = reverse("horizon:project:access_and_security:"
- "security_groups:detail", args=[data['id']])
+ redirect = reverse("horizon:project:security_groups:detail",
+ args=[data['id']])
try:
rule = api.network.security_group_rule_create(
request,
diff --git a/openstack_dashboard/dashboards/project/access_and_security/panel.py b/openstack_dashboard/dashboards/project/security_groups/panel.py
index d8a7b01dc..720c5c451 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/panel.py
+++ b/openstack_dashboard/dashboards/project/security_groups/panel.py
@@ -1,5 +1,4 @@
-# Copyright 2012 Nebula, Inc.
-# Copyright 2012 OpenStack Foundation
+# Copyright 2017 Cisco Systems, 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
@@ -14,10 +13,9 @@
# under the License.
from django.utils.translation import ugettext_lazy as _
-
import horizon
-class AccessAndSecurity(horizon.Panel):
- name = _("Access & Security")
- slug = 'access_and_security'
+class SecurityGroups(horizon.Panel):
+ name = _("Security Groups")
+ slug = 'security_groups'
diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tables.py b/openstack_dashboard/dashboards/project/security_groups/tables.py
index 3d5f465ec..cedfe7752 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tables.py
+++ b/openstack_dashboard/dashboards/project/security_groups/tables.py
@@ -65,7 +65,7 @@ class DeleteGroup(policy.PolicyTargetMixin, tables.DeleteAction):
class CreateGroup(tables.LinkAction):
name = "create"
verbose_name = _("Create Security Group")
- url = "horizon:project:access_and_security:security_groups:create"
+ url = "horizon:project:security_groups:create"
classes = ("ajax-modal",)
icon = "plus"
@@ -90,7 +90,7 @@ class CreateGroup(tables.LinkAction):
class EditGroup(policy.PolicyTargetMixin, tables.LinkAction):
name = "edit"
verbose_name = _("Edit Security Group")
- url = "horizon:project:access_and_security:security_groups:update"
+ url = "horizon:project:security_groups:update"
classes = ("ajax-modal",)
icon = "pencil"
@@ -112,7 +112,7 @@ class EditGroup(policy.PolicyTargetMixin, tables.LinkAction):
class ManageRules(policy.PolicyTargetMixin, tables.LinkAction):
name = "manage_rules"
verbose_name = _("Manage Rules")
- url = "horizon:project:access_and_security:security_groups:detail"
+ url = "horizon:project:security_groups:detail"
icon = "pencil"
def allowed(self, request, security_group=None):
@@ -151,7 +151,7 @@ class SecurityGroupsTable(tables.DataTable):
class CreateRule(tables.LinkAction):
name = "add_rule"
verbose_name = _("Add Rule")
- url = "horizon:project:access_and_security:security_groups:add_rule"
+ url = "horizon:project:security_groups:add_rule"
classes = ("ajax-modal",)
icon = "plus"
@@ -197,8 +197,7 @@ class DeleteRule(tables.DeleteAction):
def get_success_url(self, request):
sg_id = self.table.kwargs['security_group_id']
- return reverse("horizon:project:access_and_security:"
- "security_groups:detail", args=[sg_id])
+ return reverse("horizon:project:security_groups:detail", args=[sg_id])
def get_remote_ip_prefix(rule):
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_add_rule.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_add_rule.html
index 5c29163b5..5c29163b5 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_add_rule.html
+++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_add_rule.html
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_create.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_create.html
index 655cebaf1..655cebaf1 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_create.html
+++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_create.html
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_update.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_update.html
index d94ea7339..d94ea7339 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_update.html
+++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_update.html
diff --git a/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html
new file mode 100644
index 000000000..ff94652ba
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html
@@ -0,0 +1,6 @@
+{% extends 'base.html' %}
+
+{% block main %}
+ {% include 'project/security_groups/_add_rule.html' %}
+{% endblock %}
+
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/create.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/create.html
index 1b5e578cd..ab43e707e 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/create.html
+++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/create.html
@@ -1,6 +1,4 @@
{% extends 'base.html' %}
-{% load i18n %}
-{% block title %}{% trans "Create Security Group" %}{% endblock %}
{% block main %}
{% include 'project/access_and_security/security_groups/_create.html' %}
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/detail.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/detail.html
index f2c0656a3..0717ae625 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/detail.html
+++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/detail.html
@@ -1,7 +1,4 @@
{% extends 'base.html' %}
-{% load i18n %}
-
-{% block title %}{% trans "Manage Security Group Rules" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_detail_header.html" %}
diff --git a/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html
new file mode 100644
index 000000000..cef7fb43d
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html
@@ -0,0 +1,5 @@
+{% extends 'base.html' %}
+
+{% block main %}
+ {% include 'project/security_groups/_update.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tests.py b/openstack_dashboard/dashboards/project/security_groups/tests.py
index ac1f2b893..52d1aed5b 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tests.py
+++ b/openstack_dashboard/dashboards/project/security_groups/tests.py
@@ -17,6 +17,7 @@
# under the License.
import cgi
+import six
import django
from django.conf import settings
@@ -30,21 +31,20 @@ from horizon import forms
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
+from openstack_dashboard.usage import quotas
-from openstack_dashboard.dashboards.project.access_and_security.\
- security_groups import tables
+from openstack_dashboard.dashboards.project.security_groups import tables
-INDEX_URL = reverse('horizon:project:access_and_security:index')
-SG_CREATE_URL = reverse('horizon:project:access_and_security:'
- 'security_groups:create')
+INDEX_URL = reverse('horizon:project:security_groups:index')
+SG_CREATE_URL = reverse('horizon:project:security_groups:create')
-SG_VIEW_PATH = 'horizon:project:access_and_security:security_groups:%s'
+SG_VIEW_PATH = 'horizon:project:security_groups:%s'
SG_DETAIL_VIEW = SG_VIEW_PATH % 'detail'
SG_UPDATE_VIEW = SG_VIEW_PATH % 'update'
SG_ADD_RULE_VIEW = SG_VIEW_PATH % 'add_rule'
-SG_TEMPLATE_PATH = 'project/access_and_security/security_groups/%s'
+SG_TEMPLATE_PATH = 'project/security_groups/%s'
SG_DETAIL_TEMPLATE = SG_TEMPLATE_PATH % 'detail.html'
SG_CREATE_TEMPLATE = SG_TEMPLATE_PATH % 'create.html'
SG_UPDATE_TEMPLATE = SG_TEMPLATE_PATH % '_update.html'
@@ -64,6 +64,116 @@ class SecurityGroupsViewTests(test.TestCase):
self.edit_url = reverse(SG_ADD_RULE_VIEW, args=[sec_group.id])
self.update_url = reverse(SG_UPDATE_VIEW, args=[sec_group.id])
+ @test.create_stubs({api.network: ('security_group_list',),
+ api.base: ('is_service_enabled',),
+ quotas: ('tenant_quota_usages',)})
+ def test_index(self):
+ sec_groups = self.security_groups.list()
+ quota_data = self.quota_usages.first()
+ quota_data['security_groups']['available'] = 10
+
+ api.network.security_group_list(IsA(http.HttpRequest)) \
+ .AndReturn(sec_groups)
+ quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \
+ .AndReturn(quota_data)
+
+ api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
+ .MultipleTimes().AndReturn(True)
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html')
+
+ # Security groups
+ sec_groups_from_ctx = res.context['security_groups_table'].data
+ # Context data needs to contains all items from the test data.
+ self.assertItemsEqual(sec_groups_from_ctx,
+ sec_groups)
+ # Sec groups in context need to be sorted by their ``name`` attribute.
+ # This assertion is somewhat weak since it's only meaningful as long as
+ # the sec groups in the test data are *not* sorted by name (which is
+ # the case as of the time of this addition).
+ self.assertTrue(
+ all([sec_groups_from_ctx[i].name <= sec_groups_from_ctx[i + 1].name
+ for i in range(len(sec_groups_from_ctx) - 1)]))
+
+ @test.create_stubs({api.network: ('security_group_list',),
+ quotas: ('tenant_quota_usages',),
+ api.base: ('is_service_enabled',)})
+ def test_create_button_attributes(self):
+ sec_groups = self.security_groups.list()
+ quota_data = self.quota_usages.first()
+ quota_data['security_groups']['available'] = 10
+
+ api.network.security_group_list(
+ IsA(http.HttpRequest)) \
+ .AndReturn(sec_groups)
+ quotas.tenant_quota_usages(
+ IsA(http.HttpRequest)).MultipleTimes() \
+ .AndReturn(quota_data)
+
+ api.base.is_service_enabled(
+ IsA(http.HttpRequest), 'network').MultipleTimes() \
+ .AndReturn(True)
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+
+ security_groups = res.context['security_groups_table'].data
+ self.assertItemsEqual(security_groups, self.security_groups.list())
+
+ create_action = self.getAndAssertTableAction(res, 'security_groups',
+ 'create')
+
+ self.assertEqual('Create Security Group',
+ six.text_type(create_action.verbose_name))
+ self.assertIsNone(create_action.policy_rules)
+ self.assertEqual(set(['ajax-modal']), set(create_action.classes))
+
+ url = 'horizon:project:security_groups:create'
+ self.assertEqual(url, create_action.url)
+
+ @test.create_stubs({api.network: ('security_group_list',),
+ quotas: ('tenant_quota_usages',),
+ api.base: ('is_service_enabled',)})
+ def _test_create_button_disabled_when_quota_exceeded(self,
+ network_enabled):
+ sec_groups = self.security_groups.list()
+ quota_data = self.quota_usages.first()
+ quota_data['security_groups']['available'] = 0
+
+ api.network.security_group_list(
+ IsA(http.HttpRequest)) \
+ .AndReturn(sec_groups)
+ quotas.tenant_quota_usages(
+ IsA(http.HttpRequest)).MultipleTimes() \
+ .AndReturn(quota_data)
+
+ api.base.is_service_enabled(
+ IsA(http.HttpRequest), 'network').MultipleTimes() \
+ .AndReturn(network_enabled)
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+
+ security_groups = res.context['security_groups_table'].data
+ self.assertItemsEqual(security_groups, self.security_groups.list())
+
+ create_action = self.getAndAssertTableAction(res, 'security_groups',
+ 'create')
+ self.assertIn('disabled', create_action.classes,
+ 'The create button should be disabled')
+
+ def test_create_button_disabled_when_quota_exceeded_neutron_disabled(self):
+ self._test_create_button_disabled_when_quota_exceeded(False)
+
+ def test_create_button_disabled_when_quota_exceeded_neutron_enabled(self):
+ self._test_create_button_disabled_when_quota_exceeded(True)
+
@test.create_stubs({api.network: ('security_group_rule_create',
'security_group_list',
'security_group_backend')})
diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/urls.py b/openstack_dashboard/dashboards/project/security_groups/urls.py
index 456bc0f62..0dc4a2a80 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/urls.py
+++ b/openstack_dashboard/dashboards/project/security_groups/urls.py
@@ -17,12 +17,10 @@
# under the License.
from django.conf.urls import url
-
-from openstack_dashboard.dashboards.project.access_and_security.\
- security_groups import views
-
+from openstack_dashboard.dashboards.project.security_groups import views
urlpatterns = [
+ url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^create/$', views.CreateView.as_view(), name='create'),
url(r'^(?P<security_group_id>[^/]+)/$',
views.DetailView.as_view(),
diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/views.py b/openstack_dashboard/dashboards/project/security_groups/views.py
index 4d6421dae..58ae279ca 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/views.py
+++ b/openstack_dashboard/dashboards/project/security_groups/views.py
@@ -28,18 +28,19 @@ from horizon import forms
from horizon import tables
from horizon.utils import memoized
+from neutronclient.common import exceptions as neutron_exc
+
from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.security_groups \
+ import forms as project_forms
+from openstack_dashboard.dashboards.project.security_groups \
+ import tables as project_tables
from openstack_dashboard.utils import filters
-from openstack_dashboard.dashboards.project.access_and_security.\
- security_groups import forms as project_forms
-from openstack_dashboard.dashboards.project.access_and_security.\
- security_groups import tables as project_tables
-
class DetailView(tables.DataTableView):
table_class = project_tables.RulesTable
- template_name = 'project/access_and_security/security_groups/detail.html'
+ template_name = 'project/security_groups/detail.html'
page_title = _("Manage Security Group Rules: "
"{{ security_group.name }} ({{ security_group.id }})")
@@ -49,7 +50,7 @@ class DetailView(tables.DataTableView):
try:
return api.network.security_group_get(self.request, sg_id)
except Exception:
- redirect = reverse('horizon:project:access_and_security:index')
+ redirect = reverse('horizon:project:security_groups:index')
exceptions.handle(self.request,
_('Unable to retrieve security group.'),
redirect=redirect)
@@ -71,10 +72,10 @@ class UpdateView(forms.ModalFormView):
form_class = project_forms.UpdateGroup
form_id = "update_security_group_form"
modal_id = "update_security_group_modal"
- template_name = 'project/access_and_security/security_groups/update.html'
+ template_name = 'project/security_groups/update.html'
submit_label = _("Edit Security Group")
- submit_url = "horizon:project:access_and_security:security_groups:update"
- success_url = reverse_lazy('horizon:project:access_and_security:index')
+ submit_url = "horizon:project:security_groups:update"
+ success_url = reverse_lazy('horizon:project:security_groups:index')
page_title = _("Edit Security Group")
@memoized.memoized_method
@@ -105,10 +106,10 @@ class AddRuleView(forms.ModalFormView):
form_class = project_forms.AddRule
form_id = "create_security_group_rule_form"
modal_id = "create_security_group_rule_modal"
- template_name = 'project/access_and_security/security_groups/add_rule.html'
+ template_name = 'project/security_groups/add_rule.html'
submit_label = _("Add")
- submit_url = "horizon:project:access_and_security:security_groups:add_rule"
- url = "horizon:project:access_and_security:security_groups:detail"
+ submit_url = "horizon:project:security_groups:add_rule"
+ url = "horizon:project:security_groups:detail"
page_title = _("Add Rule")
def get_success_url(self):
@@ -152,9 +153,26 @@ class CreateView(forms.ModalFormView):
form_class = project_forms.CreateGroup
form_id = "create_security_group_form"
modal_id = "create_security_group_modal"
- template_name = 'project/access_and_security/security_groups/create.html'
+ template_name = 'project/security_groups/create.html'
submit_label = _("Create Security Group")
submit_url = reverse_lazy(
- "horizon:project:access_and_security:security_groups:create")
- success_url = reverse_lazy('horizon:project:access_and_security:index')
+ "horizon:project:security_groups:create")
+ success_url = reverse_lazy('horizon:project:security_groups:index')
page_title = _("Create Security Group")
+
+
+class IndexView(tables.DataTableView):
+ table_class = project_tables.SecurityGroupsTable
+ page_title = _("Security Groups")
+
+ def get_data(self):
+ try:
+ security_groups = api.network.security_group_list(self.request)
+ except neutron_exc.ConnectionFailed:
+ security_groups = []
+ exceptions.handle(self.request)
+ except Exception:
+ security_groups = []
+ exceptions.handle(self.request,
+ _('Unable to retrieve security groups.'))
+ return sorted(security_groups, key=lambda group: group.name)
diff --git a/openstack_dashboard/enabled/_1060_project_access_panel.py b/openstack_dashboard/enabled/_1060_project_access_panel.py
deleted file mode 100644
index 6ef2538c7..000000000
--- a/openstack_dashboard/enabled/_1060_project_access_panel.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# The slug of the panel to be added to HORIZON_CONFIG. Required.
-PANEL = 'access_and_security'
-# The slug of the dashboard the PANEL associated with. Required.
-PANEL_DASHBOARD = 'project'
-# The slug of the panel group the PANEL is associated with.
-PANEL_GROUP = 'compute'
-
-# Python panel class of the PANEL to be added.
-ADD_PANEL = ('openstack_dashboard.dashboards.project.'
- 'access_and_security.panel.AccessAndSecurity')
diff --git a/openstack_dashboard/enabled/_1480_security_groups_panel.py b/openstack_dashboard/enabled/_1480_security_groups_panel.py
new file mode 100644
index 000000000..5e327a073
--- /dev/null
+++ b/openstack_dashboard/enabled/_1480_security_groups_panel.py
@@ -0,0 +1,6 @@
+PANEL_DASHBOARD = 'project'
+PANEL_GROUP = 'network'
+PANEL = 'security_groups'
+
+ADD_PANEL = ('openstack_dashboard.dashboards.project.security_groups'
+ '.panel.SecurityGroups')
diff --git a/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml b/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml
index 8b98ddba7..b58a6cc6c 100644
--- a/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml
+++ b/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml
@@ -2,4 +2,7 @@
features:
- The Access & Security panel's tabs have been moved to their own panels for
clearer navigation and better performance. API Access and Key Pairs now
- reside in the Compute panel group.
+ reside in the Compute panel group. Floating IPs and Security Groups are
+ now in the Network panel group.
+ - Download buttons for OpenStack RC files have been added to the user
+ dropdown menu in the top right of Horizon.