Quota Update Panel
Allows viewing of current quota value, quota sizes and percentage of use of current quota. Allows changing of each region's quota. Change-Id: Ia9f254ffb905b4e8971d84f85aefad164e8a3438
This commit is contained in:
parent
502e6db24d
commit
519c30d185
@ -17,10 +17,12 @@ import json
|
||||
import logging
|
||||
import requests
|
||||
from six.moves.urllib.parse import urljoin
|
||||
import six
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from horizon.utils import functions as utils
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
@ -37,6 +39,68 @@ TASK = collections.namedtuple('Task',
|
||||
'created_on', 'approved_on', 'page',
|
||||
'completed_on', 'actions', 'status'])
|
||||
|
||||
QUOTA_SIZE = collections.namedtuple('QuotaSize',
|
||||
['id', 'name', 'cinder',
|
||||
'nova', 'neutron'])
|
||||
|
||||
REGION_QUOTA = collections.namedtuple('RegionQuota',
|
||||
['id', 'region',
|
||||
'quota_size', 'preapproved_quotas'])
|
||||
|
||||
REGION_QUOTA_VALUE = collections.namedtuple('RegionQuotaValue',
|
||||
['id', 'name',
|
||||
'service', 'current_quota',
|
||||
'current_usage', 'percent',
|
||||
'size_blob', 'important'])
|
||||
|
||||
SIZE_QUOTA_VALUE = collections.namedtuple('SizeQuotaValue',
|
||||
['id', 'name', 'service',
|
||||
'value', 'current_quota',
|
||||
'current_usage', 'percent'])
|
||||
|
||||
QUOTA_TASK = collections.namedtuple(
|
||||
'QuotaTask',
|
||||
['id', 'regions', 'size', 'user', 'created', 'valid', 'status'])
|
||||
|
||||
|
||||
# NOTE(amelia): A list of quota names that we consider to be the most
|
||||
# relevant to customers to be shown initially on the update page.
|
||||
# These can be overriden in the local_settings file:
|
||||
# IMPORTANT_QUOTAS = {<service>: [<quota_name>], }
|
||||
DEFAULT_IMPORTANT_QUOTAS = {
|
||||
'nova': [
|
||||
'instances', 'cores', 'ram',
|
||||
],
|
||||
'cinder': [
|
||||
'volumes', 'snapshots', 'gigabytes',
|
||||
],
|
||||
'neutron': [
|
||||
'network', 'floatingip', 'router', 'security_group',
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# NOTE(adriant): Quotas that should be hidden by default.
|
||||
# Can be overriden in the local_settings file by setting:
|
||||
# HIDDEN_QUOTAS = {<service>: [<quota_name>], }
|
||||
# or disabled entirely with: HIDDEN_QUOTAS = {}
|
||||
DEFAULT_HIDDEN_QUOTAS = {
|
||||
# these values have long since been deprecated from Nova
|
||||
'nova': [
|
||||
'security_groups', 'security_group_rules',
|
||||
'floating_ips', 'fixed_ips',
|
||||
],
|
||||
# these by default have no limit
|
||||
'cinder': [
|
||||
'per_volume_gigabytes', 'volumes_lvmdriver-1',
|
||||
'gigabytes_lvmdriver-1', 'snapshots_lvmdriver-1',
|
||||
|
||||
],
|
||||
'neutron': [
|
||||
'subnetpool',
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _get_endpoint_url(request):
|
||||
# If the request is made by an anonymous user, this endpoint request fails.
|
||||
@ -287,12 +351,11 @@ def task_list(request, filters={}, page=1):
|
||||
more = resp['has_more']
|
||||
for task in resp['tasks']:
|
||||
tasklist.append(task_obj_get(request, task=task, page=page))
|
||||
return tasklist, prev, more
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
raise
|
||||
|
||||
return tasklist, prev, more
|
||||
|
||||
|
||||
def task_get(request, task_id):
|
||||
# Get a single task
|
||||
@ -364,3 +427,190 @@ def task_revalidate(request, task_id):
|
||||
data.update(action_data)
|
||||
|
||||
return task_update(request, task_id, json.dumps(data))
|
||||
|
||||
|
||||
# Quota management functions
|
||||
def _is_quota_hidden(service, resource):
|
||||
hidden_quotas = getattr(settings, 'HIDDEN_QUOTAS', None)
|
||||
if hidden_quotas is None:
|
||||
hidden_quotas = DEFAULT_HIDDEN_QUOTAS
|
||||
return service in hidden_quotas and resource in hidden_quotas[service]
|
||||
|
||||
|
||||
def _is_quota_important(service, resource):
|
||||
important_quotas = getattr(settings, 'IMPORTANT_QUOTAS', None)
|
||||
if important_quotas is None:
|
||||
important_quotas = DEFAULT_IMPORTANT_QUOTAS
|
||||
return (
|
||||
service in important_quotas and resource in important_quotas[service])
|
||||
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_quota_information(request, regions=None):
|
||||
headers = {'Content-Type': 'application/json',
|
||||
'X-Auth-Token': request.user.token.id}
|
||||
params = {}
|
||||
if regions:
|
||||
params = {'regions': regions}
|
||||
try:
|
||||
return get(request, 'openstack/quotas/',
|
||||
params=params, headers=headers).json()
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
raise
|
||||
|
||||
|
||||
def quota_sizes_get(request, region=None):
|
||||
# Gets the list of quota sizes, and a json blob defining what they
|
||||
# have for each of the services
|
||||
# Region param is useless here, but nedded for memoized decorator to work
|
||||
quota_sizes_dict = {}
|
||||
|
||||
resp = _get_quota_information(request, regions=region)
|
||||
|
||||
for size_name, size in six.iteritems(resp['quota_sizes']):
|
||||
quota_sizes_dict[size_name] = QUOTA_SIZE(
|
||||
id=size_name,
|
||||
name=size_name,
|
||||
cinder=json.dumps(size['cinder'], indent=1),
|
||||
nova=json.dumps(size['nova'], indent=1),
|
||||
neutron=json.dumps(size['neutron'], indent=1),
|
||||
)
|
||||
|
||||
quota_sizes = []
|
||||
for size in resp['quota_size_order']:
|
||||
quota_sizes.append(quota_sizes_dict[size])
|
||||
|
||||
return quota_sizes
|
||||
|
||||
|
||||
def size_details_get(request, size, region=None):
|
||||
""" Gets the current details of the size as well as the current region's
|
||||
quota
|
||||
"""
|
||||
quota_details = []
|
||||
|
||||
if not region:
|
||||
region = request.user.services_region
|
||||
resp = _get_quota_information(request, regions=region)
|
||||
|
||||
data = resp['quota_sizes'][size]
|
||||
region_data = resp['regions'][0]['current_quota']
|
||||
for service, values in six.iteritems(data):
|
||||
for resource, value in six.iteritems(values):
|
||||
if _is_quota_hidden(service, resource):
|
||||
continue
|
||||
|
||||
usage = resp['regions'][0]['current_usage'][service].get(
|
||||
resource)
|
||||
try:
|
||||
percent = float(usage)/value
|
||||
except TypeError:
|
||||
percent = '-'
|
||||
|
||||
quota_details.append(
|
||||
SIZE_QUOTA_VALUE(
|
||||
id=resource,
|
||||
name=resource,
|
||||
service=service,
|
||||
value=value,
|
||||
current_quota=region_data[service][resource],
|
||||
current_usage=usage,
|
||||
percent=percent
|
||||
)
|
||||
)
|
||||
return quota_details
|
||||
|
||||
|
||||
def quota_details_get(request, region):
|
||||
quota_details = []
|
||||
|
||||
resp = _get_quota_information(request, regions=region)
|
||||
|
||||
data = resp['regions'][0]['current_quota']
|
||||
|
||||
for service, values in six.iteritems(data):
|
||||
for name, value in six.iteritems(values):
|
||||
if _is_quota_hidden(service, name):
|
||||
continue
|
||||
|
||||
if value < 0:
|
||||
value = 'No Limit'
|
||||
usage = resp['regions'][0]['current_usage'][service].get(name)
|
||||
try:
|
||||
percent = float(usage)/value
|
||||
except TypeError:
|
||||
percent = '-'
|
||||
|
||||
size_blob = {}
|
||||
for size_name, size_data in resp['quota_sizes'].iteritems():
|
||||
size_blob[size_name] = size_data[service].get(name, '-')
|
||||
|
||||
if name != 'id':
|
||||
quota_details.append(
|
||||
REGION_QUOTA_VALUE(
|
||||
id=name,
|
||||
name=name,
|
||||
service=service,
|
||||
current_quota=value,
|
||||
current_usage=usage,
|
||||
percent=percent,
|
||||
size_blob=size_blob,
|
||||
important=_is_quota_important(service, name)
|
||||
)
|
||||
)
|
||||
return quota_details
|
||||
|
||||
|
||||
def region_quotas_get(request, region=None):
|
||||
quota_details = []
|
||||
|
||||
resp = _get_quota_information(request, regions=region)
|
||||
|
||||
data = resp['regions']
|
||||
for region_values in data:
|
||||
quota_details.append(
|
||||
REGION_QUOTA(
|
||||
id=region_values['region'],
|
||||
region=region_values['region'],
|
||||
quota_size=region_values['current_quota_size'],
|
||||
preapproved_quotas=', '.join(region_values[
|
||||
'quota_change_options'])
|
||||
)
|
||||
)
|
||||
return quota_details
|
||||
|
||||
|
||||
def quota_tasks_get(request, region=None):
|
||||
# Region param only used to help with memoized decorator
|
||||
quota_tasks = []
|
||||
|
||||
resp = _get_quota_information(request, regions=region)
|
||||
|
||||
for task in resp['active_quota_tasks']:
|
||||
quota_tasks.append(
|
||||
QUOTA_TASK(
|
||||
id=task['id'],
|
||||
regions=', '.join(task['regions']),
|
||||
size=task['size'],
|
||||
user=task['request_user'],
|
||||
created=task['task_created'].split("T")[0],
|
||||
valid=task['valid'],
|
||||
status=task['status'],
|
||||
)
|
||||
)
|
||||
return quota_tasks
|
||||
|
||||
|
||||
def update_quotas(request, size, regions=[]):
|
||||
headers = {'Content-Type': 'application/json',
|
||||
'X-Auth-Token': request.user.token.id}
|
||||
data = {
|
||||
'size': size,
|
||||
}
|
||||
if regions:
|
||||
data['regions'] = regions
|
||||
|
||||
return post(request, 'openstack/quotas/',
|
||||
data=json.dumps(data),
|
||||
headers=headers)
|
||||
|
0
adjutant_ui/content/quota/__init__.py
Normal file
0
adjutant_ui/content/quota/__init__.py
Normal file
66
adjutant_ui/content/quota/forms.py
Normal file
66
adjutant_ui/content/quota/forms.py
Normal file
@ -0,0 +1,66 @@
|
||||
# Copyright 2016 Catalyst IT Ltd
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from adjutant_ui.api import adjutant
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateQuotaForm(forms.SelfHandlingForm):
|
||||
region = forms.CharField(label=_("Region"))
|
||||
region.widget.attrs['readonly'] = True
|
||||
size = forms.ChoiceField(label=_("Size"))
|
||||
size.widget.attrs['onchange'] = 'updateSizeTable()'
|
||||
|
||||
failure_url = 'horizon:management:quota:index'
|
||||
submit_url = 'horizon:management:quota:update'
|
||||
success_url = "horizon:management:quota:index"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
size_choices = kwargs.pop('size_choices')
|
||||
super(UpdateQuotaForm, self).__init__(*args, **kwargs)
|
||||
self.fields['size'].choices = size_choices
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
response = adjutant.update_quotas(request, data['size'],
|
||||
regions=[data['region']])
|
||||
if response.status_code == 200:
|
||||
messages.success(request, _('Quota updated sucessfully.'))
|
||||
elif response.status_code == 202:
|
||||
messages.success(request, _('Task created but requires '
|
||||
'admin approval.'))
|
||||
elif response.status_code == 400:
|
||||
messages.error(request, _('Failed to update quota. You may'
|
||||
' have usage over the new values '
|
||||
'that you are attempting to update'
|
||||
' the quota to.'))
|
||||
else:
|
||||
messages.error(request, _('Failed to update quota.'))
|
||||
return True
|
||||
except Exception:
|
||||
msg = _('Failed to update quota.')
|
||||
url = reverse('horizon:management:quota:index')
|
||||
exceptions.handle(request, msg, redirect=url)
|
||||
return False
|
23
adjutant_ui/content/quota/panel.py
Normal file
23
adjutant_ui/content/quota/panel.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2016 Catalyst IT Ltd.
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class QuotaPanel(horizon.Panel):
|
||||
name = _("Quota Managment")
|
||||
slug = 'quota'
|
||||
policy_rules = (('identity', "identity:project_mod_or_admin"),)
|
189
adjutant_ui/content/quota/tables.py
Normal file
189
adjutant_ui/content/quota/tables.py
Normal file
@ -0,0 +1,189 @@
|
||||
# Copyright 2016 Catalyst IT Ltd
|
||||
#
|
||||
# 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 json
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard.dashboards.admin.defaults.tables import get_quota_name
|
||||
|
||||
from adjutant_ui.api import adjutant
|
||||
|
||||
|
||||
def to_caps(value):
|
||||
return value.title()
|
||||
|
||||
|
||||
def display_as_percent(value):
|
||||
if value == "-":
|
||||
return value
|
||||
return '{:.1%}'.format(value)
|
||||
|
||||
|
||||
def service_name(value):
|
||||
# Takes service names and returns a 'nice' name of where they
|
||||
# are from
|
||||
service_name_dict = {'cinder': 'Volume Storage',
|
||||
'neutron': 'Networking',
|
||||
'nova': 'Compute'}
|
||||
return service_name_dict.get(value, value)
|
||||
|
||||
|
||||
class UpdateQuota(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Update Quota")
|
||||
url = "horizon:management:quota:update"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "edit"
|
||||
|
||||
|
||||
class CancelQuotaTask(tables.DeleteAction):
|
||||
help_text = _("This will cancel the selected quota update.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Cancel Quota Update",
|
||||
u"Cancel Quota Updates",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Cancelled Quota Update",
|
||||
u"Cancelled Quota Updates",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
result = adjutant.task_cancel(request, obj_id)
|
||||
if not result or result.status_code != 200:
|
||||
exception = exceptions.NotAvailable()
|
||||
exception._safe_message = False
|
||||
raise exception
|
||||
|
||||
def allowed(self, request, task=None):
|
||||
if task:
|
||||
return task.status == "Awaiting Approval"
|
||||
return True
|
||||
|
||||
|
||||
class ViewRegion(tables.LinkAction):
|
||||
name = "view_region"
|
||||
verbose_name = _("View Region")
|
||||
url = "horizon:management:quota:region_detail"
|
||||
|
||||
|
||||
class ViewSize(tables.LinkAction):
|
||||
name = "view_size"
|
||||
verbose_name = _("View Size")
|
||||
url = "horizon:management:quota:size_detail"
|
||||
|
||||
|
||||
class UpdateQuotaRow(tables.Row):
|
||||
def load_cells(self, resource=None):
|
||||
super(UpdateQuotaRow, self).load_cells(resource)
|
||||
resource = self.datum
|
||||
if resource.important is False:
|
||||
self.attrs['hide'] = True
|
||||
self.attrs['style'] = 'display: none;'
|
||||
|
||||
self.attrs['size_blob'] = json.dumps(self.datum.size_blob)
|
||||
|
||||
|
||||
class RegionQuotaDetailTable(tables.DataTable):
|
||||
service = tables.Column("service", verbose_name=_("Service"),
|
||||
filters=(service_name, ))
|
||||
name = tables.Column(get_quota_name, verbose_name=_("Resource Name"),)
|
||||
value = tables.Column("current_quota", verbose_name=_("Resource Quota"), )
|
||||
usage = tables.Column("current_usage", verbose_name=_("Current Usage"))
|
||||
percent = tables.Column("percent", verbose_name=_("Percentage of Use"),
|
||||
filters=(display_as_percent, ))
|
||||
|
||||
|
||||
class QuotaDetailUsageTable(tables.DataTable):
|
||||
service = tables.Column("service", verbose_name=_("Service"),
|
||||
filters=(service_name, ))
|
||||
name = tables.Column(get_quota_name, verbose_name=_("Resource Name"),)
|
||||
value = tables.Column("value", verbose_name=_("Quota Value"), )
|
||||
current_quota = tables.Column("current_quota",
|
||||
verbose_name=_("Current Quota "
|
||||
"(Current Region)"), )
|
||||
|
||||
|
||||
class RegionOverviewTable(tables.DataTable):
|
||||
region = tables.Column("region", verbose_name=_("Region Name"),
|
||||
link=("horizon:management:quota:region_detail"))
|
||||
quota_size = tables.Column("quota_size",
|
||||
verbose_name=_("Current Quota Size"),
|
||||
filters=(to_caps, ))
|
||||
preapproved_quotas = tables.Column(
|
||||
"preapproved_quotas", filters=(to_caps, ),
|
||||
verbose_name=_("Preapproved Quota Sizes"))
|
||||
|
||||
class Meta(object):
|
||||
name = "region_overview"
|
||||
row_actions = (UpdateQuota, ViewRegion)
|
||||
verbose_name = _("Current Quotas")
|
||||
hidden_title = False
|
||||
|
||||
|
||||
class QuotaTasksTable(tables.DataTable):
|
||||
quota_size = tables.Column(
|
||||
"size",
|
||||
verbose_name=_("Proposed Size"),
|
||||
filters=(to_caps, ))
|
||||
regions = tables.Column("regions", verbose_name=_("For Regions"))
|
||||
user = tables.Column("user", verbose_name=_("Requested By"))
|
||||
created = tables.Column("created", verbose_name=_("Requested On"))
|
||||
valid = tables.Column("valid", verbose_name=_("Valid"))
|
||||
stats = tables.Column("status", verbose_name=_("Status"))
|
||||
|
||||
class Meta(object):
|
||||
name = "quota_tasks"
|
||||
row_actions = (CancelQuotaTask, )
|
||||
verbose_name = _("Previous Quota Changes")
|
||||
hidden_title = False
|
||||
|
||||
|
||||
class SizeOverviewTable(tables.DataTable):
|
||||
id = tables.Column("id", hidden=True)
|
||||
size = tables.Column("name", verbose_name=_("Size Name"),
|
||||
filters=(to_caps, ))
|
||||
|
||||
class Meta(object):
|
||||
name = "size_overview"
|
||||
row_actions = (ViewSize, )
|
||||
verbose_name = _("Quota Sizes")
|
||||
hidden_title = False
|
||||
|
||||
|
||||
class ChangeSizeDisplayTable(tables.DataTable):
|
||||
service = tables.Column("service", verbose_name=_("Service"),
|
||||
filters=(service_name, ),
|
||||
hidden=True)
|
||||
name = tables.Column(get_quota_name, verbose_name=_("Resource"),)
|
||||
current_quota = tables.Column("current_quota",
|
||||
verbose_name=_("Current Quota"), )
|
||||
usage = tables.Column("current_usage", verbose_name=_("Current Usage"))
|
||||
value = tables.Column("value", verbose_name=_("New Quota Value"), )
|
||||
|
||||
class Meta(object):
|
||||
name = 'change_size'
|
||||
row_class = UpdateQuotaRow
|
18
adjutant_ui/content/quota/templates/quota/_index_help.html
Normal file
18
adjutant_ui/content/quota/templates/quota/_index_help.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class=quota-help>
|
||||
<p>{% blocktrans trimmed %}
|
||||
Your current quotas are avaliable here, and can be changed to suit your needs.
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
<p>{% blocktrans trimmed %}
|
||||
Certain types of quota changes, such as changing your quota more than once
|
||||
in a given period, or changing your quota by large amounts will require admin
|
||||
approval. The period and the quota sizes themselves is configured by your
|
||||
admin, with the default period being 30 days.
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
<p>{% blocktrans trimmed %}
|
||||
If your proposed change needed approval, you will be emailed on completion.
|
||||
{% endblocktrans %}</p>
|
||||
</div>
|
62
adjutant_ui/content/quota/templates/quota/_update.html
Normal file
62
adjutant_ui/content/quota/templates/quota/_update.html
Normal file
@ -0,0 +1,62 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_quota_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:management:quota:update' region.id %}{% endblock %}
|
||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a id="toggle_link" onclick='toggleDisplayTable()'>{% trans 'Display all quotas' %}</a>
|
||||
{{ change_size_table.render }}
|
||||
<script>
|
||||
var select = document.getElementById('id_size')
|
||||
var toggle_link = document.getElementById('toggle_link')
|
||||
var show_all = false;
|
||||
|
||||
var table = document.getElementById("change_size");
|
||||
var count_headings = table.getElementsByClassName("table_count");
|
||||
|
||||
for (i=0; i<count_headings.length; i++){
|
||||
count_headings[i].parentElement.parentElement.style.display = 'none';
|
||||
}
|
||||
|
||||
function updateSizeTable(){
|
||||
var current_size = select.options[select.selectedIndex].value;
|
||||
var rows = document.getElementById("change_size").tBodies[0].rows;
|
||||
for (i=1; i < rows.length; i++){
|
||||
var cell = rows[i].cells[4];
|
||||
var value_dict = $.parseJSON(rows[i].getAttribute('size_blob'));
|
||||
cell.innerHTML = value_dict[current_size]
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDisplayTable(){
|
||||
show_all = !show_all;
|
||||
|
||||
if(show_all){
|
||||
toggle_link.innerHTML = "{% trans 'Hide some rows' %}";
|
||||
}else{
|
||||
toggle_link.innerHTML = "{% trans 'Display all quotas' %}";
|
||||
}
|
||||
|
||||
var tr = document.getElementById("change_size").tBodies[0].rows;
|
||||
for (i=0; i<tr.length; i++){
|
||||
if (tr[i].getAttribute('hide') == ""){
|
||||
if (show_all){
|
||||
tr[i].style.display = '';
|
||||
} else {
|
||||
tr[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSizeTable()
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
27
adjutant_ui/content/quota/templates/quota/index.html
Normal file
27
adjutant_ui/content/quota/templates/quota/index.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Quotas" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Quotas") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="left">
|
||||
{% include 'management/quota/_index_help.html' %}
|
||||
<div id="region-overview">
|
||||
{{ region_overview_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="quota-tasks-overview">
|
||||
{{ quota_tasks_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
<div id="size-overview">
|
||||
{{ size_overview_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %} {% trans "Quota Details" %} {% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
11
adjutant_ui/content/quota/templates/quota/size_detail.html
Normal file
11
adjutant_ui/content/quota/templates/quota/size_detail.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %} {{title}} - Quota Details{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=title %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
6
adjutant_ui/content/quota/templates/quota/update.html
Normal file
6
adjutant_ui/content/quota/templates/quota/update.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Quota" %}{% endblock %}
|
||||
{% block main %}
|
||||
{% include 'management/quota/_update.html' %}
|
||||
{% endblock %}
|
28
adjutant_ui/content/quota/urls.py
Normal file
28
adjutant_ui/content/quota/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2016 Catalyst IT Ltd.
|
||||
#
|
||||
# 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 adjutant_ui.content.quota import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^region/(?P<region>[^/]+)$', views.RegionDetailView.as_view(),
|
||||
name='region_detail'),
|
||||
url(r'^(?P<region>[^/]+)/update$',
|
||||
views.RegionUpdateView.as_view(), name='update'),
|
||||
url(r'^size/(?P<size>[^/]+)$', views.QuotaSizeView.as_view(),
|
||||
name='size_detail'),
|
||||
]
|
159
adjutant_ui/content/quota/views.py
Normal file
159
adjutant_ui/content/quota/views.py
Normal file
@ -0,0 +1,159 @@
|
||||
# Copyright (c) 2016 Catalyst IT Ltd.
|
||||
#
|
||||
# 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
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables as horizon_tables
|
||||
|
||||
from adjutant_ui.content.quota import forms as quota_forms
|
||||
from adjutant_ui.content.quota import tables as quota_tables
|
||||
from adjutant_ui.api import adjutant
|
||||
|
||||
|
||||
class IndexView(horizon_tables.MultiTableView):
|
||||
page_title = _("Quota Management")
|
||||
table_classes = (quota_tables.RegionOverviewTable,
|
||||
quota_tables.SizeOverviewTable,
|
||||
quota_tables.QuotaTasksTable)
|
||||
template_name = 'management/quota/index.html'
|
||||
|
||||
def get_region_overview_data(self):
|
||||
try:
|
||||
return adjutant.region_quotas_get(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Failed to list quota sizes.'))
|
||||
return []
|
||||
|
||||
def get_size_overview_data(self):
|
||||
try:
|
||||
return adjutant.quota_sizes_get(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Failed to list quota sizes.'))
|
||||
return []
|
||||
|
||||
def get_quota_tasks_data(self):
|
||||
try:
|
||||
return adjutant.quota_tasks_get(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Failed to list quota tasks.'))
|
||||
return []
|
||||
|
||||
|
||||
class RegionDetailView(horizon_tables.DataTableView):
|
||||
table_class = quota_tables.RegionQuotaDetailTable
|
||||
template_name = 'management/quota/region_detail.html'
|
||||
page_title = _("'{{ region }}' Quota Details")
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
return adjutant.quota_details_get(self.request,
|
||||
self.kwargs['region'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Failed to list quota sizes.'))
|
||||
return []
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(RegionDetailView, self).get_context_data(**kwargs)
|
||||
context['region'] = self.kwargs['region']
|
||||
return context
|
||||
|
||||
|
||||
class QuotaSizeView(horizon_tables.DataTableView):
|
||||
table_class = quota_tables.QuotaDetailUsageTable
|
||||
template_name = 'management/quota/size_detail.html'
|
||||
page_title = _("'{{ size }}' Quota Details")
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
return adjutant.size_details_get(self.request,
|
||||
size=self.kwargs['size'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Failed to list quota size.'))
|
||||
return []
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# request.user.services_region
|
||||
context = super(QuotaSizeView, self).get_context_data(**kwargs)
|
||||
context['title'] = _("%s - Quota Details") \
|
||||
% self.kwargs['size'].title()
|
||||
return context
|
||||
|
||||
|
||||
class RegionUpdateView(forms.ModalFormView, horizon_tables.MultiTableView):
|
||||
form_class = quota_forms.UpdateQuotaForm
|
||||
table_classes = (quota_tables.ChangeSizeDisplayTable, )
|
||||
submit_url = 'horizon:management:quota:update'
|
||||
context_object_name = 'ticket'
|
||||
template_name = 'management/quota/update.html'
|
||||
success_url = reverse_lazy("horizon:management:quota:index")
|
||||
page_title = _("Update Quota")
|
||||
|
||||
def get_change_size_data(self):
|
||||
try:
|
||||
return adjutant.quota_details_get(self.request,
|
||||
region=self.kwargs['region'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Failed to list quota sizes.'))
|
||||
return []
|
||||
|
||||
def get_object(self):
|
||||
return adjutant.region_quotas_get(self.request,
|
||||
region=self.kwargs['region'])[0]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(RegionUpdateView, self).get_context_data(**kwargs)
|
||||
context['region'] = self.get_object()
|
||||
args = (self.kwargs['region'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
context['form'] = self.get_form()
|
||||
return context
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(RegionUpdateView, self).get_form_kwargs()
|
||||
sizes = adjutant.quota_sizes_get(
|
||||
self.request, region=self.kwargs['region'])
|
||||
kwargs['size_choices'] = []
|
||||
|
||||
region = self.get_object()
|
||||
for size in sizes:
|
||||
if region.quota_size == size.name:
|
||||
continue
|
||||
if size.name not in region.preapproved_quotas:
|
||||
kwargs['size_choices'].append(
|
||||
[size.id, "%s (requires approval)" % size.name.title()])
|
||||
else:
|
||||
kwargs['size_choices'].append([size.id, size.name.title()])
|
||||
return kwargs
|
||||
|
||||
def get_initial(self):
|
||||
region = self.get_object()
|
||||
data = {'id': region.id,
|
||||
'region': region.region,
|
||||
'quota_size': region.quota_size,
|
||||
'preapproved_quotas': region.preapproved_quotas
|
||||
}
|
||||
return data
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
# NOTE(amelia): The multitableview overides the form view post
|
||||
# this reinstates it.
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
9
adjutant_ui/enabled/_6080_management_quota.py
Normal file
9
adjutant_ui/enabled/_6080_management_quota.py
Normal file
@ -0,0 +1,9 @@
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'quota'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'management'
|
||||
|
||||
PANEL_GROUP = 'default'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'adjutant_ui.content.quota.panel.QuotaPanel'
|
Loading…
x
Reference in New Issue
Block a user