Skip to content

Commit 4fe668c

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Enhance rating dashboard with summary cards and admin manage panel"
2 parents c373ef0 + bf9df5a commit 4fe668c

9 files changed

Lines changed: 485 additions & 97 deletions

File tree

cloudkittydashboard/dashboards/admin/hashmap/templates/hashmap/services_list.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,35 @@
1111
{{ table.render }}
1212

1313
{{ modules }}
14+
15+
<h3>{% trans "Applied Rating Rules" %}</h3>
16+
<table class="table table-striped table-hover datatable">
17+
<thead>
18+
<tr>
19+
<th>{% trans "Service" %}</th>
20+
<th>{% trans "Field" %}</th>
21+
<th>{% trans "Value" %}</th>
22+
<th>{% trans "Type" %}</th>
23+
<th>{% trans "Cost" %}</th>
24+
</tr>
25+
</thead>
26+
<tbody>
27+
{% for rule in rating_rules %}
28+
<tr>
29+
<td>{{ rule.service }}</td>
30+
<td>{{ rule.field }}</td>
31+
<td>{{ rule.value }}</td>
32+
<td>{{ rule.type }}</td>
33+
<td>{{ rule.cost_display }}</td>
34+
</tr>
35+
{% empty %}
36+
<tr>
37+
<td colspan="5" class="text-center">{% trans "No rating rules configured" %}</td>
38+
</tr>
39+
{% endfor %}
40+
</tbody>
41+
</table>
42+
1443
{% endblock %}
1544

1645

cloudkittydashboard/dashboards/admin/hashmap/views.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,102 @@
1313
# under the License.
1414

1515

16-
from cloudkittyclient import exc as ck_exc
16+
from django.conf import settings
1717
from django.urls import reverse
1818
from django.urls import reverse_lazy
1919
from django.utils.translation import gettext_lazy as _
2020
from horizon import forms
2121
from horizon import tables
2222
from horizon import tabs
2323
from horizon import views
24-
from keystoneauth1 import exceptions
2524

2625
from cloudkittydashboard.api import cloudkitty as api
2726
from cloudkittydashboard.dashboards.admin.hashmap import forms as hashmap_forms
2827
from cloudkittydashboard.dashboards.admin.hashmap \
2928
import tables as hashmap_tables
29+
from cloudkittydashboard import utils
30+
31+
rate_prefix = getattr(settings,
32+
'OPENSTACK_CLOUDKITTY_RATE_PREFIX', None)
33+
rate_postfix = getattr(settings,
34+
'OPENSTACK_CLOUDKITTY_RATE_POSTFIX', None)
3035

3136

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

41+
def _get_rating_rules(self):
42+
"""Fetch all hashmap rating rules (services, fields, and mappings)."""
43+
try:
44+
client = api.cloudkittyclient(self.request, version='1')
45+
hashmap = client.rating.hashmap
46+
rating_rules = []
47+
48+
# Get all services
49+
services_response = hashmap.get_service()
50+
services = services_response.get('services', [])
51+
52+
for service in services:
53+
service_id = service.get('service_id')
54+
service_name = service.get('name', 'Unknown')
55+
56+
# Get service-level mappings (no field)
57+
try:
58+
service_mappings = hashmap.get_mapping(
59+
service_id=service_id)
60+
for mapping in service_mappings.get('mappings', []):
61+
cost = float(mapping.get('cost', 0))
62+
rating_rules.append({
63+
'service': service_name,
64+
'field': '-',
65+
'value': mapping.get('value') or '(all)',
66+
'type': mapping.get('type', 'flat'),
67+
'cost': cost,
68+
'cost_display': utils.formatRate(
69+
cost, rate_prefix, rate_postfix),
70+
})
71+
except Exception:
72+
pass
73+
74+
# Get fields for this service
75+
try:
76+
fields_response = hashmap.get_field(service_id=service_id)
77+
fields = fields_response.get('fields', [])
78+
79+
for field in fields:
80+
field_id = field.get('field_id')
81+
field_name = field.get('name', 'Unknown')
82+
83+
# Get field-level mappings
84+
try:
85+
field_mappings = hashmap.get_mapping(
86+
field_id=field_id)
87+
for mapping in field_mappings.get('mappings', []):
88+
cost = float(mapping.get('cost', 0))
89+
rating_rules.append({
90+
'service': service_name,
91+
'field': field_name,
92+
'value': mapping.get('value') or '(all)',
93+
'type': mapping.get('type', 'flat'),
94+
'cost': cost,
95+
'cost_display': utils.formatRate(
96+
cost, rate_prefix, rate_postfix),
97+
})
98+
except Exception:
99+
pass
100+
except Exception:
101+
pass
102+
103+
return rating_rules
104+
except Exception:
105+
return []
106+
107+
def get_context_data(self, **kwargs):
108+
context = super(IndexView, self).get_context_data(**kwargs)
109+
context['rating_rules'] = self._get_rating_rules()
110+
return context
111+
36112
def get_data(self):
37113
manager = api.cloudkittyclient(self.request)
38114
services = manager.rating.hashmap.get_service().get('services', [])
@@ -42,7 +118,7 @@ def get_data(self):
42118
try:
43119
service = manager.info.get_metric(metric_name=s['name'])
44120
unit = service['unit']
45-
except (exceptions.NotFound, ck_exc.HTTPNotFound):
121+
except Exception:
46122
unit = "-"
47123

