From ed4c7933ccd207e71a4db4437be21043626ef17f Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Thu, 14 Mar 2019 13:58:40 +0530 Subject: [PATCH] ui: instance settings visibility 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 | 4 + ui/css/cloudstack3.css | 15 ++ ui/l10n/en.js | 2 + ui/scripts/instances.js | 164 +++++++++++------- 7 files changed, 138 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 8db4f853243c..bbf6b6cf6141 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 void setDetails(Map details) { this.details = details; } + public void setReadOnlyUIDetails(String readOnlyUIDetails) { + this.readOnlyUIDetails = readOnlyUIDetails; + } + public void setOsTypeId(String osTypeId) { this.osTypeId = osTypeId; } @@ -826,6 +834,10 @@ public Map getDetails() { 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 ac29dff23a63..f76642a8d6f0 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -88,6 +88,10 @@ public interface QueryService { static final ConfigKey AllowUserViewDestroyedVM = new ConfigKey("Advanced", Boolean.class, "allow.user.view.destroyed.vm", "false", "Determines whether users can view their destroyed or expunging vm ", true, ConfigKey.Scope.Account); + 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 91e0466d9dbb..ef0f68197fd7 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 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.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 String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {AllowUserViewDestroyedVM}; + return new ConfigKey[] {AllowUserViewDestroyedVM, 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 58d5e493d6d8..4b98bfb1e40b 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 @@ -42,6 +42,7 @@ import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiResponseHelper; +import com.cloud.api.query.QueryManagerImpl; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.gpu.GPU; import com.cloud.service.ServiceOfferingDetailsVO; @@ -312,6 +313,9 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us resourceDetails.put(userVmDetailVO.getName(), userVmDetailVO.getValue()); } userVmResponse.setDetails(resourceDetails); + if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { + userVmResponse.setReadOnlyUIDetails(QueryManagerImpl.UserVMReadOnlyUIDetails.value()); + } } userVmResponse.setObjectName(objectName); diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 5294f5658802..4ad0eaba35f5 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -539,6 +539,21 @@ div.list-view div.toolbar div.section-switcher div.section-select label { margin: 0 9px 0 0; } +.blocking-overlay { + position: absolute; + width: 100%; + height: 100%; + left: 0px; + top: 0px; + background: #F2F2F2; + z-index: 500; + /*+opacity:70%;*/ + filter: alpha(opacity=70); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); + -moz-opacity: 0.7; + opacity: 0.7; +} + .loading-overlay { position: absolute; width: 100%; diff --git a/ui/l10n/en.js b/ui/l10n/en.js index eb7b82250891..0982d4c3292b 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -1940,6 +1940,8 @@ var dictionary = { "message.action.restore.instance":"Please confirm that you want to restore this instance.", "message.action.revert.snapshot":"Please confirm that you want to revert the owning volume to this snapshot.", "message.action.secure.host":"This will restart the host agent and libvirtd process after applying new X509 certificates, please confirm?", +"message.action.settings.warning.vm.running":"Please stop the virtual machine to access settings", +"message.action.settings.warning.vm.started":"Virtual machine has been started. It needs to be stopped to access settings", "message.action.start.instance":"Please confirm that you want to start this instance.", "message.action.start.router":"Please confirm that you want to start this router.", "message.action.start.systemvm":"Please confirm that you want to start this system VM.", diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 7a959dff11c1..2ef5005ac53c 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -734,10 +734,6 @@ if (includingSecurityGroupService == false) { hiddenTabs.push("securityGroups"); } - - if (args.context.instances[0].state == 'Running') { - hiddenTabs.push("settings"); - } return hiddenTabs; }, @@ -3103,10 +3099,31 @@ $.ajax({ url: createURL('listVirtualMachines&id=' + args.context.instances[0].id), success: function(json) { - var details = json.listvirtualmachinesresponse.virtualmachine[0].details; - args.response.success({ - data: parseDetails(details) - }); + var virtualMachine = json.listvirtualmachinesresponse.virtualmachine[0]; + args.response.success({ + data: parseDetails(virtualMachine.details) + }); + + if (virtualMachine.state != 'Stopped') { + $('#details-tab-settings').append($('
').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) { @@ -3121,85 +3138,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; - }, - - 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) - }); + virtualMachine = json.listvirtualmachinesresponse.virtualmachine[0]; }, 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;