From 99529a168f54ce9fe7343b52762828e008dd2ab3 Mon Sep 17 00:00:00 2001 From: Hector Sanjuan Date: Thu, 21 Jun 2012 15:45:14 +0200 Subject: [PATCH] Feature #1288: Add user and group quotas support to Sunstone (cherry picked from commit 5b66d7e3d0799b974a125385feb2d6ee7a984fe1) --- .../models/OpenNebulaJSON/GroupJSON.rb | 9 +- .../models/OpenNebulaJSON/UserJSON.rb | 7 + src/sunstone/public/css/application.css | 8 + src/sunstone/public/js/opennebula.js | 12 + src/sunstone/public/js/plugins/groups-tab.js | 111 +++++++++- src/sunstone/public/js/plugins/users-tab.js | 135 +++++++++++- src/sunstone/public/js/sunstone-util.js | 206 +++++++++++++++++- src/sunstone/public/js/sunstone.js | 7 + 8 files changed, 490 insertions(+), 5 deletions(-) diff --git a/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb b/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb index d502973d0c..35e01f1403 100644 --- a/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb +++ b/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb @@ -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 diff --git a/src/sunstone/models/OpenNebulaJSON/UserJSON.rb b/src/sunstone/models/OpenNebulaJSON/UserJSON.rb index c51c0bcfb2..ebf4338802 100644 --- a/src/sunstone/models/OpenNebulaJSON/UserJSON.rb +++ b/src/sunstone/models/OpenNebulaJSON/UserJSON.rb @@ -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 diff --git a/src/sunstone/public/css/application.css b/src/sunstone/public/css/application.css index bed2567dca..36feb13fbd 100644 --- a/src/sunstone/public/css/application.css +++ b/src/sunstone/public/css/application.css @@ -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; } \ No newline at end of file diff --git a/src/sunstone/public/js/opennebula.js b/src/sunstone/public/js/opennebula.js index fcc7fac15b..716a1ebdda 100644 --- a/src/sunstone/public/js/opennebula.js +++ b/src/sunstone/public/js/opennebula.js @@ -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, diff --git a/src/sunstone/public/js/plugins/groups-tab.js b/src/sunstone/public/js/plugins/groups-tab.js index 03edb0f522..df5c7fe6ff 100644 --- a/src/sunstone/public/js/plugins/groups-tab.js +++ b/src/sunstone/public/js/plugins/groups-tab.js @@ -17,6 +17,7 @@ var groups_select=""; var dataTable_groups; var $create_group_dialog; +var $group_quotas_dialog; var groups_tab_content = '\

'+tr("Groups")+'

\ @@ -59,6 +60,69 @@ var create_group_tmpl = \ '; +var group_quotas_tmpl = '
\ +
\ +
'+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")+'.
\ +
'+tr("Add quota")+':
\ +
\ + \ + '+tr("Virtual Machine")+'\ + '+tr("Datastore")+'\ + '+tr("Image")+'\ + '+tr("Network")+'\ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ + \ +
\ +
\ +
'+tr("Current quotas")+':
\ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
\ + \ +
\ +
\ +
'; + 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('
'); + $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); diff --git a/src/sunstone/public/js/plugins/users-tab.js b/src/sunstone/public/js/plugins/users-tab.js index c3ed6343fd..543da2c588 100644 --- a/src/sunstone/public/js/plugins/users-tab.js +++ b/src/sunstone/public/js/plugins/users-tab.js @@ -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 = '
\ \
'; +var user_quotas_tmpl = '
\ +
\ +
'+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")+'.
\ +
'+tr("Add quota")+':
\ +
\ + \ + '+tr("Virtual Machine")+'\ + '+tr("Datastore")+'\ + '+tr("Image")+'\ + '+tr("Network")+'\ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ + \ +
\ +
\ +
'+tr("Current quotas")+':
\ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
\ + \ +
\ +
\ +
'; + 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){ '' }; + var quotas_tab = { + title : tr("User quotas"), + content : '\ + \ + '+prettyPrintJSON(user_info.DATASTORE_QUOTA)+'\ +
\ + \ + '+prettyPrintJSON(user_info.VM_QUOTA)+'\ +
\ + \ + '+prettyPrintJSON(user_info.IMAGE_QUOTA)+'\ +
\ + \ + '+prettyPrintJSON(user_info.NETWORK_QUOTA)+'\ +
' + }; + 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('
'); + $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); diff --git a/src/sunstone/public/js/sunstone-util.js b/src/sunstone/public/js/sunstone-util.js index f887985a43..dbdb401bf7 100644 --- a/src/sunstone/public/js/sunstone-util.js +++ b/src/sunstone/public/js/sunstone-util.js @@ -953,4 +953,208 @@ function buildOctet(permTable){ other+=1; return ""+owner+group+other; -}; \ No newline at end of file +}; + +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 = '
  • ';
    +    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 += ' 
  • '; + return str; +} \ No newline at end of file diff --git a/src/sunstone/public/js/sunstone.js b/src/sunstone/public/js/sunstone.js index 1cc349f8de..ae10d40f8d 100644 --- a/src/sunstone/public/js/sunstone.js +++ b/src/sunstone/public/js/sunstone.js @@ -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');