You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ra...@apache.org on 2017/02/08 06:43:41 UTC

[1/2] git commit: updated refs/heads/master to 202b92f

Repository: cloudstack
Updated Branches:
  refs/heads/master 30aef2890 -> 202b92f24


CLOUDSTACK-9457: Allow retrieval and modification of VM and template details via API and UI


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/e8049af1
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/e8049af1
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/e8049af1

Branch: refs/heads/master
Commit: e8049af1534f1ab2cc8335034c2fd76c67f9fdec
Parents: 0db4471
Author: nvazquez <ni...@gmail.com>
Authored: Tue Dec 27 23:33:50 2016 -0300
Committer: nvazquez <ni...@gmail.com>
Committed: Tue Dec 27 23:33:50 2016 -0300

----------------------------------------------------------------------
 .../org/apache/cloudstack/api/ApiConstants.java |   1 +
 .../api/BaseUpdateTemplateOrIsoCmd.java         |  12 +-
 .../api/command/user/vm/UpdateVMCmd.java        |   9 +
 .../com/cloud/storage/VMTemplateDetailVO.java   |  16 ++
 .../schema/src/com/cloud/vm/UserVmDetailVO.java |   7 +
 .../cloud/api/query/dao/UserVmJoinDaoImpl.java  |  11 +-
 .../com/cloud/template/TemplateManagerImpl.java |  13 +-
 server/src/com/cloud/vm/UserVmManagerImpl.java  |  14 +-
 .../cloud/template/TemplateManagerImplTest.java |   9 +
 ui/scripts/instances.js                         | 167 ++++++++++++++++++-
 ui/scripts/templates.js                         | 121 +++++++++++++-
 ui/scripts/ui-custom/granularSettings.js        |  86 +++++++++-
 12 files changed, 445 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/api/src/org/apache/cloudstack/api/ApiConstants.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java
index 00e9d38..f7f2a37 100644
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -648,6 +648,7 @@ public class ApiConstants {
     public static final String OVM3_POOL = "ovm3pool";
     public static final String OVM3_CLUSTER = "ovm3cluster";
     public static final String OVM3_VIP = "ovm3vip";
+    public static final String CLEAN_UP_DETAILS = "cleanupdetails";
 
     public static final String ADMIN = "admin";
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java b/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
index 5dc2b06..3676734 100644
--- a/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
+++ b/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
@@ -17,7 +17,6 @@
 package org.apache.cloudstack.api;
 
 import org.apache.log4j.Logger;
-
 import org.apache.cloudstack.api.command.user.iso.UpdateIsoCmd;
 import org.apache.cloudstack.api.response.GuestOSResponse;
 import org.apache.cloudstack.api.response.TemplateResponse;
@@ -73,6 +72,11 @@ public abstract class BaseUpdateTemplateOrIsoCmd extends BaseCmd {
     @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].hypervisortoolsversion=xenserver61")
     protected Map details;
 
+    @Parameter(name = ApiConstants.CLEAN_UP_DETAILS,
+            type = CommandType.BOOLEAN,
+            description = "optional boolean field, which indicates if details should be cleaned up or not (if set to true, details removed for this resource, details field ignored; if false or not set, no action)")
+    private Boolean cleanupDetails;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -129,4 +133,8 @@ public abstract class BaseUpdateTemplateOrIsoCmd extends BaseCmd {
         Collection paramsCollection = this.details.values();
         return (Map) (paramsCollection.toArray())[0];
     }
-}
\ No newline at end of file
+
+    public boolean isCleanupDetails(){
+        return cleanupDetails == null ? false : cleanupDetails.booleanValue();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
index 4508f7e..eb03f08 100644
--- a/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
+++ b/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
@@ -116,6 +116,11 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction
             )
     private List<String> securityGroupNameList;
 
+    @Parameter(name = ApiConstants.CLEAN_UP_DETAILS,
+            type = CommandType.BOOLEAN,
+            description = "optional boolean field, which indicates if details should be cleaned up or not (if set to true, details removed for this resource, details field ignored; if false or not set, no action)")
+    private Boolean cleanupDetails;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -173,6 +178,10 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction
         return securityGroupNameList;
     }
 
