Rewrite of the whole dashboard.

Change-Id: I91d2338ce3225e80ba4c53a1a88a5300d5af9c44
This commit is contained in:
Guillaume Espanel 2015-05-05 13:35:20 +00:00 committed by Stéphane Albert
parent 797a89586b
commit 3364dc8dd2
61 changed files with 1506 additions and 426 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ htmlcov
dist
*.egg
*.sw?
.ropeproject

4
.testr.conf Normal file
View File

@ -0,0 +1,4 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./cloudkittydashboard/tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

16
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,16 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/cloudkitty

4
HACKING.rst Normal file
View File

@ -0,0 +1,4 @@
cloudkitty-dashboard Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

2
babel.cfg Normal file
View File

@ -0,0 +1,2 @@
[python: **.py]

View File

@ -0,0 +1,29 @@
# Copyright 2015 Objectif Libre
#
# 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 openstack_dashboard.utils import settings
import cloudkittydashboard.enabled
ADD_INSTALLED_APPS = ['cloudkittydashboard']
UPDATE_HORIZON_CONFIG = {}
DASHBOARD = ''
settings.update_dashboards(
(cloudkittydashboard.enabled,),
UPDATE_HORIZON_CONFIG,
ADD_INSTALLED_APPS
)

View File

@ -0,0 +1,19 @@
# Copyright 2015 Objectif Libre
#
# 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__ = pbr.version.VersionInfo(
'cloudkittydashboard').version_string()

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Objectif Libre
# Copyright 2015 Objectif Libre
#
# 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
@ -13,36 +13,36 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: François Magimel (linkid)
import collections
"""
The API for the CloudKitty Horizon plugin.
"""
import logging
from cloudkittyclient import client as cloudkitty_client
from django.conf import settings
from horizon.utils.memoized import memoized # noqa
from cloudkittyclient import client as ck_client
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
@memoized
def cloudkittyclient(request):
"""Initialization of CloudKitty client."""
username = request.user.username
token = request.user.token.id
tenant_name = request.user.tenant_id
endpoint = base.url_for(request, 'billing')
auth_url = base.url_for(request, 'identity')
"""Initialization of Cloudkitty client."""
LOG.debug('cloudkittyclient connection created using token "%s" '
'and endpoint "%s"' % (request.user.token.id, endpoint))
endpoint = base.url_for(request, 'rating')
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
return ck_client.Client('1', endpoint,
token=(lambda: request.user.token.id),
insecure=insecure,
cacert=cacert)
return cloudkitty_client.Client('1',
username=username,
token=token,
tenant_name=tenant_name,
auth_url=auth_url,
endpoint=endpoint)
def identify(what, name=False, key=None):
if isinstance(what, collections.Iterable):
for i in what:
i.id = getattr(i, key or "%s_id" % i.key)
if name:
i.name = getattr(i, key or "%s_id" % i.key)
else:
what.id = getattr(what, key or "%s_id" % what.key)
if name:
what.name = getattr(what, key or "%s_id" % what.key)
return what

View File

@ -0,0 +1,26 @@
# Copyright 2015 Objectif Libre
#
# 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
from cloudkittydashboard.dashboards.admin.hashmap import panel as hashmap_panel
from cloudkittydashboard.dashboards.admin.modules import panel as modules_panel
class CloudkittyPanels(horizon.PanelGroup):
slug = "cloudkitty"
name = _("Rating")
panels = (modules_panel.Modules,
hashmap_panel.Hashmap)

View File

