From 4b523122b0aa122d6ae5f76792fcadac00af89db Mon Sep 17 00:00:00 2001 From: manchandavishal Date: Tue, 19 Feb 2019 09:05:11 +0000 Subject: [PATCH] Add volume group-specs-list support for admin panel This commit allow admin to list/show cinder volume group-spec using horizon dashboard and user can perform the following table action : 1. group-spec-create 2. group-spec-edit 3. group-spec-delete Partially-Implements blueprint cinder-generic-volume-groups Change-Id: I7a24e21bbf86595bc7e1251d29caed7f7ff5dec8 --- openstack_dashboard/api/cinder.py | 28 +++- .../admin/group_types/specs/__init__.py | 0 .../admin/group_types/specs/forms.py | 81 ++++++++++ .../admin/group_types/specs/tables.py | 87 +++++++++++ .../admin/group_types/specs/tests.py | 141 ++++++++++++++++++ .../admin/group_types/specs/urls.py | 22 +++ .../admin/group_types/specs/views.py | 125 ++++++++++++++++ .../dashboards/admin/group_types/tables.py | 10 ++ .../templates/group_types/specs/_create.html | 7 + .../templates/group_types/specs/_edit.html | 7 + .../templates/group_types/specs/_index.html | 13 ++ .../templates/group_types/specs/create.html | 14 ++ .../templates/group_types/specs/edit.html | 14 ++ .../templates/group_types/specs/index.html | 12 ++ .../dashboards/admin/group_types/urls.py | 5 + ...eneric-volume-groups-c0bf175f5d7d3a37.yaml | 5 +- 16 files changed, 566 insertions(+), 5 deletions(-) create mode 100644 openstack_dashboard/dashboards/admin/group_types/specs/__init__.py create mode 100644 openstack_dashboard/dashboards/admin/group_types/specs/forms.py create mode 100644 openstack_dashboard/dashboards/admin/group_types/specs/tables.py create mode 100644 openstack_dashboard/dashboards/admin/group_types/specs/tests.py create mode 100644 openstack_dashboard/dashboards/admin/group_types/specs/urls.py create mode 100644 openstack_dashboard/dashboards/admin/group_types/specs/views.py create mode 100644 openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_create.html create mode 100644 openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_edit.html create mode 100644 openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_index.html create mode 100644 openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/create.html create mode 100644 openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/edit.html create mode 100644 openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/index.html diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index aeacc95f99..b1ed14cba3 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -152,6 +152,14 @@ class VolTypeExtraSpec(object): self.value = val +class GroupTypeSpec(object): + def __init__(self, group_type_id, key, val): + self.group_type_id = group_type_id + self.id = key + self.key = key + self.value = val + + class QosSpec(object): def __init__(self, id, key, val): self.id = id @@ -1154,16 +1162,28 @@ def group_type_delete(request, group_type_id): client.group_types.delete(group_type_id) +@profiler.trace +def group_type_spec_list(request, group_type_id, raw=False): + group_type = group_type_get(request, group_type_id) + specs = group_type._apiresource.get_keys() + if raw: + return specs + return [GroupTypeSpec(group_type_id, key, value) for + key, value in specs.items()] + + @profiler.trace def group_type_spec_set(request, group_type_id, metadata): - client = _cinderclient_with_generic_groups(request) - client.group_types.set_keys(metadata) + group_type = group_type_get(request, group_type_id) + if not metadata: + return None + return group_type._apiresource.set_keys(metadata) @profiler.trace def group_type_spec_unset(request, group_type_id, keys): - client = _cinderclient_with_generic_groups(request) - client.group_types.unset_keys(keys) + group_type = group_type_get(request, group_type_id) + return group_type._apiresource.unset_keys(keys) @profiler.trace diff --git a/openstack_dashboard/dashboards/admin/group_types/specs/__init__.py b/openstack_dashboard/dashboards/admin/group_types/specs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/admin/group_types/specs/forms.py b/openstack_dashboard/dashboards/admin/group_types/specs/forms.py new file mode 100644 index 0000000000..da958feb5d --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/specs/forms.py @@ -0,0 +1,81 @@ +# 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. + +import re + +from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard import api + + +KEY_NAME_REGEX = re.compile(r"^[a-zA-Z0-9_.:-]+$", re.UNICODE) +KEY_ERROR_MESSAGES = { + 'invalid': _('Key names can only contain alphanumeric characters, ' + 'underscores, periods, colons and hyphens')} + + +class CreateSpec(forms.SelfHandlingForm): + key = forms.RegexField(max_length=255, label=_("Key"), + regex=KEY_NAME_REGEX, + error_messages=KEY_ERROR_MESSAGES) + value = forms.CharField(max_length=255, label=_("Value")) + + def handle(self, request, data): + group_type_id = self.initial['group_type_id'] + error_msg = _('key with name "%s" already exists.Use Edit to ' + 'update the value, else create key with different ' + 'name.') % data['key'] + try: + specs_list = api.cinder.group_type_spec_list(self.request, + group_type_id) + for spec in specs_list: + if spec.key.lower() == data['key'].lower(): + raise forms.ValidationError(error_msg) + api.cinder.group_type_spec_set(request, + group_type_id, + {data['key']: data['value']}) + + msg = _('Created group type spec "%s".') % data['key'] + messages.success(request, msg) + return True + except forms.ValidationError: + messages.error(request, error_msg) + except Exception: + redirect = reverse("horizon:admin:group_types:index") + exceptions.handle(request, + _("Unable to create group type spec."), + redirect=redirect) + + +class EditSpec(forms.SelfHandlingForm): + value = forms.CharField(max_length=255, label=_("Value")) + + def handle(self, request, data): + key = self.initial['key'] + group_type_id = self.initial['group_type_id'] + try: + api.cinder.group_type_spec_set(request, + group_type_id, + {key: data['value']}) + msg = _('Saved group spec "%s".') % key + messages.success(request, msg) + return True + except Exception: + redirect = reverse("horizon:admin:group_types:index") + exceptions.handle(request, + _("Unable to edit group type spec."), + redirect=redirect) diff --git a/openstack_dashboard/dashboards/admin/group_types/specs/tables.py b/openstack_dashboard/dashboards/admin/group_types/specs/tables.py new file mode 100644 index 0000000000..e0b68a211e --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/specs/tables.py @@ -0,0 +1,87 @@ +# 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.urls import reverse +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy +from six.moves.urllib import parse + +from horizon import tables + +from openstack_dashboard import api + + +class GroupTypeSpecDelete(tables.DeleteAction): + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Spec", + u"Delete Specs", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Delete Spec", + u"Delete Specs", + count + ) + + def delete(self, request, obj_id): + key = parse.unquote(obj_id) + api.cinder.group_type_spec_unset(request, + self.table.kwargs['type_id'], + [key]) + + def get_success_url(self, request): + return reverse('horizon:admin:group_types:index') + + +class GroupTypeSpecCreate(tables.LinkAction): + name = "create" + verbose_name = _("Create Spec") + url = "horizon:admin:group_types:specs:create" + classes = ("ajax-modal",) + icon = "plus" + + def get_link_url(self, group_type_spec=None): + return reverse(self.url, args=[self.table.kwargs['type_id']]) + + +class GroupTypeSpecEdit(tables.LinkAction): + name = "edit" + verbose_name = _("Edit Spec") + url = "horizon:admin:group_types:specs:edit" + classes = ("btn-edit", "ajax-modal") + + def get_link_url(self, group_type_spec): + return reverse(self.url, args=[self.table.kwargs['type_id'], + group_type_spec.key]) + + +class GroupTypeSpecsTable(tables.DataTable): + key = tables.Column('key', verbose_name=_('Key')) + value = tables.Column('value', verbose_name=_('Value')) + + class Meta(object): + name = "specs" + verbose_name = _("Group Type Specs") + table_actions = (GroupTypeSpecCreate, GroupTypeSpecDelete) + row_actions = (GroupTypeSpecEdit, GroupTypeSpecDelete) + + def get_object_id(self, datum): + return parse.quote(datum.key) + + def get_object_display(self, datum): + return datum.key diff --git a/openstack_dashboard/dashboards/admin/group_types/specs/tests.py b/openstack_dashboard/dashboards/admin/group_types/specs/tests.py new file mode 100644 index 0000000000..3dac5afcba --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/specs/tests.py @@ -0,0 +1,141 @@ +# 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.urls import reverse + +from openstack_dashboard import api +from openstack_dashboard.test import helpers as test + + +class GroupTypeSpecTests(test.BaseAdminViewTests): + + @test.create_mocks({api.cinder: ('group_type_spec_list', + 'group_type_get')}) + def test_list_specs_when_none_exists(self): + group_type = self.cinder_group_types.first() + specs = [api.cinder.GroupTypeSpec(group_type.id, 'k1', 'v1')] + + self.mock_group_type_get.return_value = group_type + self.mock_group_type_spec_list.return_value = specs + url = reverse('horizon:admin:group_types:specs:index', + args=[group_type.id]) + resp = self.client.get(url) + self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed(resp, + "admin/group_types/specs/index.html") + self.mock_group_type_get.assert_called_once_with( + test.IsHttpRequest(), group_type.id) + self.mock_group_type_spec_list.assert_called_once_with( + test.IsHttpRequest(), group_type.id) + + @test.create_mocks({api.cinder: ('group_type_spec_list', + 'group_type_get')}) + def test_specs_view_with_exception(self): + group_type = self.cinder_group_types.first() + + self.mock_group_type_get.return_value = group_type + self.mock_group_type_spec_list.side_effect = self.exceptions.cinder + url = reverse('horizon:admin:group_types:specs:index', + args=[group_type.id]) + resp = self.client.get(url) + self.assertEqual(len(resp.context['specs_table'].data), 0) + self.assertMessageCount(resp, error=1) + self.mock_group_type_get.assert_called_once_with( + test.IsHttpRequest(), group_type.id) + self.mock_group_type_spec_list.assert_called_once_with( + test.IsHttpRequest(), group_type.id) + + @test.create_mocks({api.cinder: ('group_type_spec_list', + 'group_type_spec_set', )}) + def test_spec_create_post(self): + group_type = self.cinder_group_types.first() + create_url = reverse( + 'horizon:admin:group_types:specs:create', + args=[group_type.id]) + index_url = reverse( + 'horizon:admin:group_types:index') + + data = {'key': u'k1', + 'value': u'v1'} + + self.mock_group_type_spec_set.return_value = None + resp = self.client.post(create_url, data) + self.assertNoFormErrors(resp) + self.assertMessageCount(success=1) + self.assertRedirectsNoFollow(resp, index_url) + self.mock_group_type_spec_set.assert_called_once_with( + test.IsHttpRequest(), + group_type.id, + {data['key']: data['value']}) + + @test.create_mocks({api.cinder: ('group_type_get', )}) + def test_spec_create_get(self): + group_type = self.cinder_group_types.first() + create_url = reverse( + 'horizon:admin:group_types:specs:create', + args=[group_type.id]) + + self.mock_group_type_get.return_value = group_type + resp = self.client.get(create_url) + self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed( + resp, 'admin/group_types/specs/create.html') + self.mock_group_type_get.assert_called_once_with( + test.IsHttpRequest(), group_type.id) + + @test.create_mocks({api.cinder: ('group_type_spec_list', + 'group_type_spec_set',)}) + def test_spec_edit(self): + group_type = self.cinder_group_types.first() + key = 'foo' + edit_url = reverse('horizon:admin:group_types:specs:edit', + args=[group_type.id, key]) + index_url = reverse('horizon:admin:group_types:index') + + data = {'value': u'v1'} + specs = {key: data['value']} + + self.mock_group_type_spec_list.return_value = specs + self.mock_group_type_spec_set.return_value = None + + resp = self.client.post(edit_url, data) + self.assertNoFormErrors(resp) + self.assertMessageCount(success=1) + self.assertRedirectsNoFollow(resp, index_url) + + self.mock_group_type_spec_list.assert_called_once_with( + test.IsHttpRequest(), group_type.id, raw=True) + self.mock_group_type_spec_set.assert_called_once_with( + test.IsHttpRequest(), group_type.id, specs) + + @test.create_mocks({api.cinder: ('group_type_spec_list', + 'group_type_spec_unset')}) + def test_spec_delete(self): + group_type = self.cinder_group_types.first() + specs = [api.cinder.GroupTypeSpec(group_type.id, 'k1', 'v1')] + formData = {'action': 'specs__delete__k1'} + index_url = reverse('horizon:admin:group_types:specs:index', + args=[group_type.id]) + + self.mock_group_type_spec_list.return_value = specs + self.mock_group_type_spec_unset.return_value = group_type + + res = self.client.post(index_url, formData) + + redirect = reverse('horizon:admin:group_types:index') + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, redirect) + + self.mock_group_type_spec_list.assert_called_once_with( + test.IsHttpRequest(), group_type.id) + self.mock_group_type_spec_unset.assert_called_once_with( + test.IsHttpRequest(), group_type.id, ['k1']) diff --git a/openstack_dashboard/dashboards/admin/group_types/specs/urls.py b/openstack_dashboard/dashboards/admin/group_types/specs/urls.py new file mode 100644 index 0000000000..1b26608dcb --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/specs/urls.py @@ -0,0 +1,22 @@ +# 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 url + +from openstack_dashboard.dashboards.admin.group_types.specs \ + import views + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^create/$', views.CreateView.as_view(), name='create'), + url(r'^(?P[^/]+)/edit/$', views.EditView.as_view(), name='edit'), +] diff --git a/openstack_dashboard/dashboards/admin/group_types/specs/views.py b/openstack_dashboard/dashboards/admin/group_types/specs/views.py new file mode 100644 index 0000000000..e3c1853971 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/specs/views.py @@ -0,0 +1,125 @@ +# 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.urls import reverse +from django.urls import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import tables + +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.group_types.specs \ + import forms as admin_forms +from openstack_dashboard.dashboards.admin.group_types.specs \ + import tables as admin_tables + + +class GroupTypeSpecMixin(object): + def get_context_data(self, **kwargs): + context = super(GroupTypeSpecMixin, self).get_context_data(**kwargs) + try: + context['group_type'] = api.cinder.group_type_get( + self.request, self.kwargs['type_id']) + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve group type details.")) + if 'key' in self.kwargs: + context['key'] = self.kwargs['key'] + return context + + +class IndexView(GroupTypeSpecMixin, forms.ModalFormMixin, tables.DataTableView): + table_class = admin_tables.GroupTypeSpecsTable + template_name = 'admin/group_types/specs/index.html' + + def get_data(self): + try: + group_type_id = self.kwargs['type_id'] + specs_list = api.cinder.group_type_spec_list(self.request, + group_type_id) + specs_list.sort(key=lambda es: (es.key,)) + except Exception: + specs_list = [] + exceptions.handle(self.request, + _('Unable to retrieve group type spec list.')) + return specs_list + + +class CreateView(GroupTypeSpecMixin, forms.ModalFormView): + form_class = admin_forms.CreateSpec + form_id = "group_type_spec_create_form" + modal_header = _("Create Group Type Spec") + modal_id = "group_type_spec_create_modal" + submit_label = _("Create") + submit_url = "horizon:admin:group_types:specs:create" + template_name = 'admin/group_types/specs/create.html' + success_url = 'horizon:admin:group_types:index' + cancel_url = reverse_lazy('horizon:admin:group_types:index') + + def get_initial(self): + return {'group_type_id': self.kwargs['type_id']} + + def get_success_url(self): + return reverse(self.success_url) + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + args = (self.kwargs['type_id'],) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + +class EditView(GroupTypeSpecMixin, forms.ModalFormView): + form_class = admin_forms.EditSpec + form_id = "group_type_spec_edit_form" + modal_header = _('Edit Group Type Spec Value: %s') + modal_id = "group_type_spec_edit_modal" + submit_label = _("Save") + submit_url = "horizon:admin:group_types:specs:edit" + template_name = 'admin/group_types/specs/edit.html' + success_url = 'horizon:admin:group_types:index' + cancel_url = reverse_lazy('horizon:admin:group_types:index') + + def get_success_url(self): + return reverse(self.success_url) + + def get_initial(self): + group_type_id = self.kwargs['type_id'] + key = self.kwargs['key'] + try: + group_specs = api.cinder.group_type_spec_list(self.request, + group_type_id, + raw=True) + except Exception: + group_specs = {} + exceptions.handle(self.request, + _('Unable to retrieve group type spec ' + 'details.')) + return {'group_type_id': group_type_id, + 'key': key, + 'value': group_specs.get(key, '')} + + def get_context_data(self, **kwargs): + context = super(EditView, self).get_context_data(**kwargs) + args = (self.kwargs['type_id'], self.kwargs['key'],) + context['submit_url'] = reverse(self.submit_url, args=args) + context['modal_header'] = self.modal_header % self.kwargs['key'] + return context + + def form_invalid(self, form): + context = super(EditView, self).get_context_data() + context = self._populate_context(context) + context['form'] = form + context['modal_header'] = self.modal_header % self.kwargs['key'] + return self.render_to_response(context) diff --git a/openstack_dashboard/dashboards/admin/group_types/tables.py b/openstack_dashboard/dashboards/admin/group_types/tables.py index 29eab0c2f9..86792d1179 100644 --- a/openstack_dashboard/dashboards/admin/group_types/tables.py +++ b/openstack_dashboard/dashboards/admin/group_types/tables.py @@ -41,6 +41,15 @@ class EditGroupType(tables.LinkAction): policy_rules = (("volume", "group:group_types_manage"),) +class GroupTypeSpecs(tables.LinkAction): + name = "specs" + verbose_name = _("View Specs") + url = "horizon:admin:group_types:specs:index" + classes = ("ajax-modal",) + icon = "pencil" + policy_rules = (("volume", "group:group_types_manage"),) + + class GroupTypesFilterAction(tables.FilterAction): def filter(self, table, group_types, filter_string): @@ -118,6 +127,7 @@ class GroupTypesTable(tables.DataTable): DeleteGroupType, ) row_actions = ( + GroupTypeSpecs, EditGroupType, DeleteGroupType ) diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_create.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_create.html new file mode 100644 index 0000000000..e7cb7baa14 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_create.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans 'Create a new "group spec" key-value pair for a group type.' %}

