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

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


The following commit(s) were added to refs/heads/master by this push:
     new 0f047e11c [INLONG-7092][Dashboard] Source and Sink as sub-concepts of 
Stream (#7095)
0f047e11c is described below

commit 0f047e11ccf5b92221c4f77135bd2b904fc6cfcf
Author: Daniel <[email protected]>
AuthorDate: Fri Dec 30 17:32:44 2022 +0800

    [INLONG-7092][Dashboard] Source and Sink as sub-concepts of Stream (#7095)
    
    Co-authored-by: Charles Zhang <[email protected]>
---
 .../src/components/EditableTable/index.tsx         |   4 +-
 .../components/FormGenerator/FormItemContent.tsx   |  12 +-
 inlong-dashboard/src/locales/cn.json               |   8 +-
 .../src/metas/sinks/common/SinkDefaultInfo.ts      |  30 +----
 .../src/metas/sources/common/SourceDefaultInfo.ts  |  30 +----
 .../src/pages/ConsumeDetail/Info/index.tsx         |   2 +
 .../pages/GroupDetail/DataSources/DetailModal.tsx  |  12 +-
 .../src/pages/GroupDetail/DataSources/index.tsx    | 119 ++++++++++++++++----
 .../pages/GroupDetail/DataStorage/DetailModal.tsx  |  41 ++++---
 .../src/pages/GroupDetail/DataStorage/index.tsx    | 121 ++++++++++++++++-----
 .../{common.d.ts => DataStream/SourceSinkCard.tsx} |  33 +++++-
 .../src/pages/GroupDetail/DataStream/index.tsx     |  36 ++++--
 .../src/pages/GroupDetail/Info/config.tsx          |   2 +-
 inlong-dashboard/src/pages/GroupDetail/common.d.ts |   2 +-
 inlong-dashboard/src/pages/GroupDetail/index.tsx   |  12 --
 15 files changed, 311 insertions(+), 153 deletions(-)

diff --git a/inlong-dashboard/src/components/EditableTable/index.tsx 
b/inlong-dashboard/src/components/EditableTable/index.tsx
index f5961d6b8..d3ecbfe98 100644
--- a/inlong-dashboard/src/components/EditableTable/index.tsx
+++ b/inlong-dashboard/src/components/EditableTable/index.tsx
@@ -94,7 +94,7 @@ const addIdToValues = (values: RowValueType[]): RecordType[] 
=>
     return obj as RecordType;
   });
 