48124
list_services.append({

cloudkittydashboard/dashboards/project/rating/templates/rating/groupby.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
<link rel="stylesheet" href="{% static 'cloudkitty/css/grouping.css' %}">
1414
<script src="{% static 'cloudkitty/js/grouping.js' %}" type="text/javascript" charset="utf-8"></script>
1515

16-
<form method="GET" action="?" id="groupby_checkbox" class="groupby-form">
17-
<h4>{% trans "Group by:" %}</h4>
16+
<form method="GET" action="?" id="groupby_checkbox" class="groupby-form groupby-inline">
17+
<strong>{% trans "Group by:" %}</strong>
1818
<div id="checkboxes"></div>
19-
<button class="btn btn-primary" id="toggleAll" type="button">
19+
<button class="btn btn-primary btn-sm" id="toggleAll" type="button">
2020
{% trans "Select All" %}
2121
</button>
22-
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
22+
<button class="btn btn-primary btn-sm" type="submit">{% trans "Submit" %}</button>
2323
</form>
Lines changed: 154 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,163 @@
11
{% extends 'base.html' %}
22
{% load i18n %}
3-
{% block title %}{% trans "Rating Summary" %}{% endblock %}
3+
{% block title %}{% trans "Rating Dashboard" %}{% endblock %}
44

55
{% block page_header %}
6-
{% include "horizon/common/_page_header.html" with title=_("Rating Summary") %}
6+
{% include "horizon/common/_page_header.html" with title=_("Rating Dashboard") %}
77
{% endblock page_header %}
88

99
{% block main %}
10-
{{ groupby_list|json_script:"groupby_list_config" }}
11-
{% include "project/rating/groupby.html" %}
12-
{{ table.render }}
1310

14-
{{ modules }}
11+
<!-- Summary Cards -->
12+
<div class="row">
13+
<div class="col-sm-4">
14+
<div class="panel panel-default">
15+
<div class="panel-heading">
16+
<h3 class="panel-title">{% trans "Current Month" %}</h3>
17+
<small class="text-muted">{{ current_month_name }}</small>
18+
</div>
19+
<div class="panel-body">
20+
<p class="h2" style="margin-top: 5px;">{{ current_month_total }}</p>
21+
<small class="text-muted">{% trans "Day" %} {{ days_elapsed }} {% trans "of" %} {{ days_in_month }}</small>
22+
</div>
23+
</div>
24+
</div>
25+
26+
<div class="col-sm-4">
27+
<div class="panel panel-default">
28+
<div class="panel-heading">
29+
<h3 class="panel-title">{% trans "Forecasted Month End" %}</h3>
30+
<small class="text-muted">{% trans "Based on current usage" %}</small>
31+
</div>
32+
<div class="panel-body">
33+
<p class="h2" style="margin-top: 5px;">{{ forecast_total }}</p>
34+
<small class="text-muted">{% trans "Projected total" %}</small>
35+
</div>
36+
</div>
37+
</div>
38+
39+
<div class="col-sm-4">
40+
<div class="panel panel-default">
41+
<div class="panel-heading">
42+
<h3 class="panel-title">{% trans "Last Month" %}</h3>
43+
<small class="text-muted">{{ last_month_name }}</small>
44+
</div>
45+
<div class="panel-body">
46+
<p class="h2" style="margin-top: 5px;">{{ last_month_total }}</p>
47+
<small class="text-muted">{% trans "Total spent" %}</small>
48+
</div>
49+
</div>
50+
</div>
51+
</div>
52+
53+
<!-- Cost Breakdown Section -->
54+
<div class="row">
55+
<div class="col-sm-6">
56+
<div class="panel panel-default">
57+
<div class="panel-heading">
58+
<h3 class="panel-title">{% trans "Current Month Breakdown" %}</h3>
59+
</div>
60+
<div class="panel-body">
61+
<table class="table table-striped table-hover">
62+
<thead>
63+
<tr>
64+
<th>{% trans "Metric Type" %}</th>
65+
<th>{% trans "Cost" %}</th>
66+
<th>{% trans "Percentage" %}</th>
67+
</tr>
68+
</thead>
69+
<tbody>
70+
{% for item in breakdown_data %}
71+
<tr>
72+
<td>{{ item.type }}</td>
73+
<td>{{ item.rate_display }}</td>
74+
<td>
75+
<div class="progress" style="margin-bottom: 0; min-width: 100px; background-color: #e9ecef;">
76+
<div class="progress-bar" role="progressbar"
77+
style="width: {{ item.percentage_css }}%; background-color: #337ab7;"
78+
aria-valuenow="{{ item.percentage }}"
79+
aria-valuemin="0"
80+
aria-valuemax="100">
81+
{{ item.percentage }}%
82+
</div>
83+
</div>
84+
</td>
85+
</tr>
86+
{% empty %}
87+
<tr>
88+
<td colspan="3" class="text-center">{% trans "No data available" %}</td>
89+
</tr>
90+
{% endfor %}
91+
</tbody>
92+
</table>
93+
</div>
94+
</div>
95+
</div>
96+
97+
<div class="col-sm-6">
98+
<div class="panel panel-default">
99+
<div class="panel-heading">
100+
<h3 class="panel-title">{% trans "Costs Breakdown Comparison" %}</h3>
101+
</div>
102+
<div class="panel-body">
103+
<div id="breakdown-chart" style="height: 300px;">
104+
{% for item in breakdown_data %}
105+
<div style="margin-bottom: 10px;">
106+
<div style="display: flex; justify-content: space-between; margin-bottom: 2px;">
107+
<span>{{ item.type }}</span>
108+
<span>{{ item.rate_display }}</span>
109+
</div>
110+
<div class="progress" style="height: 25px; background-color: #e9ecef;">
111+
<div class="progress-bar" role="progressbar"
112+
style="width: {{ item.percentage_css }}%; line-height: 25px; background-color: #5bc0de;"
113+
aria-valuenow="{{ item.percentage }}"
114+
aria-valuemin="0"
115+
aria-valuemax="100">
116+
</div>
117+
</div>
118+
</div>
119+
{% empty %}
120+
<p class="text-center text-muted">{% trans "No data available for chart" %}</p>
121+
{% endfor %}
122+
</div>
123+
</div>
124+
</div>
125+
</div>
126+
</div>
127+
128+
<!-- Top Cost Generators -->
129+
<div class="row">
130+
<div class="col-sm-12">
131+
<div class="panel panel-default">
132+
<div class="panel-heading">
133+
<h3 class="panel-title">{% trans "Top Cost Generators" %}</h3>
134+
</div>
135+
<div class="panel-body">
136+
<table class="table table-striped table-hover datatable">
137+
<thead>
138+
<tr>
139+
<th>{% trans "Resource ID" %}</th>
140+
<th>{% trans "Type" %}</th>
141+
<th>{% trans "Current Cost" %}</th>
142+
</tr>
143+
</thead>
144+
<tbody>
145+
{% for resource in top_resources %}
146+
<tr>
147+
<td>{{ resource.resource_id|default:"-" }}</td>
148+
<td>{{ resource.type|default:"-" }}</td>
149+
<td>{{ resource.rate_display }}</td>
150+
</tr>
151+
{% empty %}
152+
<tr>
153+
<td colspan="3" class="text-center">{% trans "No resources found" %}</td>
154+
</tr>
155+
{% endfor %}
156+
</tbody>
157+
</table>
158+
</div>
159+
</div>
160+
</div>
161+
</div>
162+
15163
{% endblock %}

0 commit comments

Comments
 (0)