Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,35 @@
{{ table.render }}

{{ modules }}

<h3>{% trans "Applied Rating Rules" %}</h3>
<table class="table table-striped table-hover datatable">
<thead>
<tr>
<th>{% trans "Service" %}</th>
<th>{% trans "Field" %}</th>
<th>{% trans "Value" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Cost" %}</th>
</tr>
</thead>
<tbody>
{% for rule in rating_rules %}
<tr>
<td>{{ rule.service }}</td>
<td>{{ rule.field }}</td>
<td>{{ rule.value }}</td>
<td>{{ rule.type }}</td>
<td>{{ rule.cost_display }}</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center">{% trans "No rating rules configured" %}</td>
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}


82 changes: 79 additions & 3 deletions cloudkittydashboard/dashboards/admin/hashmap/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,102 @@
# under the License.


from cloudkittyclient import exc as ck_exc
from django.conf import settings
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from horizon import forms
from horizon import tables
from horizon import tabs
from horizon import views
from keystoneauth1 import exceptions

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
from cloudkittydashboard import utils

rate_prefix = getattr(settings,
'OPENSTACK_CLOUDKITTY_RATE_PREFIX', None)
rate_postfix = getattr(settings,
'OPENSTACK_CLOUDKITTY_RATE_POSTFIX', None)


class IndexView(tables.DataTableView):
table_class = hashmap_tables.ServicesTable
template_name = "admin/hashmap/services_list.html"

def _get_rating_rules(self):
"""Fetch all hashmap rating rules (services, fields, and mappings)."""
try:
client = api.cloudkittyclient(self.request, version='1')
hashmap = client.rating.hashmap
rating_rules = []

# Get all services
services_response = hashmap.get_service()
services = services_response.get('services', [])

for service in services:
service_id = service.get('service_id')
service_name = service.get('name', 'Unknown')

# Get service-level mappings (no field)
try:
service_mappings = hashmap.get_mapping(
service_id=service_id)
for mapping in service_mappings.get('mappings', []):
cost = float(mapping.get('cost', 0))
rating_rules.append({
'service': service_name,
'field': '-',
'value': mapping.get('value') or '(all)',
'type': mapping.get('type', 'flat'),
'cost': cost,
'cost_display': utils.formatRate(
cost, rate_prefix, rate_postfix),
})
except Exception:
pass

# Get fields for this service
try:
fields_response = hashmap.get_field(service_id=service_id)
fields = fields_response.get('fields', [])

for field in fields:
field_id = field.get('field_id')
field_name = field.get('name', 'Unknown')

# Get field-level mappings
try:
field_mappings = hashmap.get_mapping(
field_id=field_id)
for mapping in field_mappings.get('mappings', []):
cost = float(mapping.get('cost', 0))
rating_rules.append({
'service': service_name,
'field': field_name,
'value': mapping.get('value') or '(all)',
'type': mapping.get('type', 'flat'),
'cost': cost,
'cost_display': utils.formatRate(
cost, rate_prefix, rate_postfix),
})
except Exception:
pass
except Exception:
pass

return rating_rules
except Exception:
return []

def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['rating_rules'] = self._get_rating_rules()
return context

def get_data(self):
manager = api.cloudkittyclient(self.request)
services = manager.rating.hashmap.get_service().get('services', [])
Expand All @@ -42,7 +118,7 @@ def get_data(self):
try:
service = manager.info.get_metric(metric_name=s['name'])
unit = service['unit']
except (exceptions.NotFound, ck_exc.HTTPNotFound):
except Exception:
unit = "-"

list_services.append({
Expand Down
31 changes: 29 additions & 2 deletions cloudkittydashboard/dashboards/admin/summary/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
# License for the specific language governing permissions and limitations
# under the License.

from django.conf import settings
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from horizon import tables

from cloudkittydashboard.utils import formatTitle


def get_details_link(datum):
if datum.tenant_id:
Expand All @@ -37,12 +40,36 @@ class Meta(object):


class TenantSummaryTable(tables.DataTable):
res_type = tables.Column('type', verbose_name=_("Resource Type"))
groupby_list = getattr(settings,
'OPENSTACK_CLOUDKITTY_GROUPBY_LIST',
['type'])

# Dynamically create columns based on groupby_list
for field in groupby_list:
locals()[field] = tables.Column(
field, verbose_name=_(formatTitle(field)))

rate = tables.Column('rate', verbose_name=_("Rate"))

def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
super().__init__(request, data, needs_form_wrapper, **kwargs)

# Hide columns based on checkbox selection
for field in self.groupby_list:
if request.GET.get(field) != 'true':
self.columns[field].classes = ['hidden']

class Meta(object):
name = "tenant_summary"
verbose_name = _("Project Summary")

def get_object_id(self, datum):
return datum.get('type')
# Prevents the table from displaying the same ID for different rows
id_parts = []
for field in self.groupby_list:
if field in datum and datum[field]:
id_parts.append(str(datum[field]))

if id_parts:
return '_'.join(id_parts)
return _('No IDs found')
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

{% trans "Project ID:" %} {{ project_id }}

{{ groupby_list|json_script:"groupby_list_config" }}
{% include "project/rating/groupby.html" %}
{{ table.render }}

{{ modules }}
Expand Down
41 changes: 29 additions & 12 deletions cloudkittydashboard/dashboards/admin/summary/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from cloudkittydashboard.dashboards.admin.summary import tables as sum_tables
from cloudkittydashboard import utils

from cloudkittydashboard import forms

rate_prefix = getattr(settings,
'OPENSTACK_CLOUDKITTY_RATE_PREFIX', None)
rate_postfix = getattr(settings,
Expand All @@ -35,8 +37,7 @@ class IndexView(tables.DataTableView):
def get_data(self):
summary = api.cloudkittyclient(
self.request, version='2').summary.get_summary(
groupby=['project_id'],
response_format='object')
groupby=['project_id'], response_format='object')

tenants, unused = api_keystone.tenant_list(self.request)
tenants = {tenant.id: tenant.name for tenant in tenants}
Expand All @@ -47,6 +48,7 @@ def get_data(self):
'project_id': 'ALL',
'rate': total,
})