+    public boolean isCleanupDetails(){
+        return cleanupDetails == null ? false : cleanupDetails.booleanValue();
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/engine/schema/src/com/cloud/storage/VMTemplateDetailVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/storage/VMTemplateDetailVO.java b/engine/schema/src/com/cloud/storage/VMTemplateDetailVO.java
old mode 100644
new mode 100755
index f988aba..5010edf
--- a/engine/schema/src/com/cloud/storage/VMTemplateDetailVO.java
+++ b/engine/schema/src/com/cloud/storage/VMTemplateDetailVO.java
@@ -79,4 +79,20 @@ public class VMTemplateDetailVO implements ResourceDetail {
     public boolean isDisplay() {
         return display;
     }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public void setResourceId(long resourceId) {
+        this.resourceId = resourceId;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/engine/schema/src/com/cloud/vm/UserVmDetailVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/vm/UserVmDetailVO.java b/engine/schema/src/com/cloud/vm/UserVmDetailVO.java
old mode 100644
new mode 100755
index 2b169a3..81bb6dd
--- a/engine/schema/src/com/cloud/vm/UserVmDetailVO.java
+++ b/engine/schema/src/com/cloud/vm/UserVmDetailVO.java
@@ -80,4 +80,11 @@ public class UserVmDetailVO implements ResourceDetail {
         return display;
     }
 
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
index 106cd25..75b42c2 100644
--- a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
+++ b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -51,7 +51,6 @@ import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.vm.UserVmDetailVO;
 import com.cloud.vm.VirtualMachine.State;
-import com.cloud.vm.VmDetailConstants;
 import com.cloud.vm.VmStats;
 import com.cloud.vm.dao.NicSecondaryIpVO;
 import com.cloud.vm.dao.UserVmDetailsDao;
@@ -292,11 +291,13 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
         }
 
         // set resource details map
-        // only hypervisortoolsversion can be returned to the end user
-        UserVmDetailVO hypervisorToolsVersion = _userVmDetailsDao.findDetail(userVm.getId(), VmDetailConstants.HYPERVISOR_TOOLS_VERSION);
-        if (hypervisorToolsVersion != null) {
+        // Allow passing details to end user
+        List<UserVmDetailVO> vmDetails = _userVmDetailsDao.listDetails(userVm.getId());
+        if (vmDetails != null) {
             Map<String, String> resourceDetails = new HashMap<String, String>();
-            resourceDetails.put(hypervisorToolsVersion.getName(), hypervisorToolsVersion.getValue());
+            for (UserVmDetailVO userVmDetailVO : vmDetails) {
+                resourceDetails.put(userVmDetailVO.getName(), userVmDetailVO.getValue());
+            }
             userVmResponse.setDetails(resourceDetails);
         }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/server/src/com/cloud/template/TemplateManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java
index 7130042..c20889e 100644
--- a/server/src/com/cloud/template/TemplateManagerImpl.java
+++ b/server/src/com/cloud/template/TemplateManagerImpl.java
@@ -161,6 +161,7 @@ import com.cloud.storage.dao.LaunchPermissionDao;
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VMTemplateDetailsDao;
 import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.storage.dao.VMTemplateZoneDao;
 import com.cloud.storage.dao.VolumeDao;
@@ -269,6 +270,8 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
     private ImageStoreDao _imgStoreDao;
     @Inject
     MessageBus _messageBus;
+    @Inject
+    private VMTemplateDetailsDao _tmpltDetailsDao;
 
     private boolean _disableExtraction = false;
     private List<TemplateAdapter> _adapters;
@@ -1880,6 +1883,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
         Integer sortKey = cmd.getSortKey();
         Map details = cmd.getDetails();
         Account account = CallContext.current().getCallingAccount();
+        boolean cleanupDetails = cmd.isCleanupDetails();
 
         // verify that template exists
         VMTemplateVO template = _tmpltDao.findById(id);
@@ -1911,7 +1915,8 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
                   sortKey == null &&
                   isDynamicallyScalable == null &&
                   isRoutingTemplate == null &&
-                        details == null);
+                  (! cleanupDetails && details == null) //update details in every case except this one
+                  );
         if (!updateNeeded) {
             return template;
         }
@@ -1989,7 +1994,11 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
             }
         }
 
-        if (details != null && !details.isEmpty()) {
+        if (cleanupDetails) {
+            template.setDetails(null);
+            _tmpltDetailsDao.removeDetails(id);
+        }
+        else if (details != null && !details.isEmpty()) {
             template.setDetails(details);
             _tmpltDao.saveDetails(template);
         }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/server/src/com/cloud/vm/UserVmManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java
index 453800d..1db0956 100644
--- a/server/src/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/com/cloud/vm/UserVmManagerImpl.java
@@ -2307,6 +2307,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
         Map<String,String> details = cmd.getDetails();
         Account caller = CallContext.current().getCallingAccount();
         List<Long> securityGroupIdList = getSecurityGroupIdList(cmd);
+        boolean cleanupDetails = cmd.isCleanupDetails();
 
         // Input validation and permission checks
         UserVmVO vmInstance = _vmDao.findById(id);
@@ -2345,14 +2346,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
 
         }
 
