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 <[email protected]>
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"
}