This is an automated email from the ASF dual-hosted git repository.
RosiKyu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 492682697a7 UI: fix clone offering forms silently reverting user edits
on services (#14062)
492682697a7 is described below
commit 492682697a7438bb6a7af59cca39a02071b3b716
Author: Pearl Dsilva <[email protected]>
AuthorDate: Wed Nov 11 06:47:24 2026 -0500
UI: fix clone offering forms silently reverting user edits on services
(#14062)
* UI: fix clone offering forms silently reverting user edits on services
* add support for conserve mode
* ui: prefill conserve mode in clone VPC offering form
---
.../java/com/cloud/network/vpc/VpcManagerImpl.java | 5 ++
ui/src/views/offering/CloneNetworkOffering.vue | 11 ++--
ui/src/views/offering/CloneVpcOffering.vue | 68 ++++++++++++++++++----
3 files changed, 68 insertions(+), 16 deletions(-)
diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
index 1f5fe7382fc..41f2d3fe539 100644
--- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
@@ -1081,6 +1081,11 @@ public class VpcManagerImpl extends ManagerBase
implements VpcManager, VpcProvis
ConfigurationManagerImpl.setField(cmd, "specifyAsNumber",
sourceOffering.isSpecifyAsNumber());
}
+ Boolean conserveModeFieldValue = getRawFieldValue(cmd,
"conserveMode", Boolean.class);
+ if (conserveModeFieldValue == null) {
+ ConfigurationManagerImpl.setField(cmd, "conserveMode",
sourceOffering.isConserveMode());
+ }
+
if (cmd.getInternetProtocol() == null) {
String internetProtocol =
vpcOfferingDetailsDao.getDetail(sourceOffering.getId(),
ApiConstants.INTERNET_PROTOCOL);
if (internetProtocol != null) {
diff --git a/ui/src/views/offering/CloneNetworkOffering.vue
b/ui/src/views/offering/CloneNetworkOffering.vue
index 0cd5e733b24..4b4343a823d 100644
--- a/ui/src/views/offering/CloneNetworkOffering.vue
+++ b/ui/src/views/offering/CloneNetworkOffering.vue
@@ -1228,12 +1228,7 @@ export default {
},
handleSupportedServiceChange (service, checked, provider) {
if (checked) {
- const correctProvider = this.serviceProviderMap[service]
- if (correctProvider && provider !== correctProvider) {
- this.selectedServiceProviderMap[service] = correctProvider
- } else {
- this.selectedServiceProviderMap[service] = provider
- }
+ this.selectedServiceProviderMap[service] = provider
} else {
delete this.selectedServiceProviderMap[service]
}
@@ -1294,6 +1289,10 @@ export default {
params.guestiptype = values.guestiptype
}
+ if (values.forvpc === true) {
+ params.forvpc = true
+ }
+
// Use composable for service capability params
if (this.selectedServiceProviderMap != null) {
buildServiceCapabilityParams(params, values,
this.selectedServiceProviderMap, this.registeredServicePackages)
diff --git a/ui/src/views/offering/CloneVpcOffering.vue
b/ui/src/views/offering/CloneVpcOffering.vue
index 6adbfafad61..e4c013b12af 100644
--- a/ui/src/views/offering/CloneVpcOffering.vue
+++ b/ui/src/views/offering/CloneVpcOffering.vue
@@ -212,6 +212,12 @@
</a-select-option>
</a-select>
</a-form-item>
+ <a-form-item name="conservemode" ref="conservemode">
+ <template #label>
+ <tooltip-label :title="$t('label.conservemode')"
:tooltip="apiParams.conservemode.description"/>
+ </template>
+ <a-switch v-model:checked="form.conservemode" />
+ </a-form-item>
<a-form-item name="ispublic" ref="ispublic"
:label="$t('label.ispublic')" v-if="isAdmin()">
<a-switch v-model:checked="form.ispublic" @change="val => { isPublic
= val }" />
</a-form-item>
@@ -308,7 +314,6 @@ export default {
return {
selectedDomains: [],
selectedZones: [],
- isConserveMode: true,
internetProtocolValue: 'ipv4',
domains: [],
domainLoading: false,
@@ -405,7 +410,7 @@ export default {
this.fetchZoneData()
this.fetchIpv6NetworkOfferingConfiguration()
this.fetchRoutedNetworkConfiguration()
- this.fetchSupportedServiceData()
+ this.fetchSupportedServiceData(true)
},
isAdmin () {
return isAdmin()
@@ -455,7 +460,7 @@ export default {
this.zoneLoading = false
})
},
- fetchSupportedServiceData () {
+ fetchSupportedServiceData (isInitialLoad = false) {
this.supportedServiceLoading = true
getAPI('listSupportedNetworkServices', {}).then(json => {
const networkServices =
json.listsupportednetworkservicesresponse.networkservice || []
@@ -491,7 +496,11 @@ export default {
this.supportedServiceLoading = false
this.$nextTick(() => {
- this.populateFormFromResource()
+ if (isInitialLoad) {
+ this.populateFormFromResource()
+ } else {
+ this.syncServiceSelectionsForCurrentMode()
+ }
})
})
},
@@ -541,6 +550,7 @@ export default {
const r = this.resource
this.form.name = r.name + ' - Clone'
this.form.displaytext = r.displaytext || r.name
+ if (r.conservemode !== undefined) this.form.conservemode = r.conservemode
if (r.internetprotocol) {
this.form.internetprotocol = r.internetprotocol.toLowerCase()
@@ -656,6 +666,45 @@ export default {
this.form.nsxsupportlb = Boolean(this.serviceProviderMap.Lb)
}
},
+ syncServiceSelectionsForCurrentMode () {
+ const updatedServices = this.supportedServices.map(svc => {
+ const serviceCopy = { ...svc, provider: [...svc.provider] }
+ const providerName = this.selectedServiceProviderMap[serviceCopy.name]
+
+ if (providerName) {
+ const providerIndex = serviceCopy.provider.findIndex(p => p.name ===
providerName)
+ if (providerIndex > 0) {
+ const targetProvider = serviceCopy.provider[providerIndex]
+ serviceCopy.provider.splice(providerIndex, 1)
+ serviceCopy.provider.unshift(targetProvider)
+ }
+ serviceCopy.defaultChecked = true
+ serviceCopy.selectedProvider = providerName
+ } else {
+ serviceCopy.defaultChecked = false
+ serviceCopy.selectedProvider = null
+ }
+ return serviceCopy
+ })
+ this.supportedServices = updatedServices
+
+ const availableNames = new Set(updatedServices.map(svc => svc.name))
+ Object.keys(this.selectedServiceProviderMap).forEach(name => {
+ if (!availableNames.has(name)) {
+ delete this.selectedServiceProviderMap[name]
+ }
+ })
+
+ this.connectivityServiceChecked =
Boolean(this.selectedServiceProviderMap.Connectivity)
+ this.sourceNatServiceChecked =
Boolean(this.selectedServiceProviderMap.SourceNat)
+
+ this.$nextTick(() => {
+ this.servicesReady = true
+ this.$nextTick(() => {
+ this.checkVpcVirtualRouterForServices()
+ })
+ })
+ },
async handleProviderChange (value) {
this.provider = value
if (this.provider === 'NSX') {
@@ -682,12 +731,7 @@ export default {
},
handleSupportedServiceChange (service, checked, provider) {
if (checked) {
- const correctProvider = this.serviceProviderMap[service]
- if (correctProvider && provider !== correctProvider) {
- this.selectedServiceProviderMap[service] = correctProvider
- } else {
- this.selectedServiceProviderMap[service] = provider
- }
+ this.selectedServiceProviderMap[service] = provider
} else {
delete this.selectedServiceProviderMap[service]
}
@@ -818,6 +862,10 @@ export default {
params.enable = values.enable
}
+ if (values.conservemode !== undefined) {
+ params.conservemode = values.conservemode
+ }
+
this.loading = true
postAPI('cloneVPCOffering', params).then(json => {
this.$message.success(`${this.$t('message.success.clone.vpc.offering')}
${values.name}`)