usage cost page using tab styling (#1)
* usage cost page using tab styling * refactor admin usage cost page
This commit is contained in:
parent
c5e8b01793
commit
15a794fe97
7 changed files with 479 additions and 4 deletions
|
@ -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 %}
|
||||
|
||||
<div id="usage_cost">
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-md-6">
|
||||
Invoice Month <b>{{ invoice.start_date|date:"M Y" }}</b>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<span class="pull-right" data-html2canvas-ignore="true">
|
||||
{% include 'admin/projects_invoice/manage_invoice_modal.html' with invoice=invoice project_balance_amount=project_balance_amount %}
|
||||
<button onclick="javascript:downloadPdf();" class="btn btn-default">Download PDF</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Invoice State</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2> {{ invoice.state_text }} </h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Subtotal</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2>{{ invoice.subtotal_money }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if invoice.state != 1 %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Tax</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2> {{ invoice.tax_money }} </h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Total</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2>{{ invoice.total_money }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
<h1>Billing not enabled or you don't have any usage yet</h1> <br/>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"
|
||||
integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function downloadPdf() {
|
||||
let opt = {
|
||||
filename: 'usage_cost.pdf',
|
||||
margin: [16, 16],
|
||||
enableLinks: true,
|
||||
image: {type: 'jpeg', quality: 0.98},
|
||||
pagebreak: { mode: 'avoid-all', },
|
||||
jsPDF: {unit: 'mm', format: 'a4', orientation: 'portrait'},
|
||||
}
|
||||
|
||||
// clone element to modify
|
||||
const ucElement = document.getElementById('usage_cost');
|
||||
const ucElementClone = ucElement.cloneNode(true);
|
||||
|
||||
// modify element
|
||||
const navTabs = ucElementClone.querySelector('.nav-tabs');
|
||||
navTabs.style.display = 'none';
|
||||
const tabContent = ucElementClone.querySelectorAll('.tab-pane');
|
||||
tabContent.forEach(content => {
|
||||
content.classList.remove("tab-pane");
|
||||
});
|
||||
|
||||
html2pdf().set(opt).from(ucElementClone).save();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -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<project_id>[^/]+)/(?P<id>[^/]+)/$', views.InvoiceView.as_view(), name='invoice_detail'),
|
||||
url(r'^invoice/usage/(?P<project_id>[^/]+)/(?P<id>[^/]+)/$', views.UsageCostView.as_view(), name='usage_cost'),
|
||||
url(r'^invoice/usage/(?P<project_id>[^/]+)/(?P<id>[^/]+)/$', views.UsageCostTabView.as_view(), name='usage_cost'),
|
||||
url(r'^invoice/finish/(?P<id>[^/]+)/$', views.FinishInvoice.as_view(), name='finish_invoice'),
|
||||
url(r'^invoice/rollback_to_unpaid/(?P<id>[^/]+)/$', views.RollbackToUnpaidInvoice.as_view(),
|
||||
name='rollback_to_unpaid'),
|
||||
|
|
|
@ -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)
|
||||
|
|
181
yuyu/core/usage_cost/tabs.py
Normal file
181
yuyu/core/usage_cost/tabs.py
Normal file
|
@ -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
|
122
yuyu/project/usage_cost/templates/usage_cost/index_tabs.html
Normal file
122
yuyu/project/usage_cost/templates/usage_cost/index_tabs.html
Normal file
|
@ -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 %}
|
||||
<div id="usage_cost">
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-md-6">
|
||||
|
||||
Invoice Month
|
||||
<select id="invoice_select" onchange="onInvoiceChange(this.value)">
|
||||
{% for i in invoice_list %}
|
||||
<option value="{{ i.id }}" {% if i.id == invoice.id %}
|
||||
selected {% endif %}>{{ i.start_date|date:"M Y" }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<button onclick="javascript:downloadPdf();" class="btn btn-default pull-right" data-html2canvas-ignore="true">Download PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Invoice State</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2> {{ invoice.state_text }} </h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Subtotal</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2>{{ invoice.subtotal_money }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if invoice.state != 1 %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Tax</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2> {{ invoice.tax_money }} </h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Total</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h2>{{ invoice.total_money }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
<h1>You don't have any usage yet</h1> <br/>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"
|
||||
integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function onInvoiceChange(val) {
|
||||
var search = "?invoice_id=" + val;
|
||||
window.location.href = window.location.protocol + "//" + window.location.host + window.location.pathname + search;
|
||||
}
|
||||
|
||||
function downloadPdf() {
|
||||
let opt = {
|
||||
filename: 'usage_cost.pdf',
|
||||
margin: [16, 16],
|
||||
enableLinks: true,
|
||||
image: {type: 'jpeg', quality: 0.98},
|
||||
pagebreak: { mode: 'avoid-all', },
|
||||
jsPDF: {unit: 'mm', format: 'a4', orientation: 'portrait'},
|
||||
}
|
||||
|
||||
// clone element to modify
|
||||
const ucElement = document.getElementById('usage_cost');
|
||||
const ucElementClone = ucElement.cloneNode(true);
|
||||
|
||||
// modify element
|
||||
const navTabs = ucElementClone.querySelector('.nav-tabs');
|
||||
navTabs.style.display = 'none';
|
||||
const tabContent = ucElementClone.querySelectorAll('.tab-pane');
|
||||
tabContent.forEach(content => {
|
||||
content.classList.remove("tab-pane");
|
||||
});
|
||||
|
||||
html2pdf().set(opt).from(ucElementClone).save();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue