Merge "feat: Add flavor selection and monitoring URL option during load balancer creation"

This commit is contained in:
Zuul
2025-09-30 12:51:55 +00:00
committed by Gerrit Code Review
10 changed files with 168 additions and 6 deletions

View File

@@ -88,6 +88,11 @@ export class OctaviaClient extends Base {
}, },
], ],
}, },
{
name: 'flavors',
key: 'lbaas/flavors',
responseKey: 'flavor',
},
]; ];
} }
} }

View File

@@ -276,7 +276,7 @@ class MagicInput extends PureComponent {
const { tags } = this.state; const { tags } = this.state;
const tagItems = tags.map((it) => { const tagItems = tags.map((it) => {
const { filter, value } = it; const { filter, value } = it;
const { options } = filter; const { options } = filter || {};
let label = value; let label = value;
if (options) { if (options) {
const current = options.find((item) => { const current = options.find((item) => {

View File

@@ -69,6 +69,7 @@ export class EditHealthMonitor extends ModalAction {
max_retries: 3, max_retries: 3,
enableHealthMonitor: false, enableHealthMonitor: false,
admin_state_up: true, admin_state_up: true,
url_path: '/',
}; };
} }
const { const {
@@ -78,6 +79,7 @@ export class EditHealthMonitor extends ModalAction {
delay, delay,
timeout, timeout,
max_retries, max_retries,
url_path,
} = healthMonitor; } = healthMonitor;
return { return {
enableHealthMonitor: true, enableHealthMonitor: true,
@@ -87,6 +89,7 @@ export class EditHealthMonitor extends ModalAction {
delay, delay,
timeout, timeout,
max_retries, max_retries,
url_path,
}; };
} }
@@ -194,6 +197,23 @@ export class EditHealthMonitor extends ModalAction {
tip: t('Defines the admin state of the health monitor.'), tip: t('Defines the admin state of the health monitor.'),
hidden: !enableHealthMonitor, hidden: !enableHealthMonitor,
}, },
{
name: 'url_path',
label: t('Monitoring URL'),
type: 'input',
required: false,
validator: (_, value) => {
if (value && !value.startsWith('/')) {
return Promise.reject(new Error(t('URL must start with /')));
}
return Promise.resolve();
},
placeholder: t('e.g., /status.html or /healthcheck.html'),
extra: t(
'Defaults to "/" if left blank. Recommended: use a dedicated status page like "/status.html".'
),
hidden: !enableHealthMonitor,
},
]; ];
} }
@@ -201,12 +221,16 @@ export class EditHealthMonitor extends ModalAction {
const { default_pool_id } = this.item; const { default_pool_id } = this.item;
const { healthMonitor } = this.state; const { healthMonitor } = this.state;
const { id } = healthMonitor || {}; const { id } = healthMonitor || {};
const { enableHealthMonitor, type, ...others } = values; const { enableHealthMonitor, type, url_path, ...others } = values;
const updatedUrlPath = url_path ?? '/';
if (id) { if (id) {
if (!enableHealthMonitor) { if (!enableHealthMonitor) {
return globalHealthMonitorStore.delete({ id }); return globalHealthMonitorStore.delete({ id });
} }
return globalHealthMonitorStore.edit({ id }, others); return globalHealthMonitorStore.edit(
{ id },
{ ...others, url_path: updatedUrlPath }
);
} }
if (!enableHealthMonitor) { if (!enableHealthMonitor) {
return Promise.resolve(); return Promise.resolve();
@@ -215,6 +239,7 @@ export class EditHealthMonitor extends ModalAction {
type, type,
...others, ...others,
pool_id: default_pool_id, pool_id: default_pool_id,
url_path: updatedUrlPath,
}; };
return globalHealthMonitorStore.create(data); return globalHealthMonitorStore.create(data);
}; };

View File

@@ -85,7 +85,8 @@ export class BaseDetail extends Base {
get healthMonitor() { get healthMonitor() {
const healthMonitor = this.detailData.healthMonitor || {}; const healthMonitor = this.detailData.healthMonitor || {};
const { type, delay, timeout, max_retries, admin_state_up } = healthMonitor; const { type, delay, timeout, max_retries, admin_state_up, url_path } =
healthMonitor;
const options = [ const options = [
{ {
label: t('Enable Health Monitor'), label: t('Enable Health Monitor'),
@@ -115,6 +116,10 @@ export class BaseDetail extends Base {
label: t('Admin State Up'), label: t('Admin State Up'),
content: admin_state_up ? t('On') : t('Off'), content: admin_state_up ? t('On') : t('Off'),
}, },
{
label: t('Monitoring URL'),
content: url_path || '/',
},
] ]
); );
} }

View File

@@ -70,6 +70,10 @@ export class LoadBalancerDetail extends Base {
title: t('Subnet'), title: t('Subnet'),
dataIndex: 'vip_subnet_id', dataIndex: 'vip_subnet_id',
}, },
{
title: t('Flavor'),
dataIndex: 'flavor_id',
},
{ {
title: t('IP'), title: t('IP'),
dataIndex: 'vip_address', dataIndex: 'vip_address',

View File

@@ -16,13 +16,16 @@ import Base from 'components/Form';
import { inject, observer } from 'mobx-react'; import { inject, observer } from 'mobx-react';
import { NetworkStore } from 'stores/neutron/network'; import { NetworkStore } from 'stores/neutron/network';
import { SubnetStore } from 'stores/neutron/subnet'; import { SubnetStore } from 'stores/neutron/subnet';
import globalLoadBalancerFlavorStore from 'stores/octavia/flavor';
import { LbaasStore } from 'stores/octavia/loadbalancer'; import { LbaasStore } from 'stores/octavia/loadbalancer';
export class BaseStep extends Base { export class BaseStep extends Base {
init() { init() {
this.store = new LbaasStore(); this.store = new LbaasStore();
this.flavorStore = globalLoadBalancerFlavorStore;
this.networkStore = new NetworkStore(); this.networkStore = new NetworkStore();
this.subnetStore = new SubnetStore(); this.subnetStore = new SubnetStore();
this.getFlavors();
} }
get title() { get title() {
@@ -79,6 +82,14 @@ export class BaseStep extends Base {
}); });
}; };
async getFlavors() {
await this.flavorStore.fetchList({ enabled: true });
this.setState({
flavorList: this.flavorStore.list.data || [],
loading: false,
});
}
get formItems() { get formItems() {
const { network_id, subnetDetails = [] } = this.state; const { network_id, subnetDetails = [] } = this.state;
return [ return [
@@ -94,6 +105,42 @@ export class BaseStep extends Base {
label: t('Description'), label: t('Description'),
type: 'textarea', type: 'textarea',
}, },
{
name: 'flavor_id',
label: t('Flavors'),
type: 'select-table',
data: this.state.flavorList || [],
required: false,
filterParams: [
{
name: 'id',
label: t('ID'),
},
{
name: 'name',
label: t('Name'),
},
],
columns: [
{
title: t('ID'),
dataIndex: 'id',
},
{
title: t('Name'),
dataIndex: 'name',
},
{
title: t('Description'),
dataIndex: 'description',
},
{
title: t('Enabled'),
dataIndex: 'enabled',
valueRender: 'yesNo',
},
],
},
{ {
name: 'vip_network_id', name: 'vip_network_id',
label: t('Owned Network'), label: t('Owned Network'),

View File

@@ -95,6 +95,8 @@ export class StepCreate extends StepAction {
pool_admin_state_up, pool_admin_state_up,
monitor_admin_state_up, monitor_admin_state_up,
insert_headers, insert_headers,
flavor_id,
health_url_path,
...rest ...rest
} = values; } = values;
const data = { const data = {
@@ -108,6 +110,9 @@ export class StepCreate extends StepAction {
data.vip_address = ip_address.ip; data.vip_address = ip_address.ip;
} }
data.admin_state_up = admin_state_enabled; data.admin_state_up = admin_state_enabled;
if (flavor_id?.selectedRowKeys?.length) {
data.flavor_id = flavor_id.selectedRowKeys[0];
}
const listenerData = { const listenerData = {
admin_state_up: listener_admin_state_up, admin_state_up: listener_admin_state_up,
@@ -141,7 +146,10 @@ export class StepCreate extends StepAction {
} }
const poolData = { admin_state_up: pool_admin_state_up }; const poolData = { admin_state_up: pool_admin_state_up };
const healthMonitorData = { admin_state_up: monitor_admin_state_up }; const healthMonitorData = {
admin_state_up: monitor_admin_state_up,
url_path: health_url_path,
};
Object.keys(rest).forEach((i) => { Object.keys(rest).forEach((i) => {
if (i.indexOf('listener') === 0) { if (i.indexOf('listener') === 0) {
listenerData[i.replace('listener_', '')] = values[i]; listenerData[i.replace('listener_', '')] = values[i];
@@ -153,7 +161,10 @@ export class StepCreate extends StepAction {
}); });
if (enableHealthMonitor) { if (enableHealthMonitor) {
poolData.healthmonitor = healthMonitorData; poolData.healthmonitor = {
...healthMonitorData,
url_path: healthMonitorData.url_path ?? '/',
};
} }
const { const {
extMembers = [], extMembers = [],

View File

@@ -42,6 +42,7 @@ export class HealthMonitorStep extends Base {
health_max_retries: 3, health_max_retries: 3,
health_type: '', health_type: '',
monitor_admin_state_up: true, monitor_admin_state_up: true,
health_url_path: '/',
}; };
} }
@@ -124,6 +125,28 @@ export class HealthMonitorStep extends Base {
tip: t('Defines the admin state of the health monitor.'), tip: t('Defines the admin state of the health monitor.'),
hidden: !enableHealthMonitor, hidden: !enableHealthMonitor,
}, },
{
name: 'health_url_path',
label: t('Monitoring URL'),
type: 'input',
rules: [
{ required: false },
{
validator: (_, value) => {
if (value && !value.startsWith('/')) {
return Promise.reject(new Error(t('URL must start with /')));
}
return Promise.resolve();
},
},
],
initialValue: '/',
placeholder: t('e.g., /status.html or /healthcheck.html'),
extra: t(
'Defaults to "/" if left blank. Recommended: use a dedicated status page like "/status.html".'
),
hidden: !enableHealthMonitor,
},
]; ];
} }
} }

View File

@@ -159,6 +159,7 @@ export const healthProtocols = [
export const INSERT_HEADERS = { export const INSERT_HEADERS = {
'X-Forwarded-For': t('Specify the client IP address'), 'X-Forwarded-For': t('Specify the client IP address'),
'X-Forwarded-Port': t('Specify the listener port'), 'X-Forwarded-Port': t('Specify the listener port'),
'X-Forwarded-Proto': t('When true this header is inserted'),
}; };
export const insertHeaderOptions = Object.keys(INSERT_HEADERS).map((key) => ({ export const insertHeaderOptions = Object.keys(INSERT_HEADERS).map((key) => ({

View File

@@ -0,0 +1,41 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import client from 'client';
import Base from 'stores/base';
export class LoadBalancerFlavorStore extends Base {
get client() {
return client.octavia.flavors;
}
get listWithDetail() {
return false;
}
listDidFetch(items, allProject, originParams) {
if (!items.length) {
return items;
}
const { enabled } = originParams || {};
if (enabled === undefined) {
return items;
}
return items.filter((it) => it.enabled === enabled);
}
}
const globalLoadBalancerFlavorStore = new LoadBalancerFlavorStore();
export default globalLoadBalancerFlavorStore;