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 2020/06/02 13:07:27 UTC

[incubator-apisix-dashboard] branch next updated: feat: add form validation (#234)

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

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


The following commit(s) were added to refs/heads/next by this push:
     new 0e5bbe4  feat: add form validation (#234)
0e5bbe4 is described below

commit 0e5bbe4d0578748a7437f198a04325e55be7f248
Author: litesun <31...@users.noreply.github.com>
AuthorDate: Tue Jun 2 21:07:18 2020 +0800

    feat: add form validation (#234)
    
    * feat: limit upload file
    
    * feat: intercept default upload api request
    
    * feat: limit upload file type
    
    * fix: show file when parse SSL file  fail
    
    * feat: add search feature
    
    * feat: format code
    
    * fix: remove list item not work
    
    * feat: remove relatedRouting
    
    * feat: add routes step1 page
    
    * feat: update route
    
    * feat: format code
    
    * feat: update
    
    * feat: update
    
    * feat: add checkbox rule
    
    * feat: handle Modal close event
    
    * feat: format code
    
    * feat: add page skip
    
    * feat: format code
    
    * merge
    
    * feat: clean code
    
    * fix: step4 error
    
    * feat: add edit modal
    
    * feat: add form validation
---
 src/pages/Routes/Create.tsx                        |  36 +++-
 src/pages/Routes/components/Step1/MetaView.tsx     |   1 +
 .../Routes/components/Step1/RequestConfigView.tsx  | 236 +++++++++++----------
 src/pages/Routes/components/Step1/index.tsx        |   3 +-
 .../Routes/components/Step2/RequestRewriteView.tsx | 172 +++++++++------
 src/pages/Routes/components/Step2/index.tsx        |   5 +-
 src/pages/Routes/constants.ts                      |  10 +-
 src/pages/Routes/typing.d.ts                       |   2 +
 8 files changed, 276 insertions(+), 189 deletions(-)

diff --git a/src/pages/Routes/Create.tsx b/src/pages/Routes/Create.tsx
index bd07bda..0772606 100644
--- a/src/pages/Routes/Create.tsx
+++ b/src/pages/Routes/Create.tsx
@@ -1,5 +1,5 @@
 import React, { useState } from 'react';
-import { Card, Steps } from 'antd';
+import { Card, Steps, Form } from 'antd';
 import { PageHeaderWrapper } from '@ant-design/pro-layout';
 
 import Step1 from './components/Step1';
@@ -17,7 +17,10 @@ const Create: React.FC = () => {
   const [step2Data, setStep2Data] = useState(DEFAULT_STEP_2_DATA);
   const [step3Data, setStep3Data] = useState(DEFAULT_STEP_3_DATA);
 
-  const [step, setStep] = useState(0);
+  const [form1] = Form.useForm();
+  const [form2] = Form.useForm();
+
+  const [step, setStep] = useState(1);
   const [stepHeader] = useState(['定义 API 请求', '定义 API 后端服务', '插件配置', '预览']);
 
   const data = {
@@ -31,6 +34,7 @@ const Create: React.FC = () => {
       return (
         <Step1
           data={data}
+          form={form1}
           onChange={(_data: RouteModule.Step1Data) => {
             setStep1Data(_data);
           }}
@@ -42,6 +46,7 @@ const Create: React.FC = () => {
       return (
         <Step2
           data={data}
+          form={form2}
           onChange={(params: RouteModule.Step2Data) => setStep2Data({ ...step2Data, ...params })}
         />
       );
@@ -58,6 +63,30 @@ const Create: React.FC = () => {
     return null;
   };
 
+  const onStepChange = (nextStep: number) => {
+    const nextStepAction = () => {
+      setStep(nextStep);
+      window.scrollTo({ top: 0 });
+    };
+    if (nextStep > step && nextStep < 3) {
+      // Form Validation
+      if (step === 0) {
+        form1.validateFields().then((value) => {
+          setStep1Data({ ...step1Data, ...value });
+          nextStepAction();
+        });
+        return;
+      }
+      if (step === 1) {
+        form2.validateFields().then((value) => {
+          setStep1Data({ ...step1Data, ...value });
+          nextStepAction();
+        });
+        return;
+      }
+    }
+    nextStepAction();
+  };
   return (
     <>
       <PageHeaderWrapper>
@@ -73,8 +102,7 @@ const Create: React.FC = () => {
       <ActionBar
         step={step}
         onChange={(nextStep) => {
-          setStep(nextStep);
-          window.scrollTo({ top: 0 });
+          onStepChange(nextStep);
         }}
       />
     </>
diff --git a/src/pages/Routes/components/Step1/MetaView.tsx b/src/pages/Routes/components/Step1/MetaView.tsx
index 69e64c7..3c1679d 100644
--- a/src/pages/Routes/components/Step1/MetaView.tsx
+++ b/src/pages/Routes/components/Step1/MetaView.tsx
@@ -2,6 +2,7 @@ import React from 'react';
 import Form from 'antd/es/form';
 import { Input } from 'antd';
 
+// import { FORM_ITEM_LAYOUT } from '@/pages/Routes/constants';
 import PanelSection from '../PanelSection';
 
 interface Props extends RouteModule.Data {}
diff --git a/src/pages/Routes/components/Step1/RequestConfigView.tsx b/src/pages/Routes/components/Step1/RequestConfigView.tsx
index be46a99..f1417fe 100644
--- a/src/pages/Routes/components/Step1/RequestConfigView.tsx
+++ b/src/pages/Routes/components/Step1/RequestConfigView.tsx
@@ -1,149 +1,161 @@
 import React, { useState } from 'react';
 import Form from 'antd/es/form';
-import { Row, Checkbox, Button, Col, Input, Space } from 'antd';
+import { Checkbox, Button, Input } from 'antd';
 import { CheckboxValueType } from 'antd/lib/checkbox/Group';
 import { CheckboxChangeEvent } from 'antd/lib/checkbox';
 
-import { HTTP_METHOD_OPTION_LIST } from '@/pages/Routes/constants';
+import {
+  HTTP_METHOD_OPTION_LIST,
+  FORM_ITEM_LAYOUT,
+  FORM_ITEM_WITHOUT_LABEL,
+} from '@/pages/Routes/constants';
 
+import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
 import PanelSection from '../PanelSection';
 
 interface Props extends RouteModule.Data {}
 
 const RequestConfigView: React.FC<Props> = ({ data, disabled, onChange }) => {
-  const { paths, hosts, protocols } = data.step1Data;
-  const [httpMethodList, setHttpMethodList] = useState({
-    checkedList: HTTP_METHOD_OPTION_LIST,
-    indeterminate: false,
-    checkAll: true,
-  });
+  const { protocols } = data.step1Data;
+
+  // TODO: checkedList Validation
+  const [checkedList, setCheckedList] = useState(HTTP_METHOD_OPTION_LIST);
+  const [indeterminate, setIndeterminate] = useState(false);
+  const [checkAll, setCheckAll] = useState(true);
 
   const onProtocolChange = (e: CheckboxValueType[]) => {
     if (!e.includes('HTTP') && !e.includes('HTTPS')) return;
     onChange({ ...data.step1Data, protocols: e });
   };
-  const onMethodsChange = (checkedList: CheckboxValueType[]) => {
-    setHttpMethodList({
-      checkedList: checkedList as RouteModule.HttpMethod[],
-      indeterminate: !!checkedList.length && checkedList.length < HTTP_METHOD_OPTION_LIST.length,
-      checkAll: checkedList.length === HTTP_METHOD_OPTION_LIST.length,
-    });
+
+  const onMethodsChange = (methods: CheckboxValueType[]) => {
+    setCheckedList(methods as RouteModule.HttpMethod[]);
+    setIndeterminate(!!methods.length && methods.length < HTTP_METHOD_OPTION_LIST.length);
+    setCheckAll(methods.length === HTTP_METHOD_OPTION_LIST.length);
   };
+
   const onCheckAllChange = (e: CheckboxChangeEvent) => {
-    setHttpMethodList({
-      checkedList: e.target.checked ? HTTP_METHOD_OPTION_LIST : [],
-      indeterminate: false,
-      checkAll: e.target.checked,
-    });
+    setCheckedList(e.target.checked ? HTTP_METHOD_OPTION_LIST : []);
+    setIndeterminate(false);
+    setCheckAll(e.target.checked);
   };
 
-  const renderHosts = () =>
-    hosts.map((item, index) => (
-      <Row key={`${item + index}`} style={{ marginBottom: '10px' }} gutter={[16, 16]}>
-        <Col span={16}>
-          <Input placeholder="域名" disabled={disabled} />
-        </Col>
-        <Col span={4}>
-          <Space>
-            {hosts.length > 1 && !disabled && (
-              <Button
-                type="primary"
-                danger
-                onClick={() => {
-                  onChange({
-                    ...data.step1Data,
-                    hosts: hosts.filter((_, _index) => _index !== index),
-                  });
-                }}
+  const renderHosts = () => (
+    <Form.List name="hosts">
+      {(fields, { add, remove }) => {
+        return (
+          <div>
+            {fields.map((field, index) => (
+              <Form.Item
+                {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)}
+                label={index === 0 ? '域名' : ''}
+                required
+                key={field.key}
               >
-                删除
-              </Button>
-            )}
-          </Space>
-        </Col>
-      </Row>
-    ));
+                <Form.Item
+                  {...field}
+                  validateTrigger={['onChange', 'onBlur']}
+                  rules={[
+                    {
+                      required: true,
+                      whitespace: true,
+                      message: '请输入域名',
+                    },
+                  ]}
+                  noStyle
+                >
+                  <Input placeholder="请输入域名" style={{ width: '60%' }} />
+                </Form.Item>
+                {fields.length > 1 ? (
+                  <MinusCircleOutlined
+                    className="dynamic-delete-button"
+                    style={{ margin: '0 8px' }}
+                    onClick={() => {
+                      remove(field.name);
+                    }}
+                  />
+                ) : null}
+              </Form.Item>
+            ))}
+            <Form.Item {...FORM_ITEM_WITHOUT_LABEL}>
+              {!disabled && (
+                <Button
+                  type="dashed"
+                  onClick={() => {
+                    add();
+                  }}
+                >
+                  <PlusOutlined /> 增加
+                </Button>
+              )}
+            </Form.Item>
+          </div>
+        );
+      }}
+    </Form.List>
+  );
 
-  const renderPaths = () =>
-    paths.map((item, index) => (
-      <Row key={`${item + index}`} style={{ marginBottom: '10px' }} gutter={[16, 16]}>
-        <Col span={16}>
-          <Input placeholder="请输入请求路径" disabled={disabled} />
-        </Col>
-        {!disabled && (
-          <Col span={4}>
-            <Space>
-              <Button
-                type="primary"
-                danger
-                onClick={() => {
-                  onChange({
-                    ...data.step1Data,
-                    paths: paths.filter((_, _index) => _index !== index),
-                  });
-                }}
-              >
-                删除
-              </Button>
-            </Space>
-          </Col>
-        )}
-      </Row>
-    ));
+  const renderPaths = () => (
+    <Form.List name="paths">
+      {(fields, { add, remove }) => {
+        return (
+          <div>
+            {fields.map((field) => (
+              <Form.Item required key={field.key}>
+                <Form.Item {...field} validateTrigger={['onChange', 'onBlur']} noStyle>
+                  <Input placeholder="请输入路径" style={{ width: '60%' }} />
+                </Form.Item>
+                <MinusCircleOutlined
+                  className="dynamic-delete-button"
+                  style={{ margin: '0 8px' }}
+                  onClick={() => {
+                    remove(field.name);
+                  }}
+                />
+              </Form.Item>
+            ))}
+            <Form.Item>
+              {!disabled && (
+                <Button
+                  type="dashed"
+                  onClick={() => {
+                    add();
+                  }}
+                >
+                  <PlusOutlined /> 增加
+                </Button>
+              )}
+            </Form.Item>
+          </div>
+        );
+      }}
+    </Form.List>
+  );
 
   return (
     <PanelSection title="请求基础定义">
-      <Form.Item label="协议" name="protocol" rules={[{ required: true, message: '请勾选协议' }]}>
-        <Row>
-          <Checkbox.Group
-            disabled={disabled}
-            options={['HTTP', 'HTTPS', 'WebSocket']}
-            defaultValue={protocols}
-            value={protocols}
-            onChange={onProtocolChange}
-          />
-        </Row>
-      </Form.Item>
-      {/* TODO: name */}
-      <Form.Item label="域名" rules={[{ required: true, message: '请输入域名' }]}>
-        {renderHosts()}
-        {!disabled && (
-          <Button
-            type="primary"
-            onClick={() => onChange({ ...data.step1Data, hosts: hosts.concat('') })}
-          >
-            增加
-          </Button>
-        )}
-      </Form.Item>
-      {/* TODO: name */}
-      <Form.Item label="路径">
-        {renderPaths()}
-        {!disabled && (
-          <Button
-            onClick={() => onChange({ ...data.step1Data, paths: paths.concat(['']) })}
-            type="primary"
-          >
-            增加
-          </Button>
-        )}
+      <Form.Item label="协议" name="protocols" rules={[{ required: true, message: '请勾选协议' }]}>
+        <Checkbox.Group
+          disabled={disabled}
+          options={['HTTP', 'HTTPS', 'WebSocket']}
+          value={protocols}
+          onChange={onProtocolChange}
+        />
       </Form.Item>
-      <Form.Item
-        label="HTTP 方法"
-        name="httpMethods"
-        rules={[{ required: true, message: '请选择 HTTP 方法' }]}
-      >
+      {renderHosts()}
+      <Form.Item label="路径">{renderPaths()}</Form.Item>
+      <Form.Item label="HTTP 方法" name="httpMethods">
         <Checkbox
-          indeterminate={httpMethodList.indeterminate}
+          indeterminate={indeterminate}
           onChange={onCheckAllChange}
-          checked={httpMethodList.checkAll}
+          checked={checkAll}
           disabled={disabled}
         >
           ANY
         </Checkbox>
         <Checkbox.Group
           options={HTTP_METHOD_OPTION_LIST}
-          value={httpMethodList.checkedList}
+          value={checkedList}
           onChange={onMethodsChange}
           disabled={disabled}
         />
diff --git a/src/pages/Routes/components/Step1/index.tsx b/src/pages/Routes/components/Step1/index.tsx
index 78b817b..2fcf38c 100644
--- a/src/pages/Routes/components/Step1/index.tsx
+++ b/src/pages/Routes/components/Step1/index.tsx
@@ -9,8 +9,7 @@ import RequestConfigView from './RequestConfigView';
 import MatchingRulesView from './MatchingRulesView';
 
 const Step1: React.FC<RouteModule.Data> = (props) => {
-  const { data } = props;
-  const [form] = Form.useForm();
+  const { data, form } = props;
 
   return (
     <>
diff --git a/src/pages/Routes/components/Step2/RequestRewriteView.tsx b/src/pages/Routes/components/Step2/RequestRewriteView.tsx
index a6a3f46..a403769 100644
--- a/src/pages/Routes/components/Step2/RequestRewriteView.tsx
+++ b/src/pages/Routes/components/Step2/RequestRewriteView.tsx
@@ -3,7 +3,8 @@ import Form, { FormInstance } from 'antd/es/form';
 import Radio, { RadioChangeEvent } from 'antd/lib/radio';
 import { Input, Row, Col, InputNumber, Button } from 'antd';
 
-import { FORM_ITEM_LAYOUT } from '@/pages/Routes/constants';
+import { FORM_ITEM_LAYOUT, FORM_ITEM_WITHOUT_LABEL } from '@/pages/Routes/constants';
+import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
 import PanelSection from '../PanelSection';
 import styles from '../../Create.less';
 
@@ -13,40 +14,82 @@ interface Props extends RouteModule.Data {
 
 const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange }) => {
   const { step2Data } = data;
-  const { backendAddressList, backendProtocol } = step2Data;
   const onProtocolChange = (e: RadioChangeEvent) => {
     onChange({ backendProtocol: e.target.value });
   };
 
-  const renderBackendAddress = () =>
-    backendAddressList.map((item, index) => (
-      <Row key={`${item.host + index}`} style={{ marginBottom: '10px' }} gutter={16}>
-        <Col span={9}>
-          <Input placeholder="域名" disabled={disabled} />
-        </Col>
-        <Col span={4}>
-          <InputNumber placeholder="端口号" disabled={disabled} min={1} max={65535} />
-        </Col>
-        <Col span={4} offset={1}>
-          <InputNumber placeholder="权重" disabled={disabled} min={0} max={100} />
-        </Col>
-        <Col span={4} offset={1}>
-          {backendAddressList.length > 1 && !disabled && (
-            <Button
-              type="primary"
-              danger
-              onClick={() => {
-                onChange({
-                  backendAddressList: backendAddressList.filter((_, _index) => _index !== index),
-                });
-              }}
-            >
-              删除
-            </Button>
-          )}
-        </Col>
-      </Row>
-    ));
+  const renderBackendAddress = () => (
+    <Form.List name="backendAddressList">
+      {(fields, { add, remove }) => {
+        return (
+          <div>
+            {fields.map((field, index) => {
+              return (
+                <Form.Item
+                  required
+                  key={field.key}
+                  {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)}
+                  label={index === 0 ? '域名/IP' : ''}
+                >
+                  <Row style={{ marginBottom: '10px' }} gutter={16}>
+                    <Col span={9}>
+                      <Form.Item
+                        style={{ marginBottom: 0 }}
+                        name={[field.name, 'host']}
+                        rules={[{ required: true, message: '请输入域名/IP' }]}
+                      >
+                        <Input placeholder="域名/IP" disabled={disabled} />
+                      </Form.Item>
+                    </Col>
+                    <Col span={4}>
+                      <Form.Item
+                        style={{ marginBottom: 0 }}
+                        name={[field.name, 'port']}
+                        rules={[{ required: true, message: '请输入端口' }]}
+                      >
+                        <InputNumber placeholder="端口号" disabled={disabled} min={1} max={65535} />
+                      </Form.Item>
+                    </Col>
+                    <Col span={4} offset={1}>
+                      <Form.Item
+                        style={{ marginBottom: 0 }}
+                        name={[field.name, 'weight']}
+                        rules={[{ required: true, message: '请输入权重' }]}
+                      >
+                        <InputNumber placeholder="权重" disabled={disabled} min={0} max={100} />
+                      </Form.Item>
+                    </Col>
+                    <Col>
+                      {fields.length > 1 ? (
+                        <MinusCircleOutlined
+                          style={{ margin: '0 8px' }}
+                          onClick={() => {
+                            remove(field.name);
+                          }}
+                        />
+                      ) : null}
+                    </Col>
+                  </Row>
+                </Form.Item>
+              );
+            })}
+            <Form.Item {...FORM_ITEM_WITHOUT_LABEL}>
+              {!disabled && (
+                <Button
+                  type="dashed"
+                  onClick={() => {
+                    add();
+                  }}
+                >
+                  <PlusOutlined /> 增加
+                </Button>
+              )}
+            </Form.Item>
+          </div>
+        );
+      }}
+    </Form.List>
+  );
 
   return (
     <PanelSection title="请求改写">
@@ -57,47 +100,44 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange })
         className={styles.stepForm}
         initialValues={step2Data}
       >
-        <Form.Item label="协议" name="protocol" rules={[{ required: true, message: '请勾选协议' }]}>
-          <Row>
-            <Radio.Group
-              onChange={onProtocolChange}
-              name="backendProtocol"
-              value={backendProtocol}
-              disabled={disabled}
-            >
-              <Radio value="originalRequest">原始请求</Radio>
-              <Radio value="HTTP">HTTP</Radio>
-              <Radio value="HTTPS">HTTPS</Radio>
-            </Radio.Group>
-          </Row>
-        </Form.Item>
-        <Form.Item label="请求地址" rules={[{ required: true, message: '请输入后端地址' }]}>
-          {renderBackendAddress()}
-          {!disabled && (
-            <Button
-              type="primary"
-              onClick={() => {
-                onChange({
-                  backendAddressList: backendAddressList.concat({ host: '', port: 0, weight: 0 }),
-                });
-              }}
-            >
-              增加
-            </Button>
-          )}
+        <Form.Item
+          label="协议"
+          name="backendProtocol"
+          rules={[{ required: true, message: '请勾选协议' }]}
+        >
+          <Radio.Group onChange={onProtocolChange} name="backendProtocol" disabled={disabled}>
+            <Radio value="originalRequest">原始请求</Radio>
+            <Radio value="HTTP">HTTP</Radio>
+            <Radio value="HTTPS">HTTPS</Radio>
+          </Radio.Group>
         </Form.Item>
-        <Form.Item label="请求路径">
-          <Row>
-            <Input disabled={disabled} />
-          </Row>
+        {renderBackendAddress()}
+        <Form.Item
+          label="请求路径"
+          name="backendAddressPath"
+          rules={[{ required: true, message: '请输入请求路径' }]}
+        >
+          <Input disabled={disabled} />
         </Form.Item>
-        <Form.Item label="连接超时">
+        <Form.Item
+          label="连接超时"
+          name={['timeout', 'connect']}
+          rules={[{ required: true, message: '请输入连接超时' }]}
+        >
           <InputNumber disabled={disabled} defaultValue={30000} /> ms
         </Form.Item>
-        <Form.Item label="发送超时">
+        <Form.Item
+          label="发送超时"
+          name={['timeout', 'send']}
+          rules={[{ required: true, message: '请输入发送超时' }]}
+        >
           <InputNumber disabled={disabled} defaultValue={30000} /> ms
         </Form.Item>
-        <Form.Item label="接收超时">
+        <Form.Item
+          label="接收超时"
+          name={['timeout', 'read']}
+          rules={[{ required: true, message: '请输入接收超时' }]}
+        >
           <InputNumber disabled={disabled} defaultValue={30000} /> ms
         </Form.Item>
       </Form>
diff --git a/src/pages/Routes/components/Step2/index.tsx b/src/pages/Routes/components/Step2/index.tsx
index e3c3dcc..ff4d918 100644
--- a/src/pages/Routes/components/Step2/index.tsx
+++ b/src/pages/Routes/components/Step2/index.tsx
@@ -1,15 +1,12 @@
 import React from 'react';
-import { Form } from 'antd';
 
 import RequestRewriteView from './RequestRewriteView';
 import HttpHeaderRewriteView from './HttpHeaderRewriteView';
 
 const Step2: React.FC<RouteModule.Data> = (props) => {
-  const [form] = Form.useForm();
-
   return (
     <>
-      <RequestRewriteView form={form} {...props} />
+      <RequestRewriteView form={props.form} {...props} />
       <HttpHeaderRewriteView {...props} />
     </>
   );
diff --git a/src/pages/Routes/constants.ts b/src/pages/Routes/constants.ts
index 5bc45a0..c2473a8 100644
--- a/src/pages/Routes/constants.ts
+++ b/src/pages/Routes/constants.ts
@@ -9,10 +9,17 @@ export const FORM_ITEM_LAYOUT = {
   },
 };
 
+export const FORM_ITEM_WITHOUT_LABEL = {
+  wrapperCol: {
+    xs: { span: 24, offset: 0 },
+    sm: { span: 20, offset: 6 },
+  },
+};
+
 export const DEFAULT_STEP_1_DATA: RouteModule.Step1Data = {
   name: '',
   protocols: ['HTTP', 'HTTPS'],
-  hosts: [],
+  hosts: [''],
   paths: [],
   httpMethods: [],
   advancedMatchingRules: [],
@@ -22,6 +29,7 @@ export const DEFAULT_STEP_2_DATA: RouteModule.Step2Data = {
   backendProtocol: 'originalRequest',
   backendAddressList: [{ host: '', port: 0, weight: 0 }],
   upstream_header: [],
+  backendAddressPath: '',
   timeout: {
     connect: 30000,
     send: 30000,
diff --git a/src/pages/Routes/typing.d.ts b/src/pages/Routes/typing.d.ts
index 6a340d7..f766abf 100644
--- a/src/pages/Routes/typing.d.ts
+++ b/src/pages/Routes/typing.d.ts
@@ -33,6 +33,7 @@ declare namespace RouteModule {
       step3Data: Step3Data;
     };
     onChange(data: T): void;
+    form?: any;
   }
 
   type backendAddressItemProps = {
@@ -51,6 +52,7 @@ declare namespace RouteModule {
   type Step2Data = {
     backendProtocol: 'HTTP' | 'HTTPS' | 'originalRequest';
     backendAddressList: backendAddressItemProps[];
+    backendAddressPath: string;
     upstream_header: UpstreamHeader[];
     timeout: {
       connect: number;