This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.20 by this push:
     new c5afee21018 UI improvements  (#9773)
c5afee21018 is described below

commit c5afee210181db2335f059267c7a2b74f91002d4
Author: Abhisar Sinha <63767682+abh1...@users.noreply.github.com>
AuthorDate: Thu Feb 6 11:18:40 2025 +0530

    UI improvements  (#9773)
    
    * Show Usage Server configuration in a separate pane
    
    * UI: Option to attach volume to an instance during create volume
    
    * Show service ip in management server details tab
    
    * change Schedule Snapshots to Recurring Snapshots
    
    * Change the hypervisor order so that kvm, vmware, xenserver show up first
    
    * Remove extra space in hypervisor names in config.java
    
    * Fix `updateTemplatePermission` when the UI is set to a language other 
than English (#9766)
    
    * Fix updateTemplatePermission UI in non-english language
    
    * Improve fix
    
    ---------
    
    Co-authored-by: Lucas Martins <lucas.mart...@scclouds.com.br>
    
    * Autofill vcenter details in add cluster form
    
    * UI: condition to display create vm-vol-snapshots to same as create 
vol-snapshots
    
    * Fix alignment on wrapping in global settings tabs
    
    * rename Autofill vCenter credentials to Autofill vCenter credentials from 
Zone
    
    * Rename Service Ip to Ip Address in management server response
    
    * Change description of kvm.snapshot.enabled to say that it applies to 
volume snapshots
    
    * Return error when kvm vm snapshot is taken withoutsnapshot memory
    
    * Minor naming changes and grammar
    
    * Fix tooltip for attach volume to instance button
    
    * Show Usage Server configuration in a separate pane
    
    * UI: Option to attach volume to an instance during create volume
    
    * Show service ip in management server details tab
    
    * change Schedule Snapshots to Recurring Snapshots
    
    * Change the hypervisor order so that kvm, vmware, xenserver show up first
    
    * Remove extra space in hypervisor names in config.java
    
    * Autofill vcenter details in add cluster form
    
    * UI: condition to display create vm-vol-snapshots to same as create 
vol-snapshots
    
    * Fix alignment on wrapping in global settings tabs
    
    * rename Autofill vCenter credentials to Autofill vCenter credentials from 
Zone
    
    * Rename Service Ip to Ip Address in management server response
    
    * Change description of kvm.snapshot.enabled to say that it applies to 
volume snapshots
    
    * Return error when kvm vm snapshot is taken withoutsnapshot memory
    
    * Minor naming changes and grammar
    
    * Fix tooltip for attach volume to instance button
    
    * Show Usage Server configuration in a separate pane
    
    * UI: Option to attach volume to an instance during create volume
    
    * Show service ip in management server details tab
    
    * change Schedule Snapshots to Recurring Snapshots
    
    * Change the hypervisor order so that kvm, vmware, xenserver show up first
    
    * Remove extra space in hypervisor names in config.java
    
    * Autofill vcenter details in add cluster form
    
    * UI: condition to display create vm-vol-snapshots to same as create 
vol-snapshots
    
    * Fix alignment on wrapping in global settings tabs
    
    * rename Autofill vCenter credentials to Autofill vCenter credentials from 
Zone
    
    * Rename Service Ip to Ip Address in management server response
    
    * Change description of kvm.snapshot.enabled to say that it applies to 
volume snapshots
    
    * Return error when kvm vm snapshot is taken withoutsnapshot memory
    
    * Minor naming changes and grammar
    
    * Fix tooltip for attach volume to instance button
    
    * UI: Option to attach volume to an instance during create volume
    
    * UI: condition to display create vm-vol-snapshots to same as create 
vol-snapshots
    
    * moved db changes from 41900to42000 to 42000to42010
    
    * Update group_id in already present usage configuration settings
    
    * remove "schedule" from message in create Recurring Snapshots form
    
    * Update 
server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java
    
    ---------
    
    Co-authored-by: Daan Hoogland <d...@onecht.net>
    Co-authored-by: Lucas Martins 
<56271185+lucas-a-mart...@users.noreply.github.com>
    Co-authored-by: Lucas Martins <lucas.mart...@scclouds.com.br>
    Co-authored-by: Boris Stoyanov - a.k.a Bobby <bss.stoya...@gmail.com>
    Co-authored-by: Andrija Panic 
<45762285+andrijapani...@users.noreply.github.com>
---
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 -
 .../api/response/ManagementServerResponse.java     |  12 +--
 .../resources/META-INF/db/schema-42000to42010.sql  |   8 ++
 .../java/com/cloud/api/query/QueryManagerImpl.java |   2 +-
 .../main/java/com/cloud/configuration/Config.java  |   6 +-
 .../cloud/network/vpn/Site2SiteVpnManagerImpl.java |   4 +-
 .../cloud/vm/snapshot/VMSnapshotManagerImpl.java   |  10 ++-
 ui/public/locales/de_DE.json                       |   1 -
 ui/public/locales/el_GR.json                       |   1 -
 ui/public/locales/en.json                          |   6 +-
 ui/public/locales/ja_JP.json                       |   1 -
 ui/public/locales/ko_KR.json                       |   1 -
 ui/public/locales/pt_BR.json                       |   1 -
 ui/public/locales/zh_CN.json                       |   1 -
 ui/src/config/section/compute.js                   |   7 +-
 ui/src/config/section/infra/managementServers.js   |   4 +-
 ui/src/config/section/storage.js                   |  14 +--
 ui/src/views/infra/ClusterAdd.vue                  | 100 +++++++++++++--------
 ui/src/views/setting/ConfigurationHierarchy.vue    |   9 +-
 ui/src/views/storage/CreateVolume.vue              | 100 ++++++++++++++++++++-
 ui/src/views/storage/RecurringSnapshotVolume.vue   |   2 +-
 21 files changed, 213 insertions(+), 78 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java 
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index cf03f1d2699..efbf9d9d78e 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -447,7 +447,6 @@ public class ApiConstants {
     public static final String SENT = "sent";
     public static final String SENT_BYTES = "sentbytes";
     public static final String SERIAL = "serial";
-    public static final String SERVICE_IP = "serviceip";
     public static final String SERVICE_OFFERING_ID = "serviceofferingid";
     public static final String SESSIONKEY = "sessionkey";
     public static final String SHOW_CAPACITIES = "showcapacities";
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java
 
b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java
index fc7d3b722ab..4165ea25778 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java
@@ -74,9 +74,9 @@ public class ManagementServerResponse extends BaseResponse {
     @Param(description = "the running OS kernel version for this Management 
Server")
     private String kernelVersion;
 
-    @SerializedName(ApiConstants.SERVICE_IP)
+    @SerializedName(ApiConstants.IP_ADDRESS)
     @Param(description = "the IP Address for this Management Server")
-    private String serviceIp;
+    private String ipAddress;
 
     @SerializedName(ApiConstants.PEERS)
     @Param(description = "the Management Server Peers")
@@ -122,8 +122,8 @@ public class ManagementServerResponse extends BaseResponse {
         return lastBoot;
     }
 
-    public String getServiceIp() {
-        return serviceIp;
+    public String getIpAddress() {
+        return ipAddress;
     }
 
     public void setId(String id) {
@@ -170,8 +170,8 @@ public class ManagementServerResponse extends BaseResponse {
         this.kernelVersion = kernelVersion;
     }
 
-    public void setServiceIp(String serviceIp) {
-        this.serviceIp = serviceIp;
+    public void setIpAddress(String ipAddress) {
+        this.ipAddress = ipAddress;
     }
 
     public String getKernelVersion() {
diff --git 
a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql 
b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
index 92e0dbb5b2a..bf13e5eee1a 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
@@ -24,6 +24,14 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user', 
'api_key_access', 'boolean DE
 CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.account', 'api_key_access', 
'boolean DEFAULT NULL COMMENT "is api key access allowed for the account" ');
 CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.account', 
'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for 
the account" ');
 
+-- Create a new group for Usage Server related configurations
+INSERT INTO `cloud`.`configuration_group` (`name`, `description`, 
`precedence`) VALUES ('Usage Server', 'Usage Server related configuration', 9);
+UPDATE `cloud`.`configuration_subgroup` set `group_id` = (SELECT `id` FROM 
`cloud`.`configuration_group` WHERE `name` = 'Usage Server'), `precedence` = 1 
WHERE `name`='Usage';
+UPDATE `cloud`.`configuration` SET `group_id` = (SELECT `id` FROM 
`cloud`.`configuration_group` WHERE `name` = 'Usage Server') where 
`subgroup_id` = (SELECT `id` FROM `cloud`.`configuration_subgroup` WHERE `name` 
= 'Usage');
+
+-- Update the description to indicate this setting applies only to volume 
snapshots on running instances
+UPDATE `cloud`.`configuration` SET `description`='whether volume snapshot is 
enabled on running instances on KVM hosts' WHERE `name`='kvm.snapshot.enabled';
+
 -- Modify index for mshost_peer
 DELETE FROM `cloud`.`mshost_peer`;
 CALL 
`cloud`.`IDEMPOTENT_DROP_FOREIGN_KEY`('cloud.mshost_peer','fk_mshost_peer__owner_mshost');
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 94a87a0b31c..40249dda17a 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -5413,13 +5413,13 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         mgmtResponse.setLastServerStart(mgmt.getLastJvmStart());
         mgmtResponse.setLastServerStop(mgmt.getLastJvmStop());
         mgmtResponse.setLastBoot(mgmt.getLastSystemBoot());
-        mgmtResponse.setServiceIp(mgmt.getServiceIP());
         if (listPeers) {
             List<ManagementServerHostPeerJoinVO> peers = 
mshostPeerJoinDao.listByOwnerMshostId(mgmt.getId());
             for (ManagementServerHostPeerJoinVO peer: peers) {
                 
mgmtResponse.addPeer(createPeerManagementServerNodeResponse(peer));
             }
         }
+        mgmtResponse.setIpAddress(mgmt.getServiceIP());
         mgmtResponse.setObjectName("managementserver");
         return mgmtResponse;
     }
diff --git a/server/src/main/java/com/cloud/configuration/Config.java 
b/server/src/main/java/com/cloud/configuration/Config.java
index b9de906ba46..8093af6afc1 100644
--- a/server/src/main/java/com/cloud/configuration/Config.java
+++ b/server/src/main/java/com/cloud/configuration/Config.java
@@ -506,7 +506,7 @@ public enum Config {
             "The time interval in seconds when the management server polls for 
snapshots to be scheduled.",
             null),
     SnapshotDeltaMax("Snapshots", SnapshotManager.class, Integer.class, 
"snapshot.delta.max", "16", "max delta snapshots between two full snapshots.", 
null),
-    KVMSnapshotEnabled("Hidden", SnapshotManager.class, Boolean.class, 
"kvm.snapshot.enabled", "false", "whether snapshot is enabled for KVM hosts", 
null),
+    KVMSnapshotEnabled("Hidden", SnapshotManager.class, Boolean.class, 
"kvm.snapshot.enabled", "false", "Whether volume snapshot is enabled on running 
instances on a KVM host", null),
 
     // Advanced
     EventPurgeInterval(
@@ -665,8 +665,8 @@ public enum Config {
             ManagementServer.class,
             String.class,
             "hypervisor.list",
-            HypervisorType.Hyperv + "," + HypervisorType.KVM + "," + 
HypervisorType.XenServer + "," + HypervisorType.VMware + "," + 
HypervisorType.BareMetal + "," +
-                    HypervisorType.Ovm + "," + HypervisorType.LXC + "," + 
HypervisorType.Ovm3,
+            HypervisorType.KVM + "," + HypervisorType.VMware + "," + 
HypervisorType.XenServer + "," + HypervisorType.Hyperv + "," +
+                    HypervisorType.BareMetal + "," + HypervisorType.Ovm + "," 
+ HypervisorType.LXC + "," + HypervisorType.Ovm3,
                     "The list of hypervisors that this deployment will use.",
             "hypervisorList",
             ConfigKey.Kind.CSV,
diff --git 
a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java 
b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java
index ed83e396abf..8be97644bf5 100644
--- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java
@@ -343,7 +343,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase 
implements Site2SiteVpn
     private void validatePrerequisiteVpnGateway(Site2SiteVpnGateway 
vpnGateway) {
         // check if gateway has been defined on the VPC
         if (_vpnGatewayDao.findByVpcId(vpnGateway.getVpcId()) == null) {
-            throw new InvalidParameterValueException("we can not create a VPN 
connection for a VPC that does not have a VPN gateway defined");
+            throw new InvalidParameterValueException("We can not create a VPN 
connection for a VPC that does not have a VPN gateway defined");
         }
     }
 
@@ -590,7 +590,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase 
implements Site2SiteVpn
     private void stopVpnConnection(Long id) throws 
ResourceUnavailableException {
         Site2SiteVpnConnectionVO conn = 
_vpnConnectionDao.acquireInLockTable(id);
         if (conn == null) {
-            throw new CloudRuntimeException("Unable to acquire lock for 
stopping of VPN connection with ID " + id);
+            throw new CloudRuntimeException("Unable to acquire lock for 
stopping VPN connection with ID " + id);
         }
         try {
             if (conn.getState() == State.Pending) {
diff --git 
a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java 
b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java
index 8d43875190f..907182edc2a 100644
--- a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.storage.snapshot.SnapshotManager;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
@@ -377,9 +378,14 @@ public class VMSnapshotManagerImpl extends 
MutualExclusiveIdsManagerBase impleme
             //StorageVMSnapshotStrategy - allows volume snapshots without 
memory; VM has to be in Running state; No limitation of the image format if the 
storage plugin supports volume snapshots; "kvm.vmstoragesnapshot.enabled" has 
to be enabled
             //Other Storage volume plugins could integrate this with their own 
functionality for group snapshots
             VMSnapshotStrategy snapshotStrategy = 
storageStrategyFactory.getVmSnapshotStrategy(userVmVo.getId(), 
rootVolumePool.getId(), snapshotMemory);
-
             if (snapshotStrategy == null) {
-                String message = "KVM does not support the type of snapshot 
requested";
+                String message;
+                if (!SnapshotManager.VmStorageSnapshotKvm.value() && 
!snapshotMemory) {
+                    message = "Creating a snapshot of a running KVM instance 
without memory is not supported";
+                } else {
+                    message = "KVM does not support the type of snapshot 
requested";
+                }
+
                 logger.debug(message);
                 throw new CloudRuntimeException(message);
             }
diff --git a/ui/public/locales/de_DE.json b/ui/public/locales/de_DE.json
index d9adc43469e..abf471ae17d 100644
--- a/ui/public/locales/de_DE.json
+++ b/ui/public/locales/de_DE.json
@@ -1265,7 +1265,6 @@
 "label.save.new.rule": "Neue Regel Speichern",
 "label.schedule": "Zeitplan",
 "label.scheduled.backups": "geplante Backups",
-"label.scheduled.snapshots": "geplante Schnappschüsse",
 "label.scope": "Geltungsbereich",
 "label.search": "Suche",
 "label.secondary.isolated.vlan.type.isolated": "Isoliert",
diff --git a/ui/public/locales/el_GR.json b/ui/public/locales/el_GR.json
index 754cde89542..7b7f4b29e12 100644
--- a/ui/public/locales/el_GR.json
+++ b/ui/public/locales/el_GR.json
@@ -1517,7 +1517,6 @@
 "label.scale.vm": "Κλίμακα εικονικής μηχανής",
 "label.schedule": "Πρόγραμμα",
 "label.scheduled.backups": "Προγραμματισμένα αντίγραφα ασφαλείας",
-"label.scheduled.snapshots": "Προγραμματισμένα στιγμιότυπα",
 "label.scope": "Πεδίο εφαρμογής",
 "label.search": "Αναζήτηση",
 "label.secondary.isolated.vlan.type.isolated": "Απομονωμένες",
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index a2d82d477aa..29c917b5174 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -54,6 +54,7 @@
 "label.action": "Action",
 "label.action.attach.disk": "Attach disk",
 "label.action.attach.iso": "Attach ISO",
+"label.action.attach.to.instance": "Attach to Instance",
 "label.action.bulk.delete.egress.firewall.rules": "Bulk delete egress firewall 
rules",
 "label.action.bulk.delete.firewall.rules": "Bulk delete firewall rules",
 "label.action.bulk.delete.ip.v6.firewall.rules": "Bulk remove IPv6 firewall 
rules",
@@ -399,9 +400,11 @@
 "label.associatednetworkid": "Associated Network ID",
 "label.associatednetworkname": "Network name",
 "label.asyncbackup": "Async backup",
+"label.attach.vol.to.instance": "Attach the created Volume to an existing 
Instance",
 "label.attaching": "Attaching",
 "label.authentication.method": "Authentication Method",
 "label.authentication.sshkey": "System SSH Key",
+"label.use.existing.vcenter.credentials.from.zone": "Use existing vCenter 
credentials from the Zone",
 "label.autoscale": "AutoScale",
 "label.autoscalevmgroupname": "AutoScaling Group",
 "label.author.email": "Author e-mail",
@@ -1073,7 +1076,7 @@
 "label.hastate": "HA state",
 "label.headers": "Headers",
 "label.header.backup.schedule": "You can set up recurring backup schedules by 
selecting from the available options below and applying your policy 
preference.",
-"label.header.volume.snapshot": "You can set up recurring Snapshot schedules 
by selecting from the available options below and applying your policy 
preference.",
+"label.header.volume.snapshot": "You can set up recurring Snapshots by 
selecting from the available options below and applying your policy 
preference.",
 "label.header.volume.take.snapshot": "Please confirm that you want to take a 
Snapshot of this volume.",
 "label.health.check": "Health check",
 "label.heapmemoryused": "Heap-memory used",
@@ -1999,7 +2002,6 @@
 "label.schedule": "Schedule",
 "label.schedule.add": "Add schedule",
 "label.scheduled.backups": "Scheduled backups",
-"label.scheduled.snapshots": "Scheduled Snapshots",
 "label.schedules": "Schedules",
 "label.scope": "Scope",
 "label.scope.tooltip": "Primary Storage Pool Scope",
diff --git a/ui/public/locales/ja_JP.json b/ui/public/locales/ja_JP.json
index f1518286f80..53009781500 100644
--- a/ui/public/locales/ja_JP.json
+++ b/ui/public/locales/ja_JP.json
@@ -1969,7 +1969,6 @@
   "label.scaleup.policy": "スケールアップポリシー",
   "label.schedule": "スケジュール",
   "label.scheduled.backups": "スケジュールされたバックアップ",
-  "label.scheduled.snapshots": "スケジュールされたスナップショット",
   "label.scope": "スコープ",
   "label.search": "検索",
   "label.secondary.isolated.vlan.type.isolated": "隔離",
diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json
index a34cdea767f..153bfd64cef 100644
--- a/ui/public/locales/ko_KR.json
+++ b/ui/public/locales/ko_KR.json
@@ -1322,7 +1322,6 @@
 "label.scale.vm": "VM \ud655\uc7a5",
 "label.schedule": "\uc2a4\ucf00\uc904",
 "label.scheduled.backups": "\uc608\uc57d\ub41c \ubc31\uc5c5",
-"label.scheduled.snapshots": "\uc608\uc57d\ub41c \uc2a4\ub0c5\uc0f7",
 "label.scope": "\ubc94\uc704",
 "label.search": "\uac80\uc0c9",
 "label.secondary.isolated.vlan.type.isolated": "isolated",
diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json
index a9c67baa32d..af4d5209017 100644
--- a/ui/public/locales/pt_BR.json
+++ b/ui/public/locales/pt_BR.json
@@ -1428,7 +1428,6 @@
 "label.scale.vm": "Escalar VM",
 "label.schedule": "Programar",
 "label.scheduled.backups": "Backups programados",
-"label.scheduled.snapshots": "Snapshots programados",
 "label.scope": "Escopo",
 "label.search": "Pesquisar",
 "label.secondary.isolated.vlan.type.isolated": "Isolada",
diff --git a/ui/public/locales/zh_CN.json b/ui/public/locales/zh_CN.json
index 91353d2f81a..5337829b53d 100644
--- a/ui/public/locales/zh_CN.json
+++ b/ui/public/locales/zh_CN.json
@@ -2248,7 +2248,6 @@
 
   "label.schedule": "\u65E5\u7A0B",
   "label.scheduled.backups": "\u5B9A\u65F6\u5907\u4EFD",
-  "label.scheduled.snapshots": "\u8BA1\u5212\u5FEB\u7167",
   "label.scope": "\u8303\u56F4",
   "label.search": "\u641C\u7D22",
 
diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js
index 5737126d463..5de6d78d263 100644
--- a/ui/src/config/section/compute.js
+++ b/ui/src/config/section/compute.js
@@ -207,9 +207,10 @@ export default {
           docHelp: 
'adminguide/virtual_machines.html#virtual-machine-snapshots',
           dataView: true,
           popup: true,
-          show: (record) => {
-            return ((['Running'].includes(record.state) && record.hypervisor 
!== 'LXC') ||
-              (['Stopped'].includes(record.state) && !['KVM', 
'LXC'].includes(record.hypervisor)))
+          show: (record, store) => {
+            return (record.hypervisor !== 'KVM') ||
+              ['Stopped', 'Destroyed'].includes(record.state) ||
+              store.features.kvmsnapshotenabled
           },
           disabled: (record) => { return record.hostcontrolstate === 'Offline' 
&& record.hypervisor === 'KVM' },
           component: shallowRef(defineAsyncComponent(() => 
import('@/views/compute/CreateSnapshotWizard.vue')))
diff --git a/ui/src/config/section/infra/managementServers.js 
b/ui/src/config/section/infra/managementServers.js
index a8bf1be1a01..bc7b42d9cf0 100644
--- a/ui/src/config/section/infra/managementServers.js
+++ b/ui/src/config/section/infra/managementServers.js
@@ -26,14 +26,14 @@ export default {
   permission: ['listManagementServersMetrics'],
   resourceType: 'ManagementServer',
   columns: () => {
-    const fields = ['name', 'state', 'serviceip', 'version', 'osdistribution', 
'agentcount']
+    const fields = ['name', 'state', 'ipaddress', 'version', 'osdistribution', 
'agentcount']
     const metricsFields = ['collectiontime', 'availableprocessors', 'cpuload', 
'heapmemoryused']
     if (store.getters.metrics) {
       fields.push(...metricsFields)
     }
     return fields
   },
-  details: ['collectiontime', 'usageislocal', 'dbislocal', 'lastserverstart', 
'lastserverstop', 'lastboottime', 'version', 'loginfo', 'systemtotalcpucycles', 
'systemloadaverages', 'systemcycleusage', 'systemmemorytotal', 
'systemmemoryfree', 'systemmemoryvirtualsize', 'availableprocessors', 
'javadistribution', 'javaversion', 'osdistribution', 'kernelversion', 
'agentcount', 'sessions', 'heapmemoryused', 'heapmemorytotal', 
'threadsblockedcount', 'threadsdeamoncount', 'threadsnewcount', 'thr [...]
+  details: ['ipaddress', 'collectiontime', 'usageislocal', 'dbislocal', 
'lastserverstart', 'lastserverstop', 'lastboottime', 'version', 'loginfo', 
'systemtotalcpucycles', 'systemloadaverages', 'systemcycleusage', 
'systemmemorytotal', 'systemmemoryfree', 'systemmemoryvirtualsize', 
'availableprocessors', 'javadistribution', 'javaversion', 'osdistribution', 
'kernelversion', 'agentcount', 'sessions', 'heapmemoryused', 'heapmemorytotal', 
'threadsblockedcount', 'threadsdeamoncount', 'threadsne [...]
   tabs: [
     {
       name: 'details',
diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js
index e0fa72dff8a..3ab890d530a 100644
--- a/ui/src/config/section/storage.js
+++ b/ui/src/config/section/storage.js
@@ -165,9 +165,10 @@ export default {
           label: 'label.action.take.snapshot',
           dataView: true,
           show: (record, store) => {
-            return record.state === 'Ready' && (record.hypervisor !== 'KVM' ||
-                record.hypervisor === 'KVM' && record.vmstate === 'Running' && 
store.features.kvmsnapshotenabled ||
-                record.hypervisor === 'KVM' && record.vmstate !== 'Running')
+            return record.state === 'Ready' &&
+              (record.hypervisor !== 'KVM' ||
+               ['Stopped', 'Destroyed'].includes(record.vmstate) ||
+               store.features.kvmsnapshotenabled)
           },
           popup: true,
           component: shallowRef(defineAsyncComponent(() => 
import('@/views/storage/TakeSnapshot.vue')))
@@ -179,9 +180,10 @@ export default {
           label: 'label.action.recurring.snapshot',
           dataView: true,
           show: (record, store) => {
-            return record.state === 'Ready' && (record.hypervisor !== 'KVM' ||
-                record.hypervisor === 'KVM' && record.vmstate === 'Running' && 
store.features.kvmsnapshotenabled ||
-                record.hypervisor === 'KVM' && record.vmstate !== 'Running')
+            return record.state === 'Ready' &&
+              (record.hypervisor !== 'KVM' ||
+               (['Stopped', 'Destroyed'].includes(record.vmstate)) ||
+               (store.features.kvmsnapshotenabled))
           },
           popup: true,
           component: shallowRef(defineAsyncComponent(() => 
import('@/views/storage/RecurringSnapshotVolume.vue'))),
diff --git a/ui/src/views/infra/ClusterAdd.vue 
b/ui/src/views/infra/ClusterAdd.vue
index 924722d59bb..e4f5d67a3aa 100644
--- a/ui/src/views/infra/ClusterAdd.vue
+++ b/ui/src/views/infra/ClusterAdd.vue
@@ -47,7 +47,7 @@
         <div class="form__label">{{ $t('label.hypervisor') }}</div>
         <a-select
           v-model:value="hypervisor"
-          @change="resetAllFields"
+          @change="hypervisor => onChangeHypervisor(hypervisor)"
           showSearch
           optionFilterProp="value"
           :filterOption="(input, option) => {
@@ -109,19 +109,28 @@
         </div>
 
         <div class="form__item">
-          <div class="form__label">{{ $t('label.vcenterusername') }}</div>
-          <a-input v-model:value="username"></a-input>
+          <div class="form__label">{{ $t('label.vcenterdatacenter') }}</div>
+          <a-input v-model:value="dataCenter"></a-input>
         </div>
 
-        <div class="form__item">
-          <div class="form__label">{{ $t('label.vcenterpassword') }}</div>
-          <a-input type="password" v-model:value="password"></a-input>
+        <div class="form__item" name="useDefaultVMwareCred">
+          <div class="form__label">{{ 
$t('label.use.existing.vcenter.credentials.from.zone') }}</div>
+          <a-switch v-model="useDefaultVMwareCred" 
:checked="useDefaultVMwareCred" @change="onChangeUseDefaultVMwareCred()" />
         </div>
 
-        <div class="form__item">
-          <div class="form__label">{{ $t('label.vcenterdatacenter') }}</div>
-          <a-input v-model:value="dataCenter"></a-input>
-        </div>
+        <template v-if="useDefaultVMwareCred === false">
+          <div class="form__item">
+            <div class="form__label"><span class="required">* </span>{{ 
$t('label.vcenterusername') }}</div>
+            <span class="required required-label" ref="requiredUsername">{{ 
$t('label.required') }}</span>
+            <a-input v-model:value="username"></a-input>
+          </div>
+
+          <div class="form__item">
+            <div class="form__label"><span class="required">* </span>{{ 
$t('label.vcenterpassword') }}</div>
+            <span class="required required-label" ref="requiredPassword">{{ 
$t('label.required') }}</span>
+            <a-input type="password" v-model:value="password"></a-input>
+          </div>
+        </template>
       </template>
 
       <div class="form__item">
@@ -176,6 +185,7 @@ export default {
       username: null,
       password: null,
       url: null,
+      useDefaultVMwareCred: true,
       host: null,
       dataCenter: null,
       ovm3pool: null,
@@ -257,6 +267,27 @@ export default {
         this.loading = false
       })
     },
+    fetchVMwareCred () {
+      this.loading = true
+      this.clustertype = 'ExternalManaged'
+      api('listVmwareDcs', {
+        zoneid: this.zoneId
+      }).then(response => {
+        var vmwaredcs = response.listvmwaredcsresponse.VMwareDC
+        if (vmwaredcs !== null) {
+          this.host = vmwaredcs[0].vcenter
+          this.dataCenter = vmwaredcs[0].name
+        }
+      }).catch(error => {
+        this.$notification.error({
+          message: `${this.$t('label.error')} ${error.response.status}`,
+          description: error.response.data.listvmwaredcsresponse.errortext,
+          duration: 0
+        })
+      }).finally(() => {
+        this.loading = false
+      })
+    },
     toggleDedicated () {
       this.dedicatedDomainId = null
       this.dedicatedAccount = null
@@ -270,35 +301,24 @@ export default {
       }
       this.$refs.requiredCluster.classList.remove('required-label--visible')
 
+      if (this.hypervisor === 'VMware' && this.useDefaultVMwareCred === false) 
{
+        if (!this.username) {
+          this.$refs.requiredUsername.classList.add('required-label--visible')
+          return
+        }
+        if (!this.password) {
+          this.$refs.requiredPassword.classList.add('required-label--visible')
+          return
+        }
+        this.$refs.requiredUsername.classList.remove('required-label--visible')
+        this.$refs.requiredPassword.classList.remove('required-label--visible')
+      }
+
       if (this.hypervisor === 'Ovm3') {
         this.ovm3pool = 'on'
         this.ovm3cluster = 'undefined'
         this.ovm3vip = ''
       }
-
-      if (this.hypervisor === 'VMware') {
-        this.clustertype = 'ExternalManaged'
-        if ((this.host === null || this.host.length === 0) &&
-          (this.dataCenter === null || this.dataCenter.length === 0)) {
-          api('listVmwareDcs', {
-            zoneid: this.zoneId
-          }).then(response => {
-            var vmwaredcs = response.listvmwaredcsresponse.VMwareDC
-            if (vmwaredcs !== null) {
-              this.host = vmwaredcs[0].vcenter
-              this.dataCenter = vmwaredcs[0].name
-            }
-            this.addCluster()
-          }).catch(error => {
-            this.$notification.error({
-              message: `${this.$t('label.error')} ${error.response.status}`,
-              description: error.response.data.listvmwaredcsresponse.errortext,
-              duration: 0
-            })
-          })
-          return
-        }
-      }
       this.addCluster()
     },
     addCluster () {
@@ -387,7 +407,7 @@ export default {
         this.loading = false
       })
     },
-    resetAllFields () {
+    onChangeHypervisor (hypervisor) {
       this.clustertype = 'CloudManaged'
       this.username = null
       this.password = null
@@ -397,6 +417,16 @@ export default {
       this.ovm3pool = null
       this.ovm3cluster = null
       this.ovm3vip = null
+      if (hypervisor === 'VMware') {
+        this.fetchVMwareCred()
+      }
+    },
+    onChangeUseDefaultVMwareCred () {
+      this.useDefaultVMwareCred = !this.useDefaultVMwareCred
+      if (this.useDefaultVMwareCred) {
+        this.username = null
+        this.password = null
+      }
     },
     returnPlaceholder (field) {
       this.params.find(i => {
diff --git a/ui/src/views/setting/ConfigurationHierarchy.vue 
b/ui/src/views/setting/ConfigurationHierarchy.vue
index 45060efa594..80b464e657c 100644
--- a/ui/src/views/setting/ConfigurationHierarchy.vue
+++ b/ui/src/views/setting/ConfigurationHierarchy.vue
@@ -28,11 +28,10 @@
 
     <template #bodyCell="{ column, record }">
       <template v-if="column.key === 'name'">
-    <span :style="hierarchyExists ? 'padding-left: 0px;' : 'padding-left: 
25px;'">
-      <b><span v-if="record.parent">└─ &nbsp;</span>{{record.displaytext }} 
</b> {{ ' (' + record.name + ')' }}
-    </span>
-    <br/>
-    <span :style="record.parent ? 'padding-left: 50px; display:block' : 
'padding-left: 25px; display:block'">{{ record.description }}</span>
+        <a-row :style="hierarchyExists ? 'padding-left: 0px;' : 'padding-left: 
25px;'">
+          <b><span v-if="record.parent">└─ &nbsp;</span>{{record.displaytext 
}}&nbsp</b> {{ '(' + record.name + ')' }}
+        </a-row>
+        <span :style="record.parent ? 'padding-left: 50px; display:block' : 
'padding-left: 25px; display:block'">{{ record.description }}</span>
       </template>
       <template v-if="column.key === 'value'">
         <ConfigurationValue :configrecord="record" />
diff --git a/ui/src/views/storage/CreateVolume.vue 
b/ui/src/views/storage/CreateVolume.vue
index 0b667a1f51f..82fcce5c974 100644
--- a/ui/src/views/storage/CreateVolume.vue
+++ b/ui/src/views/storage/CreateVolume.vue
@@ -116,6 +116,48 @@
             :placeholder="apiParams.maxiops.description"/>
         </a-form-item>
       </span>
+      <a-form-item name="attachVolume" ref="attachVolume" 
v-if="!createVolumeFromVM">
+        <template #label>
+          <tooltip-label :title="$t('label.action.attach.to.instance')" 
:tooltip="$t('label.attach.vol.to.instance')" />
+        </template>
+        <a-switch v-model:checked="form.attachVolume" :checked="attachVolume" 
@change="zone => onChangeAttachToVM(zone.id)" />
+      </a-form-item>
+      <span v-if="attachVolume">
+        <a-form-item :label="$t('label.virtualmachineid')" 
name="virtualmachineid" ref="virtualmachineid">
+          <a-select
+            v-focus="true"
+            v-model:value="form.virtualmachineid"
+            :placeholder="attachVolumeApiParams.virtualmachineid.description"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) 
>= 0
+            }" >
+            <a-select-option v-for="vm in virtualmachines" :key="vm.id" 
:label="vm.name || vm.displayname">
+              {{ vm.name || vm.displayname }}
+            </a-select-option>
+          </a-select>
+        </a-form-item >
+        <a-form-item :label="$t('label.deviceid')">
+          <div style="margin-bottom: 10px">
+            <a-collapse>
+              <a-collapse-panel header="More information about deviceID">
+                <a-alert type="warning">
+                  <template #message>
+                    <span v-html="attachVolumeApiParams.deviceid.description" 
/>
+                  </template>
+                </a-alert>
+              </a-collapse-panel>
+            </a-collapse>
+          </div>
+          <a-input-number
+            v-model:value="form.deviceid"
+            style="width: 100%;"
+            :min="0"
+            :placeholder="$t('label.deviceid')"
+          />
+        </a-form-item>
+      </span>
       <div :span="24" class="action-button">
         <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
         <a-button type="primary" ref="submit" @click="handleSubmit">{{ 
$t('label.ok') }}</a-button>
@@ -159,7 +201,10 @@ export default {
       offerings: [],
       customDiskOffering: false,
       loading: false,
-      isCustomizedDiskIOps: false
+      isCustomizedDiskIOps: false,
+      virtualmachines: [],
+      attachVolume: false,
+      vmidtoattach: null
     }
   },
   computed: {
@@ -204,6 +249,10 @@ export default {
           }
         }]
       })
+      if (this.attachVolume) {
+        this.rules.virtualmachineid = [{ required: true, message: 
this.$t('message.error.select') }]
+        this.rules.deviceid = [{ required: true, message: 
this.$t('message.error.select') }]
+      }
       if (!this.createVolumeFromSnapshot) {
         this.rules.name = [{ required: true, message: 
this.$t('message.error.volume.name') }]
         this.rules.diskofferingid = [{ required: true, message: 
this.$t('message.error.select') }]
@@ -248,6 +297,9 @@ export default {
         this.zones = json.listzonesresponse.zone || []
         this.form.zoneid = this.zones[0].id || ''
         this.fetchDiskOfferings(this.form.zoneid)
+        if (this.attachVolume) {
+          this.fetchVirtualMachines(this.form.zoneid)
+        }
       }).finally(() => {
         this.loading = false
       })
@@ -301,6 +353,31 @@ export default {
         this.loading = false
       })
     },
+    fetchVirtualMachines (zoneId) {
+      var params = {
+        zoneid: zoneId,
+        details: 'min'
+      }
+      if (this.owner.projectid) {
+        params.projectid = this.owner.projectid
+      } else {
+        params.account = this.owner.account
+        params.domainid = this.owner.domainid
+      }
+
+      this.loading = true
+      var vmStates = ['Running', 'Stopped']
+      vmStates.forEach((state) => {
+        params.state = state
+        api('listVirtualMachines', params).then(response => {
+          this.virtualmachines = 
this.virtualmachines.concat(response.listvirtualmachinesresponse.virtualmachine 
|| [])
+        }).catch(error => {
+          this.$notifyError(error)
+        }).finally(() => {
+          this.loading = false
+        })
+      })
+    },
     handleSubmit (e) {
       if (this.loading) return
       this.formRef.value.validate().then(() => {
@@ -315,6 +392,10 @@ export default {
         if (this.createVolumeFromSnapshot) {
           values.snapshotid = this.resource.id
         }
+        if (this.attachVolume) {
+          this.vmidtoattach = values.virtualmachineid
+          values.virtualmachineid = null
+        }
         values.domainid = this.owner.domainid
         if (this.owner.projectid) {
           values.projectid = this.owner.projectid
@@ -330,10 +411,15 @@ export default {
             successMessage: this.$t('message.success.create.volume'),
             successMethod: (result) => {
               this.closeModal()
-              if (this.createVolumeFromVM) {
+              if (this.createVolumeFromVM || this.attachVolume) {
                 const params = {}
                 params.id = result.jobresult.volume.id
-                params.virtualmachineid = this.resource.id
+                if (this.createVolumeFromVM) {
+                  params.virtualmachineid = this.resource.id
+                } else {
+                  params.virtualmachineid = this.vmidtoattach
+                  params.deviceid = values.deviceid
+                }
                 api('attachVolume', params).then(response => {
                   this.$pollJob({
                     jobId: response.attachvolumeresponse.jobid,
@@ -368,6 +454,14 @@ export default {
       const offering = this.offerings.filter(x => x.id === id)
       this.customDiskOffering = offering[0]?.iscustomized || false
       this.isCustomizedDiskIOps = offering[0]?.iscustomizediops || false
+    },
+    onChangeAttachToVM (zone) {
+      this.attachVolume = !this.attachVolume
+      this.virtualmachines = []
+      if (this.attachVolume) {
+        this.attachVolumeApiParams = this.$getApiParams('attachVolume')
+        this.fetchVirtualMachines(this.form.zoneid)
+      }
     }
   }
 }
diff --git a/ui/src/views/storage/RecurringSnapshotVolume.vue 
b/ui/src/views/storage/RecurringSnapshotVolume.vue
index ce929848de4..dc2e83a91c4 100644
--- a/ui/src/views/storage/RecurringSnapshotVolume.vue
+++ b/ui/src/views/storage/RecurringSnapshotVolume.vue
@@ -27,7 +27,7 @@
           @close-action="closeAction"
           @refresh="handleRefresh"/>
       </a-tab-pane>
-      <a-tab-pane :tab="$t('label.scheduled.snapshots')" key="2">
+      <a-tab-pane :tab="$t('label.action.recurring.snapshot')" key="2">
         <ScheduledSnapshots
           :loading="loading"
           :resource="resource"


Reply via email to