You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2021/04/12 07:55:59 UTC

[apisix-dashboard] branch master updated: chore: refactor upstream form module (#1726)

This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new eede893  chore: refactor upstream form module (#1726)
eede893 is described below

commit eede8939499a758fa8aa489eb34f49363b889bea
Author: 琚致远 <ju...@apache.org>
AuthorDate: Mon Apr 12 15:55:10 2021 +0800

    chore: refactor upstream form module (#1726)
---
 web/cypress/fixtures/export-route-dataset.json     |   6 +-
 web/src/components/Plugin/UI/plugin.tsx            |   2 +-
 web/src/components/Upstream/UpstreamForm.tsx       | 761 +++------------------
 web/src/components/Upstream/components/Nodes.tsx   | 125 ++++
 .../components/Upstream/components/PassHost.tsx    |  88 +++
 .../plugin.tsx => Upstream/components/Retries.tsx} |  41 +-
 web/src/components/Upstream/components/Scheme.tsx  |  62 ++
 .../components/TimeUnit.tsx}                       |  29 +-
 .../plugin.tsx => Upstream/components/Timeout.tsx} |  53 +-
 web/src/components/Upstream/components/Type.tsx    | 112 +++
 .../Upstream/components/UpstreamSelector.tsx       |  69 ++
 .../components/active-check/Healthy/Interval.tsx   |  54 ++
 .../components/active-check/Healthy/Successes.tsx  |  50 ++
 .../components/active-check/Healthy/index.ts}      |  31 +-
 .../Upstream/components/active-check/Host.tsx      |  61 ++
 .../Upstream/components/active-check/HttpPath.tsx  |  55 ++
 .../components/active-check/Port.tsx}              |  45 +-
 .../components/active-check/ReqHeaders.tsx         |  81 +++
 .../components/active-check/Timeout.tsx}           |  39 +-
 .../components/active-check/Type.tsx}              |  57 +-
 .../active-check/Unhealthy/HttpFailures.tsx        |  52 ++
 .../active-check/Unhealthy/HttpStatuses.tsx        |  74 ++
 .../components/active-check/Unhealthy/Interval.tsx |  54 ++
 .../active-check/Unhealthy/Timeouts.tsx}           |  40 +-
 .../components/active-check/Unhealthy/index.ts}    |  35 +-
 .../components/active-check/index.ts}              |  42 +-
 .../passive-check/Healthy/HttpStatuses.tsx         |  76 ++
 .../components/passive-check/Healthy/Successes.tsx |  49 ++
 .../components/passive-check/Healthy/index.ts}     |  31 +-
 .../components/passive-check/Type.tsx}             |  57 +-
 .../passive-check/Unhealthy/HttpFailures.tsx       |  52 ++
 .../passive-check/Unhealthy/HttpStatuses.tsx       |  75 ++
 .../passive-check/Unhealthy/TcpFailures.tsx        |  51 ++
 .../passive-check/Unhealthy/Timeouts.tsx}          |  40 +-
 .../components/passive-check/Unhealthy/index.ts}   |  35 +-
 .../components/passive-check/index.ts}             |  33 +-
 web/src/components/Upstream/constant.ts            |  56 +-
 web/src/pages/Route/locales/zh-CN.ts               |   2 +-
 web/src/pages/Upstream/locales/en-US.ts            |   6 +-
 web/src/pages/Upstream/locales/zh-CN.ts            |   8 +-
 40 files changed, 1642 insertions(+), 1047 deletions(-)

diff --git a/web/cypress/fixtures/export-route-dataset.json b/web/cypress/fixtures/export-route-dataset.json
index 133971f..15f70fc 100644
--- a/web/cypress/fixtures/export-route-dataset.json
+++ b/web/cypress/fixtures/export-route-dataset.json
@@ -38,13 +38,13 @@
                 "weight": 1
               }
             ],
-            "retries": 1,
             "timeout": {
               "connect": 6,
               "read": 6,
               "send": 6
             },
             "type": "roundrobin",
+            "scheme": "http",
             "pass_host": "pass"
           }
         }
@@ -90,13 +90,13 @@
                 "weight": 1
               }
             ],
-            "retries": 1,
             "timeout": {
               "connect": 6,
               "read": 6,
               "send": 6
             },
             "type": "roundrobin",
+            "scheme": "http",
             "pass_host": "pass"
           }
         }
@@ -141,13 +141,13 @@
                 "weight": 1
               }
             ],
-            "retries": 1,
             "timeout": {
               "connect": 6,
               "read": 6,
               "send": 6
             },
             "type": "roundrobin",
+            "scheme": "http",
             "pass_host": "pass"
           }
         }
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Plugin/UI/plugin.tsx
index f13b31a..620f98b 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Plugin/UI/plugin.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 import React from 'react';
-import { FormInstance } from 'antd/es/form';
+import type { FormInstance } from 'antd/es/form';
 import { Empty } from 'antd';
 import { useIntl } from 'umi';
 
diff --git a/web/src/components/Upstream/UpstreamForm.tsx b/web/src/components/Upstream/UpstreamForm.tsx
index 2d54f75..7fade14 100644
--- a/web/src/components/Upstream/UpstreamForm.tsx
+++ b/web/src/components/Upstream/UpstreamForm.tsx
@@ -14,8 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
-import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } from 'antd';
+import { Divider, Form, Switch } from 'antd';
 import React, { useState, forwardRef, useImperativeHandle, useEffect } from 'react';
 import { useIntl } from 'umi';
 import type { FormInstance } from 'antd/es/form';
@@ -23,32 +22,15 @@ import type { FormInstance } from 'antd/es/form';
 import { PanelSection } from '@api7-dashboard/ui';
 import { transformRequest } from '@/pages/Upstream/transform';
 import { DEFAULT_UPSTREAM } from './constant';
