From b7304ab60c3a5e410039eece09d7af081690f3dd Mon Sep 17 00:00:00 2001 From: Luka Peschke Date: Thu, 5 Jul 2018 17:46:06 +0200 Subject: [PATCH] Improve CloudKitty's dashboard Work items: - The "Project/Rating" tab has been improved: it does now provide a total by metric type. This make use of the /summary endpoint instead of /total (/total is deprecated). - An "Admin/Rating Summary" tab has been added. An admin user can now have the cost of every rated tenant at once. By clicking on a tenant, a per-resource total for the given tenant can be obtained (this view is similar to the "Project/Rating" tab). A per-resource total for the whole cloud is also available. Change-Id: I13b9ba9e04a330ec216378258ad024c8651f6ff5 --- .../dashboards/admin/hashmap/views.py | 1 - .../dashboards/admin/summary/__init__.py | 0 .../dashboards/admin/summary/panel.py | 22 ++++++ .../dashboards/admin/summary/tables.py | 45 ++++++++++++ .../templates/rating_summary/details.html | 16 +++++ .../templates/rating_summary/index.html | 16 +++++ .../dashboards/admin/summary/urls.py | 23 +++++++ .../dashboards/admin/summary/views.py | 68 +++++++++++++++++++ .../dashboards/project/rating/tables.py | 27 ++++++++ .../rating/templates/rating/index.html | 11 ++- .../dashboards/project/rating/views.py | 30 +++++--- .../enabled/_11_admin_summary_panel.py | 21 ++++++ .../improve-total-tab-96df42d1e562fc4f.yaml | 14 ++++ 13 files changed, 275 insertions(+), 19 deletions(-) create mode 100644 cloudkittydashboard/dashboards/admin/summary/__init__.py create mode 100644 cloudkittydashboard/dashboards/admin/summary/panel.py create mode 100644 cloudkittydashboard/dashboards/admin/summary/tables.py create mode 100644 cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/details.html create mode 100644 cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/index.html create mode 100644 cloudkittydashboard/dashboards/admin/summary/urls.py create mode 100644 cloudkittydashboard/dashboards/admin/summary/views.py create mode 100644 cloudkittydashboard/dashboards/project/rating/tables.py create mode 100644 cloudkittydashboard/enabled/_11_admin_summary_panel.py create mode 100644 releasenotes/notes/improve-total-tab-96df42d1e562fc4f.yaml diff --git a/cloudkittydashboard/dashboards/admin/hashmap/views.py b/cloudkittydashboard/dashboards/admin/hashmap/views.py index d8c0bb0..3a15b48 100644 --- a/cloudkittydashboard/dashboards/admin/hashmap/views.py +++ b/cloudkittydashboard/dashboards/admin/hashmap/views.py @@ -22,7 +22,6 @@ from horizon import tabs from horizon import views from keystoneauth1 import exceptions -# from cloudkittyclient.apiclient import exceptions from cloudkittydashboard.api import cloudkitty as api from cloudkittydashboard.dashboards.admin.hashmap import forms as hashmap_forms from cloudkittydashboard.dashboards.admin.hashmap \ diff --git a/cloudkittydashboard/dashboards/admin/summary/__init__.py b/cloudkittydashboard/dashboards/admin/summary/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudkittydashboard/dashboards/admin/summary/panel.py b/cloudkittydashboard/dashboards/admin/summary/panel.py new file mode 100644 index 0000000..a4e98ea --- /dev/null +++ b/cloudkittydashboard/dashboards/admin/summary/panel.py @@ -0,0 +1,22 @@ +# Copyright 2018 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 Summary(horizon.Panel): + name = _("Rating Summary") + slug = "rating_summary" diff --git a/cloudkittydashboard/dashboards/admin/summary/tables.py b/cloudkittydashboard/dashboards/admin/summary/tables.py new file mode 100644 index 0000000..dcb1507 --- /dev/null +++ b/cloudkittydashboard/dashboards/admin/summary/tables.py @@ -0,0 +1,45 @@ +# Copyright 2018 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 + + +def get_details_link(datum): + if datum.tenant_id: + url = "horizon:admin:rating_summary:project_details" + return reverse(url, kwargs={'project_id': datum.tenant_id}) + + +class SummaryTable(tables.DataTable): + project_id = tables.Column( + 'tenant_id', verbose_name=_("Project ID"), link=get_details_link) + project_name = tables.Column( + 'name', verbose_name=_("Project Name"), link=get_details_link) + total = tables.Column('rate', verbose_name=_("Project Total")) + + class Meta(object): + name = "summary" + verbose_name = _("Summary") + + +class TenantSummaryTable(tables.DataTable): + res_type = tables.Column('res_type', verbose_name=_("Res Type")) + rate = tables.Column('rate', verbose_name=_("Rate")) + + class Meta(object): + name = "tenant_summary" + verbose_name = _("Tenant Summary") diff --git a/cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/details.html b/cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/details.html new file mode 100644 index 0000000..083ffce --- /dev/null +++ b/cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/details.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Tenant Details" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Tenant Details") %} +{% endblock page_header %} + +{% block main %} + +{% trans "Project ID:" %} {{ project_id }} + +{{ table.render }} + +{{ modules }} +{% endblock %} diff --git a/cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/index.html b/cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/index.html new file mode 100644 index 0000000..97ec6a8 --- /dev/null +++ b/cloudkittydashboard/dashboards/admin/summary/templates/rating_summary/index.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Summary" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Summary") %} +{% endblock page_header %} + +{% block main %} + +{{ table.render }} + +{{ modules }} +{% endblock %} + + diff --git a/cloudkittydashboard/dashboards/admin/summary/urls.py b/cloudkittydashboard/dashboards/admin/summary/urls.py new file mode 100644 index 0000000..11c7c47 --- /dev/null +++ b/cloudkittydashboard/dashboards/admin/summary/urls.py @@ -0,0 +1,23 @@ +# Copyright 2018 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 url + +from cloudkittydashboard.dashboards.admin.summary import views + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)/?$', views.TenantDetailsView.as_view(), + name="project_details"), +] diff --git a/cloudkittydashboard/dashboards/admin/summary/views.py b/cloudkittydashboard/dashboards/admin/summary/views.py new file mode 100644 index 0000000..4f53f28 --- /dev/null +++ b/cloudkittydashboard/dashboards/admin/summary/views.py @@ -0,0 +1,68 @@ +# Copyright 2018 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 openstack_dashboard.api import keystone as api_keystone + +from cloudkittydashboard.api import cloudkitty as api +from cloudkittydashboard.dashboards.admin.summary import tables as sum_tables + + +class IndexView(tables.DataTableView): + template_name = 'admin/rating_summary/index.html' + table_class = sum_tables.SummaryTable + + def get_data(self): + summary = api.cloudkittyclient(self.request).report.get_summary( + groupby=['tenant_id'], all_tenants=True)['summary'] + + tenants, _ = api_keystone.tenant_list(self.request) + tenants = {tenant.id: tenant.name for tenant in tenants} + summary.append({ + 'tenant_id': 'ALL', + 'rate': sum([float(item['rate']) for item in summary]), + }) + summary = api.identify(summary, key='tenant_id') + for tenant in summary: + tenant['name'] = tenants.get(tenant.id, '-') + summary[-1]['name'] = 'Cloud Total' + return summary + + +class TenantDetailsView(tables.DataTableView): + template_name = 'admin/rating_summary/details.html' + table_class = sum_tables.TenantSummaryTable + page_title = _("Script Details : {{ table.project_id }}") + + def _get_cloud_total_summary(self): + return api.cloudkittyclient(self.request).report.get_summary( + groupby=['res_type'], all_tenants=True)['summary'] + + def get_data(self): + tenant_id = self.kwargs['project_id'] + if tenant_id == 'ALL': + summary = self._get_cloud_total_summary() + else: + summary = api.cloudkittyclient(self.request).report.get_summary( + groupby=['res_type'], tenant_id=tenant_id)['summary'] + + summary.append({ + 'tenant_id': tenant_id, + 'res_type': 'TOTAL', + 'rate': sum([float(item['rate']) for item in summary]), + }) + summary = api.identify(summary, key='res_type', name=True) + return summary diff --git a/cloudkittydashboard/dashboards/project/rating/tables.py b/cloudkittydashboard/dashboards/project/rating/tables.py new file mode 100644 index 0000000..9fd8692 --- /dev/null +++ b/cloudkittydashboard/dashboards/project/rating/tables.py @@ -0,0 +1,27 @@ +# Copyright 2018 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 + + +class SummaryTable(tables.DataTable): + """This table formats a summary for the given tenant.""" + + res_type = tables.Column('res_type', verbose_name=_('Metric Type')) + rate = tables.Column('rate', verbose_name=_('Rate')) + + class Meta(object): + name = "summary" + verbose_name = _("Summary") diff --git a/cloudkittydashboard/dashboards/project/rating/templates/rating/index.html b/cloudkittydashboard/dashboards/project/rating/templates/rating/index.html index 74841af..e12aaea 100644 --- a/cloudkittydashboard/dashboards/project/rating/templates/rating/index.html +++ b/cloudkittydashboard/dashboards/project/rating/templates/rating/index.html @@ -7,11 +7,8 @@ {% endblock page_header %} {% block main %} -
-
-
-
{% trans "Total" %}
-
{{ total }}
-
-
+ +{{ table.render }} + +{{ modules }} {% endblock %} diff --git a/cloudkittydashboard/dashboards/project/rating/views.py b/cloudkittydashboard/dashboards/project/rating/views.py index 0f1820f..ac4ca28 100644 --- a/cloudkittydashboard/dashboards/project/rating/views.py +++ b/cloudkittydashboard/dashboards/project/rating/views.py @@ -18,21 +18,30 @@ import json from django import http from django.utils.translation import ugettext_lazy as _ from horizon import exceptions -from horizon import views +from horizon import tables from cloudkittydashboard.api import cloudkitty as api +from cloudkittydashboard.dashboards.project.rating \ + import tables as rating_tables +from cloudkittydashboard.utils import TemplatizableDict -class IndexView(views.APIView): - # A very simple class-based view... +class IndexView(tables.DataTableView): + table_class = rating_tables.SummaryTable template_name = 'project/rating/index.html' - def get_data(self, request, context, *args, **kwargs): - # Add data to the context here... - total = api.cloudkittyclient(request).report.get_total( - tenant_id=request.user.tenant_id) or 0.00 - context['total'] = total - return context + def get_data(self): + summary = api.cloudkittyclient(self.request).report.get_summary( + tenant_id=self.request.user.tenant_id, + groupby=['tenant_id', 'res_type'])['summary'] + summary = api.identify(summary, key='res_type', name=True) + summary.append(TemplatizableDict({ + 'id': 'ALL', + 'res_type': 'TOTAL', + 'name': 'ALL', + 'rate': sum([float(i['rate']) for i in summary]), + })) + return summary def quote(request): @@ -42,8 +51,7 @@ def quote(request): json_data = json.loads(request.body) try: pricing = decimal.Decimal(api.cloudkittyclient(request) - .quotations.quote(json_data)) - pricing = pricing.normalize().to_eng_string() + .rating.get_quotation(json_data)) except Exception: exceptions.handle(request, _('Unable to retrieve price.')) diff --git a/cloudkittydashboard/enabled/_11_admin_summary_panel.py b/cloudkittydashboard/enabled/_11_admin_summary_panel.py new file mode 100644 index 0000000..aa46f1b --- /dev/null +++ b/cloudkittydashboard/enabled/_11_admin_summary_panel.py @@ -0,0 +1,21 @@ +# Copyright 2018 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_summary' + +# Python panel class of the PANEL to be added. +ADD_PANEL = \ + 'cloudkittydashboard.dashboards.admin.summary.panel.Summary' diff --git a/releasenotes/notes/improve-total-tab-96df42d1e562fc4f.yaml b/releasenotes/notes/improve-total-tab-96df42d1e562fc4f.yaml new file mode 100644 index 0000000..7203e17 --- /dev/null +++ b/releasenotes/notes/improve-total-tab-96df42d1e562fc4f.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + An "Admin/Rating Summary" tab has been added. An admin user can now have + the cost of every rated tenant at once. By clicking on a tenant, a + per-resource total for the given tenant can be obtained (this view is + similar to the "Project/Rating" tab). A per-resource total for the whole + cloud is also available. + +upgrade: + - | + The "Project/Rating" tab has been improved: it does now provide a total + by metric type. This make use of the /summary endpoint instead of /total + (/total is deprecated).