-        if (details != null && !details.isEmpty()) {
-            _vmDao.loadDetails(vmInstance);
-
-            for(Map.Entry<String,String> entry : details.entrySet()) {
-                if(entry instanceof Map.Entry) {
-                    vmInstance.setDetail(entry.getKey(), entry.getValue());
-                }
-            }
+        if (cleanupDetails){
+            _vmDetailsDao.removeDetails(id);
+        }
+        else if (details != null && !details.isEmpty()) {
+            vmInstance.setDetails(details);
             _vmDao.saveDetails(vmInstance);
         }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/server/test/com/cloud/template/TemplateManagerImplTest.java
----------------------------------------------------------------------
diff --git a/server/test/com/cloud/template/TemplateManagerImplTest.java b/server/test/com/cloud/template/TemplateManagerImplTest.java
index 6e16938..61a8a4a 100644
--- a/server/test/com/cloud/template/TemplateManagerImplTest.java
+++ b/server/test/com/cloud/template/TemplateManagerImplTest.java
@@ -45,6 +45,7 @@ import com.cloud.storage.dao.LaunchPermissionDao;
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VMTemplateDetailsDao;
 import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.storage.dao.VMTemplateZoneDao;
 import com.cloud.storage.dao.VolumeDao;
@@ -161,6 +162,9 @@ public class TemplateManagerImplTest {
     @Inject
     SnapshotDao snapshotDao;
 
+    @Inject
+    VMTemplateDetailsDao tmpltDetailsDao;
+
     public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
         AtomicInteger ai = new AtomicInteger(0);
         public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
@@ -623,6 +627,11 @@ public class TemplateManagerImplTest {
             return Mockito.mock(TemplateAdapter.class);
         }
 
+        @Bean
+        public VMTemplateDetailsDao vmTemplateDetailsDao() {
+            return Mockito.mock(VMTemplateDetailsDao.class);
+        }
+
         public static class Library implements TypeFilter {
             @Override
             public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException {

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/ui/scripts/instances.js
----------------------------------------------------------------------
diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js
index 19db257..ec10df8 100644
--- a/ui/scripts/instances.js
+++ b/ui/scripts/instances.js
@@ -497,6 +497,10 @@
                     if (includingSecurityGroupService == false) {
                         hiddenTabs.push("securityGroups");
                     }
+					
+					if (args.context.instances[0].state == 'Running') {
+						hiddenTabs.push("settings");
+					}
 
                     return hiddenTabs;
                 },
@@ -2679,11 +2683,172 @@
                                 }
                             });
                         }
-                    }
+                    },
+					
+					/**
+                     * Settings tab
+                     */
+					settings: {
+						title: 'label.settings',
+						custom: cloudStack.uiCustom.granularDetails({
+							dataProvider: function(args) {
+								$.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)
+										});
+									},
+
+									error: function(json) {
+										args.response.error(parseXMLHttpResponse(json));
+									}
+								});
+
+							},
+							actions: {
+								edit: function(args) {
+									var data = {
+										name: args.data.jsonObj.name,
+										value: args.data.value
+									};
+									var existingDetails;
+									$.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)
+											});
+										},
+
+										error: function(json) {
+											args.response.error(parseXMLHttpResponse(json));
+										}
+									});
+								},
+								remove: function(args) {
+									var existingDetails;
+									$.ajax({
+										url: createURL('listVirtualMachines&id=' + args.context.instances[0].id),
+										async:false,
+										success: function(json) {
+											var details = json.listvirtualmachinesresponse.virtualmachine[0].details;
+											existingDetails = details;
+										},
+
+										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));
+										}
+									});
+								},
+								add: function(args) {
+									var name = args.data.name;
+									var value = args.data.value;
+									
+									var details;
+									$.ajax({
+										url: createURL('listVirtualMachines&id=' + args.context.instances[0].id),
+										async:false,
+										success: function(json) {
+											var dets = json.listvirtualmachinesresponse.virtualmachine[0].details;
+											details = dets;
+										},
+
+										error: function(json) {
+											args.response.error(parseXMLHttpResponse(json));
+										}
+									});
+									
+									var detailsFormat = '';
+									for (key in details) {
+										detailsFormat += "details[0]." + key + "=" + details[key] + "&";
+									}
+									// Add new detail to the existing ones
+									detailsFormat += "details[0]." + name + "=" + value;
+									$.ajax({
+										url: createURL('updateVirtualMachine&id=' + args.context.instances[0].id + "&" + detailsFormat),
+										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));
+										}
+									});
+								}
+							}
+						})
+					}
                 }
             }
         }
     };