+{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_edit.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_edit.html new file mode 100644 index 0000000000..d4aef0c62b --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_edit.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% blocktrans with key=key %}Update the "group spec" value for "{{ key }}"{% endblocktrans %}

+{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_index.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_index.html new file mode 100644 index 0000000000..8332095e93 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/_index.html @@ -0,0 +1,13 @@ +{% extends "horizon/common/_modal.html" %} +{% load i18n %} + +{% block modal_id %}group_type_specs_modal{% endblock %} +{% block modal-header %}{% trans "Group Type Specs" %}{% endblock %} + +{% block modal-body %} + {{ table.render }} +{% endblock %} + +{% block modal-footer %} + {% trans "Close" %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/create.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/create.html new file mode 100644 index 0000000000..1c4daed184 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/create.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Create Group Type Spec" %}{% endblock %} + +{% block page_header %} +

+ {% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }} {% endblocktrans %} +

+{% endblock page_header %} + +{% block main %} + {% include "admin/group_types/specs/_create.html" %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/edit.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/edit.html new file mode 100644 index 0000000000..e60eb1fc40 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/edit.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Edit Group Type Spec" %}{% endblock %} + +{% block page_header %} +

+ {% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }} {% endblocktrans %} +

+{% endblock page_header %} + +{% block main %} + {% include "admin/group_types/specs/_edit.html" %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/index.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/index.html new file mode 100644 index 0000000000..d2c474ee0d --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/specs/index.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Group Type Specs" %}{% endblock %} + +{% block page_header %} +

{% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }}{% endblocktrans %}

+{% endblock page_header %} + +{% block main %} + {% include "admin/group_types/specs/_index.html" %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/group_types/urls.py b/openstack_dashboard/dashboards/admin/group_types/urls.py index e1193d1fd9..543b86e45a 100644 --- a/openstack_dashboard/dashboards/admin/group_types/urls.py +++ b/openstack_dashboard/dashboards/admin/group_types/urls.py @@ -10,8 +10,11 @@ # 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.admin.group_types.specs \ + import urls as specs_urls from openstack_dashboard.dashboards.admin.group_types \ import views @@ -23,4 +26,6 @@ urlpatterns = [ url(r'^(?P[^/]+)/update_type/$', views.EditGroupTypeView.as_view(), name='update_type'), + url(r'^(?P[^/]+)/specs/', + include((specs_urls, 'specs'))), ] diff --git a/releasenotes/notes/generic-volume-groups-c0bf175f5d7d3a37.yaml b/releasenotes/notes/generic-volume-groups-c0bf175f5d7d3a37.yaml index 653db0ac1c..9533d6eae2 100644 --- a/releasenotes/notes/generic-volume-groups-c0bf175f5d7d3a37.yaml +++ b/releasenotes/notes/generic-volume-groups-c0bf175f5d7d3a37.yaml @@ -3,4 +3,7 @@ features: - | [:blueprint:`cinder-generic-volume-groups`] Cinder generic groups is now supported for admin panel. - Admin is now able to view all groups for differenet users. + Admin is now able to view all groups and group snapshots + for differenet users. + Also group-type and group-type-spec support added to admin panel. + Admin is able to create group-type and group-type-spec now.