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
This commit is contained in:
parent
8bc497a5e3
commit
4b523122b0
@ -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
|
||||
|
@ -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)
|
@ -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
|
141
openstack_dashboard/dashboards/admin/group_types/specs/tests.py
Normal file
141
openstack_dashboard/dashboards/admin/group_types/specs/tests.py
Normal file
@ -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'])
|
@ -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<key>[^/]+)/edit/$', views.EditView.as_view(), name='edit'),
|
||||
]
|
125
openstack_dashboard/dashboards/admin/group_types/specs/views.py
Normal file
125
openstack_dashboard/dashboards/admin/group_types/specs/views.py
Normal file
@ -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)
|
@ -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
|
||||
)
|
||||
|
@ -0,0 +1,7 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans 'Create a new "group spec" key-value pair for a group type.' %}</p>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% blocktrans with key=key %}Update the "group spec" value for "{{ key }}"{% endblocktrans %}</p>
|
||||
{% endblock %}
|
@ -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 %}
|
||||
<a href="{% url 'horizon:admin:group_types:index' %}" class="btn btn-default cancel">{% trans "Close" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,14 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Create Group Type Spec" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<h2>
|
||||
{% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }} {% endblocktrans %}
|
||||
</h2>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/group_types/specs/_create.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,14 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Edit Group Type Spec" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<h2>
|
||||
{% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }} {% endblocktrans %}
|
||||
</h2>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/group_types/specs/_edit.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Group Type Specs" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<h2>{% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }}{% endblocktrans %}</h2>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/group_types/specs/_index.html" %}
|
||||
{% endblock %}
|
@ -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<type_id>[^/]+)/update_type/$',
|
||||
views.EditGroupTypeView.as_view(),
|
||||
name='update_type'),
|
||||
url(r'^(?P<type_id>[^/]+)/specs/',
|
||||
include((specs_urls, 'specs'))),
|
||||
]
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user