You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@inlong.apache.org by he...@apache.org on 2022/05/16 08:32:21 UTC

[incubator-inlong] branch master updated: [INLONG-4218][Dashboard] Unified group page (#4219)

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

healchow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-inlong.git


The following commit(s) were added to refs/heads/master by this push:
     new ff35db420 [INLONG-4218][Dashboard] Unified group page (#4219)
ff35db420 is described below

commit ff35db420cc6c032693f057d6dc66e4414dbf24a
Author: Daniel <le...@outlook.com>
AuthorDate: Mon May 16 16:32:16 2022 +0800

    [INLONG-4218][Dashboard] Unified group page (#4219)
---
 .../src/components/FormGenerator/FormGenerator.tsx |   3 +-
 inlong-dashboard/src/configs/routes/index.tsx      |   4 +-
 inlong-dashboard/src/locales/cn.json               |   2 -
 inlong-dashboard/src/locales/en.json               |   2 -
 .../src/pages/AccessCreate/DataStream/config.tsx   |  16 +-
 .../src/pages/AccessDashboard/config.tsx           |   2 +-
 .../src/pages/AccessDetail/DataSources/index.tsx   |   6 +-
 .../src/pages/AccessDetail/DataStorage/index.tsx   |   6 +-
 .../AccessDetail/DataStream/StreamItemModal.tsx    |   8 -
 .../src/pages/AccessDetail/DataStream/config.tsx   |  40 ++---
 .../src/pages/AccessDetail/DataStream/index.tsx    |  29 ++--
 .../src/pages/AccessDetail/Info/config.tsx         |  85 +++++-----
 .../src/pages/AccessDetail/Info/index.tsx          | 105 ++++++++-----
 .../src/pages/AccessDetail/common.d.ts             |   5 +-
 inlong-dashboard/src/pages/AccessDetail/index.tsx  | 174 +++++++++++++++++----
 15 files changed, 315 insertions(+), 172 deletions(-)

diff --git a/inlong-dashboard/src/components/FormGenerator/FormGenerator.tsx b/inlong-dashboard/src/components/FormGenerator/FormGenerator.tsx
index f43c6939c..99634b69d 100644
--- a/inlong-dashboard/src/components/FormGenerator/FormGenerator.tsx
+++ b/inlong-dashboard/src/components/FormGenerator/FormGenerator.tsx
@@ -129,11 +129,12 @@ const FormGenerator: React.FC<FormGeneratorProps> = props => {
     if (props.allValues) {
       setRealTimeValues(props.allValues);
     } else if (form) {
-      setTimeout(() => {
+      const timmer = setTimeout(() => {
         const { getFieldsValue } = form;
         const values = getFieldsValue(true);
         setRealTimeValues(prev => ({ ...prev, ...values }));
       }, 0);
+      return () => clearTimeout(timmer);
     }
   }, [form, props.allValues]);
 
diff --git a/inlong-dashboard/src/configs/routes/index.tsx b/inlong-dashboard/src/configs/routes/index.tsx
index cf7634fd6..f885bae6f 100644
--- a/inlong-dashboard/src/configs/routes/index.tsx
+++ b/inlong-dashboard/src/configs/routes/index.tsx
@@ -42,8 +42,8 @@ const routes: RouteProps[] = [
     exact: true,
     childRoutes: [
       {
-        path: '/create',
-        component: () => import('@/pages/AccessCreate'),
+        path: '/create/:id?',
+        component: () => import('@/pages/AccessDetail'),
         exact: true,
       },
       {
diff --git a/inlong-dashboard/src/locales/cn.json b/inlong-dashboard/src/locales/cn.json
index a6db70428..99e5576c4 100644
--- a/inlong-dashboard/src/locales/cn.json
+++ b/inlong-dashboard/src/locales/cn.json
@@ -178,13 +178,11 @@
   "pages.AccessCreate.Business.config.AccessRequirements": "接入要求",
   "pages.AccessCreate.Business.config.AccessScale": "接入规模",
   "pages.AccessCreate.DataStream.config.Basic": "基础信息",
-  "pages.AccessCreate.DataStream.config.DataSources": "数据来源",
   "pages.AccessCreate.DataStream.config.DataInfo": "数据信息",
   "pages.AccessCreate.DataStream.config.DataStorages": "数据流向",
   "pages.AccessCreate.Back": "返回",
   "pages.AccessCreate.NextStep": "下一步",
   "pages.AccessCreate.DataStreams": "数据流",
-  "pages.AccessCreate.CheckFormIntegrity": "请检查表单完整性",
   "pages.AccessCreate.Submit": "提交审批",
   "pages.AccessCreate.SubmittedSuccessfully": "提交成功",
   "pages.AccessCreate.BusinessInfo": "分组信息",
diff --git a/inlong-dashboard/src/locales/en.json b/inlong-dashboard/src/locales/en.json
index 469cd3bae..8c2d2b322 100644
--- a/inlong-dashboard/src/locales/en.json
+++ b/inlong-dashboard/src/locales/en.json
@@ -178,13 +178,11 @@
   "pages.AccessCreate.Business.config.AccessRequirements": "Access requirements",
   "pages.AccessCreate.Business.config.AccessScale": "Access scale",
   "pages.AccessCreate.DataStream.config.Basic": "Basic Info",
-  "pages.AccessCreate.DataStream.config.DataSources": "Data Sources",
   "pages.AccessCreate.DataStream.config.DataInfo": "Data Info",
   "pages.AccessCreate.DataStream.config.DataStorages": "Data Storages",
   "pages.AccessCreate.Back": "Back",
   "pages.AccessCreate.NextStep": "Next",
   "pages.AccessCreate.DataStreams": "Data streams",
-  "pages.AccessCreate.CheckFormIntegrity": "Check form integrity",
   "pages.AccessCreate.Submit": "Submit",
   "pages.AccessCreate.SubmittedSuccessfully": "Submitted successfully",
   "pages.AccessCreate.BusinessInfo": "Group information",
diff --git a/inlong-dashboard/src/pages/AccessCreate/DataStream/config.tsx b/inlong-dashboard/src/pages/AccessCreate/DataStream/config.tsx
index d7a980a87..f3dc20a11 100644
--- a/inlong-dashboard/src/pages/AccessCreate/DataStream/config.tsx
+++ b/inlong-dashboard/src/pages/AccessCreate/DataStream/config.tsx
@@ -41,14 +41,14 @@ export const genFormContent = (currentValues, inlongGroupId, middlewareType) =>
         'inlongStreamId',
         'name',
         'description',
-        {
-          type: (
-            <Divider orientation="left">
-              {i18n.t('pages.AccessCreate.DataStream.config.DataSources')}
-            </Divider>
-          ),
-        },
-        'dataSourceType',
+        // {
+        //   type: (
+        //     <Divider orientation="left">
+        //       {i18n.t('pages.AccessCreate.DataStream.config.DataSources')}
+        //     </Divider>
+        //   ),
+        // },
+        // 'dataSourceType',
         // 'hasHigher',
         // 'isHybridSource',
         // 'isTableMapping',
diff --git a/inlong-dashboard/src/pages/AccessDashboard/config.tsx b/inlong-dashboard/src/pages/AccessDashboard/config.tsx
index 2a6cc38e4..4f836b4af 100644
--- a/inlong-dashboard/src/pages/AccessDashboard/config.tsx
+++ b/inlong-dashboard/src/pages/AccessDashboard/config.tsx
@@ -70,7 +70,7 @@ export const getFilterFormContent = defaultValues => [
 ];
 
 export const getColumns = ({ onDelete, openModal }) => {
-  const genCreateUrl = record => `/access/create?inlongGroupId=${record.inlongGroupId}`;
+  const genCreateUrl = record => `/access/create/${record.inlongGroupId}`;
   const genDetailUrl = record =>
     [0, 100].includes(record.status)
       ? genCreateUrl(record)
diff --git a/inlong-dashboard/src/pages/AccessDetail/DataSources/index.tsx b/inlong-dashboard/src/pages/AccessDetail/DataSources/index.tsx
index 9e73f98cc..96f99013b 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataSources/index.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/DataSources/index.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useState } from 'react';
+import React, { useState, forwardRef } from 'react';
 import { Button, Modal, message } from 'antd';
 import HighTable from '@/components/HighTable';
 import { defaultSize } from '@/configs/pagination';
@@ -58,7 +58,7 @@ const getFilterFormContent = defaultValues => [
   },
 ];
 
-const Comp: React.FC<Props> = ({ inlongGroupId }) => {
+const Comp = ({ inlongGroupId }: Props, ref) => {
   const [options, setOptions] = useState({
     // keyword: '',
     pageSize: defaultSize,
@@ -245,4 +245,4 @@ const Comp: React.FC<Props> = ({ inlongGroupId }) => {
   );
 };
 
-export default Comp;
+export default forwardRef(Comp);
diff --git a/inlong-dashboard/src/pages/AccessDetail/DataStorage/index.tsx b/inlong-dashboard/src/pages/AccessDetail/DataStorage/index.tsx
index 94a831279..aa04f4b18 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataStorage/index.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/DataStorage/index.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useState, useMemo } from 'react';
+import React, { useState, useMemo, forwardRef } from 'react';
 import { Button, Modal, message } from 'antd';
 import HighTable from '@/components/HighTable';
 import { defaultSize } from '@/configs/pagination';
@@ -51,7 +51,7 @@ const getFilterFormContent = defaultValues => [
   },
 ];
 
-const Comp: React.FC<Props> = ({ inlongGroupId }) => {
+const Comp = ({ inlongGroupId }: Props, ref) => {
   const [options, setOptions] = useState({
     keyword: '',
     pageSize: defaultSize,
@@ -261,4 +261,4 @@ const Comp: React.FC<Props> = ({ inlongGroupId }) => {
   );
 };
 
-export default Comp;
+export default forwardRef(Comp);
diff --git a/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx b/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx
index 328c6a322..ccc38ee33 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx
@@ -52,14 +52,6 @@ export const genFormContent = (currentValues, inlongGroupId, middlewareType) =>
         'inlongStreamId',
         'name',
         'description',
-        {
-          type: (
-            <Divider orientation="left">
-              {i18n.t('pages.AccessCreate.DataStream.config.DataSources')}
-            </Divider>
-          ),
-        },
-        'dataSourceType',
         {
           type: (
             <Divider orientation="left">
diff --git a/inlong-dashboard/src/pages/AccessDetail/DataStream/config.tsx b/inlong-dashboard/src/pages/AccessDetail/DataStream/config.tsx
index 16e2db191..dbc1ac69b 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataStream/config.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/DataStream/config.tsx
@@ -21,7 +21,6 @@ import React from 'react';
 import { Divider } from 'antd';
 import i18n from '@/i18n';
 import { genBusinessFields, genDataFields } from '@/components/AccessHelper';
-import { Storages } from '@/components/MetaData';
 
 export const getFilterFormContent = (defaultValues = {} as any) => [
   {
@@ -101,15 +100,6 @@ export const genFormContent = (
         },
         'name',
         'description',
-        {
-          type: (
-            <Divider orientation="left">
-              {i18n.t('pages.AccessCreate.DataStream.config.DataSources')}
-            </Divider>
-          ),
-        },
-        'dataSourceType',
-        'dataSourcesConfig',
         {
           type: (
             <Divider orientation="left">
@@ -139,21 +129,21 @@ export const genFormContent = (
         visible: middlewareType === 'PULSAR',
       }),
     ),
-    ...genDataFields(
-      [
-        {
-          type: (
-            <Divider orientation="left">
-              {i18n.t('pages.AccessCreate.DataStream.config.DataStorages')}
-            </Divider>
-          ),
-        },
-        'streamSink',
-        ...Storages.map(item => `streamSink${item.value}`),
-      ],
-      currentValues,
-      extraParams,
-    ),
+    // ...genDataFields(
+    //   [
+    //     {
+    //       type: (
+    //         <Divider orientation="left">
+    //           {i18n.t('pages.AccessCreate.DataStream.config.DataStorages')}
+    //         </Divider>
+    //       ),
+    //     },
+    //     'streamSink',
+    //     ...Storages.map(item => `streamSink${item.value}`),
+    //   ],
+    //   currentValues,
+    //   extraParams,
+    // ),
   ].map(item => {
     if (
       (editingId === true && currentValues?.id === undefined) ||
diff --git a/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx b/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx
index d39044807..de16f4ce3 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useImperativeHandle, forwardRef } from 'react';
 import ReactDom from 'react-dom';
 import { Form, Collapse, Button, Empty, Modal, Space, message } from 'antd';
 import FormGenerator, { FormItemContent } from '@/components/FormGenerator';
@@ -26,7 +26,6 @@ import { useRequest } from '@/hooks';
 import request from '@/utils/request';
 import { useTranslation } from 'react-i18next';
 import { dataToValues, valuesToData } from '@/pages/AccessCreate/DataStream/helper';
-import { pickObject } from '@/utils';
 import { CommonInterface } from '../common';
 import StreamItemModal from './StreamItemModal';
 import { getFilterFormContent, genExtraContent, genFormContent } from './config';
@@ -35,7 +34,7 @@ import styles from './index.module.less';
 
 type Props = CommonInterface;
 
-const Comp: React.FC<Props> = ({ inlongGroupId, readonly, middlewareType }) => {
+const Comp = ({ inlongGroupId, readonly, middlewareType }: Props, ref) => {
   const { t } = useTranslation();
 
   const [form] = Form.useForm();
@@ -118,13 +117,10 @@ const Comp: React.FC<Props> = ({ inlongGroupId, readonly, middlewareType }) => {
       const { list } = await getTouchedValues();
       const values = list.find(item => item.id === record.id);
       const data = valuesToData(values ? [values] : [], inlongGroupId);
-      const submitData = data.map(item =>
-        pickObject(['dbBasicInfo', 'fileBasicInfo', 'streamInfo'], item),
-      );
       await request({
         url: '/stream/update',
         method: 'POST',
-        data: submitData?.[0]?.streamInfo,
+        data: data?.[0]?.streamInfo,
       });
     } else {
       // create
@@ -132,9 +128,9 @@ const Comp: React.FC<Props> = ({ inlongGroupId, readonly, middlewareType }) => {
       const values = list?.[0];
       const data = valuesToData(values ? [values] : [], inlongGroupId);
       await request({
-        url: '/stream/saveAll',
+        url: '/stream/save',
         method: 'POST',
-        data: data?.[0],
+        data: data?.[0]?.streamInfo,
       });
     }
     await getList();
@@ -142,6 +138,18 @@ const Comp: React.FC<Props> = ({ inlongGroupId, readonly, middlewareType }) => {
     message.success(t('basic.OperatingSuccess'));
   };
 
+  const onOk = () => {
+    if (editingId) {
+      return Promise.reject('Please save the data');
+    } else {
+      return Promise.resolve();
+    }
+  };
+
+  useImperativeHandle(ref, () => ({
+    onOk,
+  }));
+
   const onEdit = record => {
     // setEditingId(record.id);
     // setActiveKey(index.toString());
@@ -149,6 +157,7 @@ const Comp: React.FC<Props> = ({ inlongGroupId, readonly, middlewareType }) => {
   };
 
   const onCancel = async () => {
+    setEditingId(false);
     await getList();
   };
 
@@ -324,4 +333,4 @@ const Comp: React.FC<Props> = ({ inlongGroupId, readonly, middlewareType }) => {
   );
 };
 
-export default Comp;
+export default forwardRef(Comp);
diff --git a/inlong-dashboard/src/pages/AccessDetail/Info/config.tsx b/inlong-dashboard/src/pages/AccessDetail/Info/config.tsx
index e339ccc51..190d7e54c 100644
--- a/inlong-dashboard/src/pages/AccessDetail/Info/config.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/Info/config.tsx
@@ -20,42 +20,55 @@
 // import React from 'react';
 import { genBusinessFields } from '@/components/AccessHelper';
 
-export const getFormContent = ({ editing, initialValues }) =>
-  genBusinessFields(
-    [
-      'middlewareType',
-      'inlongGroupId',
-      'mqResourceObj',
-      'name',
-      'cnName',
-      'inCharges',
-      'description',
-      'queueModule',
-      'topicPartitionNum',
-      'dailyRecords',
-      'dailyStorage',
-      'peakRecords',
-      'maxLength',
-      // 'mqExtInfo.ensemble',
-      'mqExtInfo.writeQuorum',
-      'mqExtInfo.ackQuorum',
-      'mqExtInfo.retentionTime',
-      'mqExtInfo.ttl',
-      'mqExtInfo.retentionSize',
-    ],
-    initialValues,
-  ).map(item => ({
-    ...item,
-    type: transType(editing, item, initialValues),
-    suffix:
-      typeof item.suffix === 'object' && !editing
-        ? {
-            ...item.suffix,
-            type: 'text',
-          }
-        : item.suffix,
-    extra: null,
-  }));
+export const getFormContent = ({ editing, initialValues, isCreate, isUpdate }) => {
+  const keys = [
+    'middlewareType',
+    !isCreate && 'inlongGroupId',
+    !isCreate && 'mqResourceObj',
+    'name',
+    'cnName',
+    'inCharges',
+    'description',
+    'queueModule',
+    'topicPartitionNum',
+    'dailyRecords',
+    'dailyStorage',
+    'peakRecords',
+    'maxLength',
+    // 'mqExtInfo.ensemble',
+    'mqExtInfo.writeQuorum',
+    'mqExtInfo.ackQuorum',
+    'mqExtInfo.retentionTime',
+    'mqExtInfo.ttl',
+    'mqExtInfo.retentionSize',
+  ].filter(Boolean);
+
+  return isCreate
+    ? genBusinessFields(keys, initialValues).map(item => {
+        if (item.name === 'name' && isUpdate) {
+          return {
+            ...item,
+            props: {
+              ...item.props,
+              disabled: true,
+            },
+          };
+        }
+        return item;
+      })
+    : genBusinessFields(keys, initialValues).map(item => ({
+        ...item,
+        type: transType(editing, item, initialValues),
+        suffix:
+          typeof item.suffix === 'object' && !editing
+            ? {
+                ...item.suffix,
+                type: 'text',
+              }
+            : item.suffix,
+        extra: null,
+      }));
+};
 
 function transType(editing: boolean, conf, initialValues) {
   const arr = [
diff --git a/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx b/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx
index b0b1fc6c2..4cf602b7d 100644
--- a/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx
@@ -17,25 +17,33 @@
  * under the License.
  */
 
-import React from 'react';
-import ReactDom from 'react-dom';
+import React, { useEffect, useMemo, useImperativeHandle, forwardRef } from 'react';
 import { Button, Space, message } from 'antd';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
-import { useRequest, useBoolean } from '@/hooks';
+import { useRequest, useBoolean, useSelector } from '@/hooks';
 import { useTranslation } from 'react-i18next';
 import request from '@/utils/request';
+import { State } from '@/models';
 import { CommonInterface } from '../common';
 import { getFormContent } from './config';
 
 type Props = CommonInterface;
 
-const Comp: React.FC<Props> = ({ inlongGroupId, isActive, readonly, extraRef }) => {
+const Comp = ({ inlongGroupId, readonly, isCreate }: Props, ref) => {
   const { t } = useTranslation();
-  const [editing, { setTrue, setFalse }] = useBoolean(false);
+  const [editing, { setTrue, setFalse }] = useBoolean(isCreate);
+
+  const { userName } = useSelector<State, State>(state => state);
 
   const [form] = useForm();
 
+  const isUpdate = useMemo(() => {
+    return !!inlongGroupId;
+  }, [inlongGroupId]);
+
   const { data, run: getData } = useRequest(`/group/get/${inlongGroupId}`, {
+    ready: isUpdate,
+    refreshDeps: [inlongGroupId],
     formatResult: data => ({
       ...data,
       inCharges: data.inCharges.split(','),
@@ -44,26 +52,50 @@ const Comp: React.FC<Props> = ({ inlongGroupId, isActive, readonly, extraRef })
     onSuccess: data => form.setFieldsValue(data),
   });
 
-  const onSave = async () => {
+  const onOk = async () => {
     const values = await form.validateFields();
 
     const submitData = {
       ...values,
-      inCharges: values.inCharges.join(','),
-      followers: values.inCharges.join(','),
+      inCharges: values.inCharges?.join(','),
+      followers: values.followers?.join(','),
       mqExtInfo: {
-        ...data.mqExtInfo,
+        ...data?.mqExtInfo,
         ...values.mqExtInfo,
+        middlewareType: values.middlewareType,
       },
     };
-    await request({
-      url: '/group/update',
+
+    if (isUpdate) {
+      submitData.inlongGroupId = inlongGroupId;
+    }
+
+    const result = await request({
+      url: isUpdate ? '/group/update' : '/group/save',
       method: 'POST',
-      data: {
-        ...data,
-        ...submitData,
-      },
+      data: submitData,
     });
+
+    return {
+      ...values,
+      inlongGroupId: result,
+    };
+  };
+
+  useEffect(() => {
+    const values = {} as Record<string, unknown>;
+    if (!isUpdate) {
+      if (userName) values.inCharges = [userName];
+      form.setFieldsValue(values);
+    }
+  }, [isUpdate, form, userName]);
+
+  useImperativeHandle(ref, () => ({
+    onOk,
+  }));
+
+  const onSave = async () => {
+    await onOk();
     await getData();
     setFalse();
     message.success(t('basic.OperatingSuccess'));
@@ -74,39 +106,38 @@ const Comp: React.FC<Props> = ({ inlongGroupId, isActive, readonly, extraRef })
     setFalse();
   };
 
-  const Extra = () => {
-    return editing ? (
-      <Space>
-        <Button type="primary" onClick={onSave}>
-          {t('basic.Save')}
-        </Button>
-        <Button onClick={onCancel}>{t('basic.Cancel')}</Button>
-      </Space>
-    ) : (
-      <Button type="primary" onClick={setTrue}>
-        {t('basic.Edit')}
-      </Button>
-    );
-  };
-
   return (
-    <>
+    <div style={{ position: 'relative' }}>
       <FormGenerator
         form={form}
         content={getFormContent({
           editing,
           initialValues: data,
+          isCreate,
+          isUpdate,
         })}
         allValues={data}
         useMaxWidth={600}
       />
 
-      {isActive &&
-        !readonly &&
-        extraRef?.current &&
-        ReactDom.createPortal(<Extra />, extraRef.current)}
-    </>
+      {!isCreate && !readonly && (
+        <div style={{ position: 'absolute', top: 0, right: 0 }}>
+          {editing ? (
+            <Space>
+              <Button type="primary" onClick={onSave}>
+                {t('basic.Save')}
+              </Button>
+              <Button onClick={onCancel}>{t('basic.Cancel')}</Button>
+            </Space>
+          ) : (
+            <Button type="primary" onClick={setTrue}>
+              {t('basic.Edit')}
+            </Button>
+          )}
+        </div>
+      )}
+    </div>
   );
 };
 
-export default Comp;
+export default forwardRef(Comp);
diff --git a/inlong-dashboard/src/pages/AccessDetail/common.d.ts b/inlong-dashboard/src/pages/AccessDetail/common.d.ts
index 43a86dd86..50cc69c59 100644
--- a/inlong-dashboard/src/pages/AccessDetail/common.d.ts
+++ b/inlong-dashboard/src/pages/AccessDetail/common.d.ts
@@ -21,7 +21,6 @@ export interface CommonInterface {
   inlongGroupId: string;
   middlewareType: 'TUBE' | 'PULSAR';
   readonly?: boolean;
-  isActive?: boolean;
-  // extraRef of Tab
-  extraRef?: React.RefObject<HTMLDivElement>;
+  isCreate?: boolean;
+  ref?: React.RefObject<unknown>;
 }
diff --git a/inlong-dashboard/src/pages/AccessDetail/index.tsx b/inlong-dashboard/src/pages/AccessDetail/index.tsx
index 0fe95453c..ab9b5e14f 100644
--- a/inlong-dashboard/src/pages/AccessDetail/index.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/index.tsx
@@ -17,11 +17,13 @@
  * under the License.
  */
 
-import React, { useMemo, useState, useRef } from 'react';
-import { Tabs } from 'antd';
-import { PageContainer } from '@/components/PageContainer';
-import { useParams, useRequest } from '@/hooks';
+import React, { useMemo, useState, useRef, useEffect } from 'react';
+import { Tabs, Button, Card, message, Steps, Space } from 'antd';
+import { PageContainer, FooterToolbar } from '@/components/PageContainer';
+import { parse } from 'qs';
+import { useParams, useRequest, useSet, useHistory, useLocation } from '@/hooks';
 import { useTranslation } from 'react-i18next';
+import request from '@/utils/request';
 import Info from './Info';
 import DataSources from './DataSources';
 import DataStream from './DataStream';
@@ -30,16 +32,33 @@ import Audit from './Audit';
 
 const Comp: React.FC = () => {
   const { t } = useTranslation();
-  const { id } = useParams<{ id: string }>();
+  const location = useLocation();
+  const history = useHistory();
+  const { id: groupId } = useParams<{ id: string }>();
+
+  const qs = parse(location.search.slice(1));
+
+  const [current, setCurrent] = useState(+qs.step || 0);
+  const [, { add: addOpened, has: hasOpened }] = useSet([current]);
+  const [confirmLoading, setConfirmLoading] = useState(false);
+  const [id, setId] = useState(groupId || '');
+
+  const childRef = useRef(null);
+  const [middlewareType, setMiddlewareType] = useState();
+
+  const [isCreate] = useState(location.pathname.indexOf('/access/create') === 0);
+
+  useEffect(() => {
+    if (!hasOpened(current)) addOpened(current);
+  }, [current, addOpened, hasOpened]);
 
   const { data } = useRequest(`/group/get/${id}`, {
+    ready: !!id && !middlewareType,
     refreshDeps: [id],
+    onSuccess: result => setMiddlewareType(result.middlewareType),
   });
 
-  const extraRef = useRef<HTMLDivElement>();
-
   const isReadonly = useMemo(() => [0, 101, 102].includes(data?.status), [data]);
-  const middlewareType = data?.middlewareType;
 
   const list = useMemo(
     () =>
@@ -58,45 +77,138 @@ const Comp: React.FC = () => {
           label: t('pages.AccessDetail.DataSources'),
           value: 'dataSources',
           content: DataSources,
-          hidden: isReadonly,
         },
         {
           label: t('pages.AccessDetail.DataStorages'),
           value: 'streamSink',
           content: DataStorage,
-          hidden: isReadonly,
         },
         {
           label: t('pages.AccessDetail.Audit'),
           value: 'audit',
           content: Audit,
-          hidden: isReadonly,
+          hidden: isReadonly || isCreate,
         },
       ].filter(item => !item.hidden),
-    [isReadonly, t],
+    [isReadonly, isCreate, t],
   );
 
-  const [actived, setActived] = useState(list[0].value);
+  const onOk = async current => {
+    const onOk = childRef?.current?.onOk;
+
+    setConfirmLoading(true);
+    try {
+      const result = onOk && (await onOk());
+      if (current === 0) {
+        setMiddlewareType(result.middlewareType);
+        setId(result.inlongGroupId);
+      }
+      history.push({
+        pathname: `/access/create/${result?.inlongGroupId || id}`,
+        search: `?step=${current + 1}`,
+      });
+    } finally {
+      setConfirmLoading(false);
+    }
+  };
+
+  const onSubmit = async () => {
+    await request({
+      url: `/group/startProcess/${id}`,
+      method: 'POST',
+    });
+    message.success(t('pages.AccessCreate.SubmittedSuccessfully'));
+    history.push('/access');
+  };
+
+  const Footer = () => (
+    <Space style={{ display: 'flex', justifyContent: 'center' }}>
+      {current > 0 && (
+        <Button disabled={confirmLoading} onClick={() => setCurrent(current - 1)}>
+          {t('pages.AccessCreate.Previous')}
+        </Button>
+      )}
+      {current !== list.length - 1 && (
+        <Button
+          type="primary"
+          loading={confirmLoading}
+          onClick={async () => {
+            await onOk(current).catch(err => {
+              if (err?.errorFields?.length) {
+                message.error(t('pages.AccessCreate.CheckFormIntegrity'));
+              }
+              return Promise.reject(err);
+            });
+
+            const newCurrent = current + 1;
+            setCurrent(newCurrent);
+          }}
+        >
+          {t('pages.AccessCreate.NextStep')}
+        </Button>
+      )}
+      {current === list.length - 1 && (
+        <Button type="primary" onClick={onSubmit}>
+          {t('pages.AccessCreate.Submit')}
+        </Button>
+      )}
+      <Button onClick={() => history.push('/access')}>{t('pages.AccessCreate.Back')}</Button>
+    </Space>
+  );
+
+  const Div = isCreate ? Card : Tabs;
 
   return (
-    <PageContainer breadcrumb={[{ name: `${t('pages.AccessDetail.BusinessDetail')}${id}` }]}>
-      <Tabs
-        activeKey={actived}
-        onChange={val => setActived(val)}
-        tabBarExtraContent={<div ref={extraRef} />}
-      >
-        {list.map(({ content: Content, ...item }) => (
-          <Tabs.TabPane tab={item.label} key={item.value}>
-            <Content
-              inlongGroupId={id}
-              isActive={actived === item.value}
-              readonly={isReadonly}
-              extraRef={extraRef}
-              middlewareType={middlewareType}
-            />
-          </Tabs.TabPane>
-        ))}
-      </Tabs>
+    <PageContainer
+      breadcrumb={[
+        {
+          name: isCreate
+            ? t('pages.AccessCreate.NewAccess')
+            : `${t('pages.AccessDetail.BusinessDetail')}${id}`,
+        },
+      ]}
+      useDefaultContainer={!isCreate}
+    >
+      {isCreate && (
+        <Steps
+          current={current}
+          size="small"
+          style={{ marginBottom: 20, width: 600 }}
+          onChange={c => setCurrent(c)}
+        >
+          {list.map(item => (
+            <Steps.Step key={item.label} title={item.label} />
+          ))}
+        </Steps>
+      )}
+
+      <Div>
+        {list.map(({ content: Content, ...item }, index) => {
+          // Lazy load the content of the step, and at the same time make the loaded useCache content not destroy
+          const child =
+            !isCreate || hasOpened(index) ? (
+              <Content
+                inlongGroupId={id}
+                readonly={isReadonly}
+                middlewareType={middlewareType}
+                isCreate={isCreate}
+                ref={index === current ? childRef : null}
+              />
+            ) : null;
+
+          return isCreate ? (
+            <div key={item.label} style={{ display: `${index === current ? 'block' : 'none'}` }}>
+              {child}
+            </div>
+          ) : (
+            <Tabs.TabPane tab={item.label} key={item.value}>
+              {child}
+            </Tabs.TabPane>
+          );
+        })}
+      </Div>
+
+      {isCreate && <FooterToolbar extra={<Footer />} />}
     </PageContainer>
   );
 };