diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js
index 37cdd0c8b98a..bc45f4c8eacf 100644
--- a/ui/src/config/section/network.js
+++ b/ui/src/config/section/network.js
@@ -1139,6 +1139,11 @@ export default {
args: ['name', 'description', 'sourceipaddress', 'sourceport', 'instanceport', 'algorithm', 'networkid', 'sourceipaddressnetworkid', 'scheme'],
mapping: {
algorithm: {
+ // TODO: derive from the selected network's offering capabilities
+ // (service Lb, SupportedLBAlgorithms) - mirrors what LoadBalancing.vue
+ // does for public LB. Doing it here requires AutogenView to support
+ // an async/function-valued `options`, which is a framework change
+ // outside the scope of this UI patch.
options: ['source', 'roundrobin', 'leastconn']
},
scheme: {
diff --git a/ui/src/views/compute/AutoScaleLoadBalancing.vue b/ui/src/views/compute/AutoScaleLoadBalancing.vue
index 6c04ce1c2504..deebd93f26ef 100644
--- a/ui/src/views/compute/AutoScaleLoadBalancing.vue
+++ b/ui/src/views/compute/AutoScaleLoadBalancing.vue
@@ -267,9 +267,7 @@
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
- {{ $t('label.lb.algorithm.roundrobin') }}
- {{ $t('label.lb.algorithm.leastconn') }}
- {{ $t('label.lb.algorithm.source') }}
+ {{ $t('label.lb.algorithm.' + algo) }}
@@ -338,6 +336,10 @@ export default {
algorithm: '',
protocol: ''
},
+ // Default fallback list. Replaced by fetchLbCapabilitiesForRule() with whatever the
+ // rule's tier network advertises via service.Lb.capability.SupportedLbAlgorithms,
+ // matching the pattern in LoadBalancing.vue and VpcTiersTab.vue.
+ supportedAlgorithms: ['roundrobin', 'leastconn', 'source'],
vms: [],
nics: [],
totalCount: 0,
@@ -836,6 +838,36 @@ export default {
this.editRuleDetails.name = this.selectedRule.name
this.editRuleDetails.algorithm = this.selectedRule.algorithm
this.editRuleDetails.protocol = this.selectedRule.protocol
+ this.fetchLbCapabilitiesForRule(this.selectedRule)
+ },
+ /**
+ * Loads SupportedLbAlgorithms from the LB rule's tier network. Same approach as
+ * LoadBalancing.vue's fetchLbCapabilities() and VpcTiersTab.vue's
+ * fetchLbCapabilitiesForNetwork() — capability is read from the live network
+ * (listNetworks) rather than the offering, since the offering response does not
+ * carry the per-provider Lb capability map.
+ */
+ fetchLbCapabilitiesForRule (rule) {
+ const networkId = rule?.networkid
+ if (!networkId) {
+ return
+ }
+ getAPI('listNetworks', { id: networkId, listall: true }).then(json => {
+ const network = json?.listnetworksresponse?.network?.[0]
+ const lbService = network?.service?.find(s => s.name === 'Lb')
+ const algoCap = lbService?.capability?.find(c => c.name === 'SupportedLbAlgorithms')
+ if (algoCap && algoCap.value) {
+ const algos = algoCap.value.split(',').map(s => s.trim()).filter(s => s)
+ if (algos.length > 0) {
+ this.supportedAlgorithms = algos
+ if (!algos.includes(this.editRuleDetails.algorithm)) {
+ this.editRuleDetails.algorithm = algos[0]
+ }
+ }
+ }
+ }).catch(error => {
+ console.warn('Failed to load AutoScale LB algorithm capabilities; using defaults', error)
+ })
},
handleSubmitEditForm () {
if (this.editRuleModalLoading) return
diff --git a/ui/src/views/network/LoadBalancing.vue b/ui/src/views/network/LoadBalancing.vue
index 0b9ed7684a89..95b982d3cd47 100644
--- a/ui/src/views/network/LoadBalancing.vue
+++ b/ui/src/views/network/LoadBalancing.vue
@@ -49,9 +49,7 @@
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
-
{{ $t('label.lb.algorithm.roundrobin') }}
-
{{ $t('label.lb.algorithm.leastconn') }}
-
{{ $t('label.lb.algorithm.source') }}
+
{{ $t('label.lb.algorithm.' + algo) }}
@@ -872,6 +868,10 @@ export default {
cidrlist: ''
},
lbProvider: null,
+ // Populated from the network's offering capabilities (service Lb,
+ // SupportedLBAlgorithms). Defaults to the union we historically hard-coded so
+ // legacy networks/offerings without an explicit cap keep working.
+ supportedAlgorithms: ['roundrobin', 'leastconn', 'source'],
addVmModalVisible: false,
addVmModalLoading: false,
addVmModalNicLoading: false,
@@ -1089,6 +1089,69 @@ export default {
this.fetchListTiers()
this.fetchLBRules()
this.fetchZone()
+ this.fetchLbCapabilities()
+ },
+ /**
+ * Loads the SupportedLbAlgorithms capability from the network's Lb service.
+ * The capability lives at network.service[name=Lb].capability[name=SupportedLbAlgorithms]
+ * with a CSV value like "roundrobin,source". Falls back to the legacy
+ * static list when the lookup fails so providers that don't declare it
+ * keep their pre-existing behaviour.
+ *
+ * Note: listNetworks is the right endpoint here (not listNetworkOfferings):
+ * the offering response hard-codes only SupportedLBIsolation/ElasticLb/
+ * InlineMode/VmAutoScaling for the Lb service, while the network response
+ * walks the live provider's getCapabilities() and exposes everything we
+ * declared. The capability name on the wire is "SupportedLbAlgorithms"
+ * (note the lowercase 'b' in 'Lb') - that's how Capability.SupportedLBAlgorithms.getName()
+ * serialises.
+ */
+ fetchLbCapabilities () {
+ const networkId = this.resource?.associatednetworkid || this.resource?.networkid
+ if (networkId) {
+ this.fetchLbCapabilitiesByNetworkId(networkId)
+ return
+ }
+ // VPC public IP that has not been associated to a specific tier yet (no rule yet).
+ // Pick any Lb-enabled tier of the VPC and read the capability from there - every tier
+ // of the same VPC uses the same Lb provider, so the answer is provider-uniform.
+ if (this.resource?.vpcid) {
+ getAPI('listNetworks', { vpcid: this.resource.vpcid, supportedservices: 'Lb', listall: true }).then(json => {
+ const tier = json?.listnetworksresponse?.network?.[0]
+ if (tier?.id) {
+ this.applyLbAlgorithmCapability(tier)
+ }
+ }).catch(error => {
+ console.warn('Failed to enumerate VPC tiers for LB capabilities; using defaults', error)
+ })
+ }
+ },
+ fetchLbCapabilitiesByNetworkId (networkId) {
+ getAPI('listNetworks', { id: networkId, listall: true }).then(json => {
+ const network = json?.listnetworksresponse?.network?.[0]
+ if (network) {
+ this.applyLbAlgorithmCapability(network)
+ }
+ }).catch(error => {
+ // Swallow - we just keep the default list. Don't pop a notification
+ // because this is non-fatal background data.
+ console.warn('Failed to load LB algorithm capabilities; using defaults', error)
+ })
+ },
+ applyLbAlgorithmCapability (network) {
+ const lbService = network?.service?.find(s => s.name === 'Lb')
+ const algoCap = lbService?.capability?.find(c => c.name === 'SupportedLbAlgorithms')
+ if (algoCap && algoCap.value) {
+ const algos = algoCap.value.split(',').map(s => s.trim()).filter(s => s)
+ if (algos.length > 0) {
+ this.supportedAlgorithms = algos
+ // If the default algorithm is not in the supported set, switch to the first one
+ // so the dropdown shows a valid selection from the start.
+ if (!algos.includes(this.newRule.algorithm)) {
+ this.newRule.algorithm = algos[0]
+ }
+ }
+ }
},
fetchVpc () {
if (!this.resource.vpcid) {
diff --git a/ui/src/views/network/VpcTiersTab.vue b/ui/src/views/network/VpcTiersTab.vue
index 4a689f13c34d..bf21c3ec8227 100644
--- a/ui/src/views/network/VpcTiersTab.vue
+++ b/ui/src/views/network/VpcTiersTab.vue
@@ -347,8 +347,8 @@
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
-
- {{ key }}
+
+ {{ $t('label.lb.algorithm.' + algo) }}
@@ -409,11 +409,13 @@ export default {
errorPrivateMtu: '',
gatewayPlaceholder: '',
netmaskPlaceholder: '',
- algorithms: {
- Source: 'source',
- 'Round-robin': 'roundrobin',
- 'Least connections': 'leastconn'
- },
+ // Default fallback algorithm list for the Internal LB modal. Replaced at runtime by
+ // fetchLbCapabilitiesForNetwork() with whatever the selected tier's Lb provider
+ // declares via service.Lb.capability.SupportedLbAlgorithms — same approach used by
+ // LoadBalancing.vue for the public LB form, so providers like OVN that only support
+ // {roundrobin, source} expose exactly that subset, while providers that declare the
+ // full set keep their previous options.
+ supportedAlgorithms: ['source', 'roundrobin', 'leastconn'],
internalLbCols: [
{
key: 'name',
@@ -728,13 +730,45 @@ export default {
this.initForm()
this.showAddInternalLB = true
this.networkid = id
- this.form.algorithm = 'Source'
+ // Reset to the static default first so the dropdown is never empty while the async
+ // capability lookup runs; fetchLbCapabilitiesForNetwork() narrows it down once the
+ // listNetworks response arrives.
+ this.supportedAlgorithms = ['source', 'roundrobin', 'leastconn']
+ this.form.algorithm = this.supportedAlgorithms[0]
this.rules = {
name: [{ required: true, message: this.$t('message.error.internallb.name') }],
sourcePort: [{ required: true, message: this.$t('message.error.internallb.source.port') }],
instancePort: [{ required: true, message: this.$t('message.error.internallb.instance.port') }],
algorithm: [{ required: true, message: this.$t('label.required') }]
}
+ this.fetchLbCapabilitiesForNetwork(id)
+ },
+ /**
+ * Loads SupportedLbAlgorithms from the tier network's Lb service. Mirrors the
+ * fetchLbCapabilities() helper in LoadBalancing.vue so the public-LB form and the
+ * Internal-LB modal converge on the same data-driven dropdown — provider-agnostic and
+ * future-proof. Falls back silently to the static default when the lookup fails.
+ */
+ fetchLbCapabilitiesForNetwork (networkId) {
+ if (!networkId) {
+ return
+ }
+ getAPI('listNetworks', { id: networkId, listall: true }).then(json => {
+ const network = json?.listnetworksresponse?.network?.[0]
+ const lbService = network?.service?.find(s => s.name === 'Lb')
+ const algoCap = lbService?.capability?.find(c => c.name === 'SupportedLbAlgorithms')
+ if (algoCap && algoCap.value) {
+ const algos = algoCap.value.split(',').map(s => s.trim()).filter(s => s)
+ if (algos.length > 0) {
+ this.supportedAlgorithms = algos
+ if (!algos.includes(this.form.algorithm)) {
+ this.form.algorithm = algos[0]
+ }
+ }
+ }
+ }).catch(error => {
+ console.warn('Failed to load Internal LB algorithm capabilities; using defaults', error)
+ })
},
handleAddNetworkSubmit () {
if (this.modalLoading) return