+	
+	var parseDetails = function(details) {
+		var listDetails = [];
+		for (detail in details){
+			var det = {};
+			det["name"] = detail;
+			det["value"] = details[detail];
+			listDetails.push(det);
+		}
+		return listDetails;
+	}
 
     var vmActionfilter = cloudStack.actionFilter.vmActionFilter = function(args) {
         var jsonObj = args.context.item;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/ui/scripts/templates.js
----------------------------------------------------------------------
diff --git a/ui/scripts/templates.js b/ui/scripts/templates.js
old mode 100644
new mode 100755
index 96ef43a..26f0fd1
--- a/ui/scripts/templates.js
+++ b/ui/scripts/templates.js
@@ -1780,8 +1780,125 @@
                                         }
                                     }}
                                 }
-                            }
-                        }
+                            },
+                            /**
+							 * Settings tab
+							 */
+							settings: {
+								title: 'label.settings',
+								custom: cloudStack.uiCustom.granularDetails({
+									dataProvider: function(args) {
+										$.ajax({
+											url: createURL('listTemplates'),
+											data: {
+												templatefilter: "self",
+												id: args.context.templates[0].id
+											},
+											success: function(json) {
+												var details = json.listtemplatesresponse.template[0].details;
+												var listDetails = [];
+												for (detail in details){
+													var det = {};
+													det["name"] = detail;
+													det["value"] = details[detail];
+													listDetails.push(det);
+												}
+												args.response.success({
+													data: listDetails
+												});
+											},
+
+											error: function(json) {
+												args.response.error(parseXMLHttpResponse(json));
+											}
+										});
+
+									},
+									actions: {
+										edit: function(args) {
+											var data = {
+												name: args.data.jsonObj.name,
+												value: args.data.value
+											};
+											var existingDetails = args.context.templates[0].details;
+											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('updateTemplate&id=' + args.context.templates[0].id + '&' + newDetails),
+												success: function(json) {
+													var template = json.updatetemplateresponse.template;
+													args.context.templates[0].details = template.details;
+													args.response.success({
+														data: template.details
+													});
+												},
+
+												error: function(json) {
+													args.response.error(parseXMLHttpResponse(json));
+												}
+											});
+										},
+										remove: function(args) {
+											var existingDetails = args.context.templates[0].details;
+											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('updateTemplate&id=' + args.context.templates[0].id + '&' + newDetails),
+												success: function(json) {
+													var template = json.updatetemplateresponse.template;
+													args.context.templates[0].details = template.details;
+													args.response.success({
+														data: template.details
+													});
+												},
+												error: function(json) {
+													args.response.error(parseXMLHttpResponse(json));
+												}
+											});
+										},
+										add: function(args) {
+											var name = args.data.name;
+											var value = args.data.value;
+											var details = args.context.templates[0].details;
+											var detailsFormat = '';
+											for (key in details) {
+												detailsFormat += "details[0]." + key + "=" + details[key] + "&";
+											}
+											// Add new detail to the existing ones
+											detailsFormat += "details[0]." + name + "=" + value;
+											$.ajax({
+												url: createURL('updateTemplate&id=' + args.context.templates[0].id + "&" + detailsFormat),
+												async: false,
+												success: function(json) {
+													var template = json.updatetemplateresponse.template;
+													args.context.templates[0].details = template.details;
+													args.response.success({
+														data: template.details
+													});
+												}
+											});
+										}
+									}
+								})
+							}
+						}
                     }
                 }
             },

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e8049af1/ui/scripts/ui-custom/granularSettings.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui-custom/granularSettings.js b/ui/scripts/ui-custom/granularSettings.js
index 5ab60b7..5312394 100644
--- a/ui/scripts/ui-custom/granularSettings.js
+++ b/ui/scripts/ui-custom/granularSettings.js
@@ -54,4 +54,88 @@
             return $listView;
         }
     };
