diff --git a/yuyu/admin/projects_invoice/templates/projects_invoice/cost_tabs.html b/yuyu/admin/projects_invoice/templates/projects_invoice/cost_tabs.html
new file mode 100644
index 0000000..e7c51db
--- /dev/null
+++ b/yuyu/admin/projects_invoice/templates/projects_invoice/cost_tabs.html
@@ -0,0 +1,114 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Usage Cost" %}{% endblock %}
+
+{% block main %}
+ {% include "admin/price_configuration/missing_prices.html" %}
+ {% if invoice %}
+
+
+
+
+ Invoice Month {{ invoice.start_date|date:"M Y" }}
+
+
+
+ {% include 'admin/projects_invoice/manage_invoice_modal.html' with invoice=invoice project_balance_amount=project_balance_amount %}
+
+
+
+
+
+
+
+
+
+
Invoice State
+
+
+
{{ invoice.state_text }}
+
+
+
+
+
+
+
Subtotal
+
+
+
{{ invoice.subtotal_money }}
+
+
+
+
+
+ {% if invoice.state != 1 %}
+
+
+
+
+
Tax
+
+
+
{{ invoice.tax_money }}
+
+
+
+
+
+
+
Total
+
+
+
{{ invoice.total_money }}
+
+
+
+
+ {% endif %}
+
+
+
+ {{ tab_group.render }}
+
+
+
+
+ {% else %}
+ Billing not enabled or you don't have any usage yet
+ {% endif %}
+{% endblock %}
+
+{% block js %}
+ {{ block.super }}
+
+
+
+{% endblock %}
diff --git a/yuyu/admin/projects_invoice/urls.py b/yuyu/admin/projects_invoice/urls.py
index 13ea78d..18cf879 100644
--- a/yuyu/admin/projects_invoice/urls.py
+++ b/yuyu/admin/projects_invoice/urls.py
@@ -14,10 +14,11 @@ from django.conf.urls import url
from openstack_dashboard.dashboards.yuyu.admin.projects_invoice import views
+
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^invoice/detail/(?P[^/]+)/(?P[^/]+)/$', views.InvoiceView.as_view(), name='invoice_detail'),
- url(r'^invoice/usage/(?P[^/]+)/(?P[^/]+)/$', views.UsageCostView.as_view(), name='usage_cost'),
+ url(r'^invoice/usage/(?P[^/]+)/(?P[^/]+)/$', views.UsageCostTabView.as_view(), name='usage_cost'),
url(r'^invoice/finish/(?P[^/]+)/$', views.FinishInvoice.as_view(), name='finish_invoice'),
url(r'^invoice/rollback_to_unpaid/(?P[^/]+)/$', views.RollbackToUnpaidInvoice.as_view(),
name='rollback_to_unpaid'),
diff --git a/yuyu/admin/projects_invoice/views.py b/yuyu/admin/projects_invoice/views.py
index 118a389..932c894 100644
--- a/yuyu/admin/projects_invoice/views.py
+++ b/yuyu/admin/projects_invoice/views.py
@@ -24,6 +24,8 @@ from djmoney.money import Money
from horizon import exceptions
from horizon import tables
from horizon import views
+from horizon import tabs
+
from openstack_dashboard import api
from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase
from .tables import InvoiceTable
@@ -31,6 +33,7 @@ from ...cases.balance_use_case import BalanceUseCase
from ...cases.setting_use_case import SettingUseCase
from ...core.usage_cost.tables import InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, \
SnapshotCostTable, ImageCostTable
+from ...core.usage_cost.tabs import UsageCostTabs
from ...core.utils.invoice_utils import state_to_text
@@ -106,6 +109,31 @@ class InvoiceView(views.APIView):
return sum(instance_prices)
+class UsageCostTabView(tabs.TabbedTableView):
+ tab_group_class = UsageCostTabs
+ page_title = _("Usage Cost")
+ template_name = "admin/projects_invoice/cost_tabs.html"
+
+ invoice_uc = InvoiceUseCase()
+ balance_uc = BalanceUseCase()
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ def get(self, request, *args, **kwargs):
+ request.invoice = self.invoice_uc.get_invoice(self.request, self.kwargs['id'],
+ tenant_id=self.kwargs['project_id'])
+ return super().get(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ balance = self.balance_uc.retrieve_by_project(self.request, self.kwargs['project_id'])
+ context['invoice'] = self.request.invoice
+ context['project_balance_amount'] = Money(amount=balance['amount'],
+ currency=balance['amount_currency']) if balance else 0
+ return context
+
+
class UsageCostView(tables.MultiTableView):
table_classes = (
InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable)
diff --git a/yuyu/core/usage_cost/tabs.py b/yuyu/core/usage_cost/tabs.py
new file mode 100644
index 0000000..a511181
--- /dev/null
+++ b/yuyu/core/usage_cost/tabs.py
@@ -0,0 +1,181 @@
+import dateutil.parser
+from django.utils.timesince import timesince
+from django.utils.translation import ugettext_lazy as _
+from djmoney.money import Money
+
+from horizon import exceptions, tabs
+from openstack_dashboard import api
+from openstack_dashboard.dashboards.yuyu.core.usage_cost.tables import (
+ InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable
+)
+
+
+class InstanceTab(tabs.TableTab):
+ table_classes = (InstanceCostTable,)
+ name = _("Instance")
+ slug = "instance"
+ template_name = 'horizon/common/_detail_table.html'
+
+ def _get_flavor_name(self, flavor_id):
+ try:
+ return api.nova.flavor_get(self.request, flavor_id).name
+ except Exception:
+ return 'Invalid Flavor'
+
+ def get_instance_cost_data(self):
+ try:
+ datas = map(lambda x: {
+ "id": x['id'],
+ "name": x['name'],
+ "flavor": self._get_flavor_name(x['flavor_id']),
+ "usage": timesince(
+ dateutil.parser.isoparse(x['start_date']),
+ dateutil.parser.isoparse(x['adjusted_end_date'])
+ ),
+ "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency'])
+ }, self.request.invoice.get('instances', []))
+ return datas
+ except Exception:
+ error_message = _('Unable to get instance cost')
+ exceptions.handle(self.request, error_message)
+ return []
+
+
+class VolumeTab(tabs.TableTab):
+ table_classes = (VolumeCostTable,)
+ name = _("Volume")
+ slug = "volume"
+ template_name = 'horizon/common/_detail_table.html'
+
+ def _get_volume_name(self, volume_type_id):
+ try:
+ return api.cinder.volume_type_get(self.request, volume_type_id).name
+ except Exception:
+ return 'Invalid Volume'
+
+ def get_volume_cost_data(self):
+ try:
+ datas = map(lambda x: {
+ "id": x['id'],
+ "name": x['volume_name'],
+ "usage": timesince(
+ dateutil.parser.isoparse(x['start_date']),
+ dateutil.parser.isoparse(x['adjusted_end_date'])
+ ),
+ 'type': self._get_volume_name(x['volume_type_id']),
+ 'size': x['space_allocation_gb'],
+ "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency'])
+ }, self.request.invoice.get('volumes', []))
+ return datas
+ except Exception:
+ error_message = _('Unable to get volume cost')
+ exceptions.handle(self.request, error_message)
+ return []
+
+
+class FloatingIpTab(tabs.TableTab):
+ table_classes = (FloatingIpCostTable,)
+ name = _("Floating IP")
+ slug = "floating_ip"
+ template_name = 'horizon/common/_detail_table.html'
+
+ def get_floating_ip_cost_data(self):
+ try:
+ datas = map(lambda x: {
+ "id": x['id'],
+ "name": x['ip'],
+ "usage": timesince(
+ dateutil.parser.isoparse(x['start_date']),
+ dateutil.parser.isoparse(x['adjusted_end_date'])
+ ),
+ "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency'])
+ }, self.request.invoice.get('floating_ips', []))
+
+ return datas
+ except Exception:
+ error_message = _('Unable to get floating ip cost')
+ exceptions.handle(self.request, error_message)
+ return []
+
+
+class RouterTab(tabs.TableTab):
+ table_classes = (RouterCostTable,)
+ name = _("Router")
+ slug = "router"
+ template_name = 'horizon/common/_detail_table.html'
+
+ def get_router_cost_data(self):
+ try:
+ datas = map(lambda x: {
+ "id": x['id'],
+ "name": x['name'],
+ "usage": timesince(
+ dateutil.parser.isoparse(x['start_date']),
+ dateutil.parser.isoparse(x['adjusted_end_date'])
+ ),
+ "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency'])
+ }, self.request.invoice.get('routers', []))
+
+ return datas
+ except Exception:
+ error_message = _('Unable to get router cost')
+ exceptions.handle(self.request, error_message)
+ return []
+
+
+class SnapshotTab(tabs.TableTab):
+ table_classes = (SnapshotCostTable,)
+ name = _("Snapshot")
+ slug = "snapshot"
+ template_name = 'horizon/common/_detail_table.html'
+
+ def get_snapshot_cost_data(self):
+ try:
+ datas = map(lambda x: {
+ "id": x['id'],
+ "name": x['name'],
+ 'size': x['space_allocation_gb'],
+ "usage": timesince(
+ dateutil.parser.isoparse(x['start_date']),
+ dateutil.parser.isoparse(x['adjusted_end_date'])
+ ),
+ "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency'])
+ }, self.request.invoice.get('snapshots', []))
+
+ return datas
+ except Exception:
+ error_message = _('Unable to get snapshot cost')
+ exceptions.handle(self.request, error_message)
+ return []
+
+
+class ImageTab(tabs.TableTab):
+ table_classes = (ImageCostTable,)
+ name = _("Image")
+ slug = "image"
+ template_name = 'horizon/common/_detail_table.html'
+
+ def get_image_cost_data(self):
+ try:
+ datas = map(lambda x: {
+ "id": x['id'],
+ "name": x['name'],
+ 'size': x['space_allocation_gb'],
+ "usage": timesince(
+ dateutil.parser.isoparse(x['start_date']),
+ dateutil.parser.isoparse(x['adjusted_end_date'])
+ ),
+ "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency'])
+ }, self.request.invoice.get('images', []))
+
+ return datas
+ except Exception:
+ error_message = _('Unable to get images cost')
+ exceptions.handle(self.request, error_message)
+ return []
+
+
+class UsageCostTabs(tabs.TabGroup):
+ slug = "usage_cost"
+ tabs = (InstanceTab, VolumeTab, FloatingIpTab, RouterTab, SnapshotTab, ImageTab, )
+ sticky = True
diff --git a/yuyu/project/usage_cost/templates/usage_cost/index_tabs.html b/yuyu/project/usage_cost/templates/usage_cost/index_tabs.html
new file mode 100644
index 0000000..7aa9036
--- /dev/null
+++ b/yuyu/project/usage_cost/templates/usage_cost/index_tabs.html
@@ -0,0 +1,122 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Usage Cost" %}{% endblock %}
+
+{% block main %}
+ {% include "admin/price_configuration/missing_prices.html" %}
+ {% if invoice %}
+
+
+
+
+ Invoice Month
+
+
+
+
+
+
+
+
+
+
+
+
Invoice State
+
+
+
{{ invoice.state_text }}
+
+
+
+
+
+
+
Subtotal
+
+
+
{{ invoice.subtotal_money }}
+
+
+
+
+
+ {% if invoice.state != 1 %}
+
+
+
+
+
Tax
+
+
+
{{ invoice.tax_money }}
+
+
+
+
+
+
+
Total
+
+
+
{{ invoice.total_money }}
+
+
+
+
+ {% endif %}
+
+
+
+ {{ tab_group.render }}
+
+
+
+
+ {% else %}
+ You don't have any usage yet
+ {% endif %}
+{% endblock %}
+
+{% block js %}
+ {{ block.super }}
+
+
+
+{% endblock %}
diff --git a/yuyu/project/usage_cost/urls.py b/yuyu/project/usage_cost/urls.py
index 3892a28..b57a22b 100644
--- a/yuyu/project/usage_cost/urls.py
+++ b/yuyu/project/usage_cost/urls.py
@@ -16,5 +16,5 @@ from openstack_dashboard.dashboards.yuyu.project.usage_cost import views
urlpatterns = [
- url(r'^$', views.IndexView.as_view(), name='index'),
+ url(r'^$', views.IndexViewTab.as_view(), name='index'),
]
diff --git a/yuyu/project/usage_cost/views.py b/yuyu/project/usage_cost/views.py
index d9bda72..2953609 100644
--- a/yuyu/project/usage_cost/views.py
+++ b/yuyu/project/usage_cost/views.py
@@ -14,14 +14,43 @@ from django.utils.timesince import timesince
from django.utils.translation import ugettext_lazy as _
from djmoney.money import Money
-from horizon import exceptions
-from horizon import tables
+from horizon import exceptions, tables, tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase
from openstack_dashboard.dashboards.yuyu.core.usage_cost.tables import InstanceCostTable, VolumeCostTable, \
FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable
+from openstack_dashboard.dashboards.yuyu.core.usage_cost.tabs import UsageCostTabs
+class IndexViewTab(tabs.TabbedTableView):
+
+ tab_group_class = UsageCostTabs
+ page_title = _("Usage Cost")
+ template_name = "project/usage_cost/index_tabs.html"
+
+ invoice_uc = InvoiceUseCase()
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ def get(self, request, *args, **kwargs):
+ request.invoice_list = self.invoice_uc.get_simple_list(self.request)
+ request.has_invoice = len(request.invoice_list) != 0
+
+ invoice_id = self.request.GET.get('invoice_id', None)
+ if not invoice_id and request.has_invoice:
+ invoice_id = request.invoice_list[0]['id']
+
+ request.invoice = self.invoice_uc.get_invoice(self.request, invoice_id) if request.has_invoice else {}
+ return super().get(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['invoice_list'] = self.request.invoice_list
+ context['invoice'] = self.request.invoice
+ return context
+
+
class IndexView(tables.MultiTableView):
table_classes = (
InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable)