1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00

Feature #1607: Add accounting tab to user and group resources

This commit is contained in:
Carlos Martín 2014-04-30 16:02:19 +02:00
parent f377dfd9b6
commit 2487c4f1ea
6 changed files with 514 additions and 0 deletions

View File

@ -1497,6 +1497,7 @@ src/sunstone/public/vendor/flot/jquery.flot.navigate.min.js \
src/sunstone/public/vendor/flot/jquery.flot.pie.min.js \
src/sunstone/public/vendor/flot/jquery.flot.resize.min.js \
src/sunstone/public/vendor/flot/jquery.flot.stack.min.js \
src/sunstone/public/vendor/flot/jquery.flot.tooltip.min.js \
src/sunstone/public/vendor/flot/LICENSE.txt \
src/sunstone/public/vendor/flot/NOTICE"

View File

@ -703,9 +703,16 @@ function updateGroupInfo(request,group){
</div>'
};
var accounting_tab = {
title: tr("Accounting"),
icon: "fa-bar-chart-o",
content: '<div id="group_accounting"></div>'
};
Sunstone.updateInfoPanelTab("group_info_panel","group_info_tab",info_tab);
Sunstone.updateInfoPanelTab("group_info_panel","group_quotas_tab",quotas_tab);
Sunstone.updateInfoPanelTab("group_info_panel","group_providers_tab",providers_tab);
Sunstone.updateInfoPanelTab("group_info_panel","group_accouning_tab",accounting_tab);
Sunstone.popUpInfoPanel("group_info_panel", 'groups-tab');
$("#add_rp_button", $("#group_info_panel")).click(function(){
@ -715,6 +722,11 @@ function updateGroupInfo(request,group){
return false;
});
accountingGraphs(
$("#group_accounting","#group_info_panel"),
{ fixed_group: info.ID,
init_group_by: "user" });
}
function setup_group_resource_tab_content(zone_id, zone_section, str_zone_tab_id, str_datatable_id, selected_group_clusters, group) {

View File

@ -715,10 +715,23 @@ function updateUserInfo(request,user){
content : quotas_html
};
var accounting_tab = {
title: tr("Accounting"),
icon: "fa-bar-chart-o",
content: '<div id="user_accounting"></div>'
};
Sunstone.updateInfoPanelTab("user_info_panel","user_info_tab",info_tab);
Sunstone.updateInfoPanelTab("user_info_panel","user_quotas_tab",quotas_tab);
Sunstone.updateInfoPanelTab("user_info_panel","user_accouning_tab",accounting_tab);
//Sunstone.updateInfoPanelTab("user_info_panel","user_acct_tab",acct_tab);
Sunstone.popUpInfoPanel("user_info_panel", 'users-tab');
accountingGraphs(
$("#user_accounting","#user_info_panel"),
{ fixed_user: info.ID,
init_group_by: "vm" });
};
// Used also from groups-tabs.js

View File

