Skip to content

Commit

Permalink
fix: show form when not in None
Browse files Browse the repository at this point in the history
  • Loading branch information
juzhiyuan committed Apr 17, 2021
1 parent 1e2e0b7 commit e810ab9
Show file tree
Hide file tree
Showing 19 changed files with 223 additions and 240 deletions.
133 changes: 69 additions & 64 deletions web/src/components/Upstream/UpstreamForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Divider, Form, Switch } from 'antd';
import { Divider, Form, notification, Switch } from 'antd';
import React, { useState, forwardRef, useImperativeHandle, useEffect } from 'react';
import { useIntl } from 'umi';
import type { FormInstance } from 'antd/es/form';

import { PanelSection } from '@api7-dashboard/ui';
import { transformRequest } from '@/pages/Upstream/transform';
import PassiveCheck from './components/passive-check';
import ActiveCheck from './components/active-check'
import Nodes from './components/Nodes'
Expand All @@ -31,7 +30,7 @@ import UpstreamSelector from './components/UpstreamSelector';
import Retries from './components/Retries';
import PassHost from './components/PassHost';
import TLSComponent from './components/TLS';
import { transformUpstreamDataFromRequest } from './service';
import { convertToRequestData } from './service';

type Upstream = {
name?: string;
Expand All @@ -48,12 +47,15 @@ type Props = {
required?: boolean;
};

/**
* UpstreamForm is used to reuse Upstream Form UI,
* before using this component, we need to execute the following command:
* form.setFieldsValue(convertToFormData(VALUE_FROM_API))
*/
const UpstreamForm: React.FC<Props> = forwardRef(
({ form, disabled, list = [], showSelector, required = true }, ref) => {
({ form, disabled = false, list = [], showSelector = false, required = true }, ref) => {
const { formatMessage } = useIntl();
const [readonly, setReadonly] = useState(
Boolean(form.getFieldValue('upstream_id')) || disabled,
);
const [readonly, setReadonly] = useState(false);
const [hiddenForm, setHiddenForm] = useState(false);

const timeoutFields = [
Expand All @@ -75,41 +77,61 @@ const UpstreamForm: React.FC<Props> = forwardRef(
];

useImperativeHandle(ref, () => ({
getData: () => transformRequest(form.getFieldsValue()),
getData: () => convertToRequestData(form.getFieldsValue()),
}));

useEffect(() => {
const formData = transformRequest(form.getFieldsValue()) || {};
const { upstream_id } = form.getFieldsValue();
const resetForm = (upstream_id: string) => {
if (upstream_id === undefined) {
return
}

setReadonly(!["Custom", "None"].includes(upstream_id) || disabled);

// NOTE: When editing Upstream under the `/upstream/:id/edit` page
if (disabled === false) {
setReadonly(false)
}

/**
* upstream_id === None <==> required === false
* No need to bind Upstream object.
* When creating Route and binds with a Service, no need to configure Upstream in Route.
*/
if (upstream_id === 'None') {
setHiddenForm(true);
if (required) {
requestAnimationFrame(() => {
form.resetFields();
setHiddenForm(false);
});
}
} else {
if (upstream_id) {
requestAnimationFrame(() => {
const targetData = list.find((item) => item.id === upstream_id) as UpstreamComponent.ResponseData
if (targetData) {
form.setFieldsValue(transformUpstreamDataFromRequest(targetData));
} else {
// TODO: 提示 upstream_id 找不到想要的数据
}
});
}
if (!required && !Object.keys(formData).length) {
requestAnimationFrame(() => {
form.setFieldsValue({ upstream_id: 'None' });
setHiddenForm(true);
});
}
form.resetFields()
form.setFieldsValue({ upstream_id: 'None' })
return
}
setReadonly(Boolean(upstream_id) || disabled);
}, [list]);

setHiddenForm(false)

// NOTE: Use Ant Design's form object to set data automatically
if (upstream_id === "Custom") {
return
}

// NOTE: Set data from Upstream List (Upstream Selector)
if (list.length === 0) {
return
}
form.resetFields()
const targetData = list.find((item) => item.id === upstream_id) as UpstreamComponent.ResponseData
if (targetData) {
form.setFieldsValue(targetData);
}
}

/**
* upstream_id
* - None: No need to bind Upstream to a resource (e.g Service).
* - Custom: Users could input values on UpstreamForm
* - Upstream ID from API
*/
useEffect(() => {
const upstream_id = form.getFieldValue('upstream_id');
resetForm(upstream_id)
}, [form.getFieldValue('upstream_id'), list]);

const ActiveHealthCheck = () => (
<React.Fragment>
Expand Down Expand Up @@ -194,19 +216,26 @@ const UpstreamForm: React.FC<Props> = forwardRef(
}
</Form.Item>
<Divider orientation="left" plain />
<Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive' })} name={['custom', 'checks', 'passive']} valuePropName="checked">
<Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive' })} name={['custom', 'checks', 'passive']} valuePropName="checked" tooltip={formatMessage({ id: 'component.upstream.other.health-check.passive-only' })}>
<Switch disabled={readonly} />
</Form.Item>
<Form.Item shouldUpdate noStyle>
<Form.Item shouldUpdate={(prev, next) => prev.custom?.checks?.passive !== next.custom?.checks?.passive} noStyle>
{
() => {
const passive = form.getFieldValue(['custom', 'checks', 'passive'])
const active = form.getFieldValue(['custom', 'checks', 'active'])
if (passive) {
/*
* When enable passive check, we should enable active check, too.
* When we use form.setFieldsValue to enable active check, error throws.
* We choose to alert users first, and need users to enable active check manually.
*/
if (!active) {
notification.warn({
message: formatMessage({ id: 'component.upstream.other.health-check.invalid' }),
description: formatMessage({ id: 'component.upstream.other.health-check.passive-only' })
})
}
return <PassiveHealthCheck />
}
return null
Expand All @@ -227,32 +256,8 @@ const UpstreamForm: React.FC<Props> = forwardRef(
list={list}
disabled={disabled}
required={required}
shouldUpdate={(prev, next) => {
setReadonly(Boolean(next.upstream_id));
if (prev.upstream_id !== next.upstream_id) {
const id = next.upstream_id;
if (id) {
const targetData = list.find((item) => item.id === id) as UpstreamComponent.ResponseData
if (targetData) {
form.setFieldsValue(transformUpstreamDataFromRequest(targetData));
}
form.setFieldsValue({
upstream_id: id,
});
}
}
return prev.upstream_id !== next.upstream_id;
}}
onChange={(upstream_id) => {
setReadonly(Boolean(upstream_id));
setHiddenForm(Boolean(upstream_id === 'None'));
const targetData = list.find((item) => item.id === upstream_id) as UpstreamComponent.ResponseData
if (targetData) {
form.setFieldsValue(transformUpstreamDataFromRequest(targetData));
}
if (upstream_id === '') {
form.resetFields();
}
onChange={(nextUpstreamId) => {
resetForm(nextUpstreamId);
}}
/>
)}
Expand Down
10 changes: 9 additions & 1 deletion web/src/components/Upstream/components/PassHost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/
import React from 'react'
import { Form, Input, Select } from 'antd'
import { Form, Input, notification, Select } from 'antd'
import { useIntl } from 'umi'
import type { FormInstance } from 'antd/lib/form'

Expand Down Expand Up @@ -79,6 +79,14 @@ const Component: React.FC<Props> = ({ form, readonly }) => {
</Form.Item>
);
}

if (form.getFieldValue('pass_host') === 'node' && (form.getFieldValue('nodes') || []).length !== 1) {
notification.warning({
message: formatMessage({id: 'component.upstream.other.pass_host-with-multiple-nodes.title'}),
description: formatMessage({id: 'component.upstream.other.pass_host-with-multiple-nodes'})
})
form.setFieldsValue({pass_host: 'pass'})
}
return null;
}}
</Form.Item>
Expand Down
10 changes: 4 additions & 6 deletions web/src/components/Upstream/components/UpstreamSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,16 @@ type Props = {
list?: Upstream[];
disabled?: boolean;
required?: boolean;
shouldUpdate: (prev: any, next: any) => void;
onChange: (id: string) => void
}

const Component: React.FC<Props> = ({ shouldUpdate, onChange, list = [], disabled, required }) => {
const UpstreamSelector: React.FC<Props> = ({ onChange, list = [], disabled, required }) => {
const { formatMessage } = useIntl()

return (
<Form.Item
label={formatMessage({ id: 'page.upstream.step.select.upstream' })}
name="upstream_id"
shouldUpdate={shouldUpdate as any}
>
<Select
showSearch
Expand All @@ -49,11 +47,11 @@ const Component: React.FC<Props> = ({ shouldUpdate, onChange, list = [], disable
item?.children.toLowerCase().includes(input.toLowerCase())
}
>
{Boolean(!required) && <Select.Option value={'None'}>None</Select.Option>}
<Select.Option value="None" disabled={required}>{formatMessage({id: 'component.upstream.other.none'})}</Select.Option>
{[
{
name: formatMessage({ id: 'page.upstream.step.select.upstream.select.option' }),
id: '',
id: 'Custom',
},
...list,
].map((item) => (
Expand All @@ -66,4 +64,4 @@ const Component: React.FC<Props> = ({ shouldUpdate, onChange, list = [], disable
)
}

export default Component
export default UpstreamSelector
6 changes: 6 additions & 0 deletions web/src/components/Upstream/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,10 @@ export default {

'component.upstream.fields.checks.passive.unhealthy.timeouts': 'Timeouts',
'component.upstream.fields.checks.passive.unhealthy.timeouts.tooltip': 'Number of timeouts in proxied traffic to consider a target unhealthy, as observed by passive health checks.',

'component.upstream.other.none': 'None',
'component.upstream.other.pass_host-with-multiple-nodes.title': 'Please check the target node configuration',
'component.upstream.other.pass_host-with-multiple-nodes': 'When using a host name or IP in the target node list, make sure there is only one target node',
'component.upstream.other.health-check.passive-only': 'When passive health check is enabled, active health check needs to be enabled at the same time.',
'component.upstream.other.health-check.invalid': 'Please check the health check configuration',
}
6 changes: 6 additions & 0 deletions web/src/components/Upstream/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,10 @@ export default {

'component.upstream.fields.checks.passive.unhealthy.timeouts': '超时时间',
'component.upstream.fields.checks.passive.unhealthy.timeouts.tooltip': '根据被动健康检查的观察,在代理中认为目标不健康的超时次数。',

'component.upstream.other.none': '不选择(仅在绑定服务时可用)',
'component.upstream.other.pass_host-with-multiple-nodes.title': '请检查目标节点配置',
'component.upstream.other.pass_host-with-multiple-nodes': '当使用目标节点列表中的主机名或者 IP 时,请确认只有一个目标节点',
'component.upstream.other.health-check.passive-only': '启用被动健康检查时,需要同时启用主动健康检查。',
'component.upstream.other.health-check.invalid': '请检查健康检查配置',
}
91 changes: 89 additions & 2 deletions web/src/components/Upstream/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cloneDeep from 'lodash/cloneDeep'
import { notification } from 'antd';
import { isNil, omitBy, omit, pick, cloneDeep } from 'lodash';
import { formatMessage, request } from 'umi';

/**
* Because we have some `custom` field in Upstream Form, like custom.tls/custom.checks.active etc,
* we need to transform data that doesn't have `custom` field to data contains `custom` field
*/
export const transformUpstreamDataFromRequest = (originData: UpstreamComponent.ResponseData) => {
export const convertToFormData = (originData: UpstreamComponent.ResponseData) => {
if (originData === undefined) {
// NOTE: When binding Service without Upstream configuration (None), originData === undefined
return undefined
}

const data = cloneDeep(originData)
data.custom = {}
data.upstream_id = "Custom"

if (data.checks) {
data.custom.checks = {}
Expand All @@ -40,5 +48,84 @@ export const transformUpstreamDataFromRequest = (originData: UpstreamComponent.R
data.custom.tls = "enable"
}

if (data.id) {
data.upstream_id = data.id;
}

return data
}

/**
* Transform Upstream Form data from custom data to API needed data
*/
export const convertToRequestData = (
formData: UpstreamModule.RequestBody,
): UpstreamModule.RequestBody | undefined | { upstream_id: string } => {
let data = omitBy(formData, isNil) as UpstreamModule.RequestBody;
data = omit(data, 'custom');

const {
type,
hash_on,
key,
k8s_deployment_info,
nodes,
pass_host,
upstream_host,
upstream_id = "Custom",
checks
} = data;

if (!["Custom", "None"].includes(upstream_id)) {
return { upstream_id };
}

data = omit(data, "upstream_id") as any

if (nodes && k8s_deployment_info) {
return undefined;
}

if (!nodes && !k8s_deployment_info) {
return undefined;
}

if (type === 'chash') {
if (!hash_on) {
return undefined;
}

if (hash_on !== 'consumer' && !key) {
return undefined;
}
}

if (pass_host === 'rewrite' && !upstream_host) {
return undefined;
}

if (checks?.passive && !checks.active) {
notification.error({
message: formatMessage({id: 'component.upstream.other.health-check.invalid'}),
description: formatMessage({id: 'component.upstream.other.health-check.passive-only'})
})
return undefined
}

if (nodes) {
// NOTE: https://github.com/ant-design/ant-design/issues/27396
data.nodes = data.nodes?.map((item) => {
return pick(item, ['host', 'port', 'weight']);
});
return data;
}

return undefined;
};

export const fetchUpstreamList = () => {
return request<Res<ResListData<UpstreamComponent.ResponseData>>>('/upstreams').then(({ data }) => ({
data: data.rows.map(row => convertToFormData(row)),
total: data.total_size,
}));
};
Loading

0 comments on commit e810ab9

Please sign in to comment.