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

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


The following commit(s) were added to refs/heads/master by this push:
     new 6c94610372 [INLONG-10377][Dashboard] Add Source Data Field Template 
(#10378)
6c94610372 is described below

commit 6c9461037217b2bfff61676a9745f80d66bb2766
Author: wohainilaodou <[email protected]>
AuthorDate: Wed Jun 12 19:30:30 2024 +0800

    [INLONG-10377][Dashboard] Add Source Data Field Template (#10378)
    
    Co-authored-by: v_shuomqiu <[email protected]>
---
 inlong-dashboard/src/configs/menus/conf.tsx        |  13 +-
 inlong-dashboard/src/configs/routes/conf.ts        |   4 +
 inlong-dashboard/src/i18n.ts                       |   4 +
 .../plugins/groups/common/GroupDataTemplateInfo.ts | 171 ++++++++++++++++++
 .../plugins/streams/common/StreamDefaultInfo.ts    |  30 ++++
 inlong-dashboard/src/ui/locales/cn.json            |  12 +-
 inlong-dashboard/src/ui/locales/en.json            |  10 +-
 .../src/ui/pages/Clusters/NodeEditModal.tsx        | 114 ++++++++----
 .../src/ui/pages/GroupDataTemplate/CreateModal.tsx | 112 ++++++++++++
 .../src/ui/pages/GroupDataTemplate/index.tsx       | 195 +++++++++++++++++++++
 .../GroupDetail/DataStream/StreamItemModal.tsx     |  21 +++
 11 files changed, 649 insertions(+), 37 deletions(-)

diff --git a/inlong-dashboard/src/configs/menus/conf.tsx 
b/inlong-dashboard/src/configs/menus/conf.tsx
index dc660dc62f..a4e80f26bd 100644
--- a/inlong-dashboard/src/configs/menus/conf.tsx
+++ b/inlong-dashboard/src/configs/menus/conf.tsx
@@ -33,9 +33,18 @@ import type { MenuItemType } from '.';
 
 const conf: MenuItemType[] = [
   {
-    path: '/group',
-    name: i18n.t('configs.menus.Groups'),
+    name: i18n.t('configs.menus.GroupsManagement'),
     icon: <ApiOutlined />,
+    children: [
+      {
+        path: '/group',
+        name: i18n.t('configs.menus.Groups'),
+      },
+      {
+        path: '/dataTemplate',
+        name: i18n.t('configs.menus.Groups.Template'),
+      },
+    ],
   },
   {
     path: '/sync',
diff --git a/inlong-dashboard/src/configs/routes/conf.ts 
b/inlong-dashboard/src/configs/routes/conf.ts
index 007538d997..ce3a38ab12 100644
--- a/inlong-dashboard/src/configs/routes/conf.ts
+++ b/inlong-dashboard/src/configs/routes/conf.ts
@@ -38,6 +38,10 @@ const conf: RouteProps[] = [
       },
     ],
   },
+  {
+    path: '/dataTemplate',
+    component: () => import('@/ui/pages/GroupDataTemplate'),
+  },
   {
     path: '/consume',
     component: () => import('@/ui/pages/ConsumeDashboard'),
diff --git a/inlong-dashboard/src/i18n.ts b/inlong-dashboard/src/i18n.ts
index 0146bf9320..eebff6c37f 100644
--- a/inlong-dashboard/src/i18n.ts
+++ b/inlong-dashboard/src/i18n.ts
@@ -27,6 +27,8 @@ const resources = {
     translation: {
       'configs.menus.Process': 'Approval',
       'configs.menus.Groups': 'Ingestion',
+      'configs.menus.GroupsManagement': 'Ingestion Management',
+      'configs.menus.Groups.Template': 'Template',
       'configs.menus.Subscribe': 'Subscription',
       'configs.menus.Clusters': 'Clusters',
       'configs.menus.ClusterTags': 'ClusterTags',
@@ -46,6 +48,8 @@ const resources = {
     translation: {
       'configs.menus.Process': '审批管理',
       'configs.menus.Groups': '数据接入',
+      'configs.menus.GroupsManagement': '接入管理',
+      'configs.menus.Groups.Template': '模板',
       'configs.menus.Subscribe': '数据订阅',
       'configs.menus.Clusters': '集群管理',
       'configs.menus.ClusterTags': '标签管理',
diff --git 
a/inlong-dashboard/src/plugins/groups/common/GroupDataTemplateInfo.ts 
b/inlong-dashboard/src/plugins/groups/common/GroupDataTemplateInfo.ts
new file mode 100644
index 0000000000..4699970415
--- /dev/null
+++ b/inlong-dashboard/src/plugins/groups/common/GroupDataTemplateInfo.ts
@@ -0,0 +1,171 @@
+/*
+ * 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 { DataWithBackend } from '@/plugins/DataWithBackend';
+import { RenderRow } from '@/plugins/RenderRow';
+import { RenderList } from '@/plugins/RenderList';
+import i18n from '@/i18n';
+import EditableTable from '@/ui/components/EditableTable';
+import { fieldTypes as sourceFieldsTypes } from 
'@/plugins/sinks/common/sourceFields';
+import UserSelect from '@/ui/components/UserSelect';
+
+const { I18nMap, I18n } = DataWithBackend;
+const { FieldList, FieldDecorator } = RenderRow;
+const { ColumnList, ColumnDecorator } = RenderList;
+
+export class GroupDataTemplateInfo implements DataWithBackend, RenderRow, 
RenderList {
+  static I18nMap = I18nMap;
+  static FieldList = FieldList;
+  static ColumnList = ColumnList;
+
+  readonly id: number;
+
+  @FieldDecorator({
+    type: 'input',
+    rules: [{ required: true }],
+  })
+  @ColumnDecorator()
+  @I18n('pages.GroupDataTemplate.Name')
+  name: string;
+
+  @FieldDecorator({
+    type: UserSelect,
+    rules: [{ required: true }],
+    props: {
+      mode: 'multiple',
+      currentUserClosable: false,
+    },
+  })
+  @ColumnDecorator()
+  @I18n('pages.GroupDataTemplate.InChargers')
+  inCharges: string;
+
+  @FieldDecorator({
+    type: 'select',
+    props: {
+      mode: 'multiple',
+      filterOption: true,
+      options: {
+        requestTrigger: ['onOpen', 'onSearch'],
+        requestService: keyword => ({
+          url: '/tenant/list',
+          method: 'POST',
+          data: {
+            keyword,
+            pageNum: 1,
+            pageSize: 9999,
+          },
+        }),
+        requestParams: {
+          formatResult: result => {
+            return result?.list?.map(item => ({
+              ...item,
+              label: item.name,
+              value: item.name.toString(),
+            }));
+          },
+        },
+      },
+    },
+    rules: [
+      {
+        required: true,
+      },
+    ],
+  })
+  @ColumnDecorator()
+  @I18n('pages.GroupDataTemplate.TenantList')
+  tenantList: string[];
+
+  @FieldDecorator({
+    type: 'input',
+    initialValue: 0,
+    rules: [{ required: true }],
+  })
+  @I18n('pages.GroupDataTemplate.Version')
+  version: number;
+
+  @FieldDecorator({
+    type: 'input',
+    initialValue: '',
+    rules: [{ required: true }],
+  })
+  @I18n('pages.GroupDataTemplate.VisibleRange')
+  visibleRange: String;
+
+  @FieldDecorator({
+    type: EditableTable,
+    props: values => ({
+      size: 'small',
+      canDelete: record => !(record.id && [110].includes(values?.status)),
+      canBatchAdd: true,
+      columns: [
+        {
+          title: i18n.t('meta.Stream.FieldName'),
+          dataIndex: 'fieldName',
+          rules: [
+            { required: true },
+            {
+              pattern: /^[_a-zA-Z][a-zA-Z0-9_]*$/,
+              message: i18n.t('meta.Stream.FieldNameRule'),
+            },
+          ],
+          props: (text, record) => ({
+            disabled: record.id && [110].includes(values?.status),
+          }),
+        },
+        {
+          title: i18n.t('meta.Stream.FieldType'),
+          dataIndex: 'fieldType',
+          type: 'select',
+          initialValue: sourceFieldsTypes[0].value,
+          props: (text, record) => ({
+            disabled: record.id && [110].includes(values?.status),
+            options: sourceFieldsTypes,
+          }),
+          rules: [{ required: true }],
+        },
+        {
+          title: i18n.t('meta.Stream.FieldComment'),
+          dataIndex: 'fieldComment',
+        },
+      ],
+    }),
+  })
+  @I18n('meta.Stream.SourceDataField')
+  fieldList: Record<string, string>[];
+
+  parse(data) {
+    return data;
+  }
+
+  stringify(data) {
+    return data;
+  }
+
+  renderRow() {
+    const constructor = this.constructor as typeof GroupDataTemplateInfo;
+    return constructor.FieldList;
+  }
+
+  renderList() {
+    const constructor = this.constructor as typeof GroupDataTemplateInfo;
+    return constructor.ColumnList;
+  }
+}
diff --git a/inlong-dashboard/src/plugins/streams/common/StreamDefaultInfo.ts 
b/inlong-dashboard/src/plugins/streams/common/StreamDefaultInfo.ts
index 54b840bd29..592c53b508 100644
--- a/inlong-dashboard/src/plugins/streams/common/StreamDefaultInfo.ts
+++ b/inlong-dashboard/src/plugins/streams/common/StreamDefaultInfo.ts
@@ -222,6 +222,36 @@ export class StreamDefaultInfo implements DataWithBackend, 
RenderRow, RenderList
   })
   @I18n('meta.Stream.DataSeparator')
   dataSeparator: string;
+  @FieldDecorator({
+    type: 'select',
+    props: {
+      filterOption: true,
+      options: {
+        requestTrigger: ['onOpen', 'onSearch'],
+        requestService: keyword => ({
+          url: '/template/list',
+          method: 'POST',
+          data: {
+            keyword,
+            pageNum: 1,
+            pageSize: 20,
+          },
+        }),
+        requestParams: {
+          formatResult: result => {
+            return result?.list?.map(item => ({
+              ...item,
+              label: item.name,
+              value: item.name,
+            }));
+          },
+        },
+      },
+    },
+    rules: [],
+  })
+  @I18n('meta.Stream.SourceDataField.Template')
+  streamDataTemplate: string;
 
   @FieldDecorator({
     type: EditableTable,
diff --git a/inlong-dashboard/src/ui/locales/cn.json 
b/inlong-dashboard/src/ui/locales/cn.json
index d357134e7b..09b1d493f8 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -34,6 +34,7 @@
   "meta.Sources.File.FilePathHelp": "必须是绝对路径,支持正则表达式,多个时以逗号分隔,如:/data/.*log",
   "meta.Sources.File.FileIpHelp": "请选择文件采集节点 IP,若 IP 不固定请选择 All",
   "meta.Sources.File.IpRule": "请输入正确的 IP 地址",
+  "meta.Sources.File.VersionRule": "请输入正确的版本号",
   "meta.Sources.File.TimeOffset": "时间偏移量",
   "meta.Sources.File.TimeOffsetHelp": 
"从文件的某个时间开始采集,'1m'表示1分钟之后,'-1m'表示1分钟之前,支持m(分钟),h(小时),d(天),空则从当前时间开始采集",
   "meta.Sources.File.TimeOffsetRules": "只能包含数字和字母 m、h、d",
@@ -428,6 +429,7 @@
   "meta.Stream.DataEncoding": "数据编码",
   "meta.Stream.Description": "介绍",
   "meta.Stream.SourceDataField": "源数据字段",
+  "meta.Stream.SourceDataField.Template": "源数据字段模板",
   "meta.Stream.IgnoreParseError": "忽略数据解析错误",
   "meta.Stream.Status.New": "新建",
   "meta.Stream.Status.Pending": "配置中",
@@ -758,6 +760,7 @@
   "pages.Clusters.Node.Port": "端口",
   "pages.Clusters.Node.ProtocolType": "协议类型",
   "pages.Clusters.Node.Agent": "Agent",
+  "pages.Clusters.Node.Agent.Version": "Version",
   "pages.Clusters.Node.AgentInstaller": "Installer",
   "pages.Clusters.Node.IsInstall": "安装方式",
   "pages.Clusters.Node.ManualInstall": "手动安装",
@@ -899,5 +902,12 @@
   "pages.PackageAgent.Config.FileName": "文件名称",
   "pages.PackageAgent.Config.DownloadUrl": "下载地址",
   "pages.PackageAgent.Config.StoragePath": "存储路径",
-  "pages.PackageAgent.Config.Md5": "MD5"
+  "pages.PackageAgent.Config.Md5": "MD5",
+  "pages.GroupDataTemplate.Edit":"编辑模板",
+  "pages.GroupDataTemplate.Create":"新建模板",
+  "pages.GroupDataTemplate.Name":"模板名称",
+  "pages.GroupDataTemplate.TenantList":"租户",
+  "pages.GroupDataTemplate.Version":"版本",
+  "pages.GroupDataTemplate.VisibleRange":"可视范围",
+  "pages.GroupDataTemplate.InChargers":"负责人"
 }
diff --git a/inlong-dashboard/src/ui/locales/en.json 
b/inlong-dashboard/src/ui/locales/en.json
index 88dd932c31..fdc2bf3e85 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -428,6 +428,7 @@
   "meta.Stream.DataEncoding": "Data encoding",
   "meta.Stream.Description": "Description",
   "meta.Stream.SourceDataField": "Source fields",
+  "meta.Stream.SourceDataField.Template": "Source fields Template",
   "meta.Stream.IgnoreParseError": "Ignore parse error",
   "meta.Stream.Status.New": "New",
   "meta.Stream.Status.Pending": "Pending",
@@ -899,5 +900,12 @@
   "pages.PackageAgent.Config.FileName": "File Name",
   "pages.PackageAgent.Config.DownloadUrl": "Download Url",
   "pages.PackageAgent.Config.Md5": "MD5",
-  "pages.PackageAgent.Config.StoragePath": "Storage Path"
+  "pages.PackageAgent.Config.StoragePath": "Storage Path",
+  "pages.GroupDataTemplate.Edit":"Edit Template",
+  "pages.GroupDataTemplate.Create":"Create Template",
+  "pages.GroupDataTemplate.Name":"Template Name",
+  "pages.GroupDataTemplate.TenantList":"Tenant",
+  "pages.GroupDataTemplate.Version":"Version",
+  "pages.GroupDataTemplate.VisibleRange":"Visible Range",
+  "pages.GroupDataTemplate.InChargers":"InChargers"
 }
diff --git a/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx 
b/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx
index 8050756849..687713f9e7 100644
--- a/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx
+++ b/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx
@@ -79,12 +79,40 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id, 
type, clusterId, ...m
     message.success(i18n.t('basic.OperatingSuccess'));
   };
 
+  const { data: agentInstaller, run: getAgentInstall } = useRequest(
+    () => ({
+      url: '/module/list',
+      method: 'POST',
+      data: {
+        pageNum: 1,
+        pageSize: 9999,
+      },
+    }),
+    {
+      manual: true,
+      onSuccess: result => {
+        const temp = result?.list
+          ?.filter(item => item.type === 'INSTALLER')
+          .map(item => ({
+            ...item,
+            label: `${item.name} ${item.version}`,
+            value: item.id,
+          }));
+        form.setFieldValue('installer', temp[0].id);
+      },
+    },
+  );
+
   useUpdateEffect(() => {
     if (modalProps.open) {
       // open
       form.resetFields();
       if (id) {
         getData(id);
+      } else {
+        if (type === 'AGENT') {
+          getAgentInstall();
+        }
       }
     }
   }, [modalProps.open]);
@@ -139,42 +167,29 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ 
id, type, clusterId, ...m
       },
       {
         type: 'select',
-        label: i18n.t('pages.Clusters.Node.Agent'),
-        name: 'moduleIdList',
-        hidden: type !== 'AGENT',
+        label: i18n.t('pages.Clusters.Node.ProtocolType'),
+        name: 'protocolType',
+        initialValue: 'HTTP',
+        rules: [{ required: true }],
         props: {
-          options: {
-            requestAuto: true,
-            requestTrigger: ['onOpen'],
-            requestService: keyword => ({
-              url: '/module/list',
-              method: 'POST',
-              data: {
-                keyword,
-                pageNum: 1,
-                pageSize: 9999,
-              },
-            }),
-            requestParams: {
-              formatResult: result =>
-                result?.list
-                  ?.filter(item => item.type === 'AGENT')
-                  .map(item => ({
-                    ...item,
-                    label: `${item.name} ${item.version}`,
-                    value: item.id,
-                  })),
+          options: [
+            {
+              label: 'HTTP',
+              value: 'HTTP',
             },
-          },
+            {
+              label: 'TCP',
+              value: 'TCP',
+            },
+          ],
         },
       },
       {
         type: 'select',
-        label: i18n.t('pages.Clusters.Node.AgentInstaller'),
-        name: 'installer',
+        label: i18n.t('pages.Clusters.Node.Agent.Version'),
+        name: 'moduleIdList',
         hidden: type !== 'AGENT',
         props: {
-          mode: 'multiple',
           options: {
             requestAuto: true,
             requestTrigger: ['onOpen'],
@@ -190,7 +205,7 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id, 
type, clusterId, ...m
             requestParams: {
               formatResult: result =>
                 result?.list
-                  ?.filter(item => item.type === 'INSTALLER')
+                  ?.filter(item => item.type === 'AGENT')
                   .map(item => ({
                     ...item,
                     label: `${item.name} ${item.version}`,
@@ -200,6 +215,14 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id, 
type, clusterId, ...m
           },
         },
       },
+      {
+        type: 'textarea',
+        label: i18n.t('pages.Clusters.Description'),
+        name: 'description',
+        props: {
+          maxLength: 256,
+        },
+      },
       {
         type: 'radio',
         label: i18n.t('pages.Clusters.Node.IsInstall'),
@@ -245,11 +268,36 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ 
id, type, clusterId, ...m
         visible: values => values?.isInstall,
       },
       {
-        type: 'textarea',
-        label: i18n.t('pages.Clusters.Description'),
-        name: 'description',
+        type: 'select',
+        label: i18n.t('pages.Clusters.Node.AgentInstaller'),
+        name: 'installer',
+        isPro: type === 'AGENT',
+        hidden: type !== 'AGENT',
         props: {
-          maxLength: 256,
+          mode: 'multiple',
+          options: {
+            requestAuto: true,
+            requestTrigger: ['onOpen'],
+            requestService: keyword => ({
+              url: '/module/list',
+              method: 'POST',
+              data: {
+                keyword,
+                pageNum: 1,
+                pageSize: 9999,
+              },
+            }),
+            requestParams: {
+              formatResult: result =>
+                result?.list
+                  ?.filter(item => item.type === 'INSTALLER')
+                  .map(item => ({
+                    ...item,
+                    label: `${item.name} ${item.version}`,
+                    value: item.id,
+                  })),
+            },
+          },
         },
       },
     ];
diff --git a/inlong-dashboard/src/ui/pages/GroupDataTemplate/CreateModal.tsx 
b/inlong-dashboard/src/ui/pages/GroupDataTemplate/CreateModal.tsx
new file mode 100644
index 0000000000..e385d48070
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/GroupDataTemplate/CreateModal.tsx
@@ -0,0 +1,112 @@
+/*
+ * 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 React, { useState, useMemo } from 'react';
+import { Modal, message, Button } from 'antd';
+import { ModalProps } from 'antd/es/modal';
+import FormGenerator, { useForm } from '@/ui/components/FormGenerator';
+import { useRequest, useUpdateEffect } from '@/ui/hooks';
+import request from '@/core/utils/request';
+import i18n from '@/i18n';
+import { GroupDataTemplateInfo } from 
'@/plugins/groups/common/GroupDataTemplateInfo';
+import { dataToValues } from '@/ui/pages/GroupDetail/DataStream/helper';
+
+export interface Props extends ModalProps {
+  // Require when edit
+  id?: string;
+  templateName?: string;
+}
+
+const Comp: React.FC<Props> = ({ id, templateName, ...modalProps }) => {
+  const [form] = useForm();
+
+  const { data: savedData, run: getData } = useRequest(
+    () => ({
+      url: `/template/get/`,
+      method: 'GET',
+      params: {
+        templateName: templateName,
+      },
+    }),
+    {
+      manual: true,
+      formatResult: result => ({
+        ...result,
+        inCharges: result.inCharges?.split(','),
+      }),
+      onSuccess: result => {
+        form.setFieldsValue(dataToValues(result));
+      },
+    },
+  );
+
+  const onOk = async () => {
+    const values = await form.validateFields();
+    const isUpdate = id;
+    const submitData = {
+      ...values,
+      inCharges: values.inCharges?.join(','),
+    };
+
+    if (isUpdate) {
+      submitData.id = id;
+      submitData.version = savedData?.version;
+    }
+    await request({
+      url: `/template/${isUpdate ? 'update' : 'save'}`,
+      method: 'POST',
+      data: submitData,
+    });
+    await modalProps?.onOk(submitData);
+    message.success(i18n.t('basic.OperatingSuccess'));
+  };
+
+  useUpdateEffect(() => {
+    if (modalProps.open) {
+      if (templateName) {
+        getData();
+      }
+    } else {
+      form.resetFields();
+    }
+  }, [modalProps.open]);
+
+  const content = useMemo(() => {
+    return new GroupDataTemplateInfo().renderRow();
+  }, []);
+  return (
+    <Modal
+      width={1000}
+      {...modalProps}
+      title={id ? i18n.t('pages.GroupDataTemplate.Edit') : 
i18n.t('pages.GroupDataTemplate.Create')}
+      footer={[
+        <Button key="cancel" onClick={e => modalProps.onCancel(e)}>
+          {i18n.t('basic.Cancel')}
+        </Button>,
+        <Button key="save" type="primary" onClick={onOk}>
+          {i18n.t('basic.Save')}
+        </Button>,
+      ]}
+    >
+      <FormGenerator content={content} form={form} onValuesChange={(c, values) 
=> {}} useMaxWidth />
+    </Modal>
+  );
+};
+
+export default Comp;
diff --git a/inlong-dashboard/src/ui/pages/GroupDataTemplate/index.tsx 
b/inlong-dashboard/src/ui/pages/GroupDataTemplate/index.tsx
new file mode 100644
index 0000000000..b53dddd83f
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/GroupDataTemplate/index.tsx
@@ -0,0 +1,195 @@
+/*
+ * 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 React, { useCallback, useMemo, useState } from 'react';
+import { Button } from 'antd';
+import i18n from '@/i18n';
+import HighTable from '@/ui/components/HighTable';
+import { PageContainer } from '@/ui/components/PageContainer';
+import { defaultSize } from '@/configs/pagination';
+import { useRequest } from '@/ui/hooks';
+import CreateModal from './CreateModal';
+import { timestampFormat } from '@/core/utils';
+
+const Comp: React.FC = () => {
+  const [options, setOptions] = useState({
+    inCharges: null,
+    name: null,
+    tenantList: null,
+    orderField: null,
+    orderType: null,
+    pageNum: 1,
+    pageSize: defaultSize,
+    visibleRange: null,
+  });
+
+  const [createModal, setCreateModal] = useState<Record<string, unknown>>({
+    open: false,
+  });
+
+  const {
+    data,
+    loading,
+    run: getList,
+  } = useRequest(
+    {
+      url: '/template/list',
+      method: 'POST',
+      data: {
+        ...options,
+      },
+    },
+    {
+      refreshDeps: [options],
+      onSuccess: result => {},
+    },
+  );
+
+  const onEdit = ({ id, name }) => {
+    setCreateModal({ open: true, id, templateName: name });
+  };
+
+  const onChange = ({ current: pageNum, pageSize }) => {
+    setOptions(prev => ({
+      ...prev,
+      pageNum,
+      pageSize,
+    }));
+  };
+
+  const onFilter = allValues => {
+    for (const key in allValues) {
+      if (allValues[key] === '') {
+        allValues[key] = null;
+      }
+    }
+    setOptions(prev => ({
+      ...prev,
+      ...allValues,
+      pageNum: 1,
+    }));
+  };
+
+  const pagination = {
+    pageSize: +options.pageSize,
+    current: +options.pageNum,
+    total: data?.total,
+  };
+
+  const getFilterFormContent = useCallback(
+    defaultValues => [
+      {
+        type: 'inputsearch',
+        name: 'name',
+      },
+    ],
+    [],
+  );
+  const entityColumns = useMemo(() => {
+    return [
+      {
+        title: 'id',
+        dataIndex: 'id',
+        key: 'id',
+        width: 100,
+      },
+      {
+        title: i18n.t('pages.GroupDataTemplate.Name'),
+        dataIndex: 'name',
+        key: 'name',
+        width: 100,
+      },
+      {
+        title: i18n.t('pages.GroupDataTemplate.InChargers'),
+        dataIndex: 'inCharges',
+        key: 'inCharges',
+        width: 100,
+      },
+      {
+        title: i18n.t('pages.GroupDataTemplate.TenantList'),
+        dataIndex: 'tenantList',
+        key: 'tenantList',
+        width: 200,
+        render: (text, record: any) => (
+          <>
+            <div>{record.tenantList?.join(',')}</div>
+          </>
+        ),
+      },
+      {
+        title: i18n.t('pages.GroupDataTemplate.Version'),
+        dataIndex: 'version',
+        key: 'version',
+        width: 200,
+      },
+    ];
+  }, []);
+  const columns = useMemo(() => {
+    return entityColumns?.concat([
+      {
+        title: i18n.t('basic.Operating'),
+        dataIndex: 'action',
+        width: 200,
+        render: (text, record) => (
+          <>
+            <Button type="link" onClick={() => onEdit(record)}>
+              {i18n.t('basic.Edit')}
+            </Button>
+          </>
+        ),
+      } as any,
+    ]);
+  }, [entityColumns]);
+
+  return (
+    <PageContainer useDefaultBreadcrumb={false}>
+      <HighTable
+        filterForm={{
+          content: getFilterFormContent(options),
+          onFilter,
+        }}
+        suffix={
+          <Button type="primary" onClick={() => setCreateModal({ open: true 
})}>
+            {i18n.t('pages.GroupDataTemplate.Create')}
+          </Button>
+        }
+        table={{
+          columns,
+          rowKey: 'id',
+          dataSource: data?.list,
+          pagination,
+          loading,
+          onChange,
+        }}
+      />
+
+      <CreateModal
+        {...createModal}
+        open={createModal.open as boolean}
+        onOk={async () => {
+          await getList();
+          setCreateModal({ open: false });
+        }}
+        onCancel={() => setCreateModal({ open: false })}
+      />
+    </PageContainer>
+  );
+};
+
+export default Comp;
diff --git 
a/inlong-dashboard/src/ui/pages/GroupDetail/DataStream/StreamItemModal.tsx 
b/inlong-dashboard/src/ui/pages/GroupDetail/DataStream/StreamItemModal.tsx
index b7b9c4d0d4..c8587efa46 100644
--- a/inlong-dashboard/src/ui/pages/GroupDetail/DataStream/StreamItemModal.tsx
+++ b/inlong-dashboard/src/ui/pages/GroupDetail/DataStream/StreamItemModal.tsx
@@ -175,6 +175,22 @@ const Comp: React.FC<Props> = ({ inlongGroupId, 
inlongStreamId, mqType, ...modal
     message.success(i18n.t('basic.OperatingSuccess'));
   };
 
+  const { run: getDataTemplateValue } = useRequest(
+    template => ({
+      url: '/template/get',
+      params: {
+        templateName: template,
+      },
+    }),
+    {
+      manual: true,
+      onSuccess: result => {
+        savedData.fieldList = result['fieldList'];
+        form.setFieldsValue(dataToValues(savedData));
+      },
+    },
+  );
+
   useUpdateEffect(() => {
     if (modalProps.open) {
       // open
@@ -199,6 +215,11 @@ const Comp: React.FC<Props> = ({ inlongGroupId, 
inlongStreamId, mqType, ...modal
         content={formContent}
         form={form}
         useMaxWidth
+        onValuesChange={(c, values) => {
+          if (Object.keys(c)[0] === 'streamDataTemplate') {
+            getDataTemplateValue(c['streamDataTemplate']);
+          }
+        }}
       />
     </Modal>
   );

Reply via email to