usage cost page using tab styling (#1)

* usage cost page using tab styling

* refactor admin usage cost page
This commit is contained in:
Muhammad Irfan 2023-05-18 19:48:00 +07:00 committed by GitHub
parent c5e8b01793
commit 15a794fe97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 479 additions and 4 deletions

View file

@ -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 %}

View file

@ -14,10 +14,11 @@ from django.conf.urls import url
from openstack_dashboard.dashboards.yuyu.admin.projects_invoice import views from openstack_dashboard.dashboards.yuyu.admin.projects_invoice import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'), 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/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/finish/(?P<id>[^/]+)/$', views.FinishInvoice.as_view(), name='finish_invoice'),
url(r'^invoice/rollback_to_unpaid/(?P<id>[^/]+)/$', views.RollbackToUnpaidInvoice.as_view(), url(r'^invoice/rollback_to_unpaid/(?P<id>[^/]+)/$', views.RollbackToUnpaidInvoice.as_view(),
name='rollback_to_unpaid'), name='rollback_to_unpaid'),

View file

@ -24,6 +24,8 @@ from djmoney.money import Money
from horizon import exceptions from horizon import exceptions
from horizon import tables from horizon import tables
from horizon import views from horizon import views
from horizon import tabs
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase
from .tables import InvoiceTable from .tables import InvoiceTable
@ -31,6 +33,7 @@ from ...cases.balance_use_case import BalanceUseCase
from ...cases.setting_use_case import SettingUseCase from ...cases.setting_use_case import SettingUseCase
from ...core.usage_cost.tables import InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, \ from ...core.usage_cost.tables import InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, \
SnapshotCostTable, ImageCostTable SnapshotCostTable, ImageCostTable
from ...core.usage_cost.tabs import UsageCostTabs
from ...core.utils.invoice_utils import state_to_text from ...core.utils.invoice_utils import state_to_text
@ -106,6 +109,31 @@ class InvoiceView(views.APIView):
return sum(instance_prices) 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): class UsageCostView(tables.MultiTableView):
table_classes = ( table_classes = (
InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable) InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable)

View 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

View 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 %}

View file

@ -16,5 +16,5 @@ from openstack_dashboard.dashboards.yuyu.project.usage_cost import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'), url(r'^$', views.IndexViewTab.as_view(), name='index'),
] ]

View file

@ -14,12 +14,41 @@ from django.utils.timesince import timesince
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from djmoney.money import Money from djmoney.money import Money
from horizon import exceptions from horizon import exceptions, tables, tabs
from horizon import tables
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase
from openstack_dashboard.dashboards.yuyu.core.usage_cost.tables import InstanceCostTable, VolumeCostTable, \ from openstack_dashboard.dashboards.yuyu.core.usage_cost.tables import InstanceCostTable, VolumeCostTable, \
FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable 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): class IndexView(tables.MultiTableView):