-}(jQuery, cloudStack));
+	cloudStack.uiCustom.granularDetails = function(args) {
+        var dataProvider = args.dataProvider;
+        var actions = args.actions;
+
+        return function(args) {
+            var context = args.context;
+
+            var listView = {
+                id: 'details',
+                fields: {
+                    name: {
+                        label: 'label.name'
+                    },
+                    value: {
+                        label: 'label.value',
+                        editable: true
+                    }
+                },
+                actions: {
+                    edit: {
+                        label: 'label.change.value',
+                        action: actions.edit
+                    },
+					remove: {
+						label: 'Remove Setting',
+						messages: {
+							confirm: function(args) {
+								return 'Delete Setting';
+							},
+							notification: function(args) {
+								return 'Setting deleted';
+							}
+						},
+						action: actions.remove,
+						notification: {
+							poll: function(args) {
+								args.complete();
+							}
+						}
+					},
+					add : {
+						label: 'Add Setting',
+						messages: {
+							confirm: function(args) {
+								return 'Add Setting';
+							},
+							notification: function(args) {
+								return 'Setting added';
+							}
+						},
+						preFilter: function(args) {
+							return true;
+						},
+						createForm: {
+							title: 'Add New Setting',
+							fields: {
+								name: {
+									label: 'label.name',
+									validation: {
+										required: true
+									}
+								},
+								value: {
+									label: 'label.value',
+									validation: {
+										required: true
+									}
+								}
+							}
+						},
+						action: actions.add
+					}
+                },
+                dataProvider: dataProvider
+            };
+
+            var $listView = $('<div>').listView({
+                context: context,
+                listView: listView
+            });
+
+            return $listView;
+        }
+    };
+}(jQuery, cloudStack));
\ No newline at end of file


[2/2] git commit: updated refs/heads/master to 202b92f

Posted by ra...@apache.org.
Merge pull request #1767 from nvazquez/userVmAndTemplatesDetails

CLOUDSTACK-9457: Allow retrieval and modification of VM and template details via API and UIJIRA TICKET: https://issues.apache.org/jira/browse/CLOUDSTACK-9457

### Goal
This PR proposes list/add/update/delete user vm and vm template details via API and UI.

### VM UI Screenshots
Setting tab is added on Instances page. Actions allowed are: Add/Edit/Remove
![](https://issues.apache.org/jira/secure/attachment/12844858/VMDetails1.JPG "Screenshot 1 - VM Details")

Settings tab is only shown if instance is Stopped:
![](https://issues.apache.org/jira/secure/attachment/12844859/VMDetailsRunning.JPG "Screenshot 2 - VM Details Hidden Running VM")
![](https://issues.apache.org/jira/secure/attachment/12844860/VMDetailsStopped.JPG "Screenshot 3 - VM Details Stopped VM")

### Templates UI Screenshots
Setting tab is added on Templates page. Actions allowed are: Add/Edit/Remove:
![](https://issues.apache.org/jira/secure/attachment/12844857/TemplateDetails1.JPG "Screenshot 4 - Template Details")

* pr/1767:
  CLOUDSTACK-9457: Allow retrieval and modification of VM and template details via API and UI

Signed-off-by: Rajani Karuturi <ra...@accelerite.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/202b92f2
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/202b92f2
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/202b92f2

Branch: refs/heads/master
Commit: 202b92f2437ea0ebd575998bf7adc7d54aeb762e
Parents: 30aef28 e8049af
Author: Rajani Karuturi <ra...@accelerite.com>
Authored: Wed Feb 8 12:12:36 2017 +0530
Committer: Rajani Karuturi <ra...@accelerite.com>
Committed: Wed Feb 8 12:12:37 2017 +0530

----------------------------------------------------------------------
 .../org/apache/cloudstack/api/ApiConstants.java |   1 +
 .../api/BaseUpdateTemplateOrIsoCmd.java         |  12 +-
 .../api/command/user/vm/UpdateVMCmd.java        |   9 +
 .../com/cloud/storage/VMTemplateDetailVO.java   |  16 ++
 .../schema/src/com/cloud/vm/UserVmDetailVO.java |   7 +
 .../cloud/api/query/dao/UserVmJoinDaoImpl.java  |  11 +-
 .../com/cloud/template/TemplateManagerImpl.java |  13 +-
 server/src/com/cloud/vm/UserVmManagerImpl.java  |  14 +-
 .../cloud/template/TemplateManagerImplTest.java |   9 +
 ui/scripts/instances.js                         | 167 ++++++++++++++++++-
 ui/scripts/templates.js                         | 121 +++++++++++++-
 ui/scripts/ui-custom/granularSettings.js        |  86 +++++++++-
 12 files changed, 445 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/202b92f2/api/src/org/apache/cloudstack/api/ApiConstants.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/202b92f2/server/src/com/cloud/vm/UserVmManagerImpl.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/202b92f2/ui/scripts/instances.js
----------------------------------------------------------------------