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/09/21 11:16:43 UTC

[apisix-dashboard] branch master updated: feat: improve route (#483)

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 234e1ea  feat: improve route (#483)
234e1ea is described below

commit 234e1ea51b55527f9bd9367dc448405fb1095270
Author: litesun <7s...@gmail.com>
AuthorDate: Mon Sep 21 19:16:33 2020 +0800

    feat: improve route (#483)
    
    * feat: improve step1
    
    * feat: improve step2
    
    * feat: improve createStep4
    
    * feat: improve transform
    
    * fix: event loop
    
    * feat: clean code
    
    * fix: lost route_group info when enable redirect
    
    * feat: UI improve
---
 src/pages/Route/Create.tsx                         | 143 +++++++--------
 .../Route/components/CreateStep4/CreateStep4.tsx   |  24 +--
 .../Route/components/Step1/MatchingRulesView.tsx   |  25 +--
 src/pages/Route/components/Step1/MetaView.tsx      |  30 ++--
 .../Route/components/Step1/RequestConfigView.tsx   |  99 ++++++-----
 src/pages/Route/components/Step1/index.tsx         |  23 +--
 .../components/Step2/HttpHeaderRewriteView.tsx     |  21 ++-
 .../Route/components/Step2/RequestRewriteView.tsx  | 191 +++++++++++----------
 src/pages/Route/components/Step2/index.tsx         |   7 +-
 src/pages/Route/constants.ts                       |  10 +-
 src/pages/Route/service.ts                         |   4 +-
 src/pages/Route/transform.ts                       | 103 ++++++-----
 src/pages/Route/typing.d.ts                        | 131 ++++++++------
 13 files changed, 431 insertions(+), 380 deletions(-)

diff --git a/src/pages/Route/Create.tsx b/src/pages/Route/Create.tsx
index 40c6365..759d775 100644
--- a/src/pages/Route/Create.tsx
+++ b/src/pages/Route/Create.tsx
@@ -22,15 +22,7 @@ import { transformer as chartTransformer } from '@api7-dashboard/pluginchart';
 
 import ActionBar from '@/components/ActionBar';
 
-import {
-  create,
-  fetchItem,
-  fetchUpstreamItem,
-  fetchRouteGroupItem,
-  update,
-  checkUniqueName,
-  checkHostWithSSL
-} from './service';
+import { create, fetchItem, update, checkUniqueName, checkHostWithSSL } from './service';
 import Step1 from './components/Step1';
 import Step2 from './components/Step2';
 import Step3 from './components/Step3';
@@ -68,10 +60,11 @@ const Page: React.FC<Props> = (props) => {
     formatMessage({ id: 'route.constants.preview' }),
   ];
 
-  const [step1Data, setStep1Data] = useState(DEFAULT_STEP_1_DATA);
-  const [step2Data, setStep2Data] = useState(DEFAULT_STEP_2_DATA);
+  const [advancedMatchingRules, setAdvancedMatchingRules] = useState<RouteModule.MatchingRule[]>(
+    [],
+  );
+  const [upstreamHeaderList, setUpstreamHeaderList] = useState<RouteModule.UpstreamHeader[]>([]);
   const [step3Data, setStep3Data] = useState(DEFAULT_STEP_3_DATA);
-
   const [redirect, setRedirect] = useState(false);
 
   const [form1] = Form.useForm();
@@ -79,69 +72,51 @@ const Page: React.FC<Props> = (props) => {
 
   const [step, setStep] = useState(1);
   const [stepHeader, setStepHeader] = useState(STEP_HEADER_4);
-
   const [chart, setChart] = useState(INIT_CHART);
 
-  const routeData = {
-    step1Data,
-    step2Data,
-    step3Data,
-  };
   const setupRoute = (rid: number) =>
     fetchItem(rid).then((data) => {
-      form1.setFieldsValue(data.step1Data);
-      setStep1Data(data.step1Data as RouteModule.Step1Data);
-
-      form2.setFieldsValue(data.step2Data);
-      setStep2Data(data.step2Data);
-
+      form1.setFieldsValue(data.form1Data);
+      setAdvancedMatchingRules(data.advancedMatchingRules);
+      form2.setFieldsValue(data.form2Data);
+      setUpstreamHeaderList(data.upstreamHeaderList);
       setStep3Data(data.step3Data);
     });
 
-  useEffect(() => {
-    if (props.route.path.indexOf('edit') !== -1) {
-      setupRoute(props.match.params.rid);
-    }
-  }, []);
-
-  useEffect(() => {
-    const { redirectOption } = step1Data;
-
-    if (redirectOption === 'customRedirect') {
-      setRedirect(true);
-      setStepHeader(STEP_HEADER_2);
-      return;
-    }
-    setRedirect(false);
-    setStepHeader(STEP_HEADER_4);
-  }, [step1Data]);
-
   const onReset = () => {
-    setStep1Data(DEFAULT_STEP_1_DATA);
-    setStep2Data(DEFAULT_STEP_2_DATA);
+    setAdvancedMatchingRules([]);
+    setUpstreamHeaderList([]);
     setStep3Data(DEFAULT_STEP_3_DATA);
-
     form1.setFieldsValue(DEFAULT_STEP_1_DATA);
     form2.setFieldsValue(DEFAULT_STEP_2_DATA);
     setStep(1);
   };
 
+  useEffect(() => {
+    if (props.route.path.indexOf('edit') !== -1) {
+      setupRoute(props.match.params.rid);
+    } else {
+      onReset();
+    }
+  }, []);
+
   const renderStep = () => {
     if (step === 1) {
       return (
         <Step1
-          data={routeData}
           form={form1}
-          onChange={(params: RouteModule.Step1Data) => {
-            if (params.route_group_id) {
-              fetchRouteGroupItem(params.route_group_id).then((data) => {
-                form1.setFieldsValue({
-                  ...form1.getFieldsValue(),
-                  ...data,
-                });
-              });
+          advancedMatchingRules={advancedMatchingRules}
+          onChange={({ action, data }) => {
+            if (action === 'redirectOptionChange' && data === 'customRedirect') {
+              setStepHeader(STEP_HEADER_2);
+              setRedirect(true);
+            } else {
+              setStepHeader(STEP_HEADER_4);
+              setRedirect(false);
+            }
+            if (action === 'advancedMatchingRulesChange') {
+              setAdvancedMatchingRules(data);
             }
-            setStep1Data({ ...form1.getFieldsValue(), ...step1Data, ...params });
           }}
           isEdit={props.route.path.indexOf('edit') > 0}
         />
@@ -151,34 +126,25 @@ const Page: React.FC<Props> = (props) => {
     if (step === 2) {
       if (redirect) {
         return (
-          <CreateStep4 data={routeData} form1={form1} form2={form2} onChange={() => {}} redirect />
+          <CreateStep4
+            advancedMatchingRules={advancedMatchingRules}
+            upstreamHeaderList={upstreamHeaderList}
+            form1={form1}
+            form2={form2}
+            step3Data={step3Data}
+            redirect
+          />
         );
       }
 
       return (
         <Step2
-          data={routeData}
+          upstreamHeaderList={upstreamHeaderList}
           form={form2}
-          onChange={(params: RouteModule.Step2Data) => {
-            if (params.upstream_id) {
-              fetchUpstreamItem(params.upstream_id).then((data) => {
-                form2.setFieldsValue({
-                  ...form2.getFieldsValue(),
-                  ...data,
-                });
-                setStep2Data({
-                  ...step2Data,
-                  ...form2.getFieldsValue(),
-                  ...params,
-                } as RouteModule.Step2Data);
-              });
-              return;
+          onChange={({ action, data }) => {
+            if (action === 'upstreamHeaderListChange') {
+              setUpstreamHeaderList(data);
             }
-            setStep2Data({
-              ...step2Data,
-              ...form2.getFieldsValue(),
-              ...params,
-            } as RouteModule.Step2Data);
           }}
         />
       );
@@ -197,7 +163,15 @@ const Page: React.FC<Props> = (props) => {
     }
 
     if (step === 4) {
-      return <CreateStep4 data={routeData} form1={form1} form2={form2} onChange={() => {}} />;
+      return (
+        <CreateStep4
+          advancedMatchingRules={advancedMatchingRules}
+          upstreamHeaderList={upstreamHeaderList}
+          form1={form1}
+          form2={form2}
+          step3Data={step3Data}
+        />
+      );
     }
 
     if (step === 5) {
@@ -216,13 +190,20 @@ const Page: React.FC<Props> = (props) => {
   };
 
   const onStepChange = (nextStep: number) => {
+    const routeData = {
+      form1Data: form1.getFieldsValue(),
+      form2Data: form2.getFieldsValue(),
+      step3Data,
+      upstreamHeaderList,
+      advancedMatchingRules,
+    } as RouteModule.RequestData;
     const onUpdateOrCreate = () => {
       if (props.route.path.indexOf('edit') !== -1) {
-        update((props as any).match.params.rid, { data: routeData }).then(() => {
+        update((props as any).match.params.rid, routeData).then(() => {
           setStep(5);
         });
       } else {
-        create({ data: routeData }).then(() => {
+        create(routeData).then(() => {
           setStep(5);
         });
       }
@@ -249,7 +230,6 @@ const Page: React.FC<Props> = (props) => {
             redirectOption === 'forceHttps' ? checkHostWithSSL(hosts) : Promise.resolve(),
             checkUniqueName(value.name, (props as any).match.params.rid || ''),
           ]).then(() => {
-            setStep1Data({ ...step1Data, ...value });
             setStep(nextStep);
           });
         });
@@ -265,8 +245,7 @@ const Page: React.FC<Props> = (props) => {
         onUpdateOrCreate();
         return;
       }
-      form2.validateFields().then((value) => {
-        setStep2Data({ ...step2Data, ...value });
+      form2.validateFields().then(() => {
         setStep(nextStep);
       });
       return;
diff --git a/src/pages/Route/components/CreateStep4/CreateStep4.tsx b/src/pages/Route/components/CreateStep4/CreateStep4.tsx
index 1d6928a..6b28150 100644
--- a/src/pages/Route/components/CreateStep4/CreateStep4.tsx
+++ b/src/pages/Route/components/CreateStep4/CreateStep4.tsx
@@ -23,11 +23,14 @@ import PluginOrchestration from '@api7-dashboard/pluginchart';
 import Step1 from '../Step1';
 import Step2 from '../Step2';
 
-interface Props extends RouteModule.Data {
+type Props = {
   form1: FormInstance;
   form2: FormInstance;
   redirect?: boolean;
-}
+  step3Data: RouteModule.Step3Data;
+  advancedMatchingRules: RouteModule.MatchingRule[];
+  upstreamHeaderList: RouteModule.UpstreamHeader[];
+};
 
 const style = {
   marginTop: '40px',
@@ -35,7 +38,7 @@ const style = {
 
 const CreateStep4: React.FC<Props> = ({ form1, form2, redirect, ...rest }) => {
   const { formatMessage } = useIntl();
-  const { plugins = {}, script = {} } = rest.data.step3Data;
+  const { plugins = {}, script = {} } = rest.step3Data;
 
   return (
     <>
@@ -44,17 +47,18 @@ const CreateStep4: React.FC<Props> = ({ form1, form2, redirect, ...rest }) => {
       {!redirect && (
         <>
           <h2 style={style}>{formatMessage({ id: 'route.create.define.api.backend.server' })}</h2>
-          <Step2 {...rest} form={form2} disabled />
+          <Step2
+            upstreamHeaderList={rest.upstreamHeaderList}
+            form={form2}
+            disabled
+            onChange={() => {}}
+          />
           <h2 style={style}>{formatMessage({ id: 'route.create.plugin.configuration' })}</h2>
           {Boolean(Object.keys(plugins).length !== 0) && (
-            <PluginPage initialData={rest.data.step3Data.plugins} readonly />
+            <PluginPage initialData={rest.step3Data.plugins} readonly />
           )}
           {Boolean(Object.keys(script).length !== 0) && (
-            <PluginOrchestration
-              data={rest.data.step3Data.script.chart}
-              readonly
-              onChange={() => {}}
-            />
+            <PluginOrchestration data={rest.step3Data.script.chart} readonly onChange={() => {}} />
           )}
         </>
       )}
diff --git a/src/pages/Route/components/Step1/MatchingRulesView.tsx b/src/pages/Route/components/Step1/MatchingRulesView.tsx
index ed42f45..5d571c6 100644
--- a/src/pages/Route/components/Step1/MatchingRulesView.tsx
+++ b/src/pages/Route/components/Step1/MatchingRulesView.tsx
@@ -19,11 +19,11 @@ import { Button, Table, Modal, Form, Select, Input, Space } from 'antd';
 import { useIntl } from 'umi';
 import { PanelSection } from '@api7-dashboard/ui';
 
-interface Props extends RouteModule.Data {}
-
-const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => {
-  const { advancedMatchingRules } = data.step1Data;
-
+const MatchingRulesView: React.FC<RouteModule.Step1PassProps> = ({
+  advancedMatchingRules,
+  disabled,
+  onChange = () => {},
+}) => {
   const [visible, setVisible] = useState(false);
   const [mode, setMode] = useState<RouteModule.ModalType>('CREATE');
   const [namePlaceholder, setNamePlaceholder] = useState('');
@@ -38,8 +38,8 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => {
       if (mode === 'EDIT') {
         const key = modalForm.getFieldValue('key');
         onChange({
-          ...data.step1Data,
-          advancedMatchingRules: advancedMatchingRules.map((rule) => {
+          action: 'advancedMatchingRulesChange',
+          data: advancedMatchingRules.map((rule) => {
             if (rule.key === key) {
               return { ...(value as RouteModule.MatchingRule), key };
             }
@@ -51,7 +51,10 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => {
           ...(value as RouteModule.MatchingRule),
           key: Math.random().toString(36).slice(2),
         };
-        onChange({ ...data.step1Data, advancedMatchingRules: advancedMatchingRules.concat(rule) });
+        onChange({
+          action: 'advancedMatchingRulesChange',
+          data: advancedMatchingRules.concat(rule),
+        });
       }
       modalForm.resetFields();
       setVisible(false);
@@ -66,8 +69,8 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => {
 
   const handleRemove = (key: string) => {
     onChange({
-      ...data.step1Data,
-      advancedMatchingRules: advancedMatchingRules.filter((item) => item.key !== key),
+      action: 'advancedMatchingRulesChange',
+      data: advancedMatchingRules.filter((item) => item.key !== key),
     });
   };
 
@@ -164,7 +167,7 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => {
       cancelText={formatMessage({ id: 'route.match.cancel' })}
       destroyOnClose
     >
-      <Form form={modalForm} labelCol={{ span: 9 }} wrapperCol={{ span: 15 }}>
+      <Form form={modalForm} labelCol={{ span: 4 }}>
         <Form.Item
           label={formatMessage({ id: 'route.match.parameter.position' })}
           name="position"
diff --git a/src/pages/Route/components/Step1/MetaView.tsx b/src/pages/Route/components/Step1/MetaView.tsx
index a90faac..4c87fc4 100644
--- a/src/pages/Route/components/Step1/MetaView.tsx
+++ b/src/pages/Route/components/Step1/MetaView.tsx
@@ -20,15 +20,14 @@ import { Input, Select, Switch } from 'antd';
 import { useIntl } from 'umi';
 import { PanelSection } from '@api7-dashboard/ui';
 
-import { fetchRouteGroupList } from '@/pages/Route/service';
+import { fetchRouteGroupList, fetchRouteGroupItem } from '@/pages/Route/service';
 
-interface Props extends RouteModule.Data {}
-
-const MetaView: React.FC<Props> = ({ data, disabled, onChange, isEdit }) => {
-  const { step1Data } = data;
+const MetaView: React.FC<RouteModule.Step1PassProps> = ({ form, disabled, isEdit }) => {
   const { formatMessage } = useIntl();
-  const routeGroupDisabled = disabled || !!step1Data.route_group_id;
+
   const [routeGroups, setRouteGroups] = useState<{ id: string; name: string }[]>();
+  let routeGroupDisabled = disabled || Boolean(form.getFieldValue('route_group_id'));
+
   useEffect(() => {
     // eslint-disable-next-line no-shadow
     fetchRouteGroupList().then(({ data }) => {
@@ -36,11 +35,9 @@ const MetaView: React.FC<Props> = ({ data, disabled, onChange, isEdit }) => {
         { name: formatMessage({ id: 'route.meta.api.create.group.name' }), id: null },
         ...data,
       ]);
-      if (step1Data.route_group_id) {
-        onChange({ route_group_id: step1Data.route_group_id });
-      }
     });
   }, []);
+
   return (
     <PanelSection title={formatMessage({ id: 'route.meta.name.description' })}>
       <Form.Item
@@ -63,9 +60,20 @@ const MetaView: React.FC<Props> = ({ data, disabled, onChange, isEdit }) => {
       <Form.Item label={formatMessage({ id: 'route.meta.api.group.name' })} name="route_group_id">
         <Select
           onChange={(value) => {
-            if (step1Data.route_group_id) {
-              onChange({ route_group_id: value });
+            if (!value) {
+              form.setFieldsValue({
+                ...form.getFieldsValue(),
+                route_group_name: '',
+              });
+              return;
             }
+            fetchRouteGroupItem(value.toString()).then((data) => {
+              form.setFieldsValue({
+                ...form.getFieldsValue(),
+                ...data,
+              });
+              routeGroupDisabled = true;
+            });
           }}
           disabled={disabled}
         >
diff --git a/src/pages/Route/components/Step1/RequestConfigView.tsx b/src/pages/Route/components/Step1/RequestConfigView.tsx
index 93481a6..236e4e6 100644
--- a/src/pages/Route/components/Step1/RequestConfigView.tsx
+++ b/src/pages/Route/components/Step1/RequestConfigView.tsx
@@ -18,7 +18,6 @@ import React from 'react';
 import Form from 'antd/es/form';
 import { Checkbox, Button, Input, Switch, Select, Row, Col } from 'antd';
 import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
-import { CheckboxValueType } from 'antd/lib/checkbox/Group';
 import { useIntl } from 'umi';
 import { PanelSection } from '@api7-dashboard/ui';
 
@@ -28,15 +27,11 @@ import {
   FORM_ITEM_WITHOUT_LABEL,
 } from '@/pages/Route/constants';
 
-interface Props extends RouteModule.Data {}
-
-const RequestConfigView: React.FC<Props> = ({ data, disabled, onChange }) => {
-  const { step1Data } = data;
-  const { protocols } = step1Data;
-  const onProtocolChange = (e: CheckboxValueType[]) => {
-    if (!e.includes('http') && !e.includes('https')) return;
-    onChange({ ...data.step1Data, protocols: e });
-  };
+const RequestConfigView: React.FC<RouteModule.Step1PassProps> = ({
+  form,
+  disabled,
+  onChange = () => {},
+}) => {
   const { formatMessage } = useIntl();
   const renderHosts = () => (
     <Form.List name="hosts">
@@ -182,12 +177,7 @@ const RequestConfigView: React.FC<Props> = ({ data, disabled, onChange }) => {
           },
         ]}
       >
-        <Checkbox.Group
-          disabled={disabled}
-          options={['http', 'https']}
-          value={protocols}
-          onChange={onProtocolChange}
-        />
+        <Checkbox.Group disabled={disabled} options={['http', 'https']} />
       </Form.Item>
       <Form.Item label="WebSocket" name="websocket" valuePropName="checked">
         <Switch disabled={disabled} />
@@ -218,7 +208,12 @@ const RequestConfigView: React.FC<Props> = ({ data, disabled, onChange }) => {
         label={formatMessage({ id: 'route.request.config.redirect' })}
         name="redirectOption"
       >
-        <Select disabled={disabled}>
+        <Select
+          disabled={disabled}
+          onChange={(parmas) => {
+            onChange({ action: 'redirectOptionChange', data: parmas });
+          }}
+        >
           <Select.Option value="forceHttps">
             {formatMessage({ id: 'route.request.config.enable.https' })}
           </Select.Option>
@@ -230,34 +225,50 @@ const RequestConfigView: React.FC<Props> = ({ data, disabled, onChange }) => {
           </Select.Option>
         </Select>
       </Form.Item>
-      {step1Data.redirectOption === 'customRedirect' && (
-        <Form.Item label={formatMessage({ id: 'route.request.config.redirect.custom' })} required>
-          <Row gutter={10}>
-            <Col>
-              <Form.Item name="redirectURI" rules={[{ required: true }]}>
-                <Input
-                  placeholder={formatMessage({
-                    id: 'route.request.config.redirect.custom.example',
-                  })}
-                  disabled={disabled}
-                />
-              </Form.Item>
-            </Col>
-            <Col span={10}>
-              <Form.Item name="redirectCode" rules={[{ required: true }]}>
-                <Select disabled={disabled}>
-                  <Select.Option value={301}>
-                    {formatMessage({ id: 'route.request.config.redirect.301' })}
-                  </Select.Option>
-                  <Select.Option value={302}>
-                    {formatMessage({ id: 'route.request.config.redirect.302' })}
-                  </Select.Option>
-                </Select>
+      <Form.Item
+        noStyle
+        shouldUpdate={(prev, next) => {
+          onChange({ action: 'redirectOptionChange', data: next.redirectOption });
+          return prev.redirectOption !== next.redirectOption;
+        }}
+      >
+        {() => {
+          if (form.getFieldValue('redirectOption') === 'customRedirect') {
+            return (
+              <Form.Item
+                label={formatMessage({ id: 'route.request.config.redirect.custom' })}
+                required
+              >
+                <Row gutter={10}>
+                  <Col>
+                    <Form.Item name="redirectURI" rules={[{ required: true }]}>
+                      <Input
+                        placeholder={formatMessage({
+                          id: 'route.request.config.redirect.custom.example',
+                        })}
+                        disabled={disabled}
+                      />
+                    </Form.Item>
+                  </Col>
+                  <Col span={10}>
+                    <Form.Item name="redirectCode" rules={[{ required: true }]}>
+                      <Select disabled={disabled}>
+                        <Select.Option value={301}>
+                          {formatMessage({ id: 'route.request.config.redirect.301' })}
+                        </Select.Option>
+                        <Select.Option value={302}>
+                          {formatMessage({ id: 'route.request.config.redirect.302' })}
+                        </Select.Option>
+                      </Select>
+                    </Form.Item>
+                  </Col>
+                </Row>
               </Form.Item>
-            </Col>
-          </Row>
-        </Form.Item>
-      )}
+            );
+          }
+          return null;
+        }}
+      </Form.Item>
     </PanelSection>
   );
 };
diff --git a/src/pages/Route/components/Step1/index.tsx b/src/pages/Route/components/Step1/index.tsx
index e73b98e..9f8c07f 100644
--- a/src/pages/Route/components/Step1/index.tsx
+++ b/src/pages/Route/components/Step1/index.tsx
@@ -16,7 +16,6 @@
  */
 import React from 'react';
 import { Form } from 'antd';
-import { FormInstance } from 'antd/lib/form';
 import { FORM_ITEM_LAYOUT } from '@/pages/Route/constants';
 import styles from '../../Create.less';
 
@@ -24,28 +23,10 @@ import MetaView from './MetaView';
 import RequestConfigView from './RequestConfigView';
 import MatchingRulesView from './MatchingRulesView';
 
-interface Props extends RouteModule.Data {
-  form: FormInstance;
-  isEdit?: boolean;
-}
-
-const Step1: React.FC<Props> = (props) => {
-  const { data, form, onChange } = props;
+const Step1: React.FC<RouteModule.Step1PassProps> = (props) => {
   return (
     <>
-      <Form
-        {...FORM_ITEM_LAYOUT}
-        form={form}
-        layout="horizontal"
-        className={styles.stepForm}
-        onValuesChange={(field) => {
-          if (field.redirectOption === 'forceHttps' || field.redirectOption === 'disabled') {
-            form.setFieldsValue({ redirectURI: '' });
-          }
-          onChange({ ...data.step1Data, ...field });
-        }}
-        initialValues={data.step1Data}
-      >
+      <Form {...FORM_ITEM_LAYOUT} form={props.form} layout="horizontal" className={styles.stepForm}>
         <MetaView {...props} />
         <RequestConfigView {...props} />
       </Form>
diff --git a/src/pages/Route/components/Step2/HttpHeaderRewriteView.tsx b/src/pages/Route/components/Step2/HttpHeaderRewriteView.tsx
index 543db05..bfc4901 100644
--- a/src/pages/Route/components/Step2/HttpHeaderRewriteView.tsx
+++ b/src/pages/Route/components/Step2/HttpHeaderRewriteView.tsx
@@ -20,10 +20,11 @@ import { Button, Table, Space, Modal, Input, Select } from 'antd';
 import { useIntl } from 'umi';
 import { PanelSection } from '@api7-dashboard/ui';
 
-interface Props extends RouteModule.Data {}
-
-const HttpHeaderRewriteView: React.FC<Props> = ({ data, disabled, onChange }) => {
-  const { upstreamHeaderList = [] } = data.step2Data;
+const HttpHeaderRewriteView: React.FC<RouteModule.Step2PassProps> = ({
+  upstreamHeaderList = [],
+  disabled,
+  onChange,
+}) => {
   const [visible, setVisible] = useState(false);
   const [modalForm] = Form.useForm();
   const [mode, setMode] = useState<RouteModule.ModalType>('CREATE');
@@ -36,7 +37,10 @@ const HttpHeaderRewriteView: React.FC<Props> = ({ data, disabled, onChange }) =>
     modalForm.setFieldsValue(record);
   };
   const handleRemove = (key: string) => {
-    onChange({ upstreamHeaderList: upstreamHeaderList.filter((item) => item.key !== key) });
+    onChange({
+      action: 'upstreamHeaderListChange',
+      data: upstreamHeaderList.filter((item) => item.key !== key),
+    });
   };
 
   const columns = [
@@ -92,17 +96,18 @@ const HttpHeaderRewriteView: React.FC<Props> = ({ data, disabled, onChange }) =>
         if (mode === 'EDIT') {
           const key = modalForm.getFieldValue('key');
           onChange({
-            upstreamHeaderList: upstreamHeaderList.map((item) => {
+            action: 'upstreamHeaderListChange',
+            data: upstreamHeaderList.map((item) => {
               if (item.key === key) {
                 return { ...(value as RouteModule.UpstreamHeader), key };
               }
               return item;
             }),
-            key,
           });
         } else {
           onChange({
-            upstreamHeaderList: upstreamHeaderList.concat({
+            action: 'upstreamHeaderListChange',
+            data: upstreamHeaderList.concat({
               ...(value as RouteModule.UpstreamHeader),
               key: Math.random().toString(36).slice(2),
             }),
diff --git a/src/pages/Route/components/Step2/RequestRewriteView.tsx b/src/pages/Route/components/Step2/RequestRewriteView.tsx
index 12ad7af..7294b0c 100644
--- a/src/pages/Route/components/Step2/RequestRewriteView.tsx
+++ b/src/pages/Route/components/Step2/RequestRewriteView.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 import React, { useEffect, useState } from 'react';
-import Form, { FormInstance } from 'antd/es/form';
+import Form from 'antd/es/form';
 import Radio from 'antd/lib/radio';
 import { Input, Row, Col, InputNumber, Button, Select } from 'antd';
 import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
@@ -29,15 +29,23 @@ import {
   HASH_ON_LIST,
 } from '@/pages/Route/constants';
 import styles from '../../Create.less';
-import { fetchUpstreamList } from '../../service';
+import { fetchUpstreamList, fetchUpstreamItem } from '../../service';
 
-interface Props extends RouteModule.Data {
-  form: FormInstance;
-}
-const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange }) => {
-  const { step2Data } = data;
+const RequestRewriteView: React.FC<RouteModule.Step2PassProps> = ({ form, disabled }) => {
   const [upstearms, setUpstreams] = useState<{ id: string; name: string }[]>();
-  const upstreamDisabled = disabled || !!step2Data.upstream_id;
+  const [upstreamId, setUpstreamId] = useState(form.getFieldValue('upstream_id'));
+  // TODO: need to check
+  let upstreamDisabled = disabled || Boolean(form.getFieldValue('upstream_id'));
+
+  if (upstreamId) {
+    fetchUpstreamItem(upstreamId).then((data) => {
+      form.setFieldsValue({
+        ...form.getFieldsValue(),
+        ...data,
+      });
+      upstreamDisabled = true;
+    });
+  }
   const { formatMessage } = useIntl();
 
   useEffect(() => {
@@ -47,41 +55,45 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange })
         { name: formatMessage({ id: 'route.request.override.input' }), id: null },
         ...data,
       ]);
-      if (step2Data.upstream_id) {
-        onChange({ upstream_id: step2Data.upstream_id });
-      }
     });
   }, []);
   const renderUpstreamMeta = () => (
     <>
       <Form.Item label="类型" name="type" rules={[{ required: true }]}>
-        <Select disabled={upstreamDisabled} onChange={(params) => onChange({ type: params })}>
+        <Select disabled={upstreamDisabled}>
           <Select.Option value="roundrobin">roundrobin</Select.Option>
           <Select.Option value="chash">chash</Select.Option>
         </Select>
       </Form.Item>
-      {step2Data.type === 'chash' && (
-        <>
-          <Form.Item label="Hash On" name="hash_on">
-            <Select disabled={upstreamDisabled}>
-              {HASH_ON_LIST.map((item) => (
-                <Select.Option value={item} key={item}>
-                  {item}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-          <Form.Item label="Key" name="key">
-            <Select disabled={upstreamDisabled}>
-              {HASH_KEY_LIST.map((item) => (
-                <Select.Option value={item} key={item}>
-                  {item}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-        </>
-      )}
+      <Form.Item noStyle shouldUpdate={(prev, next) => prev.type !== next.type}>
+        {() => {
+          if (form.getFieldValue('type') === 'chash') {
+            return (
+              <>
+                <Form.Item label="Hash On" name="hash_on">
+                  <Select disabled={upstreamDisabled}>
+                    {HASH_ON_LIST.map((item) => (
+                      <Select.Option value={item} key={item}>
+                        {item}
+                      </Select.Option>
+                    ))}
+                  </Select>
+                </Form.Item>
+                <Form.Item label="Key" name="key">
+                  <Select disabled={upstreamDisabled}>
+                    {HASH_KEY_LIST.map((item) => (
+                      <Select.Option value={item} key={item}>
+                        {item}
+                      </Select.Option>
+                    ))}
+                  </Select>
+                </Form.Item>
+              </>
+            );
+          }
+          return null;
+        }}
+      </Form.Item>
       <Form.List name="upstreamHostList">
         {(fields, { add, remove }) => (
           <>
@@ -209,13 +221,7 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange })
   const renderTimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
   return (
     <PanelSection title={formatMessage({ id: 'route.request.override' })}>
-      <Form
-        {...FORM_ITEM_LAYOUT}
-        form={form}
-        layout="horizontal"
-        className={styles.stepForm}
-        initialValues={step2Data}
-      >
+      <Form {...FORM_ITEM_LAYOUT} form={form} layout="horizontal" className={styles.stepForm}>
         <Form.Item
           label={formatMessage({ id: 'route.request.override.protocol' })}
           name="upstream_protocol"
@@ -226,71 +232,78 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange })
             },
           ]}
         >
-          <Radio.Group
-            onChange={(e) => {
-              onChange({ upstream_protocol: e.target.value });
-            }}
-            name="upstream_protocol"
-            disabled={disabled}
-          >
+          <Radio.Group name="upstream_protocol" disabled={disabled}>
             <Radio value="keep">{formatMessage({ id: 'route.request.override.stay.same' })}</Radio>
             <Radio value="http">HTTP</Radio>
             <Radio value="https">HTTPS</Radio>
           </Radio.Group>
         </Form.Item>
         <Form.Item label={formatMessage({ id: 'route.request.override.path' })} name="rewriteType">
-          <Radio.Group
-            onChange={(e) => {
-              onChange({ rewriteType: e.target.value });
-            }}
-            disabled={disabled}
-          >
+          <Radio.Group disabled={disabled}>
             <Radio value="keep">{formatMessage({ id: 'route.request.override.stay.same' })}</Radio>
             <Radio value="static">{formatMessage({ id: 'page.route.radio.static' })}</Radio>
             <Radio value="regx">{formatMessage({ id: 'page.route.radio.regx' })}</Radio>
           </Radio.Group>
         </Form.Item>
-        {step2Data.rewriteType === 'regx' && (
-          <Form.Item
-            label={formatMessage({ id: 'page.route.form.itemLabel.from' })}
-            name="mappingStrategy"
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'route.request.override.input.path' }),
-              },
-            ]}
-          >
-            <Input
-              disabled={disabled}
-              placeholder={formatMessage({ id: 'route.request.override.path.example' })}
-            />
-          </Form.Item>
-        )}
-        {(step2Data.rewriteType === 'static' || step2Data.rewriteType === 'regx') && (
-          <Form.Item
-            label={formatMessage({ id: 'route.request.override.new.path' })}
-            name="upstreamPath"
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'route.request.override.input.path' }),
-              },
-            ]}
-          >
-            <Input
-              disabled={disabled}
-              placeholder={formatMessage({ id: 'route.request.override.path.example' })}
-            />
-          </Form.Item>
-        )}
+        <Form.Item noStyle shouldUpdate={(prev, next) => prev.rewriteType !== next.rewriteType}>
+          {() => {
+            if (form.getFieldValue('rewriteType') === 'regx') {
+              return (
+                <Form.Item
+                  label={formatMessage({ id: 'page.route.form.itemLabel.from' })}
+                  name="mappingStrategy"
+                  rules={[
+                    {
+                      required: true,
+                      message: formatMessage({ id: 'route.request.override.input.path' }),
+                    },
+                  ]}
+                >
+                  <Input
+                    disabled={disabled}
+                    placeholder={formatMessage({ id: 'route.request.override.path.example' })}
+                  />
+                </Form.Item>
+              );
+            }
+            return null;
+          }}
+        </Form.Item>
+        <Form.Item noStyle shouldUpdate={(prev, next) => prev.rewriteType !== next.rewriteType}>
+          {() => {
+            if (
+              form.getFieldValue('rewriteType') === 'static' ||
+              form.getFieldValue('rewriteType') === 'regx'
+            ) {
+              return (
+                <Form.Item
+                  label={formatMessage({ id: 'route.request.override.new.path' })}
+                  name="upstreamPath"
+                  rules={[
+                    {
+                      required: true,
+                      message: formatMessage({ id: 'route.request.override.input.path' }),
+                    },
+                  ]}
+                >
+                  <Input
+                    disabled={disabled}
+                    placeholder={formatMessage({ id: 'route.request.override.path.example' })}
+                  />
+                </Form.Item>
+              );
+            }
+            return null;
+          }}
+        </Form.Item>
+
         <Form.Item
           label={formatMessage({ id: 'route.request.override.upstream' })}
           name="upstream_id"
         >
           <Select
             onChange={(value) => {
-              onChange({ upstream_id: value });
+              setUpstreamId(value);
             }}
             disabled={disabled}
           >
diff --git a/src/pages/Route/components/Step2/index.tsx b/src/pages/Route/components/Step2/index.tsx
index a5d8eb4..ba60b12 100644
--- a/src/pages/Route/components/Step2/index.tsx
+++ b/src/pages/Route/components/Step2/index.tsx
@@ -15,16 +15,11 @@
  * limitations under the License.
  */
 import React from 'react';
-import { FormInstance } from 'antd/es/form';
 
 import RequestRewriteView from './RequestRewriteView';
 import HttpHeaderRewriteView from './HttpHeaderRewriteView';
 
-interface Props extends RouteModule.Data {
-  form: FormInstance;
-}
-
-const Step2: React.FC<Props> = (props) => {
+const Step2: React.FC<RouteModule.Step2PassProps> = (props) => {
   return (
     <>
       <RequestRewriteView {...props} />
diff --git a/src/pages/Route/constants.ts b/src/pages/Route/constants.ts
index bf23e59..d421b57 100644
--- a/src/pages/Route/constants.ts
+++ b/src/pages/Route/constants.ts
@@ -40,8 +40,8 @@ export const FORM_ITEM_WITHOUT_LABEL = {
   },
 };
 
-export const DEFAULT_STEP_1_DATA: RouteModule.Step1Data = {
-  route_group_id: '',
+export const DEFAULT_STEP_1_DATA: RouteModule.Form1Data = {
+  route_group_id: null,
   route_group_name: '',
   name: '',
   desc: '',
@@ -55,13 +55,13 @@ export const DEFAULT_STEP_1_DATA: RouteModule.Step1Data = {
   redirectURI: '',
   redirectCode: 302,
   methods: HTTP_METHOD_OPTION_LIST,
-  advancedMatchingRules: [],
 };
 
-export const DEFAULT_STEP_2_DATA: RouteModule.Step2Data = {
+export const DEFAULT_STEP_2_DATA: RouteModule.Form2Data = {
   upstream_protocol: 'keep',
   upstreamHostList: [{} as RouteModule.UpstreamHost],
-  upstreamHeaderList: [],
+  upstream_id: null,
+  pass_host: 'pass',
   upstreamPath: undefined,
   type: 'roundrobin',
   mappingStrategy: undefined,
diff --git a/src/pages/Route/service.ts b/src/pages/Route/service.ts
index 92b7092..62046da 100644
--- a/src/pages/Route/service.ts
+++ b/src/pages/Route/service.ts
@@ -24,13 +24,13 @@ import {
   transformRouteDebugData,
 } from './transform';
 
-export const create = (data: Pick<RouteModule.Data, 'data'>) =>
+export const create = (data: RouteModule.RequestData) =>
   request(`/routes`, {
     method: 'POST',
     data: transformStepData(data),
   });
 
-export const update = (rid: number, data: Pick<RouteModule.Data, 'data'>) =>
+export const update = (rid: number, data: RouteModule.RequestData) =>
   request(`/routes/${rid}`, {
     method: 'PUT',
     data: transformStepData(data),
diff --git a/src/pages/Route/transform.ts b/src/pages/Route/transform.ts
index bc65add..6b07be3 100644
--- a/src/pages/Route/transform.ts
+++ b/src/pages/Route/transform.ts
@@ -18,49 +18,53 @@
 import { omit, pick } from 'lodash';
 
 export const transformStepData = ({
-  data: { step1Data, step2Data, step3Data },
-}: Pick<RouteModule.Data, 'data'>) => {
+  form1Data,
+  form2Data,
+  advancedMatchingRules,
+  upstreamHeaderList,
+  step3Data,
+}: RouteModule.RequestData) => {
   const nodes = {};
-  step2Data.upstreamHostList.forEach((node) => {
+  (form2Data.upstreamHostList || []).forEach((node) => {
     nodes[`${node.host}:${node.port}`] = node.weight;
   });
 
   const upstream_header = {};
-  (step2Data.upstreamHeaderList || []).forEach((header) => {
+  (upstreamHeaderList || []).forEach((header) => {
     upstream_header[header.header_name] = header.header_value || '';
   });
 
   const chashData: any = {};
-  if (step2Data.type === 'chash') {
-    chashData.key = step2Data.key;
-    chashData.hash_on = step2Data.hash_on;
+  if (form2Data.type === 'chash') {
+    chashData.key = form2Data.key;
+    chashData.hash_on = form2Data.hash_on;
   }
 
   let redirect: RouteModule.Redirect = {};
-  if (step1Data.redirectOption === 'disabled') {
+  if (form1Data.redirectOption === 'disabled') {
     redirect = {};
-  } else if (step1Data.redirectOption === 'forceHttps') {
+  } else if (form1Data.redirectOption === 'forceHttps') {
     redirect = { http_to_https: true };
-  } else if (step1Data.redirectURI !== '') {
+  } else if (form1Data.redirectURI !== '') {
     redirect = {
-      code: step1Data.redirectCode,
-      uri: step1Data.redirectURI,
+      code: form1Data.redirectCode,
+      uri: form1Data.redirectURI,
     };
   }
 
-  let { protocols } = step1Data;
-  if (step1Data.websocket) {
+  let { protocols } = form1Data;
+  if (form1Data.websocket) {
     protocols = protocols.concat('websocket');
   }
 
   const data: Partial<RouteModule.Body> = {
-    ...step1Data,
-    ...step2Data,
+    ...form1Data,
+    ...form2Data,
     ...step3Data,
     protocols,
-    uris: step1Data.paths,
+    uris: form1Data.paths,
     redirect,
-    vars: step1Data.advancedMatchingRules.map((rule) => {
+    vars: advancedMatchingRules.map((rule) => {
       const { operator, position, name, value } = rule;
       let key = '';
       switch (position) {
@@ -76,34 +80,33 @@ export const transformStepData = ({
       return [key, operator, value];
     }),
     upstream: {
-      type: step2Data.type,
+      type: form2Data.type,
       ...chashData,
       nodes,
-      timeout: step2Data.timeout,
+      timeout: form2Data.timeout,
     },
     upstream_header,
   };
 
-  if (step2Data.upstreamPath) {
+  if (form2Data.upstreamPath) {
     data.upstream_path = {
-      to: step2Data.upstreamPath,
+      to: form2Data.upstreamPath,
     };
-    if (step2Data.mappingStrategy) {
+    if (form2Data.mappingStrategy) {
       data.upstream_path = {
         ...data.upstream_path,
-        from: step2Data.mappingStrategy,
+        from: form2Data.mappingStrategy,
         type: 'regx',
       };
     }
   }
 
-  if (step3Data.plugins.prometheus) {
-    // eslint-disable-next-line no-param-reassign
-    step3Data.plugins.prometheus = {};
-  }
-
   // 未启用 redirect
   if (!redirect.uri) {
+    if (step3Data.plugins.prometheus) {
+      // eslint-disable-next-line no-param-reassign
+      step3Data.plugins.prometheus = {};
+    }
     // 移除前端部分自定义变量
     return omit(data, [
       'advancedMatchingRules',
@@ -118,12 +121,23 @@ export const transformStepData = ({
       'redirectCode',
       'forceHttps',
       'redirectOption',
-      step1Data.redirectOption === 'disabled' ? 'redirect' : '',
-      step2Data.upstream_id ? 'upstream' : 'upstream_id',
+      form1Data.redirectOption === 'disabled' ? 'redirect' : '',
+      form2Data.upstream_id ? 'upstream' : 'upstream_id',
     ]);
   }
 
-  return pick(data, ['name', 'desc', 'protocols', 'hosts', 'uris', 'methods', 'redirect', 'vars']);
+  return pick(data, [
+    'name',
+    'desc',
+    'protocols',
+    'hosts',
+    'uris',
+    'methods',
+    'redirect',
+    'vars',
+    'route_group_id',
+    'route_group_name',
+  ]);
 };
 
 const transformVarsToRules = (
@@ -169,7 +183,7 @@ export const transformRouteData = (data: RouteModule.Body) => {
     status,
   } = data;
 
-  const step1Data: Partial<RouteModule.Step1Data> = {
+  const form1Data: Partial<RouteModule.Form1Data> = {
     name,
     route_group_id,
     route_group_name,
@@ -180,17 +194,17 @@ export const transformRouteData = (data: RouteModule.Body) => {
     hosts,
     paths: uris,
     methods,
-    advancedMatchingRules: transformVarsToRules(vars),
   };
 
+  const advancedMatchingRules: RouteModule.MatchingRule[] = transformVarsToRules(vars);
   if (redirect?.http_to_https) {
-    step1Data.redirectOption = 'forceHttps';
+    form1Data.redirectOption = 'forceHttps';
   } else if (redirect?.uri) {
-    step1Data.redirectOption = 'customRedirect';
-    step1Data.redirectCode = redirect?.code;
-    step1Data.redirectURI = redirect?.uri;
+    form1Data.redirectOption = 'customRedirect';
+    form1Data.redirectCode = redirect?.code;
+    form1Data.redirectURI = redirect?.uri;
   } else {
-    step1Data.redirectOption = 'disabled';
+    form1Data.redirectOption = 'disabled';
   }
 
   const {
@@ -218,7 +232,7 @@ export const transformRouteData = (data: RouteModule.Body) => {
     };
   });
 
-  const step2Data: RouteModule.Step2Data = {
+  const form2Data: RouteModule.Form2Data = {
     upstream_protocol,
     upstreamHeaderList,
     type: upstream ? upstream.type : 'roundrobin',
@@ -238,18 +252,21 @@ export const transformRouteData = (data: RouteModule.Body) => {
 
   const { plugins, script } = data;
 
-  if (plugins.prometheus) {
+  if (plugins && plugins.prometheus) {
     plugins.prometheus = { enabled: true };
   }
+
   const step3Data: RouteModule.Step3Data = {
     plugins,
     script,
   };
 
   return {
-    step1Data,
-    step2Data,
+    form1Data,
+    form2Data,
     step3Data,
+    upstreamHeaderList,
+    advancedMatchingRules,
   };
 };
 
diff --git a/src/pages/Route/typing.d.ts b/src/pages/Route/typing.d.ts
index e6bab1b..6675054 100644
--- a/src/pages/Route/typing.d.ts
+++ b/src/pages/Route/typing.d.ts
@@ -39,41 +39,12 @@ declare namespace RouteModule {
     status: boolean;
   };
 
-  type Step1Data = {
-    name: string;
-    desc: string;
-    status: boolean;
-    priority: number;
-    protocols: RequestProtocol[];
-    websocket: boolean;
-    hosts: string[];
-    paths: string[];
-    methods: HttpMethod[];
-    redirectOption: 'forceHttps' | 'customRedirect' | 'disabled';
-    redirectURI?: string;
-    redirectCode?: number;
-    advancedMatchingRules: MatchingRule[];
-    route_group_id?: string;
-    route_group_name: string;
-  };
-
   type Step3Data = {
     plugins: PluginPage.PluginData;
     //  TEMP
     script: any;
   };
 
-  interface Data {
-    disabled?: boolean;
-    data: {
-      step1Data: Step1Data;
-      step2Data: Step2Data;
-      step3Data: Step3Data;
-    };
-    onChange(data: T): void;
-    isEdit?: boolean;
-  }
-
   type UpstreamHost = {
     host: string;
     port: number;
@@ -89,24 +60,6 @@ declare namespace RouteModule {
     key: string;
   }
 
-  type Step2Data = {
-    upstream_protocol: 'http' | 'https' | 'keep';
-    type: 'roundrobin' | 'chash';
-    hash_on?: string;
-    key?: string;
-    upstreamHostList: UpstreamHost[];
-    mappingStrategy: string | undefined;
-    rewriteType: string | undefined;
-    upstreamPath: string | undefined;
-    upstreamHeaderList: UpstreamHeader[];
-    upstream_id?: string;
-    timeout: {
-      connect: number;
-      send: number;
-      read: number;
-    };
-  };
-
   type ModalType = 'CREATE' | 'EDIT';
 
   type Redirect = {
@@ -118,7 +71,7 @@ declare namespace RouteModule {
   // Request Body or Response Data for API
   type Body = {
     id?: number;
-    route_group_id?: string;
+    route_group_id: string;
     route_group_name: string;
     status: boolean;
     name: string;
@@ -223,4 +176,86 @@ declare namespace RouteModule {
       };
     };
   };
+
+  // step1
+  interface MatchingRule {
+    position: VarPosition;
+    name: string;
+    operator: Operator;
+    value: string;
+    key: string;
+  }
+
+  type Step1PassProps = {
+    form: FormInstance;
+    advancedMatchingRules: MatchingRule[];
+    disabled?: boolean;
+    isEdit?: boolean;
+    onChange?(data: {
+      action: 'redirectOptionChange' | 'advancedMatchingRulesChange';
+      data: T;
+    }): void;
+  };
+
+  type Form1Data = {
+    name: string;
+    desc: string;
+    route_group_id: string | null;
+    route_group_name: string;
+    priority: number;
+    protocols: RequestProtocol[];
+    websocket: boolean;
+    hosts: string[];
+    paths: string[];
+    methods: HttpMethod[];
+    redirectOption: 'forceHttps' | 'customRedirect' | 'disabled';
+    redirectURI?: string;
+    redirectCode?: number;
+    status: boolean;
+  };
+
+  type AvancedMatchingRules = {
+    advancedMatchingRules: MatchingRule[];
+  };
+
+  // step2
+  type UpstreamHeader = {
+    key: string;
+    header_name: string;
+    header_value: string;
+  };
+
+  type Step2PassProps = {
+    form: FormInstance;
+    upstreamHeaderList: UpstreamHeader[] | undefined;
+    disabled?: boolean;
+    onChange(data: { action: 'upstreamHeaderListChange'; data: T }): void;
+  };
+
+  type Form2Data = {
+    upstream_protocol: 'http' | 'https' | 'keep';
+    type: 'roundrobin' | 'chash';
+    hash_on?: string;
+    key?: string;
+    mappingStrategy?: string;
+    rewriteType?: string;
+    upstreamPath?: string;
+    upstream_id: string | null;
+    timeout: {
+      connect: number;
+      send: number;
+      read: number;
+    };
+    pass_host: 'pass' | 'node' | 'rewrite';
+    upstream_host?: string;
+    upstreamHostList: UpstreamHost[];
+  };
+
+  type RequestData = {
+    form1Data: Form1Data;
+    form2Data: Form2Data;
+    step3Data: Step3Data;
+    upstreamHeaderList: UpstreamHeader[];
+    advancedMatchingRules: MatchingRule[];
+  };
 }