diff --git a/src/client/octavia/index.js b/src/client/octavia/index.js index 68b6d904..5abd3eb0 100644 --- a/src/client/octavia/index.js +++ b/src/client/octavia/index.js @@ -88,6 +88,11 @@ export class OctaviaClient extends Base { }, ], }, + { + name: 'flavors', + key: 'lbaas/flavors', + responseKey: 'flavor', + }, ]; } } diff --git a/src/components/MagicInput/index.jsx b/src/components/MagicInput/index.jsx index b4964222..e58233d9 100644 --- a/src/components/MagicInput/index.jsx +++ b/src/components/MagicInput/index.jsx @@ -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) => { diff --git a/src/pages/network/containers/LoadBalancers/Listener/Actions/EditHealthMonitor.jsx b/src/pages/network/containers/LoadBalancers/Listener/Actions/EditHealthMonitor.jsx index cfaa2006..6270d176 100644 --- a/src/pages/network/containers/LoadBalancers/Listener/Actions/EditHealthMonitor.jsx +++ b/src/pages/network/containers/LoadBalancers/Listener/Actions/EditHealthMonitor.jsx @@ -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); }; diff --git a/src/pages/network/containers/LoadBalancers/Listener/Detail/BaseDetail.jsx b/src/pages/network/containers/LoadBalancers/Listener/Detail/BaseDetail.jsx index 65324381..8a7d065a 100644 --- a/src/pages/network/containers/LoadBalancers/Listener/Detail/BaseDetail.jsx +++ b/src/pages/network/containers/LoadBalancers/Listener/Detail/BaseDetail.jsx @@ -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 || '/', + }, ] ); } diff --git a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/Detail/index.jsx b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/Detail/index.jsx index 1937bc59..5b8c1e86 100644 --- a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/Detail/index.jsx +++ b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/Detail/index.jsx @@ -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', diff --git a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/BaseStep/index.jsx b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/BaseStep/index.jsx index 8df5d617..dd417444 100644 --- a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/BaseStep/index.jsx +++ b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/BaseStep/index.jsx @@ -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'), diff --git a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx index aaf88b74..958853a2 100644 --- a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx +++ b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx @@ -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 = [], diff --git a/src/pages/network/containers/LoadBalancers/StepCreateComponents/HealthMonitorStep/index.jsx b/src/pages/network/containers/LoadBalancers/StepCreateComponents/HealthMonitorStep/index.jsx index f04d31d6..19dd26d0 100644 --- a/src/pages/network/containers/LoadBalancers/StepCreateComponents/HealthMonitorStep/index.jsx +++ b/src/pages/network/containers/LoadBalancers/StepCreateComponents/HealthMonitorStep/index.jsx @@ -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, + }, ]; } } diff --git a/src/resources/octavia/lb.jsx b/src/resources/octavia/lb.jsx index 03f3bc04..a1e0537c 100644 --- a/src/resources/octavia/lb.jsx +++ b/src/resources/octavia/lb.jsx @@ -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) => ({ diff --git a/src/stores/octavia/flavor.js b/src/stores/octavia/flavor.js new file mode 100644 index 00000000..29ec5aa6 --- /dev/null +++ b/src/stores/octavia/flavor.js @@ -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;