-const Comp = ({
+const EditableTable = ({
   id,
   value,
   onChange,
@@ -294,4 +294,4 @@ const Comp = ({
   );
 };
 
-export default Comp;
+export default EditableTable;
diff --git a/inlong-dashboard/src/components/FormGenerator/FormItemContent.tsx 
b/inlong-dashboard/src/components/FormGenerator/FormItemContent.tsx
index b484be49e..25fb605c8 100644
--- a/inlong-dashboard/src/components/FormGenerator/FormItemContent.tsx
+++ b/inlong-dashboard/src/components/FormGenerator/FormItemContent.tsx
@@ -117,7 +117,7 @@ const FormItemContent: React.FC<FormItemContentProps> = ({
           return null;
         }
         if (isPro) {
-          proIndex = index;
+          if (proIndex === -1) proIndex = index;
           formItemProps.hidden = !proOpened || Boolean(formItemProps.hidden);
         }
         const key = formItemProps.name || index.toString();
@@ -172,7 +172,11 @@ const FormItemContent: React.FC<FormItemContentProps> = ({
         return useInline ? (
           inner
         ) : (
-          <Col key={key.toString()} span={col || 24}>
+          <Col
+            key={key.toString()}
+            span={col || 24}
+            style={formItemProps.hidden ? { display: 'none' } : {}}
+          >
             {inner}
           </Col>
         );
@@ -182,7 +186,9 @@ const FormItemContent: React.FC<FormItemContentProps> = ({
       conts.splice(
         proIndex,
         0,
-        <TextSwitch key="_pro" value={proOpened} onChange={v => 
setProOpened(v)} />,
+        <Col key="_pro" span={24}>
+          <TextSwitch value={proOpened} onChange={v => setProOpened(v)} />
+        </Col>,
       );
     }
 
diff --git a/inlong-dashboard/src/locales/cn.json 
b/inlong-dashboard/src/locales/cn.json
index e1d316d4f..a47a78004 100644
--- a/inlong-dashboard/src/locales/cn.json
+++ b/inlong-dashboard/src/locales/cn.json
@@ -445,7 +445,7 @@
   "pages.GroupDashboard.ExecutionLogModal.CarriedOut": "执行",
   "pages.GroupDashboard.ExecutionLogModal.EndTime": "结束时间",
   "pages.GroupDashboard.ExecutionLogModal.Processing": "处理中",
-  "pages.GroupDashboard.ExecutionLogModal.ExecuteLog": "执行日志",
+  "pages.GroupDashboard.ExecutionLogModal.ExecuteLog": "日志",
   "pages.GroupDashboard.ExecutionLogModal.RunResults": "执行结果",
   "pages.GroupDashboard.ExecutionLogModal.Success": "成功",
   "pages.GroupDashboard.ExecutionLogModal.Skip": "跳过",
@@ -454,7 +454,7 @@
   "pages.GroupDashboard.config.WaitAssignCount": "待分配",
   "pages.GroupDashboard.config.WaitApproveCount": "待审批",
   "pages.GroupDashboard.config.Reject": "已驳回",
-  "pages.GroupDashboard.config.ExecuteLog": "执行日志",
+  "pages.GroupDashboard.config.ExecuteLog": "日志",
   "pages.GroupDashboard.ConfirmDelete": "确认删除吗",
   "pages.GroupDashboard.Create": "新建数据流组",
   "pages.GroupDashboard.SuccessfullyDeleted": "删除成功",
@@ -489,8 +489,8 @@
   "pages.GroupDetail.Sources.status.BeenRetry": "已下发重试",
   "pages.GroupDetail.Sources.status.BeenFrozen": "已下发停止",
   "pages.GroupDetail.Sources.status.BeenActive": "已下发重启",
-  "pages.GroupDetail.Sink.New": "新建流向配置",
-  "pages.GroupDetail.Sink.Edit": "编辑流向配置",
+  "pages.GroupDetail.Sink.New": "新建数据目标",
+  "pages.GroupDetail.Sink.Edit": "编辑数据目标",
   "pages.GroupDetail.Sink.Type": "类型",
   "pages.GroupDetail.Sink.DataStreams": "数据流",
   "pages.GroupDetail.Sink.Status.New": "新建",
diff --git a/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts 
b/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts
index cd8c2892d..f95ae001d 100644
--- a/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts
@@ -44,33 +44,11 @@ export class SinkDefaultInfo implements DataWithBackend, 
RenderRow, RenderList {
   readonly inlongGroupId: string;
 
   @FieldDecorator({
-    type: 'select',
-    props: values => ({
-      disabled: Boolean(values.id),
-      options: {
-        requestService: {
-          url: '/stream/list',
-          method: 'POST',
-          data: {
-            pageNum: 1,
-            pageSize: 1000,
-            inlongGroupId: values.inlongGroupId,
-          },
-        },
-        requestParams: {
-          formatResult: result =>
-            result?.list.map(item => ({
-              label: item.inlongStreamId,
-              value: item.inlongStreamId,
-            })) || [],
-        },
-      },
-    }),
-    rules: [{ required: true }],
+    type: 'text',
+    hidden: true,
   })
-  @ColumnDecorator()
-  @I18n('pages.GroupDetail.Sink.DataStreams')
-  inlongStreamId: string;
+  @I18n('inlongStreamId')
+  readonly inlongStreamId: string;
 
   @FieldDecorator({
     type: 'input',
diff --git a/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts 
b/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts
index fb379eb9f..dd2dbd88d 100644
--- a/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts
@@ -43,33 +43,11 @@ export class SourceDefaultInfo implements DataWithBackend, 
RenderRow, RenderList
   readonly inlongGroupId: string;
 
   @FieldDecorator({
-    type: 'select',
-    props: values => ({
-      disabled: Boolean(values.id),
-      options: {
-        requestService: {
-          url: '/stream/list',
-          method: 'POST',
-          data: {
-            pageNum: 1,
-            pageSize: 1000,
-            inlongGroupId: values.inlongGroupId,
-          },
-        },
-        requestParams: {
-          formatResult: result =>
-            result?.list.map(item => ({
-              label: item.inlongStreamId,
-              value: item.inlongStreamId,
-            })) || [],
-        },
-      },
-    }),
-    rules: [{ required: true }],
+    type: 'text',
+    hidden: true,
   })
-  @ColumnDecorator()
-  @I18n('pages.GroupDetail.Sources.DataStreams')
-  inlongStreamId: string;
+  @I18n('inlongStreamId')
+  readonly inlongStreamId: string;
 
   @FieldDecorator({
     type: 'input',
diff --git a/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx 
b/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx
index 5ca8fde8c..61d7b6a2f 100644
--- a/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx
+++ b/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx
@@ -49,6 +49,7 @@ const Comp = ({ id, readonly, isCreate }: Props, ref) => {
     formatResult: result => ({
       ...result,
       inCharges: result.inCharges?.split(',') || [],
+      topic: result.topic?.split(','),
     }),
     onSuccess: data => {
       form.setFieldsValue(data);
@@ -62,6 +63,7 @@ const Comp = ({ id, readonly, isCreate }: Props, ref) => {
       ...values,
       inCharges: values.inCharges.join(','),
       consumerGroup: values.consumerGroup || data?.consumerGroup,
+      topic: values.topic?.join(','),
     };
 
     if (isUpdate) {
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx 
b/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx
index 6d346883b..701e60f99 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx
@@ -29,11 +29,18 @@ import request from '@/utils/request';
 export interface Props extends ModalProps {
   // When editing, use the ID to call the interface for obtaining details
   id?: string;
-  inlongGroupId?: string;
+  inlongGroupId: string;
+  inlongStreamId: string;
   defaultType?: string;
 }
 
-const Comp: React.FC<Props> = ({ id, inlongGroupId, defaultType, ...modalProps 
}) => {
+const Comp: React.FC<Props> = ({
+  id,
+  inlongGroupId,
+  inlongStreamId,
+  defaultType,
+  ...modalProps
+}) => {
   const [form] = useForm();
   const { t } = useTranslation();
 
@@ -71,6 +78,7 @@ const Comp: React.FC<Props> = ({ id, inlongGroupId, 
defaultType, ...modalProps }
       data: {
         ...submitData,
         inlongGroupId,
+        inlongStreamId,
       },
     });
     modalProps?.onOk(submitData);
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx 
b/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx
index 3f66b76fe..7671ff012 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx
@@ -18,7 +18,14 @@
  */
 
 import React, { useState, forwardRef, useMemo, useCallback } from 'react';
-import { Button, Modal, message } from 'antd';
+import { Badge, Button, Card, Modal, List, Tag, Radio, message } from 'antd';
+import { PaginationConfig } from 'antd/lib/pagination';
+import {
+  UnorderedListOutlined,
+  TableOutlined,
+  EditOutlined,
+  DeleteOutlined,
+} from '@ant-design/icons';
 import HighTable from '@/components/HighTable';
 import { defaultSize } from '@/configs/pagination';
 import { useRequest } from '@/hooks';
@@ -29,17 +36,23 @@ import request from '@/utils/request';
 import { pickObjectArray } from '@/utils';
 import { CommonInterface } from '../common';
 
-type Props = CommonInterface;
+interface Props extends CommonInterface {
+  inlongStreamId?: string;
+}
+
+const Comp = ({ inlongGroupId, inlongStreamId, readonly }: Props, ref) => {
+  const [mode, setMode] = useState('list');
 
-const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
   const { defaultValue } = useDefaultMeta('source');
 
-  const [options, setOptions] = useState({
+  const defaultOptions = {
     // keyword: '',
     pageSize: defaultSize,
     pageNum: 1,
     sourceType: defaultValue,
-  });
+  };
+
+  const [options, setOptions] = useState(defaultOptions);
 
   const [createModal, setCreateModal] = useState<Record<string, unknown>>({
     visible: false,
@@ -55,6 +68,7 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
       params: {
         ...options,
         inlongGroupId,
+        inlongStreamId,
       },
     },
     {
@@ -102,10 +116,12 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => 
{
     }));
   }, []);
 
-  const pagination = {
+  const pagination: PaginationConfig = {
     pageSize: options.pageSize,
     current: options.pageNum,
     total: data?.total,
+    simple: true,
+    size: 'small',
   };
 
   const { Entity } = useLoadMeta<SourceMetaType>('source', options.sourceType);
@@ -163,32 +179,87 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => 
{
 
   return (
     <>
-      <HighTable
-        filterForm={{
-          content: getFilterFormContent(options),
-          onFilter,
-        }}
-        suffix={
+      <Card
+        size="small"
+        title={
+          <Badge size="small" count={data?.total} offset={[15, 0]}>
+            {i18n.t('pages.GroupDetail.Sources')}
+          </Badge>
+        }
+        style={{ height: '100%' }}
+        extra={[
           !readonly && (
-            <Button type="primary" onClick={() => setCreateModal({ visible: 
true })}>
+            <Button key="create" type="link" onClick={() => setCreateModal({ 
visible: true })}>
               {i18n.t('pages.GroupDetail.Sources.Create')}
             </Button>
-          )
-        }
-        table={{
-          columns,
-          rowKey: 'id',
-          dataSource: data?.list,
-          pagination,
-          loading,
-          onChange,
-        }}
-      />
+          ),
+          <Radio.Group
+            key="mode"
+            onChange={e => {
+              setMode(e.target.value);
+              setOptions(defaultOptions);
+            }}
+            defaultValue={mode}
+            size="small"
+          >
+            <Radio.Button value="list">
+              <UnorderedListOutlined />
+            </Radio.Button>
+            <Radio.Button value="table">
+              <TableOutlined />
+            </Radio.Button>
+          </Radio.Group>,
+        ]}
+      >
+        {mode === 'list' ? (
+          <List
+            size="small"
+            loading={loading}
+            dataSource={data?.list as Record<string, any>[]}
+            pagination={pagination}
+            renderItem={item => (
+              <List.Item
+                actions={[
+                  <Button key="edit" type="link" onClick={() => onEdit(item)}>
+                    <EditOutlined />
+                  </Button>,
+                  <Button key="del" type="link" onClick={() => onDelete(item)}>
+                    <DeleteOutlined />
+                  </Button>,
+                ]}
+                className="test"
+              >
+                <span>
+                  <span style={{ marginRight: 10 }}>{item.sourceName}</span>
+                  <Tag>{item.sourceType}</Tag>
+                </span>
+              </List.Item>
+            )}
+          />
+        ) : (
+          <HighTable
+            filterForm={{
+              content: getFilterFormContent(options),
+              onFilter,
+            }}
+            table={{
+              columns,
+              rowKey: 'id',
+              size: 'small',
+              dataSource: data?.list,
+              pagination,
+              loading,
+              onChange,
+            }}
+          />
+        )}
+      </Card>
 
       <DetailModal
         {...createModal}
         defaultType={options.sourceType}
         inlongGroupId={inlongGroupId}
+        inlongStreamId={inlongStreamId}
         visible={createModal.visible as boolean}
         onOk={async () => {
           await getList();
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx 
b/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx
index 540889a87..42961e873 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx
@@ -22,12 +22,14 @@ import { Button, Skeleton, Modal, message } from 'antd';
 import { ModalProps } from 'antd/es/modal';
 import { useRequest, useUpdateEffect } from '@/hooks';
 import { useTranslation } from 'react-i18next';
+import EditableTable from '@/components/EditableTable';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
 import { useLoadMeta, SinkMetaType } from '@/metas';
 import request from '@/utils/request';
 
 export interface DetailModalProps extends ModalProps {
   inlongGroupId: string;
+  inlongStreamId: string;
   defaultType?: string;
   // (True operation, save and adjust interface) Need to upload when editing
   id?: string;
@@ -35,7 +37,13 @@ export interface DetailModalProps extends ModalProps {
   onOk?: (values) => void;
 }
 
-const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, defaultType, id, 
...modalProps }) => {
+const Comp: React.FC<DetailModalProps> = ({
+  inlongGroupId,
+  inlongStreamId,
+  defaultType,
+  id,
+  ...modalProps
+}) => {
   const [form] = useForm();
 
   const { t } = useTranslation();
@@ -44,8 +52,6 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, 
defaultType, id, ...m
   // A: Avoid the table of the fields triggering the monitoring of the column 
change.
   const [sinkType, setSinkType] = useState('');
 
-  const [changedValues, setChangedValues] = useState<Record<string, any>>({});
-
   const { Entity } = useLoadMeta<SinkMetaType>('sink', sinkType);
 
   const { data: groupData, run: getGroupData } = 
useRequest(`/group/get/${inlongGroupId}`, {
@@ -86,13 +92,14 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, 
defaultType, id, ...m
   );
 
   useEffect(() => {
-    if (changedValues.inlongStreamId) {
-      getStreamDetail(changedValues.inlongStreamId);
+    if (inlongStreamId) {
+      getStreamDetail(inlongStreamId);
     }
-  }, [getStreamDetail, changedValues.inlongStreamId]);
+  }, [getStreamDetail, inlongStreamId]);
 
   useEffect(() => {
     if (
+      !id &&
       Entity &&
       streamDetail &&
       streamDetail.fieldList?.length &&
@@ -112,9 +119,8 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, 
defaultType, id, ...m
   useUpdateEffect(() => {
     if (modalProps.visible) {
       // open
-      getGroupData();
-      setChangedValues({});
       if (id) {
+        getGroupData();
         getData(id);
       } else {
         form.setFieldsValue({ inlongGroupId, sinkType: defaultType });
@@ -127,7 +133,15 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, 
defaultType, id, ...m
   }, [modalProps.visible]);
 
   const formContent = useMemo(() => {
-    return Entity ? new Entity().renderRow() : [];
+    if (Entity) {
+      const row = new Entity().renderRow();
+      return row.map(item => ({
+        ...item,
+        col: item.type === EditableTable ? 24 : 12,
+      }));
+    }
+
+    return [];
   }, [Entity]);
 
   const onOk = async (startProcess = false) => {
@@ -147,6 +161,7 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, 
defaultType, id, ...m
       data: {
         ...submitData,
         inlongGroupId,
+        inlongStreamId,
       },
     });
     modalProps?.onOk(submitData);
@@ -165,7 +180,7 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, 
defaultType, id, ...m
         <Button key="save" type="primary" onClick={() => onOk(false)}>
           {t('pages.GroupDetail.Sink.Save')}
         </Button>,
-        groupData?.status === 130 && (
+        groupData?.status === 130 && id && (
           <Button key="run" type="primary" onClick={() => onOk(true)}>
             {t('pages.GroupDetail.Sink.SaveAndRefresh')}
           </Button>
@@ -176,13 +191,13 @@ const Comp: React.FC<DetailModalProps> = ({ 
inlongGroupId, defaultType, id, ...m
         <Skeleton active />
       ) : (
         <FormGenerator
-          labelCol={{ span: 4 }}
-          wrapperCol={{ span: 12 }}
+          labelCol={{ flex: '0 0 200px' }}
+          wrapperCol={{ flex: 1 }}
+          col={12}
           content={formContent}
           form={form}
           initialValues={id ? data : { inlongGroupId }}
           onValuesChange={(c, values) => {
-            setChangedValues(c);
             setSinkType(values.sinkType);
           }}
         />
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx 
b/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx
index b03fb1095..cf37faa53 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx
@@ -18,7 +18,14 @@
  */
 
 import React, { useState, useMemo, forwardRef, useCallback } from 'react';
-import { Button, Modal, message } from 'antd';
+import { Badge, Button, Card, Modal, List, Tag, Radio, message } from 'antd';
+import { PaginationConfig } from 'antd/lib/pagination';
+import {
+  UnorderedListOutlined,
+  TableOutlined,
+  EditOutlined,
+  DeleteOutlined,
+} from '@ant-design/icons';
 import HighTable from '@/components/HighTable';
 import { defaultSize } from '@/configs/pagination';
 import { useRequest } from '@/hooks';
@@ -29,17 +36,23 @@ import request from '@/utils/request';
 import { pickObjectArray } from '@/utils';
 import { CommonInterface } from '../common';
 
-type Props = CommonInterface;
+interface Props extends CommonInterface {
+  inlongStreamId?: string;
+}
+
+const Comp = ({ inlongGroupId, inlongStreamId, readonly }: Props, ref) => {
+  const [mode, setMode] = useState('list');
 
-const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
   const { defaultValue } = useDefaultMeta('sink');
 
-  const [options, setOptions] = useState({
-    keyword: '',
+  const defaultOptions = {
+    // keyword: '',
     pageSize: defaultSize,
     pageNum: 1,
     sinkType: defaultValue,
-  });
+  };
+
+  const [options, setOptions] = useState(defaultOptions);
 
   const [createModal, setCreateModal] = useState<Record<string, unknown>>({
     visible: false,
@@ -55,6 +68,7 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
       params: {
         ...options,
         inlongGroupId,
+        inlongStreamId,
       },
     },
     {
@@ -103,10 +117,12 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => 
{
     }));
   };
 
-  const pagination = {
+  const pagination: PaginationConfig = {
     pageSize: options.pageSize,
     current: options.pageNum,
     total: data?.total,
+    simple: true,
+    size: 'small',
   };
 
   const { Entity } = useLoadMeta<SinkMetaType>('sink', options.sinkType);
@@ -158,32 +174,85 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => 
{
 
   return (
     <>
-      <HighTable
-        filterForm={{
-          content: getFilterFormContent(options),
-          onFilter,
-        }}
-        suffix={
+      <Card
+        size="small"
+        title={
+          <Badge size="small" count={data?.total} offset={[15, 0]}>
+            {i18n.t('pages.GroupDetail.Sinks')}
+          </Badge>
+        }
+        style={{ height: '100%' }}
+        extra={[
           !readonly && (
-            <Button type="primary" onClick={() => setCreateModal({ visible: 
true })}>
+            <Button key="create" type="link" onClick={() => setCreateModal({ 
visible: true })}>
               {i18n.t('pages.GroupDetail.Sink.New')}
             </Button>
-          )
-        }
-        table={{
-          columns,
-          rowKey: 'id',
-          dataSource: data?.list,
-          pagination,
-          loading,
-          onChange,
-        }}
-      />
-
+          ),
+          <Radio.Group
+            key="mode"
+            onChange={e => {
+              setMode(e.target.value);
+              setOptions(defaultOptions);
+            }}
+            defaultValue={mode}
+            size="small"
+          >
+            <Radio.Button value="list">
+              <UnorderedListOutlined />
+            </Radio.Button>
+            <Radio.Button value="table">
+              <TableOutlined />
+            </Radio.Button>
+          </Radio.Group>,
+        ]}
+      >
+        {mode === 'list' ? (
+          <List
+            size="small"
+            loading={loading}
+            dataSource={data?.list as Record<string, any>[]}
+            pagination={pagination}
+            renderItem={item => (
+              <List.Item
+                actions={[
+                  <Button key="edit" type="link" onClick={() => onEdit(item)}>
+                    <EditOutlined />
+                  </Button>,
+                  <Button key="del" type="link" onClick={() => onDelete(item)}>
+                    <DeleteOutlined />
+                  </Button>,
+                ]}
+              >
+                <span>
+                  <span style={{ marginRight: 10 }}>{item.sinkName}</span>
+                  <Tag>{item.sinkType}</Tag>
+                </span>
+              </List.Item>
+            )}
+          />
+        ) : (
+          <HighTable
+            filterForm={{
+              content: getFilterFormContent(options),
+              onFilter,
+            }}
+            table={{
+              columns,
+              rowKey: 'id',
+              size: 'small',
+              dataSource: data?.list,
+              pagination,
+              loading,
+              onChange,
+            }}
+          />
+        )}
+      </Card>
       <DetailModal
         {...createModal}
         defaultType={options.sinkType}
         inlongGroupId={inlongGroupId}
+        inlongStreamId={inlongStreamId}
         visible={createModal.visible as boolean}
         onOk={async () => {
           await getList();
diff --git a/inlong-dashboard/src/pages/GroupDetail/common.d.ts 
b/inlong-dashboard/src/pages/GroupDetail/DataStream/SourceSinkCard.tsx
similarity index 51%
copy from inlong-dashboard/src/pages/GroupDetail/common.d.ts
copy to inlong-dashboard/src/pages/GroupDetail/DataStream/SourceSinkCard.tsx
index 331158375..b573b076e 100644
--- a/inlong-dashboard/src/pages/GroupDetail/common.d.ts
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStream/SourceSinkCard.tsx
@@ -17,10 +17,33 @@
  * under the License.
  */
 
-export interface CommonInterface {
+import React from 'react';
+import { Col, Row } from 'antd';
+import { DoubleRightOutlined } from '@ant-design/icons';
+import DataSources from '../DataSources';
+import DataStorage from '../DataStorage';
+
+export interface Props {
   inlongGroupId: string;
-  mqType: 'TUBEMQ' | 'PULSAR';
-  readonly?: boolean;
-  isCreate?: boolean;
-  ref?: React.RefObject<unknown>;
+  inlongStreamId: string;
 }
+
+const Comp: React.FC<Props> = ({ inlongGroupId, inlongStreamId }) => {
+  return (
+    <>
+      <Row gutter={60}>
+        <Col span={12}>
+          <DataSources inlongGroupId={inlongGroupId} 
inlongStreamId={inlongStreamId} />
+        </Col>
+        <Col span={12}>
+          <DataStorage inlongGroupId={inlongGroupId} 
inlongStreamId={inlongStreamId} />
+        </Col>
+        <DoubleRightOutlined
+          style={{ position: 'absolute', top: '50%', left: 'calc(50% - 7px)' }}
+        />
+      </Row>
+    </>
+  );
+};
+
+export default Comp;
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx 
b/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx
index 6cd0d8612..10da98439 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx
@@ -19,6 +19,7 @@
 
 import React, { useState, useImperativeHandle, forwardRef, useMemo } from 
'react';
 import { Button, Modal, message } from 'antd';
+import { RightCircleTwoTone, DownCircleTwoTone } from '@ant-design/icons';
 import HighTable from '@/components/HighTable';
 import { defaultSize } from '@/configs/pagination';
 import { useRequest } from '@/hooks';
@@ -28,6 +29,7 @@ import { useLoadMeta, useDefaultMeta, StreamMetaType } from 
'@/metas';
 import { GroupLogs } from '@/components/GroupLogs';
 import { CommonInterface } from '../common';
 import StreamItemModal from './StreamItemModal';
+import SourceSinkCard from './SourceSinkCard';
 import { getFilterFormContent } from './config';
 
 type Props = CommonInterface;
@@ -56,6 +58,8 @@ const Comp = ({ inlongGroupId, readonly, mqType }: Props, 
ref) => {
 
   const [groupStatus, setGroupStatus] = useState();
 
+  const [expandedRowKeys, setExpandedRowKeys] = useState([]);
+
   const {
     data,
     loading,
@@ -71,6 +75,10 @@ const Comp = ({ inlongGroupId, readonly, mqType }: Props, 
ref) => {
     },
     {
       refreshDeps: [options],
+      onSuccess: result => {
+        const [item] = result?.list || [];
+        setExpandedRowKeys([item?.inlongStreamId]);
+      },
     },
   );
 
@@ -177,24 +185,24 @@ const Comp = ({ inlongGroupId, readonly, mqType }: Props, 
ref) => {
         readonly ? (
           '-'
         ) : (
-          <>
+          <div onClick={e => e.stopPropagation()}>
             <Button type="link" onClick={() => onEdit(record)}>
               {t('basic.Edit')}
             </Button>
             <Button type="link" onClick={() => onDelete(record)}>
               {t('basic.Delete')}
             </Button>
-            {record?.status && (groupStatus === 120 || groupStatus === 130) && 
(
-              <Button type="link" onClick={() => onWorkflow(record)}>
-                {t('meta.Stream.ExecuteWorkflow')}
-              </Button>
-            )}
             {record?.status && (record?.status === 120 || record?.status === 
130) && (
               <Button type="link" onClick={() => openModal(record)}>
                 {t('pages.GroupDashboard.config.ExecuteLog')}
               </Button>
             )}
-          </>
+            {record?.status && (groupStatus === 120 || groupStatus === 130) && 
(
+              <Button type="link" onClick={() => onWorkflow(record)}>
+                {t('meta.Stream.ExecuteWorkflow')}
+              </Button>
+            )}
+          </div>
         ),
     },
   ]);
@@ -215,11 +223,23 @@ const Comp = ({ inlongGroupId, readonly, mqType }: Props, 
ref) => {
         }
         table={{
           columns,
-          rowKey: 'id',
+          rowKey: 'inlongStreamId',
           dataSource: data?.list,
           pagination,
           loading,
           onChange,
+          expandRowByClick: true,
+          expandedRowKeys,
+          onExpandedRowsChange: rows => setExpandedRowKeys(rows),
+          expandedRowRender: record => (
+            <SourceSinkCard inlongGroupId={inlongGroupId} 
inlongStreamId={record.inlongStreamId} />
+          ),
+          expandIcon: ({ expanded, onExpand, record }) =>
+            expanded ? (
+              <DownCircleTwoTone onClick={e => onExpand(record, e)} />
+            ) : (
+              <RightCircleTwoTone onClick={e => onExpand(record, e)} />
+            ),
         }}
       />
 
diff --git a/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx 
b/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx
index e895cefdf..e9cf0849a 100644
--- a/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx
@@ -68,7 +68,7 @@ export const useFormContent = ({ mqType, editing, isCreate, 
isUpdate }) => {
   const isMqKey = useCallback(
     formName => {
       const defaultGroupKeysI18nMap = DefaultEntity?.I18nMap || {};
-      return !defaultGroupKeysI18nMap[formName] || formName === 'mqType';
+      return !defaultGroupKeysI18nMap[formName] || ['mqType', 
'mqResource'].includes(formName);
     },
     [DefaultEntity?.I18nMap],
   );
diff --git a/inlong-dashboard/src/pages/GroupDetail/common.d.ts 
b/inlong-dashboard/src/pages/GroupDetail/common.d.ts
index 331158375..6b44240b2 100644
--- a/inlong-dashboard/src/pages/GroupDetail/common.d.ts
+++ b/inlong-dashboard/src/pages/GroupDetail/common.d.ts
@@ -19,7 +19,7 @@
 
 export interface CommonInterface {
   inlongGroupId: string;
-  mqType: 'TUBEMQ' | 'PULSAR';
+  mqType?: 'TUBEMQ' | 'PULSAR';
   readonly?: boolean;
   isCreate?: boolean;
   ref?: React.RefObject<unknown>;
diff --git a/inlong-dashboard/src/pages/GroupDetail/index.tsx 
b/inlong-dashboard/src/pages/GroupDetail/index.tsx
index 326c8d2cc..c61bdb40b 100644
--- a/inlong-dashboard/src/pages/GroupDetail/index.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/index.tsx
@@ -25,9 +25,7 @@ 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';
-import DataStorage from './DataStorage';
 import Audit from './Audit';
 
 const Comp: React.FC = () => {
@@ -72,16 +70,6 @@ const Comp: React.FC = () => {
           value: 'dataStream',
           content: DataStream,
         },
-        {
-          label: t('pages.GroupDetail.Sources'),
-          value: 'dataSources',
-          content: DataSources,
-        },
-        {
-          label: t('pages.GroupDetail.Sinks'),
-          value: 'streamSink',
-          content: DataStorage,
-        },
         {
           label: t('pages.GroupDetail.Audit'),
           value: 'audit',

Reply via email to