LiteSun commented on a change in pull request #550:
URL: https://github.com/apache/apisix-dashboard/pull/550#discussion_r502800765



##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } 
from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+enum Type {
+  roundrobin = 'roundrobin',
+  chash = 'chash',
+}
+
+enum HashOn {
+  vars = 'vars',
+  header = 'header',
+  cookie = 'cookie',
+  consumer = 'consumer',
+}
+
+enum HashKey {
+  remote_addr = 'remote_addr',
+  host = 'host',
+  uri = 'uri',
+  server_name = 'server_name',
+  server_addr = 'server_addr',
+  request_uri = 'request_uri',
+  query_string = 'query_string',
+  remote_port = 'remote_port',
+  hostname = 'hostname',
+  arg_id = 'arg_id',
+}
+
+type Upstream = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} 
max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} 
max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 
'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 
'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>

Review comment:
       should use the same code style.`marginBottom: 10`

##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } 
from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+enum Type {
+  roundrobin = 'roundrobin',
+  chash = 'chash',
+}
+
+enum HashOn {
+  vars = 'vars',
+  header = 'header',
+  cookie = 'cookie',
+  consumer = 'consumer',
+}
+
+enum HashKey {
+  remote_addr = 'remote_addr',
+  host = 'host',
+  uri = 'uri',
+  server_name = 'server_name',
+  server_addr = 'server_addr',
+  request_uri = 'request_uri',
+  query_string = 'query_string',
+  remote_port = 'remote_port',
+  hostname = 'hostname',
+  arg_id = 'arg_id',
+}
+
+type Upstream = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} 
max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} 
max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 
'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 
'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>
+                  <Col span={10}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <Input
+                        placeholder={formatMessage({
+                          id: 
'upstream.step.input.healthy.checks.active.req_headers',
+                        })}
+                        disabled={readonly}
+                      />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,
+                      display: 'flex',
+                      alignItems: 'center',
+                    }}
+                  >
+                    {!readonly && fields.length > 1 && (
+                      <MinusCircleOutlined
+                        style={{ margin: '0 8px' }}

Review comment:
       same

##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } 
from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+enum Type {
+  roundrobin = 'roundrobin',
+  chash = 'chash',
+}
+
+enum HashOn {
+  vars = 'vars',
+  header = 'header',
+  cookie = 'cookie',
+  consumer = 'consumer',
+}
+
+enum HashKey {
+  remote_addr = 'remote_addr',
+  host = 'host',
+  uri = 'uri',
+  server_name = 'server_name',
+  server_addr = 'server_addr',
+  request_uri = 'request_uri',
+  query_string = 'query_string',
+  remote_port = 'remote_port',
+  hostname = 'hostname',
+  arg_id = 'arg_id',
+}
+
+type Upstream = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} 
max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} 
max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 
'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 
'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>
+                  <Col span={10}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <Input
+                        placeholder={formatMessage({
+                          id: 
'upstream.step.input.healthy.checks.active.req_headers',
+                        })}
+                        disabled={readonly}
+                      />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,
+                      display: 'flex',
+                      alignItems: 'center',
+                    }}
+                  >
+                    {!readonly && fields.length > 1 && (
+                      <MinusCircleOutlined
+                        style={{ margin: '0 8px' }}
+                        onClick={() => {
+                          remove(field.name);
+                        }}
+                      />
+                    )}
+                  </Col>
+                </Row>
+              </Form.Item>
+            ))}
+            {!readonly && (
+              <Form.Item wrapperCol={{ offset: 3 }}>
+                <Button type="dashed" onClick={() => add()}>
+                  <PlusOutlined />
+                  创建请求头
+                </Button>
+              </Form.Item>
+            )}
+          </>
+        )}
+      </Form.List>
+    </>
+  );
+  const InActiveHealthCheck = () => (
+    <>
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.List name={['checks', 'passive', 'healthy', 'http_statuses']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                required
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 
'upstream.step.healthy.checks.passive.http_statuses' })
+                }
+                labelCol={{ span: index === 0 ? 3 : 0 }}
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }}>

Review comment:
       ,

##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } 
from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+enum Type {
+  roundrobin = 'roundrobin',
+  chash = 'chash',
+}
+
+enum HashOn {
+  vars = 'vars',
+  header = 'header',
+  cookie = 'cookie',
+  consumer = 'consumer',
+}
+
+enum HashKey {
+  remote_addr = 'remote_addr',
+  host = 'host',
+  uri = 'uri',
+  server_name = 'server_name',
+  server_addr = 'server_addr',
+  request_uri = 'request_uri',
+  query_string = 'query_string',
+  remote_port = 'remote_port',
+  hostname = 'hostname',
+  arg_id = 'arg_id',
+}
+
+type Upstream = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} 
max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} 
max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                
/(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 
'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 
'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 
'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 
'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 
'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 
'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>
+                  <Col span={10}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <Input
+                        placeholder={formatMessage({
+                          id: 
'upstream.step.input.healthy.checks.active.req_headers',
+                        })}
+                        disabled={readonly}
+                      />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,
+                      display: 'flex',
+                      alignItems: 'center',
+                    }}
+                  >
+                    {!readonly && fields.length > 1 && (
+                      <MinusCircleOutlined
+                        style={{ margin: '0 8px' }}
+                        onClick={() => {
+                          remove(field.name);
+                        }}
+                      />
+                    )}
+                  </Col>
+                </Row>
+              </Form.Item>
+            ))}
+            {!readonly && (
+              <Form.Item wrapperCol={{ offset: 3 }}>
+                <Button type="dashed" onClick={() => add()}>
+                  <PlusOutlined />
+                  创建请求头
+                </Button>
+              </Form.Item>
+            )}
+          </>
+        )}
+      </Form.List>
+    </>
+  );
+  const InActiveHealthCheck = () => (
+    <>
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.List name={['checks', 'passive', 'healthy', 'http_statuses']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                required
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 
'upstream.step.healthy.checks.passive.http_statuses' })
+                }
+                labelCol={{ span: index === 0 ? 3 : 0 }}
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }}>
+                  <Col span={2}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <InputNumber disabled={readonly} />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,

Review comment:
       Extracts the same style as a variable.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to