diff options
author | Ladislav Smola <lsmola@redhat.com> | 2013-07-11 15:14:30 +0200 |
---|---|---|
committer | Tomas Sedovic <tomas@sedovic.cz> | 2013-08-01 16:18:56 +0200 |
commit | bdcd93ca01e179dd745a91db3f85b66bc94cb3e8 (patch) | |
tree | a24415c80e50c1a952e59955465f965f5438e9bc | |
parent | 936eb61b66c9d0c8590698f394994385361d9a7d (diff) | |
download | tuskar-ui-bdcd93ca01e179dd745a91db3f85b66bc94cb3e8.tar.gz |
Adding bar charts
- adding general bar chart
- configuration for width, height, horizontal/vertical,
multiple values
- configuration for popup message
- configuration for color scales
Change-Id: I89f0bfe24155be4ba9e2f9fa0e1406ce1e0bc3cc
9 files changed, 647 insertions, 6 deletions
diff --git a/horizon/static/horizon/js/horizon.d3singlebarchart.js b/horizon/static/horizon/js/horizon.d3singlebarchart.js new file mode 100644 index 00000000..c17415bb --- /dev/null +++ b/horizon/static/horizon/js/horizon.d3singlebarchart.js @@ -0,0 +1,472 @@ +/* + Used for animating and displaying single-bar information using + D3js rect. + + Usage: + In order to have single bars that work with this, you need to have a + DOM structure like this in your Django template: + + Example: + <div class="flavor_usage_bar" + data-popup-free='<p>Capacity remaining by flavors: </p> + {{resource_class.all_instances_flavors_info}}' + data-single-bar-orientation="horizontal" + data-single-bar-height="50" + data-single-bar-width="100%" + data-single-bar-used="{{ resource_class.all_used_instances_info }}" + data-single-bar-auto-scale-selector=".flavors_scale_selector" + data-single-bar-color-scale-range='["#000060", "#99FFFF"]'> + </div> + + The available data- attributes are: + data-popup-free, data-popup-used, data-popup-average OPTIONAL + Html content of popups that will be displayed over this areas. + + data-single-bar-orientation REQUIRED + String representing orientation of the bar.Can be "horizontal" + or "vertical" + + data-single-bar-height REQUIRED + Integer or string with percent mark format e.g. "50%". Determines + the total height of the bar. + + data-single-bar-width="100%" REQUIRED + Integer or string with percent mark format e.g. "50%". Determines + the total width of the bar. + + data-single-bar-used="integer" REQUIRED + 1. Integer + Integer representing the percent used. + 2. Array + Aray of following structure: + [{"popup_used": "Popup html 1", "used_instances": "5"}, + {"popup_used": "Popup html 2", "used_instances": "15"},....] + + used_instances: Integer representing the percent used. + popup_used: Html that will be displayed in popup window over + this area. + + data-single-bar-used-average="integer" OPTIONAL + Integer representing the average usage in percent of given + single-bar. + + data-single-bar-auto-scale-selector OPTIONAL + Jquery selector of bar elements that have Integer + data-single-bar-used attribute. It then takes maximum of these + values as 100% of the liner scale of the colors. + So the array representing linear scale interval is set + automatically.This then maps to data-single-bar-color-scale-range. + (arays must have the same structure) + + single-bar-color-scale-range OPTIONAL + Array representing linear scale interval that is set manually. + E.g "[0,10]". This then maps to data-single-bar-color-scale-range. + (arays must have the same structure) + + data-single-bar-color-scale-range OPTIONAL + Array representing linear scale of colors. + E.g '["#000060", "#99FFFF"]' + +*/ + +horizon.d3_single_bar_chart = { + SingleBarChart: function(chart_class, html_element){ + var self = this; + self.chart_class = chart_class; + + self.html_element = html_element; + self.jquery_element = $(self.html_element); + // Using only percent, so limit is 100% + self.single_bar_limit = 100; + + self.single_bar_used = $.parseJSON(self.jquery_element.attr('data-single-bar-used')); + self.average_used = parseInt(self.jquery_element.attr('data-single-bar-average-used'), 10); + + self.data = {}; + + // Percentage and used_px count + if ($.isArray(self.single_bar_used)){ + self.data.used_px = 0; + self.data.percentage_used = Array(); + self.data.tooltip_used_contents = Array(); + for (var i = 0; i < self.single_bar_used.length; ++i) { + if (!isNaN(self.single_bar_limit) && !isNaN(self.single_bar_used[i].used_instances)) { + used = Math.round((self.single_bar_used[i].used_instances / self.single_bar_limit) * 100); + + self.data.percentage_used.push(used); + // for multi-bar chart, tooltip is in the data + self.data.tooltip_used_contents.push(self.single_bar_used[i].popup_used); + + self.data.used_px += self.jquery_element.width() / 100 * used; + } else { // If NaN self.data.percentage_used is 0 + + } + } + + } + else { + if (!isNaN(self.single_bar_limit) && !isNaN(self.single_bar_used)) { + self.data.percentage_used = Math.round((self.single_bar_used / self.single_bar_limit) * 100); + self.data.used_px = self.jquery_element.width() / 100 * self.data.percentage_used; + + } else { // If NaN self.data.percentage_used is 0 + self.data.percentage_used = 0; + self.data.used_px = 0; + } + + if (!isNaN(self.single_bar_limit) && !isNaN(self.average_used)) { + self.data.percentage_average = ((self.average_used / self.single_bar_limit) * 100); + } else { + self.data.percentage_average = 0; + } + } + + // Width and height of bar + self.data.width = self.jquery_element.data('single-bar-width'); + self.data.height = self.jquery_element.data('single-bar-height'); + + // Color scales + self.auto_scale_selector = function () { + return self.jquery_element.data('single-bar-auto-scale-selector'); + }; + self.is_auto_scaling = function () { + return self.auto_scale_selector(); + }; + self.auto_scale = function () { + max_scale = 0; + $(self.auto_scale_selector()).each(function() { + var scale = parseInt($(this).data('single-bar-used')); + if (scale > max_scale) + max_scale = scale; + }); + return [0, max_scale]; + }; + + if (self.jquery_element.data('single-bar-color-scale-domain')) + self.data.color_scale_domain = + self.jquery_element.data('single-bar-color-scale-domain'); + else if (self.is_auto_scaling()) + // Dynamically set scale based on biggest value + self.data.color_scale_domain = self.auto_scale(); + else + self.data.color_scale_domain = [0,100]; + + if (self.jquery_element.data('single-bar-color-scale-range')) + self.data.color_scale_range = + self.jquery_element.data('single-bar-color-scale-range'); + else + self.data.color_scale_range = ["#000000", "#0000FF"]; + + // Tooltips data + self.data.popup_average = self.jquery_element.data('popup-average'); + self.data.popup_free = self.jquery_element.data('popup-free'); + self.data.popup_used = self.jquery_element.data('popup-used'); + + // Orientation of the Bar chart + self.data.orientation = self.jquery_element.data('single-bar-orientation'); + + // Refresh method + self.refresh = function (){ + self.chart_class.render(self.html_element, self.data); + }; + }, + BaseComponent: function(data){ + var self = this; + + self.data = data; + + self.w = data.width; + self.h = data.height; + self.lvl_curve = 3; + self.bkgrnd = "#F2F2F2"; + self.frgrnd = "grey"; + self.color_scale_max = 25; + + self.percentage_used = data.percentage_used; + self.total_used_perc = 0; + self.used_px = data.used_px; + self.percentage_average = data.percentage_average; + self.tooltip_used_contents = data.tooltip_used_contents; + + // set scales + self.usage_color = d3.scale.linear() + .domain(data.color_scale_domain) + .range(data.color_scale_range); + + // return true if it renders used percentage multiple in one chart + self.used_multi = function (){ + return ($.isArray(self.percentage_used)); + }; + + // deals with percentage if there should be multiple in one chart + self.used_multi_iterator = 0; + self.percentage_used_value = function(){ + if (self.used_multi()){ + return self.percentage_used[self.used_multi_iterator]; + } else { + return self.percentage_used; + } + }; + // deals with html tooltips if there should be multiple in one chart + self.tooltip_used_value = function (){ + if (self.used_multi()){ + return self.tooltip_used_contents[self.used_multi_iterator]; + } else + { + return ""; + } + }; + + // return true if it chart is oriented horizontally + self.horizontal_orientation = function (){ + return (self.data.orientation == "horizontal"); + }; + + }, + UsedComponent: function(base_component){ + var self = this; + self.base_component = base_component; + + // FIXME woud be good to abstract all atributes and resolve orientation inside + if (base_component.horizontal_orientation()){ + // Horizontal Bars + self.y = 0; + self.x = base_component.total_used_perc + "%"; + self.width = 0; + self.height = base_component.h; + self.trasition_attr = "width"; + self.trasition_value = base_component.percentage_used_value() + "%"; + } else + { // Vertical Bars + self.y = base_component.h; + self.x = 0; + self.width = base_component.w; + self.height = base_component.percentage_used_value() + "%"; + self.trasition_attr = "y"; + self.trasition_value = 100 - base_component.percentage_used_value() + "%"; + } + + self.append = function(bar, tooltip){ + var used_component = self; + var base_component = self.base_component; + + bar.append("rect") + .attr("class", "usedbar") + .attr("y", used_component.y) + .attr("x", used_component.x) + .attr("width", used_component.width) + .attr("height", used_component.height) + //.attr("rx", base_component.lvl_curve) + //.attr("ry", base_component.lvl_curve) + .style("fill", base_component.usage_color(base_component.percentage_used_value())) + .style("stroke", "#000000") + .style("stroke-width", 0) + .attr("d", base_component.percentage_used_value()) + .attr("popup-used", base_component.tooltip_used_value()) + .on("mouseover", function(d){ + if ($(this).attr('popup-used')){ + tooltip.html($(this).attr('popup-used')); + } + tooltip.style("visibility", "visible");}) + .on("mousemove", function(d){tooltip.style("top", + (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) + .on("mouseout", function(d){tooltip.style("visibility", "hidden");}) + .transition() + .duration(500) + .attr(used_component.trasition_attr, used_component.trasition_value); + }; + }, + AverageComponent: function(base_component){ + var self = this; + self.base_component = base_component; + + // FIXME woud be good to abstract all atributes and resolve orientation inside + if (base_component.horizontal_orientation()){ + // Horizontal Bars + self.y = 1; + self.x = 0; + self.width = 1; + self.height = base_component.h; + self.trasition_attr = "x"; + self.trasition_value = base_component.percentage_average + "%"; + } else + { // Vertical Bars + self.y = 0; + self.x = 0; + self.width = base_component.w; + self.height = 3; + self.trasition_attr = "y"; + self.trasition_value = 100 - base_component.percentage_average + "%"; + } + + self.append = function(bar, tooltip){ + var average_component = self; + var base_component = self.base_component; + + bar.append("rect") + .attr("y", average_component.y) + .attr("x", average_component.x) + .attr("class", "average") + .attr("height", average_component.height) + .attr("width", average_component.width) + .style("fill", "black") + .on("mouseover", function(){tooltip.style("visibility", "visible");}) + .on("mousemove", function(){tooltip.style("top", + (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) + .on("mouseout", function(){tooltip.style("visibility", "hidden");}) + .transition() + .duration(500) + .attr(average_component.trasition_attr, average_component.trasition_value); + }; + }, + /* TODO rewrite below as components */ + /* FIXME use some pretty tooltip from some library we will use + * this one is just temporary */ + append_tooltip: function(tooltip, html_content){ + return tooltip + .style("position", "absolute") + .style("z-index", "10") + .style("visibility", "hidden") + .style("min-width", "100px") + .style("max-width", "200px") + .style("min-height", "30px") + .style("max-height", "150px") + .style("border", "1px ridge grey") + .style("padding", "8px") + .style("padding-top", "5px") + .style("background-color", "white") + .html(html_content); + }, + append_unused: function(bar, base_component, tooltip_free){ + bar.append("rect") + .attr("y", 0) + .attr("width", base_component.w) + .attr("height", base_component.h) + .attr("rx", base_component.lvl_curve) + .attr("ry", base_component.lvl_curve) + .style("fill", base_component.bkgrnd) + .style("stroke", "#000000") + .style("stroke-width", 1) + .on("mouseover", function(d){tooltip_free.style("visibility", "visible");}) + .on("mousemove", function(d){tooltip_free.style("top", + (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) + .on("mouseout", function(d){tooltip_free.style("visibility", "hidden");}); + }, + // TODO This have to be enhanced, so this library can replace jtomasek capacity charts + append_text: function(bar, base_component, tooltip){ + bar.append("text") + .text("FREE") + .attr("y", base_component.h/2) + .attr("x", 3) + .attr("dominant-baseline", "middle") + .attr("font-size", 15) + .on("mouseover", function(d){tooltip.style("visibility", "visible");}) + .on("mousemove", function(d){tooltip.style("top", + (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) + .on("mouseout", function(d){tooltip.style("visibility", "hidden");}) + .transition() + .duration(500) + .attr("x", function() { + // FIXME when another panel is active, this page is hidden and used_px return 0 + // text is then badly positioned, quick fix will be to refresh charts when panel + // is switched. Need to find better solution. + if (base_component.total_used_perc > 90 && base_component.used_px > 25) + return base_component.used_px - 20; + else + return base_component.used_px + 20; + }); + }, + append_border: function(bar){ + bar.append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("height", '100%') + .attr("width", '100%') + .style("stroke", "black") + .style("fill", "none") + .style("stroke-width", 2); + }, + // INIT + init: function() { + var self = this; + this.single_bars = $('div[data-single-bar-used]'); + + this.single_bars.each(function() { + self.refresh(this); + }); + }, + refresh: function(html_element){ + var chart = new this.SingleBarChart(this, html_element); + // FIXME save chart objects somewhere so I can use them again when + // e.g. I am swithing tabs, or if I want to update them + // via web sockets + // this.charts.add_or_update(chart) + chart.refresh(); + }, + render: function(html_element, data) { + var jquery_element = $(html_element); + + // Initialize base_component + var base_component = new this.BaseComponent(data); + + // Bar + var bar_html = d3.select(html_element); + + // Tooltips + var tooltip_average = bar_html.append("div"); + if (data.popup_average) + tooltip_average = this.append_tooltip(tooltip_average, data.popup_average); + + var tooltip_free = bar_html.append("div"); + if (data.popup_free) + tooltip_free = this.append_tooltip(tooltip_free, data.popup_free); + + var tooltip_used = bar_html.append("div"); + if (data.popup_used) + tooltip_used = this.append_tooltip(tooltip_used, data.popup_used); + + // append layout for bar chart + var bar = bar_html.append("svg:svg") + .attr("class", "chart") + .attr("width", base_component.w) + .attr("height", base_component.h) + .style("background-color", "white") + .append("g"); + + // append Unused resources Bar + this.append_unused(bar, base_component, tooltip_free); + + if (base_component.used_multi()){ + // If Used is shown as multiple values in one chart + for (var i = 0; i < base_component.percentage_used.length; ++i) { + // FIXME write proper iterator + base_component.used_multi_iterator = i; + + // Use general tooltip, content of tooltip will be changed + // by inner used compoentnts on their hover + tooltip_used = this.append_tooltip(tooltip_used, ""); + + // append used so it will be shown as multiple values in one chart + var used_component = new this.UsedComponent(base_component); + used_component.append(bar, tooltip_used); + + // append Used resources to Bar + base_component.total_used_perc += base_component.percentage_used_value(); + }; + + // append Text to Bar + this.append_text(bar, base_component, tooltip_free); + + } else { + // used is show as one value it the chart + var used_component = new this.UsedComponent(base_component); + used_component.append(bar, tooltip_used); + + // append average value to Bar + var average_component = new this.AverageComponent(base_component); + average_component.append(bar, tooltip_average); + } + // append border of whole Bar + this.append_border(bar); + }, +}; diff --git a/horizon/templates/horizon/_scripts.html b/horizon/templates/horizon/_scripts.html index 2f9ebce6..9dfd0f4c 100644 --- a/horizon/templates/horizon/_scripts.html +++ b/horizon/templates/horizon/_scripts.html @@ -43,6 +43,7 @@ <script src='{{ STATIC_URL }}horizon/js/horizon.d3modallinechart.js' type='text/javascript' charset='utf-8'></script> <script src='{{ STATIC_URL }}horizon/js/horizon.d3circleschart.js' type='text/javascript' charset='utf-8'></script> <script src='{{ STATIC_URL }}horizon/js/horizon.d3linechart.js' type='text/javascript' charset='utf-8'></script> +<script src='{{ STATIC_URL }}horizon/js/horizon.d3singlebarchart.js' type='text/javascript' charset='utf-8'></script> {% block custom_js_files %}{% endblock %} {% endcompress %} diff --git a/openstack_dashboard/api/tuskar.py b/openstack_dashboard/api/tuskar.py index 3f09219d..12b4c393 100644 --- a/openstack_dashboard/api/tuskar.py +++ b/openstack_dashboard/api/tuskar.py @@ -457,11 +457,37 @@ class ResourceClass(StringIdAPIResourceWrapper): @property def list_flavors(self): if not hasattr(self, '_flavors'): - self._flavors = [Flavor(f) for f in ( - tuskarclient(self.request).flavors.list(self.id))] + # FIXME just a mock of used instances, add real values + used_instances = 0 + + added_flavors = tuskarclient(self.request).flavors.list(self.id) + self._flavors = [] + for f in added_flavors: + flavor_obj = Flavor(f) + #flavor_obj.max_vms = f.max_vms + + # FIXME just a mock of used instances, add real values + used_instances += 5 + flavor_obj.used_instances = used_instances + self._flavors.append(flavor_obj) + return self._flavors @property + def all_used_instances(self): + return [flavor.used_instances for flavor in self.list_flavors] + + @property + def total_instances(self): + # FIXME just mock implementation, add proper one + return sum(self.all_used_instances) + + @property + def remaining_capacity(self): + # FIXME just mock implementation, add proper one + return 100 - self.total_instances + + @property def all_flavors(self): """ Joined relation table resourceclassflavor with all global flavors """ @@ -472,7 +498,7 @@ class ResourceClass(StringIdAPIResourceWrapper): fname = "%s.%s" % (self.name, flavor.name) f = next((f for f in my_flavors if f.name == fname), None) if f: - flavor.set_max_vms(f.max_vms) + flavor.max_vms = f.max_vms self._all_flavors.append(flavor) return self._all_flavors @@ -611,9 +637,18 @@ class FlavorTemplate(StringIdAPIResourceWrapper): def max_vms(self): return getattr(self, '_max_vms', '0') - def set_max_vms(self, value='0'): + @max_vms.setter + def max_vms(self, value='0'): self._max_vms = value + @property + def used_instances(self): + return getattr(self, '_used_instances', 0) + + @used_instances.setter + def used_instances(self, value=0): + self._used_instances = value + @classmethod def list(cls, request, only_free_racks=False): return [cls(f) for f in dummymodels.FlavorTemplate.objects.all()] diff --git a/openstack_dashboard/dashboards/infrastructure/resource_management/resource_classes/tabs.py b/openstack_dashboard/dashboards/infrastructure/resource_management/resource_classes/tabs.py index 0909e064..0b667cad 100644 --- a/openstack_dashboard/dashboards/infrastructure/resource_management/resource_classes/tabs.py +++ b/openstack_dashboard/dashboards/infrastructure/resource_management/resource_classes/tabs.py @@ -24,8 +24,9 @@ class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" template_name = ("infrastructure/resource_management/resource_classes/" - "_detail_overview.html") - preload = False + "_detail_overview.html") + # FIXME charts doesnt work if uncommented + #preload = False def get_context_data(self, request): return {"resource_class": self.tab_group.kwargs['resource_class']} diff --git a/openstack_dashboard/dashboards/infrastructure/resource_management/templates/resource_management/resource_classes/_detail_overview.html b/openstack_dashboard/dashboards/infrastructure/resource_management/templates/resource_management/resource_classes/_detail_overview.html index 60570dd7..0c8fbb2e 100644 --- a/openstack_dashboard/dashboards/infrastructure/resource_management/templates/resource_management/resource_classes/_detail_overview.html +++ b/openstack_dashboard/dashboards/infrastructure/resource_management/templates/resource_management/resource_classes/_detail_overview.html @@ -1,5 +1,6 @@ {% load i18n sizeformat %} {% load url from future %} +{% load chart_helpers %} <div class="status row-fluid detail"> <div class="span4"> @@ -118,12 +119,70 @@ <div class="span6"> <h4>{% trans "Summary of Instances and Usage" %}</h4> <hr class="header_rule"> + <!-- <dl> {% for flavor_count in resource_class.running_virtual_machines %} <dt>{{ flavor_count.flavor.name }}</dt> <dd>{{ flavor_count.max_vms }}</dd> {% endfor %} </dl> + --> + + <div class="clear"></div> + <div><strong>{{ resource_class.total_instances }}</strong> instances + <strong>{{resource_class.remaining_capacity}}%</strong> capacity remaining</div> + <div class="flavor_usage_bar" + data-popup-free='{{resource_class|remaining_capacity_by_flavors}}' + data-single-bar-orientation="horizontal" + data-single-bar-height="50" + data-single-bar-width="100%" + data-single-bar-used="{{ resource_class|all_used_instances }}" + data-single-bar-auto-scale-selector=".flavors_scale_selector" + data-single-bar-color-scale-range='["#000060", "#99FFFF"]' + > + </div> + + <table class="flavor_usages"> + <tr> + {% for flavor in resource_class.list_flavors %} + <td class="flavor_usage_label"> + <a href="{% url 'horizon:infrastructure:resource_management:flavors:detail' flavor.id %}">{{ flavor.name }}</a> + </td> + {% endfor %} + </tr> + <tr> + {% for flavor in resource_class.list_flavors %} + <td> + <div + class="flavor_usage_bar flavors_scale_selector" + data-popup-average=' + <p>Average capacity consumed by instances of {{flavor.name}} flavor in {{resource_class.name}} class.</p> + <p>{{ flavor.used_instances }}%, <strong>{{ flavor.used_instances }} instances</strong></p> + ' + data-single-bar-orientation="vertical" + data-single-bar-height="100%" + data-single-bar-width="40" + data-single-bar-used="{{ flavor.used_instances }}" + data-single-bar-average-used="{{ 50 }}" + data-single-bar-auto-scale-selector=".flavors_scale_selector" + data-single-bar-color-scale-range='["#000060", "#99FFFF"]' + > + </div> + </td> + {% endfor %} + </tr> + <tr> + {% for flavor in resource_class.list_flavors %} + <td class="modal_chart flavor_usage_text"><a href="{{ "#" }}">{{ flavor.used_instances }}%</a></td> + {% endfor %} + </tr> + <tr> + {% for flavor in resource_class.list_flavors %} + <td class="flavor_usage_text">{{ flavor.used_instances }} inst.</td> + {% endfor %} + </tr> + </table> + </div> <div class="span6"> diff --git a/openstack_dashboard/dashboards/infrastructure/static/infrastructure/less/infrastructure.less b/openstack_dashboard/dashboards/infrastructure/static/infrastructure/less/infrastructure.less index ec6b0a8d..7fe8cba5 100644 --- a/openstack_dashboard/dashboards/infrastructure/static/infrastructure/less/infrastructure.less +++ b/openstack_dashboard/dashboards/infrastructure/static/infrastructure/less/infrastructure.less @@ -253,6 +253,28 @@ table.capacities{ } } +table.flavor_usages{ + width: 100%; + td{ + text-align: center; + padding: 3px; + &.flavor_usage_label{ + font-size: 17px; + width: 60px; + text-align: center; + } + &.flavor_usage_text{ + width: 60px; + text-align: center; + } + & div.flavor_usage_bar{ + text-align: center; + line-height: 0; + height: 150px; + } + } +} + #interval_selector { position: absolute; diff --git a/openstack_dashboard/dashboards/infrastructure/templates/infrastructure/base_detail.html b/openstack_dashboard/dashboards/infrastructure/templates/infrastructure/base_detail.html index f7274ea7..daf3f850 100644 --- a/openstack_dashboard/dashboards/infrastructure/templates/infrastructure/base_detail.html +++ b/openstack_dashboard/dashboards/infrastructure/templates/infrastructure/base_detail.html @@ -24,4 +24,15 @@ </div> </div> +<script type="text/javascript" charset="utf-8"> + + if(typeof horizon.d3_single_bar_chart !== 'undefined') { + horizon.d3_single_bar_chart.init(); + } else { + addHorizonLoadEvent(function() { + horizon.d3_single_bar_chart.init(); + }); + } + +</script> {% endblock %} diff --git a/openstack_dashboard/dashboards/infrastructure/templatetags/__init__.py b/openstack_dashboard/dashboards/infrastructure/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/openstack_dashboard/dashboards/infrastructure/templatetags/__init__.py diff --git a/openstack_dashboard/dashboards/infrastructure/templatetags/chart_helpers.py b/openstack_dashboard/dashboards/infrastructure/templatetags/chart_helpers.py new file mode 100644 index 00000000..8eefaf10 --- /dev/null +++ b/openstack_dashboard/dashboards/infrastructure/templatetags/chart_helpers.py @@ -0,0 +1,40 @@ +from django import template +from django.utils import simplejson + +register = template.Library() + + +@register.filter() +def remaining_capacity_by_flavors(obj): + flavors = obj.list_flavors + + decorated_obj = " ".join( + [("<p><strong>{0}</strong> {1}</p>").format( + str(flavor.used_instances), + flavor.name) + for flavor in flavors]) + + decorated_obj = ("<p>Capacity remaining by flavors: </p>" + + decorated_obj) + + return decorated_obj + + +@register.filter() +def all_used_instances(obj): + flavors = obj.list_flavors + + all_used_instances_info = [] + for flavor in flavors: + info = {} + info['popup_used'] = ( + '<p> {0}% total,' + ' <strong> {1} instances</strong> of {2}</p>'.format( + flavor.used_instances, + flavor.used_instances, + flavor.name)) + info['used_instances'] = str(flavor.used_instances) + + all_used_instances_info.append(info) + + return simplejson.dumps(all_used_instances_info) |