You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@inlong.apache.org by le...@apache.org on 2022/11/04 03:17:21 UTC

[inlong] branch master updated: [INLONG-6391][Dashboard] Supports deep merge of field decorator (#6392)

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

leezng 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 cb1bcc897 [INLONG-6391][Dashboard] Supports deep merge of field decorator (#6392)
cb1bcc897 is described below

commit cb1bcc897c388c264692c5d84a9720252c7af86f
Author: Daniel <le...@apache.org>
AuthorDate: Fri Nov 4 11:17:16 2022 +0800

    [INLONG-6391][Dashboard] Supports deep merge of field decorator (#6392)
---
 inlong-dashboard/src/metas/DataStatic.ts           |   2 +-
 inlong-dashboard/src/metas/DataWithBackend.ts      |  96 ----------------
 inlong-dashboard/src/metas/RenderList.ts           |  51 +++++++++
 .../src/metas/{DataWithBackend.ts => RenderRow.ts} |  93 +++++----------
 .../metas/clusters/common/ClusterDefaultInfo.ts    |  42 +++++--
 .../src/metas/clusters/defaults/Agent.ts           |   6 +-
 .../src/metas/clusters/defaults/DataProxy.ts       |   6 +-
 .../src/metas/clusters/defaults/Kafka.ts           |  12 +-
 .../src/metas/clusters/defaults/Pulsar.ts          |  18 ++-
 .../src/metas/clusters/defaults/TubeMq.ts          |  16 ++-
 .../src/metas/clusters/defaults/index.ts           |   3 +-
 .../src/metas/clusters/extends/index.ts            |   3 +-
 inlong-dashboard/src/metas/clusters/index.ts       |   2 +
 .../{sinks/extends/index.ts => clusters/types.ts}  |   6 +-
 inlong-dashboard/src/metas/common.ts               |  82 --------------
 .../metas/consumes/common/ConsumeDefaultInfo.ts    |  54 ++++++---
 .../src/metas/consumes/defaults/Pulsar.ts          |  18 ++-
 .../src/metas/consumes/defaults/TubeMq.ts          |  14 ++-
 .../src/metas/consumes/defaults/index.ts           |   3 +-
 .../src/metas/consumes/extends/index.ts            |   3 +-
 inlong-dashboard/src/metas/consumes/index.ts       |   2 +
 .../{sinks/extends/index.ts => consumes/types.ts}  |   6 +-
 .../src/metas/groups/common/GroupDefaultInfo.ts    |  48 +++++---
 .../src/metas/groups/defaults/Kafka.ts             |  15 ++-
 .../src/metas/groups/defaults/Pulsar.ts            |  26 +++--
 .../src/metas/groups/defaults/TubeMq.ts            |  18 ++-
 .../src/metas/groups/defaults/index.ts             |   3 +-
 inlong-dashboard/src/metas/groups/extends/index.ts |   3 +-
 inlong-dashboard/src/metas/groups/index.ts         |   2 +
 .../{sinks/extends/index.ts => groups/types.ts}    |   6 +-
 inlong-dashboard/src/metas/index.ts                |  22 +++-
 .../src/metas/nodes/common/NodeDefaultInfo.ts      |  36 ++++--
 inlong-dashboard/src/metas/nodes/defaults/Hive.ts  |  15 ++-
 inlong-dashboard/src/metas/nodes/defaults/index.ts |   3 +-
 inlong-dashboard/src/metas/nodes/extends/index.ts  |   3 +-
 inlong-dashboard/src/metas/nodes/index.ts          |   2 +
 .../{sinks/extends/index.ts => nodes/types.ts}     |   6 +-
 .../src/metas/sinks/common/SinkDefaultInfo.ts      |  42 +++++--
 .../src/metas/sinks/common/sourceFields.ts         |   4 +-
 .../src/metas/sinks/defaults/ClickHouse.ts         |  49 ++++----
 inlong-dashboard/src/metas/sinks/defaults/Doris.ts |  44 +++----
 inlong-dashboard/src/metas/sinks/defaults/ES.ts    |  46 ++++----
 .../src/metas/sinks/defaults/Greenplum.ts          |  37 +++---
 inlong-dashboard/src/metas/sinks/defaults/HBase.ts |  44 +++----
 inlong-dashboard/src/metas/sinks/defaults/Hive.ts  |  38 ++++---
 .../src/metas/sinks/defaults/Iceberg.ts            |  45 ++++----
 inlong-dashboard/src/metas/sinks/defaults/Kafka.ts |  24 ++--
 inlong-dashboard/src/metas/sinks/defaults/MySQL.ts |  34 +++---
 .../src/metas/sinks/defaults/Oracle.ts             |  34 +++---
 .../src/metas/sinks/defaults/PostgreSQL.ts         |  38 ++++---
 .../src/metas/sinks/defaults/SQLServer.ts          |  43 ++++---
 .../src/metas/sinks/defaults/TDSQLPostgreSQL.ts    |  37 +++---
 inlong-dashboard/src/metas/sinks/defaults/index.ts |   3 +-
 inlong-dashboard/src/metas/sinks/extends/index.ts  |   3 +-
 inlong-dashboard/src/metas/sinks/index.ts          |   2 +
 .../src/metas/sinks/{extends/index.ts => types.ts} |   6 +-
 .../src/metas/sources/common/SourceDefaultInfo.ts  |  40 +++++--
 .../src/metas/sources/defaults/AutoPush.ts         |   6 +-
 .../src/metas/sources/defaults/File.ts             |  21 ++--
 .../src/metas/sources/defaults/MySQLBinlog.ts      |  33 +++---
 .../src/metas/sources/defaults/index.ts            |   3 +-
 .../src/metas/sources/extends/index.ts             |   3 +-
 inlong-dashboard/src/metas/sources/index.ts        |   2 +
 .../{sinks/extends/index.ts => sources/types.ts}   |   6 +-
 .../src/metas/streams/common/StreamDefaultInfo.ts  |  48 +++++---
 .../src/metas/streams/defaults/index.ts            |   3 +-
 .../src/metas/streams/extends/index.ts             |   3 +-
 inlong-dashboard/src/metas/streams/index.ts        |   2 +
 .../{sinks/extends/index.ts => streams/types.ts}   |   6 +-
 inlong-dashboard/src/metas/types.ts                |  34 ++----
 .../src/pages/Clusters/CreateModal.tsx             |   6 +-
 inlong-dashboard/src/pages/Clusters/index.tsx      |  14 ++-
 .../src/pages/ConsumeDashboard/config.tsx          |  60 +++++-----
 .../src/pages/ConsumeDetail/Info/config.tsx        |  11 +-
 .../src/pages/GroupDashboard/config.tsx            |  68 ++++++-----
 .../pages/GroupDetail/DataSources/DetailModal.tsx  |   6 +-
 .../src/pages/GroupDetail/DataSources/index.tsx    |  22 ++--
 .../pages/GroupDetail/DataStorage/DetailModal.tsx  |   6 +-
 .../src/pages/GroupDetail/DataStorage/index.tsx    |  22 ++--
 .../GroupDetail/DataStream/StreamItemModal.tsx     | 126 +++++++++++----------
 .../src/pages/GroupDetail/DataStream/index.tsx     |  12 +-
 .../src/pages/GroupDetail/Info/config.tsx          |  11 +-
 inlong-dashboard/src/pages/Nodes/DetailModal.tsx   |   6 +-
 inlong-dashboard/src/pages/Nodes/index.tsx         |  54 +++++----
 .../src/pages/ProcessDetail/AccessConfig.tsx       |  12 +-
 .../src/pages/ProcessDetail/ConsumeConfig.tsx      |  12 +-
 inlong-dashboard/src/utils/metaData.ts             |  66 -----------
 87 files changed, 1078 insertions(+), 965 deletions(-)

diff --git a/inlong-dashboard/src/metas/DataStatic.ts b/inlong-dashboard/src/metas/DataStatic.ts
index 0059abdf8..292ec38d4 100644
--- a/inlong-dashboard/src/metas/DataStatic.ts
+++ b/inlong-dashboard/src/metas/DataStatic.ts
@@ -20,7 +20,7 @@
 import i18n from '@/i18n';
 
 export abstract class DataStatic {
-  static I18nMap: Record<string, unknown> = {};
+  static I18nMap: Record<string, string> = {};
 
   static I18n(i18nkey: string): PropertyDecorator {
     return (target: any, propertyKey: string) => {
diff --git a/inlong-dashboard/src/metas/DataWithBackend.ts b/inlong-dashboard/src/metas/DataWithBackend.ts
index 34cf70b39..8104b17d0 100644
--- a/inlong-dashboard/src/metas/DataWithBackend.ts
+++ b/inlong-dashboard/src/metas/DataWithBackend.ts
@@ -17,105 +17,9 @@
  * under the License.
  */
 
-import { ColumnType } from 'antd/es/table';
-import type { FieldItemType } from '@/metas/common';
-import { isDevelopEnv } from '@/utils';
 import { DataStatic } from './DataStatic';
 
-interface PositionObjectType extends Record<string, unknown> {
-  position: ['before' | 'after', string];
-}
-
-function sortListPosition(list: PositionObjectType[], primaryPositionKey = 'name') {
-  const output: Record<string, unknown>[] = [];
-  const notFoundPosMap: Record<string, PositionObjectType> = {};
-  const _list = [...list];
-  let loopCount = 0;
-  while (_list.length) {
-    loopCount++;
-    if (loopCount > 500) {
-      console.error(
-        '[Apache InLong Error] The number of loops of the sorting algorithm array has reached the maximum limit, please check or adjust the configuration.',
-      );
-      break;
-    }
-    const listItem = _list.shift();
-    if (!listItem) continue;
-    if (listItem.position) {
-      const [positionType, positionName] = listItem.position;
-      const index = output.findIndex(item => item[primaryPositionKey] === positionName);
-      if (index !== -1) {
-        output.splice(positionType === 'before' ? index : index + 1, 0, listItem);
-      } else {
-        notFoundPosMap[positionName] = listItem;
-      }
-    } else {
-      output.push(listItem);
-    }
-    const currentItemName = listItem[primaryPositionKey] as string;
-    if (notFoundPosMap[currentItemName]) {
-      _list.push(notFoundPosMap[currentItemName]);
-      delete notFoundPosMap[currentItemName];
-    }
-  }
-
-  const notFoundPosList = Object.keys(notFoundPosMap).map(name => notFoundPosMap[name]);
-  return output.concat(notFoundPosList);
-}
-
 export abstract class DataWithBackend extends DataStatic {
-  static FieldList: FieldItemType[] = [];
-  static ColumnList: ColumnType<Record<string, any>>[] = [];
-
-  static FormField(config: FieldItemType): PropertyDecorator {
-    return (target: any, propertyKey: string) => {
-      const { I18nMap, FieldList } = target.constructor;
-      const newFieldList = [...FieldList];
-
-      if (isDevelopEnv()) {
-        // Hot refresh of the development env will have old data
-        const existIndex = newFieldList.findIndex(item => item.name === propertyKey);
-        if (existIndex !== -1) {
-          newFieldList.splice(existIndex, 1);
-        }
-      }
-
-      newFieldList.push({
-        ...config,
-        name: propertyKey,
-        label: I18nMap[propertyKey],
-      });
-
-      // console.log('--', target.constructor, target.constructor.timer);
-      // if (target.constructor.timer) {
-      //   clearTimeout(target.constructor.timer);
-      // }
-      // target.constructor.timer = setTimeout(() => {
-      //   sortListPosition(newFieldList);
-      // }, 0);
-      // const sortedFieldList = sortListPosition(newFieldList);
-
-      target.constructor.FieldList = newFieldList;
-    };
-  }
-
-  static TableColumn(config?: ColumnType<Record<string, any>>): PropertyDecorator {
-    return (target: any, propertyKey: string) => {
-      const { I18nMap, ColumnList } = target.constructor;
-      const oldIndex = ColumnList.findIndex(item => item.name === propertyKey);
-      const subColumnList = [...ColumnList];
-      if (oldIndex !== -1) {
-        subColumnList.splice(oldIndex, 1);
-      }
-      subColumnList.push({
-        ...(typeof config === 'object' ? config : {}),
-        dataIndex: propertyKey,
-        title: I18nMap[propertyKey],
-      });
-      target.constructor.ColumnList = subColumnList;
-    };
-  }
-
   abstract parse<T, K>(data: T): K;
 
   abstract stringify<T, K>(data: T): K;
diff --git a/inlong-dashboard/src/metas/RenderList.ts b/inlong-dashboard/src/metas/RenderList.ts
new file mode 100644
index 000000000..19f1a46cd
--- /dev/null
+++ b/inlong-dashboard/src/metas/RenderList.ts
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ColumnType } from 'antd/es/table';
+import merge from 'lodash/merge';
+
+type ColumnItemType = ColumnType<Record<string, any>>;
+
+export abstract class RenderList {
+  static ColumnList: ColumnItemType[] = [];
+
+  static ColumnDecorator(config?: ColumnItemType): PropertyDecorator {
+    return (target: any, propertyKey: string) => {
+      const { I18nMap, ColumnList } = target.constructor;
+      const newColumnList = [...ColumnList];
+      const oldIndex = ColumnList.findIndex(item => item.name === propertyKey);
+      const column = {
+        ...(typeof config === 'object' ? config : {}),
+        dataIndex: propertyKey,
+        title: I18nMap[propertyKey],
+      };
+
+      if (oldIndex !== -1) {
+        const mergeField = merge(newColumnList[oldIndex], column);
+        newColumnList.splice(oldIndex, 1, mergeField);
+      } else {
+        newColumnList.push(column);
+      }
+
+      target.constructor.ColumnList = newColumnList;
+    };
+  }
+
+  abstract renderList(columns?: ColumnItemType[]): ColumnItemType[];
+}
diff --git a/inlong-dashboard/src/metas/DataWithBackend.ts b/inlong-dashboard/src/metas/RenderRow.ts
similarity index 61%
copy from inlong-dashboard/src/metas/DataWithBackend.ts
copy to inlong-dashboard/src/metas/RenderRow.ts
index 34cf70b39..c01643853 100644
--- a/inlong-dashboard/src/metas/DataWithBackend.ts
+++ b/inlong-dashboard/src/metas/RenderRow.ts
@@ -17,10 +17,37 @@
  * under the License.
  */
 
-import { ColumnType } from 'antd/es/table';
-import type { FieldItemType } from '@/metas/common';
-import { isDevelopEnv } from '@/utils';
-import { DataStatic } from './DataStatic';
+import type { FormItemProps as FieldItemType } from '@/components/FormGenerator';
+import merge from 'lodash/merge';
+
+export abstract class RenderRow {
+  static FieldList: FieldItemType[] = [];
+
+  static FieldDecorator(config: FieldItemType): PropertyDecorator {
+    return (target: any, propertyKey: string) => {
+      const { I18nMap, FieldList } = target.constructor;
+      const newFieldList = [...FieldList];
+      const existIndex = newFieldList.findIndex(item => item.name === propertyKey);
+      const field = {
+        ...config,
+        name: propertyKey,
+        label: I18nMap[propertyKey],
+      };
+
+      if (existIndex !== -1) {
+        const mergeField = merge(newFieldList[existIndex], field);
+        newFieldList.splice(existIndex, 1, mergeField);
+      } else {
+        newFieldList.push(field);
+      }
+
+      // const sortedFieldList = sortListPosition(newFieldList);
+      target.constructor.FieldList = newFieldList;
+    };
+  }
+
+  abstract renderRow(fields?: FieldItemType[]): FieldItemType[];
+}
 
 interface PositionObjectType extends Record<string, unknown> {
   position: ['before' | 'after', string];
@@ -62,61 +89,3 @@ function sortListPosition(list: PositionObjectType[], primaryPositionKey = 'name
   const notFoundPosList = Object.keys(notFoundPosMap).map(name => notFoundPosMap[name]);
   return output.concat(notFoundPosList);
 }
-
-export abstract class DataWithBackend extends DataStatic {
-  static FieldList: FieldItemType[] = [];
-  static ColumnList: ColumnType<Record<string, any>>[] = [];
-
-  static FormField(config: FieldItemType): PropertyDecorator {
-    return (target: any, propertyKey: string) => {
-      const { I18nMap, FieldList } = target.constructor;
-      const newFieldList = [...FieldList];
-
-      if (isDevelopEnv()) {
-        // Hot refresh of the development env will have old data
-        const existIndex = newFieldList.findIndex(item => item.name === propertyKey);
-        if (existIndex !== -1) {
-          newFieldList.splice(existIndex, 1);
-        }
-      }
-
-      newFieldList.push({
-        ...config,
-        name: propertyKey,
-        label: I18nMap[propertyKey],
-      });
-
-      // console.log('--', target.constructor, target.constructor.timer);
-      // if (target.constructor.timer) {
-      //   clearTimeout(target.constructor.timer);
-      // }
-      // target.constructor.timer = setTimeout(() => {
-      //   sortListPosition(newFieldList);
-      // }, 0);
-      // const sortedFieldList = sortListPosition(newFieldList);
-
-      target.constructor.FieldList = newFieldList;
-    };
-  }
-
-  static TableColumn(config?: ColumnType<Record<string, any>>): PropertyDecorator {
-    return (target: any, propertyKey: string) => {
-      const { I18nMap, ColumnList } = target.constructor;
-      const oldIndex = ColumnList.findIndex(item => item.name === propertyKey);
-      const subColumnList = [...ColumnList];
-      if (oldIndex !== -1) {
-        subColumnList.splice(oldIndex, 1);
-      }
-      subColumnList.push({
-        ...(typeof config === 'object' ? config : {}),
-        dataIndex: propertyKey,
-        title: I18nMap[propertyKey],
-      });
-      target.constructor.ColumnList = subColumnList;
-    };
-  }
-
-  abstract parse<T, K>(data: T): K;
-
-  abstract stringify<T, K>(data: T): K;
-}
diff --git a/inlong-dashboard/src/metas/clusters/common/ClusterDefaultInfo.ts b/inlong-dashboard/src/metas/clusters/common/ClusterDefaultInfo.ts
index 0cb4c385c..fc9e57505 100644
--- a/inlong-dashboard/src/metas/clusters/common/ClusterDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/clusters/common/ClusterDefaultInfo.ts
@@ -18,26 +18,34 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import UserSelect from '@/components/UserSelect';
 import { clusters, defaultValue } from '..';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
 
-export class ClusterDefaultInfo extends DataWithBackend {
-  id: number;
+export class ClusterDefaultInfo implements DataWithBackend, RenderRow, RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
 
-  @FormField({
+  readonly id: number;
+
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: {
       maxLength: 128,
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('pages.Clusters.Name')
   name: string;
 
-  @FormField({
+  @FieldDecorator({
     type: clusters.length > 3 ? 'select' : 'radio',
     initialValue: defaultValue,
     rules: [{ required: true }],
@@ -50,11 +58,11 @@ export class ClusterDefaultInfo extends DataWithBackend {
         })),
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('pages.Clusters.Type')
   type: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     rules: [{ required: true }],
     props: {
@@ -82,11 +90,11 @@ export class ClusterDefaultInfo extends DataWithBackend {
       },
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('pages.Clusters.Tag')
   clusterTags: string;
 
-  @FormField({
+  @FieldDecorator({
     type: UserSelect,
     rules: [{ required: true }],
     props: {
@@ -94,11 +102,11 @@ export class ClusterDefaultInfo extends DataWithBackend {
       currentUserClosable: false,
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('pages.Clusters.InCharges')
   inCharges: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'textarea',
     props: {
       maxLength: 256,
@@ -116,4 +124,14 @@ export class ClusterDefaultInfo extends DataWithBackend {
   stringify(data) {
     return data;
   }
+
+  renderRow() {
+    const constructor = this.constructor as typeof ClusterDefaultInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof ClusterDefaultInfo;
+    return constructor.ColumnList;
+  }
 }
diff --git a/inlong-dashboard/src/metas/clusters/defaults/Agent.ts b/inlong-dashboard/src/metas/clusters/defaults/Agent.ts
index d0f817001..2b81d5bce 100644
--- a/inlong-dashboard/src/metas/clusters/defaults/Agent.ts
+++ b/inlong-dashboard/src/metas/clusters/defaults/Agent.ts
@@ -18,6 +18,10 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { ClusterInfo } from '../common/ClusterInfo';
 
-export default class AgentCluster extends ClusterInfo implements DataWithBackend {}
+export default class AgentCluster
+  extends ClusterInfo
+  implements DataWithBackend, RenderRow, RenderList {}
diff --git a/inlong-dashboard/src/metas/clusters/defaults/DataProxy.ts b/inlong-dashboard/src/metas/clusters/defaults/DataProxy.ts
index 8a0a6fb6f..a834e4c77 100644
--- a/inlong-dashboard/src/metas/clusters/defaults/DataProxy.ts
+++ b/inlong-dashboard/src/metas/clusters/defaults/DataProxy.ts
@@ -18,6 +18,10 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { ClusterInfo } from '../common/ClusterInfo';
 
-export default class DataProxyCluster extends ClusterInfo implements DataWithBackend {}
+export default class DataProxyCluster
+  extends ClusterInfo
+  implements DataWithBackend, RenderRow, RenderList {}
diff --git a/inlong-dashboard/src/metas/clusters/defaults/Kafka.ts b/inlong-dashboard/src/metas/clusters/defaults/Kafka.ts
index 39322cb75..d40e21499 100644
--- a/inlong-dashboard/src/metas/clusters/defaults/Kafka.ts
+++ b/inlong-dashboard/src/metas/clusters/defaults/Kafka.ts
@@ -18,12 +18,18 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { ClusterInfo } from '../common/ClusterInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class KafkaCluster extends ClusterInfo implements DataWithBackend {
-  @FormField({
+export default class KafkaCluster
+  extends ClusterInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: {
diff --git a/inlong-dashboard/src/metas/clusters/defaults/Pulsar.ts b/inlong-dashboard/src/metas/clusters/defaults/Pulsar.ts
index 443b7296d..86667cade 100644
--- a/inlong-dashboard/src/metas/clusters/defaults/Pulsar.ts
+++ b/inlong-dashboard/src/metas/clusters/defaults/Pulsar.ts
@@ -19,12 +19,18 @@
 
 import i18n from '@/i18n';
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { ClusterInfo } from '../common/ClusterInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class PulsarCluster extends ClusterInfo implements DataWithBackend {
-  @FormField({
+export default class PulsarCluster
+  extends ClusterInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     tooltip: i18n.t('pages.Clusters.Pulsar.ServiceUrlHelper'),
     rules: [{ required: true }],
@@ -35,7 +41,7 @@ export default class PulsarCluster extends ClusterInfo implements DataWithBacken
   @I18n('Service URL')
   url: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     tooltip: i18n.t('pages.Clusters.Pulsar.AdminUrlHelper'),
     rules: [{ required: true }],
@@ -46,7 +52,7 @@ export default class PulsarCluster extends ClusterInfo implements DataWithBacken
   @I18n('Admin URL')
   adminUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: 'public',
@@ -54,7 +60,7 @@ export default class PulsarCluster extends ClusterInfo implements DataWithBacken
   @I18n('pages.Clusters.Pulsar.Tenant')
   tenant: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: {
       placeholder: 'Required if the cluster is configured with Token',
diff --git a/inlong-dashboard/src/metas/clusters/defaults/TubeMq.ts b/inlong-dashboard/src/metas/clusters/defaults/TubeMq.ts
index 6519c4a45..d8f229157 100644
--- a/inlong-dashboard/src/metas/clusters/defaults/TubeMq.ts
+++ b/inlong-dashboard/src/metas/clusters/defaults/TubeMq.ts
@@ -19,12 +19,18 @@
 
 import i18n from '@/i18n';
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { ClusterInfo } from '../common/ClusterInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class TubeMqCluster extends ClusterInfo implements DataWithBackend {
-  @FormField({
+export default class TubeMqCluster
+  extends ClusterInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     tooltip: i18n.t('pages.Clusters.Tube.MasterRpcUrlHelper'),
@@ -35,7 +41,7 @@ export default class TubeMqCluster extends ClusterInfo implements DataWithBacken
   @I18n('RPC URL')
   url: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     tooltip: i18n.t('pages.Clusters.Tube.MasterWebUrlHelper'),
@@ -46,7 +52,7 @@ export default class TubeMqCluster extends ClusterInfo implements DataWithBacken
   @I18n('Web URL')
   masterWebUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: {
       placeholder: 'Required if the cluster is configured with Token',
diff --git a/inlong-dashboard/src/metas/clusters/defaults/index.ts b/inlong-dashboard/src/metas/clusters/defaults/index.ts
index b7054aff4..2edda556d 100644
--- a/inlong-dashboard/src/metas/clusters/defaults/index.ts
+++ b/inlong-dashboard/src/metas/clusters/defaults/index.ts
@@ -18,8 +18,9 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { ClusterMetaType } from '../types';
 
-export const allDefaultClusters: MetaExportWithBackendList = [
+export const allDefaultClusters: MetaExportWithBackendList<ClusterMetaType> = [
   {
     label: 'ALL',
     value: '',
diff --git a/inlong-dashboard/src/metas/clusters/extends/index.ts b/inlong-dashboard/src/metas/clusters/extends/index.ts
index 4d794befd..e48f66967 100644
--- a/inlong-dashboard/src/metas/clusters/extends/index.ts
+++ b/inlong-dashboard/src/metas/clusters/extends/index.ts
@@ -18,7 +18,8 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { ClusterMetaType } from '../types';
 
-export const allExtendsClusters: MetaExportWithBackendList = [
+export const allExtendsClusters: MetaExportWithBackendList<ClusterMetaType> = [
   // You can extends at here...
 ];
diff --git a/inlong-dashboard/src/metas/clusters/index.ts b/inlong-dashboard/src/metas/clusters/index.ts
index 2c6643083..799156404 100644
--- a/inlong-dashboard/src/metas/clusters/index.ts
+++ b/inlong-dashboard/src/metas/clusters/index.ts
@@ -20,6 +20,8 @@
 import { allDefaultClusters } from './defaults';
 import { allExtendsClusters } from './extends';
 
+export type { ClusterMetaType } from './types';
+
 export const clusters = allDefaultClusters.concat(allExtendsClusters);
 
 export const defaultValue = clusters[0].value;
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/clusters/types.ts
similarity index 83%
copy from inlong-dashboard/src/metas/sinks/extends/index.ts
copy to inlong-dashboard/src/metas/clusters/types.ts
index 0fdb7d5b6..92f6b7b21 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/clusters/types.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import type { MetaExportWithBackendList } from '@/metas/types';
+import { ClusterInfo } from './common/ClusterInfo';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
-  // You can extends at here...
-];
+export type ClusterMetaType = typeof ClusterInfo;
diff --git a/inlong-dashboard/src/metas/common.ts b/inlong-dashboard/src/metas/common.ts
deleted file mode 100644
index d67ae935b..000000000
--- a/inlong-dashboard/src/metas/common.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import type { FormItemProps } from '@/components/FormGenerator';
-import { ColumnType } from 'antd/es/table';
-import { excludeObject } from '@/utils';
-
-export interface MetaType {
-  fields: ReturnType<typeof genFields>;
-  form: ReturnType<typeof genForm>;
-  table: ReturnType<typeof genTable>;
-}
-
-export interface FieldItemType extends FormItemProps {
-  position?: string[];
-  _renderTable?: boolean | ColumnType<Record<string, any>>;
-}
-
-export const genFields = (
-  fieldsDefault: FieldItemType[],
-  fieldsExtends?: FieldItemType[],
-): FieldItemType[] => {
-  const output: FieldItemType[] = [];
-  const fields = fieldsDefault.concat(fieldsExtends);
-  while (fields.length) {
-    const fieldItem = fields.shift();
-    if (!fieldItem) continue;
-    if (fieldItem.position) {
-      const [positionType, positionName] = fieldItem.position;
-      const index = output.findIndex(item => item.name === positionName);
-      if (index !== -1) {
-        output.splice(positionType === 'before' ? index : index + 1, 0, fieldItem);
-      } else {
-        fields.push(fieldItem);
-      }
-    } else {
-      output.push(fieldItem);
-    }
-  }
-
-  return output;
-};
-
-export const genForm = (fields: Omit<FieldItemType, 'position'>[]): FormItemProps[] => {
-  return fields.map(item => excludeObject(['_renderTable'], item));
-};
-
-export const genTable = (
-  fields: Omit<FieldItemType, 'position'>[],
-): ColumnType<Record<string, any>>[] => {
-  return fields
-    .filter(item => Boolean(item._renderTable))
-    .map(item => {
-      let output: ColumnType<Record<string, any>> = {
-        title: item.label,
-        dataIndex: item.name,
-      };
-      if (typeof item._renderTable === 'object') {
-        output = {
-          ...output,
-          ...item._renderTable,
-        };
-      }
-      return output;
-    });
-};
diff --git a/inlong-dashboard/src/metas/consumes/common/ConsumeDefaultInfo.ts b/inlong-dashboard/src/metas/consumes/common/ConsumeDefaultInfo.ts
index bc8e65875..c428d5c18 100644
--- a/inlong-dashboard/src/metas/consumes/common/ConsumeDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/consumes/common/ConsumeDefaultInfo.ts
@@ -17,9 +17,11 @@
  * under the License.
  */
 
-import UserSelect from '@/components/UserSelect';
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
+import UserSelect from '@/components/UserSelect';
 import { timestampFormat } from '@/utils';
 import {
   statusList,
@@ -28,12 +30,18 @@ import {
   genLastConsumerStatusTag,
 } from './status';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
+
+export class ConsumeDefaultInfo implements DataWithBackend, RenderRow, RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
 
-export class ConsumeDefaultInfo extends DataWithBackend {
   readonly id: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     extra: i18n.t('meta.Consume.ConsumerGroupNameRules'),
     rules: [
@@ -44,11 +52,11 @@ export class ConsumeDefaultInfo extends DataWithBackend {
       },
     ],
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Consume.ConsumerGroupName')
   consumerGroup: string;
 
-  @FormField({
+  @FieldDecorator({
     type: UserSelect,
     extra: i18n.t('meta.Consume.OwnersExtra'),
     rules: [{ required: true }],
@@ -57,11 +65,11 @@ export class ConsumeDefaultInfo extends DataWithBackend {
       currentUserClosable: false,
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Consume.Owner')
   inCharges: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     extraNames: ['mqType'],
     rules: [{ required: true }],
@@ -95,15 +103,15 @@ export class ConsumeDefaultInfo extends DataWithBackend {
       }),
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Consume.TargetInlongGroupID')
   inlongGroupId: string;
 
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Consume.MQType')
   mqType: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     rules: [{ required: true }],
     props: values => ({
@@ -136,11 +144,11 @@ export class ConsumeDefaultInfo extends DataWithBackend {
     }),
     visible: values => Boolean(values.inlongGroupId),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Consume.TopicName')
   topic: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: {
       allowClear: true,
@@ -149,19 +157,19 @@ export class ConsumeDefaultInfo extends DataWithBackend {
     },
     visible: false,
   })
-  @TableColumn({
+  @ColumnDecorator({
     render: text => genStatusTag(text),
   })
   @I18n('basic.Status')
   readonly status: string;
 
-  @TableColumn({
+  @ColumnDecorator({
     render: text => text && timestampFormat(text),
   })
   @I18n('pages.ConsumeDashboard.config.RecentConsumeTime')
   readonly lastConsumeTime: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: {
       allowClear: true,
@@ -170,13 +178,13 @@ export class ConsumeDefaultInfo extends DataWithBackend {
     },
     visible: false,
   })
-  @TableColumn({
+  @ColumnDecorator({
     render: text => text && genLastConsumerStatusTag(text),
   })
   @I18n('pages.ConsumeDashboard.config.OperatingStatus')
   readonly lastConsumeStatus: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'text',
   })
   @I18n('meta.Consume.MQAddress')
@@ -189,4 +197,14 @@ export class ConsumeDefaultInfo extends DataWithBackend {
   stringify(data) {
     return data;
   }
+
+  renderRow() {
+    const constructor = this.constructor as typeof ConsumeDefaultInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof ConsumeDefaultInfo;
+    return constructor.ColumnList;
+  }
 }
diff --git a/inlong-dashboard/src/metas/consumes/defaults/Pulsar.ts b/inlong-dashboard/src/metas/consumes/defaults/Pulsar.ts
index f5a8ae6a3..04e0ac284 100644
--- a/inlong-dashboard/src/metas/consumes/defaults/Pulsar.ts
+++ b/inlong-dashboard/src/metas/consumes/defaults/Pulsar.ts
@@ -18,13 +18,19 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import { ConsumeInfo } from '../common/ConsumeInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class PulsarConsume extends ConsumeInfo implements DataWithBackend {
-  @FormField({
+export default class PulsarConsume
+  extends ConsumeInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'radio',
     initialValue: 0,
     rules: [{ required: true }],
@@ -44,7 +50,7 @@ export default class PulsarConsume extends ConsumeInfo implements DataWithBacken
   @I18n('isDlq')
   isDlq: 0 | 1;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     visible: values => values?.isDlq,
@@ -52,7 +58,7 @@ export default class PulsarConsume extends ConsumeInfo implements DataWithBacken
   @I18n('deadLetterTopic')
   deadLetterTopic: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 0,
     rules: [{ required: true }],
@@ -73,7 +79,7 @@ export default class PulsarConsume extends ConsumeInfo implements DataWithBacken
   @I18n('isRlq')
   isRlq: 0 | 1;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     visible: values => values?.isDlq && values?.isRlq,
diff --git a/inlong-dashboard/src/metas/consumes/defaults/TubeMq.ts b/inlong-dashboard/src/metas/consumes/defaults/TubeMq.ts
index 9aa4ef7da..e0758b531 100644
--- a/inlong-dashboard/src/metas/consumes/defaults/TubeMq.ts
+++ b/inlong-dashboard/src/metas/consumes/defaults/TubeMq.ts
@@ -18,13 +18,19 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import { ConsumeInfo } from '../common/ConsumeInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class TubeMqConsume extends ConsumeInfo implements DataWithBackend {
-  @FormField({
+export default class TubeMqConsume
+  extends ConsumeInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'radio',
     initialValue: 0,
     props: {
@@ -44,7 +50,7 @@ export default class TubeMqConsume extends ConsumeInfo implements DataWithBacken
   @I18n('meta.Consume.FilterEnabled')
   filterEnabled: 0 | 1;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     visible: values => values.filterEnabled,
diff --git a/inlong-dashboard/src/metas/consumes/defaults/index.ts b/inlong-dashboard/src/metas/consumes/defaults/index.ts
index 792430a45..214cf092f 100644
--- a/inlong-dashboard/src/metas/consumes/defaults/index.ts
+++ b/inlong-dashboard/src/metas/consumes/defaults/index.ts
@@ -18,8 +18,9 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { ConsumeMetaType } from '../types';
 
-export const allDefaultConsumes: MetaExportWithBackendList = [
+export const allDefaultConsumes: MetaExportWithBackendList<ConsumeMetaType> = [
   {
     label: 'ALL',
     value: '',
diff --git a/inlong-dashboard/src/metas/consumes/extends/index.ts b/inlong-dashboard/src/metas/consumes/extends/index.ts
index ce5443978..715e26018 100644
--- a/inlong-dashboard/src/metas/consumes/extends/index.ts
+++ b/inlong-dashboard/src/metas/consumes/extends/index.ts
@@ -18,7 +18,8 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { ConsumeMetaType } from '../types';
 
-export const allExtendsConsumes: MetaExportWithBackendList = [
+export const allExtendsConsumes: MetaExportWithBackendList<ConsumeMetaType> = [
   // You can extends at here...
 ];
diff --git a/inlong-dashboard/src/metas/consumes/index.ts b/inlong-dashboard/src/metas/consumes/index.ts
index 2ed0bd403..1d6c821c3 100644
--- a/inlong-dashboard/src/metas/consumes/index.ts
+++ b/inlong-dashboard/src/metas/consumes/index.ts
@@ -20,6 +20,8 @@
 import { allDefaultConsumes } from './defaults';
 import { allExtendsConsumes } from './extends';
 
+export type { ConsumeMetaType } from './types';
+
 export const consumes = allDefaultConsumes.concat(allExtendsConsumes);
 
 export const defaultValue = consumes[0].value;
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/consumes/types.ts
similarity index 83%
copy from inlong-dashboard/src/metas/sinks/extends/index.ts
copy to inlong-dashboard/src/metas/consumes/types.ts
index 0fdb7d5b6..53f95595a 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/consumes/types.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import type { MetaExportWithBackendList } from '@/metas/types';
+import { ConsumeInfo } from './common/ConsumeInfo';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
-  // You can extends at here...
-];
+export type ConsumeMetaType = typeof ConsumeInfo;
diff --git a/inlong-dashboard/src/metas/groups/common/GroupDefaultInfo.ts b/inlong-dashboard/src/metas/groups/common/GroupDefaultInfo.ts
index 762c66a25..892cb9e18 100644
--- a/inlong-dashboard/src/metas/groups/common/GroupDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/groups/common/GroupDefaultInfo.ts
@@ -17,18 +17,26 @@
  * under the License.
  */
 
-import UserSelect from '@/components/UserSelect';
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
+import UserSelect from '@/components/UserSelect';
 import { statusList, genStatusTag } from './status';
 import { groups, defaultValue } from '..';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
+
+export class GroupDefaultInfo implements DataWithBackend, RenderRow, RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
 
-export class GroupDefaultInfo extends DataWithBackend {
   readonly id: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: {
       maxLength: 32,
@@ -41,11 +49,11 @@ export class GroupDefaultInfo extends DataWithBackend {
       },
     ],
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Group.InlongGroupId')
   inlongGroupId: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: {
       maxLength: 32,
@@ -54,7 +62,7 @@ export class GroupDefaultInfo extends DataWithBackend {
   @I18n('meta.Group.InlongGroupName')
   name: string;
 
-  @FormField({
+  @FieldDecorator({
     type: UserSelect,
     extra: i18n.t('meta.Group.InlongGroupOwnersExtra'),
     rules: [{ required: true }],
@@ -63,11 +71,11 @@ export class GroupDefaultInfo extends DataWithBackend {
       currentUserClosable: false,
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Group.InlongGroupOwners')
   inCharges: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'textarea',
     props: {
       showCount: true,
@@ -77,7 +85,7 @@ export class GroupDefaultInfo extends DataWithBackend {
   @I18n('meta.Group.InlongGroupIntroduction')
   description: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: defaultValue,
     rules: [{ required: true }],
@@ -85,17 +93,17 @@ export class GroupDefaultInfo extends DataWithBackend {
       options: groups.filter(item => Boolean(item.value)),
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Group.MQType')
   mqType: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'text',
   })
   @I18n('MQ Resource')
   readonly mqResource: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: {
       allowClear: true,
@@ -104,13 +112,13 @@ export class GroupDefaultInfo extends DataWithBackend {
     },
     visible: false,
   })
-  @TableColumn({
+  @ColumnDecorator({
     render: text => genStatusTag(text),
   })
   @I18n('basic.Status')
   readonly status: string;
 
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('basic.CreateTime')
   readonly createTime: string;
 
@@ -121,4 +129,14 @@ export class GroupDefaultInfo extends DataWithBackend {
   stringify(data) {
     return data;
   }
+
+  renderRow() {
+    const constructor = this.constructor as typeof GroupDefaultInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof GroupDefaultInfo;
+    return constructor.ColumnList;
+  }
 }
diff --git a/inlong-dashboard/src/metas/groups/defaults/Kafka.ts b/inlong-dashboard/src/metas/groups/defaults/Kafka.ts
index f809c65b3..bc7d138b0 100644
--- a/inlong-dashboard/src/metas/groups/defaults/Kafka.ts
+++ b/inlong-dashboard/src/metas/groups/defaults/Kafka.ts
@@ -18,13 +18,18 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
-import i18n from '@/i18n';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { GroupInfo } from '../common/GroupInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class KafkaGroup extends GroupInfo implements DataWithBackend {
-  @FormField({
+export default class KafkaGroup
+  extends GroupInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     props: {
@@ -35,7 +40,7 @@ export default class KafkaGroup extends GroupInfo implements DataWithBackend {
   @I18n('meta.Group.Kafka.Partition')
   numPartitions: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     initialValue: 1,
diff --git a/inlong-dashboard/src/metas/groups/defaults/Pulsar.ts b/inlong-dashboard/src/metas/groups/defaults/Pulsar.ts
index 635c9601e..d64c851b4 100644
--- a/inlong-dashboard/src/metas/groups/defaults/Pulsar.ts
+++ b/inlong-dashboard/src/metas/groups/defaults/Pulsar.ts
@@ -18,13 +18,19 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import { GroupInfo } from '../common/GroupInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class PulsarGroup extends GroupInfo implements DataWithBackend {
-  @FormField({
+export default class PulsarGroup
+  extends GroupInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'radio',
     initialValue: 'SERIAL',
     rules: [{ required: true }],
@@ -44,7 +50,7 @@ export default class PulsarGroup extends GroupInfo implements DataWithBackend {
   @I18n('meta.Group.Pulsar.QueueModule')
   queueModule: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 3,
     rules: [{ required: true }],
@@ -58,7 +64,7 @@ export default class PulsarGroup extends GroupInfo implements DataWithBackend {
   @I18n('meta.Group.Pulsar.PartitionNum')
   partitionNum: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 3,
     suffix: i18n.t('meta.Group.Pulsar.EnsembleSuffix'),
@@ -86,7 +92,7 @@ export default class PulsarGroup extends GroupInfo implements DataWithBackend {
   @I18n('ensemble')
   ensemble: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 3,
     suffix: i18n.t('meta.Group.Pulsar.WriteQuorumSuffix'),
@@ -100,7 +106,7 @@ export default class PulsarGroup extends GroupInfo implements DataWithBackend {
   @I18n('Write Quorum')
   writeQuorum: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 2,
     suffix: i18n.t('meta.Group.Pulsar.AckQuorumSuffix'),
@@ -114,7 +120,7 @@ export default class PulsarGroup extends GroupInfo implements DataWithBackend {
   @I18n('ACK Quorum')
   ackQuorum: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 24,
     rules: [
@@ -155,7 +161,7 @@ export default class PulsarGroup extends GroupInfo implements DataWithBackend {
   @I18n('Time To Live')
   ttl: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 72,
     rules: [
@@ -204,7 +210,7 @@ export default class PulsarGroup extends GroupInfo implements DataWithBackend {
   @I18n('Retention Time')
   retentionTime: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: -1,
     suffix: {
diff --git a/inlong-dashboard/src/metas/groups/defaults/TubeMq.ts b/inlong-dashboard/src/metas/groups/defaults/TubeMq.ts
index 796536374..a9a3ec41d 100644
--- a/inlong-dashboard/src/metas/groups/defaults/TubeMq.ts
+++ b/inlong-dashboard/src/metas/groups/defaults/TubeMq.ts
@@ -18,13 +18,19 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import { GroupInfo } from '../common/GroupInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class TubeMqGroup extends GroupInfo implements DataWithBackend {
-  @FormField({
+export default class TubeMqGroup
+  extends GroupInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     suffix: i18n.t('meta.Group.TubeMq.TenThousand/Day'),
@@ -36,7 +42,7 @@ export default class TubeMqGroup extends GroupInfo implements DataWithBackend {
   @I18n('meta.Group.TubeMq.NumberOfAccess')
   dailyRecords: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     suffix: i18n.t('meta.Group.TubeMq.GB/Day'),
@@ -48,7 +54,7 @@ export default class TubeMqGroup extends GroupInfo implements DataWithBackend {
   @I18n('meta.Group.TubeMq.AccessSize')
   dailyStorage: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     suffix: i18n.t('meta.Group.TubeMq.Stripe/Second'),
@@ -60,7 +66,7 @@ export default class TubeMqGroup extends GroupInfo implements DataWithBackend {
   @I18n('meta.Group.TubeMq.AccessPeakPerSecond')
   peakRecords: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     suffix: 'Byte',
diff --git a/inlong-dashboard/src/metas/groups/defaults/index.ts b/inlong-dashboard/src/metas/groups/defaults/index.ts
index 3877a4b78..e68379f4a 100644
--- a/inlong-dashboard/src/metas/groups/defaults/index.ts
+++ b/inlong-dashboard/src/metas/groups/defaults/index.ts
@@ -18,8 +18,9 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { GroupMetaType } from '../types';
 
-export const allDefaultGroups: MetaExportWithBackendList = [
+export const allDefaultGroups: MetaExportWithBackendList<GroupMetaType> = [
   {
     label: 'ALL',
     value: '',
diff --git a/inlong-dashboard/src/metas/groups/extends/index.ts b/inlong-dashboard/src/metas/groups/extends/index.ts
index abe3010b7..d4c701227 100644
--- a/inlong-dashboard/src/metas/groups/extends/index.ts
+++ b/inlong-dashboard/src/metas/groups/extends/index.ts
@@ -18,7 +18,8 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { GroupMetaType } from '../types';
 
-export const allExtendsGroups: MetaExportWithBackendList = [
+export const allExtendsGroups: MetaExportWithBackendList<GroupMetaType> = [
   // You can extends at here...
 ];
diff --git a/inlong-dashboard/src/metas/groups/index.ts b/inlong-dashboard/src/metas/groups/index.ts
index a794094eb..24c980961 100644
--- a/inlong-dashboard/src/metas/groups/index.ts
+++ b/inlong-dashboard/src/metas/groups/index.ts
@@ -20,6 +20,8 @@
 import { allDefaultGroups } from './defaults';
 import { allExtendsGroups } from './extends';
 
+export type { GroupMetaType } from './types';
+
 export const groups = allDefaultGroups.concat(allExtendsGroups);
 
 export const defaultValue = groups[0].value;
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/groups/types.ts
similarity index 83%
copy from inlong-dashboard/src/metas/sinks/extends/index.ts
copy to inlong-dashboard/src/metas/groups/types.ts
index 0fdb7d5b6..4f52a323c 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/groups/types.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import type { MetaExportWithBackendList } from '@/metas/types';
+import { GroupInfo } from './common/GroupInfo';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
-  // You can extends at here...
-];
+export type GroupMetaType = typeof GroupInfo;
diff --git a/inlong-dashboard/src/metas/index.ts b/inlong-dashboard/src/metas/index.ts
index 50fcf4015..4f2a1d833 100644
--- a/inlong-dashboard/src/metas/index.ts
+++ b/inlong-dashboard/src/metas/index.ts
@@ -18,7 +18,7 @@
  */
 
 import { useState, useEffect, useCallback } from 'react';
-import type { MetaExportWithBackend, MetaExportWithBackendList } from '@/metas/types';
+import type { MetaExportWithBackendList } from '@/metas/types';
 import { consumes, defaultValue as defaultConsume } from './consumes';
 import { groups, defaultValue as defaultGroup } from './groups';
 import { clusters, defaultValue as defaultCluster } from './clusters';
@@ -27,14 +27,24 @@ import { streams, defaultValue as defaultStream } from './streams';
 import { sources, defaultValue as defaultSource } from './sources';
 import { sinks, defaultValue as defaultSink } from './sinks';
 
-export interface UseLoadMetaResult {
+export type {
+  ClusterMetaType,
+  ConsumeMetaType,
+  GroupMetaType,
+  NodeMetaType,
+  SourceMetaType,
+  SinkMetaType,
+  StreamMetaType,
+} from './types';
+
+export interface UseLoadMetaResult<T> {
   loading: boolean;
-  Entity: MetaExportWithBackend;
+  Entity: T;
 }
 
 export type MetaTypeKeys = 'consume' | 'group' | 'cluster' | 'node' | 'stream' | 'source' | 'sink';
 
-const metasMap: Record<MetaTypeKeys, [MetaExportWithBackendList, string?]> = {
+const metasMap: Record<MetaTypeKeys, [MetaExportWithBackendList<any>, string?]> = {
   consume: [consumes, defaultConsume],
   group: [groups, defaultGroup],
   cluster: [clusters, defaultCluster],
@@ -52,9 +62,9 @@ export const useDefaultMeta = (metaType: MetaTypeKeys) => {
   };
 };
 
-export const useLoadMeta = (metaType: MetaTypeKeys, subType: string): UseLoadMetaResult => {
+export const useLoadMeta = <T>(metaType: MetaTypeKeys, subType: string): UseLoadMetaResult<T> => {
   const [loading, setLoading] = useState<boolean>(false);
-  const [Entity, setEntity] = useState<{ default: MetaExportWithBackend }>();
+  const [Entity, setEntity] = useState<{ default: T }>();
 
   const load = useCallback(
     async subType => {
diff --git a/inlong-dashboard/src/metas/nodes/common/NodeDefaultInfo.ts b/inlong-dashboard/src/metas/nodes/common/NodeDefaultInfo.ts
index 4024634cc..4aec5d6c5 100644
--- a/inlong-dashboard/src/metas/nodes/common/NodeDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/nodes/common/NodeDefaultInfo.ts
@@ -18,26 +18,34 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import UserSelect from '@/components/UserSelect';
 import { nodes, defaultValue } from '..';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
+
+export class NodeDefaultInfo implements DataWithBackend, RenderRow, RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
 
-export class NodeDefaultInfo extends DataWithBackend {
   readonly id: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: {
       maxLength: 128,
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Nodes.Name')
   name: string;
 
-  @FormField({
+  @FieldDecorator({
     type: nodes.length > 3 ? 'select' : 'radio',
     initialValue: defaultValue,
     rules: [{ required: true }],
@@ -50,11 +58,11 @@ export class NodeDefaultInfo extends DataWithBackend {
         })),
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Nodes.Type')
   type: string;
 
-  @FormField({
+  @FieldDecorator({
     type: UserSelect,
     rules: [{ required: true }],
     props: {
@@ -62,13 +70,13 @@ export class NodeDefaultInfo extends DataWithBackend {
       currentUserClosable: false,
     },
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Nodes.Owners')
   inCharges: string;
 
   clusterTags: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'textarea',
     props: {
       maxLength: 256,
@@ -86,4 +94,14 @@ export class NodeDefaultInfo extends DataWithBackend {
   stringify(data) {
     return data;
   }
+
+  renderRow() {
+    const constructor = this.constructor as typeof NodeDefaultInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof NodeDefaultInfo;
+    return constructor.ColumnList;
+  }
 }
diff --git a/inlong-dashboard/src/metas/nodes/defaults/Hive.ts b/inlong-dashboard/src/metas/nodes/defaults/Hive.ts
index 35dd66a57..bfe11b4c0 100644
--- a/inlong-dashboard/src/metas/nodes/defaults/Hive.ts
+++ b/inlong-dashboard/src/metas/nodes/defaults/Hive.ts
@@ -17,14 +17,17 @@
  * under the License.
  */
 
-import i18n from '@/i18n';
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
+import i18n from '@/i18n';
 import { NodeInfo } from '../common/NodeInfo';
 
-const { I18n, FormField } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
 
-export default class HiveNode extends NodeInfo implements DataWithBackend {
-  @FormField({
+export default class HiveNode extends NodeInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: 'jdbc:hive2://127.0.0.1:10000',
@@ -32,7 +35,7 @@ export default class HiveNode extends NodeInfo implements DataWithBackend {
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     tooltip: i18n.t('meta.Sinks.DataPathHelp'),
@@ -41,7 +44,7 @@ export default class HiveNode extends NodeInfo implements DataWithBackend {
   @I18n('meta.Sinks.Hive.DataPath')
   dataPath: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     tooltip: i18n.t('meta.Sinks.Hive.ConfDirHelp'),
diff --git a/inlong-dashboard/src/metas/nodes/defaults/index.ts b/inlong-dashboard/src/metas/nodes/defaults/index.ts
index 6af7c9114..9fab0a862 100644
--- a/inlong-dashboard/src/metas/nodes/defaults/index.ts
+++ b/inlong-dashboard/src/metas/nodes/defaults/index.ts
@@ -18,8 +18,9 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { NodeMetaType } from '../types';
 
-export const allDefaultNodes: MetaExportWithBackendList = [
+export const allDefaultNodes: MetaExportWithBackendList<NodeMetaType> = [
   {
     label: 'ALL',
     value: '',
diff --git a/inlong-dashboard/src/metas/nodes/extends/index.ts b/inlong-dashboard/src/metas/nodes/extends/index.ts
index 8824d752a..741949cca 100644
--- a/inlong-dashboard/src/metas/nodes/extends/index.ts
+++ b/inlong-dashboard/src/metas/nodes/extends/index.ts
@@ -18,7 +18,8 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { NodeMetaType } from '../types';
 
-export const allExtendsNodes: MetaExportWithBackendList = [
+export const allExtendsNodes: MetaExportWithBackendList<NodeMetaType> = [
   // You can extends at here...
 ];
diff --git a/inlong-dashboard/src/metas/nodes/index.ts b/inlong-dashboard/src/metas/nodes/index.ts
index b8ddbb30d..cc5f698c5 100644
--- a/inlong-dashboard/src/metas/nodes/index.ts
+++ b/inlong-dashboard/src/metas/nodes/index.ts
@@ -20,6 +20,8 @@
 import { allDefaultNodes } from './defaults';
 import { allExtendsNodes } from './extends';
 
+export type { NodeMetaType } from './types';
+
 export * as dao from './common/dao';
 
 export const nodes = allDefaultNodes.concat(allExtendsNodes);
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/nodes/types.ts
similarity index 83%
copy from inlong-dashboard/src/metas/sinks/extends/index.ts
copy to inlong-dashboard/src/metas/nodes/types.ts
index 0fdb7d5b6..ee3caaf90 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/nodes/types.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import type { MetaExportWithBackendList } from '@/metas/types';
+import { NodeInfo } from './common/NodeInfo';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
-  // You can extends at here...
-];
+export type NodeMetaType = typeof NodeInfo;
diff --git a/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts b/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts
index ed1955d5c..5fb4f5870 100644
--- a/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/sinks/common/SinkDefaultInfo.ts
@@ -18,16 +18,24 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import { statusList, genStatusTag } from './status';
 import { sinks, defaultValue } from '..';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
+
+export class SinkDefaultInfo implements DataWithBackend, RenderRow, RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
 
-export class SinkDefaultInfo extends DataWithBackend {
   readonly id: number;
 
-  @FormField({
+  @FieldDecorator({
     // This field is not visible or editable, but form value should exists.
     type: 'text',
     hidden: true,
@@ -35,7 +43,7 @@ export class SinkDefaultInfo extends DataWithBackend {
   @I18n('inlongGroupId')
   readonly inlongGroupId: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: values => ({
       disabled: Boolean(values.id),
@@ -60,11 +68,11 @@ export class SinkDefaultInfo extends DataWithBackend {
     }),
     rules: [{ required: true }],
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('pages.GroupDetail.Sink.DataStreams')
   inlongStreamId: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [
       { required: true },
@@ -78,11 +86,11 @@ export class SinkDefaultInfo extends DataWithBackend {
       maxLength: 128,
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.SinkName')
   sinkName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: sinks.length > 3 ? 'select' : 'radio',
     label: i18n.t('meta.Sinks.SinkType'),
     rules: [{ required: true }],
@@ -98,11 +106,11 @@ export class SinkDefaultInfo extends DataWithBackend {
         })),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.SinkType')
   sinkType: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'textarea',
     props: {
       showCount: true,
@@ -112,7 +120,7 @@ export class SinkDefaultInfo extends DataWithBackend {
   @I18n('meta.Sinks.Description')
   description: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: {
       allowClear: true,
@@ -121,7 +129,7 @@ export class SinkDefaultInfo extends DataWithBackend {
     },
     visible: false,
   })
-  @TableColumn({
+  @ColumnDecorator({
     render: text => genStatusTag(text),
   })
   @I18n('basic.Status')
@@ -134,4 +142,14 @@ export class SinkDefaultInfo extends DataWithBackend {
   stringify(data) {
     return data;
   }
+
+  renderRow() {
+    const constructor = this.constructor as typeof SinkDefaultInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof SinkDefaultInfo;
+    return constructor.ColumnList;
+  }
 }
diff --git a/inlong-dashboard/src/metas/sinks/common/sourceFields.ts b/inlong-dashboard/src/metas/sinks/common/sourceFields.ts
index 0d0267a9e..e8a1a716d 100644
--- a/inlong-dashboard/src/metas/sinks/common/sourceFields.ts
+++ b/inlong-dashboard/src/metas/sinks/common/sourceFields.ts
@@ -40,7 +40,7 @@ export const sourceFields: ColumnsItemProps[] = [
       },
     ],
     props: (text, record, idx, isNew) => ({
-      disabled: text && !isNew,
+      disabled: Boolean(record?.id),
     }),
   },
   {
@@ -50,7 +50,7 @@ export const sourceFields: ColumnsItemProps[] = [
     type: 'select',
     rules: [{ required: true }],
     props: (text, record, idx, isNew) => ({
-      disabled: text && !isNew,
+      disabled: Boolean(record?.id),
       options: fieldTypes,
     }),
   },
diff --git a/inlong-dashboard/src/metas/sinks/defaults/ClickHouse.ts b/inlong-dashboard/src/metas/sinks/defaults/ClickHouse.ts
index 095357879..52d503dc1 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/ClickHouse.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/ClickHouse.ts
@@ -18,12 +18,16 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { SinkInfo } from '../common/SinkInfo';
 import { sourceFields } from '../common/sourceFields';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const clickHouseTargetTypes = [
   'String',
@@ -40,30 +44,33 @@ const clickHouseTargetTypes = [
   value: item,
 }));
 
-export default class ClickHouseSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class ClickHouseSink
+  extends SinkInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ClickHouse.DbName')
   dbName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ClickHouse.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -85,7 +92,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -95,7 +102,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
@@ -105,7 +112,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -116,7 +123,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 1,
     props: values => ({
@@ -129,7 +136,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.FlushInterval')
   flushInterval: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 1000,
     props: values => ({
@@ -142,7 +149,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.FlushRecord')
   flushRecord: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 3,
     props: values => ({
@@ -155,7 +162,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.RetryTimes')
   retryTime: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 0,
     props: values => ({
@@ -176,7 +183,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.IsDistributed')
   isDistributed: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     initialValue: 'BALANCE',
     rules: [{ required: true }],
@@ -202,7 +209,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.PartitionStrategy')
   partitionStrategy: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     visible: values => values.isDistributed && values.partitionStrategy === 'HASH',
@@ -213,7 +220,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.PartitionFields')
   partitionFields: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     initialValue: 'Log',
     rules: [{ required: true }],
@@ -224,7 +231,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.Engine')
   engine: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: values => ({
       disabled: [110, 130].includes(values?.status),
@@ -233,7 +240,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.OrderBy')
   orderBy: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: values => ({
       disabled: [110, 130].includes(values?.status),
@@ -242,7 +249,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.PartitionBy')
   partitionBy: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: values => ({
       disabled: [110, 130].includes(values?.status),
@@ -251,7 +258,7 @@ export default class ClickHouseSink extends SinkInfo implements DataWithBackend
   @I18n('meta.Sinks.ClickHouse.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/Doris.ts b/inlong-dashboard/src/metas/sinks/defaults/Doris.ts
index 05b82e148..2ac77fb30 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/Doris.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/Doris.ts
@@ -18,12 +18,16 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { SinkInfo } from '../common/SinkInfo';
 import { sourceFields } from '../common/sourceFields';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const dorisTargetTypes = [
   'NULL_TYPE',
@@ -48,19 +52,19 @@ const dorisTargetTypes = [
   value: item,
 }));
 
-export default class DorisSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class DorisSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Doris.HttpAddress')
   feNodes: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -70,7 +74,7 @@ export default class DorisSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
@@ -80,40 +84,40 @@ export default class DorisSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Doris.TableIdentifier')
   tableIdentifier: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Doris.LabelPrefix')
   labelPrefix: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Doris.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: false,
@@ -134,40 +138,40 @@ export default class DorisSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Doris.SinkMultipleEnable')
   sinkMultipleEnable: boolean;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Doris.SinkMultipleFormat')
   sinkMultipleFormat: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Doris.DatabasePattern')
   databasePattern: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Doris.TablePattern')
   tablePattern: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/ES.ts b/inlong-dashboard/src/metas/sinks/defaults/ES.ts
index 3ff63d203..13ccfaaf3 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/ES.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/ES.ts
@@ -17,13 +17,17 @@
  * under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { sourceFields } from '../common/sourceFields';
 import { SinkInfo } from '../common/SinkInfo';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const esTypes = [
   'text',
@@ -43,19 +47,19 @@ const esTypes = [
   value: item,
 }));
 
-export default class EsSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class EsSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ES.IndexName')
   indexName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -77,40 +81,40 @@ export default class EsSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ES.Host')
   host: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 9200,
     rules: [{ required: true }],
@@ -120,11 +124,11 @@ export default class EsSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ES.Port')
   port: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 1,
     rules: [{ required: true }],
@@ -134,11 +138,11 @@ export default class EsSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ES.FlushInterval')
   flushInterval: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 1000,
     rules: [{ required: true }],
@@ -148,11 +152,11 @@ export default class EsSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ES.FlushRecord')
   flushRecord: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 3,
     rules: [{ required: true }],
@@ -162,11 +166,11 @@ export default class EsSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.ES.RetryTimes')
   retryTime: number;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/Greenplum.ts b/inlong-dashboard/src/metas/sinks/defaults/Greenplum.ts
index 6b3313cd2..73853895c 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/Greenplum.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/Greenplum.ts
@@ -15,13 +15,17 @@
  * limitations under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { sourceFields } from '../common/sourceFields';
 import { SinkInfo } from '../common/SinkInfo';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const fieldTypesConf = {
   SMALLINT: (m, d) => (1 <= m && m <= 6 ? '' : '1 <= M <= 6'),
@@ -58,8 +62,11 @@ const greenplumFieldTypes = Object.keys(fieldTypesConf).reduce(
   [],
 );
 
-export default class GreenplumSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class GreenplumSink
+  extends SinkInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -67,33 +74,33 @@ export default class GreenplumSink extends SinkInfo implements DataWithBackend {
       placeholder: 'jdbc:postgresql://127.0.0.1:5432/write',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Greenplum.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Greenplum.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -115,29 +122,29 @@ export default class GreenplumSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/HBase.ts b/inlong-dashboard/src/metas/sinks/defaults/HBase.ts
index b9ff6c231..c0c745d49 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/HBase.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/HBase.ts
@@ -15,13 +15,17 @@
  * limitations under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 import { SinkInfo } from '../common/SinkInfo';
 import { sourceFields } from '../common/sourceFields';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const hbaseFieldTypes = [
   'int',
@@ -38,41 +42,41 @@ const hbaseFieldTypes = [
   value: item,
 }));
 
-export default class HBaseSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class HBaseSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.Namespace')
   namespace: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.RowKey')
   rowKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -80,11 +84,11 @@ export default class HBaseSink extends SinkInfo implements DataWithBackend {
       placeholder: '127.0.0.1:2181,127.0.0.2:2181',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.ZkQuorum')
   zkQuorum: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: '/hbase',
@@ -92,11 +96,11 @@ export default class HBaseSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.ZkNodeParent')
   zkNodeParent: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     initialValue: 2,
@@ -105,11 +109,11 @@ export default class HBaseSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.BufferFlushMaxSize')
   bufferFlushMaxSize: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     initialValue: 1000,
@@ -118,11 +122,11 @@ export default class HBaseSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.BufferFlushMaxRows')
   bufferFlushMaxRows: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     rules: [{ required: true }],
     initialValue: 1,
@@ -132,11 +136,11 @@ export default class HBaseSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.HBase.BufferFlushInterval')
   bufferFlushInterval: number;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/Hive.ts b/inlong-dashboard/src/metas/sinks/defaults/Hive.ts
index 4d0dd9a49..71ef8b53d 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/Hive.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/Hive.ts
@@ -18,12 +18,16 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { SinkInfo } from '../common/SinkInfo';
 import { sourceFields } from '../common/sourceFields';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const hiveFieldTypes = [
   'string',
@@ -47,30 +51,30 @@ const hiveFieldTypes = [
   value: item,
 }));
 
-export default class HiveSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class HiveSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Hive.DbName')
   dbName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Hive.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -92,7 +96,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -102,7 +106,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
@@ -112,7 +116,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -123,7 +127,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     tooltip: i18n.t('meta.Sinks.DataPathHelp'),
@@ -135,7 +139,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Hive.DataPath')
   dataPath: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     tooltip: i18n.t('meta.Sinks.Hive.ConfDirHelp'),
@@ -147,7 +151,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Hive.ConfDir')
   hiveConfDir: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 'TextFile',
     rules: [{ required: true }],
@@ -184,7 +188,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Hive.FileFormat')
   fileFormat: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 'UTF-8',
     props: values => ({
@@ -205,7 +209,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Hive.DataEncoding')
   dataEncoding: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     initialValue: '124',
     props: values => ({
@@ -257,7 +261,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Hive.DataSeparator')
   dataSeparator: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
@@ -267,7 +271,7 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   })
   sinkFieldList: Record<string, unknown>[];
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     tooltip: i18n.t('meta.Sinks.Hive.PartitionFieldListHelp'),
     col: 24,
diff --git a/inlong-dashboard/src/metas/sinks/defaults/Iceberg.ts b/inlong-dashboard/src/metas/sinks/defaults/Iceberg.ts
index 7a944c705..d9df3e867 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/Iceberg.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/Iceberg.ts
@@ -17,13 +17,17 @@
  * under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { sourceFields } from '../common/sourceFields';
 import { SinkInfo } from '../common/SinkInfo';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const icebergFieldTypes = [
   'string',
@@ -101,30 +105,33 @@ const matchPartitionStrategies = fieldType => {
   return data.filter(item => !item.disabled);
 };
 
-export default class IcebergSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class IcebergSink
+  extends SinkInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Iceberg.DbName')
   dbName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Iceberg.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -146,7 +153,7 @@ export default class IcebergSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -154,11 +161,11 @@ export default class IcebergSink extends SinkInfo implements DataWithBackend {
       placeholder: 'thrift://127.0.0.1:9083',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('Catalog URI')
   catalogUri: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -166,11 +173,11 @@ export default class IcebergSink extends SinkInfo implements DataWithBackend {
       placeholder: 'hdfs://127.0.0.1:9000/user/iceberg/warehouse',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Iceberg.Warehouse')
   warehouse: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     rules: [{ required: true }],
     initialValue: 'Parquet',
@@ -192,11 +199,11 @@ export default class IcebergSink extends SinkInfo implements DataWithBackend {
       ],
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Iceberg.FileFormat')
   fileFormat: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     rules: [{ required: true }],
     initialValue: [],
@@ -220,11 +227,11 @@ export default class IcebergSink extends SinkInfo implements DataWithBackend {
       ],
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Iceberg.ExtList')
   extList: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     rules: [{ required: true }],
     initialValue: 'EXACTLY_ONCE',
@@ -243,11 +250,11 @@ export default class IcebergSink extends SinkInfo implements DataWithBackend {
       ],
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Iceberg.DataConsistency')
   dataConsistency: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/Kafka.ts b/inlong-dashboard/src/metas/sinks/defaults/Kafka.ts
index 2fe41b67c..6a9609bd8 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/Kafka.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/Kafka.ts
@@ -18,12 +18,16 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { SinkInfo } from '../common/SinkInfo';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
-export default class KafkaSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class KafkaSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: '127.0.0.1:9092',
@@ -31,22 +35,22 @@ export default class KafkaSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Kafka.Server')
   bootstrapServers: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('Topic')
   topicName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 'JSON',
     rules: [{ required: true }],
@@ -68,11 +72,11 @@ export default class KafkaSink extends SinkInfo implements DataWithBackend {
       ],
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Kafka.SerializationType')
   serializationType: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 3,
     props: values => ({
@@ -85,7 +89,7 @@ export default class KafkaSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Kafka.PartitionNum')
   partitionNum: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 'earliest',
     rules: [{ required: true }],
diff --git a/inlong-dashboard/src/metas/sinks/defaults/MySQL.ts b/inlong-dashboard/src/metas/sinks/defaults/MySQL.ts
index 898ade561..0960ed616 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/MySQL.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/MySQL.ts
@@ -15,13 +15,17 @@
  * limitations under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { sourceFields } from '../common/sourceFields';
 import { SinkInfo } from '../common/SinkInfo';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const fieldTypesConf = {
   TINYINT: (m, d) => (1 <= m && m <= 4 ? '' : '1<=M<=4'),
@@ -58,8 +62,8 @@ const fieldTypes = Object.keys(fieldTypesConf).reduce(
   [],
 );
 
-export default class HiveSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class HiveSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -67,33 +71,33 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
       placeholder: 'jdbc:mysql://127.0.0.1:3306/write',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.MySQL.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.MySQL.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -115,29 +119,29 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/Oracle.ts b/inlong-dashboard/src/metas/sinks/defaults/Oracle.ts
index 6837be3c2..a74edf241 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/Oracle.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/Oracle.ts
@@ -15,13 +15,17 @@
  * limitations under the License.
  */
 
-import i18n from '@/i18n';
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
+import i18n from '@/i18n';
 import { SinkInfo } from '../common/SinkInfo';
 import { sourceFields } from '../common/sourceFields';
 import EditableTable from '@/components/EditableTable';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const fieldTypesConf = {
   BINARY_FLOAT: (m, d) => (1 <= m && m <= 6 ? '' : '1 <= M <= 6'),
@@ -54,8 +58,8 @@ const oracleFieldTypes = Object.keys(fieldTypesConf).reduce(
   [],
 );
 
-export default class OracleSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class OracleSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -63,33 +67,33 @@ export default class OracleSink extends SinkInfo implements DataWithBackend {
       placeholder: 'jdbc:oracle:thin://127.0.0.1:1521/db_name',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Oracle.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Oracle.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -111,29 +115,29 @@ export default class OracleSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/PostgreSQL.ts b/inlong-dashboard/src/metas/sinks/defaults/PostgreSQL.ts
index 59e68d4f5..cb205c68c 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/PostgreSQL.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/PostgreSQL.ts
@@ -15,13 +15,17 @@
  * limitations under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 import { SinkInfo } from '../common/SinkInfo';
 import { sourceFields } from '../common/sourceFields';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const fieldTypesConf = {
   SMALLINT: (m, d) => (1 <= m && m <= 6 ? '' : '1 <= M <= 6'),
@@ -58,8 +62,8 @@ const postgreSqlFieldTypes = Object.keys(fieldTypesConf).reduce(
   [],
 );
 
-export default class HiveSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class HiveSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList {
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -67,44 +71,44 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
       placeholder: 'jdbc:postgresql://127.0.0.1:5432/db_name',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.PostgreSQL.DbName')
   dbName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.PostgreSQL.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.PostgreSQL.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -126,29 +130,29 @@ export default class HiveSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/SQLServer.ts b/inlong-dashboard/src/metas/sinks/defaults/SQLServer.ts
index 52b46b3fd..f3413911c 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/SQLServer.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/SQLServer.ts
@@ -15,13 +15,17 @@
  * limitations under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { SinkInfo } from '../common/SinkInfo';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 import { sourceFields } from '../common/sourceFields';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const fieldTypesConf = {
   CHAR: (m, d) => (1 <= m && m <= 8000 ? '' : '1 <= M <= 8000'),
@@ -59,8 +63,11 @@ const sqlserverFieldTypes = Object.keys(fieldTypesConf).reduce(
   [],
 );
 
-export default class SqlServerSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class SqlServerSink
+  extends SinkInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -68,22 +75,22 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
       placeholder: 'jdbc:sqlserver://127.0.0.1:1433;database=db_name',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.SQLServer.SchemaName')
   schemaName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: 'UTC',
@@ -91,11 +98,11 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.SQLServer.ServerTimezone')
   serverTimezone: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: 'UTC',
@@ -103,11 +110,11 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.SQLServer.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: 'UTC',
@@ -115,11 +122,11 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.SQLServer.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -141,7 +148,7 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: true,
@@ -162,7 +169,7 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.AllMigration')
   allMigration: boolean;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -172,7 +179,7 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
@@ -182,7 +189,7 @@ export default class SqlServerSink extends SinkInfo implements DataWithBackend {
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/TDSQLPostgreSQL.ts b/inlong-dashboard/src/metas/sinks/defaults/TDSQLPostgreSQL.ts
index 26e7cc9fa..79c88b3e1 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/TDSQLPostgreSQL.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/TDSQLPostgreSQL.ts
@@ -15,13 +15,17 @@
  * limitations under the License.
  */
 
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
-import { DataWithBackend } from '@/metas/DataWithBackend';
 import { sourceFields } from '../common/sourceFields';
 import { SinkInfo } from '../common/SinkInfo';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
 const tdsqlPostgreSQLFieldTypes = [
   'SMALLINT',
@@ -52,8 +56,11 @@ const tdsqlPostgreSQLFieldTypes = [
   value: item,
 }));
 
-export default class TDSQLPostgreSQLSink extends SinkInfo implements DataWithBackend {
-  @FormField({
+export default class TDSQLPostgreSQLSink
+  extends SinkInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -61,44 +68,44 @@ export default class TDSQLPostgreSQLSink extends SinkInfo implements DataWithBac
       placeholder: 'jdbc:sqlserver://127.0.0.1:1433;database=db_name',
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('JDBC URL')
   jdbcUrl: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.TDSQLPostgreSQL.SchemaName')
   schemaName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.TDSQLPostgreSQL.TableName')
   tableName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: [110, 130].includes(values?.status),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sinks.TDSQLPostgreSQL.PrimaryKey')
   primaryKey: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: 1,
@@ -120,7 +127,7 @@ export default class TDSQLPostgreSQLSink extends SinkInfo implements DataWithBac
   @I18n('meta.Sinks.EnableCreateResource')
   enableCreateResource: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -130,7 +137,7 @@ export default class TDSQLPostgreSQLSink extends SinkInfo implements DataWithBac
   @I18n('meta.Sinks.Username')
   username: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
@@ -140,7 +147,7 @@ export default class TDSQLPostgreSQLSink extends SinkInfo implements DataWithBac
   @I18n('meta.Sinks.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: values => ({
       size: 'small',
diff --git a/inlong-dashboard/src/metas/sinks/defaults/index.ts b/inlong-dashboard/src/metas/sinks/defaults/index.ts
index 9c4e13378..536c114ab 100644
--- a/inlong-dashboard/src/metas/sinks/defaults/index.ts
+++ b/inlong-dashboard/src/metas/sinks/defaults/index.ts
@@ -18,8 +18,9 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { SinkMetaType } from '../types';
 
-export const allDefaultSinks: MetaExportWithBackendList = [
+export const allDefaultSinks: MetaExportWithBackendList<SinkMetaType> = [
   {
     label: 'ALL',
     value: '',
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/sinks/extends/index.ts
index 0fdb7d5b6..ec5ce74bb 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/sinks/extends/index.ts
@@ -18,7 +18,8 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { SinkMetaType } from '../types';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
+export const allExtendsSinks: MetaExportWithBackendList<SinkMetaType> = [
   // You can extends at here...
 ];
diff --git a/inlong-dashboard/src/metas/sinks/index.ts b/inlong-dashboard/src/metas/sinks/index.ts
index 760e38423..f223a53d5 100644
--- a/inlong-dashboard/src/metas/sinks/index.ts
+++ b/inlong-dashboard/src/metas/sinks/index.ts
@@ -20,6 +20,8 @@
 import { allDefaultSinks } from './defaults';
 import { allExtendsSinks } from './extends';
 
+export type { SinkMetaType } from './types';
+
 export const sinks = allDefaultSinks.concat(allExtendsSinks);
 
 export const defaultValue = sinks[0].value;
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/sinks/types.ts
similarity index 83%
copy from inlong-dashboard/src/metas/sinks/extends/index.ts
copy to inlong-dashboard/src/metas/sinks/types.ts
index 0fdb7d5b6..a9cfdf81b 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/sinks/types.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import type { MetaExportWithBackendList } from '@/metas/types';
+import { SinkInfo } from './common/SinkInfo';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
-  // You can extends at here...
-];
+export type SinkMetaType = typeof SinkInfo;
diff --git a/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts b/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts
index c39ce9d4a..301512ec9 100644
--- a/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/sources/common/SourceDefaultInfo.ts
@@ -18,15 +18,23 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { statusList, genStatusTag } from './status';
 import { sources, defaultValue } from '..';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
+
+export class SourceDefaultInfo implements DataWithBackend, RenderRow, RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
 
-export class SourceDefaultInfo extends DataWithBackend {
   readonly id: number;
 
-  @FormField({
+  @FieldDecorator({
     // This field is not visible or editable, but form value should exists.
     type: 'text',
     hidden: true,
@@ -34,7 +42,7 @@ export class SourceDefaultInfo extends DataWithBackend {
   @I18n('inlongGroupId')
   readonly inlongGroupId: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: values => ({
       disabled: Boolean(values.id),
@@ -59,11 +67,11 @@ export class SourceDefaultInfo extends DataWithBackend {
     }),
     rules: [{ required: true }],
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('pages.GroupDetail.Sources.DataStreams')
   inlongStreamId: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -71,11 +79,11 @@ export class SourceDefaultInfo extends DataWithBackend {
       maxLength: 128,
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sources.Name')
   sourceName: string;
 
-  @FormField({
+  @FieldDecorator({
     type: sources.length > 3 ? 'select' : 'radio',
     rules: [{ required: true }],
     initialValue: defaultValue,
@@ -89,11 +97,11 @@ export class SourceDefaultInfo extends DataWithBackend {
         })),
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sources.Type')
   sourceType: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: {
       allowClear: true,
@@ -102,7 +110,7 @@ export class SourceDefaultInfo extends DataWithBackend {
     },
     visible: false,
   })
-  @TableColumn({
+  @ColumnDecorator({
     render: text => genStatusTag(text),
   })
   @I18n('basic.Status')
@@ -115,4 +123,14 @@ export class SourceDefaultInfo extends DataWithBackend {
   stringify(data) {
     return data;
   }
+
+  renderRow() {
+    const constructor = this.constructor as typeof SourceDefaultInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof SourceDefaultInfo;
+    return constructor.ColumnList;
+  }
 }
diff --git a/inlong-dashboard/src/metas/sources/defaults/AutoPush.ts b/inlong-dashboard/src/metas/sources/defaults/AutoPush.ts
index b1bc5dc98..51c28ae63 100644
--- a/inlong-dashboard/src/metas/sources/defaults/AutoPush.ts
+++ b/inlong-dashboard/src/metas/sources/defaults/AutoPush.ts
@@ -18,6 +18,10 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import { SourceInfo } from '../common/SourceInfo';
 
-export default class AutoPushSource extends SourceInfo implements DataWithBackend {}
+export default class AutoPushSource
+  extends SourceInfo
+  implements DataWithBackend, RenderRow, RenderList {}
diff --git a/inlong-dashboard/src/metas/sources/defaults/File.ts b/inlong-dashboard/src/metas/sources/defaults/File.ts
index 1e2b9e115..212e562a4 100644
--- a/inlong-dashboard/src/metas/sources/defaults/File.ts
+++ b/inlong-dashboard/src/metas/sources/defaults/File.ts
@@ -18,14 +18,21 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import rulesPattern from '@/utils/pattern';
 import { SourceInfo } from '../common/SourceInfo';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
-export default class PulsarSource extends SourceInfo implements DataWithBackend {
-  @FormField({
+export default class PulsarSource
+  extends SourceInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [
       {
@@ -38,11 +45,11 @@ export default class PulsarSource extends SourceInfo implements DataWithBackend
       disabled: values?.status === 101,
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sources.File.DataSourceIP')
   agentIp: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     tooltip: i18n.t('meta.Sources.File.FilePathHelp'),
     rules: [{ required: true }],
@@ -50,11 +57,11 @@ export default class PulsarSource extends SourceInfo implements DataWithBackend
       disabled: values?.status === 101,
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sources.File.FilePath')
   pattern: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     tooltip: i18n.t('meta.Sources.File.TimeOffsetHelp'),
     props: values => ({
diff --git a/inlong-dashboard/src/metas/sources/defaults/MySQLBinlog.ts b/inlong-dashboard/src/metas/sources/defaults/MySQLBinlog.ts
index 63d34e074..ba844359e 100644
--- a/inlong-dashboard/src/metas/sources/defaults/MySQLBinlog.ts
+++ b/inlong-dashboard/src/metas/sources/defaults/MySQLBinlog.ts
@@ -18,24 +18,31 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import { SourceInfo } from '../common/SourceInfo';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+const { ColumnDecorator } = RenderList;
 
-export default class TubeMqSource extends SourceInfo implements DataWithBackend {
-  @FormField({
+export default class TubeMqSource
+  extends SourceInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
       disabled: values?.status === 101,
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sources.Db.Server')
   hostname: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 3306,
     rules: [{ required: true }],
@@ -45,11 +52,11 @@ export default class TubeMqSource extends SourceInfo implements DataWithBackend
       max: 65535,
     }),
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Sources.Db.Port')
   port: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     props: values => ({
@@ -59,7 +66,7 @@ export default class TubeMqSource extends SourceInfo implements DataWithBackend
   @I18n('meta.Sources.Db.User')
   user: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'password',
     rules: [{ required: true }],
     props: values => ({
@@ -69,7 +76,7 @@ export default class TubeMqSource extends SourceInfo implements DataWithBackend
   @I18n('meta.Sources.Db.Password')
   password: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     rules: [{ required: true }],
     initialValue: '/data/inlong-agent/.history',
@@ -80,7 +87,7 @@ export default class TubeMqSource extends SourceInfo implements DataWithBackend
   @I18n('meta.Sources.Db.HistoryFilename')
   historyFilename: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     tooltip: 'UTC, UTC+8, Asia/Shanghai, ...',
     initialValue: 'UTC',
@@ -92,7 +99,7 @@ export default class TubeMqSource extends SourceInfo implements DataWithBackend
   @I18n('meta.Sources.Db.ServerTimezone')
   serverTimezone: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'inputnumber',
     initialValue: 1000,
     rules: [{ required: true }],
@@ -106,7 +113,7 @@ export default class TubeMqSource extends SourceInfo implements DataWithBackend
   @I18n('meta.Sources.Db.IntervalMs')
   intervalMs: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     rules: [{ required: true }],
     initialValue: false,
@@ -127,7 +134,7 @@ export default class TubeMqSource extends SourceInfo implements DataWithBackend
   @I18n('meta.Sources.Db.AllMigration')
   allMigration: boolean;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     tooltip: i18n.t('meta.Sources.Db.TableWhiteListHelp'),
     rules: [{ required: true }],
diff --git a/inlong-dashboard/src/metas/sources/defaults/index.ts b/inlong-dashboard/src/metas/sources/defaults/index.ts
index 735b7f604..9e1720de2 100644
--- a/inlong-dashboard/src/metas/sources/defaults/index.ts
+++ b/inlong-dashboard/src/metas/sources/defaults/index.ts
@@ -18,8 +18,9 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { SourceMetaType } from '../types';
 
-export const allDefaultSources: MetaExportWithBackendList = [
+export const allDefaultSources: MetaExportWithBackendList<SourceMetaType> = [
   {
     label: 'ALL',
     value: '',
diff --git a/inlong-dashboard/src/metas/sources/extends/index.ts b/inlong-dashboard/src/metas/sources/extends/index.ts
index 38e4eb2d9..5b63ac6ac 100644
--- a/inlong-dashboard/src/metas/sources/extends/index.ts
+++ b/inlong-dashboard/src/metas/sources/extends/index.ts
@@ -18,7 +18,8 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { SourceMetaType } from '../types';
 
-export const allExtendsSources: MetaExportWithBackendList = [
+export const allExtendsSources: MetaExportWithBackendList<SourceMetaType> = [
   // You can extends at here...
 ];
diff --git a/inlong-dashboard/src/metas/sources/index.ts b/inlong-dashboard/src/metas/sources/index.ts
index 58928e352..cf58ba2c9 100644
--- a/inlong-dashboard/src/metas/sources/index.ts
+++ b/inlong-dashboard/src/metas/sources/index.ts
@@ -20,6 +20,8 @@
 import { allDefaultSources } from './defaults';
 import { allExtendsSources } from './extends';
 
+export type { SourceMetaType } from './types';
+
 export const sources = allDefaultSources.concat(allExtendsSources);
 
 export const defaultValue = sources[0].value;
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/sources/types.ts
similarity index 83%
copy from inlong-dashboard/src/metas/sinks/extends/index.ts
copy to inlong-dashboard/src/metas/sources/types.ts
index 0fdb7d5b6..24ad3a416 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/sources/types.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import type { MetaExportWithBackendList } from '@/metas/types';
+import { SourceInfo } from './common/SourceInfo';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
-  // You can extends at here...
-];
+export type SourceMetaType = typeof SourceInfo;
diff --git a/inlong-dashboard/src/metas/streams/common/StreamDefaultInfo.ts b/inlong-dashboard/src/metas/streams/common/StreamDefaultInfo.ts
index 35e639d56..773a51b19 100644
--- a/inlong-dashboard/src/metas/streams/common/StreamDefaultInfo.ts
+++ b/inlong-dashboard/src/metas/streams/common/StreamDefaultInfo.ts
@@ -18,17 +18,25 @@
  */
 
 import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
 import i18n from '@/i18n';
 import EditableTable from '@/components/EditableTable';
 import { fieldTypes as sourceFieldsTypes } from '@/metas/sinks/common/sourceFields';
 import { statusList, genStatusTag } from './status';
 
-const { I18n, FormField, TableColumn } = DataWithBackend;
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
+
+export class StreamDefaultInfo implements DataWithBackend, RenderRow, RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
 
-export class StreamDefaultInfo extends DataWithBackend {
   readonly id: number;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
     props: {
       maxLength: 32,
@@ -41,18 +49,18 @@ export class StreamDefaultInfo extends DataWithBackend {
       },
     ],
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Stream.StreamId')
   inlongStreamId: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'input',
   })
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('meta.Stream.StreamName')
   name: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'textarea',
     props: {
       showCount: true,
@@ -62,15 +70,15 @@ export class StreamDefaultInfo extends DataWithBackend {
   @I18n('meta.Stream.Description')
   description: string;
 
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('basic.Creator')
   readonly creator: string;
 
-  @TableColumn()
+  @ColumnDecorator()
   @I18n('basic.CreateTime')
   readonly createTime: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     props: {
       allowClear: true,
@@ -79,13 +87,13 @@ export class StreamDefaultInfo extends DataWithBackend {
     },
     visible: false,
   })
-  @TableColumn({
+  @ColumnDecorator({
     render: text => genStatusTag(text),
   })
   @I18n('basic.Status')
   status: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 'CSV',
     tooltip: i18n.t('meta.Stream.DataTypeHelp'),
@@ -106,7 +114,7 @@ export class StreamDefaultInfo extends DataWithBackend {
   @I18n('meta.Stream.DataType')
   dataType: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'radio',
     initialValue: 'UTF-8',
     props: {
@@ -126,7 +134,7 @@ export class StreamDefaultInfo extends DataWithBackend {
   @I18n('meta.Stream.DataEncoding')
   dataEncoding: string;
 
-  @FormField({
+  @FieldDecorator({
     type: 'select',
     initialValue: '124',
     props: {
@@ -176,7 +184,7 @@ export class StreamDefaultInfo extends DataWithBackend {
   @I18n('meta.Stream.DataSeparator')
   dataSeparator: string;
 
-  @FormField({
+  @FieldDecorator({
     type: EditableTable,
     props: {
       size: 'small',
@@ -219,4 +227,14 @@ export class StreamDefaultInfo extends DataWithBackend {
   stringify(data) {
     return data;
   }
+
+  renderRow() {
+    const constructor = this.constructor as typeof StreamDefaultInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof StreamDefaultInfo;
+    return constructor.ColumnList;
+  }
 }
diff --git a/inlong-dashboard/src/metas/streams/defaults/index.ts b/inlong-dashboard/src/metas/streams/defaults/index.ts
index 7d92d260d..a4181399c 100644
--- a/inlong-dashboard/src/metas/streams/defaults/index.ts
+++ b/inlong-dashboard/src/metas/streams/defaults/index.ts
@@ -18,8 +18,9 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { StreamMetaType } from '../types';
 
-export const allDefaultStreams: MetaExportWithBackendList = [
+export const allDefaultStreams: MetaExportWithBackendList<StreamMetaType> = [
   {
     label: 'ALL',
     value: '',
diff --git a/inlong-dashboard/src/metas/streams/extends/index.ts b/inlong-dashboard/src/metas/streams/extends/index.ts
index 309324157..e6f51b573 100644
--- a/inlong-dashboard/src/metas/streams/extends/index.ts
+++ b/inlong-dashboard/src/metas/streams/extends/index.ts
@@ -18,7 +18,8 @@
  */
 
 import type { MetaExportWithBackendList } from '@/metas/types';
+import type { StreamMetaType } from '../types';
 
-export const allExtendsStreams: MetaExportWithBackendList = [
+export const allExtendsStreams: MetaExportWithBackendList<StreamMetaType> = [
   // You can extends at here...
 ];
diff --git a/inlong-dashboard/src/metas/streams/index.ts b/inlong-dashboard/src/metas/streams/index.ts
index 2b7f10519..42b6503da 100644
--- a/inlong-dashboard/src/metas/streams/index.ts
+++ b/inlong-dashboard/src/metas/streams/index.ts
@@ -20,6 +20,8 @@
 import { allDefaultStreams } from './defaults';
 import { allExtendsStreams } from './extends';
 
+export type { StreamMetaType } from './types';
+
 export const streams = allDefaultStreams.concat(allExtendsStreams);
 
 export const defaultValue = streams[0].value;
diff --git a/inlong-dashboard/src/metas/sinks/extends/index.ts b/inlong-dashboard/src/metas/streams/types.ts
similarity index 83%
copy from inlong-dashboard/src/metas/sinks/extends/index.ts
copy to inlong-dashboard/src/metas/streams/types.ts
index 0fdb7d5b6..3793823f5 100644
--- a/inlong-dashboard/src/metas/sinks/extends/index.ts
+++ b/inlong-dashboard/src/metas/streams/types.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import type { MetaExportWithBackendList } from '@/metas/types';
+import { StreamInfo } from './common/StreamInfo';
 
-export const allExtendsSinks: MetaExportWithBackendList = [
-  // You can extends at here...
-];
+export type StreamMetaType = typeof StreamInfo;
diff --git a/inlong-dashboard/src/metas/types.ts b/inlong-dashboard/src/metas/types.ts
index 00c8c1548..6c249cbb0 100644
--- a/inlong-dashboard/src/metas/types.ts
+++ b/inlong-dashboard/src/metas/types.ts
@@ -17,32 +17,22 @@
  * under the License.
  */
 
-import { DataStatic } from './DataStatic';
-import { DataWithBackend } from './DataWithBackend';
-
-class MetaClassStatic implements DataStatic {}
-
-export type MetaExportStatic = typeof MetaClassStatic;
-
-export type MetaExportStaticList = {
+export type MetaExportStaticList<T> = {
   label: string;
   value: string;
-  LoadEntity: () => Promise<{ default: MetaExportStatic }>;
+  LoadEntity: () => Promise<{ default: T }>;
 }[];
 
-class MetaClassWithBackend extends DataWithBackend implements DataWithBackend {
-  parse(data) {
-    return data;
-  }
-  stringify(data) {
-    return data;
-  }
-}
-
-export type MetaExportWithBackend = typeof MetaClassWithBackend;
-
-export type MetaExportWithBackendList = {
+export type MetaExportWithBackendList<T> = {
   label: string;
   value: string;
-  LoadEntity: () => Promise<{ default: MetaExportWithBackend }>;
+  LoadEntity: () => Promise<{ default: T }>;
 }[];
+
+export type { ClusterMetaType } from './clusters';
+export type { ConsumeMetaType } from './consumes';
+export type { GroupMetaType } from './groups';
+export type { NodeMetaType } from './nodes';
+export type { SourceMetaType } from './sources';
+export type { SinkMetaType } from './sinks';
+export type { StreamMetaType } from './streams';
diff --git a/inlong-dashboard/src/pages/Clusters/CreateModal.tsx b/inlong-dashboard/src/pages/Clusters/CreateModal.tsx
index 3618f1914..7d692fbd5 100644
--- a/inlong-dashboard/src/pages/Clusters/CreateModal.tsx
+++ b/inlong-dashboard/src/pages/Clusters/CreateModal.tsx
@@ -23,7 +23,7 @@ import { ModalProps } from 'antd/es/modal';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
 import { useRequest, useUpdateEffect } from '@/hooks';
 import request from '@/utils/request';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, ClusterMetaType } from '@/metas';
 import i18n from '@/i18n';
 
 export interface Props extends ModalProps {
@@ -88,10 +88,10 @@ const Comp: React.FC<Props> = ({ id, ...modalProps }) => {
     }
   }, [modalProps.visible]);
 
-  const { Entity } = useLoadMeta('cluster', type);
+  const { Entity } = useLoadMeta<ClusterMetaType>('cluster', type);
 
   const content = useMemo(() => {
-    return Entity ? Entity.FieldList : [];
+    return Entity ? new Entity().renderRow() : [];
   }, [Entity]);
 
   return (
diff --git a/inlong-dashboard/src/pages/Clusters/index.tsx b/inlong-dashboard/src/pages/Clusters/index.tsx
index 0ef46ce1d..42829341f 100644
--- a/inlong-dashboard/src/pages/Clusters/index.tsx
+++ b/inlong-dashboard/src/pages/Clusters/index.tsx
@@ -25,7 +25,7 @@ import HighTable from '@/components/HighTable';
 import { PageContainer } from '@/components/PageContainer';
 import { defaultSize } from '@/configs/pagination';
 import { useRequest } from '@/hooks';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, ClusterMetaType } from '@/metas';
 import CreateModal from './CreateModal';
 import request from '@/utils/request';
 import { timestampFormat } from '@/utils';
@@ -124,12 +124,14 @@ const Comp: React.FC = () => {
     [clusters],
   );
 
-  const { Entity } = useLoadMeta('cluster', options.type);
+  const { Entity } = useLoadMeta<ClusterMetaType>('cluster', options.type);
 
-  const columns = useMemo(() => {
-    if (!Entity) return [];
+  const entityColumns = useMemo(() => {
+    return Entity ? new Entity().renderList() : [];
+  }, [Entity]);
 
-    return Entity.ColumnList?.concat([
+  const columns = useMemo(() => {
+    return entityColumns?.concat([
       {
         title: i18n.t('pages.Clusters.LastModifier'),
         dataIndex: 'modifier',
@@ -162,7 +164,7 @@ const Comp: React.FC = () => {
         ),
       } as any,
     ]);
-  }, [Entity, onDelete]);
+  }, [entityColumns, onDelete]);
 
   return (
     <PageContainer useDefaultBreadcrumb={false}>
diff --git a/inlong-dashboard/src/pages/ConsumeDashboard/config.tsx b/inlong-dashboard/src/pages/ConsumeDashboard/config.tsx
index 486a31d38..11b650459 100644
--- a/inlong-dashboard/src/pages/ConsumeDashboard/config.tsx
+++ b/inlong-dashboard/src/pages/ConsumeDashboard/config.tsx
@@ -17,12 +17,12 @@
  * under the License.
  */
 
-import React from 'react';
+import React, { useMemo } from 'react';
 import { Button } from 'antd';
 import { Link } from 'react-router-dom';
 import i18n from '@/i18n';
 import { DashTotal, DashToBeAssigned, DashPending, DashRejected } from '@/components/Icons';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, ConsumeMetaType } from '@/metas';
 import { statusList, lastConsumerStatusList } from '@/metas/consumes/common/status';
 
 export const dashCardList = [
@@ -84,36 +84,42 @@ export const getFilterFormContent = defaultValues => [
 export const useColumns = ({ onDelete }) => {
   const { defaultValue } = useDefaultMeta('consume');
 
-  const { Entity } = useLoadMeta('consume', defaultValue);
+  const { Entity } = useLoadMeta<ConsumeMetaType>('consume', defaultValue);
+
+  const entityColumns = useMemo(() => {
+    return Entity ? new Entity().renderList() : [];
+  }, [Entity]);
 
   const genCreateUrl = record => `/consume/create/${record.id}`;
   const genDetailUrl = record =>
     [0, 10].includes(record.status) ? genCreateUrl(record) : `/consume/detail/${record.id}`;
 
-  return Entity?.ColumnList?.map(item => {
-    if (item.dataIndex === 'consumerGroup') {
-      return { ...item, render: (text, record) => <Link to={genDetailUrl(record)}>{text}</Link> };
-    }
-    return item;
-  }).concat([
-    {
-      title: i18n.t('basic.Operating'),
-      dataIndex: 'action',
-      render: (text, record) => (
-        <>
-          <Button type="link">
-            <Link to={genDetailUrl(record)}>{i18n.t('basic.Detail')}</Link>
-          </Button>
-          {[20, 22].includes(record?.status) && (
+  return entityColumns
+    ?.map(item => {
+      if (item.dataIndex === 'consumerGroup') {
+        return { ...item, render: (text, record) => <Link to={genDetailUrl(record)}>{text}</Link> };
+      }
+      return item;
+    })
+    .concat([
+      {
+        title: i18n.t('basic.Operating'),
+        dataIndex: 'action',
+        render: (text, record) => (
+          <>
             <Button type="link">
-              <Link to={genCreateUrl(record)}>{i18n.t('basic.Edit')}</Link>
+              <Link to={genDetailUrl(record)}>{i18n.t('basic.Detail')}</Link>
             </Button>
-          )}
-          <Button type="link" onClick={() => onDelete(record)}>
-            {i18n.t('basic.Delete')}
-          </Button>
-        </>
-      ),
-    },
-  ]);
+            {[20, 22].includes(record?.status) && (
+              <Button type="link">
+                <Link to={genCreateUrl(record)}>{i18n.t('basic.Edit')}</Link>
+              </Button>
+            )}
+            <Button type="link" onClick={() => onDelete(record)}>
+              {i18n.t('basic.Delete')}
+            </Button>
+          </>
+        ),
+      },
+    ]);
 };
diff --git a/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx b/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx
index c9feafaea..858689352 100644
--- a/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx
+++ b/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx
@@ -17,14 +17,19 @@
  * under the License.
  */
 
-import { useLoadMeta } from '@/metas';
+import { useMemo } from 'react';
+import { useLoadMeta, ConsumeMetaType } from '@/metas';
 import { excludeObjectArray } from '@/utils';
 
 export const useFormContent = ({ mqType, editing, isCreate }) => {
-  const { Entity } = useLoadMeta('consume', mqType);
+  const { Entity } = useLoadMeta<ConsumeMetaType>('consume', mqType);
+
+  const entityFields = useMemo(() => {
+    return Entity ? new Entity().renderRow() : [];
+  }, [Entity]);
 
   const excludeKeys = isCreate ? ['masterUrl'] : [];
-  const fields = excludeObjectArray(excludeKeys, Entity?.FieldList || []);
+  const fields = excludeObjectArray(excludeKeys, entityFields);
 
   return isCreate
     ? fields
diff --git a/inlong-dashboard/src/pages/GroupDashboard/config.tsx b/inlong-dashboard/src/pages/GroupDashboard/config.tsx
index 9707d6ea7..53d14c559 100644
--- a/inlong-dashboard/src/pages/GroupDashboard/config.tsx
+++ b/inlong-dashboard/src/pages/GroupDashboard/config.tsx
@@ -17,12 +17,12 @@
  * under the License.
  */
 
-import React from 'react';
+import React, { useMemo } from 'react';
 import { Link } from 'react-router-dom';
 import i18n from '@/i18n';
 import { DashTotal, DashToBeAssigned, DashPending, DashRejected } from '@/components/Icons';
 import { Button } from 'antd';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, GroupMetaType } from '@/metas';
 import { statusList } from '@/metas/groups/common/status';
 
 export const dashCardList = [
@@ -73,7 +73,11 @@ export const getFilterFormContent = defaultValues => [
 export const useColumns = ({ onDelete, openModal }) => {
   const { defaultValue } = useDefaultMeta('group');
 
-  const { Entity } = useLoadMeta('group', defaultValue);
+  const { Entity } = useLoadMeta<GroupMetaType>('group', defaultValue);
+
+  const entityColumns = useMemo(() => {
+    return Entity ? new Entity().renderList() : [];
+  }, [Entity]);
 
   const genCreateUrl = record => `/group/create/${record.inlongGroupId}`;
   const genDetailUrl = record =>
@@ -81,35 +85,37 @@ export const useColumns = ({ onDelete, openModal }) => {
       ? genCreateUrl(record)
       : `/group/detail/${record.inlongGroupId}`;
 
-  return Entity?.ColumnList?.map(item => {
-    if (item.dataIndex === 'inlongGroupId') {
-      return { ...item, render: (text, record) => <Link to={genDetailUrl(record)}>{text}</Link> };
-    }
-    return item;
-  }).concat([
-    {
-      title: i18n.t('basic.Operating'),
-      dataIndex: 'action',
-      render: (text, record) => (
-        <>
-          <Button type="link">
-            <Link to={genDetailUrl(record)}>{i18n.t('basic.Detail')}</Link>
-          </Button>
-          {[102].includes(record?.status) && (
+  return entityColumns
+    ?.map(item => {
+      if (item.dataIndex === 'inlongGroupId') {
+        return { ...item, render: (text, record) => <Link to={genDetailUrl(record)}>{text}</Link> };
+      }
+      return item;
+    })
+    .concat([
+      {
+        title: i18n.t('basic.Operating'),
+        dataIndex: 'action',
+        render: (text, record) => (
+          <>
             <Button type="link">
-              <Link to={genCreateUrl(record)}>{i18n.t('basic.Edit')}</Link>
+              <Link to={genDetailUrl(record)}>{i18n.t('basic.Detail')}</Link>
             </Button>
-          )}
-          <Button type="link" onClick={() => onDelete(record)}>
-            {i18n.t('basic.Delete')}
-          </Button>
-          {record?.status && (record?.status === 120 || record?.status === 130) && (
-            <Button type="link" onClick={() => openModal(record)}>
-              {i18n.t('pages.GroupDashboard.config.ExecuteLog')}
+            {[102].includes(record?.status) && (
+              <Button type="link">
+                <Link to={genCreateUrl(record)}>{i18n.t('basic.Edit')}</Link>
+              </Button>
+            )}
+            <Button type="link" onClick={() => onDelete(record)}>
+              {i18n.t('basic.Delete')}
             </Button>
-          )}
-        </>
-      ),
-    },
-  ]);
+            {record?.status && (record?.status === 120 || record?.status === 130) && (
+              <Button type="link" onClick={() => openModal(record)}>
+                {i18n.t('pages.GroupDashboard.config.ExecuteLog')}
+              </Button>
+            )}
+          </>
+        ),
+      },
+    ]);
 };
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx b/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx
index 3cd253ea6..6d6df52d0 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataSources/DetailModal.tsx
@@ -23,7 +23,7 @@ import { ModalProps } from 'antd/es/modal';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
 import { useRequest, useUpdateEffect } from '@/hooks';
 import { useTranslation } from 'react-i18next';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, SourceMetaType } from '@/metas';
 import request from '@/utils/request';
 
 export interface Props extends ModalProps {
@@ -40,7 +40,7 @@ const Comp: React.FC<Props> = ({ id, inlongGroupId, ...modalProps }) => {
 
   const [type, setType] = useState(defaultValue);
 
-  const { Entity } = useLoadMeta('source', type);
+  const { Entity } = useLoadMeta<SourceMetaType>('source', type);
 
   const { data, run: getData } = useRequest(
     id => ({
@@ -91,7 +91,7 @@ const Comp: React.FC<Props> = ({ id, inlongGroupId, ...modalProps }) => {
   }, [modalProps.visible]);
 
   const formContent = useMemo(() => {
-    return Entity ? Entity.FieldList : [];
+    return Entity ? new Entity().renderRow() : [];
   }, [Entity]);
 
   return (
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx b/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx
index 980c0325d..640df7aea 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataSources/index.tsx
@@ -22,7 +22,7 @@ import { Button, Modal, message } from 'antd';
 import HighTable from '@/components/HighTable';
 import { defaultSize } from '@/configs/pagination';
 import { useRequest } from '@/hooks';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, SourceMetaType } from '@/metas';
 import DetailModal from './DetailModal';
 import i18n from '@/i18n';
 import request from '@/utils/request';
@@ -108,7 +108,15 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
     total: data?.total,
   };
 
-  const { Entity } = useLoadMeta('source', options.sourceType);
+  const { Entity } = useLoadMeta<SourceMetaType>('source', options.sourceType);
+
+  const entityColumns = useMemo(() => {
+    return Entity ? new Entity().renderList() : [];
+  }, [Entity]);
+
+  const entityFields = useMemo(() => {
+    return Entity ? new Entity().renderRow() : [];
+  }, [Entity]);
 
   const getFilterFormContent = useCallback(
     defaultValues =>
@@ -122,19 +130,17 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
           },
         },
       ].concat(
-        pickObjectArray(['sourceType', 'status'], Entity?.FieldList).map(item => ({
+        pickObjectArray(['sourceType', 'status'], entityFields).map(item => ({
           ...item,
           visible: true,
           initialValue: defaultValues[item.name],
         })),
       ),
-    [Entity?.FieldList],
+    [entityFields],
   );
 
   const columns = useMemo(() => {
-    if (!Entity) return [];
-
-    return Entity.ColumnList?.concat([
+    return entityColumns?.concat([
       {
         title: i18n.t('basic.Operating'),
         dataIndex: 'action',
@@ -153,7 +159,7 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
           ),
       },
     ]);
-  }, [Entity, onDelete, onEdit, readonly]);
+  }, [entityColumns, onDelete, onEdit, readonly]);
 
   return (
     <>
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx b/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx
index 0db83f494..bba88d61b 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStorage/DetailModal.tsx
@@ -23,7 +23,7 @@ import { ModalProps } from 'antd/es/modal';
 import { useRequest, useUpdateEffect } from '@/hooks';
 import { useTranslation } from 'react-i18next';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, SinkMetaType } from '@/metas';
 import request from '@/utils/request';
 
 export interface DetailModalProps extends ModalProps {
@@ -45,7 +45,7 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, id, ...modalProps })
   // A: Avoid the table of the fields triggering the monitoring of the column change.
   const [sinkType, setSinkType] = useState('');
 
-  const { Entity } = useLoadMeta('sink', sinkType);
+  const { Entity } = useLoadMeta<SinkMetaType>('sink', sinkType);
 
   const {
     data,
@@ -81,7 +81,7 @@ const Comp: React.FC<DetailModalProps> = ({ inlongGroupId, id, ...modalProps })
   }, [modalProps.visible]);
 
   const formContent = useMemo(() => {
-    return Entity ? Entity.FieldList : [];
+    return Entity ? new Entity().renderRow() : [];
   }, [Entity]);
 
   const onOk = async () => {
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx b/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx
index 9158542cb..cdada63ba 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStorage/index.tsx
@@ -24,7 +24,7 @@ import { defaultSize } from '@/configs/pagination';
 import { useRequest } from '@/hooks';
 import i18n from '@/i18n';
 import DetailModal from './DetailModal';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, SinkMetaType } from '@/metas';
 import request from '@/utils/request';
 import { pickObjectArray } from '@/utils';
 import { CommonInterface } from '../common';
@@ -108,7 +108,15 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
     total: data?.total,
   };
 
-  const { Entity } = useLoadMeta('sink', options.sinkType);
+  const { Entity } = useLoadMeta<SinkMetaType>('sink', options.sinkType);
+
+  const entityColumns = useMemo(() => {
+    return Entity ? new Entity().renderList() : [];
+  }, [Entity]);
+
+  const entityFields = useMemo(() => {
+    return Entity ? new Entity().renderRow() : [];
+  }, [Entity]);
 
   const getFilterFormContent = useCallback(
     defaultValues => [
@@ -116,19 +124,17 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
         type: 'inputsearch',
         name: 'keyword',
       },
-      ...pickObjectArray(['sinkType', 'status'], Entity?.FieldList).map(item => ({
+      ...pickObjectArray(['sinkType', 'status'], entityFields).map(item => ({
         ...item,
         visible: true,
         initialValue: defaultValues[item.name],
       })),
     ],
-    [Entity?.FieldList],
+    [entityFields],
   );
 
   const columns = useMemo(() => {
-    if (!Entity) return [];
-
-    return Entity.ColumnList?.concat([
+    return entityColumns?.concat([
       {
         title: i18n.t('basic.Operating'),
         dataIndex: 'action',
@@ -147,7 +153,7 @@ const Comp = ({ inlongGroupId, readonly }: Props, ref) => {
           ),
       } as any,
     ]);
-  }, [Entity, onDelete, onEdit, readonly]);
+  }, [entityColumns, onDelete, onEdit, readonly]);
 
   return (
     <>
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataStream/StreamItemModal.tsx b/inlong-dashboard/src/pages/GroupDetail/DataStream/StreamItemModal.tsx
index 46a704fbe..08c4af720 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataStream/StreamItemModal.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStream/StreamItemModal.tsx
@@ -17,13 +17,13 @@
  * under the License.
  */
 
-import React, { useCallback } from 'react';
+import React, { useMemo } from 'react';
 import { Modal, message } from 'antd';
 import { ModalProps } from 'antd/es/modal';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
 import { useUpdateEffect, useRequest } from '@/hooks';
 import i18n from '@/i18n';
-import { useLoadMeta, useDefaultMeta } from '@/metas';
+import { useLoadMeta, useDefaultMeta, StreamMetaType } from '@/metas';
 import request from '@/utils/request';
 import { dataToValues, valuesToData } from './helper';
 
@@ -39,72 +39,74 @@ const Comp: React.FC<Props> = ({ inlongGroupId, inlongStreamId, mqType, ...modal
 
   const { defaultValue } = useDefaultMeta('stream');
 
-  const { Entity } = useLoadMeta('stream', defaultValue);
+  const { Entity } = useLoadMeta<StreamMetaType>('stream', defaultValue);
 
-  const genFormContent = useCallback(
-    (isCreate, mqType) => {
-      return [
-        ...(Entity?.FieldList || []),
-        {
-          type: 'inputnumber',
-          label: i18n.t('meta.Group.NumberOfAccess'),
-          name: 'dailyRecords',
-          rules: [{ required: true }],
-          suffix: i18n.t('meta.Group.TenThousand/Day'),
-          props: {
-            min: 1,
-            precision: 0,
-          },
-          visible: mqType === 'PULSAR',
+  const entityFields = useMemo(() => {
+    return Entity ? new Entity().renderRow() : [];
+  }, [Entity]);
+
+  const formContent = useMemo(() => {
+    return [
+      ...(entityFields || []),
+      {
+        type: 'inputnumber',
+        label: i18n.t('meta.Group.TubeMq.NumberOfAccess'),
+        name: 'dailyRecords',
+        rules: [{ required: true }],
+        suffix: i18n.t('meta.Group.TubeMq.TenThousand/Day'),
+        props: {
+          min: 1,
+          precision: 0,
         },
-        {
-          type: 'inputnumber',
-          label: i18n.t('meta.Group.AccessSize'),
-          name: 'dailyStorage',
-          rules: [{ required: true }],
-          suffix: i18n.t('meta.Group.GB/Day'),
-          props: {
-            min: 1,
-            precision: 0,
-          },
-          visible: mqType === 'PULSAR',
+        visible: mqType === 'PULSAR',
+      },
+      {
+        type: 'inputnumber',
+        label: i18n.t('meta.Group.TubeMq.AccessSize'),
+        name: 'dailyStorage',
+        rules: [{ required: true }],
+        suffix: i18n.t('meta.Group.TubeMq.GB/Day'),
+        props: {
+          min: 1,
+          precision: 0,
         },
-        {
-          type: 'inputnumber',
-          label: i18n.t('meta.Group.AccessPeakPerSecond'),
-          name: 'peakRecords',
-          rules: [{ required: true }],
-          suffix: i18n.t('meta.Group.Stripe/Second'),
-          props: {
-            min: 1,
-            precision: 0,
-          },
-          visible: mqType === 'PULSAR',
+        visible: mqType === 'PULSAR',
+      },
+      {
+        type: 'inputnumber',
+        label: i18n.t('meta.Group.TubeMq.AccessPeakPerSecond'),
+        name: 'peakRecords',
+        rules: [{ required: true }],
+        suffix: i18n.t('meta.Group.TubeMq.Stripe/Second'),
+        props: {
+          min: 1,
+          precision: 0,
         },
-        {
-          type: 'inputnumber',
-          label: i18n.t('meta.Group.SingleStripMaximumLength'),
-          name: 'maxLength',
-          rules: [{ required: true }],
-          suffix: 'Byte',
-          props: {
-            min: 1,
-            precision: 0,
-          },
-          visible: mqType === 'PULSAR',
+        visible: mqType === 'PULSAR',
+      },
+      {
+        type: 'inputnumber',
+        label: i18n.t('meta.Group.TubeMq.SingleStripMaximumLength'),
+        name: 'maxLength',
+        rules: [{ required: true }],
+        suffix: 'Byte',
+        props: {
+          min: 1,
+          precision: 0,
         },
-      ].map(item => {
-        const obj = { ...item };
+        visible: mqType === 'PULSAR',
+      },
+    ].map(item => {
+      const obj = { ...item };
+      const isCreate = !inlongStreamId;
 
-        if (!isCreate && (obj.name === 'inlongStreamId' || obj.name === 'dataType')) {
-          obj.type = 'text';
-        }
+      if (!isCreate && (obj.name === 'inlongStreamId' || obj.name === 'dataType')) {
+        obj.type = 'text';
+      }
 
-        return obj;
-      });
-    },
-    [Entity?.FieldList],
-  );
+      return obj;
+    });
+  }, [entityFields, mqType, inlongStreamId]);
 
   const { data: savedData, run: getStreamData } = useRequest(
     {
@@ -158,7 +160,7 @@ const Comp: React.FC<Props> = ({ inlongGroupId, inlongStreamId, mqType, ...modal
       <FormGenerator
         labelCol={{ span: 4 }}
         wrapperCol={{ span: 20 }}
-        content={genFormContent(!inlongStreamId, mqType)}
+        content={formContent}
         form={form}
         useMaxWidth
       />
diff --git a/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx b/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx
index 0a93524e4..634127bd9 100644
--- a/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/DataStream/index.tsx
@@ -17,14 +17,14 @@
  * under the License.
  */
 
-import React, { useState, useImperativeHandle, forwardRef } from 'react';
+import React, { useState, useImperativeHandle, forwardRef, useMemo } from 'react';
 import { Button, Modal, message } from 'antd';
 import HighTable from '@/components/HighTable';
 import { defaultSize } from '@/configs/pagination';
 import { useRequest } from '@/hooks';
 import request from '@/utils/request';
 import { useTranslation } from 'react-i18next';
-import { useLoadMeta, useDefaultMeta } from '@/metas';
+import { useLoadMeta, useDefaultMeta, StreamMetaType } from '@/metas';
 import { CommonInterface } from '../common';
 import StreamItemModal from './StreamItemModal';
 import { getFilterFormContent } from './config';
@@ -125,9 +125,13 @@ const Comp = ({ inlongGroupId, readonly, mqType }: Props, ref) => {
     total: data?.total,
   };
 
-  const { Entity } = useLoadMeta('stream', defaultValue);
+  const { Entity } = useLoadMeta<StreamMetaType>('stream', defaultValue);
 
-  const columns = Entity?.ColumnList?.concat([
+  const entityColumns = useMemo(() => {
+    return Entity ? new Entity().renderList() : [];
+  }, [Entity]);
+
+  const columns = entityColumns?.concat([
     {
       title: t('basic.Operating'),
       dataIndex: 'action',
diff --git a/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx b/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx
index bea9d36b8..4a423f3ab 100644
--- a/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx
+++ b/inlong-dashboard/src/pages/GroupDetail/Info/config.tsx
@@ -17,14 +17,19 @@
  * under the License.
  */
 
-import { useLoadMeta } from '@/metas';
+import { useMemo } from 'react';
+import { useLoadMeta, GroupMetaType } from '@/metas';
 import { excludeObjectArray } from '@/utils';
 
 export const useFormContent = ({ mqType, editing, isCreate, isUpdate }) => {
-  const { Entity } = useLoadMeta('group', mqType);
+  const { Entity } = useLoadMeta<GroupMetaType>('group', mqType);
+
+  const entityFields = useMemo(() => {
+    return Entity ? new Entity().renderRow() : [];
+  }, [Entity]);
 
   const excludeKeys = ['ensemble'].concat(isCreate ? 'mqResource' : '');
-  const fields = excludeObjectArray(excludeKeys, Entity?.FieldList || []);
+  const fields = excludeObjectArray(excludeKeys, entityFields || []);
 
   return isCreate
     ? fields.map(item => {
diff --git a/inlong-dashboard/src/pages/Nodes/DetailModal.tsx b/inlong-dashboard/src/pages/Nodes/DetailModal.tsx
index 1c89bdc5c..fd6154530 100644
--- a/inlong-dashboard/src/pages/Nodes/DetailModal.tsx
+++ b/inlong-dashboard/src/pages/Nodes/DetailModal.tsx
@@ -23,7 +23,7 @@ import { ModalProps } from 'antd/es/modal';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
 import { useUpdateEffect } from '@/hooks';
 import { dao } from '@/metas/nodes';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, NodeMetaType } from '@/metas';
 import i18n from '@/i18n';
 
 const { useFindNodeDao, useSaveNodeDao } = dao;
@@ -74,10 +74,10 @@ const Comp: React.FC<Props> = ({ id, ...modalProps }) => {
     }
   }, [modalProps.visible]);
 
-  const { Entity } = useLoadMeta('node', type);
+  const { Entity } = useLoadMeta<NodeMetaType>('node', type);
 
   const content = useMemo(() => {
-    return Entity ? Entity.FieldList : [];
+    return Entity ? new Entity().renderRow() : [];
   }, [Entity]);
 
   return (
diff --git a/inlong-dashboard/src/pages/Nodes/index.tsx b/inlong-dashboard/src/pages/Nodes/index.tsx
index fd2c279ae..4b00c4045 100644
--- a/inlong-dashboard/src/pages/Nodes/index.tsx
+++ b/inlong-dashboard/src/pages/Nodes/index.tsx
@@ -24,7 +24,7 @@ import HighTable from '@/components/HighTable';
 import { PageContainer } from '@/components/PageContainer';
 import { defaultSize } from '@/configs/pagination';
 import { dao } from '@/metas/nodes';
-import { useDefaultMeta, useLoadMeta } from '@/metas';
+import { useDefaultMeta, useLoadMeta, NodeMetaType } from '@/metas';
 import DetailModal from './DetailModal';
 
 const { useListNodeDao, useDeleteNodeDao } = dao;
@@ -107,32 +107,36 @@ const Comp: React.FC = () => {
     [nodes],
   );
 
-  const { Entity } = useLoadMeta('node', options.type);
+  const { Entity } = useLoadMeta<NodeMetaType>('node', options.type);
 
-  const columns = useMemo(() => {
-    if (!Entity) return [];
+  const entityColumns = useMemo(() => {
+    return Entity ? new Entity().renderList() : [];
+  }, [Entity]);
 
-    return Entity.ColumnList?.map(item => ({
-      ...item,
-      ellipsisMulti: 2,
-    })).concat([
-      {
-        title: i18n.t('basic.Operating'),
-        dataIndex: 'action',
-        width: 200,
-        render: (text, record) => (
-          <>
-            <Button type="link" onClick={() => onEdit(record)}>
-              {i18n.t('basic.Edit')}
-            </Button>
-            <Button type="link" onClick={() => onDelete(record)}>
-              {i18n.t('basic.Delete')}
-            </Button>
-          </>
-        ),
-      } as any,
-    ]);
-  }, [Entity, onDelete]);
+  const columns = useMemo(() => {
+    return entityColumns
+      ?.map(item => ({
+        ...item,
+        ellipsisMulti: 2,
+      }))
+      .concat([
+        {
+          title: i18n.t('basic.Operating'),
+          dataIndex: 'action',
+          width: 200,
+          render: (text, record) => (
+            <>
+              <Button type="link" onClick={() => onEdit(record)}>
+                {i18n.t('basic.Edit')}
+              </Button>
+              <Button type="link" onClick={() => onDelete(record)}>
+                {i18n.t('basic.Delete')}
+              </Button>
+            </>
+          ),
+        } as any,
+      ]);
+  }, [entityColumns, onDelete]);
 
   return (
     <PageContainer useDefaultBreadcrumb={false}>
diff --git a/inlong-dashboard/src/pages/ProcessDetail/AccessConfig.tsx b/inlong-dashboard/src/pages/ProcessDetail/AccessConfig.tsx
index f025e0637..3ffd3fbef 100644
--- a/inlong-dashboard/src/pages/ProcessDetail/AccessConfig.tsx
+++ b/inlong-dashboard/src/pages/ProcessDetail/AccessConfig.tsx
@@ -17,15 +17,19 @@
  * under the License.
  */
 
-import React from 'react';
+import React, { useMemo } from 'react';
 import { Divider, Table } from 'antd';
 import i18n from '@/i18n';
-import { useLoadMeta } from '@/metas';
+import { useLoadMeta, GroupMetaType } from '@/metas';
 
 export const useGroupFormContent = ({ mqType = '', isFinished, isViwer }) => {
-  const { Entity } = useLoadMeta('group', mqType);
+  const { Entity } = useLoadMeta<GroupMetaType>('group', mqType);
 
-  return Entity?.FieldList?.map(item => {
+  const entityFields = useMemo(() => {
+    return Entity ? new Entity().renderRow() : [];
+  }, [Entity]);
+
+  return entityFields?.map(item => {
     const obj = { ...item };
 
     const canEditSet = new Set([
diff --git a/inlong-dashboard/src/pages/ProcessDetail/ConsumeConfig.tsx b/inlong-dashboard/src/pages/ProcessDetail/ConsumeConfig.tsx
index b4245f4d7..859c37499 100644
--- a/inlong-dashboard/src/pages/ProcessDetail/ConsumeConfig.tsx
+++ b/inlong-dashboard/src/pages/ProcessDetail/ConsumeConfig.tsx
@@ -17,15 +17,19 @@
  * under the License.
  */
 
-import React from 'react';
+import React, { useMemo } from 'react';
 import { Divider } from 'antd';
 import i18n from '@/i18n';
-import { useLoadMeta } from '@/metas';
+import { useLoadMeta, ConsumeMetaType } from '@/metas';
 
 export const useConsumeFormContent = (mqType = '') => {
-  const { Entity } = useLoadMeta('consume', mqType);
+  const { Entity } = useLoadMeta<ConsumeMetaType>('consume', mqType);
 
-  return Entity?.FieldList?.map(item => {
+  const entityFields = useMemo(() => {
+    return Entity ? new Entity().renderRow() : [];
+  }, [Entity]);
+
+  return entityFields?.map(item => {
     const obj = { ...item };
     if (typeof obj.suffix !== 'string') {
       delete obj.suffix;
diff --git a/inlong-dashboard/src/utils/metaData.ts b/inlong-dashboard/src/utils/metaData.ts
deleted file mode 100644
index 6532de7b0..000000000
--- a/inlong-dashboard/src/utils/metaData.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { FormItemProps } from '@/components/FormGenerator';
-import { ColumnsType } from 'antd/es/table';
-import { ColumnsItemProps } from '@/components/EditableTable';
-
-export type GetStorageColumnsType = (
-  dataType: string,
-  currentValues?: Record<string, unknown>,
-) => ColumnsItemProps[];
-
-export type GetStorageFormFieldsType = (
-  type: 'form' | 'col',
-  {
-    currentValues,
-    inlongGroupId,
-    isEdit,
-    dataType,
-    form,
-  }?: {
-    currentValues?: Record<string, any>;
-    inlongGroupId?: string;
-    isEdit?: boolean;
-    dataType?: string;
-    form?: any;
-  },
-) => FormItemProps[] | ColumnsType;
-
-interface FieldConfigItem extends FormItemProps {
-  _inTable?: boolean | Record<string, unknown>;
-}
-
-export const getColsFromFields = (fieldsConfig: FieldConfigItem[]): ColumnsType => {
-  return fieldsConfig
-    .filter(item => item._inTable)
-    .map(item => {
-      let output = {
-        title: item.label,
-        dataIndex: item.name,
-      };
-      if (typeof item._inTable === 'object') {
-        output = {
-          ...output,
-          ...item._inTable,
-        };
-      }
-      return output;
-    });
-};