From bf441bafce2835b44c7904213987bed63f89b8d7 Mon Sep 17 00:00:00 2001 From: wohainilaodou Date: Tue, 8 Oct 2024 14:20:05 +0800 Subject: [PATCH] [INLONG-11178][Dashboard] Added http sink and http node --- .../src/plugins/clusters/defaults/SortHttp.ts | 8 + .../src/plugins/clusters/defaults/index.ts | 5 + .../src/plugins/nodes/defaults/Http.ts | 79 ++++++++ .../src/plugins/nodes/defaults/index.ts | 5 + .../src/plugins/sinks/defaults/Http.ts | 186 ++++++++++++++++++ .../src/plugins/sinks/defaults/index.ts | 5 + inlong-dashboard/src/ui/locales/cn.json | 11 ++ inlong-dashboard/src/ui/locales/en.json | 11 ++ .../src/ui/pages/Clusters/CreateModal.tsx | 3 +- 9 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 inlong-dashboard/src/plugins/clusters/defaults/SortHttp.ts create mode 100644 inlong-dashboard/src/plugins/nodes/defaults/Http.ts create mode 100644 inlong-dashboard/src/plugins/sinks/defaults/Http.ts diff --git a/inlong-dashboard/src/plugins/clusters/defaults/SortHttp.ts b/inlong-dashboard/src/plugins/clusters/defaults/SortHttp.ts new file mode 100644 index 00000000000..8547f14d0e6 --- /dev/null +++ b/inlong-dashboard/src/plugins/clusters/defaults/SortHttp.ts @@ -0,0 +1,8 @@ +import { ClusterInfo } from '@/plugins/clusters/common/ClusterInfo'; +import { DataWithBackend } from '@/plugins/DataWithBackend'; +import { RenderRow } from '@/plugins/RenderRow'; +import { RenderList } from '@/plugins/RenderList'; + +export default class SortHttp + extends ClusterInfo + implements DataWithBackend, RenderRow, RenderList {} diff --git a/inlong-dashboard/src/plugins/clusters/defaults/index.ts b/inlong-dashboard/src/plugins/clusters/defaults/index.ts index 1c5e49bb493..0c947572b5b 100644 --- a/inlong-dashboard/src/plugins/clusters/defaults/index.ts +++ b/inlong-dashboard/src/plugins/clusters/defaults/index.ts @@ -71,4 +71,9 @@ export const allDefaultClusters: MetaExportWithBackendList = [ value: 'SORT_KAFKA', LoadEntity: () => import('./SortKafka'), }, + { + label: 'Sort Http', + value: 'SORT_HTTP', + LoadEntity: () => import('./SortHttp'), + }, ]; diff --git a/inlong-dashboard/src/plugins/nodes/defaults/Http.ts b/inlong-dashboard/src/plugins/nodes/defaults/Http.ts new file mode 100644 index 00000000000..80173941439 --- /dev/null +++ b/inlong-dashboard/src/plugins/nodes/defaults/Http.ts @@ -0,0 +1,79 @@ +/* + * 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 i18n from 'i18next'; +import { Input } from 'antd'; + +const { I18n } = DataWithBackend; +const { FieldDecorator } = RenderRow; + +export default class HttpNodeInfo + extends NodeInfo + implements DataWithBackend, RenderRow, RenderList +{ + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + }) + @I18n('meta.Nodes.Http.BaseUrl') + baseUrl: string; + + @FieldDecorator({ + type: 'radio', + rules: [{ required: true }], + initialValue: false, + props: { + options: [ + { + label: i18n.t('basic.Yes'), + value: true, + }, + { + label: i18n.t('basic.No'), + value: false, + }, + ], + }, + }) + @I18n('meta.Nodes.Http.EnableCredential') + enableCredential: string; + + @FieldDecorator({ + type: 'input', + }) + @I18n('meta.Nodes.Http.Username') + username: string; + + @FieldDecorator({ + type: Input.Password, + }) + @I18n('meta.Nodes.Http.Password') + password: string; + + @FieldDecorator({ + type: 'inputnumber', + initialValue: 1000, + }) + @I18n('meta.Nodes.Http.MaxConnect') + maxConnect: number; +} diff --git a/inlong-dashboard/src/plugins/nodes/defaults/index.ts b/inlong-dashboard/src/plugins/nodes/defaults/index.ts index 06209fd9409..858ac347ebf 100644 --- a/inlong-dashboard/src/plugins/nodes/defaults/index.ts +++ b/inlong-dashboard/src/plugins/nodes/defaults/index.ts @@ -96,4 +96,9 @@ export const allDefaultNodes: MetaExportWithBackendList = [ value: 'KUDU', LoadEntity: () => import('./Kudu'), }, + { + label: 'Http', + value: 'HTTP', + LoadEntity: () => import('./Http'), + }, ]; diff --git a/inlong-dashboard/src/plugins/sinks/defaults/Http.ts b/inlong-dashboard/src/plugins/sinks/defaults/Http.ts new file mode 100644 index 00000000000..2dce2466da4 --- /dev/null +++ b/inlong-dashboard/src/plugins/sinks/defaults/Http.ts @@ -0,0 +1,186 @@ +/* + * 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 { SinkInfo } from '@/plugins/sinks/common/SinkInfo'; +import { RenderList } from '@/plugins/RenderList'; +import NodeSelect from '@/ui/components/NodeSelect'; +import i18n from '@/i18n'; +import EditableTable from '@/ui/components/EditableTable'; +import { + fieldTypes, + fieldTypes as sourceFieldsTypes, + sourceFields, +} from '@/plugins/sinks/common/sourceFields'; + +const { I18n } = DataWithBackend; +const { FieldDecorator, SyncField, SyncCreateTableField, IngestionField } = RenderRow; +const { ColumnDecorator } = RenderList; + +export default class HttpSinkInfo + extends SinkInfo + implements DataWithBackend, RenderRow, RenderList +{ + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + props: values => ({ + disabled: [110].includes(values?.status), + }), + }) + @SyncField() + @I18n('meta.Sinks.Http.Path') + @IngestionField() + path: string; + + @FieldDecorator({ + type: 'radio', + initialValue: 'GET', + rules: [{ required: true }], + props: values => ({ + options: [ + { + label: 'GET', + value: 'GET', + }, + { + label: 'POST', + value: 'POST', + }, + ], + }), + }) + @SyncField() + @IngestionField() + @I18n('meta.Sinks.Http.Method') + method: string; + + @FieldDecorator({ + type: NodeSelect, + rules: [{ required: true }], + props: values => ({ + disabled: [110].includes(values?.status), + nodeType: 'HTTP', + }), + }) + @I18n('meta.Sinks.DataNodeName') + @SyncField() + @IngestionField() + @ColumnDecorator() + dataNodeName: string; + + @FieldDecorator({ + type: EditableTable, + props: values => ({ + size: 'small', + canDelete: record => !(record.id && [110].includes(values?.status)), + canBatchAdd: true, + columns: [ + { + title: i18n.t('meta.Sinks.Http.FieldName'), + dataIndex: 'fieldName', + props: (text, record) => ({ + disabled: record.id && [110].includes(values?.status), + }), + }, + { + title: i18n.t('meta.Sinks.Http.FieldValue'), + dataIndex: 'fieldValue', + type: 'input', + }, + ], + }), + }) + @SyncField() + @IngestionField() + @I18n('meta.Sinks.Http.Headers') + headers: Record[]; + + @FieldDecorator({ + type: 'inputnumber', + initialValue: '0', + }) + @SyncField() + @IngestionField() + @I18n('meta.Sinks.Http.MaxRetryTimes') + maxRetryTimes: number; + @FieldDecorator({ + type: EditableTable, + props: values => ({ + size: 'small', + editing: ![110].includes(values?.status), + columns: getFieldListColumns(values), + canBatchAdd: true, + upsertByFieldKey: true, + }), + }) + @IngestionField() + sinkFieldList: Record[]; +} + +const getFieldListColumns = sinkValues => { + return [ + ...sourceFields, + { + title: i18n.t('meta.Sinks.SinkFieldName'), + dataIndex: 'fieldName', + initialValue: '', + rules: [ + { required: true }, + { + pattern: /^[a-zA-Z_][0-9a-z_]*$/, + message: i18n.t('meta.Sinks.SinkFieldNameRule'), + }, + ], + props: (text, record, idx, isNew) => ({ + disabled: [110].includes(sinkValues?.status as number) && !isNew, + }), + }, + { + title: i18n.t('meta.Sinks.SinkFieldType'), + dataIndex: 'fieldType', + initialValue: fieldTypes[0].value, + type: 'select', + props: (text, record, idx, isNew) => ({ + options: fieldTypes, + disabled: [110].includes(sinkValues?.status as number) && !isNew, + }), + rules: [{ required: true, message: `${i18n.t('meta.Sinks.FieldTypeMessage')}` }], + }, + { + title: i18n.t('meta.Sinks.Redis.FieldFormat'), + dataIndex: 'fieldFormat', + initialValue: 0, + type: 'autocomplete', + props: (text, record, idx, isNew) => ({ + options: ['MICROSECONDS', 'MILLISECONDS', 'SECONDS', 'SQL', 'ISO_8601'].map(item => ({ + label: item, + value: item, + })), + }), + visible: (text, record) => ['BIGINT', 'DATE'].includes(record.fieldType as string), + }, + { + title: i18n.t('meta.Sinks.FieldDescription'), + dataIndex: 'fieldComment', + initialValue: '', + }, + ]; +}; diff --git a/inlong-dashboard/src/plugins/sinks/defaults/index.ts b/inlong-dashboard/src/plugins/sinks/defaults/index.ts index 0fe788c1034..c916f8dcabb 100644 --- a/inlong-dashboard/src/plugins/sinks/defaults/index.ts +++ b/inlong-dashboard/src/plugins/sinks/defaults/index.ts @@ -126,4 +126,9 @@ export const allDefaultSinks: MetaExportWithBackendList = [ value: 'KUDU', LoadEntity: () => import('./Kudu'), }, + { + label: 'Http', + value: 'HTTP', + LoadEntity: () => import('./Http'), + }, ]; diff --git a/inlong-dashboard/src/ui/locales/cn.json b/inlong-dashboard/src/ui/locales/cn.json index b97e66643b5..d68a58001c8 100644 --- a/inlong-dashboard/src/ui/locales/cn.json +++ b/inlong-dashboard/src/ui/locales/cn.json @@ -171,6 +171,12 @@ "meta.Sinks.EnableCreateResource": "是否创建资源", "meta.Sinks.EnableCreateResourceHelp": "如果库表已经存在,且无需修改,则选【不创建】,否则请选择【创建】,由系统自动创建资源。", "meta.Sinks.DataNodeName": "数据节点", + "meta.Sinks.Http.MaxRetryTimes": "最大重试次数", + "meta.Sinks.Http.Headers": "请求头", + "meta.Sinks.Http.Method": "请求方式", + "meta.Sinks.Http.Path": "请求路径", + "meta.Sinks.Http.FieldName": "参数名", + "meta.Sinks.Http.FieldValue": "参数值", "meta.Sinks.FieldTypeMessage": "请输入字段类型", "meta.Sinks.Hive.FileFormat": "落地格式", "meta.Sinks.Hive.Day": "天", @@ -532,6 +538,11 @@ "meta.Nodes.StarRocks.Url": "地址", "meta.Nodes.Hive.Username": "用户名", "meta.Nodes.Hive.Password": "密码", + "meta.Nodes.Http.Username": "用户名", + "meta.Nodes.Http.Password": "密码", + "meta.Nodes.Http.BaseUrl": "基础路径", + "meta.Nodes.Http.EnableCredential": "启用凭证", + "meta.Nodes.Http.MaxConnect": "最大连接数", "meta.Nodes.Hudi.Username": "用户名", "meta.Nodes.Hudi.Password": "密码", "meta.Nodes.Hudi.Url": "地址", diff --git a/inlong-dashboard/src/ui/locales/en.json b/inlong-dashboard/src/ui/locales/en.json index ebf2bbfcb1a..144e15e37d9 100644 --- a/inlong-dashboard/src/ui/locales/en.json +++ b/inlong-dashboard/src/ui/locales/en.json @@ -171,6 +171,12 @@ "meta.Sinks.EnableCreateResource": "Create resource", "meta.Sinks.EnableCreateResourceHelp": "If the library table already exists and does not need to be modified, select [Do not create], otherwise select [Create], and the system will automatically create the resource.", "meta.Sinks.DataNodeName": "Data node", + "meta.Sinks.Http.MaxRetryTimes": "Max Retry Times", + "meta.Sinks.Http.Headers": "Headers", + "meta.Sinks.Http.Method": "Method", + "meta.Sinks.Http.Path": "Path", + "meta.Sinks.Http.FieldName": "Field Name", + "meta.Sinks.Http.FieldValue": "Field Value", "meta.Sinks.FieldTypeMessage": "Please enter field type", "meta.Sinks.Hive.FileFormat": "File format", "meta.Sinks.Hive.Day": "Day(s)", @@ -542,6 +548,11 @@ "meta.Nodes.Redis.ClusterMode": "Cluster mode", "meta.Nodes.Redis.ClusterModeHelper": "Please select custer mode", "meta.Nodes.Redis.PortHelper": "Please select Redis server port, default: 6379", + "meta.Nodes.Http.Username": "Username", + "meta.Nodes.Http.Password": "Password", + "meta.Nodes.Http.BaseUrl": "Base url", + "meta.Nodes.Http.EnableCredential": "EnableCredential", + "meta.Nodes.Http.MaxConnect": "MaxConnect", "meta.Nodes.Hudi.Username": "Username", "meta.Nodes.Hudi.Password": "Password", "meta.Nodes.Hudi.Url": "URL", diff --git a/inlong-dashboard/src/ui/pages/Clusters/CreateModal.tsx b/inlong-dashboard/src/ui/pages/Clusters/CreateModal.tsx index bf5645c58cb..988f41613cd 100644 --- a/inlong-dashboard/src/ui/pages/Clusters/CreateModal.tsx +++ b/inlong-dashboard/src/ui/pages/Clusters/CreateModal.tsx @@ -71,7 +71,8 @@ const Comp: React.FC = ({ id, defaultType, ...modalProps }) => { values.type === 'SORT_ES' || values.type === 'SORT_CKAFKA' || values.type === 'SORT_KAFKA' || - values.type === 'SORT_PULSAR' + values.type === 'SORT_PULSAR' || + values.type === 'SORT_HTTP' ) { submitData.name = values.displayName; }