diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index bab3f4daac3..0da52865a96 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2692,7 +2692,7 @@ "message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN Customer Gateway", "message.delete.vpn.gateway": "Please confirm that you want to delete this VPN Gateway", "message.deleting.vm": "Deleting VM", -"message.deployasis": "Selected template is Deploy As-Is i.e., the VM is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped VMs for such templates.", +"message.deployasis": "Selected template is Deploy As-Is i.e., the VM is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped VMs for such templates.", "message.desc.add.new.lb.sticky.rule": "Add new LB sticky rule", "message.desc.advanced.zone": "For more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.", "message.desc.basic.zone": "Provide a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering).", @@ -2850,6 +2850,7 @@ "message.error.upload.template.description": "Only one template can be uploaded at a time", "message.error.url": "Please enter URL", "message.error.username": "Enter your username", +"message.error.valid.iops.range": "Please enter a valid IOPS range", "message.error.vcenter.datacenter": "Please enter vCenter Datacenter", "message.error.vcenter.datastore": "Please enter vCenter Datastore", "message.error.vcenter.host": "Please enter vCenter Host", diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js index b8b2beff3d9..5dacd7afd21 100644 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@ -31,7 +31,7 @@ export default { params: { isrecursive: 'true' }, columns: ['name', 'displaytext', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'], details: () => { - var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created'] + var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created'] if (store.getters.apis.createServiceOffering && store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) { fields.splice(6, 0, 'vspherestoragepolicy') @@ -124,7 +124,7 @@ export default { params: { isrecursive: 'true' }, columns: ['name', 'displaytext', 'disksize', 'domain', 'zone', 'order'], details: () => { - var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'tags', 'domain', 'zone', 'created'] + var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'tags', 'domain', 'zone', 'created'] if (store.getters.apis.createDiskOffering && store.getters.apis.createDiskOffering.params.filter(x => x.name === 'storagepolicy').length > 0) { fields.splice(6, 0, 'vspherestoragepolicy') diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index a7a9ebbdbfd..7f02e90b931 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -107,7 +107,7 @@ {{ $t('label.override.rootdisk.size') }} @@ -117,6 +117,7 @@ v-show="showRootDiskSizeChanger" input-decorator="rootdisksize" :preFillContent="dataPreFill" + :isCustomized="true" :minDiskSize="dataPreFill.minrootdisksize" @update-disk-size="updateFieldValue" style="margin-top: 10px;"/> @@ -202,7 +203,7 @@ @handle-search-filter="($event) => handleSearchFilter('serviceOfferings', $event)" > @@ -260,14 +265,19 @@ :loading="loading.diskOfferings" :preFillContent="dataPreFill" :isIsoSelected="tabKey==='isoid'" + @on-selected-disk-size="onSelectDiskSize" @select-disk-offering-item="($event) => updateDiskOffering($event)" @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)" > + :diskSelected="diskSelected" + :isCustomized="diskOffering.iscustomized" + @handler-error="handlerError" + @update-disk-size="updateFieldValue" + @update-iops-value="updateIOPSValue"/> @@ -789,7 +799,13 @@ export default { showDetails: false, showRootDiskSizeChanger: false, securitygroupids: [], - rootDiskSizeFixed: 0 + rootDiskSizeFixed: 0, + error: false, + diskSelected: {}, + diskIOpsMin: 0, + diskIOpsMax: 0, + minIOPs: 0, + maxIOPs: 0 } }, computed: { @@ -983,6 +999,12 @@ export default { }, showSecurityGroupSection () { return (this.networks.length > 0 && this.zone.securitygroupsenabled) || (this.zone && this.zone.networktype === 'Basic') + }, + isCustomizedDiskIOPS () { + return this.diskSelected?.iscustomizediops || false + }, + isCustomizedIOPS () { + return this.serviceOffering?.iscustomizediops || false } }, watch: { @@ -1424,6 +1446,13 @@ export default { }) return } + if (this.error) { + this.$notification.error({ + message: this.$t('message.request.failed'), + description: this.$t('error.form.message') + }) + return + } this.loading.deploy = true @@ -1474,6 +1503,10 @@ export default { if (this.selectedTemplateConfiguration) { deployVmData['details[0].configurationId'] = this.selectedTemplateConfiguration.id } + if (this.isCustomizedIOPS) { + deployVmData['details[0].minIops'] = this.minIOPs + deployVmData['details[0].maxIops'] = this.maxIOPs + } // step 4: select disk offering if (!this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) { if (values.multidiskoffering) { @@ -1492,6 +1525,10 @@ export default { deployVmData.size = values.size } } + if (this.isCustomizedDiskIOPS) { + deployVmData['details[0].minIopsDo'] = this.diskIOpsMin + deployVmData['details[0].maxIopsDo'] = this.diskIOpsMax + } // step 5: select an affinity group deployVmData.affinitygroupids = (values.affinitygroupids || []).join(',') // step 6: select network @@ -1994,6 +2031,15 @@ export default { this.rootDiskSizeFixed = offering.rootdisksize this.showRootDiskSizeChanger = false } + }, + handlerError (error) { + this.error = error + }, + onSelectDiskSize (rowSelected) { + this.diskSelected = rowSelected + }, + updateIOPSValue (input, value) { + this[input] = value } } } diff --git a/ui/src/views/compute/wizard/ComputeSelection.vue b/ui/src/views/compute/wizard/ComputeSelection.vue index df178a8922d..816e0c914b9 100644 --- a/ui/src/views/compute/wizard/ComputeSelection.vue +++ b/ui/src/views/compute/wizard/ComputeSelection.vue @@ -19,7 +19,7 @@ - + - + - + + + + +

{{ $t(errorMinIOps) }}

+
+
+ + + +

{{ $t(errorMaxIOps) }}

+
+
@@ -126,6 +138,14 @@ export default { preFillContent: { type: Object, default: () => {} + }, + isCustomized: { + type: Boolean, + default: false + }, + isCustomizedIOps: { + type: Boolean, + default: false } }, data () { @@ -146,7 +166,11 @@ export default { status: '', message: '' } - } + }, + minIOps: null, + maxIOps: null, + errorMinIOps: false, + errorMaxIOps: false } }, computed: { @@ -162,7 +186,9 @@ export default { } }, mounted () { - this.fillValue() + if (this.isCustomized) { + this.fillValue() + } }, methods: { fillValue () { @@ -247,6 +273,34 @@ export default { } return true + }, + updateIOpsValue () { + let flag = true + this.errorMinIOps = false + this.errorMaxIOps = false + if (this.minIOps < 0) { + this.errorMinIOps = `${this.$t('message.error.limit.value')} 0` + flag = false + } + if (this.maxIOps < 0) { + this.errorMaxIOps = `${this.$t('message.error.limit.value')} 0` + flag = false + } + + if (!flag) { + this.$emit('handler-error', true) + return + } + + if (this.minIOps > this.maxIOps) { + this.errorMinIOps = this.$t('message.error.valid.iops.range') + this.errorMaxIOps = this.$t('message.error.valid.iops.range') + this.$emit('handler-error', true) + return + } + this.$emit('update-iops-value', 'minIOPs', this.minIOps) + this.$emit('update-iops-value', 'maxIOPs', this.maxIOps) + this.$emit('handler-error', false) } } } diff --git a/ui/src/views/compute/wizard/DiskOfferingSelection.vue b/ui/src/views/compute/wizard/DiskOfferingSelection.vue index aff96a28737..112594113be 100644 --- a/ui/src/views/compute/wizard/DiskOfferingSelection.vue +++ b/ui/src/views/compute/wizard/DiskOfferingSelection.vue @@ -128,7 +128,8 @@ export default { page: 1, pageSize: 10, keyword: null - } + }, + diskSelected: {} } }, created () { @@ -213,8 +214,13 @@ export default { } }, onSelectRow (value) { + const rowSelected = this.items.filter(item => item.id === value[0]) + if (rowSelected && rowSelected.length > 0) { + this.diskSelected = rowSelected[0] + } this.selectedRowKeys = value this.$emit('select-disk-offering-item', value[0]) + this.$emit('on-selected-disk-size', this.diskSelected) }, handleSearch (value) { this.filter = value @@ -237,8 +243,13 @@ export default { return { on: { click: () => { + const rowSelected = this.items.filter(item => item.id === record.key) + if (rowSelected && rowSelected.length > 0) { + this.diskSelected = rowSelected[0] + } this.selectedRowKeys = [record.key] this.$emit('select-disk-offering-item', record.key) + this.$emit('on-selected-disk-size', this.diskSelected) } } } diff --git a/ui/src/views/compute/wizard/DiskSizeSelection.vue b/ui/src/views/compute/wizard/DiskSizeSelection.vue index 57e8e89b7f4..42f5c510176 100644 --- a/ui/src/views/compute/wizard/DiskSizeSelection.vue +++ b/ui/src/views/compute/wizard/DiskSizeSelection.vue @@ -16,11 +16,11 @@ // under the License.