data = api.identify(data, key='project_id')
for tenant in data:
tenant['tenant_id'] = tenant.get('project_id')
Expand All @@ -60,27 +62,42 @@ def get_data(self):
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_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['groupby_list'] = getattr(settings,
'OPENSTACK_CLOUDKITTY_GROUPBY_LIST',
['type'])
return context

def get_data(self):
tenant_id = self.kwargs['project_id']
form = forms.CheckBoxForm(self.request.GET)
groupby = form.get_selected_fields()

if tenant_id == 'ALL':
summary = api.cloudkittyclient(
self.request, version='2').summary.get_summary(
groupby=['type'], response_format='object')
self.request, version='2'
).summary.get_summary(groupby=groupby, response_format='object')
else:
summary = api.cloudkittyclient(
self.request, version='2').summary.get_summary(
filters={'project_id': tenant_id},
groupby=['type'], response_format='object')
self.request, version='2'
).summary.get_summary(
filters={'project_id': tenant_id},
groupby=groupby,
response_format='object',
)

data = summary.get('results')
total = sum([r.get('rate') for r in data])
data.append({'type': 'TOTAL', 'rate': total})

for item in data:
item['rate'] = utils.formatRate(item['rate'],
rate_prefix, rate_postfix)
if not groupby:
data = [{'type': 'TOTAL', 'rate': total}]

else:
data.append({'type': 'TOTAL', 'rate': total})
for item in data:
item['rate'] = utils.formatRate(
item['rate'], rate_prefix, rate_postfix)

return data
31 changes: 29 additions & 2 deletions cloudkittydashboard/dashboards/project/rating/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,47 @@
# 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 import settings
from django.utils.translation import gettext_lazy as _

from horizon import tables

from cloudkittydashboard.utils import formatTitle


class SummaryTable(tables.DataTable):
"""This table formats a summary for the given tenant."""

res_type = tables.Column('type', verbose_name=_('Metric Type'))
groupby_list = getattr(settings,
'OPENSTACK_CLOUDKITTY_GROUPBY_LIST',
['type'])

# Dynamically create columns based on groupby_list
for field in groupby_list:
locals()[field] = tables.Column(
field, verbose_name=_(formatTitle(field)))

rate = tables.Column('rate', verbose_name=_('Rate'))

def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
super().__init__(request, data, needs_form_wrapper, **kwargs)

# Hide columns based on checkbox selection
for field in self.groupby_list:
if request.GET.get(field) != 'true':
self.columns[field].classes = ['hidden']

class Meta(object):
name = "summary"
verbose_name = _("Summary")

def get_object_id(self, datum):
return datum.get('type')
# prevents the table from displaying the same ID for different rows
id_parts = []
for field in self.groupby_list:
if field in datum and datum[field]:
id_parts.append(str(datum[field]))

if id_parts:
return '_'.join(id_parts)
return _('No IDs found')
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% load i18n %}
{% load static %}

{{ groupby_list|json_script:"groupby_list_config" }}

<!-- For select/unselect button translations -->
<script id="groupby_translations" type="application/json">
{
"select_all": "{% trans 'Select All' %}",
"unselect_all": "{% trans 'Unselect All' %}"
}
</script>
<link rel="stylesheet" href="{% static 'cloudkitty/css/grouping.css' %}">
<script src="{% static 'cloudkitty/js/grouping.js' %}" type="text/javascript" charset="utf-8"></script>

<form method="GET" action="?" id="groupby_checkbox" class="groupby-form groupby-inline">
<strong>{% trans "Group by:" %}</strong>
<div id="checkboxes"></div>
<button class="btn btn-primary btn-sm" id="toggleAll" type="button">
{% trans "Select All" %}
</button>
<button class="btn btn-primary btn-sm" type="submit">{% trans "Submit" %}</button>
</form>
Loading