Commit 373da652 authored by Kostas Papadimitriou's avatar Kostas Papadimitriou Committed by Giorgos Korfiatis

astakos: Display both summed up and per project quota in usage view

parent 38fc975e
......@@ -522,9 +522,9 @@ form input[type="text"]:-ms-input-placeholder,
.stats .bar span + em { position: absolute; }
.stats .bar { position: relative; }
.stats .red .bar span { background:#ef4f54; }
.stats .yellow .bar span { background:#f6921e; }
.stats .green .bar span { background:#55b577; }
.stats .resource-bar.red .bar span { background:#ef4f54; }
.stats .resource-bar.yellow .bar span { background:#f6921e; }
.stats .resource-bar.green .bar span { background:#55b577; }
.stats .img-wrap { float:left; width:100px; background:url(../images/statistics_icons.png) no-repeat center center; padding:30px 0; }
.stats .info { margin:0 25px ; width:320px; float:left; }
.stats .info p { color:#999; margin:0; }
......@@ -546,6 +546,10 @@ form input[type="text"]:-ms-input-placeholder,
.stats .red .img-wrap { background-position: 15px 7px; }
.stats .yellow .img-wrap { background-position: -124px 7px; }
.stats .green .img-wrap { background-position: -263px 7px; }
.stats .resource-bar.project { margin-top: 1em; }
.stats .resource-bar.project { margin-left: 100px; font-size: 0.8em;}
.stats .resource-bar.project h3 { margin-bottom: 0.5em; }
.stats .resource-bar.project .bar div { width:340px; height:25px; border:1px solid #000; margin-top:5px; overflow:hidden; font-size: 0.8em}
.projects .editable form textarea { width:70%; height:50px; max-width:70%; width:270px; height:120px;}
......
......@@ -40,7 +40,7 @@ humanize.numberFormat = function(number, decimals, decPoint, thousandsSep) {
};
DO_LOG = false
DO_LOG = false;
LOG = DO_LOG ? _.bind(console.log, console) : function() {};
WARN = DO_LOG ? _.bind(console.warn, console) : function() {};
......@@ -53,10 +53,13 @@ var default_usage_cls_map = {
function UsageView(settings) {
this.settings = settings;
this.url = this.settings.url;
this.projects_url = this.settings.projects_url;
this.container = $(this.settings.container);
this.meta = this.settings.meta;
this.groups = this.settings.groups;
this.projects = {};
this.el = {};
this.updating_projects = false;
this.usage_cls_map = this.settings.usage_cls_map || default_usage_cls_map;
this.initialize();
}
......@@ -65,11 +68,13 @@ function UsageView(settings) {
_.extend(UsageView.prototype, {
tpls: {
'main': '<div class="stats clearfix"><ul></ul></div>',
'quotas': "#quotaTpl"
'quotas': "#quotaTpl",
'projectQuota': "#projectQuotaTpl"
},
initialize: function() {
LOG("Initializing UsageView", this.settings);
this.updateProjects(this.meta.projects_details);
this.initResources();
// initial usage set ????
......@@ -78,7 +83,6 @@ _.extend(UsageView.prototype, {
this.setQuotas(this.settings.quotas);
}
this.initLayout();
this.updateQuotas();
},
$: function(selector) {
......@@ -101,34 +105,72 @@ _.extend(UsageView.prototype, {
this.container.append(this.el.main);
var ul = this.container.find("ul");
this.el.list = this.render('quotas', {
'resources': this.resources_ordered
'resources': this.resources_ordered,
});
ul.append(this.el.list);
ul.append(this.el.list).hide();
_.each(this.resources_ordered, function(resource) {
this.renderResourceProjects(this.container, resource);
}, this);
this.updateQuotas();
ul.show();
},
renderResourceProjects: function(list, resource) {
var resource_el = list.find("li[data-resource='"+resource.name+"']");
var projects_el = resource_el.find(".projects");
projects_el.empty();
_.each(resource.projects_list, function(project) {
_.extend(project, {report_desc: resource.report_desc});
projects_el.append(this.render('projectQuota', project));
}, this);
},
initResources: function() {
var ordered = this.meta.resources_order;
var resources = {};
var resources_ordered = [];
var projects = this.projects;
_.each(this.meta.resources, function(group, index) {
_.each(group[1], function(resource, rindex) {
resource.resource_name = resource.name.split(".")[1];
resources[resource.name] = resource;
})
});
}, this);
}, this);
resources_ordered = _.filter(_.map(ordered,
function(rk, index) {
rk.index = index;
return resources[rk]
}),
function(i) { return i});
resources_ordered = _.filter(
_.map(ordered, function(rk, index) {
rk.index = index;
return resources[rk]
}), function(i) { return i });
this.resources = resources;
this.resources_ordered = resources_ordered;
LOG("Resources initialized", this.resources_ordered, this.resources);
},
updateProject: function(uuid, details) {
LOG("Update project", uuid, details);
this.projects[uuid] = details;
},
addProject: function(uuid, details) {
LOG("New project", uuid, details);
this.projects[uuid] = details;
},
updateProjects: function(projects, uuids) {
this.projects = {};
_.each(projects, function(details) {
if (this.projects[details.id]) {
this.updateProject(details.id, details);
} else {
this.addProject(details.id, details);
}
}, this);
LOG("Projects updated", this.projects);
},
updateLayout: function() {
LOG("Updating layout", this.quotas);
......@@ -137,25 +179,46 @@ _.extend(UsageView.prototype, {
var usage = self.getUsage(key);
if (!usage) { return }
var el = self.$().find("li[data-resource='"+key+"']");
self.updateResourceElement(el, usage);
self.updateResourceElement(el.find(".summary.resource-bar"), usage);
_.each(self.resources[key].projects_list, function(project){
var project_el = el.find(".project-" + project.id);
if (project_el.length === 0) {
self.renderResourceProjects(self.container, self.resources[key]);
} else {
self.updateResourceElement(project_el, project.usage);
}
});
var project_ids = _.keys(self.project_quotas);
_.each(el.find(".resource-bar.project"), function(el) {
if (project_ids.indexOf($(el).data("project")) == -1) {
self.renderResourceProjects(self.container, self.resources[key]);
}
})
})
},
updateResourceElement: function(el, usage) {
var bar_el = el.find(".bar span");
var bar_value = el.find(".bar .value");
el.find(".currValue").text(usage.curr);
el.find(".maxValue").text(usage.max);
el.find(".bar span").css({width:usage.perc+"%"});
el.find(".bar .value").text(usage.perc+"%");
bar_el.css({width:usage.perc+"%"});
bar_value.text(usage.perc+"%");
var left = usage.label_left == 'auto' ?
usage.label_left : usage.label_left + "%";
el.find(".bar .value").css({left:left});
el.find(".bar .value").css({color:usage.label_color});
bar_value.css({left:left});
bar_value.css({color:usage.label_color});
el.removeClass("green yellow red");
el.addClass(usage.cls);
if (el.hasClass("summary")) {
el.parent().removeClass("green yellow red");
el.parent().addClass(usage.cls);
};
},
getUsage: function(resource_name) {
var resource = this.quotas[resource_name];
getUsage: function(resource_name, quotas) {
var resource = quotas ? quotas[resource_name] : this.quotas[resource_name];
var resource_meta = this.resources[resource_name];
if (!resource_meta) { return }
var value, limit, percentage;
......@@ -182,19 +245,38 @@ _.extend(UsageView.prototype, {
}
})
var label_left = percentage >= 30 ? percentage - 17 : 'auto';
var label_left = percentage >= 30 ? percentage - 17 : 'auto';
var label_col = label_left == 'auto' ? 'inherit' : '#fff';
if (label_left != 'auto') { label_left = label_left + "%" }
percentage = humanize.numberFormat(percentage, 0);
qdata = {'curr': value, 'max': limit, 'perc': percentage, 'cls': cls,
'label_left': label_left, 'label_color': label_col}
_.extend(qdata, resource);
return qdata
},
setQuotas: function(data) {
setQuotas: function(data, update_projects) {
LOG("Set quotas", data);
this.last_quota_received = data;
var project_uuids = _.keys(data);
var self = this;
this.quotas = data;
var sums = {};
_.each(data, function(quotas, project_uuid) {
_.each(quotas, function(values, qname) {
if (!sums[qname]) {
sums[qname] = {};
}
var qitem = sums[qname];
_.each(values, function(value, param) {
var current = qitem[param];
qitem[param] = current ? qitem[param] + value : value;
}, this);
});
}, this);
this.project_quotas = data;
this.quotas = sums;
_.each(this.quotas, function(v, k) {
var r = self.resources[k];
var usage = self.getUsage(k);
......@@ -204,12 +286,69 @@ _.extend(UsageView.prototype, {
if (!self.resources_ordered[r.index]) { return }
self.resources_ordered[r.index].usage = usage;
});
var active_project_uuids = _.keys(this.project_quotas);
var project_change = false;
_.each(this.project_quotas, function(resources, uuid) {
if (project_change) { return }
_.each(resources, function(v, k){
if (project_change) { return }
if (!self.resources[k]) { return }
if (!self.resources[k].projects) {
self.resources[k].projects = {};
self.resources[k].projects_list = [];
}
var resource = self.resources[k];
var project_usage = self.getUsage(k, resources);
var resource_projects_list = self.resources[k].projects_list;
if (!self.projects[uuid]) {
self.getProjects(function(data) {
self.updateProjects(data);
self.setQuotas(self.last_quota_received);
self.updateLayout();
});
project_change = true;
return;
}
if (!project_usage) { return; }
if (!resource.projects[uuid]) {
resource.projects[uuid] = _.clone(self.projects[uuid]);
if (self.projects[uuid].base_project) {
resource.projects[uuid].name = 'User quota'
}
}
var resource_project = resource.projects[uuid];
resource_project.usage = project_usage;
if (resource_project.index === undefined) {
resource_project.index = resource_projects_list.length;
resource_projects_list.push(resource_project);
} else {
resource_projects_list[resource_project.index] = resource_project;
}
_.each(resource.projects, function(project, uuid) {
if (active_project_uuids.indexOf(uuid) == -1) {
var index = resource.projects[uuid].index;
delete resource.projects[uuid];
delete resource.projects_list[index];
}
});
});
});
},
_ajaxOptions: function() {
_ajaxOptions: function(url) {
var token = $.cookie(this.settings.cookie_name).split("|")[1];
return {
'url': this.url,
'url': url || this.url,
'headers': {
'X-Auth-Token': token
},
......@@ -220,12 +359,20 @@ _.extend(UsageView.prototype, {
LOG("Updating quotas");
var self = this;
this.getQuotas(function(data){
self.setQuotas(data.system);
self.setQuotas(data, true);
self.updateLayout();
})
},
getProjects: function(callback) {
var options = this._ajaxOptions(this.projects_url);
options.success = callback;
LOG("Calling projects API", options);
$.ajax(options);
},
getQuotas: function(callback) {
if (this.updating_projects) { return }
var options = this._ajaxOptions();
options.success = callback;
LOG("Calling quotas API", options);
......
......@@ -10,6 +10,29 @@
{% block page.title %}Usage{% endblock %}
{% block page.body %}
<script id="projectQuotaTpl" type="text/template">
{% verbatim %}
<div class="resource-bar project clearfix project-{{id}} {{ usage.cls }}"
data-project="{{ id }}">
<div class="info" data-currvalue="" data-maxvalue="">
<h3>{{ name }}</h3>
<p>
<span class="currValue">{{ usage.curr }}</span> out of
<span class="maxValue">{{ usage.max }}</span> {{ report_desc }}
</p>
</div>
<div class="bar">
<div>
<span style="width:{{ usage.perc }}%"></span>
<em class="value"
style="left: {{ usage.label_left }}; color: {{ usage.label_color }}">
{{ usage.perc }}%
</em>
</div>
</div>
</div>
{% endverbatim %}
</script>
<script id="quotaTpl" type="text/template">
{% verbatim %}
......@@ -17,22 +40,26 @@
<li class="clearfix {{ resource_name }} {{ usage.cls }}"
data-resource="{{ name }}">
<div class="img-wrap">&nbsp;</div>
<div class="info" data-currvalue="" data-maxvalue="">
<h3>{{ report_desc }}</h3>
<p>
<span class="currValue">{{ usage.curr }}</span> out of
<span class="maxValue">{{ usage.max }}</span> {{ report_desc }}
</p>
<div class="summary resource-bar clearfix {{ usage.cls }}">
<div class="info" data-currvalue="" data-maxvalue="">
<h3>{{ report_desc }}</h3>
<p>
<span class="currValue">{{ usage.curr }}</span> out of
<span class="maxValue">{{ usage.max }}</span> {{ report_desc }}
</p>
<!--<span class="resource-extend">details</span>-->
</div>
<div class="bar">
<div>
<span style="width:{{ usage.perc }}%"></span>
<em class="value"
style="left: {{ usage.label_left }}; color: {{ usage.label_color }}">
{{ usage.perc }}%
</em>
</div>
</div>
</div>
<div class="bar">
<div>
<span style="width:{{ usage.perc }}%"></span>
<em
class="value"
style="left: {{ usage.label_left }}%; color: {{ usage.label_color }}">
{{ usage.perc }}%
</em>
</div>
<div class="projects">
</div>
</li>
{{/resources}}
......@@ -48,13 +75,15 @@
$(document).ready(function(){
var usageView = new UsageView({
'url': '{% url astakos-api-quotas %}',
'projects_url': '{% url astakos.api.projects.projects %}',
'cookie_name': '{{ token_cookie_name|safe }}',
'dataType': 'json',
'container': '#quota-container',
'quotas': {{ current_usage|safe }},
'quotas': {{ user_quotas|safe }},
'meta': {
'resources': {{ resource_catalog|safe }},
'groups': {{ resource_groups|safe }},
'projects_details': {{ projects_details|safe }},
'resources_order': {{ resources_order|safe }}
}
});
......
......@@ -74,6 +74,8 @@ from astakos.im import quotas
from astakos.im.views.util import render_response, _resources_catalog
from astakos.im.views.decorators import cookie_fix, signed_terms_required,\
required_auth_methods_assigned, valid_astakos_user_required, login_required
from astakos.api import projects as projects_api
from astakos.api.util import _dthandler
logger = logging.getLogger(__name__)
......@@ -840,8 +842,11 @@ def resource_usage(request):
resources_meta = presentation.RESOURCES
current_usage = quotas.get_user_quotas(request.user)
current_usage = json.dumps(current_usage['system'])
user_memberships = request.user.projectmembership_set.actually_accepted()
sources = [quotas.project_ref(m.project.uuid) for m in user_memberships]
user_quotas = quotas.get_user_quotas(request.user, sources=sources)
projects = [m.project for m in user_memberships]
user_projects = projects_api.get_projects_details(projects)
resource_catalog, resource_groups = _resources_catalog()
if resource_catalog is False:
# on fail resource_groups contains the result object
......@@ -852,16 +857,20 @@ def resource_usage(request):
resource_catalog = json.dumps(resource_catalog)
resource_groups = json.dumps(resource_groups)
resources_order = json.dumps(resources_meta.get('resources_order'))
projects_details = json.dumps(user_projects, default=_dthandler)
user_quotas = json.dumps(user_quotas)
interval = settings.USAGE_UPDATE_INTERVAL
return render_response('im/resource_usage.html',
context_instance=get_context(request),
resource_catalog=resource_catalog,
resource_groups=resource_groups,
resources_order=resources_order,
current_usage=current_usage,
projects_details=projects_details,
user_quotas=user_quotas,
token_cookie_name=settings.COOKIE_NAME,
usage_update_interval=
settings.USAGE_UPDATE_INTERVAL)
usage_update_interval=interval)
# TODO: action only on POST and user should confirm the removal
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment