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">└─ </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">└─ </span>{{record.displaytext }} </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"