Add QuotaManagement Panel to Horizon.

Kingbird-dashboard is a horizon plugin and therefore this commit is to
add kingbird as a panel to the existing panels. The features in this commit
helps admin to update, delete and `sync quota` for tenants.

Change-Id: I44de3b61e336e0ecfbfb74b90e791ed4d9a25650
This commit is contained in:
Goutham Pratapa 2018-02-01 15:49:13 +05:30
parent 7e3536cb8b
commit f44da40ea5
46 changed files with 650 additions and 1726 deletions

View File

@ -1,138 +1,77 @@
# 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
# Copyright 2018 - Ericsson AB.
#
# http://www.apache.org/licenses/LICENSE-2.0
# 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
#
# 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.
# 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 import settings
from horizon.utils import memoized
import kingbirdclient
from kingbirdclient.api import client as kb_client
SERVICE_TYPE = 'synchronization'
from __future__ import absolute_import
import logging
# enable following after client product implemented.
# from kingbird_dashboardclient.v1 import client as kingbird_dashboard_client
from horizon import exceptions
from horizon.utils.memoized import memoized
from openstack_dashboard.api import base
# for stab, should remove when use CLI API
import copy
from datetime import datetime
import uuid
@memoized.memoized
def kingbird_dashboardclient(request):
"""Kingbird Client for API calls."""
return kb_client.client(
username=request.user.username,
auth_token=request.user.token.id,
project_id=request.user.tenant_id,
auth_url=getattr(settings, 'OPENSTACK_KEYSTONE_URL')
)
LOG = logging.getLogger(__name__)
ATTRIBUTES = ['name', 'description', 'enabled', 'size', 'temperature',
'base', 'flavor', 'topping']
STUB_DATA = {}
def list_defaults(request):
"""Default Quota Limits."""
return kingbird_dashboardclient(request).quota_manager.\
list_defaults()
# for stab, should be removed when use CLI API
class StubResponse(object):
def __init__(self, info):
self._info = info
def __repr__(self):
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
def to_dict(self):
return copy.deepcopy(self._info)
def global_limits(request, target_tenant_id):
"""Global Quota Limits for any tenant."""
return kingbird_dashboardclient(request).quota_manager.\
global_limits(target_tenant_id)
@memoized
def apiclient(request):
api_url = ""
c = None
def update_global_limits(request, target_tenant_id, **data):
"""Update Global Limits for a tenant."""
return kingbird_dashboardclient(request).quota_manager.\
update_global_limits(target_tenant_id, **data)
def sync_quota(request, target_tenant_id):
"""On Demand Quota Sync."""
return kingbird_dashboardclient(request).quota_manager.\
sync_quota(target_tenant_id)
def delete_quota(request, target_tenant_id):
"""Delete Quota for a tenant."""
try:
api_url = base.url_for(request, 'kingbird')
except exceptions.ServiceCatalogException:
LOG.debug('No Kingbird Management service is configured.')
return None
LOG.debug('kingbird_dashboardclient using the token "%s" and url'
'"%s"' % (request.user.token.id, api_url))
# enable following after client product implemented.
# c = kingbird_dashboard_client.Client(
# username=request.user.username,
# project_id=request.user.tenant_id,
# input_auth_token=request.user.token.id,
# api_url=api_url)
return c
kingbird_dashboardclient(request).quota_manager.\
delete_quota(target_tenant_id)
return True
except kingbirdclient.exceptions.APIException:
raise
def kingbird_create(request, **kwargs):
args = {}
for (key, value) in kwargs.items():
if key in ATTRIBUTES:
args[str(key)] = value
else:
raise exceptions.BadRequest(
"Key must be in %s" % ",".join(ATTRIBUTES))
# created = apiclient(request).kingbirds.create(**args)
# create dummy response
args["uuid"] = uuid.uuid1().hex
args["created_at"] = datetime.now().isoformat()
created = StubResponse(args)
for k in args:
setattr(created, k, args[k])
STUB_DATA[created.uuid] = created
return created
def kingbird_update(request, id, **kwargs):
args = {}
for (key, value) in kwargs.items():
if key in ATTRIBUTES:
args[str(key)] = value
else:
raise exceptions.BadRequest(
"Key must be in %s" % ",".join(ATTRIBUTES))
# updated = apiclient(request).kingbird.update(id, **args)
# update dummy response
args["uuid"] = id
args["updated_at"] = datetime.now().isoformat()
updated = StubResponse(args)
for k in args:
setattr(updated, k, args[k])
STUB_DATA[updated.uuid] = updated
return updated
def kingbird_delete(request, id):
# deleted = apiclient(request).kingbirds.delete(id)
deleted = STUB_DATA.pop(id)
return deleted
def kingbird_list(
request, limit=None, marker=None, sort_key=None,
sort_dir=None, detail=True):
# list = apiclient(request).Kingbirds.list(limit, marker, sort_key,
# sort_dir, detail)
list = [STUB_DATA[data] for data in STUB_DATA]
return list
def kingbird_show(request, id):
# show = apiclient(request).kingbirds.get(id)
show = STUB_DATA.get(id)
return show
def detail_quota(request, target_tenant_id):
"""Quota Information of a tenant."""
try:
return kingbird_dashboardclient(request).quota_manager.\
quota_detail(target_tenant_id)
except kingbirdclient.exceptions.APIException:
raise

View File

