1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-25 02:50:08 +03:00

Feature #1288: Add user and group quotas support to Sunstone

(cherry picked from commit 5b66d7e3d0799b974a125385feb2d6ee7a984fe1)
This commit is contained in:
Hector Sanjuan 2012-06-21 15:45:14 +02:00 committed by Ruben S. Montero
parent 7122f5d5b0
commit 99529a168f
8 changed files with 490 additions and 5 deletions

View File

@ -45,7 +45,8 @@ module OpenNebulaJSON
end
rc = case action_hash['perform']
when "chown" then self.chown(action_hash['params'])
when "chown" then self.chown(action_hash['params'])
when "set_quota" then self.set_quota(action_hash['params'])
else
error_msg = "#{action_hash['perform']} action not " <<
" available for this resource"
@ -56,5 +57,11 @@ module OpenNebulaJSON
def chown(params=Hash.new)
super(params['owner_id'].to_i)
end
def set_quota(params=Hash.new)
quota_json = params['quotas']
quota_template = template_to_str(quota_json)
super(quota_template)
end
end
end

View File

@ -42,6 +42,7 @@ module OpenNebulaJSON
when "chgrp" then self.chgrp(action_hash['params'])
when "chauth" then self.chauth(action_hash['params'])
when "update" then self.update(action_hash['params'])
when "set_quota" then self.set_quota(action_hash['params'])
when "addgroup" then self.addgroup(action_hash['params'])
when "delgroup" then self.delgroup(action_hash['params'])
else
@ -67,6 +68,12 @@ module OpenNebulaJSON
super(params['template_raw'])
end
def set_quota(params=Hash.new)
quota_json = params['quotas']
quota_template = template_to_str(quota_json)
super(quota_template)
end
def addgroup(params=Hash.new)
super(params['group_id'].to_i)
end

View File

@ -663,4 +663,12 @@ ul.action_list li a:hover{
font-family: serif;
text-align:center;
vertical-align:middle;
}
.quota_edit_icon:hover, .quota_remove_icon:hover {
cursor: pointer;
}
div.current_quotas ul{
list-style: none;
}

View File