-
-enum Type {
-  roundrobin = 'roundrobin',
-  chash = 'chash',
-  ewma = 'ewma',
-}
-
-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',
-}
+import PassiveCheck from './components/passive-check';
+import ActiveCheck from './components/active-check'
+import Nodes from './components/Nodes'
+import Scheme from './components/Scheme';
+import Timeout from './components/Timeout';
+import Type from './components/Type';
+import UpstreamSelector from './components/UpstreamSelector';
+import Retries from './components/Retries';
+import PassHost from './components/PassHost';
 
 type Upstream = {
   name?: string;
@@ -65,12 +47,6 @@ type Props = {
   required?: boolean;
 };
 
-const removeBtnStyle = {
-  marginLeft: 20,
-  display: 'flex',
-  alignItems: 'center',
-};
-
 const UpstreamForm: React.FC<Props> = forwardRef(
   ({ form, disabled, list = [], showSelector, required = true }, ref) => {
     const { formatMessage } = useIntl();
@@ -130,487 +106,102 @@ const UpstreamForm: React.FC<Props> = forwardRef(
       setReadonly(Boolean(upstream_id) || disabled);
     }, [list]);
 
-    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' }}>s</span>;
-    const NodeList = () => (
-      <Form.List name="nodes">
-        {(fields, { add, remove }) => (
-          <>
-            <Form.Item label={formatMessage({ id: 'page.upstream.form.item-label.node.domain.or.ip' })}>
-              {fields.map((field, index) => (
-                <Row style={{ marginBottom: 10 }} gutter={16} key={index}>
-                  <Col span={5}>
-                    <Form.Item
-                      style={{ marginBottom: 0 }}
-                      name={[field.name, 'host']}
-                      rules={[
-                        {
-                          required: true,
-                          message: formatMessage({
-                            id: 'page.upstream.step.input.domain.name.or.ip',
-                          }),
-                        },
-                        {
-                          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={formatMessage({ id: 'page.upstream.step.domain.name.or.ip' })}
-                        disabled={readonly}
-                      />
-                    </Form.Item>
-                  </Col>
-                  <Col span={4}>
-                    <Form.Item
-                      style={{ marginBottom: 0 }}
-                      name={[field.name, 'port']}
-                      label={formatMessage({ id: 'page.upstream.step.port' })}
-                      rules={[
-                        {
-                          required: true,
-                          message: formatMessage({ id: 'page.upstream.step.input.port' }),
-                        },
-                      ]}
-                    >
-                      <InputNumber
-                        placeholder={formatMessage({ id: 'page.upstream.step.port' })}
-                        disabled={readonly}
-                        min={0}
-                        max={65535}
-                      />
-                    </Form.Item>
-                  </Col>
-                  <Col span={5}>
-                    <Form.Item
-                      style={{ marginBottom: 0 }}
-                      name={[field.name, 'weight']}
-                      label={formatMessage({ id: 'page.upstream.step.weight' })}
-                      rules={[
-                        {
-                          required: true,
-                          message: formatMessage({ id: 'page.upstream.step.input.weight' }),
-                        },
-                      ]}
-                    >
-                      <InputNumber
-                        placeholder={formatMessage({ id: 'page.upstream.step.weight' })}
-                        disabled={readonly}
-                        min={0}
-                        max={1000}
-                      />
-                    </Form.Item>
-                  </Col>
-                  <Col style={{ ...removeBtnStyle, marginLeft: -25 }}>
-                    {!readonly && (
-                      <MinusCircleOutlined onClick={() => remove(field.name)} />
-                    )}
-                  </Col>
-                </Row>
-              ))}
-            </Form.Item>
-            {!readonly && (
-              <Form.Item wrapperCol={{ offset: 3 }}>
-                <Button type="dashed" onClick={add}>
-                  <PlusOutlined />
-                  {formatMessage({ id: 'page.upstream.step.create.node' })}
-                </Button>
-              </Form.Item>
-            )}
-          </>
-        )}
-      </Form.List>
-    );
 
     const ActiveHealthCheck = () => (
-      <>
-        <Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.active.timeout' })} tooltip={formatMessage({ id: 'page.upstream.checks.active.timeout.description' })}>
-          <Form.Item name={['checks', 'active', 'timeout']} noStyle>
-            <InputNumber disabled={readonly} min={0} />
-          </Form.Item>
-          <TimeUnit />
-        </Form.Item>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.activeHost' })}
-          required
-          tooltip={formatMessage({ id: 'page.upstream.checks.active.host.description' })}
-        >
-          <Form.Item
-            style={{ marginBottom: 0 }}
-            name={['checks', 'active', 'host']}
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'page.upstream.step.input.healthyCheck.activeHost' }),
-              },
-              {
-                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: 'page.upstream.step.domain.name.or.ip.rule' }),
-              },
-            ]}
-          >
-            <Input
-              placeholder={formatMessage({
-                id: 'page.upstream.step.input.healthyCheck.activeHost',
-              })}
-              disabled={readonly}
-            />
-          </Form.Item>
-        </Form.Item>
-
-        <Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.activePort' })}>
-          <Form.Item name={['checks', 'active', 'port']} noStyle>
-            <InputNumber
-              placeholder={formatMessage({
-                id: 'page.upstream.step.input.healthyCheck.activePort',
-              })}
-              disabled={readonly}
-              min={0}
-              max={65535}
-            />
-          </Form.Item>
-        </Form.Item>
-
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.active.http_path' })}
-          required
-          tooltip={formatMessage({
-            id: 'page.upstream.step.input.healthyCheck.active.http_path',
-          })}
-        >
-          <Form.Item
-            name={['checks', 'active', 'http_path']}
-            noStyle
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'page.upstream.checks.active.http_path.placeholder' }),
-              },
-            ]}
-          >
-            <Input
-              disabled={readonly}
-              placeholder={formatMessage({ id: 'page.upstream.checks.active.http_path.placeholder' })}
-            />
-          </Form.Item>
-        </Form.Item>
+      <React.Fragment>
+        <ActiveCheck.Type readonly={readonly} />
+        <ActiveCheck.Timeout readonly={readonly} />
+        <ActiveCheck.Host readonly={readonly} />
+        <ActiveCheck.Port readonly={readonly} />
+        <ActiveCheck.HttpPath readonly={readonly} />
 
         <Divider orientation="left" plain>
           {formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.status' })}
         </Divider>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.activeInterval' })}
-          required
-          tooltip={formatMessage({ id: 'page.upstream.checks.active.healthy.interval.description' })}
-        >
-          <Form.Item
-            noStyle
-            style={{ marginBottom: 0 }}
-            name={['checks', 'active', 'healthy', 'interval']}
-            rules={[
-              {
-                required: true,
-                message: formatMessage({
-                  id: 'page.upstream.step.input.healthyCheck.activeInterval',
-                }),
-              },
-            ]}
-          >
-            <InputNumber disabled={readonly} min={1} />
-          </Form.Item>
-          <TimeUnit />
-        </Form.Item>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.successes' })}
-          required
-          tooltip={formatMessage({ id: 'page.upstream.checks.active.healthy.successes.description' })}
-        >
-          <Form.Item
-            name={['checks', 'active', 'healthy', 'successes']}
-            noStyle
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'page.upstream.step.input.healthyCheck.successes' }),
-              },
-            ]}
-          >
-            <InputNumber disabled={readonly} min={1} max={254} />
-          </Form.Item>
-        </Form.Item>
+
+        <ActiveCheck.Healthy.Interval readonly={readonly} />
+        <ActiveCheck.Healthy.Successes readonly={readonly} />
 
         <Divider orientation="left" plain>
           {formatMessage({ id: 'page.upstream.step.healthyCheck.unhealthyStatus' })}
         </Divider>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.activeInterval' })}
-          required
-          tooltip={formatMessage({ id: 'page.upstream.checks.active.unhealthy.interval.description' })}
-        >
-          <Form.Item
-            name={['checks', 'active', 'unhealthy', 'interval']}
-            noStyle
-            rules={[
-              {
-                required: true,
-                message: formatMessage({
-                  id: 'page.upstream.step.input.healthyCheck.activeInterval',
-                }),
-              },
-            ]}
-          >
-            <InputNumber disabled={readonly} min={1} />
-          </Form.Item>
-          <TimeUnit />
-        </Form.Item>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.http_failures' })}
-          required
-          tooltip={formatMessage({ id: 'page.upstream.checks.active.unhealthy.http_failures.description' })}
-        >
-          <Form.Item
-            name={['checks', 'active', 'unhealthy', 'http_failures']}
-            noStyle
-            rules={[
-              {
-                required: true,
-                message: formatMessage({
-                  id: 'page.upstream.step.input.healthyCheck.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: 'page.upstream.step.healthyCheck.active.req_headers' })
-                  }
-                  wrapperCol={{ offset: index === 0 ? 0 : 3 }}
-                >
-                  <Row style={{ marginBottom: 10 }} gutter={16}>
-                    <Col span={10}>
-                      <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
-                        <Input
-                          placeholder={formatMessage({
-                            id: 'page.upstream.step.input.healthyCheck.active.req_headers',
-                          })}
-                          disabled={readonly}
-                        />
-                      </Form.Item>
-                    </Col>
-                    <Col style={removeBtnStyle}>
-                      {!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 />
-                    {formatMessage({
-                      id: 'page.upstream.step.healthyCheck.active.create.req_headers',
-                    })}
-                  </Button>
-                </Form.Item>
-              )}
-            </>
-          )}
-        </Form.List>
-      </>
+
+        <ActiveCheck.Unhealthy.Timeouts readonly={readonly} />
+        <ActiveCheck.Unhealthy.Interval readonly={readonly} />
+        <ActiveCheck.Unhealthy.HttpStatuses readonly={readonly} />
+        <ActiveCheck.Unhealthy.HttpFailures readonly={readonly} />
+
+        <Divider orientation="left" plain>Others</Divider>
+
+        <ActiveCheck.ReqHeaders readonly={readonly} />
+      </React.Fragment>
     );
+
     const InActiveHealthCheck = () => (
-      <>
+      <React.Fragment>
+        <PassiveCheck.Type readonly={readonly} />
+
         <Divider orientation="left" plain>
           {formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.status' })}
         </Divider>
-        <Form.List name={['checks', 'passive', 'healthy', 'http_statuses']}>
-          {(fields, { add, remove }) => (
-            <>
-              <Form.Item
-                required
-                label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive.http_statuses' })}
-                tooltip={formatMessage({ id: 'page.upstream.checks.passive.healthy.http_statuses.description' })}
-              >
-                {fields.map((field, index) => (
-                  <Row style={{ marginBottom: 10 }} key={index}>
-                    <Col span={2}>
-                      <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
-                        <InputNumber disabled={readonly} min={200} max={599} />
-                      </Form.Item>
-                    </Col>
-                    <Col style={removeBtnStyle}>
-                      {!readonly && (
-                        <MinusCircleOutlined
-                          onClick={() => {
-                            remove(field.name);
-                          }}
-                        />
-                      )}
-                    </Col>
-                  </Row>
-                ))}
-              </Form.Item>
-              {!readonly && (
-                <Form.Item wrapperCol={{ offset: 3 }}>
-                  <Button type="dashed" onClick={() => add()}>
-                    <PlusOutlined />
-                    {formatMessage({
-                      id: 'page.upstream.step.healthyCheck.passive.create.http_statuses',
-                    })}
-                  </Button>
-                </Form.Item>
-              )}
-            </>
-          )}
-        </Form.List>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.successes' })}
-          tooltip={formatMessage({ id: 'page.upstream.checks.passive.healthy.successes.description' })}
-          required
-        >
-          <Form.Item
-            name={['checks', 'passive', 'healthy', 'successes']}
-            noStyle
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'page.upstream.step.input.healthyCheck.successes' }),
-              },
-            ]}
-          >
-            <InputNumber disabled={readonly} min={1} max={254} />
-          </Form.Item>
-        </Form.Item>
+
+        <PassiveCheck.Healthy.HttpStatuses readonly={readonly} />
+        <PassiveCheck.Healthy.Successes readonly={readonly} />
 
         <Divider orientation="left" plain>
           {formatMessage({ id: 'page.upstream.step.healthyCheck.unhealthyStatus' })}
         </Divider>
