From 46a32795bcbefd7475d62421a71fbdeff0e2c407 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 24 May 2019 15:58:15 +0530 Subject: [PATCH] ui: instance settings visibility (#3244) This change allows instance Settings tab to be visible but inaccessible when instance is running. A warning is shown when user tries to access Settings for a running instance and tab content is greyed out. It also allows some admin defined instance settings/details to be made static for user. User will be able to see them in instance settings tab but cannot change their values as action buttons are disabled and greyed out. This can be achieved by providing a comma-separated list details for global settings key 'user.vm.readonly.ui.details'. A new value 'readonlyuidetails' has been added in UserVMResponse for UI manipulate editing functionality of settings/details. Signed-off-by: Abhishek Kumar --- .../api/response/UserVmResponse.java | 12 ++ .../apache/cloudstack/query/QueryService.java | 4 + .../com/cloud/api/query/QueryManagerImpl.java | 6 +- .../api/query/dao/UserVmJoinDaoImpl.java | 3 + ui/css/cloudstack3.css | 15 ++ ui/l10n/en.js | 2 + ui/scripts/instances.js | 164 +++++++++++------- 7 files changed, 137 insertions(+), 69 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 8db4f853243..bbf6b6cf614 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -262,6 +262,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "Vm details in key/value pairs.", since = "4.2.1") private Map details; + @SerializedName("readonlyuidetails") + @Param(description = "List of UI read-only Vm details as comma separated string.", since = "4.13.0") + private String readOnlyUIDetails; + @SerializedName(ApiConstants.SSH_KEYPAIR) @Param(description = "ssh key-pair") private String keyPairName; @@ -810,6 +814,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co this.details = details; } + public void setReadOnlyUIDetails(String readOnlyUIDetails) { + this.readOnlyUIDetails = readOnlyUIDetails; + } + public void setOsTypeId(String osTypeId) { this.osTypeId = osTypeId; } @@ -826,6 +834,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co return details; } + public String getReadOnlyUIDetails() { + return readOnlyUIDetails; + } + public Boolean getDynamicallyScalable() { return isDynamicallyScalable; } diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index e8fd0b8a977..6a7c200cc28 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -92,6 +92,10 @@ public interface QueryService { "user.vm.blacklisted.details", "rootdisksize, cpuOvercommitRatio, memoryOvercommitRatio, Message.ReservedCapacityFreed.Flag", "Determines whether users can view certain VM settings", true); + static final ConfigKey UserVMReadOnlyUIDetails = new ConfigKey("Advanced", String.class, + "user.vm.readonly.ui.details", "dataDiskController, rootDiskController", + "List of UI read-only VM settings/details as comma separated string", true); + ListResponse searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException; ListResponse searchForEvents(ListEventsCmd cmd); diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index e1652773399..2cf72e26ae6 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -25,8 +25,6 @@ import java.util.Set; import javax.inject.Inject; -import com.cloud.cluster.ManagementServerHostVO; -import com.cloud.cluster.dao.ManagementServerHostDao; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; import org.apache.cloudstack.affinity.AffinityGroupResponse; @@ -156,6 +154,8 @@ import com.cloud.api.query.vo.TemplateJoinVO; import com.cloud.api.query.vo.UserAccountJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.api.query.vo.VolumeJoinVO; +import com.cloud.cluster.ManagementServerHostVO; +import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.dao.DedicatedResourceDao; import com.cloud.domain.Domain; @@ -3714,6 +3714,6 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {AllowUserViewDestroyedVM, UserVMBlacklistedDetails}; + return new ConfigKey[] {AllowUserViewDestroyedVM, UserVMBlacklistedDetails, UserVMReadOnlyUIDetails}; } } diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 9b757769682..58b167f6ac3 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -321,6 +321,9 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation').addClass('blocking-overlay')); + cloudStack.dialog.notice({ + message: _l('message.action.settings.warning.vm.running') + }); + } else { + if(virtualMachine && virtualMachine.readonlyuidetails && virtualMachine.readonlyuidetails.length > 0) { + var readOnlyUIDetails = [] + $.each(virtualMachine.readonlyuidetails.split(","), function(){ + readOnlyUIDetails.push($.trim(this)); + }); + $('#details-tab-settings tr').each(function() { + if($.inArray($.trim($(this).find('td:first').text()), readOnlyUIDetails) >= 0) { + $(this).find('td:last div.action').each(function() { + $(this).addClass("disabled") + }); + } + }); + } + }; }, error: function(json) { @@ -3234,85 +3251,100 @@ name: args.data.jsonObj.name, value: args.data.value }; - var existingDetails; + var virtualMachine; $.ajax({ url: createURL('listVirtualMachines&id=' + args.context.instances[0].id), async:false, success: function(json) { - var details = json.listvirtualmachinesresponse.virtualmachine[0].details; - console.log(details); - existingDetails = details; + virtualMachine = json.listvirtualmachinesresponse.virtualmachine[0]; }, error: function(json) { args.response.error(parseXMLHttpResponse(json)); } }); - console.log(existingDetails); - var newDetails = ''; - for (d in existingDetails) { - if (d != data.name) { - newDetails += 'details[0].' + d + '=' + existingDetails[d] + '&'; - } - } - newDetails += 'details[0].' + data.name + '=' + data.value; - - $.ajax({ - url: createURL('updateVirtualMachine&id=' + args.context.instances[0].id + '&' + newDetails), - async:false, - success: function(json) { - var items = json.updatevirtualmachineresponse.virtualmachine.details; - args.response.success({ - data: parseDetails(items) - }); - }, - - error: function(json) { - args.response.error(parseXMLHttpResponse(json)); - } - }); - }, + if (virtualMachine && virtualMachine.state == "Stopped") { + // It could happen that a stale web page has been opened up when VM was stopped but + // vm was turned on through another route - UI or API. so we should check again. + var existingDetails = virtualMachine.details; + console.log(existingDetails); + var newDetails = {}; + for (d in existingDetails) { + if (d != data.name) { + newDetails['details[0].' + d] = existingDetails[d]; + } + } + newDetails['details[0].' + data.name] = data.value; + var postData = {'id' : args.context.instances[0].id}; + $.extend(postData, newDetails); + $.ajax({ + url: createURL('updateVirtualMachine'), + data: postData, + async:false, + success: function(json) { + var items = json.updatevirtualmachineresponse.virtualmachine.details; + args.response.success({ + data: parseDetails(items) + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + }); + } else { + $('#details-tab-settings').append($('
').addClass('blocking-overlay')); + cloudStack.dialog.notice({ + message: _l('message.action.settings.warning.vm.started') + }); + } + }, remove: function(args) { - var existingDetails; + var virtualMachine; $.ajax({ url: createURL('listVirtualMachines&id=' + args.context.instances[0].id), async:false, success: function(json) { - var details = json.listvirtualmachinesresponse.virtualmachine[0].details; - existingDetails = details; + virtualMachine = json.listvirtualmachinesresponse.virtualmachine[0]; }, error: function(json) { args.response.error(parseXMLHttpResponse(json)); } }); - - var detailToDelete = args.data.jsonObj.name; - var newDetails = '' - for (detail in existingDetails) { - if (detail != detailToDelete) { - newDetails += 'details[0].' + detail + '=' + existingDetails[detail] + '&'; - } - } - if (newDetails != '') { - newDetails = newDetails.substring(0, newDetails.length - 1); - } - else { - newDetails += 'cleanupdetails=true' - } - $.ajax({ - url: createURL('updateVirtualMachine&id=' + args.context.instances[0].id + '&' + newDetails), - async:false, - success: function(json) { - var items = json.updatevirtualmachineresponse.virtualmachine.details; - args.response.success({ - data: parseDetails(items) - }); - }, - error: function(json) { - args.response.error(parseXMLHttpResponse(json)); - } - }); + if (virtualMachine && virtualMachine.state == "Stopped") { + // It could happen that a stale web page has been opened up when VM was stopped but + // vm was turned on through another route - UI or API. so we should check again. + var detailToDelete = args.data.jsonObj.name; + var existingDetails = virtualMachine.details; + var newDetails = {}; + for (detail in existingDetails) { + if (detail != detailToDelete) { + newDetails['details[0].' + detail] = existingDetails[detail]; + } + } + + var postData = $.isEmptyObject(newDetails) ? {'cleanupdetails': true} : newDetails; + $.extend(postData, {'id' : args.context.instances[0].id}); + $.ajax({ + url: createURL('updateVirtualMachine'), + data: postData, + async:false, + success: function(json) { + var items = json.updatevirtualmachineresponse.virtualmachine.details; + args.response.success({ + data: parseDetails(items) + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + }); + } else { + $('#details-tab-settings').append($('
').addClass('blocking-overlay')); + cloudStack.dialog.notice({ + message: _l('message.action.settings.warning.vm.started') + }); + } }, add: function(args) { var name = args.data.name;