@ -693,6 +693,13 @@ var OpenNebula = {
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.Group.resource);
},
"set_quota" : function(params){
var action_obj = { quotas : params.data.extra_param };
OpenNebula.Action.simple_action(params,OpenNebula.Group.resource,"set_quota",action_obj);
},
"show" : function(params){
OpenNebula.Action.show(params,OpenNebula.Group.resource);
}
},
@ -736,6 +743,11 @@ var OpenNebula = {
"fetch_template" : function(params){
OpenNebula.Action.show(params,OpenNebula.User.resource,"template");
},
"set_quota" : function(params){
var action_obj = { quotas : params.data.extra_param };
OpenNebula.Action.simple_action(params,OpenNebula.User.resource,"set_quota",action_obj);
},
// "addgroup" : function(params){
// var action_obj = {"group_id": params.data.extra_param };
// OpenNebula.Action.simple_action(params,OpenNebula.User.resource,

View File

@ -17,6 +17,7 @@
var groups_select="";
var dataTable_groups;
var $create_group_dialog;
var $group_quotas_dialog;
var groups_tab_content = '\
<h2>'+tr("Groups")+'</h2>\
@ -59,6 +60,69 @@ var create_group_tmpl =
</fieldset>\
</form>';
var group_quotas_tmpl = '<form id="group_quotas_form" action="">\
<fieldset>\
<div>'+tr("Please add/edit/remove quotas and click on the apply changes button. Note that if several items are selected, changes will be applied to each of them")+'.</div>\
<div>'+tr("Add quota")+':</div>\
<div id="quota_types">\
<label>'+tr("Quota type")+':</label>\
<input type="radio" name="quota_type" value="vm">'+tr("Virtual Machine")+'</input>\
<input type="radio" name="quota_type" value="datastore">'+tr("Datastore")+'</input>\
<input type="radio" name="quota_type" value="image">'+tr("Image")+'</input>\
<input type="radio" name="quota_type" value="network">'+tr("Network")+'</input>\
</div>\
<div id="vm_quota">\
<label>'+tr("Max VMs")+':</label>\
<input type="text" name="VMS"></input><br />\
<label>'+tr("Max Memory (MB)")+':</label>\
<input type="text" name="MEMORY"></input><br />\
<label>'+tr("Max CPU")+':</label>\
<input type="text" name="CPU"></input>\
</div>\
<div id="datastore_quota">\
<label>'+tr("Datastore")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max size (MB)")+':</label>\
<input type="text" name="SIZE"></input><br />\
<label>'+tr("Max images")+':</label>\
<input type="text" name="IMAGES"></input>\
</div>\
<div id="image_quota">\
<label>'+tr("Image")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max RVMs")+'</label>\
<input type="text" name="RVMS"></input>\
</div>\
<div id="network_quota">\
<label>'+tr("Network")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max leases")+'</label>\
<input type="text" name="LEASES"></input>\
</div>\
<button style="width:100px!important;" class="add_remove_button add_button" id="add_quota_button" value="add_quota">'+tr("Add/edit quota")+'</button>\
<div class="clear"></div>\
<div class="clear"></div>\
<div>'+tr("Current quotas")+':</div>\
<div class="current_quotas">\
<label>'+tr("VM quota")+':</label><br />\
<ul id="quotas_ul_vm">\
</ul>\
<label>'+tr("Datastore quotas")+':</label><br />\
<ul id="quotas_ul_datastore">\
</ul>\
<label>'+tr("Image quotas")+':</label><br />\
<ul id="quotas_ul_image">\
</ul>\
<label>'+tr("Network quotas")+':</label><br />\
<ul id="quotas_ul_network">\
</ul>\
</div>\
<div class="form_buttons">\
<button class="button" type="submit" value="Group.set_quota">'+tr("Apply changes")+'</button>\
</div>\
</fieldset>\
</form>';
var group_actions = {
"Group.create" : {
@ -119,6 +183,34 @@ var group_actions = {
// error : onError,
// notify:true
// },
"Group.fetch_quotas" : {
type: "single",
call: OpenNebula.Group.show,
callback: function (request,response) {
var parsed = parseQuotas(response.GROUP);
$('ul#quotas_ul_vm',$group_quotas_dialog).html(parsed.VM)
$('ul#quotas_ul_datastore',$group_quotas_dialog).html(parsed.DATASTORE)
$('ul#quotas_ul_image',$group_quotas_dialog).html(parsed.IMAGE)
$('ul#quotas_ul_network',$group_quotas_dialog).html(parsed.NETWORK)
},
error: onError
},
"Group.quotas_dialog" : {
type: "custom",
call: popUpGroupQuotasDialog
},
"Group.set_quota" : {
type: "multiple",
call: OpenNebula.Group.set_quota,
elements: groupElements,
callback: function() {
notifyMessage(tr("Quotas updated correctly"));
},
error: onError
},
"Group.help" : {
type: "custom",
call: function() {
@ -145,7 +237,10 @@ var group_buttons = {
// tip: "Select the new group owner:",
// condition : True
// },
"Group.quotas_dialog" : {
type : "action",
text : tr("Update quotas")
},
"Group.delete" : {
type: "confirm",
text: tr("Delete")
@ -276,6 +371,19 @@ function popUpCreateGroupDialog(){
return false;
}
function setupGroupQuotasDialog(){
dialogs_context.append('<div title="'+tr("Group quotas")+'" id="group_quotas_dialog"></div>');
$group_quotas_dialog = $('#group_quotas_dialog',dialogs_context);
var dialog = $group_quotas_dialog;
dialog.html(group_quotas_tmpl);
setupQuotasDialog(dialog);
}
function popUpGroupQuotasDialog(){
popUpQuotasDialog($group_quotas_dialog, 'Group', groupElements())
}
//Prepares the autorefresh
function setGroupAutorefresh(){
setInterval(function(){
@ -312,6 +420,7 @@ $(document).ready(function(){
Sunstone.runAction("Group.list");
setupCreateGroupDialog();
setupGroupQuotasDialog();
setGroupAutorefresh();
initCheckAllBoxes(dataTable_groups);

View File

@ -18,6 +18,7 @@
var dataTable_users;
var users_select="";
var $create_user_dialog;
var $user_quotas_dialog;
var $update_pw_dialog;
var users_tab_content = '\
@ -100,6 +101,69 @@ var update_pw_tmpl = '<form id="update_user_pw_form" action="">\
</fieldset>\
</form>';
var user_quotas_tmpl = '<form id="user_quotas_form" action="">\
<fieldset>\
<div>'+tr("Please add/edit/remove quotas and click on the apply changes button. Note that if several items are selected, changes will be applied to each of them")+'.</div>\
<div>'+tr("Add quota")+':</div>\
<div id="quota_types">\
<label>'+tr("Quota type")+':</label>\
<input type="radio" name="quota_type" value="vm">'+tr("Virtual Machine")+'</input>\
<input type="radio" name="quota_type" value="datastore">'+tr("Datastore")+'</input>\
<input type="radio" name="quota_type" value="image">'+tr("Image")+'</input>\
<input type="radio" name="quota_type" value="network">'+tr("Network")+'</input>\
</div>\
<div id="vm_quota">\
<label>'+tr("Max VMs")+':</label>\
<input type="text" name="VMS"></input><br />\
<label>'+tr("Max Memory (MB)")+':</label>\
<input type="text" name="MEMORY"></input><br />\
<label>'+tr("Max CPU")+':</label>\
<input type="text" name="CPU"></input>\
</div>\
<div id="datastore_quota">\
<label>'+tr("Datastore")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max size (MB)")+':</label>\
<input type="text" name="SIZE"></input><br />\
<label>'+tr("Max images")+':</label>\
<input type="text" name="IMAGES"></input>\
</div>\
<div id="image_quota">\
<label>'+tr("Image")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max RVMs")+'</label>\
<input type="text" name="RVMS"></input>\
</div>\
<div id="network_quota">\
<label>'+tr("Network")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max leases")+'</label>\
<input type="text" name="LEASES"></input>\
</div>\
<button style="width:100px!important;" class="add_remove_button add_button" id="add_quota_button" value="add_quota">'+tr("Add/edit quota")+'</button>\
<div class="clear"></div>\
<div class="clear"></div>\
<div>'+tr("Current quotas")+':</div>\
<div class="current_quotas">\
<label>'+tr("VM quota")+':</label><br />\
<ul id="quotas_ul_vm">\
</ul>\
<label>'+tr("Datastore quotas")+':</label><br />\
<ul id="quotas_ul_datastore">\
</ul>\
<label>'+tr("Image quotas")+':</label><br />\
<ul id="quotas_ul_image">\
</ul>\
<label>'+tr("Network quotas")+':</label><br />\
<ul id="quotas_ul_network">\
</ul>\
</div>\
<div class="form_buttons">\
<button class="button" type="submit" value="User.set_quota">'+tr("Apply changes")+'</button>\
</div>\
</fieldset>\
</form>';
var user_actions = {
"User.create" : {
@ -254,6 +318,34 @@ var user_actions = {
error: onError
},
"User.fetch_quotas" : {
type: "single",
call: OpenNebula.User.show,
callback: function (request,response) {
var parsed = parseQuotas(response.USER);
$('ul#quotas_ul_vm',$user_quotas_dialog).html(parsed.VM)
$('ul#quotas_ul_datastore',$user_quotas_dialog).html(parsed.DATASTORE)
$('ul#quotas_ul_image',$user_quotas_dialog).html(parsed.IMAGE)
$('ul#quotas_ul_network',$user_quotas_dialog).html(parsed.NETWORK)
},
error: onError
},
"User.quotas_dialog" : {
type: "custom",
call: popUpUserQuotasDialog
},
"User.set_quota" : {
type: "multiple",
call: OpenNebula.User.set_quota,
elements: userElements,
callback: function() {
notifyMessage(tr("Quotas updated correctly"));
},
error: onError
},
"User.help" : {
type: "custom",
call: function() {
@ -283,6 +375,10 @@ var user_buttons = {
type : "action",
text : tr("Change password"),
},
"User.quotas_dialog" : {
type : "action",
text : tr("Update quotas")
},
"User.chgrp" : {
type: "confirm_with_select",
text: tr("Change group"),
@ -331,6 +427,10 @@ var user_info_panel = {
title: tr("User information"),
content:""
},
"user_quotas_tab" : {
title: tr("User quotas"),
content:""
},
};
var users_tab = {
@ -473,7 +573,25 @@ function updateUserInfo(request,user){
'</table>'
};
var quotas_tab = {
title : tr("User quotas"),
content : '\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.DATASTORE_QUOTA)+'</tbody>\
</table>\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.VM_QUOTA)+'</tbody>\
</table>\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.IMAGE_QUOTA)+'</tbody>\
</table>\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.NETWORK_QUOTA)+'</tbody>\
</table>'
};
Sunstone.updateInfoPanelTab("user_info_panel","user_info_tab",info_tab);
Sunstone.updateInfoPanelTab("user_info_panel","user_quotas_tab",quotas_tab);
Sunstone.popUpInfoPanel("user_info_panel");
};
@ -554,8 +672,22 @@ function setupUpdatePasswordDialog(){
});
};
function setupUserQuotasDialog(){
dialogs_context.append('<div title="'+tr("User quotas")+'" id="user_quotas_dialog"></div>');
$user_quotas_dialog = $('#user_quotas_dialog',dialogs_context);
var dialog = $user_quotas_dialog;
dialog.html(user_quotas_tmpl);
setupQuotasDialog(dialog);
}
function popUpUserQuotasDialog(){
popUpQuotasDialog($user_quotas_dialog, 'User', userElements())
}
function popUpCreateUserDialog(){
$create_user_dialog.dialog('open');
}
@ -564,8 +696,6 @@ function popUpUpdatePasswordDialog(){
$update_pw_dialog.dialog('open');
}
// Prepare the autorefresh of the list
function setUserAutorefresh(){
setInterval(function(){
@ -610,6 +740,7 @@ $(document).ready(function(){
setupCreateUserDialog();
setupUpdatePasswordDialog();
setupUserQuotasDialog();
setUserAutorefresh();
initCheckAllBoxes(dataTable_users);

View File

@ -953,4 +953,208 @@ function buildOctet(permTable){
other+=1;
return ""+owner+group+other;
};
};
function setupQuotasDialog(dialog){
var height = Math.floor($(window).height()*0.8); //set height to a percentage of the window
//Prepare jquery dialog
dialog.dialog({
autoOpen: false,
modal:true,
width: 740,
height: height
});
$('button',dialog).button();
$('#vm_quota,#datastore_quota,#image_quota,#network_quota',dialog).hide();
$('#quota_types input',dialog).click(function(){
$('#vm_quota,#datastore_quota,#image_quota,#network_quota',dialog).hide();
$('#'+$(this).val()+'_quota',dialog).show();
$('#add_quota_button',dialog).show();
})
$('#add_quota_button',dialog).hide();
$('#add_quota_button',dialog).click(function(){
var sel = $('#quota_types input:checked',dialog).val();
var fields = $('div#'+sel+'_quota input,div#'+sel+'_quota select',dialog);
var json = {};
for (var i = 0; i < fields.length; i++){
var field = $(fields[i]);
var name = field.attr('name');
var value = field.val();
if (name == 'ID' && !value.length){
notifyError(tr("Please select an element"));
return false;
};
if (!value) value = 0;
json[name] = value;
};
json['TYPE'] = sel.toUpperCase();
var li = quotaListItem(json)
$('ul#quotas_ul_'+sel,dialog).append($(li).hide().fadeIn());
return false;
});
$('form', dialog).submit(function(){
var obj = {};
$('ul li',this).each(function(){
var json = JSON.parse($(this).attr('quota'));
var type = json['TYPE'];
delete json['TYPE'];
obj[type.toUpperCase()] = json;
});
var action = $('div.form_buttons button',this).val();
var sel_elems = SunstoneCfg["actions"][action].elements();
Sunstone.runAction(action,sel_elems,obj);
dialog.dialog('close');
return false;
});
}
function popUpQuotasDialog(dialog, resource, sel_elems){
var im_sel = makeSelectOptions(dataTable_images,1,4,[],[]);
var vn_sel = makeSelectOptions(dataTable_vNetworks,1,4,[],[]);
$('#datastore_quota select',dialog).html(datastores_sel());
$('#image_quota select',dialog).html(im_sel);
$('#network_quota select',dialog).html(vn_sel);
//If only one user is selected we fecth the user's quotas, otherwise we do nothing.
if (sel_elems.length == 1){
var id = sel_elems[0];
Sunstone.runAction(resource + '.fetch_quotas',id);
} else {
$('ul',dialog).empty();
};
dialog.dialog('open');
}
function setupQuotaIcons(){
$('.quota_edit_icon').live('click',function(){
var dialog = $(this).parents('form');
var li = $(this).parents('li');
var quota = JSON.parse(li.attr('quota'));
switch (quota.TYPE){
case "VM":
$('div#vm_quota input[name="VMS"]',dialog).val(quota.VMS);
$('div#vm_quota input[name="MEMORY"]',dialog).val(quota.MEMORY);
$('div#vm_quota input[name="CPU"]',dialog).val(quota.CPU);
break;
case "DATASTORE":
$('div#datastore_quota select[name="ID"]',dialog).val(quota.ID);
$('div#datastore_quota input[name="SIZE"]',dialog).val(quota.SIZE);
$('div#datastore_quota input[name="IMAGES"]').val(quota.IMAGES);
break;
case "IMAGE":
$('div#image_quota select[name="ID"]',dialog).val(quota.ID);
$('div#image_quota input[name="RVMS"]',dialog).val(quota.RVMS);
break;
case "NETWORK":
$('div#network_quota select[name="ID"]',dialog).val(quota.ID);
$('div#network_quota input[name="LEASES"]',dialog).val(quota.LEASES);
break;
}
$('div#quota_types input[value="'+quota.TYPE.toLowerCase()+'"]',dialog).trigger('click');
$(this).parents('li').fadeOut(function(){$(this).remove()});
return false;
});
$('.quota_remove_icon').live('click',function(){
$(this).parents('li').fadeOut(function(){$(this).remove()});
return false;
});
}
function parseQuotas(elem){
var quotas = [];
var results = {
VM : "",
DATASTORE : "",
IMAGE : "",
NETWORK : ""
}
//max 1 vm quota
if (!$.isEmptyObject(elem.VM_QUOTA)){
elem.VM_QUOTA.VM.TYPE = 'VM'
quotas.push(elem.VM_QUOTA.VM)
}
var ds_arr = []
if ($.isArray(elem.DATASTORE_QUOTA.DATASTORE)){
ds_arr = elem.DATASTORE_QUOTA.DATASTORE
} else if (!$.isEmptyObject(elem.DATASTORE_QUOTA)){
ds_arr = [elem.DATASTORE_QUOTA.DATASTORE]
}
for (var i = 0; i < ds_arr.length; i++){
ds_arr[i].TYPE = 'DATASTORE';
quotas.push(ds_arr[i]);
}
var im_arr = []
if ($.isArray(elem.IMAGE_QUOTA.IMAGE)){
im_arr = elem.IMAGE_QUOTA.IMAGE
} else if (!$.isEmptyObject(elem.IMAGE_QUOTA)){
im_arr = [elem.IMAGE_QUOTA.IMAGE]
}
for (var i = 0; i < im_arr.length; i++){
im_arr[i].TYPE = 'IMAGE';
quotas.push(im_arr[i]);
}
var vn_arr = []
if ($.isArray(elem.NETWORK_QUOTA)){
vn_arr = elem.NETWORK_QUOTA.NETWORK
} else if (!$.isEmptyObject(elem.NETWORK_QUOTA)){
vn_arr = [elem.NETWORK_QUOTA.NETWORK]
}
for (var i = 0; i < vn_arr.length; i++){
vn_arr[i].TYPE = 'NETWORK';
quotas.push(vn_arr[i]);
}
for (var i = 0; i < quotas.length; i++){
var li = quotaListItem(quotas[i]);
results[quotas[i].TYPE] += li;
}
return results;
}
//Receives a quota json object. Returns a nice string out of it.
function quotaListItem(quota_json){
var value = JSON.stringify(quota_json)
var str = '<li quota=\''+value+'\'><pre style="margin:0;">';
switch(quota_json.TYPE){
case "VM":
str += 'VMs: ' + quota_json.VMS + (quota_json.VMS_USED ? ' (' + quota_json.VMS_USED + '). ' : ". ") +
'Memory: ' + quota_json.MEMORY + (quota_json.MEMORY_USED ? ' (' + quota_json.MEMORY_USED + '). ' : ". ") +
'CPU: ' + quota_json.CPU + (quota_json.CPU_USED ? ' (' + quota_json.CPU_USED + '). ' : ". ");
break;
case "DATASTORE":
str += 'ID: ' + getDatastoreName(quota_json.ID) + '. ' +
'Size: ' + quota_json.SIZE + (quota_json.SIZE_USED ? ' (' + quota_json.SIZE_USED + '). ' : ". ") +
'Images: ' + quota_json.IMAGES + (quota_json.IMAGES_USED ? ' (' + quota_json.IMAGES_USED + '). ' : ".");
break;
case "IMAGE":
str += 'ID: ' + getImageName(quota_json.ID) + '. ' +
'RVMs: ' + quota_json.RVMS + (quota_json.RVMS_USED ? ' (' + quota_json.RVMS_USED + '). ' : ". ");
break;
case "NETWORK":
str += 'ID: ' + getVNetName(quota_json.ID) + '. ' +
'Leases: ' + quota_json.LEASES + (quota_json.LEASES_USED ? ' (' + quota_json.LEASES_USED + '). ': ". ");
break;
}
str += '<i class="quota_edit_icon icon-pencil"></i> <i class="quota_remove_icon icon-remove"></i></pre></li>';
return str;
}

View File

@ -309,6 +309,11 @@ $(document).ready(function(){
//This dialog is shared to update templates
setupTemplateUpdateDialog();
//Setup quota icons
//Live listeners not working when being added in specific
//context of users/groups dialog. Adding them globally then.
setupQuotaIcons();
//Listen for .action_buttons
//An action buttons runs a predefined action. If it has type
//"multiple" it runs that action on the elements of a datatable.
@ -373,6 +378,8 @@ $(document).ready(function(){
return false;
});
//Start with the dashboard (supposing we have one).
showTab('dashboard_tab');