diff --git a/wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts b/wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts index 5f3d30a6b..bcd0e7b4e 100644 --- a/wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts +++ b/wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts @@ -6,7 +6,7 @@ import { IbisQueryResponse, ValidationRules, } from '../ibisAdaptor'; -import { DataSourceName } from '../../types'; +import { DataSourceName, SSLMode } from '../../types'; import { Manifest } from '../../mdl/type'; import { BIG_QUERY_CONNECTION_INFO, @@ -45,6 +45,8 @@ describe('IbisAdaptor', () => { database: 'my-database', user: 'my-user', password: 'my-password', + sslMode: SSLMode.VERIFY_CA, + sslCA: 'encrypted-certificate-string', }; const mockPostgresConnectionInfo: POSTGRES_CONNECTION_INFO = { @@ -175,17 +177,28 @@ describe('IbisAdaptor', () => { mockedAxios.post.mockResolvedValue(mockResponse); // mock decrypt method in Encryptor to return the same password mockedEncryptor.prototype.decrypt.mockReturnValue( - JSON.stringify({ password: mockMySQLConnectionInfo.password }), + JSON.stringify({ + password: mockMySQLConnectionInfo.password, + ...(mockMySQLConnectionInfo.sslCA && { sslCA: mockMySQLConnectionInfo.sslCA }) + }), ); const result = await ibisAdaptor.getConstraints( DataSourceName.MYSQL, mockMySQLConnectionInfo, ); - const expectConnectionInfo = Object.entries(mockMySQLConnectionInfo).reduce( - (acc, [key, value]) => ((acc[snakeCase(key)] = value), acc), - {}, - ); + const expectConnectionInfo = Object.entries( + mockMySQLConnectionInfo, + ).reduce((acc, [key, value]) => { + if (key === 'sslCA') { + acc['sslCA'] = Buffer + .from(mockMySQLConnectionInfo.sslCA) + .toString('base64'); + } else { + acc[key] = value; + } + return acc; + }, {}); expect(result).toEqual([]); expect(mockedAxios.post).toHaveBeenCalledWith( diff --git a/wren-ui/src/apollo/server/dataSource.ts b/wren-ui/src/apollo/server/dataSource.ts index 6d2370dce..d57478927 100644 --- a/wren-ui/src/apollo/server/dataSource.ts +++ b/wren-ui/src/apollo/server/dataSource.ts @@ -113,15 +113,27 @@ const dataSource = { // mysql [DataSourceName.MYSQL]: { - sensitiveProps: ['password'], + sensitiveProps: ['password', 'sslCA'], toIbisConnectionInfo(connectionInfo) { const decryptedConnectionInfo = decryptConnectionInfo( DataSourceName.MYSQL, connectionInfo, ); - const { host, port, database, user, password } = + const { host, port, database, user, password, ...ssl } = decryptedConnectionInfo as MYSQL_CONNECTION_INFO; - return { host, port, database, user, password }; + return { + host, + port, + database, + user, + password, + sslMode: ssl.sslMode, + ...(ssl.sslCA && { + sslCA: Buffer.from( + ssl.sslCA, + ).toString('base64') + }), + }; }, } as IDataSourceConnectionInfo< MYSQL_CONNECTION_INFO, diff --git a/wren-ui/src/apollo/server/repositories/projectRepository.ts b/wren-ui/src/apollo/server/repositories/projectRepository.ts index f20d765af..b261ff723 100644 --- a/wren-ui/src/apollo/server/repositories/projectRepository.ts +++ b/wren-ui/src/apollo/server/repositories/projectRepository.ts @@ -8,7 +8,7 @@ import { snakeCase, isEmpty, } from 'lodash'; -import { DataSourceName } from '@server/types'; +import { DataSourceName, SSLMode } from '@server/types'; export interface BIG_QUERY_CONNECTION_INFO { projectId: string; @@ -30,6 +30,8 @@ export interface MYSQL_CONNECTION_INFO { user: string; password: string; database: string; + sslMode: SSLMode; + sslCA?: string; } export interface MS_SQL_CONNECTION_INFO { diff --git a/wren-ui/src/apollo/server/types/index.ts b/wren-ui/src/apollo/server/types/index.ts index 57569a2b7..632d1681f 100644 --- a/wren-ui/src/apollo/server/types/index.ts +++ b/wren-ui/src/apollo/server/types/index.ts @@ -4,3 +4,4 @@ export * from './manifest'; export * from './diagram'; export * from './metric'; export * from './context'; +export * from './sslMode'; diff --git a/wren-ui/src/apollo/server/types/sslMode.ts b/wren-ui/src/apollo/server/types/sslMode.ts new file mode 100644 index 000000000..5b78fa34d --- /dev/null +++ b/wren-ui/src/apollo/server/types/sslMode.ts @@ -0,0 +1,5 @@ +export enum SSLMode { + DISABLED = 'disabled', + ENABLED = 'enabled', + VERIFY_CA = 'verify_ca', +} diff --git a/wren-ui/src/components/pages/setup/dataSources/MySQLProperties.tsx b/wren-ui/src/components/pages/setup/dataSources/MySQLProperties.tsx index 166988329..1e17a2c56 100644 --- a/wren-ui/src/components/pages/setup/dataSources/MySQLProperties.tsx +++ b/wren-ui/src/components/pages/setup/dataSources/MySQLProperties.tsx @@ -1,15 +1,73 @@ -import { Form, Input } from 'antd'; +import { useEffect, useState } from 'react'; +import { Form, Input, Select, Button, Upload } from 'antd'; +import UploadOutlined from '@ant-design/icons/UploadOutlined'; +import { UploadFile } from 'antd/lib/upload/interface'; import { ERROR_TEXTS } from '@/utils/error'; import { FORM_MODE } from '@/utils/enum'; import { hostValidator } from '@/utils/validator'; +import { SSLMode } from '@/apollo/server/types'; interface Props { mode?: FORM_MODE; } +const UploadSSL = (props) => { + const { onChange, value } = props; + + const [fileList, setFileList] = useState([]); + + useEffect(() => { + if (!value) setFileList([]); + }, [value]); + + const readFileContent = (file: any, callback: (value: string) => void) => { + const reader = new FileReader(); + reader.onloadend = (_e) => { + const result = reader.result; + + if (result) { + const fileContent = String(result); + callback(fileContent); + } + }; + + reader.readAsText(file); + }; + + const onUploadChange = (info) => { + const { file, fileList } = info; + if (fileList.length) { + const uploadFile = fileList[0]; + readFileContent(file.originFileObj, (fileContent: string) => { + onChange && onChange(fileContent); + }); + setFileList([uploadFile]); + } + }; + + const onRemove = () => { + setFileList([]); + onChange && onChange(undefined); + }; + + return ( + + + + ); +}; + export default function MySQLProperties(props: Props) { const { mode } = props; const isEditMode = mode === FORM_MODE.EDIT; + const [sslMode, setSSLMode] = useState(SSLMode.DISABLED); + const onSSLModeChange = (value: string) => setSSLMode(value) return ( <> + +