@ -0,0 +1,107 @@
# Copyright 2015 Objectif Libre
#
# 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.utils.translation import ugettext_lazy as _
from horizon import forms
from cloudkittydashboard.api import cloudkitty as api
LOG = logging.getLogger(__name__)
class CreateServiceForm(forms.SelfHandlingForm):
name = forms.CharField(label=_("Name"))
def handle(self, request, data):
name = data['name']
LOG.info('Creating service with name %s' % (name))
return api.cloudkittyclient(request).hashmap.services.create(name=name)
class CreateFieldForm(forms.SelfHandlingForm):
name = forms.CharField(label=_("Name"))
service_id = forms.CharField(label=_("Service ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
def handle(self, request, data):
name = data['name']
service_id = data['service_id']
LOG.info('Creating field with name %s' % (name))
field_client = api.cloudkittyclient(request).hashmap.fields
return field_client.create(name=name, service_id=service_id)
class CreateMappingForm(forms.SelfHandlingForm):
cost = forms.DecimalField(label=_("Cost"))
type = forms.ChoiceField(label=_("Type"),
choices=(("flat", _("Flat")),
("rate", _("Rate")),
("threshold", _("Threshold"))))
group_id = forms.CharField(label=_("Group"), required=False)
def handle(self, request, data):
mapping_client = api.cloudkittyclient(request).hashmap.mappings
mapping = {k: v for k, v in data.items() if v and v != ''}
return mapping_client.create(**mapping)
class CreateFieldMappingForm(CreateMappingForm):
field_id = forms.CharField(label=_("Field ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}),
required=False)
value = forms.CharField(label=_("Value"))
def __init__(self, *args, **kwargs):
super(CreateFieldMappingForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['value',
'cost',
'type',
'group_id',
'field_id']
class CreateServiceMappingForm(CreateMappingForm):
service_id = forms.CharField(label=_("Service ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}),
required=False)
class EditServiceMappingForm(CreateServiceMappingForm):
mapping_id = forms.CharField(label=_("Mapping ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
def handle(self, request, data):
mapping_client = api.cloudkittyclient(request).hashmap.mappings
mapping = {k: v for k, v in data.items() if v and v != ''}
mapping['mapping_id'] = self.initial['mapping_id']
return mapping_client.update(**mapping)
class EditFieldMappingForm(CreateFieldMappingForm):
mapping_id = forms.CharField(label=_("Mapping ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
def handle(self, request, data):
mapping_client = api.cloudkittyclient(request).hashmap.mappings
mapping = {k: v for k, v in data.items() if v and v != ''}
mapping['mapping_id'] = self.initial['mapping_id']
return mapping_client.update(**mapping)

View File

@ -0,0 +1,22 @@
# Copyright 2015 Objectif Libre
#
# 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 Hashmap(horizon.Panel):
name = _("Hashmap")
slug = "hashmap"

View File

@ -0,0 +1,227 @@
# Copyright 2015 Objectif Libre
#
# 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 tables
from horizon import tabs
from cloudkittydashboard.api import cloudkitty as api
class CreateService(tables.LinkAction):
name = "createservice"
verbose_name = _("Create new Service")
url = 'horizon:admin:hashmap:service_create'
icon = "create"
ajax = True
classes = ("ajax-modal",)
class DeleteService(tables.BatchAction):
name = "deleteservice"
verbose_name = _("Delete Service")
action_present = _("Delete")
action_past = _("Deleted")
data_type_singular = _("Service")
data_type_plural = _("Services")
icon = "remove"
def action(self, request, service_id):
api.cloudkittyclient(request).hashmap.services.delete(
service_id=service_id
)
class ServicesTable(tables.DataTable):
"""This table list the available services.
Clicking on a service name sends you to a ServiceTabs page.
"""
name = tables.Column('name', verbose_name=_("Name"),
link='horizon:admin:hashmap:service')
class Meta(object):
name = "services"
verbose_name = _("Services")
table_actions = (CreateService,)
row_actions = (DeleteService,)
class DeleteField(tables.BatchAction):
name = "deletefield"
verbose_name = _("Delete Field")
action_present = _("Delete")
action_past = _("Deleted")
data_type_singular = _("Field")
data_type_plural = _("Fields")
icon = "remove"
def action(self, request, field_id):
api.cloudkittyclient(request).hashmap.fields.delete(
field_id=field_id
)
class CreateField(tables.LinkAction):
name = "createfield"
verbose_name = _("Create new Field")
icon = "create"
ajax = True
classes = ("ajax-modal",)
def get_link_url(self, datum=None):
url = 'horizon:admin:hashmap:field_create'
service_id = self.table.request.service_id
return reverse(url, args=[service_id])
class FieldsTable(tables.DataTable):
"""This table lists the available fields for a given service.
Clicking on a fields sends you to a MappingsTable.
"""
name = tables.Column('name', verbose_name=_("Name"),
link='horizon:admin:hashmap:field')
class Meta(object):
name = "fields"
verbose_name = _("Fields")
multi_select = False
row_actions = (DeleteField,)
table_actions = (CreateField,)
class FieldsTab(tabs.TableTab):
name = _("Fields")
slug = "hashmap_fields"
table_classes = (FieldsTable,)
template_name = "horizon/common/_detail_table.html"
preload = True
def get_fields_data(self):
client = api.cloudkittyclient(self.request)
fields = client.hashmap.fields.list(service_id=self.request.service_id)
return api.identify(fields)
class DeleteMapping(tables.BatchAction):
name = "deletemapping"
verbose_name = _("Delete Mapping")
action_present = _("Delete")
action_past = _("Deleted")
data_type_singular = _("Mapping")
data_type_plural = _("Mappings")
icon = "remove"
def action(self, request, mapping_id):
api.cloudkittyclient(request).hashmap.mappings.delete(
mapping_id=mapping_id
)
class CreateServiceMapping(tables.LinkAction):
name = "createiservicemapping"
verbose_name = _("Create new Mapping")
icon = "create"
ajax = True
classes = ("ajax-modal",)
def get_link_url(self, datum=None):
url = 'horizon:admin:hashmap:service_mapping_create'
service_id = self.table.request.service_id
return reverse(url, args=[service_id])
class EditServiceMapping(tables.LinkAction):
name = "editservicemapping"
verbose_name = _("Edit Mapping")
icon = "edit"
ajax = True
classes = ("ajax-modal",)
def get_link_url(self, datum=None):
url = 'horizon:admin:hashmap:service_mapping_edit'
return reverse(url, args=[datum.mapping_id])
class ServiceMappingsTable(tables.DataTable):
cost = tables.Column('cost', verbose_name=_("Cost"))
type = tables.Column('type', verbose_name=_("Type"))
group_id = tables.Column('group_id', verbose_name=_("Group"))
class Meta(object):
name = "mappings"
verbose_name = _("Mappings")
row_actions = (EditServiceMapping, DeleteMapping)
table_actions = (CreateServiceMapping,)
class CreateFieldMapping(tables.LinkAction):
name = "createfieldmapping"
verbose_name = _("Create new Mapping")
icon = "create"
ajax = True
classes = ("ajax-modal",)
def get_link_url(self, datum=None):
url = 'horizon:admin:hashmap:field_mapping_create'
field_id = self.table.request.field_id
return reverse(url, args=[field_id])
class EditFieldMapping(tables.LinkAction):
name = "editfieldmapping"
verbose_name = _("Edit Mapping")
icon = "edit"
ajax = True
classes = ("ajax-modal",)
def get_link_url(self, datum=None):
url = 'horizon:admin:hashmap:field_mapping_edit'
return reverse(url, args=[datum.mapping_id])
class FieldMappingsTable(tables.DataTable):
value = tables.Column('value', verbose_name=_("Value"))
cost = tables.Column('cost', verbose_name=_("Cost"))
type = tables.Column('type', verbose_name=_("Type"))
group_id = tables.Column('group_id', verbose_name=_("Group"))
class Meta(object):
name = "mappings"
verbose_name = _("Mappings")
row_actions = (EditFieldMapping, DeleteMapping)
table_actions = (CreateFieldMapping,)
class MappingsTab(tabs.TableTab):
name = _("Mappings")
slug = "hashmap_mappings"
table_classes = (ServiceMappingsTable,)
template_name = "horizon/common/_detail_table.html"
preload = True
def get_mappings_data(self):
client = api.cloudkittyclient(self.request)
mappings = client.hashmap.mappings.list(
service_id=self.request.service_id
)
return api.identify(mappings)
class ServiceTabs(tabs.TabGroup):
slug = "services_tabs"
tabs = (FieldsTab, MappingsTab)
sticky = True

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Fields" %}{% endblock %}
{% block main %}
{{ table.render }}
{{ modules }}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Hashmap" %}{% endblock %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Services" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Services") %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{{ modules }}
{% endblock %}

View File

@ -0,0 +1,21 @@
# Copyright 2015 Objectif Libre
#
# 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 horizon.test import helpers as test
class CkprojectTests(test.TestCase):
# Unit tests for ckproject.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,47 @@
# Copyright 2015 Objectif Libre
#
# 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 patterns
from django.conf.urls import url
from cloudkittydashboard.dashboards.admin.hashmap import views
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^service/(?P<service_id>[^/]+)/?$',
views.ServiceView.as_view(),
name='service'),
url(r'^create_service/?$',
views.ServiceCreateView.as_view(),
name='service_create'),
url(r'^field/(?P<field_id>[^/]+)/?$',
views.FieldView.as_view(),
name='field'),
url(r'^create_field/service/(?P<service_id>[^/]+)/?$',
views.FieldCreateView.as_view(),
name='field_create'),
url(r'^create_mapping/service/(?P<service_id>[^/]+)/?$',
views.ServiceMappingCreateView.as_view(),
name='service_mapping_create'),
url(r'^create_mapping/field/(?P<field_id>[^/]+)/?$',
views.FieldMappingCreateView.as_view(),
name='field_mapping_create'),
url(r'^edit_mapping/service/(?P<mapping_id>[^/]+)/?$',
views.ServiceMappingEditView.as_view(),
name='service_mapping_edit'),
url(r'^edit_mapping/field/(?P<mapping_id>[^/]+)/?$',
views.FieldMappingEditView.as_view(),
name='field_mapping_edit'),
)

View File

@ -0,0 +1,194 @@
# Copyright 2015 Objectif Libre
#
# 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 horizon import forms
from horizon import tables
from horizon import tabs
from cloudkittydashboard.api import cloudkitty as api
from cloudkittydashboard.dashboards.admin.hashmap import forms as hashmap_forms
from cloudkittydashboard.dashboards.admin.hashmap \
import tables as hashmap_tables
class IndexView(tables.DataTableView):
table_class = hashmap_tables.ServicesTable
template_name = "admin/hashmap/services_list.html"
def get_data(self):
out = api.cloudkittyclient(self.request).hashmap.services.list()
return api.identify(out)
class ServiceView(tabs.TabbedTableView):
tab_group_class = hashmap_tables.ServiceTabs
template_name = 'admin/hashmap/service_details.html'
def get(self, *args, **kwargs):
service = api.cloudkittyclient(self.request).hashmap.services.get(
service_id=kwargs['service_id']
)
self.request.service_id = service.service_id
self.page_title = "Hashmap Service : %s" % service.name
return super(ServiceView, self).get(*args, **kwargs)
class ServiceCreateView(forms.ModalFormView):
form_class = hashmap_forms.CreateServiceForm
template_name = 'horizon/common/modal_form.html'
success_url = reverse_lazy('horizon:admin:hashmap:index')
submit_url = reverse_lazy('horizon:admin:hashmap:service_create')
def get_object_id(self, obj):
return obj.service_id
class FieldView(tables.DataTableView):
table_class = hashmap_tables.FieldMappingsTable
template_name = 'admin/hashmap/field_details.html'
def get(self, *args, **kwargs):
field = api.cloudkittyclient(self.request).hashmap.fields.get(
field_id=kwargs['field_id']
)
self.request.field_id = field.field_id
self.page_title = "Hashmap Field : %s" % field.name
return super(FieldView, self).get(*args, **kwargs)
def get_data(self):
out = api.cloudkittyclient(self.request).hashmap.mappings.list(
field_id=self.kwargs['field_id']
)
return api.identify(out)
class FieldCreateView(forms.ModalFormView):
form_class = hashmap_forms.CreateFieldForm
template_name = 'horizon/common/modal_form.html'
success_url = 'horizon:admin:hashmap:service'
submit_url = 'horizon:admin:hashmap:field_create'
def get_object_id(self, obj):
return obj.field_id
def get_context_data(self, **kwargs):
context = super(FieldCreateView, self).get_context_data(**kwargs)
context["service_id"] = self.kwargs['service_id']
args = (self.kwargs['service_id'],)
context['submit_url'] = reverse_lazy(self.submit_url, args=args)
return context
def get_initial(self):
return {"service_id": self.kwargs["service_id"]}
def get_success_url(self, **kwargs):
args = (self.kwargs['service_id'],)
return reverse_lazy(self.success_url, args=args)
class ServiceMappingCreateView(forms.ModalFormView):
form_class = hashmap_forms.CreateServiceMappingForm
template_name = 'horizon/common/modal_form.html'
submit_url = 'horizon:admin:hashmap:service_mapping_create'
def get_object_id(self, obj):
return obj.mapping_id
def get_context_data(self, **kwargs):
context = super(ServiceMappingCreateView,
self).get_context_data(**kwargs)
context["service_id"] = self.kwargs.get('service_id')
context['submit_url'] = reverse_lazy(self.submit_url,
args=(context['service_id'], ))
return context
def get_initial(self):
return {"service_id": self.kwargs.get("service_id")}
def get_success_url(self, **kwargs):
return reverse('horizon:admin:hashmap:service',
args=(self.kwargs['service_id'],))
class ServiceMappingEditView(ServiceMappingCreateView):
form_class = hashmap_forms.EditServiceMappingForm
submit_url = 'horizon:admin:hashmap:service_mapping_edit'
def get_initial(self):
out = api.cloudkittyclient(self.request).hashmap.mappings.get(
mapping_id=self.kwargs['mapping_id'])
self.initial = out.to_dict()
return self.initial
def get_context_data(self, **kwargs):
context = super(ServiceMappingEditView,
self).get_context_data(**kwargs)
context["mapping_id"] = self.kwargs.get('mapping_id')
context['submit_url'] = reverse_lazy(self.submit_url,
args=(context['mapping_id'], ))
return context
def get_success_url(self, **kwargs):
return reverse('horizon:admin:hashmap:service',
args=(self.initial['service_id'], ))
class FieldMappingCreateView(forms.ModalFormView):
form_class = hashmap_forms.CreateFieldMappingForm
template_name = 'horizon/common/modal_form.html'
submit_url = 'horizon:admin:hashmap:field_mapping_create'
def get_object_id(self, obj):
return obj.mapping_id
def get_context_data(self, **kwargs):
context = super(FieldMappingCreateView,
self).get_context_data(**kwargs)
context["field_id"] = self.kwargs.get('field_id')
context['submit_url'] = reverse_lazy(self.submit_url,
args=(context['field_id'], ))
return context
def get_initial(self):
return {"field_id": self.kwargs.get("field_id")}
def get_success_url(self, **kwargs):
return reverse('horizon:admin:hashmap:field',
args=(self.kwargs['field_id'], ))
class FieldMappingEditView(FieldMappingCreateView):
form_class = hashmap_forms.EditFieldMappingForm
submit_url = 'horizon:admin:hashmap:field_mapping_edit'
def get_initial(self):
out = api.cloudkittyclient(self.request).hashmap.mappings.get(
mapping_id=self.kwargs['mapping_id'])
self.initial = out.to_dict()
return self.initial
def get_context_data(self, **kwargs):
context = super(FieldMappingEditView,
self).get_context_data(**kwargs)
context["mapping_id"] = self.kwargs.get('mapping_id')
context['submit_url'] = reverse_lazy(self.submit_url,
args=(context['mapping_id'], ))
return context
def get_success_url(self, **kwargs):
return reverse('horizon:admin:hashmap:field',
args=(self.initial['field_id'], ))

View File

@ -0,0 +1,22 @@
# Copyright 2015 Objectif Libre
#
# 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 Modules(horizon.Panel):
name = _("Rating Modules")
slug = "rating_modules"

View File

@ -0,0 +1,60 @@
# Copyright 2015 Objectif Libre
#
# 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
from cloudkittydashboard.api import cloudkitty as api
ENABLE = 0
DISABLE = 1
class ToggleEnabledModule(tables.BatchAction):
name = "toggle_module"
action_present = (_("Enable"), _("Disable"))
action_past = (_("Enabled"), _("Disabled"))
data_type_singular = _("Module")
data_type_plural = _("Modules")
classes = ("btn-toggle",)
def allowed(self, request, module=None):
self.enabled = module.enabled
if self.enabled:
self.current_present_action = DISABLE
else:
self.current_present_action = ENABLE
return True
def action(self, request, obj_id):
module = api.cloudkittyclient(request).modules.get(module_id=obj_id)
self.enabled = module.enabled
if self.enabled:
self.current_past_action = DISABLE
module.disable()
else:
module.enable()
self.current_past_action = ENABLE
class ModulesTable(tables.DataTable):
name = tables.Column('name', verbose_name=_("Name"))
description = tables.Column('description', verbose_name=_("Description"))
hot_config = tables.Column('hot-config', verbose_name=_("Configurable"))
enabled = tables.Column('enabled', verbose_name=_("Enabled"))
class Meta(object):
name = "modules"
verbose_name = _("Modules")
row_actions = (ToggleEnabledModule,)

View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Modules" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Modules") %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{{ modules }}
{% endblock %}

View File

@ -0,0 +1,7 @@
from horizon.test import helpers as test
class CkprojectTests(test.TestCase):
# Unit tests for ckproject.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,24 @@
# Copyright 2015 Objectif Libre
#
# 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 patterns
from django.conf.urls import url
from cloudkittydashboard.dashboards.admin.modules.views \
import IndexView
urlpatterns = patterns(
'',
url(r'^$', IndexView.as_view(), name='index'),
)

View File

@ -0,0 +1,32 @@
# Copyright 2015 Objectif Libre
#
# 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 horizon import tables
from cloudkittydashboard.api import cloudkitty as api
from cloudkittydashboard.dashboards.admin.modules import tables as admin_tables
class IndexView(tables.DataTableView):
# A very simple class-based view...
template_name = 'admin/rating_modules/index.html'
table_class = admin_tables.ModulesTable
def get_data(self):
# Add data to the context here...
modules = api.identify(
api.cloudkittyclient(self.request).modules.list(),
name=True
)
return modules

View File

@ -0,0 +1,26 @@
# Copyright 2015 Objectif Libre
#
# 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
from openstack_dashboard.dashboards.project import dashboard
class Project_rating(horizon.Panel):
name = _("Rating")
slug = "rating"
dashboard.Project.register(Project_rating)

View File

@ -0,0 +1,15 @@
{% include 'project/instances/_launch_details_help.html' %}
{% load i18n %}
{% load static %}
{% block price %}
<table class="flavor_table table-striped">
<tbody>
<tr>
<td class="price">{% trans "Price" %}</td><td><span id="price"></span> {% trans "$" %}</td>
</tr>
</tbody>
</table>
{% endblock %}
<script src='{% static "cloudkitty/js/pricing.js" %}' type='text/javascript' charset='utf-8'></script>

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Rating" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Rating") %}
{% endblock page_header %}
{% block main %}
Total : {{ total }}
{% endblock %}

View File

@ -0,0 +1,7 @@
from horizon.test import helpers as test
class CkprojectTests(test.TestCase):
# Unit tests for ckproject.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,24 @@
# Copyright 2015 Objectif Libre
#
# 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 patterns
from django.conf.urls import url
from cloudkittydashboard.dashboards.project.rating import views
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^quote$', views.quote, name='quote')
)

View File

@ -0,0 +1,49 @@
# Copyright 2015 Objectif Libre
#
# 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 import http
from horizon import exceptions
from horizon import views
from cloudkittydashboard.api import cloudkitty as api
class IndexView(views.APIView):
# A very simple class-based view...
template_name = 'project/rating/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
total = api.cloudkittyclient(request).reports.get_total(
tenant_id=request.user.tenant_id) or 0.00
context['total'] = total
return context
def quote(request):
pricing = "0"
if request.is_ajax():
if request.method == 'POST':
json_data = json.loads(request.body)
try:
pricing = (api.cloudkittyclient(request)
.quotations.quote(json_data))
except Exception:
exceptions.handle(request,
_('Unable to retrieve price.'))
return http.HttpResponse(json.dumps(pricing),
content_type='application/json')

View File

@ -0,0 +1,17 @@
# Copyright 2015 Objectif Libre
#
# 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.
PANEL_GROUP = 'rating'
PANEL_GROUP_NAME = 'Rating'
PANEL_GROUP_DASHBOARD = 'admin'

View File

@ -0,0 +1,17 @@
# Copyright 2015 Objectif Libre
#
# 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.
PANEL_GROUP = 'rating'
PANEL_GROUP_NAME = 'Rating'
PANEL_GROUP_DASHBOARD = 'project'

View File

@ -0,0 +1,21 @@
# Copyright 2015 Objectif Libre
#
# 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.
PANEL_GROUP = 'rating'
PANEL_DASHBOARD = 'admin'
PANEL = 'hashmap'
# Python panel class of the PANEL to be added.
ADD_PANEL = \
'cloudkittydashboard.dashboards.admin.hashmap.panel.Hashmap'

View File

@ -0,0 +1,21 @@
# Copyright 2015 Objectif Libre
#
# 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.
PANEL_GROUP = 'rating'
PANEL_DASHBOARD = 'admin'
PANEL = 'rating_modules'
# Python panel class of the PANEL to be added.
ADD_PANEL = \
'cloudkittydashboard.dashboards.admin.modules.panel.Modules'

View File

@ -0,0 +1,24 @@
# Copyright 2015 Objectif Libre
#
# 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.
PANEL_GROUP = 'rating'
PANEL_DASHBOARD = 'project'
PANEL = 'rating'
# Python panel class of the PANEL to be added.
ADD_PANEL = \
'cloudkittydashboard.dashboards.project.rating.panel.Project_rating'
UPDATE_HORIZON_CONFIG = {'customization_module':
"cloudkittydashboard.overrides"}

View File

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Objectif Libre
#
# 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 patterns # noqa
from django.conf.urls import url # noqa
from django.utils.translation import ugettext_lazy as _
from openstack_dashboard.dashboards.project.instances.workflows \
import create_instance
# Instances Panel
class InstancePredictivePricing(create_instance.SetInstanceDetailsAction):
class Meta(object):
name = _("Details")
help_text_template = ("project/rating/"
"_launch_details_price.html")
def get_help_text(self, extra_context=None):
extra = extra_context or {}
extra['price'] = 0
extra = super(InstancePredictivePricing,
self).get_help_text(extra_context=extra)
return extra
create_instance.SetInstanceDetails.action_class = InstancePredictivePricing

View File

@ -0,0 +1,140 @@
/*
Copyright 2015 Objectif Libre
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.
*/
pricing = {
is_price: false, // Is this a price display ?
init: function() {
this._attachInputHandlers(); // handler
},
init_work: function() {
this.is_price = true;
//$('#price').text('0');
this.init();
this.work();
},
/*
Attaches event handlers for the form elements associated with the price
(see horizon.Quota._attachInputHandlers).
*/
_attachInputHandlers: function() {
var scope = this;
if (this.is_price) {
var eventCallback = function(evt) {
scope.work();
};
$('#id_flavor').on('change', eventCallback);
$('#id_count').on('keyup', eventCallback);
$('#id_image_id').on('change', eventCallback);
}
},
work: function() {
// Quota available → what about the selected flavor
if (horizon.Quota.selected_flavor) {
// get data of the flavor (form)
_image = horizon.Quota.getSelectedImage();
var flavor = horizon.Quota.selected_flavor.name;
var vcpus = horizon.Quota.selected_flavor.vcpus;
var disk = horizon.Quota.selected_flavor.disk;
var ephemeral = horizon.Quota.selected_flavor["OS-FLV-EXT-DATA:ephemeral"];
var disk_total = horizon.Quota.selected_flavor.disk + horizon.Quota.selected_flavor["OS-FLV-EXT-DATA:ephemeral"];
var disk_total_display = disk_total;
var ram = horizon.Quota.selected_flavor.ram;
var source_type = $('#id_source_type option:selected').val();
var source_val = $('#id_' + source_type + ' option:selected').val();
var instance_count = parseInt($("#id_count").val());
// make the json data form
desc_form = {
'flavor': flavor,
'source_type': source_type,
'source_val': source_val, // images : horizon.Quota.findImageById(source_val);
'vcpus': vcpus,
'disk': disk,
'ephemeral': ephemeral,
'disk_total': disk_total,
'disk_total_display': disk_total_display,
'ram': ram
}
if (_image != undefined) {
desc_form['image_id'] = _image.id
}
var form_data = [{"service": "compute", "desc": desc_form, "volume": instance_count}];
// send the JSON by a POST request
var url_data = '/project/rating/quote';
this.sendPost(form_data, url_data);
}
},
sendPost: function(form_data, url_data) {
$.ajax({
type: "post", // send POST data
url: url_data,
dataType: 'json',
data: JSON.stringify(form_data), // data sent
contentType: 'application/json; charset=utf-8',
success: function (data) {
$("#price").text(data);
},
beforeSend: function(xhr, settings){
$.ajaxSettings.beforeSend(xhr, settings);
}
});
}
};
// https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
// http://stackoverflow.com/a/6199592
// http://coreymaynard.com/blog/performing-ajax-post-requests-in-django/
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
if(typeof horizon.Quota !== 'undefined') {
pricing.init_work();
} else {
addHorizonLoadEvent(function() {
pricing.init_work();
});
}

View File

@ -1,5 +1,7 @@
#!/usr/bin/env python
# Copyright 2014 Objectif Libre
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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
@ -13,13 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
from django.core.management import execute_from_command_line # noqa
from oslotest import base
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"openstack_dashboard.settings")
execute_from_command_line(sys.argv)
class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""

View File

@ -1,133 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Objectif Libre
#
# 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 os
from django.utils.translation import ugettext as _
from horizon.test.settings import * # noqa
from horizon.utils import secret_key as secret_key_utils
from openstack_dashboard import exceptions
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_PATH = os.path.abspath(os.path.join(TEST_DIR, ".."))
SECRET_KEY = secret_key_utils.generate_or_read_from_file(
os.path.join(TEST_DIR, '.secret_key_store'))
ROOT_URLCONF = 'openstack_dashboard.urls'
TEMPLATE_DIRS = (
os.path.join(TEST_DIR, 'templates'),
)
TEMPLATE_CONTEXT_PROCESSORS += (
'openstack_dashboard.context_processors.openstack',
)
INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.staticfiles',
'django.contrib.messages',
'django.contrib.humanize',
'django_nose',
'openstack_auth',
'compressor',
'horizon',
'openstack_dashboard',
'openstack_dashboard.dashboards.project',
'openstack_dashboard.dashboards.admin',
'openstack_dashboard.dashboards.settings',
)
AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
SITE_BRANDING = 'OpenStack'
HORIZON_CONFIG = {
'dashboards': ('project', 'admin',),
'default_dashboard': 'project',
"password_validator": {
"regex": '^.{8,18}$',
"help_text": _("Password must be between 8 and 18 characters.")
},
'user_home': None,
'help_url': "http://docs.openstack.org",
'exceptions': {'recoverable': exceptions.RECOVERABLE,
'not_found': exceptions.NOT_FOUND,
'unauthorized': exceptions.UNAUTHORIZED},
'angular_modules': [],
'js_files': [],
}
# Set to True to allow users to upload images to glance via Horizon server.
# When enabled, a file form field will appear on the create image form.
# See documentation for deployment considerations.
HORIZON_IMAGES_ALLOW_UPLOAD = True
AVAILABLE_REGIONS = [
('http://localhost:5000/v2.0', 'local'),
('http://remote:5000/v2.0', 'remote'),
]
OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0"
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_"
OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True
OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'test_domain'
OPENSTACK_KEYSTONE_BACKEND = {
'name': 'native',
'can_edit_user': True,
'can_edit_group': True,
'can_edit_project': True,
'can_edit_domain': True,
'can_edit_role': True
}
OPENSTACK_HYPERVISOR_FEATURES = {
'can_set_mount_point': True,
'can_set_password': True,
}
LOGGING['loggers']['openstack_dashboard'] = {
'handlers': ['test'],
'propagate': False,
}
SECURITY_GROUP_RULES = {
'all_tcp': {
'name': 'ALL TCP',
'ip_protocol': 'tcp',
'from_port': '1',
'to_port': '65535',
},
'http': {
'name': 'HTTP',
'ip_protocol': 'tcp',
'from_port': '80',
'to_port': '80',
},
}
NOSE_ARGS = ['--nocapture',
'--nologcapture',
'--with-coverage',
'--cover-package=cloudkittydashboard',
'--cover-inclusive',
'--all-modules']
CLOUDKITTY_ENDPOINT_URL = "http://127.0.0.1:8888"

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Objectif Libre
# 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.
"""
test_cloudkittydashboard
----------------------------------
Tests for `cloudkittydashboard` module.
"""
from cloudkittydashboard.tests import base
class TestCloudkittydashboard(base.TestCase):
def test_something(self):
pass

View File

@ -1,6 +0,0 @@
import testtools
class FakeTest(testtools.TestCase):
def test_foo(self):
pass

View File

@ -1,177 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/cloudkitty-dashboard.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/cloudkitty-dashboard.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/cloudkitty-dashboard"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/cloudkitty-dashboard"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

View File

@ -0,0 +1,4 @@
============
Contributing
============
.. include:: ../../CONTRIBUTING.rst

View File

@ -1,21 +1,20 @@
.. cloudkitty-dashboard documentation master file, created by
sphinx-quickstart on Thu Jul 3 17:15:04 2014.
sphinx-quickstart on Tue Jul 9 22:26:36 2013.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to the CloudKitty Dashboard's documentation!
====================================================
Welcome to cloudkitty-dashboard's documentation!
========================================================
Introduction
============
Contents:
CloudKitty is a PricingAsAService project aimed at translating Ceilometer
metrics to prices.
python-cloudkitty is the Python client library for CloudKitty API.
cloudkitty-dashboard is the Horizon plugin for CloudKitty.
.. toctree::
:maxdepth: 2
readme
installation
usage
contributing
Indices and tables
==================
@ -23,3 +22,4 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -0,0 +1,12 @@
============
Installation
============
At the command line::
$ pip install cloudkitty-dashboard
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv cloudkitty-dashboard
$ pip install cloudkitty-dashboard

1
doc/source/readme.rst Normal file
View File

@ -0,0 +1 @@
.. include:: ../../README.rst

7
doc/source/usage.rst Normal file
View File

@ -0,0 +1,7 @@
========
Usage
========
To use cloudkitty-dashboard in a project::
import cloudkittydashboard

6
openstack-common.conf Normal file
View File

@ -0,0 +1,6 @@
[DEFAULT]
# The list of modules to copy from oslo-incubator.git
# The base module to hold the copy of openstack.common
base=cloudkittydashboard

View File

@ -1,5 +1,2 @@
pbr>=0.6,!=0.7,<1.0
http://tarballs.openstack.org/python-cloudkittyclient/python-cloudkittyclient-master.tar.gz
# Horizon Core Requirements
Django>=1.4.2,<1.7
Babel>=1.3

View File

@ -1,34 +1,47 @@
[metadata]
name = cloudkitty-dashboard
summary = CloudKitty Horizon Plugin
summary = CloudKitty Horizon dashboard
description-file =
README.rst
author = Objectif Libre
author-email = francois.magimel@objectif-libre.com
home-page = http://objectif-libre.com
author-email = openstack-dev@lists.openstack.org
home-page = http://www.objectif-libre.com
classifier =
Environment :: OpenStack
Framework :: Django
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
Topic :: Internet :: WWW/HTTP
Programming Language :: Python :: 2.6
Programming Language :: Python :: 3
Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
[files]
packages =
cloudkittydashboard
[global]
setup-hooks =
pbr.hooks.setup_hook
[build_sphinx]
all_files = 1
build-dir = doc/build
source-dir = doc/source
build-dir = doc/build
all_files = 1
[upload_sphinx]
upload-dir = doc/build/html
[compile_catalog]
directory = cloudkittydashboard/locale
domain = cloudkitty-dashboard
[update_catalog]
domain = cloudkitty-dashboard
output_dir = cloudkittydashboard/locale
input_file = cloudkittydashboard/locale/cloudkitty-dashboard.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = cloudkittydashboard/locale/cloudkitty-dashboard.pot

19
setup.py Normal file → Executable file
View File

@ -1,17 +1,18 @@
#!/usr/bin/env python
# Copyright 2014 Objectif Libre
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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
# 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
# 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.
# 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.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools

View File

@ -1,13 +1,11 @@
hacking>=0.9.1,<0.10
hacking<0.11,>=0.10.0
coverage>=3.6
django-nose
doc8
nose
openstack.nose_plugin>=0.7
oslosphinx
oslotest
sphinx>=1.1.2
discover
python-subunit>=0.0.18
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
oslosphinx>=2.2.0 # Apache-2.0
oslotest>=1.2.0 # Apache-2.0
testrepository>=0.0.18
http://tarballs.openstack.org/horizon/horizon-master.tar.gz
testscenarios>=0.4
testtools>=0.9.36,!=1.2.0

46
tox.ini
View File

@ -1,44 +1,36 @@
[tox]
envlist = py26,py27,py33,pep8
minversion = 1.6
envlist = py26,py27,pypy,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
setenv =
VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python {toxinidir}/manage.py test cloudkittydashboard --settings=cloudkittydashboard.tests.settings
[tox:jenkins]
downloadcache = ~/cache/pip
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
setenv = DJANGO_SETTINGS_MODULE=cloudkittydashboard.tests.settings
commands = flake8 {posargs}
[testenv:cover]
commands =
coverage erase
coverage run {toxinidir}/manage.py test cloudkittydashboard --settings=cloudkittydashboard.tests.settings
coverage html -d cover
[testenv:docs]
setenv = DJANGO_SETTINGS_MODULE=cloudkittydashboard.tests.settings
commands =
doc8 -e .rst README.rst doc/source
python setup.py build_sphinx
commands = flake8
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:debug]
commands = oslo_debug_helper {posargs}
[flake8]
# H102 Apache 2.0 license header not found
# H302 import only modules.
# H307 like imports should be grouped together
# H904 Wrap long lines in parentheses instead of a backslash
# E265 block comment should start with '# '
ignore = H102,H302,H307,H904,E265
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,enabled
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,.ropeproject