Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
<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">
<h4>{% trans "Group by:" %}</h4>
<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" id="toggleAll" type="button">
<button class="btn btn-primary btn-sm" id="toggleAll" type="button">
{% trans "Select All" %}
</button>
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
<button class="btn btn-primary btn-sm" type="submit">{% trans "Submit" %}</button>
</form>
Original file line number Diff line number Diff line change
@@ -1,15 +1,163 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Rating Summary" %}{% endblock %}
{% block title %}{% trans "Rating Dashboard" %}{% endblock %}

{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Rating Summary") %}
{% include "horizon/common/_page_header.html" with title=_("Rating Dashboard") %}
{% endblock page_header %}

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

{{ modules }}
<!-- Summary Cards -->
<div class="row">
<div class="col-sm-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Current Month" %}</h3>
<small class="text-muted">{{ current_month_name }}</small>
</div>
<div class="panel-body">
<p class="h2" style="margin-top: 5px;">{{ current_month_total }}</p>
<small class="text-muted">{% trans "Day" %} {{ days_elapsed }} {% trans "of" %} {{ days_in_month }}</small>
</div>
</div>
</div>

<div class="col-sm-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Forecasted Month End" %}</h3>
<small class="text-muted">{% trans "Based on current usage" %}</small>
</div>
<div class="panel-body">
<p class="h2" style="margin-top: 5px;">{{ forecast_total }}</p>
<small class="text-muted">{% trans "Projected total" %}</small>
</div>
</div>
</div>

<div class="col-sm-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Last Month" %}</h3>
<small class="text-muted">{{ last_month_name }}</small>
</div>
<div class="panel-body">
<p class="h2" style="margin-top: 5px;">{{ last_month_total }}</p>
<small class="text-muted">{% trans "Total spent" %}</small>
</div>
</div>
</div>
</div>

<!-- Cost Breakdown Section -->
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Current Month Breakdown" %}</h3>
</div>
<div class="panel-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans "Metric Type" %}</th>
<th>{% trans "Cost" %}</th>
<th>{% trans "Percentage" %}</th>
</tr>
</thead>
<tbody>
{% for item in breakdown_data %}
<tr>
<td>{{ item.type }}</td>
<td>{{ item.rate_display }}</td>
<td>
<div class="progress" style="margin-bottom: 0; min-width: 100px; background-color: #e9ecef;">
<div class="progress-bar" role="progressbar"
style="width: {{ item.percentage_css }}%; background-color: #337ab7;"
aria-valuenow="{{ item.percentage }}"
aria-valuemin="0"
aria-valuemax="100">
{{ item.percentage }}%
</div>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center">{% trans "No data available" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>

<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Costs Breakdown Comparison" %}</h3>
</div>
<div class="panel-body">
<div id="breakdown-chart" style="height: 300px;">
{% for item in breakdown_data %}
<div style="margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 2px;">
<span>{{ item.type }}</span>
<span>{{ item.rate_display }}</span>
</div>
<div class="progress" style="height: 25px; background-color: #e9ecef;">
<div class="progress-bar" role="progressbar"
style="width: {{ item.percentage_css }}%; line-height: 25px; background-color: #5bc0de;"
aria-valuenow="{{ item.percentage }}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
</div>
{% empty %}
<p class="text-center text-muted">{% trans "No data available for chart" %}</p>
{% endfor %}
</div>
</div>
</div>
</div>
</div>

<!-- Top Cost Generators -->
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Top Cost Generators" %}</h3>
</div>
<div class="panel-body">
<table class="table table-striped table-hover datatable">
<thead>
<tr>
<th>{% trans "Resource ID" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Current Cost" %}</th>
</tr>
</thead>
<tbody>
{% for resource in top_resources %}
<tr>
<td>{{ resource.resource_id|default:"-" }}</td>
<td>{{ resource.type|default:"-" }}</td>
<td>{{ resource.rate_display }}</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center">{% trans "No resources found" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>

{% endblock %}
Loading