diff --git a/packages/base/src/locale/zh-CN/dmsDataSource.ts b/packages/base/src/locale/zh-CN/dmsDataSource.ts
index 1d01c5bc91..bc4515174f 100644
--- a/packages/base/src/locale/zh-CN/dmsDataSource.ts
+++ b/packages/base/src/locale/zh-CN/dmsDataSource.ts
@@ -50,6 +50,9 @@ export default {
name: '数据源名称',
describe: '数据源描述',
type: '数据源类型',
+ connectionMode: '连接模式',
+ connectionModeStandalone: '单实例',
+ connectionModeCluster: 'Cluster',
ip: '数据源地址',
ipTips: '数据源IP或域名',
port: '数据源端口',
@@ -104,6 +107,28 @@ export default {
lineNumberLimit: '回滚行数限制',
lineNumberLimitTips: '当预计影响行数超过指定值则不回滚',
dataSourceConnectError: '数据源连通性测试失败',
+ mongoTopology: 'MongoDB 形态',
+ mongoTopologyTips:
+ '按 MongoDB 部署拓扑选择(Standalone / Replica set / Sharded cluster);选错会导致测连失败。',
+ mongoTopologySingle: '单机',
+ mongoTopologyReplicaSet: '副本集',
+ mongoTopologyShard: '分片集群',
+ mongoSingleTips:
+ '填写单个 standalone 实例地址与端口;无需副本集名称与成员地址',
+ mongoReplicaSetTips:
+ '在「副本集成员地址」填写多个 host:port;基础地址取首个成员;需填副本集名称',
+ mongoShardTips:
+ '只填单个 mongos 地址与端口;勿填 configsvr、shard 数据节点或副本集多成员',
+ mongoAuthSource: '认证数据库',
+ mongoAuthSourceTips: 'MongoDB 认证库名,通常为 admin',
+ mongoReplicaSet: '副本集名称',
+ mongoReplicaSetNameTips: '仅副本集形态需要填写',
+ mongoSeedHosts: '副本集成员地址',
+ mongoSeedHostsTips: '多个成员使用英文逗号或换行分隔',
+ mongoSeedHostsPlaceholder:
+ '例如:192.0.2.10:27017,192.0.2.11:27017,192.0.2.12:27017',
+ mongoSeedHostsRule:
+ '请填写 host:port 格式的成员地址,多个成员用英文逗号或换行分隔',
returnModify: '返回修改',
continueSubmit: '继续提交'
},
diff --git a/packages/base/src/page/DataSource/components/AddDataSource/__snapshots__/index.test.tsx.snap b/packages/base/src/page/DataSource/components/AddDataSource/__snapshots__/index.test.tsx.snap
index 7c86bca849..9568e78989 100644
--- a/packages/base/src/page/DataSource/components/AddDataSource/__snapshots__/index.test.tsx.snap
+++ b/packages/base/src/page/DataSource/components/AddDataSource/__snapshots__/index.test.tsx.snap
@@ -803,26 +803,30 @@ exports[`page/DataSource/AddDataSource render add database snap 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -1170,6 +1174,22 @@ exports[`page/DataSource/AddDataSource render add database snap 1`] = `
exports[`page/DataSource/AddDataSource render conenctable modal when current service can not connect 1`] = `
+
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
diff --git a/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx b/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx
index 8b717a3ec3..eb2cf2e271 100644
--- a/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx
+++ b/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx
@@ -35,6 +35,39 @@ describe('page/DataSource/AddDataSource', () => {
return baseSuperRender();
};
+ const mockMongoDriverOption = () => {
+ baseMockApi.global.getListDBServiceDriverOption().mockImplementation(() =>
+ createSpySuccessResponse({
+ data: [
+ {
+ db_type: 'MongoDB',
+ logo_path: 'logo_path_mock',
+ params: [
+ {
+ description: 'MongoDB authentication database',
+ name: 'auth_source',
+ type: 'string',
+ value: 'admin'
+ },
+ {
+ description: 'Replica set name',
+ name: 'replica_set',
+ type: 'string',
+ value: ''
+ },
+ {
+ description: 'MongoDB seed hosts',
+ name: 'seed_hosts',
+ type: 'string',
+ value: ''
+ }
+ ]
+ }
+ ]
+ })
+ );
+ };
+
beforeEach(() => {
jest.useFakeTimers();
(useNavigate as jest.Mock).mockImplementation(() => navigateSpy);
@@ -237,6 +270,157 @@ describe('page/DataSource/AddDataSource', () => {
expect(screen.getByText('添加数据源')).toBeInTheDocument();
});
+ it.each([
+ {
+ topology: '单机',
+ name: 'mongo-single-source',
+ host: '192.0.2.10',
+ port: '27017',
+ expectedParams: [
+ { name: 'auth_source', value: 'admin' },
+ { name: 'replica_set', value: '' },
+ { name: 'topology', value: 'single' }
+ ]
+ },
+ {
+ topology: '副本集',
+ name: 'mongo-replica-source',
+ host: '192.0.2.10',
+ port: '27017',
+ replicaSet: 'rs0',
+ seedHosts: '192.0.2.10:27017,27018,27019',
+ expectedParams: [
+ { name: 'auth_source', value: 'admin' },
+ { name: 'replica_set', value: 'rs0' },
+ {
+ name: 'seed_hosts',
+ value: '192.0.2.10:27017,192.0.2.10:27018,192.0.2.10:27019'
+ },
+ { name: 'topology', value: 'replicaSet' }
+ ]
+ },
+ {
+ topology: '分片集群',
+ name: 'mongo-shard-source',
+ host: '192.0.2.20',
+ port: '27017',
+ expectedParams: [
+ { name: 'auth_source', value: 'admin' },
+ { name: 'replica_set', value: '' },
+ { name: 'topology', value: 'shard' }
+ ]
+ }
+ ])(
+ 'submits MongoDB $topology to v2 create API with expected params',
+ async ({
+ topology,
+ name,
+ host,
+ port,
+ replicaSet,
+ seedHosts,
+ expectedParams
+ }) => {
+ mockMongoDriverOption();
+ const { baseElement } = customRender();
+ await act(async () => jest.advanceTimersByTime(9300));
+
+ fireEvent.change(getBySelector('#name', baseElement), {
+ target: { value: name }
+ });
+ await act(async () => jest.advanceTimersByTime(300));
+
+ fireEvent.mouseDown(getBySelector('#type', baseElement));
+ await act(async () => jest.advanceTimersByTime(300));
+ fireEvent.click(getBySelector('span[title="MongoDB"]', baseElement));
+ await act(async () => jest.advanceTimersByTime(3000));
+
+ if (topology !== '单机') {
+ fireEvent.mouseDown(getBySelector('#mongoTopology', baseElement));
+ await act(async () => jest.advanceTimersByTime(300));
+ fireEvent.click(screen.getByText(topology));
+ await act(async () => jest.advanceTimersByTime(300));
+ }
+
+ fireEvent.change(getBySelector('#ip', baseElement), {
+ target: { value: host }
+ });
+ fireEvent.change(getBySelector('#port', baseElement), {
+ target: { value: port }
+ });
+ fireEvent.change(getBySelector('#user', baseElement), {
+ target: { value: 'mongo' }
+ });
+ fireEvent.change(getBySelector('#password', baseElement), {
+ target: { value: 'Mongo@123' }
+ });
+ fireEvent.change(getBySelector('#params_auth_source', baseElement), {
+ target: { value: 'admin' }
+ });
+
+ if (replicaSet) {
+ fireEvent.change(getBySelector('#params_replica_set', baseElement), {
+ target: { value: replicaSet }
+ });
+ }
+ if (seedHosts) {
+ fireEvent.change(getBySelector('#params_seed_hosts', baseElement), {
+ target: { value: seedHosts }
+ });
+ fireEvent.blur(getBySelector('#params_seed_hosts', baseElement));
+ } else {
+ expect(
+ baseElement.querySelector('#params_seed_hosts')
+ ).not.toBeInTheDocument();
+ }
+ await act(async () => jest.advanceTimersByTime(300));
+
+ fireEvent.click(getBySelector('.editable-select-trigger', baseElement));
+ await act(async () => jest.advanceTimersByTime(0));
+ fireEvent.click(getAllBySelector('.ant-dropdown-menu-item')[0]);
+ await act(async () => jest.advanceTimersByTime(0));
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('提 交'));
+ });
+ expect(checkDbServiceIsConnectableSpy).toHaveBeenCalledTimes(1);
+ await act(async () => jest.advanceTimersByTime(3000));
+
+ expect(requestAddDBServiceSpy).toHaveBeenCalledWith({
+ db_service: {
+ additional_params: expectedParams,
+ db_type: 'MongoDB',
+ desc: undefined,
+ environment_tag_uid: '1',
+ host,
+ maintenance_times: [],
+ name,
+ password: 'Mongo@123',
+ port,
+ sqle_config: {
+ audit_enabled: undefined,
+ data_export_rule_template_id: undefined,
+ data_export_rule_template_name: undefined,
+ rule_template_id: undefined,
+ rule_template_name: undefined,
+ sql_query_config: {
+ allow_query_when_less_than_audit_level: undefined,
+ audit_enabled: undefined,
+ maintenance_times: [],
+ rule_template_id: undefined,
+ rule_template_name: undefined,
+ workflow_exec_enabled: undefined
+ }
+ },
+ user: 'mongo',
+ enable_backup: undefined,
+ backup_max_rows: undefined
+ },
+ project_uid: projectID
+ });
+ }
+ );
+
it('render conenctable modal when current service can not connect', async () => {
checkDbServiceIsConnectableSpy.mockImplementation(() =>
createSpySuccessResponse({
diff --git a/packages/base/src/page/DataSource/components/Form/FormItem/index.tsx b/packages/base/src/page/DataSource/components/Form/FormItem/index.tsx
index 6f83db2d0a..351fe7c80c 100644
--- a/packages/base/src/page/DataSource/components/Form/FormItem/index.tsx
+++ b/packages/base/src/page/DataSource/components/Form/FormItem/index.tsx
@@ -1,7 +1,7 @@
import { useBoolean } from 'ahooks';
-import { useEffect, useState, useContext } from 'react';
+import { useEffect, useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { FormInstance } from 'antd';
+import { Form, FormInstance } from 'antd';
import EmitterKey from '../../../../../data/EmitterKey';
import EventEmitter from '../../../../../utils/EventEmitter';
import { DataSourceFormField } from '../index.type';
@@ -12,13 +12,30 @@ import {
EmptyBox,
TestDatabaseConnectButton
} from '@actiontech/dms-kit';
-import { FormItemLabel, FormItemNoLabel } from '@actiontech/dms-kit';
+import {
+ CustomLabelContent,
+ FormItemLabel,
+ FormItemNoLabel
+} from '@actiontech/dms-kit';
import { validatorPort } from '@actiontech/dms-kit';
import {
AutoCreatedFormItemByApi,
BackendFormItemParams
} from '@actiontech/shared';
import { DataSourceFormContext } from '../../../context';
+import {
+ DEFAULT_MONGO_TOPOLOGY,
+ DEFAULT_REDIS_CONNECTION_MODE,
+ isRedisDbType,
+ MONGODB_AUTH_SOURCE_PARAM,
+ MONGODB_MAIN_PARAMS,
+ MONGODB_REPLICA_SET_PARAM,
+ MONGODB_SEED_HOSTS_PARAM,
+ MONGODB_TOPOLOGY_PARAM,
+ normalizeMongoSeedHosts,
+ validateMongoSeedHosts
+} from '../../../tool';
+
const DatabaseFormItem: React.FC<{
form: FormInstance;
isUpdate?: boolean;
@@ -26,14 +43,130 @@ const DatabaseFormItem: React.FC<{
generateDriverSelectOptions?: () => JSX.Element[];
updateDriverListLoading: boolean;
currentAsyncParams?: BackendFormItemParams[];
+ databaseType?: string;
isExternalInstance?: boolean;
}> = (props) => {
const { t } = useTranslation();
const formContext = useContext(DataSourceFormContext);
+ const databaseType = Form.useWatch('type', props.form);
+ const connectionMode = Form.useWatch('connectionMode', props.form);
+ const isRedis = isRedisDbType(databaseType);
const [
hideConnectionInfo,
{ setFalse: setConnectionInfoShow, setTrue: setConnectionInfoHide }
] = useBoolean(true);
+ const isMongoDB = props.databaseType?.toLowerCase() === 'mongodb';
+ const mongoTopology = Form.useWatch('mongoTopology', props.form);
+ const mongodbAsyncParams = useMemo(() => {
+ return props.currentAsyncParams ?? [];
+ }, [props.currentAsyncParams]);
+ const seedHostsParam = useMemo(() => {
+ return mongodbAsyncParams.find(
+ (item) => item.key === MONGODB_SEED_HOSTS_PARAM
+ );
+ }, [mongodbAsyncParams]);
+ const replicaSetParam = useMemo(() => {
+ return mongodbAsyncParams.find(
+ (item) => item.key === MONGODB_REPLICA_SET_PARAM
+ );
+ }, [mongodbAsyncParams]);
+
+ const mongoParamDescMap = useMemo>(
+ () => ({
+ [MONGODB_AUTH_SOURCE_PARAM]: `${t(
+ 'dmsDataSource.dataSourceForm.mongoAuthSource'
+ )}(${t('dmsDataSource.dataSourceForm.mongoAuthSourceTips')})`,
+ [MONGODB_REPLICA_SET_PARAM]: `${t(
+ 'dmsDataSource.dataSourceForm.mongoReplicaSet'
+ )}(${t('dmsDataSource.dataSourceForm.mongoReplicaSetNameTips')})`
+ }),
+ [t]
+ );
+
+ const withMongoChineseDesc = (
+ items: BackendFormItemParams[]
+ ): BackendFormItemParams[] =>
+ items.map((item) => {
+ const key = item.key ?? '';
+ const desc = mongoParamDescMap[key];
+ return desc ? { ...item, desc } : item;
+ });
+
+ const mongoHandledKeys = useMemo(
+ () =>
+ new Set([
+ ...MONGODB_MAIN_PARAMS,
+ MONGODB_REPLICA_SET_PARAM,
+ MONGODB_SEED_HOSTS_PARAM,
+ MONGODB_TOPOLOGY_PARAM
+ ]),
+ []
+ );
+
+ const mongoMainParams = useMemo(() => {
+ if (!isMongoDB) {
+ return [];
+ }
+ return withMongoChineseDesc(
+ mongodbAsyncParams.filter((item) =>
+ (MONGODB_MAIN_PARAMS as readonly string[]).includes(item.key ?? '')
+ )
+ );
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isMongoDB, mongodbAsyncParams, mongoParamDescMap]);
+
+ const autoCreatedAsyncParams = useMemo(() => {
+ if (!isMongoDB) {
+ return mongodbAsyncParams;
+ }
+ return mongodbAsyncParams.filter(
+ (item) => !mongoHandledKeys.has(item.key ?? '')
+ );
+ }, [isMongoDB, mongodbAsyncParams, mongoHandledKeys]);
+
+ const mergeMongoTopologyParams = (
+ topology: DataSourceFormField['mongoTopology'],
+ paramsValue: Record
+ ) => {
+ const nextParams: Record = {
+ ...paramsValue,
+ [MONGODB_SEED_HOSTS_PARAM]:
+ topology === 'replicaSet' ? paramsValue[MONGODB_SEED_HOSTS_PARAM] : ''
+ };
+
+ if (topology === 'single' || topology === 'shard') {
+ nextParams.replica_set = '';
+ }
+
+ return nextParams;
+ };
+ const changeMongoTopology = (value: string | number) => {
+ const nextTopology = value as DataSourceFormField['mongoTopology'];
+ const paramsValue = (props.form.getFieldValue('params') ?? {}) as Record<
+ string,
+ string | boolean | undefined
+ >;
+
+ props.form.setFieldsValue({
+ mongoTopology: nextTopology,
+ params: mergeMongoTopologyParams(nextTopology, paramsValue)
+ });
+ };
+ const formatSeedHosts = () => {
+ const paramsValue = (props.form.getFieldValue('params') ?? {}) as Record<
+ string,
+ string | boolean | undefined
+ >;
+ const seedHosts = paramsValue[MONGODB_SEED_HOSTS_PARAM];
+ if (typeof seedHosts === 'string') {
+ props.form.setFieldsValue({
+ params: {
+ ...paramsValue,
+ [MONGODB_SEED_HOSTS_PARAM]: normalizeMongoSeedHosts(seedHosts)
+ }
+ });
+ }
+ };
const [needUpdatePassword, setNeedUpdatePassword] = useState(false);
const changeNeedUpdatePassword = (check: boolean) => {
setNeedUpdatePassword(check);
@@ -62,6 +195,7 @@ const DatabaseFormItem: React.FC<{
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+
return (
<>
+
+
+
+
+
- 0}>
+
+
+ }
+ name="mongoTopology"
+ initialValue={DEFAULT_MONGO_TOPOLOGY}
+ >
+
+
+
+
+ {mongoTopology === 'replicaSet'
+ ? t('dmsDataSource.dataSourceForm.mongoReplicaSetTips')
+ : mongoTopology === 'shard'
+ ? t('dmsDataSource.dataSourceForm.mongoShardTips')
+ : t('dmsDataSource.dataSourceForm.mongoSingleTips')}
+
+
+
+
+ 0}>
+
+
+
+
+
+
+
+
+
+ }
+ name={['params', MONGODB_SEED_HOSTS_PARAM]}
+ rules={[
+ {
+ required: true,
+ message: t('common.form.rule.require', {
+ name: t('dmsDataSource.dataSourceForm.mongoSeedHosts')
+ })
+ },
+ {
+ validator: (_, value) => {
+ if (validateMongoSeedHosts(value)) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ t('dmsDataSource.dataSourceForm.mongoSeedHostsRule')
+ )
+ );
+ }
+ }
+ ]}
+ >
+
+
+
+
+ 0}>
diff --git a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/ConfirmSwitch.tsx b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/ConfirmSwitch.tsx
index 2bf672bd04..8a8f7087dc 100644
--- a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/ConfirmSwitch.tsx
+++ b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/ConfirmSwitch.tsx
@@ -26,6 +26,11 @@ const ConfirmSwitch: React.FC = ({
onChange?.(value);
}
};
+ const onTriggerClick = () => {
+ if (checked) {
+ setAuditRequiredPopupVisible(true);
+ }
+ };
const onInnerConfirm = () => {
onChange?.(false);
onConfirm?.();
@@ -38,11 +43,16 @@ const ConfirmSwitch: React.FC = ({
onOpenChange={onOpenChange}
onConfirm={onInnerConfirm}
>
-
+
+
+
);
};
diff --git a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/__tests__/__snapshots__/index.test.tsx.snap b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/__tests__/__snapshots__/index.test.tsx.snap
index 704b65bf4e..a908a97685 100644
--- a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/__tests__/__snapshots__/index.test.tsx.snap
+++ b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/__tests__/__snapshots__/index.test.tsx.snap
@@ -71,26 +71,30 @@ exports[`SqlAuditFields should match snapshot when needSqlAuditService and needA
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -884,26 +888,30 @@ exports[`SqlAuditFields should match snapshot when needSqlAuditService is false
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -986,26 +994,30 @@ exports[`SqlAuditFields should match snapshot when needSqlAuditService is true 1
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
diff --git a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx
index 417118f15d..7c4f21f93b 100644
--- a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx
+++ b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx
@@ -45,8 +45,8 @@ const SqlAuditFields: React.FC = ({
}) => {
const { t } = useTranslation();
const form = Form.useFormInstance();
- const needSqlAuditService = Form.useWatch('needSqlAuditService');
- const needAuditForSqlQuery = Form.useWatch('needAuditForSqlQuery');
+ const needSqlAuditService = Form.useWatch('needSqlAuditService', form);
+ const needAuditForSqlQuery = Form.useWatch('needAuditForSqlQuery', form);
const changeRuleTemplate = (
value: string,
option: BaseOptionType,
@@ -97,7 +97,7 @@ const SqlAuditFields: React.FC = ({
className="has-required-style has-label-tip"
rules={[
{
- required: true,
+ required: !!needSqlAuditService,
message: t('common.form.placeholder.select', {
name: t('dmsDataSource.dataSourceForm.ruleTemplate')
})
@@ -136,7 +136,7 @@ const SqlAuditFields: React.FC = ({
className="has-required-style has-label-tip"
rules={[
{
- required: true,
+ required: !!needSqlAuditService,
message: t('common.form.placeholder.select', {
name: t(
'dmsDataSource.dataSourceForm.dataExportAuditRuleTemplate'
@@ -196,7 +196,8 @@ const SqlAuditFields: React.FC = ({
name="workbenchTemplateName"
rules={[
{
- required: true
+ required:
+ !!needSqlAuditService && !!needAuditForSqlQuery
}
]}
className="has-required-style"
@@ -230,7 +231,8 @@ const SqlAuditFields: React.FC = ({
name="allowQueryWhenLessThanAuditLevel"
rules={[
{
- required: true,
+ required:
+ !!needSqlAuditService && !!needAuditForSqlQuery,
message: t('common.form.placeholder.select', {
name: t(
'dmsDataSource.dataSourceForm.allowQueryWhenLessThanAuditLevel'
diff --git a/packages/base/src/page/DataSource/components/Form/__snapshots__/index.ce.test.tsx.snap b/packages/base/src/page/DataSource/components/Form/__snapshots__/index.ce.test.tsx.snap
index 42f4be71be..455f897e69 100644
--- a/packages/base/src/page/DataSource/components/Form/__snapshots__/index.ce.test.tsx.snap
+++ b/packages/base/src/page/DataSource/components/Form/__snapshots__/index.ce.test.tsx.snap
@@ -735,26 +735,30 @@ exports[`page/DataSource/DataSourceForm CE render business field when getProject
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
diff --git a/packages/base/src/page/DataSource/components/Form/__snapshots__/index.test.tsx.snap b/packages/base/src/page/DataSource/components/Form/__snapshots__/index.test.tsx.snap
index acb7cd7109..a94467f77b 100644
--- a/packages/base/src/page/DataSource/components/Form/__snapshots__/index.test.tsx.snap
+++ b/packages/base/src/page/DataSource/components/Form/__snapshots__/index.test.tsx.snap
@@ -854,26 +854,30 @@ exports[`page/DataSource/DataSourceForm render cancel rule 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -2371,26 +2375,30 @@ exports[`page/DataSource/DataSourceForm render cancel rule 2`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -3474,26 +3482,30 @@ exports[`page/DataSource/DataSourceForm render change switch when page is update
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -5166,26 +5178,30 @@ exports[`page/DataSource/DataSourceForm render database type 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -6421,26 +6437,30 @@ exports[`page/DataSource/DataSourceForm render name rule 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -7533,26 +7553,30 @@ exports[`page/DataSource/DataSourceForm render name rule 2`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -8686,26 +8710,30 @@ exports[`page/DataSource/DataSourceForm render update form snap 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
diff --git a/packages/base/src/page/DataSource/components/Form/index.tsx b/packages/base/src/page/DataSource/components/Form/index.tsx
index d771121761..ba43dac477 100644
--- a/packages/base/src/page/DataSource/components/Form/index.tsx
+++ b/packages/base/src/page/DataSource/components/Form/index.tsx
@@ -35,7 +35,21 @@ import {
import { nameRule } from '@actiontech/dms-kit';
import DatabaseFormItem from './FormItem';
import MaintenanceTimePicker from './MaintenanceTimePicker';
-import { turnDataSourceAsyncFormToCommon } from '../../tool';
+import {
+ DEFAULT_REDIS_CONNECTION_MODE,
+ filterMongoTopologyParam,
+ filterRedisConnectionModeParam,
+ getMongoTopologyFromParams,
+ getRedisConnectionModeFromParams,
+ isMongoDbType,
+ isRedisDbType,
+ mergeMongoTopologyIntoParams,
+ mergeRedisConnectionModeIntoParams,
+ MONGODB_SEED_HOSTS_PARAM,
+ normalizeMongoParams,
+ normalizeMongoRequestParams,
+ turnDataSourceAsyncFormToCommon
+} from '../../tool';
import { useAsyncParams, BackendFormItemParams } from '@actiontech/shared';
import { useRequest } from 'ahooks';
import rule_template from '@actiontech/shared/lib/api/sqle/service/rule_template';
@@ -79,6 +93,36 @@ const DataSourceForm: React.FC = (props) => {
const { projectID } = useCurrentProject();
const project = Form.useWatch('project', props.form);
const enableBackup = Form.useWatch('enableBackup', props.form);
+ const changeAuditEnabled = useCallback(
+ (check: boolean) => {
+ if (!check) {
+ props.form.setFieldsValue({
+ needSqlAuditService: false,
+ ruleTemplateId: undefined,
+ ruleTemplateName: undefined,
+ dataExportRuleTemplateId: undefined,
+ dataExportRuleTemplateName: undefined,
+ needAuditForSqlQuery: false,
+ workbenchTemplateId: undefined,
+ workbenchTemplateName: undefined,
+ allowQueryWhenLessThanAuditLevel: undefined,
+ allowExecuteNonDqlInWorkflow: undefined
+ });
+ } else {
+ if (props.defaultData) {
+ props.form.setFieldsValue({
+ allowQueryWhenLessThanAuditLevel:
+ props.defaultData.sqle_config?.sql_query_config
+ ?.allow_query_when_less_than_audit_level,
+ allowExecuteNonDqlInWorkflow:
+ !!props.defaultData.sqle_config?.sql_query_config
+ ?.workflow_exec_enabled
+ });
+ }
+ }
+ },
+ [props.defaultData, props.form]
+ );
const getBackupSupportStatus = useCallback((value: string) => {
system
.getSystemModuleStatus({
@@ -94,6 +138,10 @@ const DataSourceForm: React.FC = (props) => {
const databaseTypeChange = useCallback(
(value: string) => {
setDatabaseType(value);
+ props.form.setFieldValue(
+ 'connectionMode',
+ isRedisDbType(value) ? DEFAULT_REDIS_CONNECTION_MODE : undefined
+ );
// #if [sqle]
props.form.resetFields([
'ruleTemplateName',
@@ -105,12 +153,15 @@ const DataSourceForm: React.FC = (props) => {
'allowExecuteNonDqlInWorkflow',
'sqlWorkbenchMaintenanceTime'
]);
+ if (value.toLowerCase() === 'mongodb') {
+ changeAuditEnabled(false);
+ }
// #endif
// #if [sqle && ee]
getBackupSupportStatus(value);
// #endif
},
- [props.form, getBackupSupportStatus]
+ [props.form, getBackupSupportStatus, changeAuditEnabled]
);
const { generateFormValueByParams, mergeFromValueIntoParams } =
useAsyncParams();
@@ -148,25 +199,11 @@ const DataSourceForm: React.FC = (props) => {
};
});
}, [ruleTemplateList, globalRuleTemplateList, databaseType]);
- const changeAuditEnabled = (check: boolean) => {
- if (!check) {
- props.form.setFieldsValue({
- allowQueryWhenLessThanAuditLevel: undefined,
- allowExecuteNonDqlInWorkflow: undefined
- });
- } else {
- if (props.defaultData) {
- props.form.setFieldsValue({
- allowQueryWhenLessThanAuditLevel:
- props.defaultData.sqle_config?.sql_query_config
- ?.allow_query_when_less_than_audit_level,
- allowExecuteNonDqlInWorkflow:
- !!props.defaultData.sqle_config?.sql_query_config
- ?.workflow_exec_enabled
- });
- }
+ useEffect(() => {
+ if (databaseType.toLowerCase() === 'mongodb') {
+ changeAuditEnabled(false);
}
- };
+ }, [databaseType, changeAuditEnabled]);
// #endif
const params = useMemo(() => {
@@ -177,7 +214,26 @@ const DataSourceForm: React.FC = (props) => {
if (!temp) {
return [];
}
- return turnDataSourceAsyncFormToCommon(temp.params ?? []);
+ const driverParams = filterMongoTopologyParam(
+ filterRedisConnectionModeParam(
+ turnDataSourceAsyncFormToCommon(temp.params ?? [])
+ )
+ );
+ if (
+ databaseType.toLowerCase() === 'mongodb' &&
+ !driverParams.some((item) => item.key === MONGODB_SEED_HOSTS_PARAM)
+ ) {
+ return [
+ ...driverParams,
+ {
+ desc: 'MongoDB seed hosts',
+ key: MONGODB_SEED_HOSTS_PARAM,
+ type: 'string',
+ value: ''
+ }
+ ];
+ }
+ return driverParams;
}, [databaseType, driverMeta]);
useEffect(() => {
if (!!props.defaultData) {
@@ -185,12 +241,24 @@ const DataSourceForm: React.FC = (props) => {
name: props.defaultData.name,
describe: props.defaultData.desc,
type: props.defaultData.db_type,
+ connectionMode: isRedisDbType(props.defaultData.db_type)
+ ? getRedisConnectionModeFromParams(
+ props.defaultData.additional_params
+ )
+ : undefined,
+ mongoTopology: isMongoDbType(props.defaultData.db_type)
+ ? getMongoTopologyFromParams(props.defaultData.additional_params)
+ : undefined,
ip: props.defaultData.host,
port: Number.parseInt(props.defaultData.port ?? ''),
user: props.defaultData.user,
params: generateFormValueByParams(
- turnDataSourceAsyncFormToCommon(
- props.defaultData.additional_params ?? []
+ filterMongoTopologyParam(
+ filterRedisConnectionModeParam(
+ turnDataSourceAsyncFormToCommon(
+ props.defaultData.additional_params ?? []
+ )
+ )
)
),
maintenanceTime: props.defaultData.maintenance_times?.map((item) => ({
@@ -262,15 +330,25 @@ const DataSourceForm: React.FC = (props) => {
}, [props.form]);
const submit = useCallback(async () => {
const values = props.form.getFieldsValue();
+ values.params = normalizeMongoParams(values.params);
if (values.params) {
- values.asyncParams = mergeFromValueIntoParams(values.params, params).map(
- (v) => ({
+ values.asyncParams = normalizeMongoRequestParams(
+ mergeFromValueIntoParams(values.params, params).map((v) => ({
name: v.key,
value: v.value
- })
+ }))
);
delete values.params;
}
+ values.asyncParams = mergeMongoTopologyIntoParams(
+ mergeRedisConnectionModeIntoParams(
+ values.asyncParams,
+ values.type,
+ values.connectionMode
+ ),
+ values.type,
+ values.mongoTopology
+ );
props.submit(values).then(() => {
closeModal();
});
@@ -398,6 +476,7 @@ const DataSourceForm: React.FC = (props) => {
generateDriverSelectOptions={generateDriverSelectOptions}
updateDriverListLoading={updateDriverListLoading}
currentAsyncParams={params}
+ databaseType={databaseType}
isExternalInstance={isExternalInstance}
/>
= (props) => {
{/* #if [sqle] */}
-
-
-
+ {databaseType.toLowerCase() !== 'mongodb' && (
+
+
+
+ )}
{/* #endif */}
{/* #if [sqle && ee] */}
diff --git a/packages/base/src/page/DataSource/components/Form/index.type.ts b/packages/base/src/page/DataSource/components/Form/index.type.ts
index eba0e903a3..d4c98df01c 100644
--- a/packages/base/src/page/DataSource/components/Form/index.type.ts
+++ b/packages/base/src/page/DataSource/components/Form/index.type.ts
@@ -6,11 +6,13 @@ import {
import { MaintenanceTimeValue } from './MaintenanceTimePicker';
import { SQLQueryConfigAllowQueryWhenLessThanAuditLevelEnum } from '@actiontech/shared/lib/api/base/service/common.enum';
import { IListDBServiceV2 } from '@actiontech/shared/lib/api/base/service/common';
+import type { MongoTopology, RedisConnectionMode } from '../../tool';
export type DataSourceFormField = {
name: string;
describe?: string;
type: string;
+ connectionMode?: RedisConnectionMode;
ip: string;
port: number;
user: string;
@@ -31,6 +33,7 @@ export type DataSourceFormField = {
dataExportRuleTemplateName?: string;
params?: BackendFormValues;
asyncParams?: BackendFormRequestParams[];
+ mongoTopology?: MongoTopology;
needUpdatePassword?: boolean;
enableBackup?: boolean;
backupMaxRows?: number;
diff --git a/packages/base/src/page/DataSource/components/UpdateDataSource/__snapshots__/index.test.tsx.snap b/packages/base/src/page/DataSource/components/UpdateDataSource/__snapshots__/index.test.tsx.snap
index 6dfa9768e8..3519952b4c 100644
--- a/packages/base/src/page/DataSource/components/UpdateDataSource/__snapshots__/index.test.tsx.snap
+++ b/packages/base/src/page/DataSource/components/UpdateDataSource/__snapshots__/index.test.tsx.snap
@@ -840,26 +840,30 @@ exports[`page/DataSource/UpdateDataSource render edit database snap 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -2365,26 +2369,30 @@ exports[`page/DataSource/UpdateDataSource render prepare api 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -3591,26 +3599,30 @@ exports[`page/DataSource/UpdateDataSource return list by click return button 1`]
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
diff --git a/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx b/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx
index b41c606b67..74f77c2297 100644
--- a/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx
+++ b/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx
@@ -189,7 +189,10 @@ describe('page/DataSource/UpdateDataSource', () => {
expect(getBySelector('#needAuditForSqlQuery')).toBeChecked();
fireEvent.click(getBySelector('#needAuditForSqlQuery', baseElement));
await act(async () => jest.advanceTimersByTime(0));
- expect(getBySelector('#needAuditForSqlQuery')).not.toBeChecked();
+ // onNeedAuditForSqlQueryChange(false) clears needSqlAuditService; field unmounts
+ expect(
+ baseElement.querySelector('#needAuditForSqlQuery')
+ ).not.toBeInTheDocument();
await act(async () => {
fireEvent.click(screen.getByText('提 交'));
diff --git a/packages/base/src/page/DataSource/hooks/useCheckConnectable.ts b/packages/base/src/page/DataSource/hooks/useCheckConnectable.ts
index 7916055d64..0281533818 100644
--- a/packages/base/src/page/DataSource/hooks/useCheckConnectable.ts
+++ b/packages/base/src/page/DataSource/hooks/useCheckConnectable.ts
@@ -9,6 +9,12 @@ import {
getDBServiceConnectableErrorMessage,
getDbServiceIsConnectbale
} from '../../../utils/common';
+import {
+ mergeMongoTopologyIntoParams,
+ mergeRedisConnectionModeIntoParams,
+ normalizeMongoParams,
+ normalizeMongoRequestParams
+} from '../tool';
const useCheckConnectable = (form: FormInstance) => {
const projectID = Form.useWatch('project', form);
@@ -28,17 +34,32 @@ const useCheckConnectable = (form: FormInstance) => {
'port',
'user',
'type',
- 'params'
+ 'params',
+ 'connectionMode',
+ 'mongoTopology'
]);
+ values.params = normalizeMongoParams(values.params);
+
if (values.params && currentAsyncParams) {
- values.asyncParams = mergeFromValueIntoParams(
- values.params,
- currentAsyncParams
- ).map((v) => ({ name: v.key, value: v.value }));
+ values.asyncParams = normalizeMongoRequestParams(
+ mergeFromValueIntoParams(values.params, currentAsyncParams).map(
+ (v) => ({ name: v.key, value: v.value })
+ )
+ );
delete values.params;
}
+ values.asyncParams = mergeMongoTopologyIntoParams(
+ mergeRedisConnectionModeIntoParams(
+ values.asyncParams,
+ values.type,
+ values.connectionMode
+ ),
+ values.type,
+ values.mongoTopology
+ );
+
setLoadingTrue();
return DmsApi.DBServiceService.CheckDBServiceIsConnectable({
db_service: {
diff --git a/packages/base/src/page/DataSource/tool/index.test.ts b/packages/base/src/page/DataSource/tool/index.test.ts
index fec618ef7d..b793ec74fd 100644
--- a/packages/base/src/page/DataSource/tool/index.test.ts
+++ b/packages/base/src/page/DataSource/tool/index.test.ts
@@ -1,6 +1,10 @@
import {
+ normalizeMongoParams,
+ normalizeMongoRequestParams,
+ normalizeMongoSeedHosts,
turnCommonToDataSourceParams,
- turnDataSourceAsyncFormToCommon
+ turnDataSourceAsyncFormToCommon,
+ validateMongoSeedHosts
} from '.';
describe('datasource tool', () => {
@@ -39,4 +43,37 @@ describe('datasource tool', () => {
}
]);
});
+
+ it('should normalize mongodb seed hosts dynamic param', () => {
+ expect(normalizeMongoSeedHosts('192.0.2.10:27017,27018,27019')).toBe(
+ '192.0.2.10:27017,192.0.2.10:27018,192.0.2.10:27019'
+ );
+ expect(
+ normalizeMongoParams({
+ seed_hosts: '192.0.2.10:27017\n192.0.2.11:27017',
+ replica_set: 'rs0'
+ })
+ ).toEqual({
+ seed_hosts: '192.0.2.10:27017,192.0.2.11:27017',
+ replica_set: 'rs0'
+ });
+ });
+
+ it('should validate mongodb seed hosts format', () => {
+ expect(validateMongoSeedHosts('192.0.2.10:27017,27018,27019')).toBeTruthy();
+ expect(validateMongoSeedHosts('192.0.2.10')).toBeFalsy();
+ expect(validateMongoSeedHosts('192.0.2.10:70000')).toBeFalsy();
+ });
+
+ it('should omit empty mongodb seed hosts from request params', () => {
+ expect(
+ normalizeMongoRequestParams([
+ { name: 'auth_source', value: 'admin' },
+ { name: 'seed_hosts', value: '' }
+ ])
+ ).toEqual([{ name: 'auth_source', value: 'admin' }]);
+ expect(
+ normalizeMongoRequestParams([{ name: 'seed_hosts', value: 'h:27017' }])
+ ).toEqual([{ name: 'seed_hosts', value: 'h:27017' }]);
+ });
});
diff --git a/packages/base/src/page/DataSource/tool/index.ts b/packages/base/src/page/DataSource/tool/index.ts
index 767773a55c..9f86a74573 100644
--- a/packages/base/src/page/DataSource/tool/index.ts
+++ b/packages/base/src/page/DataSource/tool/index.ts
@@ -37,3 +37,264 @@ export const turnCommonToDataSourceParams = <
value: item.value
}));
};
+
+export const REDIS_CONNECTION_MODE_PARAM_NAME = 'connection_mode';
+
+export type RedisConnectionMode = 'standalone' | 'cluster';
+
+export const DEFAULT_REDIS_CONNECTION_MODE: RedisConnectionMode = 'standalone';
+
+export const isRedisDbType = (dbType?: string): boolean => {
+ return dbType?.toLowerCase() === 'redis';
+};
+
+export const getRedisConnectionModeFromParams = <
+ T extends {
+ name?: string;
+ key?: string;
+ value?: string;
+ }[]
+>(
+ params?: T
+): RedisConnectionMode => {
+ const value = params?.find(
+ (item) =>
+ item.name === REDIS_CONNECTION_MODE_PARAM_NAME ||
+ item.key === REDIS_CONNECTION_MODE_PARAM_NAME
+ )?.value;
+
+ return value === 'cluster' ? 'cluster' : DEFAULT_REDIS_CONNECTION_MODE;
+};
+
+export const filterRedisConnectionModeParam = <
+ T extends {
+ name?: string;
+ key?: string;
+ }[]
+>(
+ params: T
+): T => {
+ return params.filter(
+ (item) =>
+ item.name !== REDIS_CONNECTION_MODE_PARAM_NAME &&
+ item.key !== REDIS_CONNECTION_MODE_PARAM_NAME
+ ) as T;
+};
+
+export const mergeRedisConnectionModeIntoParams = <
+ T extends {
+ name?: string;
+ value?: string;
+ }[]
+>(
+ params: T | undefined,
+ dbType?: string,
+ connectionMode?: RedisConnectionMode
+): T => {
+ const nextParams = filterRedisConnectionModeParam(params ?? []) as T;
+
+ if (!isRedisDbType(dbType)) {
+ return nextParams;
+ }
+
+ return [
+ ...nextParams,
+ {
+ name: REDIS_CONNECTION_MODE_PARAM_NAME,
+ value: connectionMode ?? DEFAULT_REDIS_CONNECTION_MODE
+ }
+ ] as T;
+};
+
+export const MONGODB_SEED_HOSTS_PARAM = 'seed_hosts';
+export const MONGODB_AUTH_SOURCE_PARAM = 'auth_source';
+export const MONGODB_REPLICA_SET_PARAM = 'replica_set';
+export const MONGODB_TOPOLOGY_PARAM = 'topology';
+
+export type MongoTopology = 'single' | 'replicaSet' | 'shard';
+
+export const DEFAULT_MONGO_TOPOLOGY: MongoTopology = 'single';
+
+export const isMongoDbType = (dbType?: string): boolean => {
+ return dbType?.toLowerCase() === 'mongodb';
+};
+
+export const isMongoTopology = (value?: string): value is MongoTopology =>
+ value === 'single' || value === 'replicaSet' || value === 'shard';
+
+const getParamValueByNameOrKey = <
+ T extends {
+ name?: string;
+ key?: string;
+ value?: string;
+ }[]
+>(
+ params: T | undefined,
+ paramName: string
+) =>
+ params?.find((item) => item.name === paramName || item.key === paramName)
+ ?.value;
+
+/**
+ * 优先读 additional_params.topology;存量无键时:
+ * 有 seed_hosts 或非空 replica_set → replicaSet,否则 single。
+ * 旧分片无键无法识别,回落 single(需用户改选分片集群后保存补齐)。
+ */
+export const getMongoTopologyFromParams = <
+ T extends {
+ name?: string;
+ key?: string;
+ value?: string;
+ }[]
+>(
+ params?: T
+): MongoTopology => {
+ const topology = getParamValueByNameOrKey(params, MONGODB_TOPOLOGY_PARAM);
+ if (isMongoTopology(topology)) {
+ return topology;
+ }
+
+ const seedHosts = getParamValueByNameOrKey(params, MONGODB_SEED_HOSTS_PARAM);
+ const replicaSet = getParamValueByNameOrKey(
+ params,
+ MONGODB_REPLICA_SET_PARAM
+ );
+ if (
+ (typeof seedHosts === 'string' && !!seedHosts.trim()) ||
+ (typeof replicaSet === 'string' && !!replicaSet.trim())
+ ) {
+ return 'replicaSet';
+ }
+
+ return DEFAULT_MONGO_TOPOLOGY;
+};
+
+export const filterMongoTopologyParam = <
+ T extends {
+ name?: string;
+ key?: string;
+ }[]
+>(
+ params: T
+): T => {
+ return params.filter(
+ (item) =>
+ item.name !== MONGODB_TOPOLOGY_PARAM &&
+ item.key !== MONGODB_TOPOLOGY_PARAM
+ ) as T;
+};
+
+export const mergeMongoTopologyIntoParams = <
+ T extends {
+ name?: string;
+ value?: string;
+ }[]
+>(
+ params: T | undefined,
+ dbType?: string,
+ topology?: MongoTopology
+): T => {
+ const nextParams = filterMongoTopologyParam(params ?? []) as T;
+
+ if (!isMongoDbType(dbType)) {
+ return nextParams;
+ }
+
+ return [
+ ...nextParams,
+ {
+ name: MONGODB_TOPOLOGY_PARAM,
+ value: topology ?? DEFAULT_MONGO_TOPOLOGY
+ }
+ ] as T;
+};
+
+/** Mongo 主表单常驻参数(不含拓扑条件字段) */
+export const MONGODB_MAIN_PARAMS = [MONGODB_AUTH_SOURCE_PARAM] as const;
+
+/** 仅副本集形态展示 */
+export const MONGODB_REPLICA_PARAMS = [
+ MONGODB_REPLICA_SET_PARAM,
+ MONGODB_SEED_HOSTS_PARAM
+] as const;
+
+const splitMongoSeedHosts = (value?: string) =>
+ (value ?? '')
+ .split(/[\n,]/)
+ .map((item) => item.trim())
+ .filter(Boolean);
+
+const isValidMongoPort = (value: string) => {
+ if (!/^\d+$/.test(value)) {
+ return false;
+ }
+ const port = Number(value);
+ return port > 0 && port <= 65535;
+};
+
+export const normalizeMongoSeedHosts = (value?: string) => {
+ let latestHost = '';
+ return splitMongoSeedHosts(value)
+ .map((item) => {
+ if (/^\d+$/.test(item) && latestHost) {
+ return `${latestHost}:${item}`;
+ }
+
+ const splitIndex = item.lastIndexOf(':');
+ if (splitIndex > 0) {
+ latestHost = item.slice(0, splitIndex).trim();
+ }
+
+ return item;
+ })
+ .join(',');
+};
+
+export const validateMongoSeedHosts = (value?: string) => {
+ const normalizedValue = normalizeMongoSeedHosts(value);
+ const seedHosts = splitMongoSeedHosts(normalizedValue);
+
+ if (seedHosts.length === 0) {
+ return false;
+ }
+
+ return seedHosts.every((item) => {
+ const splitIndex = item.lastIndexOf(':');
+ if (splitIndex <= 0 || splitIndex === item.length - 1) {
+ return false;
+ }
+
+ const host = item.slice(0, splitIndex).trim();
+ const port = item.slice(splitIndex + 1).trim();
+ return !!host && isValidMongoPort(port);
+ });
+};
+
+export const normalizeMongoParams = >(
+ params?: T
+) => {
+ if (
+ !params ||
+ typeof params[MONGODB_SEED_HOSTS_PARAM] !== 'string' ||
+ !params[MONGODB_SEED_HOSTS_PARAM]
+ ) {
+ return params;
+ }
+
+ return {
+ ...params,
+ [MONGODB_SEED_HOSTS_PARAM]: normalizeMongoSeedHosts(
+ params[MONGODB_SEED_HOSTS_PARAM] as string
+ )
+ };
+};
+
+export const normalizeMongoRequestParams = <
+ T extends { name?: string; value?: string }
+>(
+ params: T[]
+) => {
+ return params.filter(
+ (item) => item.name !== MONGODB_SEED_HOSTS_PARAM || !!item.value
+ );
+};
diff --git a/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap b/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap
index c3d623ca02..bf7ffdeafb 100644
--- a/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap
+++ b/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap
@@ -608,26 +608,30 @@ exports[`page/SyncDataSource/AddPage render add submit for success 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -2941,26 +2945,30 @@ exports[`page/SyncDataSource/AddPage render add sync task snap 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -4032,26 +4040,30 @@ exports[`page/SyncDataSource/AddPage render form item for prepare api 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
diff --git a/packages/base/src/page/SyncDataSource/Form/__snapshots__/index.test.tsx.snap b/packages/base/src/page/SyncDataSource/Form/__snapshots__/index.test.tsx.snap
index ecbe26c18e..3d2d97b67b 100644
--- a/packages/base/src/page/SyncDataSource/Form/__snapshots__/index.test.tsx.snap
+++ b/packages/base/src/page/SyncDataSource/Form/__snapshots__/index.test.tsx.snap
@@ -424,26 +424,30 @@ exports[`page/SyncDataSource/SyncTaskForm render for loading form 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -1288,26 +1292,30 @@ exports[`page/SyncDataSource/SyncTaskForm render name rule 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -2152,26 +2160,30 @@ exports[`page/SyncDataSource/SyncTaskForm render name rule 2`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -3003,26 +3015,30 @@ exports[`page/SyncDataSource/SyncTaskForm render syncInterval for form item 1`]
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -3865,26 +3881,30 @@ exports[`page/SyncDataSource/SyncTaskForm render syncInterval for form item 2`]
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
diff --git a/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap b/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap
index df00a2c581..6bc850b3b3 100644
--- a/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap
+++ b/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap
@@ -488,26 +488,30 @@ exports[`page/SyncDataSource/UpdateSyncTask get task source failed 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -1595,26 +1599,30 @@ exports[`page/SyncDataSource/UpdateSyncTask render click reset btn 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -2638,26 +2646,30 @@ exports[`page/SyncDataSource/UpdateSyncTask render click reset btn 2`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -4119,26 +4131,30 @@ exports[`page/SyncDataSource/UpdateSyncTask render edit database snap 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+
@@ -5933,26 +5949,30 @@ exports[`page/SyncDataSource/UpdateSyncTask render update task submit 1`] = `
-
+ class="ant-switch-inner"
+ >
+
+
+
+
+