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 ffda109fac [INLONG-11766][Dashboard] Support SQL source and SQL node 
(#11767)
ffda109fac is described below

commit ffda109faca7f9fb194da013926a37485f488c19
Author: kamianlaida <165994047+wohainilao...@users.noreply.github.com>
AuthorDate: Fri Feb 21 14:05:39 2025 +0800

    [INLONG-11766][Dashboard] Support SQL source and SQL node (#11767)
---
 inlong-dashboard/src/core/utils/pattern.ts         |   1 +
 .../src/plugins/groups/common/GroupDefaultInfo.ts  |   2 +-
 inlong-dashboard/src/plugins/images/COS.png        | Bin 0 -> 79078 bytes
 inlong-dashboard/src/plugins/images/SQL.png        | Bin 0 -> 152893 bytes
 inlong-dashboard/src/plugins/nodes/defaults/SQL.ts |  61 +++++
 .../src/plugins/nodes/defaults/index.ts            |   5 +
 .../plugins/sources/common/SourceDefaultInfo.ts    |   2 +
 .../src/plugins/sources/defaults/SQL.ts            | 264 +++++++++++++++++++++
 .../src/plugins/sources/defaults/index.ts          |   5 +
 .../src/ui/components/CheckCard/index.tsx          |  29 ++-
 inlong-dashboard/src/ui/locales/cn.json            |   9 +-
 inlong-dashboard/src/ui/locales/en.json            |   9 +-
 12 files changed, 383 insertions(+), 4 deletions(-)

diff --git a/inlong-dashboard/src/core/utils/pattern.ts 
b/inlong-dashboard/src/core/utils/pattern.ts
index 7f4dc7df21..814ff738df 100644
--- a/inlong-dashboard/src/core/utils/pattern.ts
+++ b/inlong-dashboard/src/core/utils/pattern.ts
@@ -28,4 +28,5 @@ export default {
   url: 
/^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i,
   port: 
/^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/,
   version: /^((\d+)\.(\d+)\.(\d+))$/,
+  sql: /^jdbc:(mysql|postgresql|oracle|sqlserver):\/\/[^\/]+(\/.*)?$/,
 };
diff --git a/inlong-dashboard/src/plugins/groups/common/GroupDefaultInfo.ts 
b/inlong-dashboard/src/plugins/groups/common/GroupDefaultInfo.ts
index 47397b9999..9a37f94d48 100644
--- a/inlong-dashboard/src/plugins/groups/common/GroupDefaultInfo.ts
+++ b/inlong-dashboard/src/plugins/groups/common/GroupDefaultInfo.ts
@@ -57,7 +57,7 @@ export class GroupDefaultInfo implements DataWithBackend, 
RenderRow, RenderList
   @FieldDecorator({
     type: 'input',
     props: {
-      maxLength: 32,
+      maxLength: 200,
     },
   })
   @I18n('meta.Group.InlongGroupName')
diff --git a/inlong-dashboard/src/plugins/images/COS.png 
b/inlong-dashboard/src/plugins/images/COS.png
new file mode 100644
index 0000000000..f6d1ce5a66
Binary files /dev/null and b/inlong-dashboard/src/plugins/images/COS.png differ
diff --git a/inlong-dashboard/src/plugins/images/SQL.png 
b/inlong-dashboard/src/plugins/images/SQL.png
new file mode 100644
index 0000000000..2018f6f931
Binary files /dev/null and b/inlong-dashboard/src/plugins/images/SQL.png differ
diff --git a/inlong-dashboard/src/plugins/nodes/defaults/SQL.ts 
b/inlong-dashboard/src/plugins/nodes/defaults/SQL.ts
new file mode 100644
index 0000000000..686e55de35
--- /dev/null
+++ b/inlong-dashboard/src/plugins/nodes/defaults/SQL.ts
@@ -0,0 +1,61 @@
+/*
+ * 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 { NodeInfo } from '../common/NodeInfo';
+import rulesPattern from '@/core/utils/pattern';
+import i18n from '@/i18n';
+
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+
+export default class SQLNode extends NodeInfo implements DataWithBackend, 
RenderRow, RenderList {
+  @FieldDecorator({
+    type: 'input',
+    rules: [
+      {
+        required: true,
+        pattern: rulesPattern.sql,
+        message: i18n.t('meta.SQL.Check'),
+      },
+    ],
+    props: {
+      placeholder:
+        
'jdbc:mysql://xxx.xxx.xxx.xxx:3306/data_base_name?useSSL=false&serverTimezone=UTC',
+    },
+  })
+  @I18n('meta.Nodes.MySQL.Url')
+  url: string;
+
+  @FieldDecorator({
+    type: 'input',
+    rules: [{ required: true }],
+  })
+  @I18n('meta.Nodes.MySQL.Username')
+  username: string;
+
+  @FieldDecorator({
+    type: 'password',
+    rules: [{ required: true }],
+  })
+  @I18n('meta.Nodes.MySQL.Password')
+  token: string;
+}
diff --git a/inlong-dashboard/src/plugins/nodes/defaults/index.ts 
b/inlong-dashboard/src/plugins/nodes/defaults/index.ts
index b25fd69cf9..c2623019c5 100644
--- a/inlong-dashboard/src/plugins/nodes/defaults/index.ts
+++ b/inlong-dashboard/src/plugins/nodes/defaults/index.ts
@@ -106,4 +106,9 @@ export const allDefaultNodes: 
MetaExportWithBackendList<NodeMetaType> = [
     value: 'COS',
     LoadEntity: () => import('./COS'),
   },
+  {
+    label: 'SQL',
+    value: 'SQL',
+    LoadEntity: () => import('./SQL'),
+  },
 ];
diff --git a/inlong-dashboard/src/plugins/sources/common/SourceDefaultInfo.ts 
b/inlong-dashboard/src/plugins/sources/common/SourceDefaultInfo.ts
index b89a77ada9..c396305c15 100644
--- a/inlong-dashboard/src/plugins/sources/common/SourceDefaultInfo.ts
+++ b/inlong-dashboard/src/plugins/sources/common/SourceDefaultInfo.ts
@@ -201,6 +201,7 @@ export class SourceDefaultInfo implements DataWithBackend, 
RenderRow, RenderList
               label: item.label,
               value: item.value,
               image: loadImage(item.label),
+              isSmallImage: item.label === 'SQL' || item.label === 'COS' || 
item.label === 'File',
             })),
         });
       }
@@ -222,6 +223,7 @@ export class SourceDefaultInfo implements DataWithBackend, 
RenderRow, RenderList
               label: item.label,
               value: item.value,
               image: loadImage(item.label),
+              isSmallImage: item.label === 'SQL' || item.label === 'COS' || 
item.label === 'File',
             })),
         });
       }
diff --git a/inlong-dashboard/src/plugins/sources/defaults/SQL.ts 
b/inlong-dashboard/src/plugins/sources/defaults/SQL.ts
new file mode 100644
index 0000000000..b632492f09
--- /dev/null
+++ b/inlong-dashboard/src/plugins/sources/defaults/SQL.ts
@@ -0,0 +1,264 @@
+/*
+ * 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 rulesPattern from '@/core/utils/pattern';
+import { SourceInfo } from '../common/SourceInfo';
+import MultiSelectWithALL, { ALL_OPTION_VALUE } from 
'@/ui/components/MultiSelectWithAll';
+
+const { I18n } = DataWithBackend;
+const { FieldDecorator, IngestionField } = RenderRow;
+const { ColumnDecorator } = RenderList;
+
+export default class SQLSource
+  extends SourceInfo
+  implements DataWithBackend, RenderRow, RenderList
+{
+  @FieldDecorator({
+    type: 'select',
+    rules: [{ required: true }],
+    props: values => ({
+      disabled: Boolean(values.id),
+      showSearch: true,
+      allowClear: true,
+      filterOption: false,
+      options: {
+        requestTrigger: ['onOpen', 'onSearch'],
+        requestService: keyword => ({
+          url: '/cluster/list',
+          method: 'POST',
+          data: {
+            keyword,
+            type: 'AGENT',
+            clusterTag: values.clusterTag,
+            pageNum: 1,
+            pageSize: 10,
+          },
+        }),
+        requestParams: {
+          formatResult: result =>
+            result?.list?.map(item => ({
+              ...item,
+              label: item.displayName,
+              value: item.name,
+            })),
+        },
+      },
+      onChange: (value, option) => {
+        return {
+          clusterId: option.id,
+        };
+      },
+    }),
+  })
+  @ColumnDecorator()
+  @IngestionField()
+  @I18n('meta.Sources.File.ClusterName')
+  inlongClusterName: string;
+
+  @FieldDecorator({
+    type: 'text',
+    hidden: true,
+    props: values => ({
+      disabled: Boolean(values.id),
+    }),
+  })
+  @I18n('clusterId')
+  @IngestionField()
+  clusterId: number;
+
+  @FieldDecorator({
+    type: 'select',
+    rules: [
+      {
+        pattern: rulesPattern.ip,
+        message: i18n.t('meta.Sources.File.IpRule'),
+        required: true,
+      },
+    ],
+    props: values => ({
+      disabled: Boolean(values.id),
+      showSearch: true,
+      allowClear: true,
+      filterOption: false,
+      options: {
+        requestTrigger: ['onOpen', 'onSearch'],
+        requestService: keyword => ({
+          url: '/cluster/node/list',
+          method: 'POST',
+          data: {
+            keyword,
+            parentId: values.clusterId,
+            pageNum: 1,
+            pageSize: 10,
+          },
+        }),
+        requestParams: {
+          formatResult: result =>
+            result?.list?.map(item => ({
+              ...item,
+              label: item.ip,
+              value: item.ip,
+            })),
+        },
+      },
+    }),
+  })
+  @ColumnDecorator()
+  @IngestionField()
+  @I18n('meta.Sources.File.DataSourceIP')
+  agentIp: string;
+
+  @FieldDecorator({
+    type: 'select',
+    rules: [{ required: true }],
+    props: values => ({
+      showSearch: true,
+      allowClear: true,
+      filterOption: false,
+      disabled: [200, 201, 202, 204, 205, 300, 301, 302, 304, 
305].includes(values?.status),
+      options: {
+        requestAuto: true,
+        requestTrigger: ['onOpen', 'onSearch'],
+        requestService: keyword => ({
+          url: '/node/list',
+          method: 'POST',
+          data: {
+            keyword,
+            pageNum: 1,
+            pageSize: 20,
+            type: 'SQL',
+          },
+        }),
+        requestParams: {
+          formatResult: result =>
+            result?.list?.map(item => ({
+              ...item,
+              label: item.displayName,
+              value: item.name,
+            })),
+        },
+      },
+    }),
+  })
+  @IngestionField()
+  @ColumnDecorator()
+  @I18n('meta.Sources.COS.DataNode')
+  dataNodeName: string;
+
+  @FieldDecorator({
+    type: 'input',
+    rules: [{ required: true }],
+    props: values => ({
+      disabled: Boolean(values.id),
+    }),
+  })
+  @IngestionField()
+  @ColumnDecorator()
+  @I18n('meta.Sinks.SQL.Sql')
+  sql: string;
+
+  @FieldDecorator({
+    type: 'radio',
+    initialValue: 'H',
+    props: values => ({
+      disabled: Boolean(values.id),
+      options: [
+        {
+          label: i18n.t('meta.Sources.File.Cycle.Day'),
+          value: 'D',
+        },
+        {
+          label: i18n.t('meta.Sources.File.Cycle.Hour'),
+          value: 'H',
+        },
+      ],
+    }),
+  })
+  @IngestionField()
+  @I18n('meta.Sources.File.Cycle')
+  cycleUnit: string;
+
+  @FieldDecorator({
+    type: 'input',
+    tooltip: i18n.t('meta.Sources.File.TimeOffsetHelp'),
+    initialValue: '-1h',
+    rules: [
+      {
+        pattern: /-?\d+[mhd]$/,
+        required: true,
+        message: i18n.t('meta.Sources.File.TimeOffsetRules'),
+      },
+    ],
+    props: values => ({
+      disabled: Boolean(values.id),
+    }),
+  })
+  @IngestionField()
+  @I18n('meta.Sources.File.TimeOffset')
+  timeOffset: string;
+
+  @FieldDecorator({
+    type: 'inputnumber',
+    rules: [{ required: true }],
+    initialValue: 20,
+    props: values => ({
+      min: 1,
+      max: 100,
+      precision: 0,
+      disabled: Boolean(values.id),
+    }),
+  })
+  @IngestionField()
+  @I18n('meta.Sources.File.MaxFileCount')
+  maxInstanceCount: string;
+
+  @FieldDecorator({
+    type: 'inputnumber',
+    initialValue: 1000,
+    rules: [{ required: true }],
+    props: values => ({
+      disabled: Boolean(values.id),
+    }),
+  })
+  @IngestionField()
+  @I18n('meta.Sinks.SQL.FetchSize')
+  fetchSize: string;
+
+  @FieldDecorator({
+    type: 'select',
+    initialValue: 'GMT+8:00',
+    props: values => ({
+      disabled: Boolean(values.id),
+      options: [
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, -1, -2, -3, -4, -5, -6, 
-7, -8, -9, -10, -11,
+        -12,
+      ].map(item => ({
+        label: Math.sign(item) === 1 || Math.sign(item) === 0 ? 
`GMT+${item}:00` : `GMT${item}:00`,
+        value: Math.sign(item) === 1 || Math.sign(item) === 0 ? 
`GMT+${item}:00` : `GMT${item}:00`,
+      })),
+    }),
+  })
+  @IngestionField()
+  @I18n('meta.Sources.File.TimeZone')
+  dataTimeZone: string;
+}
diff --git a/inlong-dashboard/src/plugins/sources/defaults/index.ts 
b/inlong-dashboard/src/plugins/sources/defaults/index.ts
index 48a4093e52..2c55bd8ee3 100644
--- a/inlong-dashboard/src/plugins/sources/defaults/index.ts
+++ b/inlong-dashboard/src/plugins/sources/defaults/index.ts
@@ -100,4 +100,9 @@ export const allDefaultSources: 
MetaExportWithBackendList<SourceMetaType> = [
     value: 'COS',
     LoadEntity: () => import('./COS'),
   },
+  {
+    label: 'SQL',
+    value: 'SQL',
+    LoadEntity: () => import('./SQL'),
+  },
 ];
diff --git a/inlong-dashboard/src/ui/components/CheckCard/index.tsx 
b/inlong-dashboard/src/ui/components/CheckCard/index.tsx
index 467bc51222..a8a2ecde7d 100644
--- a/inlong-dashboard/src/ui/components/CheckCard/index.tsx
+++ b/inlong-dashboard/src/ui/components/CheckCard/index.tsx
@@ -26,6 +26,7 @@ export interface CheckCardOption {
   label: string;
   value: string | number;
   image?: string | Promise<{ default: string }>;
+  isSmallImage?: boolean;
 }
 
 export interface CheckCardProps {
@@ -45,6 +46,7 @@ const CheckCard: React.FC<CheckCardProps> = ({ options, 
value, onChange, disable
 
   const [isExpand, setExpandStatus] = useState(!Boolean(currentValue));
 
+  const [smallImageMap, setSmallImageMap] = useState({});
   const { token } = useToken();
 
   useEffect(() => {
@@ -77,6 +79,16 @@ const CheckCard: React.FC<CheckCardProps> = ({ options, 
value, onChange, disable
     })();
   }, [options]);
 
+  useEffect(() => {
+    setSmallImageMap(
+      options
+        .filter(item => item.isSmallImage)
+        .reduce((acc, item) => {
+          acc[item.label] = item.isSmallImage;
+          return acc;
+        }, {}),
+    );
+  }, [options]);
   const handleCardClick = newValue => {
     setExpandStatus(false);
     if (newValue !== currentValue) {
@@ -91,7 +103,22 @@ const CheckCard: React.FC<CheckCardProps> = ({ options, 
value, onChange, disable
     <Tooltip placement="top" title={label}>
       <div className={styles.cardInfo}>
         {logoMap[label] ? (
-          <img height="100%" alt={label} src={logoMap[label]}></img>
+          !smallImageMap[label] ? (
+            <img height="100%" alt={label} src={logoMap[label]}></img>
+          ) : (
+            <div
+              style={{
+                display: 'flex',
+                alignItems: 'center',
+                justifyContent: 'center',
+                height: '100%',
+                width: '100%',
+              }}
+            >
+              <img style={{ marginLeft: 10 }} height="100%" alt={label} 
src={logoMap[label]}></img>
+              <span style={{ marginLeft: 10 }}>{label}</span>
+            </div>
+          )
         ) : (
           <>
             <DatabaseOutlined style={{ fontSize: 20 }} />
diff --git a/inlong-dashboard/src/ui/locales/cn.json 
b/inlong-dashboard/src/ui/locales/cn.json
index c49db0d276..598e1be34e 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -1062,5 +1062,12 @@
   "meta.Sinks.DirtyData.DirtyType.FieldMappingError":"字段映射错误",
   "meta.Sinks.DirtyData.DirtyType.LoadError":"加载错误",
   "meta.Sinks.DirtyData.Search.KeyWordHelp":"请输入关键字",
-  "meta.Sinks.DirtyData.Search.KeyWord":"关键字"
+  "meta.Sinks.DirtyData.Search.KeyWord":"关键字",
+  "meta.Sources.COS.DataNode": "数据节点",
+  "meta.Sinks.SQL.Sql": "Sql",
+  "meta.Sinks.SQL.JdbcUrl": "JDBC 地址",
+  "meta.Sinks.SQL.Username": "用户名",
+  "meta.Sinks.SQL.Password": "密码",
+  "meta.Sinks.SQL.FetchSize": "捕获大小",
+  "meta.SQL.Check": "校验失败,请输入正确的SQL连接地址"
 }
diff --git a/inlong-dashboard/src/ui/locales/en.json 
b/inlong-dashboard/src/ui/locales/en.json
index b87b2c15c8..25878c36ee 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -1056,5 +1056,12 @@
   "meta.Sinks.DirtyData.DirtyType.FieldMappingError":"Field Mapping Error",
   "meta.Sinks.DirtyData.DirtyType.LoadError":"Load Error",
   "meta.Sinks.DirtyData.Search.KeyWordHelp":"Please enter a keyword",
-  "meta.Sinks.DirtyData.Search.KeyWord":"Key word"
+  "meta.Sinks.DirtyData.Search.KeyWord":"Key word",
+  "meta.Sources.COS.DataNode": "Data node",
+  "meta.Sinks.SQL.Sql": "Sql",
+  "meta.Sinks.SQL.JdbcUrl": "JDBC url",
+  "meta.Sinks.SQL.Username": "username",
+  "meta.Sinks.SQL.Password": "password",
+  "meta.Sinks.SQL.FetchSize": "Fetch size",
+  "meta.SQL.Check": "Check failed, please enter the correct SQL connection 
address"
 }

Reply via email to