-        <Form.List name={['checks', 'passive', 'unhealthy', 'http_statuses']}>
-          {(fields, { add, remove }) => (
+
+        <PassiveCheck.Unhealthy.Timeouts readonly={readonly} />
+        <PassiveCheck.Unhealthy.TcpFailures readonly={readonly} />
+        <PassiveCheck.Unhealthy.HttpFailures readonly={readonly} />
+        <PassiveCheck.Unhealthy.HttpStatuses readonly={readonly} />
+      </React.Fragment>
+    );
+
+    const HealthCheckComponent = () => {
+      const options = [
+        {
+          label: formatMessage({ id: 'page.upstream.step.healthyCheck.active' }),
+          name: ['checks', 'active'],
+          component: (
             <>
-              <Form.Item
-                required
-                label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive.http_statuses' })}
-                tooltip={formatMessage({ id: 'page.upstream.checks.passive.unhealthy.http_statuses.description' })}
-              >
-                {fields.map((field, index) => (
-                  <Row style={{ marginBottom: 10 }} key={index}>
-                    <Col span={2}>
-                      <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
-                        <InputNumber disabled={readonly} min={200} max={599} />
-                      </Form.Item>
-                    </Col>
-                    <Col style={removeBtnStyle}>
-                      {!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 />
-                    {formatMessage({
-                      id: 'page.upstream.step.healthyCheck.passive.create.http_statuses',
-                    })}
-                  </Button>
-                </Form.Item>
-              )}
+              <ActiveHealthCheck />
+              <Divider orientation="left" plain />
             </>
-          )}
-        </Form.List>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.http_failures' })}
-          required
-          tooltip={formatMessage({ id: 'page.upstream.checks.passive.unhealthy.http_failures.description' })}
-        >
-          <Form.Item
-            name={['checks', 'passive', 'unhealthy', 'http_failures']}
-            noStyle
-            rules={[
-              {
-                required: true,
-                message: formatMessage({
-                  id: 'page.upstream.step.input.healthyCheck.http_failures',
-                }),
-              },
-            ]}
-          >
-            <InputNumber disabled={readonly} min={1} max={254} />
-          </Form.Item>
-        </Form.Item>
-        <Form.Item
-          label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive.tcp_failures' })}
-          required
-          tooltip={formatMessage({ id: 'page.upstream.checks.passive.unhealthy.tcp_failures.description' })}
+          ),
+        },
+        {
+          label: formatMessage({ id: 'page.upstream.step.healthyCheck.passive' }),
+          name: ['checks', 'passive'],
+          component: <InActiveHealthCheck />,
+        },
+      ]
+
+      return (
+        <PanelSection
+          title={formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.check' })}
         >
-          <Form.Item
-            name={['checks', 'passive', 'unhealthy', 'tcp_failures']}
-            noStyle
-            rules={[
-              {
-                required: true,
-                message: formatMessage({
-                  id: 'page.upstream.step.input.healthyCheck.passive.tcp_failures',
-                }),
-              },
-            ]}
-          >
-            <InputNumber disabled={readonly} min={1} max={254} />
-          </Form.Item>
-        </Form.Item>
-      </>
-    );
+          {options.map(({ label, name, component }) => (
+            <div key={label}>
+              <Form.Item label={label} name={name} valuePropName="checked" key={label}>
+                <Switch disabled={readonly} />
+              </Form.Item>
+              <Form.Item shouldUpdate noStyle>
+                {() => {
+                  if (form.getFieldValue(name)) {
+                    return component;
+                  }
+                  return null;
+                }}
+              </Form.Item>
+            </div>
+          ))}
+        </PanelSection>
+      )
+    }
+
 
     return (
       <Form
@@ -621,9 +212,10 @@ const UpstreamForm: React.FC<Props> = forwardRef(
         }}
       >
         {showSelector && (
-          <Form.Item
-            label={formatMessage({ id: 'page.upstream.step.select.upstream' })}
-            name="upstream_id"
+          <UpstreamSelector
+            list={list}
+            disabled={disabled}
+            required={required}
             shouldUpdate={(prev, next) => {
               setReadonly(Boolean(next.upstream_id));
               if (prev.upstream_id !== next.upstream_id) {
@@ -637,173 +229,32 @@ const UpstreamForm: React.FC<Props> = forwardRef(
               }
               return prev.upstream_id !== next.upstream_id;
             }}
-          >
-            <Select
-              showSearch
-              data-cy="upstream_selector"
-              disabled={disabled}
-              onChange={(upstream_id) => {
-                setReadonly(Boolean(upstream_id));
-                setHiddenForm(Boolean(upstream_id === 'None'));
-                form.setFieldsValue(list.find((item) => item.id === upstream_id));
-                if (upstream_id === '') {
-                  form.resetFields();
-                  form.setFieldsValue(DEFAULT_UPSTREAM);
-                }
-              }}
-              filterOption={(input, item) =>
-                item?.children.toLowerCase().includes(input.toLowerCase())
+            onChange={(upstream_id) => {
+              setReadonly(Boolean(upstream_id));
+              setHiddenForm(Boolean(upstream_id === 'None'));
+              form.setFieldsValue(list.find((item) => item.id === upstream_id));
+              if (upstream_id === '') {
+                form.resetFields();
+                form.setFieldsValue(DEFAULT_UPSTREAM);
               }
-            >
-              {Boolean(!required) && <Select.Option value={'None'}>None</Select.Option>}
-              {[
-                {
-                  name: formatMessage({ id: 'page.upstream.step.select.upstream.select.option' }),
-                  id: '',
-                },
-                ...list,
-              ].map((item) => (
-                <Select.Option value={item.id!} key={item.id}>
-                  {item.name}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
+            }}
+          />
         )}
 
         {!hiddenForm && (
-          <>
-            <Form.Item
-              label={formatMessage({ id: 'page.upstream.step.type' })}
-              name="type"
-              rules={[{ required: true }]}
-            >
-              <Select disabled={readonly}>
-                {Object.entries(Type).map(([label, value]) => {
-                  return (
-                    <Select.Option value={value} key={value}>
-                      {formatMessage({ id: `page.upstream.type.${label}` })}
-                    </Select.Option>
-                  );
-                })}
-              </Select>
-            </Form.Item>
-            <Form.Item shouldUpdate noStyle>
-              {() => {
-                if (form.getFieldValue('type') === 'chash') {
-                  return <CHash />;
-                }
-                return null;
-              }}
-            </Form.Item>
-            {NodeList()}
-            <Form.Item
-              label={formatMessage({ id: 'page.upstream.step.pass-host' })}
-              name="pass_host"
-            >
-              <Select disabled={readonly}>
-                <Select.Option value="pass">
-                  {formatMessage({ id: 'page.upstream.step.pass-host.pass' })}
-                </Select.Option>
-                <Select.Option value="node">
-                  {formatMessage({ id: 'page.upstream.step.pass-host.node' })}
-                </Select.Option>
-                <Select.Option value="rewrite" disabled>
-                  {formatMessage({ id: 'page.upstream.step.pass-host.rewrite' })}
-                </Select.Option>
-              </Select>
-            </Form.Item>
-
-            <Form.Item
-              label={formatMessage({ id: 'page.upstream.retries' })}
-              name="retries"
-            >
-              <InputNumber
-                disabled={disabled}
-              />
-            </Form.Item>
+          <React.Fragment>
+            <Type form={form} readonly={readonly} />
+            <Nodes readonly={readonly} />
 
-            <Form.Item
-              noStyle
-              shouldUpdate={(prev, next) => {
-                return prev.pass_host !== next.pass_host;
-              }}
-            >
-              {() => {
-                if (form.getFieldValue('pass_host') === 'rewrite') {
-                  return (
-                    <Form.Item
-                      label={formatMessage({ id: 'page.upstream.step.pass-host.upstream_host' })}
-                      name="upstream_host"
-                      rules={[
-                        {
-                          required: true,
-                          message: "",
-                        },
-                      ]}
-                    >
-                      <Input disabled={readonly} placeholder={formatMessage({ id: `page.upstream.upstream_host.required` })} />
-                    </Form.Item>
-                  );
-                }
-                return null;
-              }}
-            </Form.Item>
-
-            {timeoutFields.map(({ label, name, desc = '' }) => (
-              <Form.Item label={label} required key={label} tooltip={desc}>
-                <Form.Item
-                  name={name}
-                  noStyle
-                  rules={[
-                    {
-                      required: true,
-                      message: formatMessage({ id: `page.upstream.step.input.${name[1]}.timeout` }),
-                    },
-                  ]}
-                >
-                  <InputNumber disabled={readonly} />
-                </Form.Item>
-                <TimeUnit />
-              </Form.Item>
+            <PassHost form={form} readonly={readonly} />
+            <Retries readonly={readonly} />
+            <Scheme readonly={readonly} />
+            {timeoutFields.map((item, index) => (
+              <Timeout key={index} {...item} readonly={readonly} />
             ))}
 
-            <PanelSection
-              title={formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.check' })}
-            >
-              {[
-                {
-                  label: formatMessage({ id: 'page.upstream.step.healthyCheck.active' }),
-                  name: ['checks', 'active'],
-                  component: (
-                    <>
-                      <ActiveHealthCheck />
-                      <Divider orientation="left" plain />
-                    </>
-                  ),
-                },
-                {
-                  label: formatMessage({ id: 'page.upstream.step.healthyCheck.passive' }),
-                  name: ['checks', 'passive'],
-                  component: <InActiveHealthCheck />,
-                },
-              ].map(({ label, name, component }) => (
-                <div key={label}>
-                  <Form.Item label={label} name={name} valuePropName="checked" key={label}>
-                    <Switch disabled={readonly} />
-                  </Form.Item>
-                  <Form.Item shouldUpdate noStyle>
-                    {() => {
-                      if (form.getFieldValue(name)) {
-                        return component;
-                      }
-                      return null;
-                    }}
-                  </Form.Item>
-                </div>
-              ))}
-            </PanelSection>
-          </>
+            <HealthCheckComponent />
+          </React.Fragment>
         )}
       </Form>
     );
diff --git a/web/src/components/Upstream/components/Nodes.tsx b/web/src/components/Upstream/components/Nodes.tsx
new file mode 100644
index 0000000..2a92d48
--- /dev/null
+++ b/web/src/components/Upstream/components/Nodes.tsx
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Input, Row, Col, InputNumber, Button } from 'antd'
+import { useIntl } from 'umi'
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
+
+import { removeBtnStyle } from '..'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          <Form.Item label={formatMessage({ id: 'page.upstream.form.item-label.node.domain.or.ip' })} style={{ marginBottom: 0 }}>
+            {fields.map((field, index) => (
+              <Row style={{ marginBottom: 10 }} gutter={16} key={index}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                        message: formatMessage({
+                          id: 'page.upstream.step.input.domain.name.or.ip',
+                        }),
+                      },
+                      {
+                        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={formatMessage({ id: 'page.upstream.step.domain.name.or.ip' })}
+                      disabled={readonly}
+                    />
+                  </Form.Item>
+                </Col>
+                <Col span={4}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    label={formatMessage({ id: 'page.upstream.step.port' })}
+                    rules={[
+                      {
+                        required: true,
+                        message: formatMessage({ id: 'page.upstream.step.input.port' }),
+                      },
+                    ]}
+                  >
+                    <InputNumber
+                      placeholder={formatMessage({ id: 'page.upstream.step.port' })}
+                      disabled={readonly}
+                      min={1}
+                      max={65535}
+                    />
+                  </Form.Item>
+                </Col>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    label={formatMessage({ id: 'page.upstream.step.weight' })}
+                    rules={[
+                      {
+                        required: true,
+                        message: formatMessage({ id: 'page.upstream.step.input.weight' }),
+                      },
+                    ]}
+                  >
+                    <InputNumber
+                      placeholder={formatMessage({ id: 'page.upstream.step.weight' })}
+                      disabled={readonly}
+                      min={0}
+                      max={1000}
+                    />
+                  </Form.Item>
+                </Col>
+                <Col style={{ ...removeBtnStyle, marginLeft: -50 }}>
+                  {!readonly && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            ))}
+          </Form.Item>
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                {formatMessage({ id: 'component.global.add' })}
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/PassHost.tsx b/web/src/components/Upstream/components/PassHost.tsx
new file mode 100644
index 0000000..6102076
--- /dev/null
+++ b/web/src/components/Upstream/components/PassHost.tsx
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Input, Select } from 'antd'
+import { useIntl } from 'umi'
+import type { FormInstance } from 'antd/lib/form'
+
+type Props = {
+  form: FormInstance
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ form, readonly }) => {
+  const { formatMessage } = useIntl()
+
+  const options = [
+    {
+      value: "pass",
+      label: formatMessage({ id: 'page.upstream.step.pass-host.pass' })
+    }, {
+      value: "node",
+      label: formatMessage({ id: 'page.upstream.step.pass-host.node' })
+    }, {
+      value: "rewrite",
+      label: formatMessage({ id: 'page.upstream.step.pass-host.rewrite' }),
+      disabled: true
+    }
+  ]
+
+  return (
+    <React.Fragment>
+      <Form.Item
+        label={formatMessage({ id: 'page.upstream.step.pass-host' })}
+        name="pass_host"
+      >
+        <Select disabled={readonly}>
+          {options.map(item => (
+            <Select.Option value={item.value} key={item.value} disabled={item.disabled}>
+              {item.label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item
+        noStyle
+        shouldUpdate={(prev, next) => {
+          return prev.pass_host !== next.pass_host;
+        }}
+      >
+        {() => {
+          if (form.getFieldValue('pass_host') === 'rewrite') {
+            return (
+              <Form.Item
+                label={formatMessage({ id: 'page.upstream.step.pass-host.upstream_host' })}
+                name="upstream_host"
+                rules={[
+                  {
+                    required: true,
+                    message: "",
+                  },
+                ]}
+              >
+                <Input disabled={readonly} placeholder={formatMessage({ id: `page.upstream.upstream_host.required` })} />
+              </Form.Item>
+            );
+          }
+          return null;
+        }}
+      </Form.Item>
+    </React.Fragment>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/Retries.tsx
similarity index 56%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/Retries.tsx
index f13b31a..a77ecef 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/Retries.tsx
@@ -14,31 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
-
-import BasicAuth from './basic-auth'
+import React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
 
 type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+  readonly?: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.retries' })}
+      name="retries"
+    >
+      <InputNumber
+        disabled={readonly}
+      />
+    </Form.Item>
+  )
 }
+
+export default Component
diff --git a/web/src/components/Upstream/components/Scheme.tsx b/web/src/components/Upstream/components/Scheme.tsx
new file mode 100644
index 0000000..840d5e6
--- /dev/null
+++ b/web/src/components/Upstream/components/Scheme.tsx
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Select } from 'antd'
+import { useIntl } from 'umi'
+
+const options = [
+  {
+    label: "HTTP",
+    value: "http"
+  }, {
+    label: "HTTPs",
+    value: "https"
+  }, {
+    label: "gRPC",
+    value: "grpc"
+  }, {
+    label: "gRPCs",
+    value: "grpcs"
+  }
+]
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.scheme' })}
+      name="scheme"
+      rules={[{ required: true }]}
+    >
+      <Select disabled={readonly}>
+        {options.map(item => {
+          return (
+            <Select.Option value={item.value} key={item.value}>
+              {item.label}
+            </Select.Option>
+          );
+        })}
+      </Select>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/TimeUnit.tsx
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/TimeUnit.tsx
index f13b31a..292c0cf 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/TimeUnit.tsx
@@ -14,31 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import React from 'react'
 
-import BasicAuth from './basic-auth'
+const TimeUnit = () => <span style={{ margin: '0 8px' }}>s</span>;
 
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
-}
-
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
-}
+export default TimeUnit
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/Timeout.tsx
similarity index 52%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/Timeout.tsx
index f13b31a..059b63a 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/Timeout.tsx
@@ -14,31 +14,36 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
 
-import BasicAuth from './basic-auth'
+import TimeUnit from './TimeUnit'
 
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+const Component: React.FC<{
+  label: string;
+  desc: string;
+  name: string[];
+  readonly?: boolean;
+}> = ({ label, desc, name, readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item label={label} required tooltip={desc}>
+      <Form.Item
+        name={name}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({ id: `page.upstream.step.input.${name[1]}.timeout` }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} />
+      </Form.Item>
+      <TimeUnit />
+    </Form.Item>
+  )
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
-}
+export default Component
diff --git a/web/src/components/Upstream/components/Type.tsx b/web/src/components/Upstream/components/Type.tsx
new file mode 100644
index 0000000..0a7ce25
--- /dev/null
+++ b/web/src/components/Upstream/components/Type.tsx
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Select } from 'antd'
+import { useIntl } from 'umi'
+import type { FormInstance } from 'antd/es/form'
+
+enum Type {
+  roundrobin = 'roundrobin',
+  chash = 'chash',
+  ewma = 'ewma',
+  // TODO: new type
+  // least_conn = 'least_conn'
+}
+
+enum HashOn {
+  vars = 'vars',
+  header = 'header',
+  cookie = 'cookie',
+  consumer = 'consumer',
+  // TODO: new hash_on key
+  // vars_combinations = 'vars_combinations'
+}
+
+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 Props = {
+  readonly?: boolean
+  form: FormInstance
+}
+
+const CHash: React.FC<Pick<Props, 'readonly'>> = ({ readonly }) => (
+  <>
+    <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 Component: React.FC<Props> = ({ readonly, form }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <React.Fragment>
+      <Form.Item
+        label={formatMessage({ id: 'page.upstream.step.type' })}
+        name="type"
+        rules={[{ required: true }]}
+      >
+        <Select disabled={readonly}>
+          {Object.entries(Type).map(([label, value]) => {
+            return (
+              <Select.Option value={value} key={value}>
+                {formatMessage({ id: `page.upstream.type.${label}` })}
+              </Select.Option>
+            );
+          })}
+        </Select>
+      </Form.Item>
+      <Form.Item shouldUpdate noStyle>
+        {() => {
+          if (form.getFieldValue('type') === 'chash') {
+            return <CHash readonly={readonly} />;
+          }
+          return null;
+        }}
+      </Form.Item>
+    </React.Fragment>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/UpstreamSelector.tsx b/web/src/components/Upstream/components/UpstreamSelector.tsx
new file mode 100644
index 0000000..1f8b225
--- /dev/null
+++ b/web/src/components/Upstream/components/UpstreamSelector.tsx
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Select } from 'antd'
+import { useIntl } from 'umi'
+
+type Upstream = {
+  name?: string;
+  id?: string;
+};
+
+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 { formatMessage } = useIntl()
+
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.select.upstream' })}
+      name="upstream_id"
+      shouldUpdate={shouldUpdate as any}
+    >
+      <Select
+        showSearch
+        data-cy="upstream_selector"
+        disabled={disabled}
+        onChange={onChange}
+        filterOption={(input, item) =>
+          item?.children.toLowerCase().includes(input.toLowerCase())
+        }
+      >
+        {Boolean(!required) && <Select.Option value={'None'}>None</Select.Option>}
+        {[
+          {
+            name: formatMessage({ id: 'page.upstream.step.select.upstream.select.option' }),
+            id: '',
+          },
+          ...list,
+        ].map((item) => (
+          <Select.Option value={item.id!} key={item.id}>
+            {item.name}
+          </Select.Option>
+        ))}
+      </Select>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/active-check/Healthy/Interval.tsx b/web/src/components/Upstream/components/active-check/Healthy/Interval.tsx
new file mode 100644
index 0000000..47ca56e
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/Healthy/Interval.tsx
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
+import TimeUnit from '../../TimeUnit'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.activeInterval' })}
+      required
+      tooltip={formatMessage({ id: 'page.upstream.checks.active.healthy.interval.description' })}
+    >
+      <Form.Item
+        noStyle
+        style={{ marginBottom: 0 }}
+        name={['checks', 'active', 'healthy', 'interval']}
+        rules={[
+          {
+            required: true,
+            message: formatMessage({
+              id: 'page.upstream.step.input.healthyCheck.activeInterval',
+            }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} min={1} />
+      </Form.Item>
+      <TimeUnit />
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/active-check/Healthy/Successes.tsx b/web/src/components/Upstream/components/active-check/Healthy/Successes.tsx
new file mode 100644
index 0000000..84fbccf
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/Healthy/Successes.tsx
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { Form, InputNumber } from 'antd'
+import React from 'react'
+import { useIntl } from 'umi'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.successes' })}
+      required
+      tooltip={formatMessage({ id: 'page.upstream.checks.active.healthy.successes.description' })}
+    >
+      <Form.Item
+        name={['checks', 'active', 'healthy', 'successes']}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({ id: 'page.upstream.step.input.healthyCheck.successes' }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} min={1} max={254} />
+      </Form.Item>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/active-check/Healthy/index.ts
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/active-check/Healthy/index.ts
index f13b31a..cef27c1 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/active-check/Healthy/index.ts
@@ -14,31 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import Interval from './Interval'
+import Successes from './Successes'
 
-import BasicAuth from './basic-auth'
-
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
-}
-
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+export default {
+  Interval,
+  Successes
 }
diff --git a/web/src/components/Upstream/components/active-check/Host.tsx b/web/src/components/Upstream/components/active-check/Host.tsx
new file mode 100644
index 0000000..871000b
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/Host.tsx
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Input } from 'antd'
+import { useIntl } from 'umi'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.activeHost' })}
+      required
+      tooltip={formatMessage({ id: 'page.upstream.checks.active.host.description' })}
+    >
+      <Form.Item
+        style={{ marginBottom: 0 }}
+        name={['checks', 'active', 'host']}
+        rules={[
+          {
+            required: true,
+            message: formatMessage({ id: 'page.upstream.step.input.healthyCheck.activeHost' }),
+          },
+          {
+            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: 'page.upstream.step.domain.name.or.ip.rule' }),
+          },
+        ]}
+      >
+        <Input
+          placeholder={formatMessage({
+            id: 'page.upstream.step.input.healthyCheck.activeHost',
+          })}
+          disabled={readonly}
+        />
+      </Form.Item>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/active-check/HttpPath.tsx b/web/src/components/Upstream/components/active-check/HttpPath.tsx
new file mode 100644
index 0000000..4914a49
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/HttpPath.tsx
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Input } from 'antd'
+import { useIntl } from 'umi'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.active.http_path' })}
+      required
+      tooltip={formatMessage({
+        id: 'page.upstream.step.input.healthyCheck.active.http_path',
+      })}
+    >
+      <Form.Item
+        name={['checks', 'active', 'http_path']}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({ id: 'page.upstream.checks.active.http_path.placeholder' }),
+          },
+        ]}
+      >
+        <Input
+          disabled={readonly}
+          placeholder={formatMessage({ id: 'page.upstream.checks.active.http_path.placeholder' })}
+        />
+      </Form.Item>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/active-check/Port.tsx
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/active-check/Port.tsx
index f13b31a..3d04e07 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/active-check/Port.tsx
@@ -14,31 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
-
-import BasicAuth from './basic-auth'
+import React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
 
 type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+  readonly?: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.activePort' })}>
+      <Form.Item name={['checks', 'active', 'port']} noStyle>
+        <InputNumber
+          placeholder={formatMessage({
+            id: 'page.upstream.step.input.healthyCheck.activePort',
+          })}
+          disabled={readonly}
+          min={1}
+          max={65535}
+        />
+      </Form.Item>
+    </Form.Item>
+  )
 }
+
+export default Component
diff --git a/web/src/components/Upstream/components/active-check/ReqHeaders.tsx b/web/src/components/Upstream/components/active-check/ReqHeaders.tsx
new file mode 100644
index 0000000..25fb8d6
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/ReqHeaders.tsx
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Row, Col, Input, Button } from 'antd'
+import { useIntl } from 'umi'
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
+
+import { removeBtnStyle } from '../../constant'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.List name={['checks', 'active', 'req_headers']}>
+      {(fields, { add, remove }) => (
+        <>
+          <Form.Item
+            label={
+              formatMessage({ id: 'page.upstream.step.healthyCheck.active.req_headers' })
+            }
+            style={{ marginBottom: 0 }}
+          >
+            {fields.map((field, index) => (
+              <Row style={{ marginBottom: 10 }} gutter={12} key={index}>
+                <Col span={5}>
+                  <Form.Item noStyle name={[field.name]}>
+                    <Input
+                      placeholder={formatMessage({
+                        id: 'page.upstream.step.input.healthyCheck.active.req_headers',
+                      })}
+                      disabled={readonly}
+                    />
+                  </Form.Item>
+                </Col>
+                <Col style={{ ...removeBtnStyle, marginLeft: 0 }}>
+                  {!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 />
+                {formatMessage({
+                  id: 'component.global.add',
+                })}
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/active-check/Timeout.tsx
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/active-check/Timeout.tsx
index f13b31a..4d294b7 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/active-check/Timeout.tsx
@@ -14,31 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
-
-import BasicAuth from './basic-auth'
+import React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
+import TimeUnit from '../TimeUnit'
 
 type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+  readonly?: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
+const ActiveCheckTimeoutComponent: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
 
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+  return (
+    <Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.active.timeout' })} tooltip={formatMessage({ id: 'page.upstream.checks.active.timeout.description' })}>
+      <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+        <InputNumber disabled={readonly} min={0} />
+      </Form.Item>
+      <TimeUnit />
+    </Form.Item>
+  )
 }
+
+export default ActiveCheckTimeoutComponent
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/active-check/Type.tsx
similarity index 52%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/active-check/Type.tsx
index f13b31a..e8b95c6 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/active-check/Type.tsx
@@ -14,31 +14,44 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
-
-import BasicAuth from './basic-auth'
+import React from 'react'
+import { Form, Select } from 'antd'
 
 type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+  readonly?: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
+const options = [
+  {
+    label: "HTTP",
+    value: "http"
+  }, {
+    label: "HTTPs",
+    value: "https"
+  }, {
+    label: "TCP",
+    value: "tcp"
   }
+]
+
+const ActiveCheckTypeComponent: React.FC<Props> = ({ readonly }) => {
+  return (
+    <Form.Item
+      label="Type"
+      name={['checks', 'active', 'type']}
+      rules={[{ required: true }]}
+    >
+      <Select disabled={readonly}>
+        {options.map(item => {
+          return (
+            <Select.Option value={item.value} key={item.value}>
+              {item.label}
+            </Select.Option>
+          );
+        })}
+      </Select>
+    </Form.Item>
+  )
 }
+
+export default ActiveCheckTypeComponent
diff --git a/web/src/components/Upstream/components/active-check/Unhealthy/HttpFailures.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/HttpFailures.tsx
new file mode 100644
index 0000000..dbd4118
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/Unhealthy/HttpFailures.tsx
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { Form, InputNumber } from 'antd'
+import React from 'react'
+import { useIntl } from 'umi'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.http_failures' })}
+      required
+      tooltip={formatMessage({ id: 'page.upstream.checks.active.unhealthy.http_failures.description' })}
+    >
+      <Form.Item
+        name={['checks', 'active', 'unhealthy', 'http_failures']}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({
+              id: 'page.upstream.step.input.healthyCheck.http_failures',
+            }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} min={1} max={254} />
+      </Form.Item>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/active-check/Unhealthy/HttpStatuses.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/HttpStatuses.tsx
new file mode 100644
index 0000000..0cdad52
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/Unhealthy/HttpStatuses.tsx
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Row, Col, Button, InputNumber } from 'antd'
+import { useIntl } from 'umi'
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
+
+import { removeBtnStyle } from '@/components/Upstream'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.List name={['checks', 'active', 'unhealthy', 'http_statuses']}>
+      {(fields, { add, remove }) => (
+        <>
+          <Form.Item
+            required
+            label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive.http_statuses' })}
+            style={{ marginBottom: 0 }}
+          >
+            {fields.map((field, index) => (
+              <Row style={{ marginBottom: 10 }} key={index}>
+                <Col span={2}>
+                  <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                    <InputNumber disabled={readonly} min={200} max={599} />
+                  </Form.Item>
+                </Col>
+                <Col style={removeBtnStyle}>
+                  {!readonly && (
+                    <MinusCircleOutlined
+                      onClick={() => {
+                        remove(field.name);
+                      }}
+                    />
+                  )}
+                </Col>
+              </Row>
+            ))}
+          </Form.Item>
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={() => add()}>
+                <PlusOutlined />
+                {formatMessage({
+                  id: 'component.global.add',
+                })}
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  )
+}
+export default Component
diff --git a/web/src/components/Upstream/components/active-check/Unhealthy/Interval.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/Interval.tsx
new file mode 100644
index 0000000..8052808
--- /dev/null
+++ b/web/src/components/Upstream/components/active-check/Unhealthy/Interval.tsx
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
+
+import TimeUnit from '../../TimeUnit'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.activeInterval' })}
+      required
+      tooltip={formatMessage({ id: 'page.upstream.checks.active.unhealthy.interval.description' })}
+    >
+      <Form.Item
+        name={['checks', 'active', 'unhealthy', 'interval']}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({
+              id: 'page.upstream.step.input.healthyCheck.activeInterval',
+            }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} min={1} />
+      </Form.Item>
+      <TimeUnit />
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/Timeouts.tsx
similarity index 56%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/active-check/Unhealthy/Timeouts.tsx
index f13b31a..2f5af5f 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/active-check/Unhealthy/Timeouts.tsx
@@ -14,31 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
-
-import BasicAuth from './basic-auth'
+import React from 'react'
+import { Form, InputNumber } from 'antd'
 
 type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+  readonly?: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
+const Component: React.FC<Props> = ({ readonly }) => (
+  <Form.Item
+    label="Timeouts"
+    required
+  >
+    <Form.Item
+      name={['checks', 'active', 'unhealthy', 'timeouts']}
+      noStyle
+    >
+      <InputNumber disabled={readonly} min={1} max={254} />
+    </Form.Item>
+  </Form.Item>
+)
 
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
-}
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/index.ts
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/active-check/Unhealthy/index.ts
index f13b31a..bb9b869 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/active-check/Unhealthy/index.ts
@@ -14,31 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import Timeouts from './Timeouts'
+import Interval from './Interval'
+import HttpStatuses from './HttpStatuses'
+import HttpFailures from './HttpFailures'
 
-import BasicAuth from './basic-auth'
-
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
-}
-
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+export default {
+  Timeouts,
+  Interval,
+  HttpStatuses,
+  HttpFailures
 }
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/active-check/index.ts
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/active-check/index.ts
index f13b31a..efc3046 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/active-check/index.ts
@@ -14,31 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import Unhealthy from './Unhealthy'
+import Healthy from './Healthy'
 
-import BasicAuth from './basic-auth'
+import Timeout from './Timeout'
+import Type from './Type'
+import ReqHeaders from './ReqHeaders'
+import Host from './Host'
+import Port from './Port'
+import HttpPath from './HttpPath'
 
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
-}
-
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+export default {
+  Unhealthy,
+  Healthy,
+  Timeout,
+  Type,
+  ReqHeaders,
+  Host,
+  Port,
+  HttpPath
 }
diff --git a/web/src/components/Upstream/components/passive-check/Healthy/HttpStatuses.tsx b/web/src/components/Upstream/components/passive-check/Healthy/HttpStatuses.tsx
new file mode 100644
index 0000000..f1b06d2
--- /dev/null
+++ b/web/src/components/Upstream/components/passive-check/Healthy/HttpStatuses.tsx
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Row, Col, InputNumber, Button } from 'antd'
+import { useIntl } from 'umi'
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
+
+import { removeBtnStyle } from '@/components/Upstream/constant'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.List name={['checks', 'passive', 'healthy', 'http_statuses']}>
+      {(fields, { add, remove }) => (
+        <>
+          <Form.Item
+            required
+            label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive.http_statuses' })}
+            tooltip={formatMessage({ id: 'page.upstream.checks.passive.healthy.http_statuses.description' })}
+            style={{ marginBottom: 0 }}
+          >
+            {fields.map((field, index) => (
+              <Row style={{ marginBottom: 10 }} key={index}>
+                <Col span={2}>
+                  <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                    <InputNumber disabled={readonly} min={200} max={599} />
+                  </Form.Item>
+                </Col>
+                <Col style={removeBtnStyle}>
+                  {!readonly && (
+                    <MinusCircleOutlined
+                      onClick={() => {
+                        remove(field.name);
+                      }}
+                    />
+                  )}
+                </Col>
+              </Row>
+            ))}
+          </Form.Item>
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={() => add()}>
+                <PlusOutlined />
+                {formatMessage({
+                  id: 'component.global.add',
+                })}
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/passive-check/Healthy/Successes.tsx b/web/src/components/Upstream/components/passive-check/Healthy/Successes.tsx
new file mode 100644
index 0000000..2ee3590
--- /dev/null
+++ b/web/src/components/Upstream/components/passive-check/Healthy/Successes.tsx
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.successes' })}
+      tooltip={formatMessage({ id: 'page.upstream.checks.passive.healthy.successes.description' })}
+      required
+    >
+      <Form.Item
+        name={['checks', 'passive', 'healthy', 'successes']}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({ id: 'page.upstream.step.input.healthyCheck.successes' }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} min={1} max={254} />
+      </Form.Item>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/passive-check/Healthy/index.ts
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/passive-check/Healthy/index.ts
index f13b31a..737fbf4 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/passive-check/Healthy/index.ts
@@ -14,31 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import HttpStatuses from './HttpStatuses'
+import Successes from './Successes'
 
-import BasicAuth from './basic-auth'
-
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
-}
-
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+export default {
+  HttpStatuses,
+  Successes
 }
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/passive-check/Type.tsx
similarity index 51%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/passive-check/Type.tsx
index f13b31a..d3401ee 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/passive-check/Type.tsx
@@ -14,31 +14,44 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
-
-import BasicAuth from './basic-auth'
+import React from 'react'
+import { Form, Select } from 'antd'
 
 type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+  readonly?: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
+const PassiveCheckTypeComponent: React.FC<Props> = ({ readonly }) => {
+  const options = [
+    {
+      label: "HTTP",
+      value: "http"
+    }, {
+      label: "HTTPs",
+      value: "https"
+    }, {
+      label: "TCP",
+      value: "tcp"
+    }
+  ]
 
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+  return (
+    <Form.Item
+      label="Type"
+      name={['checks', 'passive', 'type']}
+      rules={[{ required: true }]}
+    >
+      <Select disabled={readonly}>
+        {options.map(item => {
+          return (
+            <Select.Option value={item.value} key={item.value}>
+              {item.label}
+            </Select.Option>
+          );
+        })}
+      </Select>
+    </Form.Item>
+  )
 }
+
+export default PassiveCheckTypeComponent
diff --git a/web/src/components/Upstream/components/passive-check/Unhealthy/HttpFailures.tsx b/web/src/components/Upstream/components/passive-check/Unhealthy/HttpFailures.tsx
new file mode 100644
index 0000000..3ba6154
--- /dev/null
+++ b/web/src/components/Upstream/components/passive-check/Unhealthy/HttpFailures.tsx
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.http_failures' })}
+      required
+      tooltip={formatMessage({ id: 'page.upstream.checks.passive.unhealthy.http_failures.description' })}
+    >
+      <Form.Item
+        name={['checks', 'passive', 'unhealthy', 'http_failures']}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({
+              id: 'page.upstream.step.input.healthyCheck.http_failures',
+            }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} min={1} max={254} />
+      </Form.Item>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/passive-check/Unhealthy/HttpStatuses.tsx b/web/src/components/Upstream/components/passive-check/Unhealthy/HttpStatuses.tsx
new file mode 100644
index 0000000..ba9ba75
--- /dev/null
+++ b/web/src/components/Upstream/components/passive-check/Unhealthy/HttpStatuses.tsx
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, Row, Col, InputNumber, Button } from 'antd'
+import { useIntl } from 'umi'
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
+
+import { removeBtnStyle } from '@/components/Upstream/constant'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.List name={['checks', 'passive', 'unhealthy', 'http_statuses']}>
+      {(fields, { add, remove }) => (
+        <>
+          <Form.Item
+            required
+            label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive.http_statuses' })}
+            tooltip={formatMessage({ id: 'page.upstream.checks.passive.unhealthy.http_statuses.description' })}
+            style={{ marginBottom: 0 }}
+          >
+            {fields.map((field, index) => (
+              <Row style={{ marginBottom: 10 }} key={index}>
+                <Col span={2}>
+                  <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                    <InputNumber disabled={readonly} min={200} max={599} />
+                  </Form.Item>
+                </Col>
+                <Col style={removeBtnStyle}>
+                  {!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 />
+                {formatMessage({
+                  id: 'component.global.add',
+                })}
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Upstream/components/passive-check/Unhealthy/TcpFailures.tsx b/web/src/components/Upstream/components/passive-check/Unhealthy/TcpFailures.tsx
new file mode 100644
index 0000000..175d22f
--- /dev/null
+++ b/web/src/components/Upstream/components/passive-check/Unhealthy/TcpFailures.tsx
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React from 'react'
+import { Form, InputNumber } from 'antd'
+import { useIntl } from 'umi'
+
+type Props = {
+  readonly?: boolean
+}
+
+const Component: React.FC<Props> = ({ readonly }) => {
+  const { formatMessage } = useIntl()
+  return (
+    <Form.Item
+      label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive.tcp_failures' })}
+      required
+      tooltip={formatMessage({ id: 'page.upstream.checks.passive.unhealthy.tcp_failures.description' })}
+    >
+      <Form.Item
+        name={['checks', 'passive', 'unhealthy', 'tcp_failures']}
+        noStyle
+        rules={[
+          {
+            required: true,
+            message: formatMessage({
+              id: 'page.upstream.step.input.healthyCheck.passive.tcp_failures',
+            }),
+          },
+        ]}
+      >
+        <InputNumber disabled={readonly} min={1} max={254} />
+      </Form.Item>
+    </Form.Item>
+  )
+}
+
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/passive-check/Unhealthy/Timeouts.tsx
similarity index 56%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/passive-check/Unhealthy/Timeouts.tsx
index f13b31a..91b7b4f 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/passive-check/Unhealthy/Timeouts.tsx
@@ -14,31 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
-
-import BasicAuth from './basic-auth'
+import React from 'react'
+import { Form, InputNumber } from 'antd'
 
 type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
+  readonly?: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
+const Component: React.FC<Props> = ({ readonly }) => (
+  <Form.Item
+    label="Timeouts"
+    required
+  >
+    <Form.Item
+      name={['checks', 'passive', 'unhealthy', 'timeouts']}
+      noStyle
+    >
+      <InputNumber disabled={readonly} min={1} max={254} />
+    </Form.Item>
+  </Form.Item>
+)
 
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
-}
+export default Component
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/passive-check/Unhealthy/index.ts
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/passive-check/Unhealthy/index.ts
index f13b31a..88ab9c2 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/passive-check/Unhealthy/index.ts
@@ -14,31 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import Timeouts from './Timeouts'
+import HttpFailures from './HttpFailures'
+import TcpFailures from './TcpFailures'
+import HttpStatuses from './HttpStatuses'
 
-import BasicAuth from './basic-auth'
-
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
-}
-
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+export default {
+  Timeouts,
+  HttpFailures,
+  TcpFailures,
+  HttpStatuses
 }
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Upstream/components/passive-check/index.ts
similarity index 55%
copy from web/src/components/Plugin/UI/plugin.tsx
copy to web/src/components/Upstream/components/passive-check/index.ts
index f13b31a..f0637fd 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Upstream/components/passive-check/index.ts
@@ -14,31 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/es/form';
-import { Empty } from 'antd';
-import { useIntl } from 'umi';
+import Healthy from './Healthy'
+import Unhealthy from './Unhealthy'
+import Type from './Type'
 
-import BasicAuth from './basic-auth'
-
-type Props = {
-  name: string,
-  form: FormInstance,
-  renderForm: boolean
-}
-
-export const PLUGIN_UI_LIST = ['basic-auth',];
-
-export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
-
-  const { formatMessage } = useIntl();
-
-  if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
-
-  switch (name) {
-    case 'basic-auth':
-      return <BasicAuth form={form} />
-    default:
-      return null;
-  }
+export default {
+  Healthy,
+  Unhealthy,
+  Type
 }
diff --git a/web/src/components/Upstream/constant.ts b/web/src/components/Upstream/constant.ts
index 58d8ed5..d4f243b 100644
--- a/web/src/components/Upstream/constant.ts
+++ b/web/src/components/Upstream/constant.ts
@@ -16,6 +16,8 @@
  */
 export const DEFAULT_UPSTREAM = {
   upstream_id: '',
+  // NOTE: the following fields are the default configurations
+  // https://github.com/apache/apisix/blob/master/apisix/schema_def.lua#L325
   nodes: [
     {
       host: '',
@@ -23,42 +25,66 @@ export const DEFAULT_UPSTREAM = {
       weight: 1
     }
   ],
-  type: 'roundrobin',
+  retries: 0,
   timeout: {
     connect: 6,
     send: 6,
     read: 6,
   },
-  retries: 1
+  type: 'roundrobin',
+  checks: {},
+  scheme: "http",
+  pass_host: "pass",
+  name: "",
+  desc: ""
 };
 
 // NOTE: checks.active
+// https://github.com/apache/apisix/blob/master/apisix/schema_def.lua#L40
 export const DEFAULT_HEALTH_CHECK_ACTIVE = {
-  timeout: 0,
-  http_path: '/',
-  host: '',
+  type: "http",
+  timeout: 1,
+  concurrency: 10,
+  host: "",
   port: 80,
+  http_path: "",
+  https_verify_certificate: true,
   healthy: {
     interval: 1,
-    successes: 1
+    http_statuses: [200, 302],
+    successes: 2
   },
   unhealthy: {
     interval: 1,
-    http_failures: 1,
-    req_headers: []
-  }
+    http_statuses: [429, 404, 500, 501, 502, 503, 504, 505],
+    http_failures: 5,
+    tcp_failures: 2,
+    timeouts: 3
+  },
+  req_headers: []
 }
 
 // NOTE: checks.passive
 export const DEFAULT_HEALTH_CHECK_PASSIVE = {
+  type: "http",
   healthy: {
-    http_statuses: [],
-    successes: 1
+    http_statuses: [
+      200, 201, 202, 203, 204, 205, 206, 207,
+      208, 226, 300, 301, 302, 303, 304, 305,
+      306, 307, 308
+    ],
+    successes: 5
   },
   unhealthy: {
-    http_statuses: [],
-    tcp_failures: 1,
-    timeouts: 1,
-    http_failures: 1
+    http_statuses: [429, 500, 503],
+    tcp_failures: 2,
+    timeouts: 7,
+    http_failures: 5
   }
 }
+
+export const removeBtnStyle = {
+  marginLeft: 20,
+  display: 'flex',
+  alignItems: 'center',
+};
diff --git a/web/src/pages/Route/locales/zh-CN.ts b/web/src/pages/Route/locales/zh-CN.ts
index ca97132..5d8adda 100644
--- a/web/src/pages/Route/locales/zh-CN.ts
+++ b/web/src/pages/Route/locales/zh-CN.ts
@@ -36,7 +36,7 @@ export default {
   'page.route.status': '状态',
   'page.route.groupName': '分组名称',
   'page.route.offline': '下线',
-  'page.route.publish': '是否发布',
+  'page.route.publish': '发布',
   'page.route.published': '已发布',
   'page.route.unpublished': '未发布',
   'page.route.onlineDebug': '在线调试',
diff --git a/web/src/pages/Upstream/locales/en-US.ts b/web/src/pages/Upstream/locales/en-US.ts
index a78a162..bc078e2 100644
--- a/web/src/pages/Upstream/locales/en-US.ts
+++ b/web/src/pages/Upstream/locales/en-US.ts
@@ -32,7 +32,6 @@ export default {
   'page.upstream.step.description': 'Description',
   'page.upstream.step.input.description': 'Please enter upstream\'s description',
   'page.upstream.step.type': 'Algorithm',
-  'page.upstream.step.create.node': 'Add Target',
   'page.upstream.step.pass-host': 'Hostname',
   'page.upstream.step.pass-host.pass': 'Keep the same Host from client request',
   'page.upstream.step.pass-host.node': 'Use the domain or IP from Node List',
@@ -65,12 +64,10 @@ export default {
   'page.upstream.step.healthyCheck.successes': 'Successes',
   'page.upstream.step.input.healthyCheck.successes': 'Please enter successes',
   'page.upstream.step.healthyCheck.http_failures': 'HTTP Failures',
-  'page.upstream.step.healthyCheck.active.create.req_headers': 'Add Request Headers',
   'page.upstream.step.input.healthyCheck.http_failures': 'Please enter http failures',
   'page.upstream.step.healthyCheck.active.req_headers': 'Request Headers',
   'page.upstream.step.input.healthyCheck.active.req_headers': 'Please enter request headers',
   'page.upstream.step.healthyCheck.passive': 'Passive',
-  'page.upstream.step.healthyCheck.passive.create.http_statuses': 'Add HTTP Status',
   'page.upstream.step.healthyCheck.passive.http_statuses': 'HTTP Status',
   'page.upstream.step.input.healthyCheck.passive.http_statuses': 'Please enter http status',
   'page.upstream.step.healthyCheck.passive.tcp_failures': 'TCP Failures',
@@ -120,5 +117,6 @@ export default {
   'page.upstream.checks.passive.healthy.successes.description': 'Number of successes to consider a target healthy',
   'page.upstream.checks.passive.unhealthy.http_statuses.description': 'Which HTTP statuses to consider a success',
   'page.upstream.checks.passive.unhealthy.http_failures.description': 'Number of HTTP failures to consider a target unhealthy',
-  'page.upstream.checks.passive.unhealthy.tcp_failures.description': 'Number of TCP failures to consider a target unhealthy'
+  'page.upstream.checks.passive.unhealthy.tcp_failures.description': 'Number of TCP failures to consider a target unhealthy',
+  'page.upstream.scheme': 'Scheme'
 };
diff --git a/web/src/pages/Upstream/locales/zh-CN.ts b/web/src/pages/Upstream/locales/zh-CN.ts
index 3049b97..58620ba 100644
--- a/web/src/pages/Upstream/locales/zh-CN.ts
+++ b/web/src/pages/Upstream/locales/zh-CN.ts
@@ -32,8 +32,7 @@ export default {
   'page.upstream.step.description': '描述',
   'page.upstream.step.input.description': '请输入上游服务的描述',
   'page.upstream.step.type': '负载均衡算法',
-  'page.upstream.step.create.node': '增加目标节点',
-  'page.upstream.step.pass-host': 'Host 转换',
+  'page.upstream.step.pass-host': 'Host 请求头',
   'page.upstream.step.pass-host.pass': '保持与客户端请求一致的 Host 请求头',
   'page.upstream.step.pass-host.node': '使用上游节点列表中的域名或 IP',
   'page.upstream.step.pass-host.rewrite': '自定义 Host 请求头(即将废弃)',
@@ -65,12 +64,10 @@ export default {
   'page.upstream.step.healthyCheck.successes': '成功次数',
   'page.upstream.step.input.healthyCheck.successes': '请输入成功次数',
   'page.upstream.step.healthyCheck.http_failures': '失败次数',
-  'page.upstream.step.healthyCheck.active.create.req_headers': '增加请求头',
   'page.upstream.step.input.healthyCheck.http_failures': '请输入失败次数',
   'page.upstream.step.healthyCheck.active.req_headers': '请求头',
   'page.upstream.step.input.healthyCheck.active.req_headers': '请输入请求头',
   'page.upstream.step.healthyCheck.passive': '被动检查',
-  'page.upstream.step.healthyCheck.passive.create.http_statuses': '增加状态码',
   'page.upstream.step.healthyCheck.passive.http_statuses': '状态码',
   'page.upstream.step.input.healthyCheck.passive.http_statuses': '请输入状态码',
   'page.upstream.step.healthyCheck.passive.tcp_failures': 'TCP 失败次数',
@@ -120,5 +117,6 @@ export default {
   'page.upstream.checks.passive.healthy.successes.description': '通过被动健康检查观察到的正常代理流量的成功次数。如果达到该值,上游服务目标节点将被视为健康。',
   'page.upstream.checks.passive.unhealthy.http_statuses.description': '当被动健康检查的探针返回值是 HTTP 状态码列表的某一个值时,代表不健康状态是由代理流量产生的。',
   'page.upstream.checks.passive.unhealthy.http_failures.description': '由被动健康检查所观察,代理流量中 HTTP 失败的次数。如果达到此值,则认为上游服务目标节点是不健康的。',
-  'page.upstream.checks.passive.unhealthy.tcp_failures.description': '被动健康检查所观察到的代理流量中 TCP 失败的次数。如果达到此值,则认为上游服务目标节点是不健康的。'
+  'page.upstream.checks.passive.unhealthy.tcp_failures.description': '被动健康检查所观察到的代理流量中 TCP 失败的次数。如果达到此值,则认为上游服务目标节点是不健康的。',
+  'page.upstream.scheme': '协议'
 };