@ -3902,3 +3902,478 @@ $(document).ready(function(){
}
})
});
// div is a jQuery selector
// The following options can be set:
// fixed_user fix an owner user ID
// fixed_group fix an owner group ID
// init_group_by "user", "group", "vm". init the group-by selector
function accountingGraphs(div, opt){
div.append(
'<div class="row">\
<div class="large-2 columns">\
'+tr("Time range")+'\
</div>\
<div class="large-5 columns">\
<input id="acct_start_time" class="jdpicker" type="text" placeholder="2013/12/30"/>\
</div>\
<div class="large-5 columns">\
<input id="acct_end_time" class="jdpicker" type="text" placeholder="'+tr("Today")+'"/>\
</div>\
</div>\
<div class="row">\
<div class="large-6 columns">\
<input id="acct_owner_all" type="radio" name="acct_owner" value="acct_owner_all" checked/><label for="acct_owner_all">' + tr("All") + '</label>\
<input id="acct_owner_group" type="radio" name="acct_owner" value="acct_owner_group" /><label for="acct_owner_group">' + tr("Group") + '</label>\
<input id="acct_owner_user" type="radio" name="acct_owner" value="acct_owner_user" /><label for="acct_owner_user">' + tr("User") + '</label>\
</div>\
<div class="large-6 columns">\
<div id="acct_owner_select"/>\
</div>\
</div>\
<div class="row">\
<div class="large-12 columns">\
<label for="acct_group_by">' + tr("Group by") + '</label>\
<select id="acct_group_by" name="acct_group_by">\
<option value="user">' + tr("User") + '</option>\
<option value="group">' + tr("Group") + '</option>\
<option value="vm">' + tr("VM") + '</option>\
</select>\
</div>\
</div>\
<div class="row">\
<button class="button radius left success" id="acct_submit" type="submit">'+tr("Go")+'</button>\
</div>\
<div class="row">\
<div class="row graph_legend">\
<h3 class="subheader"><small>'+tr("CPU hours")+'</small></h3>\
</div>\
<div class="row">\
<div class="large-10 columns centered graph" id="acct_cpu_graph" style="height: 200px;">\
</div>\
</div>\
<div class="row graph_legend">\
<div class="large-10 columns centered" id="acct_cpu_legend">\
</div>\
</div>\
</div>\
<div class="row">\
<div class="row graph_legend">\
<h3 class="subheader"><small>'+tr("Memory GB hours")+'</small></h3>\
</div>\
<div class="row">\
<div class="large-10 columns centered graph" id="acct_mem_graph" style="height: 200px;">\
</div>\
</div>\
<div class="row graph_legend">\
<div class="large-10 columns centered" id="acct_mem_legend">\
</div>\
</div>\
</div>\
<div class="row graph_legend">\
<div class="large-12 columns centered" id="acct_legend">\
</div>\
</div>');
if (opt == undefined){
opt = {};
}
//--------------------------------------------------------------------------
// Init start time to 1st of last month
//--------------------------------------------------------------------------
var d = new Date();
d.setDate(1);
d.setMonth(d.getMonth() - 1);
$("#acct_start_time", div).val(d.getFullYear() + '/' + (d.getMonth()+1) + '/' + d.getDate());
$("#acct_start_time", div).jdPicker();
$("#acct_end_time", div).jdPicker();
//--------------------------------------------------------------------------
// VM owner: all, group, user
//--------------------------------------------------------------------------
if (opt.fixed_user != undefined || opt.fixed_group != undefined){
$("input[name='acct_owner']", div).attr("disabled", "disabled");
$("#acct_owner_select", div).show();
if(opt.fixed_user != undefined){
var text = tr("User") +" " + opt.fixed_user;
$("input[value='acct_owner_user']", div).attr("checked", "checked");
} else {
var text = tr("Group") + " " + opt.fixed_group;
$("input[value='acct_owner_group']", div).attr("checked", "checked");
}
$("#acct_owner_select", div).text(text);
} else {
$("input[name='acct_owner']", div).change(function(){
var value = $(this).val();
switch (value){
case "acct_owner_all":
$("#acct_owner_select", div).hide();
break;
case "acct_owner_group":
$("#acct_owner_select", div).show();
insertSelectOptions("#acct_owner_select", div, "Group");
break;
case "acct_owner_user":
$("#acct_owner_select", div).show();
insertSelectOptions("#acct_owner_select", div, "User", -1, false,
'<option value="-1">'+tr("<< me >>")+'</option>');
break;
}
});
}
//--------------------------------------------------------------------------
// Init group by select
//--------------------------------------------------------------------------
if(opt.init_group_by != undefined){
$("#acct_group_by", div).val(opt.init_group_by);
}
//--------------------------------------------------------------------------
// Submit request
//--------------------------------------------------------------------------
// TODO: make start_time mandatory
$("#acct_submit", div).on("click", function(){
var start_time = -1;
var end_time = -1;
var v = $("#acct_start_time", div).val();
if (v != ""){
start_time = new Date(v).getTime() / 1000;
}
var v = $("#acct_end_time", div).val();
if (v != ""){
end_time = new Date(v).getTime() / 1000;
}
var options = {
"start_time": start_time,
"end_time": end_time
};
if (opt.fixed_user != undefined){
options.userfilter = opt.fixed_user;
} else if (opt.fixed_group != undefined){
options.group = opt.fixed_group;
} else {
var select_val = $("#acct_owner_select .resource_list_select", div).val();
switch ($("input[name='acct_owner']:checked", div).val()){
case "acct_owner_all":
break;
case "acct_owner_group":
if(select_val != ""){
options.group = select_val;
}
break;
case "acct_owner_user":
if(select_val != ""){
options.userfilter = select_val;
}
break;
}
}
OpenNebula.VM.accounting({
// timeout: true,
success: function(req, response){
fillAccounting(div, req, response);
},
error: onError,
data: options
});
});
}
function fillAccounting(div, req, response) {
/*
console.log(req);
console.log(response);
*/
/*
TODO More options:
Granularity: month, day, hour
Time range
Group by: user, group, vm, [flow]
Filter by: user, group, vm, [flow]
Download csv
*/
var options = req.request.data[0];
//--------------------------------------------------------------------------
// Time slots
//--------------------------------------------------------------------------
// start_time is mandatory
var start = new Date(options.start_time * 1000);
var end = new Date();
if(options.end_time != undefined && options.end_time != -1){
var end = new Date(options.end_time * 1000)
}
// granularity of 1 day
var times = [];
var tmp_time = start;
// End time is the start of the last time slot. We use <=, to
// add one extra time step
while (tmp_time <= end) {
times.push(tmp_time.getTime());
// day += 1
tmp_time.setDate( tmp_time.getDate() + 1 );
}
//--------------------------------------------------------------------------
// Flot options
//--------------------------------------------------------------------------
var options = {
// colors: [ "#2ba6cb", "#707D85", "#AC5A62" ],
xaxis : {
mode: "time",
timeformat: "%y/%m/%d",
min: times[0],
max: end.getTime(),
color: "#999",
size: 8
},
yaxis : { labelWidth: 50,
min: 0,
color: "#999",
size: 8
},
series: {
bars: {
show: true,
lineWidth: 1,
fill: true,
barWidth: 24*60*60*1000 * 0.8,
align: "center"
},
stack: true
},
legend : {
show : true,
noColumns: 6,
container: $("#acct_legend", div)
},
grid: {
borderWidth: 1,
borderColor: "#cfcfcf",
hoverable: true
},
tooltip: true,
tooltipOpts: {
content: "%x | %s | %y" //"%s | X: %x | Y: %y"
/*
xDateFormat: string //null
yDateFormat: string //null
monthNames: string // null
dayNames: string // null
shifts: {
x: int //10
y: int //20
},
defaultTheme: boolean //true
onHover: function(flotItem, $tooltipEl)
*/
}
};
//--------------------------------------------------------------------------
// Group by
//--------------------------------------------------------------------------
// TODO: Allow to change group by dynamically, instead of calling oned again
switch ($("#acct_group_by", div).val()){
case "user":
var group_by_fn = function(history){
return history.VM.UID;
}
var group_by_prefix = tr("User")+" ";
break;
case "group":
var group_by_fn = function(history){
return history.VM.GID;
}
var group_by_prefix = tr("Group")+" ";
break;
case "vm":
var group_by_fn = function(history){
return history.OID;
}
var group_by_prefix = tr("VM")+" ";
break;
}
//--------------------------------------------------------------------------
// Filter history entries
//--------------------------------------------------------------------------
// TODO filter
// True to proccess, false to discard
var filter_by_fn = function(history){
// return history.OID == 3605 || history.OID == 2673;
return true;
}
//--------------------------------------------------------------------------
// Process data series for flot
//--------------------------------------------------------------------------
var series = {};
series.CPU_HOURS = {};
series.MEM_HOURS = {};
// TODO: response can be an empty object
$.each(response.HISTORY_RECORDS.HISTORY, function(index, history){
if(!filter_by_fn(history)){
return true; //continue
}
var group_by = group_by_fn(history);
if (series.CPU_HOURS[group_by] == undefined){
series.CPU_HOURS[group_by] = {};
}
if (series.MEM_HOURS[group_by] == undefined){
series.MEM_HOURS[group_by] = {};
}
// TODO Optimize getting here?
// var serie = series.CPU_HOURS[history.VM.UID];
for (var i = 0; i<times.length-1; i++){
var t = times[i];
var t_next = times[i+1];
if( (history.ETIME*1000 > t || history.ETIME == 0) &&
(history.STIME != 0 && history.STIME*1000 <= t_next) ) {
var stime = t;
if(history.STIME != 0){
stime = Math.max(t, history.STIME*1000);
}
var etime = t_next;
if(history.ETIME != 0){
etime = Math.min(t_next, history.ETIME*1000);
}
var n_hours = (etime - stime) / 1000 / 60 / 60;
// --- cpu ---
// TODO cpu may not exist
var val = parseFloat(history.VM.TEMPLATE.CPU) * n_hours;
var serie = series.CPU_HOURS[group_by];
if(serie[t] == undefined){
serie[t] = 0;
}
serie[t] += val;
// --- mem ---
// TODO memory may not exist. In GB
var val = parseInt(history.VM.TEMPLATE.MEMORY)/1024 * n_hours;
var serie = series.MEM_HOURS[group_by];
if(serie[t] == undefined){
serie[t] = 0;
}
serie[t] += val;
}
}
});
// console.log(series);
//--------------------------------------------------------------------------
// Create series, draw plots
//--------------------------------------------------------------------------
// --- cpu ---
var plot_series = [];
$.each(series.CPU_HOURS, function(key, val){
var data = [];
$.each(val, function(time,num){
data.push([parseInt(time),num]);
});
plot_series.push(
{
label: group_by_prefix+key,
data: data
});
});
$.plot($("#acct_cpu_graph", div), plot_series, options);
// --- mem ---
var plot_series = [];
$.each(series.MEM_HOURS, function(key, val){
var data = [];
$.each(val, function(time,num){
data.push([parseInt(time),num]);
});
plot_series.push(
{
label: group_by_prefix+key,
data: data
});
});
$.plot($("#acct_mem_graph", div), plot_series, options);
}

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,7 @@
<script src="vendor/flot/jquery.flot.pie.min.js?v=<%= OpenNebula::VERSION %>" type="text/javascript"></script>
<script src="vendor/flot/jquery.flot.resize.min.js?v=<%= OpenNebula::VERSION %>" type="text/javascript"></script>
<script src="vendor/flot/jquery.flot.stack.min.js?v=<%= OpenNebula::VERSION %>" type="text/javascript"></script>
<script src="vendor/flot/jquery.flot.tooltip.min.js?v=<%= OpenNebula::VERSION %>" type="text/javascript"></script>
<script src="vendor/fileuploader/fileuploader.js?v=<%= OpenNebula::VERSION %>" type="text/javascript"></script>
<script src="vendor/4.0/nouislider/jquery.nouislider.min.js?v=<%= OpenNebula::VERSION %>" type="text/javascript"></script>
<script src="vendor/4.0/jdpicker_1.1/jquery.jdpicker.js"?v=<%= OpenNebula::VERSION %> type="text/javascript"></script>