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 tagItems = tags.map((it) => {
const { filter, value } = it;
const { options } = filter;
const { options } = filter || {};
let label = value;
if (options) {
const current = options.find((item) => {

View File

@@ -69,6 +69,7 @@ export class EditHealthMonitor extends ModalAction {
max_retries: 3,
enableHealthMonitor: false,
admin_state_up: true,
url_path: '/',
};
}
const {
@@ -78,6 +79,7 @@ export class EditHealthMonitor extends ModalAction {
delay,
timeout,
max_retries,
url_path,
} = healthMonitor;
return {
enableHealthMonitor: true,
@@ -87,6 +89,7 @@ export class EditHealthMonitor extends ModalAction {
delay,
timeout,
max_retries,
url_path,
};
}
@@ -194,6 +197,23 @@ export class EditHealthMonitor extends ModalAction {
tip: t('Defines the admin state of the health monitor.'),
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 { healthMonitor } = this.state;
const { id } = healthMonitor || {};
const { enableHealthMonitor, type, ...others } = values;
const { enableHealthMonitor, type, url_path, ...others } = values;
const updatedUrlPath = url_path ?? '/';
if (id) {
if (!enableHealthMonitor) {
return globalHealthMonitorStore.delete({ id });
}
return globalHealthMonitorStore.edit({ id }, others);
return globalHealthMonitorStore.edit(
{ id },
{ ...others, url_path: updatedUrlPath }
);
}
if (!enableHealthMonitor) {
return Promise.resolve();
@@ -215,6 +239,7 @@ export class EditHealthMonitor extends ModalAction {
type,
...others,
pool_id: default_pool_id,
url_path: updatedUrlPath,
};
return globalHealthMonitorStore.create(data);
};

View File

@@ -85,7 +85,8 @@ export class BaseDetail extends Base {
get 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 = [
{
label: t('Enable Health Monitor'),
@@ -115,6 +116,10 @@ export class BaseDetail extends Base {
label: t('Admin State Up'),
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'),
dataIndex: 'vip_subnet_id',
},
{
title: t('Flavor'),
dataIndex: 'flavor_id',
},
{
title: t('IP'),
dataIndex: 'vip_address',

View File

@@ -16,13 +16,16 @@ import Base from 'components/Form';
import { inject, observer } from 'mobx-react';
import { NetworkStore } from 'stores/neutron/network';
import { SubnetStore } from 'stores/neutron/subnet';
import globalLoadBalancerFlavorStore from 'stores/octavia/flavor';
import { LbaasStore } from 'stores/octavia/loadbalancer';
export class BaseStep extends Base {
init() {
this.store = new LbaasStore();
this.flavorStore = globalLoadBalancerFlavorStore;
this.networkStore = new NetworkStore();
this.subnetStore = new SubnetStore();
this.getFlavors();
}
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() {
const { network_id, subnetDetails = [] } = this.state;
return [
@@ -94,6 +105,42 @@ export class BaseStep extends Base {
label: t('Description'),
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',
label: t('Owned Network'),

View File

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

View File

@@ -42,6 +42,7 @@ export class HealthMonitorStep extends Base {
health_max_retries: 3,
health_type: '',
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.'),
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 = {
'X-Forwarded-For': t('Specify the client IP address'),
'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) => ({

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;