@ -1,86 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views import generic
from kingbird_dashboard.api import client
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
def change_to_id(obj):
"""Change key named 'uuid' to 'id'
API returns objects with a field called 'uuid' many of Horizons
directives however expect objects to have a field called 'id'.
"""
obj['id'] = obj.pop('uuid')
return obj
@urls.register
class Kingbird(generic.View):
"""API for retrieving a single Kingbird"""
url_regex = r'kingbird_dashboard/kingbirds/(?P<id>[^/]+)$'
@rest_utils.ajax()
def get(self, request, id):
"""Get a specific kingbird"""
return change_to_id(client.kingbird_show(request, id).to_dict())
@rest_utils.ajax(data_required=True)
def post(self, request, id):
"""Update a Kingbird.
Returns the updated Kingbird object on success.
"""
kingbird = client.kingbird_update(request, id, **request.DATA)
return rest_utils.CreatedResponse(
'/api/kingbird_dashboard/kingbird/%s' % kingbird.uuid,
kingbird.to_dict())
@urls.register
class Kingbirds(generic.View):
"""API for Kingbirds"""
url_regex = r'kingbird_dashboard/kingbirds/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of the Kingbirds for a project.
The returned result is an object with property 'items' and each
item under this is a Kingbird.
"""
result = client.kingbird_list(request)
return {'items': [change_to_id(n.to_dict()) for n in result]}
@rest_utils.ajax(data_required=True)
def delete(self, request):
"""Delete one or more Kingbirds by id.
Returns HTTP 204 (no content) on successful deletion.
"""
for id in request.DATA:
client.kingbird_delete(request, id)
@rest_utils.ajax(data_required=True)
def put(self, request):
"""Create a new Kingbird.
Returns the new Kingbird object on success.
"""
kingbird = client.kingbird_create(request, **request.DATA)
return rest_utils.CreatedResponse(
'/api/kingbird_dashboard/kingbird/%s' % kingbird.uuid,
kingbird.to_dict())

View File

@ -1,3 +1,5 @@
# Copyright 2018 Ericsson AB.
#
# 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
@ -11,13 +13,21 @@
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
# This panel will be loaded from horizon, because specified in enabled file.
# To register REST api, import below here.
from kingbird_dashboard.api import rest_api # noqa: F401
from kingbird_dashboard.default import panel
class Kingbirds(horizon.Panel):
name = _("Kingbirds")
slug = "kingbirds"
class Kingbird(horizon.Dashboard):
name = _("Kingbird")
slug = "kingbird"
panels = (
'default'
'resource management',
'quota management')
default_panel = "default"
horizon.register(Kingbird)
Kingbird.register(panel.Default)

View File

@ -0,0 +1,24 @@
# Copyright 2018 Ericsson AB.
#
# 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 Default(horizon.Panel):
name = _("Default")
slug = 'default'
urls = 'kingbird_dashboard.quota_management.urls'
nav = False

View File

@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% load i18n %}
{% block css %}
{% include "_stylesheets.html" %}
<link href='{{ STATIC_URL }}dashboard/kingbird_dashboard/css/style.css' type='text/css' media='screen' rel='stylesheet' />
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'kingbird/default/base.html' %}
{% block main %}
<div class="kingbird-wrapper">
{{ table.render }}
</div>
{% endblock %}

View File

@ -22,5 +22,10 @@ PANEL = 'quota_management'
# The slug of the dashboard the PANEL_GROUP associated with. Required.
PANEL_DASHBOARD = 'kingbird'
# Python panel class of the PANEL to be added
# Python panel class of the PANEL to be added.
ADD_PANEL = 'kingbird_dashboard.quota_management.panel.QuotaManagement'
# Static CSS files to be added to Kingbird.
ADD_SCSS_FILES = ['dashboard/kingbird_dashboard/css/style.css']
AUTO_DISCOVER_STATIC_FILES = True

View File

@ -0,0 +1,74 @@
# Copyright 2018 Ericsson AB.
#
# 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 functools
import inspect
import horizon.exceptions
def handle_errors(error_message, error_default=None, request_arg=None):
"""A decorator for adding default error handling to API calls.
It wraps the original method in a try-except block, with horizon's
error handling added.
Note: it should only be used on functions or methods that take request as
their argument (it has to be named "request", or ``request_arg`` has to be
provided, indicating which argument is the request).
The decorated method accepts a number of additional parameters:
:param _error_handle: whether to handle the errors in this call
:param _error_message: override the error message
:param _error_default: override the default value returned on error
:param _error_redirect: specify a redirect url for errors
:param _error_ignore: ignore known errors
"""
def decorator(func):
if request_arg is None:
_request_arg = 'request'
if _request_arg not in inspect.getargspec(func).args:
raise RuntimeError(
"The handle_errors decorator requires 'request' as "
"an argument of the function or method being decorated")
else:
_request_arg = request_arg
@functools.wraps(func)
def wrapper(*args, **kwargs):
_error_handle = kwargs.pop('_error_handle', True)
_error_message = kwargs.pop('_error_message', error_message)
_error_default = kwargs.pop('_error_default', error_default)
_error_redirect = kwargs.pop('_error_redirect', None)
_error_ignore = kwargs.pop('_error_ignore', False)
if not _error_handle:
return func(*args, **kwargs)
try:
return func(*args, **kwargs)
except Exception as e:
callargs = inspect.getcallargs(func, *args, **kwargs)
request = callargs[_request_arg]
_error_message += ': ' + str(e)
horizon.exceptions.handle(request, _error_message,
ignore=_error_ignore,
redirect=_error_redirect)
return _error_default
wrapper.wrapped = func
return wrapper
return decorator

View File

@ -1,151 +0,0 @@
/*
* 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.
*/
'use strict';
var fs = require('fs');
var path = require('path');
module.exports = function (config) {
// This tox venv is setup in the post-install npm step
var toxPath = '../.tox/py27/lib/python2.7/site-packages/';
config.set({
preprocessors: {
// Used to collect templates for preprocessing.
// NOTE: the templates must also be listed in the files section below.
'./static/**/*.html': ['ng-html2js'],
// Used to indicate files requiring coverage reports.
'./static/**/!(*.spec).js': ['coverage'],
},
// Sets up module to process templates.
ngHtml2JsPreprocessor: {
prependPrefix: '/',
moduleName: 'templates'
},
basePath: './',
// Contains both source and test files.
files: [
/*
* shim, partly stolen from /i18n/js/horizon/
* Contains expected items not provided elsewhere (dynamically by
* Django or via jasmine template.
*/
'../test-shim.js',
// from jasmine.html
toxPath + 'xstatic/pkg/jquery/data/jquery.js',
toxPath + 'xstatic/pkg/angular/data/angular.js',
toxPath + 'xstatic/pkg/angular/data/angular-route.js',
toxPath + 'xstatic/pkg/angular/data/angular-mocks.js',
toxPath + 'xstatic/pkg/angular/data/angular-cookies.js',
toxPath + 'xstatic/pkg/angular_bootstrap/data/angular-bootstrap.js',
toxPath + 'xstatic/pkg/angular_gettext/data/angular-gettext.js',
toxPath + 'xstatic/pkg/angular/data/angular-sanitize.js',
toxPath + 'xstatic/pkg/d3/data/d3.js',
toxPath + 'xstatic/pkg/rickshaw/data/rickshaw.js',
toxPath + 'xstatic/pkg/angular_smart_table/data/smart-table.js',
toxPath + 'xstatic/pkg/angular_lrdragndrop/data/lrdragndrop.js',
toxPath + 'xstatic/pkg/spin/data/spin.js',
toxPath + 'xstatic/pkg/spin/data/spin.jquery.js',
toxPath + 'xstatic/pkg/tv4/data/tv4.js',
toxPath + 'xstatic/pkg/objectpath/data/ObjectPath.js',
toxPath + 'xstatic/pkg/angular_schema_form/data/schema-form.js',
toxPath + 'xstatic/pkg/angular_fileupload/data/ng-file-upload.js',
// TODO: These should be mocked.
toxPath + '/horizon/static/horizon/js/horizon.js',
/**
* Include framework source code from horizon that we need.
* Otherwise, karma will not be able to find them when testing.
* These files should be mocked in the foreseeable future.
*/
toxPath + 'horizon/static/framework/**/*.module.js',
toxPath + 'horizon/static/framework/**/!(*.spec|*.mock).js',
toxPath + 'openstack_dashboard/static/**/*.module.js',
toxPath + 'openstack_dashboard/static/**/!(*.spec|*.mock).js',
toxPath + 'openstack_dashboard/dashboards/**/static/**/*.module.js',
toxPath + 'openstack_dashboard/dashboards/**/static/**/!(*.spec|*.mock).js',
/**
* First, list all the files that defines application's angular modules.
* Those files have extension of `.module.js`. The order among them is
* not significant.
*/
'./static/**/*.module.js',
/**
* Followed by other JavaScript files that defines angular providers
* on the modules defined in files listed above. And they are not mock
* files or spec files defined below. The order among them is not
* significant.
*/
'./static/**/!(*.spec|*.mock).js',
/**
* Then, list files for mocks with `mock.js` extension. The order
* among them should not be significant.
*/
toxPath + 'openstack_dashboard/static/**/*.mock.js',
/**
* Finally, list files for spec with `spec.js` extension. The order
* among them should not be significant.
*/
'./static/**/*.spec.js',
/**
* Angular external templates
*/
'./static/**/*.html'
],
autoWatch: true,
frameworks: ['jasmine'],
browsers: ['Chrome'],
browserNoActivityTimeout: 60000,
reporters: ['progress', 'coverage', 'threshold'],
plugins: [
'karma-chrome-launcher',
'karma-jasmine',
'karma-ng-html2js-preprocessor',
'karma-coverage',
'karma-threshold-reporter'
],
// Places coverage report in HTML format in the subdirectory below.
coverageReporter: {
type: 'html',
dir: '../cover/karma/'
},
// Coverage threshold values.
thresholdReporter: {
statements: 10, // target 100
branches: 0, // target 100
functions: 10, // target 100
lines: 10 // target 100
}
});
};

View File

@ -0,0 +1,119 @@
# Copyright 2018 - Ericsson AB.
#
# 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.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from kingbird_dashboard.api import client as kb_client
INDEX_URL = "horizon:kingbird:quota_management:index"
class UpdateForm(forms.SelfHandlingForm):
ifcb_label = _("Injected File Content (Bytes)")
ifpb_label = _("Length of Injected File Path")
metadata_items = forms.IntegerField(min_value=-1,
label=_("Metadata Items"))
cores = forms.IntegerField(min_value=-1, label=_("VCPUs"))
instances = forms.IntegerField(min_value=-1, label=_("Instances"))
key_pairs = forms.IntegerField(min_value=-1, label=_("Key Pairs"))
volumes = forms.IntegerField(min_value=-1, label=_("Volumes"))
snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots"))
gigabytes = forms.IntegerField(
min_value=-1, label=_("Total Size of Volumes and Snapshots (GiB)"))
backup_gigabytes = forms.IntegerField(
min_value=-1, label=_(
"Total Size of backup Volumes and Snapshots (GiB)"))
backups = forms.IntegerField(
min_value=-1, label=_(
"Total Size of backup Volumes and Snapshots (GiB)"))
ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)"))
floating_ips = forms.IntegerField(min_value=-1, label=_("Floating IPs"))
fixed_ips = forms.IntegerField(min_value=-1, label=_("Fixed IPs"))
security_groups = forms.IntegerField(min_value=-1,
label=_("Security Groups"))
security_group = forms.IntegerField(min_value=-1,
label=_("Security Groups"))
security_group_rule = forms.IntegerField(min_value=-1,
label=_("Security Group Rules"))
floatingip = forms.IntegerField(min_value=-1, label=_("Floating IPs"))
network = forms.IntegerField(min_value=-1, label=_("Networks"))
port = forms.IntegerField(min_value=-1, label=_("Ports"))
router = forms.IntegerField(min_value=-1, label=_("Routers"))
subnet = forms.IntegerField(min_value=-1, label=_("Subnets"))
def __init__(self, request, *args, **kwargs):
super(UpdateForm, self).__init__(request, *args,
**kwargs)
target_tenant_id = request.build_absolute_uri().split('/')[-2]
quotas = kb_client.global_limits(request, target_tenant_id)
result = {i._data: i._Limit for i in quotas}
for field in result:
if field in self.fields:
self.fields[field].initial = result[field]
def handle(self, request, data):
try:
target_tenant_id = request.build_absolute_uri().split('/')[-2]
default_quota_obj = kb_client.global_limits(request,
target_tenant_id)
default_quota = {i._data: i._Limit for i in default_quota_obj}
for resource in default_quota:
if default_quota[resource] == data[resource]:
del data[resource]
if data:
kb_client.update_global_limits(request, target_tenant_id,
**data)
msg = _('Quotas updated successfully for tenant "%s".') \
% target_tenant_id
messages.success(request, msg)
return True
except Exception as e:
msg = _('Failed to update "%s".') % e
redirect = reverse('horizon:kingbird:quota_management:index')
exceptions.handle(request, msg, redirect=redirect)
class SyncQuotaForm(forms.SelfHandlingForm):
def handle(self, request, data=None):
target_tenant = request.build_absolute_uri().split('/')[-2]
try:
kb_client.sync_quota(request, target_tenant)
msg = _('On demand Quota sync has been triggered.')
messages.success(request, msg)
return True
except Exception as e:
msg = _('Failed to Sync Quota "%s".') % e
class DeleteQuotaForm(forms.SelfHandlingForm):
def handle(self, request, data=None):
target_tenant = request.build_absolute_uri().split('/')[-2]
msg = _('Request to delete quotas has been triggered.')
err_msg = _('Failed to Delete Quota .')
try:
kb_client.delete_quota(request, target_tenant)
messages.success(request, msg)
return True
except Exception as e:
msg = _('Failed to delete quotas for the tenant "%s".') % e
redirect = reverse('horizon:kingbird:quota_management:index')
exceptions.handle(request, err_msg, redirect=redirect)

View File

@ -1,3 +1,5 @@
# Copyright 2018 Ericsson AB.
#
# 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
@ -10,11 +12,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from django.utils.translation import ugettext_lazy as _
from horizon.browsers import views
title = _("Kingbirds")
urlpatterns = [
url('', views.AngularIndexView.as_view(title=title), name='index'),
]
import horizon
from kingbird_dashboard import dashboard
class QuotaManagement(horizon.Panel):
name = _("Quota Management")
slug = 'quota_management'
dashboard.Kingbird.register(QuotaManagement)

View File

@ -0,0 +1,63 @@
# 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 tables
class UpdateQuota(tables.LinkAction):
name = "update"
verbose_name = _("Update")
url = "horizon:kingbird:quota_management:update"
classes = ("ajax-modal", "btn-edit")
class QuotaSync(tables.LinkAction):
name = "quota_sync"
verbose_name = _("Sync Quota")
url = "horizon:kingbird:quota_management:sync"
classes = ("ajax-modal", "btn-edit")
class DeleteQuota(tables.LinkAction):
name = "delete_quota"
verbose_name = _("Delete Quota")
url = "horizon:kingbird:quota_management:delete"
classes = ("ajax-modal", "btn-edit")
class TenantsTable(tables.DataTable):
name = tables.Column(
"name",
verbose_name=_("Name")
)
description = tables.Column(
"description",
verbose_name=_("Description")
)
id = tables.Column(
"id",
verbose_name=_("Project ID"),
)
enabled = tables.Column(
"enabled",
verbose_name=_("Enabled"),
)
def get_object_id(self, datum):
return datum.id
class Meta(object):
name = "tenant_set"
verbose_name = _("Quota Management")
row_actions = (UpdateQuota, QuotaSync, DeleteQuota)

View File

@ -0,0 +1,18 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}Delete Quota{% endblock %}
{% block form_action %}{% url 'horizon:kingbird:quota_management:delete' project_id %}{% endblock %}
{% block modal-header %}{% trans "Delete Quota Sync" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
<p align="left">Delete Quotas for this tenant.</p>
</div>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}Sync Quota{% endblock %}
{% block form_action %}{% url 'horizon:kingbird:quota_management:sync' project_id %}{% endblock %}
{% block modal-header %}{% trans "Trigger Quota Sync" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
<p align="left">Click Sync to sync Quotas Dynamically. </p>
</div>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}update_quota{% endblock %}
{% block form_action %}{% url 'horizon:kingbird:quota_management:update' project_id %}{% endblock %}
{% block modal-header %}{% trans "Update Quotas" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>{% trans "From here you can update quotas." %}</p>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'kingbird/default/base.html' %}
{% load i18n %}
{% block title %}{% trans "Delete Quota" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Delete Quota") %}
{% endblock page_header %}
{% block main %}
{% include 'kingbird/quota_management/_delete.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Quota Management" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_domain_page_header.html" with title=page_title %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'kingbird/default/base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Quota" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update Quota") %}
{% endblock page_header %}
{% block main %}
{% include 'kingbird/quota_management/_sync.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'kingbird/default/base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Quota" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update Quota") %}
{% endblock page_header %}
{% block main %}
{% include 'kingbird/quota_management/_update.html' %}
{% endblock %}

View File

@ -1,3 +1,5 @@
# Copyright 2018 Ericsson AB.
#
# 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
@ -10,10 +12,17 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.test import helpers as test
from django.conf.urls import url
from kingbird_dashboard.quota_management import views
class KingbirdsTests(test.TestCase):
# Unit tests for kingbird.
def test_me(self):
self.assertTrue(1 + 1 == 2)
PROJECT_ID = r'^(?P<project_id>[^/]+)/%s$'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(PROJECT_ID % 'update', views.UpdateQuotaView.as_view(),
name='update'),
url(PROJECT_ID % 'sync', views.SyncQuotaView.as_view(), name='sync'),
url(PROJECT_ID % 'delete', views.DeleteQuotaView.as_view(),
name='delete'),
]

View File

@ -0,0 +1,144 @@
# Copyright 2018 Ericsson AB.
#
# 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 import settings
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 messages
from horizon import tables
from kingbird_dashboard.quota_management import forms as kb_forms
from kingbird_dashboard.quota_management import tables as kb_tables
from openstack_dashboard import api as os_api
from openstack_dashboard import policy
from openstack_dashboard.utils import identity
class IndexView(tables.DataTableView):
table_id = "tenants"
table_class = kb_tables.TenantsTable
template_name = 'kingbird/quota_management/index.html'
page_title = _("Quota Management")
def needs_filter_first(self, table):
return self._needs_filter_first
def has_more_data(self, table):
return self._more
def get_data(self):
tenants = []
marker = self.request.GET.get(
kb_tables.TenantsTable._meta.pagination_param, None)
self._more = False
filters = self.get_filters()
self._needs_filter_first = False
if policy.check((("identity", "identity:list_projects"),),
self.request):
# If filter_first is set and if there are not other filters
# selected, then search criteria must be provided and
# return an empty list
filter_first = getattr(settings, 'FILTER_DATA_FIRST', {})
if filter_first.get('identity.projects', False) and len(
filters) == 0:
self._needs_filter_first = True
self._more = False
return tenants
domain_id = identity.get_domain_id_for_operation(self.request)
try:
tenants, self._more = os_api.keystone.tenant_list(
self.request,
domain=domain_id,
paginate=True,
filters=filters,
marker=marker)
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve project list."))
elif policy.check((("identity", "identity:list_user_projects"),),
self.request):
try:
tenants, self._more = os_api.keystone.tenant_list(
self.request,
user=self.request.user.id,
paginate=True,
marker=marker,
filters=filters,
admin=False)
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve project information."))
else:
msg = \
_("Insufficient privilege level to view project information.")
messages.info(self.request, msg)
if os_api.keystone.VERSIONS.active >= 3:
domain_lookup = os_api.keystone.domain_lookup(self.request)
for t in tenants:
t.domain_name = domain_lookup.get(t.domain_id)
return tenants
class UpdateQuotaView(forms.ModalFormView):
form_class = kb_forms.UpdateForm
template_name = 'kingbird/quota_management/update.html'
success_url = reverse_lazy("horizon:kingbird:quota_management:index")
submit_label = _("Update")
def get_context_data(self, **kwargs):
context = super(UpdateQuotaView, self).get_context_data(**kwargs)
context["project_id"] = self.kwargs['project_id']
return context
def get_initial(self, **kwargs):
return {'project_id': self.kwargs['project_id']}
class SyncQuotaView(forms.ModalFormView):
form_class = kb_forms.SyncQuotaForm
template_name = 'kingbird/quota_management/sync.html'
success_url = reverse_lazy("horizon:kingbird:quota_management:index")
submit_label = _("Sync")
def get_context_data(self, **kwargs):
context = super(SyncQuotaView, self).get_context_data(**kwargs)
context["project_id"] = self.kwargs['project_id']
return context
def get_initial(self, **kwargs):
return {'project_id': self.kwargs['project_id']}
class DeleteQuotaView(forms.ModalFormView):
form_class = kb_forms.DeleteQuotaForm
template_name = 'kingbird/quota_management/delete.html'
success_url = reverse_lazy("horizon:kingbird:quota_management:index")
submit_label = _("Delete")
def get_context_data(self, **kwargs):
context = super(DeleteQuotaView, self).get_context_data(**kwargs)
context["project_id"] = self.kwargs['project_id']
return context
def get_initial(self, **kwargs):
return {'project_id': self.kwargs['project_id']}

View File

@ -1,40 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc overview
* @name horizon.dashboard.kingbird
* @description
* Dashboard module to host various kingbird panels.
*/
// fixme: if ngRoute and $routeProvider are unnecessary, remove them
/* eslint-disable no-unused-vars */
angular
.module('horizon.dashboard.kingbird', [
'horizon.dashboard.kingbird.kingbirds',
'ngRoute'
])
.config(config);
config.$inject = ['$provide', '$windowProvider', '$routeProvider'];
function config($provide, $windowProvider, $routeProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/kingbird/';
$provide.constant('horizon.dashboard.kingbird.basePath', path);
}
/* eslint-disable no-unused-vars */
})();

View File

@ -1,23 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
describe('horizon.dashboard.kingbird', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.kingbird')).toBeDefined();
});
});
})();

View File

@ -1,8 +0,0 @@
@import "kingbirds/kingbirds";
.batch-action {
float: right;
action-list {
padding-left: 0.3em;
}
}

View File

@ -1,80 +0,0 @@
/**
* 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.
*/
(function () {
'use strict';
angular
.module('horizon.app.core.openstack-service-api')
.factory('horizon.app.core.openstack-service-api.kingbird_dashboard', API);
API.$inject = [
'horizon.framework.util.http.service',
'horizon.framework.widgets.toast.service',
'horizon.framework.util.i18n.gettext'
];
function API(apiService, toastService, gettext) {
var service = {
getKingbird: getKingbird,
getKingbirds: getKingbirds,
createKingbird: createKingbird,
updateKingbird: updateKingbird,
deleteKingbird: deleteKingbird
};
return service;
///////////////////////////////
// Kingbirds
function getKingbird(id) {
return apiService.get('/api/kingbird_dashboard/kingbirds/' + id)
.error(function() {
var msg = gettext('Unable to retrieve the Kingbird with id: %(id)s.');
toastService.add('error', interpolate(msg, {id: id}, true));
});
}
function getKingbirds() {
return apiService.get('/api/kingbird_dashboard/kingbirds/')
.error(function() {
toastService.add('error', gettext('Unable to retrieve the Kingbirds.'));
});
}
function createKingbird(params) {
return apiService.put('/api/kingbird_dashboard/kingbirds/', params)
.error(function() {
var msg = gettext('Unable to create the Kingbird with name: %(name)s');
toastService.add('error', interpolate(msg, { name: params.name }, true));
});
}
function updateKingbird(id, params) {
return apiService.post('/api/kingbird_dashboard/kingbirds/' + id, params)
.error(function() {
var msg = gettext('Unable to update the Kingbird with id: %(id)s');
toastService.add('error', interpolate(msg, { id: params.id }, true));
});
}
function deleteKingbird(id, suppressError) {
var promise = apiService.delete('/api/kingbird_dashboard/kingbirds/', [id]);
return suppressError ? promise : promise.error(function() {
var msg = gettext('Unable to delete the Kingbird with id: %(id)s');
toastService.add('error', interpolate(msg, { id: id }, true));
});
}
}
}());

View File

@ -1,87 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc overview
* @ngname horizon.dashboard.kingbird.kingbirds.actions
*
* @description
* Provides all of the actions for Kingbirds.
*/
angular
.module('horizon.dashboard.kingbird.kingbirds.actions', [
'horizon.framework',
'horizon.dashboard.kingbird'
])
.run(registerKingbirdActions);
registerKingbirdActions.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.kingbird.kingbirds.create.service',
'horizon.dashboard.kingbird.kingbirds.update.service',
'horizon.dashboard.kingbird.kingbirds.delete.service',
'horizon.dashboard.kingbird.kingbirds.resourceType'
];
function registerKingbirdActions (
registry,
gettext,
createKingbirdService,
updateKingbirdService,
deleteKingbirdService,
resourceType
) {
var kingbirdsResourceType = registry.getResourceType(resourceType);
kingbirdsResourceType.globalActions
.append({
id: 'createKingbirdAction',
service: createKingbirdService,
template: {
type: 'create',
text: gettext('Create Kingbird')
}
});
kingbirdsResourceType.batchActions
.append({
id: 'batchDeleteKingbirdAction',
service: deleteKingbirdService,
template: {
type: 'delete-selected',
text: gettext('Delete Kingbirds')
}
});
kingbirdsResourceType.itemActions
.append({
id: 'updateKingbirdAction',
service: updateKingbirdService,
template: {
text: gettext('Update Kingbird')
}
})
.append({
id: 'deleteKingbirdAction',
service: deleteKingbirdService,
template: {
type: 'delete',
text: gettext('Delete Kingbird')
}
});
}
})();

View File

@ -1,103 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc overview
* @name horizon.dashboard.kingbird.kingbirds.create.service
* @description Service for the kingbird create modal
*/
angular
.module('horizon.dashboard.kingbird.kingbirds')
.factory('horizon.dashboard.kingbird.kingbirds.create.service', createService);
createService.$inject = [
'$location',
'horizon.app.core.openstack-service-api.kingbird_dashboard',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.i18n.gettext',
'horizon.framework.util.q.extensions',
'horizon.framework.widgets.toast.service',
'horizon.dashboard.kingbird.kingbirds.events',
'horizon.dashboard.kingbird.kingbirds.model',
'horizon.dashboard.kingbird.kingbirds.resourceType',
'horizon.dashboard.kingbird.kingbirds.workflow'
];
function createService(
$location, api, policy, actionResult, gettext, $qExtensions,
toast, events, model, resourceType, workflow
) {
var message = {
success: gettext('Kingbird %s was successfully created.')
};
var service = {
initAction: initAction,
perform: perform,
allowed: allowed
};
return service;
//////////////
// fixme: include this function in your service
// if you plan to emit events to the parent controller,
// otherwise remove it
function initAction() {
}
// fixme: if newScope is unnecessary, remove it
/* eslint-disable no-unused-vars */
function perform(selected, newScope) {
// modal title, buttons
var title, submitText, submitIcon;
title = gettext("Create Kingbird");
submitText = gettext("Create");
submitIcon = "fa fa-check";
model.init();
var result = workflow.init(title, submitText, submitIcon, model.spec);
return result.then(submit);
}
function allowed() {
return $qExtensions.booleanAsPromise(true);
// fixme: if you need to set policy, change as follow
//return policy.ifAllowed({ rules: [['kingbird', 'create_kingbird']] });
}
function submit() {
model.cleanProperties();
return api.createKingbird(model.spec).then(success);
}
function success(response) {
response.data.id = response.data.uuid;
toast.add('success', interpolate(message.success, [response.data.id]));
var result = actionResult.getActionResult()
.created(resourceType, response.data.id);
if (result.result.failed.length === 0 && result.result.created.length > 0) {
$location.path('/kingbird/kingbirds');
} else {
return result.result;
}
}
}
})();

View File

@ -1,156 +0,0 @@
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use self 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.
*/
(function() {
'use strict';
/**
* @ngDoc factory
* @name horizon.dashboard.kingbird.kingbirds.delete.service
* @Description
* Brings up the delete kingbirds confirmation modal dialog.
* On submit, delete selected resources.
* On cancel, do nothing.
*/
angular
.module('horizon.dashboard.kingbird.kingbirds')
.factory('horizon.dashboard.kingbird.kingbirds.delete.service', deleteService);
deleteService.$inject = [
'$location',
'$q',
'horizon.app.core.openstack-service-api.kingbird_dashboard',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.i18n.gettext',
'horizon.framework.util.q.extensions',
'horizon.framework.widgets.modal.deleteModalService',
'horizon.framework.widgets.toast.service',
'horizon.dashboard.kingbird.kingbirds.resourceType',
'horizon.dashboard.kingbird.kingbirds.events'
];
function deleteService(
$location, $q, api, policy, actionResult, gettext, $qExtensions,
deleteModal, toast, resourceType, events
) {
var scope;
var context = {
labels: null,
deleteEntity: deleteEntity,
successEvent: events.DELETE_SUCCESS
};
var service = {
initAction: initAction,
allowed: allowed,
perform: perform
};
var notAllowedMessage =
gettext("You are not allowed to delete kingbirds: %s");
return service;
//////////////
// fixme: include this function in your service
// if you plan to emit events to the parent controller,
// otherwise remove it
function initAction() {
}
function allowed() {
return $qExtensions.booleanAsPromise(true);
// fixme: if you need to set policy, change as follow
//return policy.ifAllowed({ rules: [['kingbird', 'delete_kingbird']] });
}
// delete selected resource objects
function perform(selected, newScope) {
scope = newScope;
selected = angular.isArray(selected) ? selected : [selected];
context.labels = labelize(selected.length);
return $qExtensions.allSettled(selected.map(checkPermission)).then(afterCheck);
}
function labelize(count) {
return {
title: ngettext('Confirm Delete Kingbird',
'Confirm Delete Kingbirds', count),
/* eslint-disable max-len */
message: ngettext('You have selected "%s". Please confirm your selection. Deleted kingbird is not recoverable.',
'You have selected "%s". Please confirm your selection. Deleted kingbirds are not recoverable.', count),
/* eslint-enable max-len */
submit: ngettext('Delete Kingbird',
'Delete Kingbirds', count),
success: ngettext('Deleted Kingbird: %s.',
'Deleted Kingbirds: %s.', count),
error: ngettext('Unable to delete Kingbird: %s.',
'Unable to delete Kingbirds: %s.', count)
};
}
// for batch delete
function checkPermission(selected) {
return {promise: allowed(selected), context: selected};
}
// for batch delete
function afterCheck(result) {
var outcome = $q.reject(); // Reject the promise by default
if (result.fail.length > 0) {
toast.add('error', getMessage(notAllowedMessage, result.fail));
outcome = $q.reject(result.fail);
}
if (result.pass.length > 0) {
outcome = deleteModal.open(scope, result.pass.map(getEntity), context).then(createResult);
}
return outcome;
}
function createResult(deleteModalResult) {
// To make the result of this action generically useful, reformat the return
// from the deleteModal into a standard form
var result = actionResult.getActionResult();
deleteModalResult.pass.forEach(function markDeleted(item) {
result.deleted(resourceType, getEntity(item).id);
});
deleteModalResult.fail.forEach(function markFailed(item) {
result.failed(resourceType, getEntity(item).id);
});
if (result.result.failed.length === 0 && result.result.deleted.length > 0) {
$location.path('/kingbird/kingbirds');
} else {
return result.result;
}
}
function getMessage(message, entities) {
return interpolate(message, [entities.map(getName).join(", ")]);
}
function getName(result) {
return getEntity(result).name;
}
// for batch delete
function getEntity(result) {
return result.context;
}
// call delete REST API
function deleteEntity(id) {
return api.deleteKingbird(id, true);
}
}
})();

View File

@ -1,122 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc overview
* @name horizon.dashboard.kingbird.kingbirds.update.service
* @description Service for the kingbird update modal
*/
angular
.module('horizon.dashboard.kingbird.kingbirds')
.factory('horizon.dashboard.kingbird.kingbirds.update.service', updateService);
updateService.$inject = [
'$location',
'horizon.app.core.openstack-service-api.kingbird_dashboard',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.i18n.gettext',
'horizon.framework.util.q.extensions',
'horizon.framework.widgets.toast.service',
'horizon.dashboard.kingbird.kingbirds.events',
'horizon.dashboard.kingbird.kingbirds.model',
'horizon.dashboard.kingbird.kingbirds.resourceType',
'horizon.dashboard.kingbird.kingbirds.workflow'
];
function updateService(
$location, api, policy, actionResult, gettext, $qExtensions,
toast, events, model, resourceType, workflow
) {
var message = {
success: gettext('Kingbird %s was successfully updated.')
};
var service = {
initAction: initAction,
perform: perform,
allowed: allowed
};
var id;
return service;
//////////////
// fixme: include this function in your service
// if you plan to emit events to the parent controller,
// otherwise remove it
function initAction() {
}
// fixme: if newScope is unnecessary, remove it
/* eslint-disable no-unused-vars */
function perform(selected, newScope) {
// modal title, buttons
var title, submitText, submitIcon;
title = gettext("Update Kingbird");
submitText = gettext("Update");
submitIcon = "fa fa-check";
model.init();
// load current data
id = selected.id;
var deferred = api.getKingbird(id);
deferred.then(onLoad);
function onLoad(response) {
model.spec.id = response.data.id;
model.spec.name = response.data.name;
model.spec.description = response.data.description;
model.spec.enabled = response.data.enabled;
model.spec.size = response.data.size;
model.spec.temperature = response.data.temperature;
model.spec.base = response.data.base;
model.spec.flavor = response.data.flavor;
model.spec.topping = response.data.topping;
}
var result = workflow.init(title, submitText, submitIcon, model.spec);
return result.then(submit);
}
function allowed() {
return $qExtensions.booleanAsPromise(true);
// fixme: if you need to set policy, change as follow
//return policy.ifAllowed({ rules: [['kingbird', 'update_kingbird']] });
}
function submit() {
model.cleanProperties();
return api.updateKingbird(id, model.spec).then(success);
}
function success(response) {
response.data.id = response.data.uuid;
toast.add('success', interpolate(message.success, [response.data.id]));
var result = actionResult.getActionResult()
.updated(resourceType, response.data.id);
if (result.result.failed.length === 0 && result.result.updated.length > 0) {
$location.path('/kingbird/kingbirds');
} else {
return result.result;
}
}
}
})();

View File

@ -1,57 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc overview
* @ngname horizon.dashboard.kingbird.kingbirds.details
*
* @description
* Provides details features for Kingbird.
*/
angular
.module('horizon.dashboard.kingbird.kingbirds.details', [
'horizon.app.core',
'horizon.framework.conf'
])
.run(registerDetails);
registerDetails.$inject = [
'horizon.app.core.openstack-service-api.kingbird_dashboard',
'horizon.dashboard.kingbird.kingbirds.basePath',
'horizon.dashboard.kingbird.kingbirds.resourceType',
'horizon.framework.conf.resource-type-registry.service'
];
function registerDetails(
api,
basePath,
resourceType,
registry
) {
registry.getResourceType(resourceType)
.setLoadFunction(loadFunction)
.detailsViews.append({
id: 'kingbirdDetailsOverview',
name: gettext('Overview'),
template: basePath + 'details/overview.html'
});
function loadFunction(identifier) {
return api.getKingbird(identifier);
}
}
})();

View File

@ -1,6 +0,0 @@
<hz-resource-property-list
resource-type-name="OS::kingbird_dashboard::Kingbird"
item="item"
property-groups="[['id'],
['topping', 'created_at', 'updated_at']]">
</hz-resource-property-list>

View File

@ -1,37 +0,0 @@
/*
* 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.
*/
(function() {
"use strict";
angular
.module('horizon.dashboard.kingbird.kingbirds')
.controller('horizon.dashboard.kingbird.kingbirds.OverviewController', controller);
controller.$inject = [
'$scope'
];
function controller(
$scope
) {
var ctrl = this;
ctrl.kingbird = {};
$scope.context.loadPromise.then(onGetKingbird);
function onGetKingbird(kingbird) {
ctrl.kingbird = kingbird.data;
}
}
})();

View File

@ -1,16 +0,0 @@
<div ng-controller="horizon.dashboard.kingbird.kingbirds.OverviewController as ctrl">
<div class="row">
<div class="col-md-12 detail">
<h3 translate>Kingbird</h3>
<hr>
<hz-resource-property-list
resource-type-name="OS::kingbird_dashboard::Kingbird"
cls="dl-horizontal"
item="ctrl.kingbird"
property-groups="[['name', 'description', 'enabled'],
['size', 'temperature', 'base', 'flavor', 'topping'],
['id', 'created_at', 'updated_at']]">
</hz-resource-property-list>
</div>
</div>
</div>

View File

@ -1,169 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc overview
* @name horizon.dashboard.kingbird.kingbirds
* @ngModule
* @description
* Provides all the services and widgets require to display the Kingbird
* panel
*/
angular
.module('horizon.dashboard.kingbird.kingbirds', [
'ngRoute',
'horizon.dashboard.kingbird.kingbirds.actions',
'horizon.dashboard.kingbird.kingbirds.details'
])
.constant('horizon.dashboard.kingbird.kingbirds.events', events())
.constant('horizon.dashboard.kingbird.kingbirds.resourceType', 'OS::kingbird_dashboard::Kingbird')
.run(run)
.config(config);
/**
* @ngdoc constant
* @name horizon.dashboard.kingbird.kingbirds.events
* @description A list of events used by Kingbird
* @returns {Object} events
*/
function events() {
return {
CREATE_SUCCESS: 'horizon.dashboard.kingbird.kingbirds.CREATE_SUCCESS',
DELETE_SUCCESS: 'horizon.dashboard.kingbird.kingbirds.DELETE_SUCCESS'
};
}
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.kingbird.kingbirds.service',
'horizon.dashboard.kingbird.kingbirds.basePath',
'horizon.dashboard.kingbird.kingbirds.resourceType'
];
function run(registry, service, basePath, resourceType) {
registry.getResourceType(resourceType)
.setNames(gettext('Kingbird'), gettext('Kingbirds'))
// for detail summary view on table row
.setSummaryTemplateUrl(basePath + 'details/drawer.html')
// specify items for table row items, summary view and details view
.setProperties(properties())
// get items for table
.setListFunction(service.getPromise)
// specify table columns
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
filters: ['noName'],
urlFunction: service.urlFunction
})
.append({
id: 'size',
priority: 1,
filters: ['noValue']
})
.append({
id: 'temperature',
priority: 1,
filters: ['noValue']
})
.append({
id: 'base',
priority: 1,
filters: ['noValue']
})
.append({
id: 'flavor',
priority: 1,
filters: ['noValue']
})
.append({
id: 'topping',
priority: 2,
filters: ['noValue']
})
.append({
id: 'created_at',
priority: 2
})
.append({
id: 'updated_at',
priority: 2
});
// for magic-search
registry.getResourceType(resourceType).filterFacets
.append({
'label': gettext('Name'),
'name': 'name',
'singleton': true
})
.append({
'label': gettext('Base'),
'name': 'base',
'singleton': true
})
.append({
'label': gettext('Flavor'),
'name': 'flavor',
'singleton': true
})
.append({
'label': gettext('ID'),
'name': 'id',
'singleton': true
});
}
function properties() {
return {
id: { label: gettext('ID'), filters: ['noValue'] },
name: { label: gettext('Name'), filters: ['noName'] },
description: { label: gettext('Description'), filters: ['noValue'] },
enabled: { label: gettext('Enabled'), filters: ['yesno'] },
size: { label: gettext('Size'), filters: ['noValue'] },
temperature: { label: gettext('Temperature'), filters: ['noValue'] },
base: { label: gettext('Base'), filters: ['noValue'] },
flavor: { label: gettext('Flavor'), filters: ['noValue'] },
topping: { label: gettext('Topping'), filters: ['noValue'] },
created_at: { label: gettext('Created'), filters: ['simpleDate', 'noValue'] },
updated_at: { label: gettext('Updated'), filters: ['simpleDate', 'noValue'] }
};
}
config.$inject = [
'$provide',
'$windowProvider',
'$routeProvider'
];
/**
* @name config
* @param {Object} $provide
* @param {Object} $windowProvider
* @param {Object} $routeProvider
* @description Routes used by this module.
* @returns {undefined} Returns nothing
*/
function config($provide, $windowProvider, $routeProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/kingbird/kingbirds/';
$provide.constant('horizon.dashboard.kingbird.kingbirds.basePath', path);
$routeProvider.when('/kingbird/kingbirds', {
templateUrl: path + 'panel.html'
});
}
})();

View File

@ -1,23 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
describe('horizon.dashboard.kingbird.kingbirds', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.kingbird.kingbirds')).toBeDefined();
});
});
})();

View File

@ -1,63 +0,0 @@
/**
* 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.
*/
(function() {
"use strict";
angular.module('horizon.dashboard.kingbird.kingbirds')
.factory('horizon.dashboard.kingbird.kingbirds.service',
service);
service.$inject = [
'$filter',
'horizon.app.core.detailRoute',
'horizon.app.core.openstack-service-api.kingbird_dashboard'
];
/*
* @ngdoc factory
* @name horizon.dashboard.kingbird.kingbirds.service
*
* @description
* This service provides functions that are used through the Kingbirds
* features. These are primarily used in the module registrations
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
function service($filter, detailRoute, api) {
return {
getPromise: getPromise,
urlFunction: urlFunction
};
function getPromise(params) {
return api.getKingbirds(params).then(modifyResponse);
}
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
var timestamp = item.updated_at ? item.updated_at : item.created_at;
item.trackBy = item.id.concat(timestamp);
return item;
}
}
function urlFunction(item) {
return detailRoute + 'OS::kingbird_dashboard::Kingbird/' + item.id;
}
}
})();

View File

@ -1,52 +0,0 @@
/**
* 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.
*/
(function() {
"use strict";
describe('Kingbirds service', function() {
var service;
beforeEach(module('horizon.dashboard.kingbird.kingbirds'));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.dashboard.kingbird.kingbirds.service');
}));
describe('getPromise', function() {
it("provides a promise", inject(function($q, $injector, $timeout) {
var api = $injector.get('horizon.app.core.openstack-service-api.kingbird_dashboard');
var deferred = $q.defer();
spyOn(api, 'getKingbirds').and.returnValue(deferred.promise);
var result = service.getPromise({});
deferred.resolve({
data:{
items: [{id: 123, name: 'resource1'}]
}
});
$timeout.flush();
expect(api.getKingbirds).toHaveBeenCalled();
expect(result.$$state.value.data.items[0].name).toBe('resource1');
}));
});
describe('urlFunction', function() {
it("get url", inject(function() {
var detailRoute = $injector.get('horizon.app.core.detailRoute');
var result = service.urlFunction({id:"123abc"});
expect(result).toBe(detailRoute + "OS::kingbird_dashboard::Kingbird/123abc");
}));
});
});
})();

View File

@ -1,6 +0,0 @@
<hz-resource-panel resource-type-name="OS::kingbird_dashboard::Kingbird">
<hz-resource-table resource-type-name="OS::kingbird_dashboard::Kingbird"
track-by="trackBy">
</hz-resource-table>
</hz-resource-panel>

View File

@ -1,4 +0,0 @@
<dl>
<dt translate>Kingbird Name</dt>
<dd translate>An arbitrary human-readable name</dd>
</dl>

View File

@ -1,63 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc model
* @name horizon.dashboard.kingbird.kingbirds.model
* @description Service for the kingbird model
*/
angular
.module('horizon.dashboard.kingbird.kingbirds')
.factory('horizon.dashboard.kingbird.kingbirds.model', model);
model.$inject = [
];
function model() {
var model = {
// params
"spec": {},
// methods
"init": init,
"cleanProperties": cleanProperties
};
function init() {
// initialize model
model.spec = {
"id": "",
"name": "", // text required
"description": "", // textarea
"enabled": true, // checkbox
"size": "M", // radio
"temperature": "H", // radio
"base": "", // select
"flavor": "", // select
"topping": "" // checkboxes
};
}
function cleanProperties() {
delete model.spec.id;
delete model.spec.tabs;
}
return model;
}
})();

View File

@ -1,7 +0,0 @@
<dl>
<dt translate>Base</dt>
<dd translate>Choose base drink.</dd>
<dt translate>Other options</dt>
<dd translate>Choose favorite options.</dd>
</dl>

View File

@ -1,211 +0,0 @@
/**
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc workflow
* @name horizon.dashboard.kingbird.kingbirds.workflow
* @description Service for the create/update workflow
*/
angular
.module('horizon.dashboard.kingbird.kingbirds')
.factory('horizon.dashboard.kingbird.kingbirds.workflow', workflow);
workflow.$inject = [
'horizon.dashboard.kingbird.basePath',
'horizon.framework.util.i18n.gettext',
'horizon.framework.widgets.form.ModalFormService'
];
function workflow(basePath, gettext, modal) {
var workflow = {
init: init
};
function init(title, submitText, submitIcon, model) {
var schema, form;
// schema
schema = {
"type": "object",
"properties": {
"name": {
"title": gettext("Name"),
"type": "string"
},
"description": {
"title": gettext("Description"),
"type": "string"
},
"enabled": {
"title": gettext("Enabled"),
"type": "boolean",
"default": true
},
"size": {
"title": gettext("Size"),
"type": "string",
"default": "M"
},
"temperature": {
"title": gettext("Temperature"),
"type": "string",
"default": "H"
},
"base": {
"title": gettext("Base"),
"type": "string",
"default": ""
},
"flavor": {
"title": gettext("Flavor"),
"type": "string",
"default": ""
},
"topping": {
"title": gettext("Topping")
}
}
};
// form
form = [
{
"type": "tabs",
"tabs": [
{
"title": gettext("Info"),
"help": basePath + "kingbirds/workflow/info.help.html",
"items": [
{
"key": "name",
"placeholder": gettext("Name of the kingbird."),
"required": true
},
{
"key": "description",
"type": "textarea"
},
{
"key": "enabled",
"type": "checkbox"
}
]
},
{
"title": gettext("Recipe"),
"help": basePath + "kingbirds/workflow/recipe.help.html",
"items": [
{
"key": "size",
"type": "radiobuttons",
"titleMap": [
{"value": "S", "name": gettext("Small")},
{"value": "M", "name": gettext("Medium")},
{"value": "L", "name": gettext("Large")},
{"value": "XL", "name": gettext("Extra Large")}
]
},
{
"key": "temperature",
"type": "radiobuttons",
"titleMap": [
{"value": "H", "name": gettext("Hot")},
{"value": "I", "name": gettext("Ice")}
]
},
{
"key": "base",
"type": "select",
"titleMap": [
{"value": "", "name": gettext("Choose base.")},
{
"value": "blend",
"name": gettext("House Blend"),
"group": gettext("Coffee")
},
{
"value": "mandheling",
"name": gettext("Mandheling"),
"group": gettext("Coffee")},
{
"value": "colombia",
"name": gettext("Colombia"),
"group": gettext("Coffee")
},
{
"value": "espresso",
"name": gettext("Espresso"),
"group": gettext("Coffee")
},
{
"value": "earl_gray",
"name": gettext("Earl Gray"),
"group": gettext("Tea")
},
{
"value": "darjeeling",
"name": gettext("Darjeeling"),
"group": gettext("Tea")},
{
"value": "orange_pekoe",
"name": gettext("Orange Pekoe"),
"group": gettext("Tea")
}
]
},
{
"key": "flavor",
"type": "select",
"titleMap": [
{"value": "", "name": gettext("Choose flavor.")},
{"value": "chocolate", "name": gettext("Chocolate")},
{"value": "mocha", "name": gettext("Mocha")},
{"value": "strawberry", "name": gettext("Strawberry")},
{"value": "blueberry", "name": gettext("Blueberry")},
{"value": "raspberry", "name": gettext("Raspberry")}
]
},
{
"key": "topping",
"type": "checkboxes",
"titleMap": [
{"value": "clushed_nuts", "name": gettext("Clushed Nuts")},
{"value": "whip_cream", "name": gettext("Whip Cream")},
{"value": "mixed_serial", "name": gettext("Mixed Serial")}
]
}
] // items
} // tab
] // tabs
}
]; // form
var config = {
"title": title,
"submitText": submitText,
"schema": schema,
"form": form,
"model": model
};
return modal.open(config);
}
return workflow;
}
})();

View File

@ -1,14 +0,0 @@
# 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 pbr.version
version_info = pbr.version.VersionInfo('kingbird_dashboard')