diff --git a/src/entities/connection/api/types.ts b/src/entities/connection/api/types.ts index 1a123b7a..16a84b69 100644 --- a/src/entities/connection/api/types.ts +++ b/src/entities/connection/api/types.ts @@ -20,7 +20,8 @@ export type ConnectionData = | ConnectionPostgres | ConnectionClickhouse | ConnectionMySql - | ConnectionMsSql; + | ConnectionMsSql + | ConnectionIceberg; export type ConnectionBucketStyle = 'domain' | 'path'; @@ -30,10 +31,21 @@ export type ConnectionSambaProtocol = 'SMB' | 'NetBIOS'; export type ConnectionSambaAuthType = 'NTLMv1' | 'NTLMv2'; +export type ConnectionIcebergS3AccessDelegation = 'vended-credentials' | 'remote-signing'; + +export enum ConnectionIcebergConnectionType { + ICEBERG_REST_S3_DIRECT = 'iceberg_rest_s3_direct', + ICEBERG_REST_S3_DELEGATED = 'iceberg_rest_s3_delegated', +} + export enum ConnectionAuthType { BASIC = 'basic', S3 = 's3', SAMBA = 'samba', + ICEBERG_REST_BEARER = 'iceberg_rest_bearer', + ICEBERG_REST_OAUTH2_CLIENT_CREDENTIALS = 'iceberg_rest_oauth2_client_credentials', + ICEBERG_REST_BEARER_S3_BASIC = 'iceberg_rest_bearer_s3_basic', + ICEBERG_REST_OAUTH2_CLIENT_CREDENTIALS_S3_BASIC = 'iceberg_rest_oauth2_client_credentials_s3_basic', } interface ConnectionAuthBasic { @@ -63,6 +75,68 @@ export interface ConnectionHive { }; } +export interface ConnectionIcebergRestS3Direct { + type: ConnectionIcebergConnectionType.ICEBERG_REST_S3_DIRECT; + rest_catalog_url: string; + s3_warehouse_path: string; + s3_host: string; + s3_bucket: string; + s3_bucket_style: ConnectionBucketStyle; + s3_port: number | null; + s3_region: string; + s3_protocol: ConnectionProtocol; +} + +export interface ConnectionIcebergRestS3Delegated { + type: ConnectionIcebergConnectionType.ICEBERG_REST_S3_DELEGATED; + rest_catalog_url: string; + s3_warehouse_name: string | null; + s3_access_delegation: ConnectionIcebergS3AccessDelegation; +} + +export interface ConnectionIcebergRestBearer { + type: ConnectionAuthType.ICEBERG_REST_BEARER; + rest_catalog_token?: string; +} + +export interface ConnectionIcebergRestBearerS3Basic { + type: ConnectionAuthType.ICEBERG_REST_BEARER_S3_BASIC; + rest_catalog_token?: string; + s3_access_key: string; + s3_secret_key?: string; +} + +export interface ConnectionIcebergRestClientCredentials { + type: ConnectionAuthType.ICEBERG_REST_OAUTH2_CLIENT_CREDENTIALS; + rest_catalog_oauth2_client_id: string; + rest_catalog_oauth2_scopes: string[]; + rest_catalog_oauth2_resource: string | null; + rest_catalog_oauth2_audience: string | null; + rest_catalog_oauth2_token_endpoint: string | null; +} + +export interface ConnectionIcebergRestClientCredentialsS3Basic { + type: ConnectionAuthType.ICEBERG_REST_OAUTH2_CLIENT_CREDENTIALS_S3_BASIC; + rest_catalog_oauth2_client_id: string; + rest_catalog_oauth2_client_secret?: string; + rest_catalog_oauth2_scopes: string[]; + rest_catalog_oauth2_resource: string | null; + rest_catalog_oauth2_audience: string | null; + rest_catalog_oauth2_token_endpoint: string | null; + s3_access_key: string; + s3_secret_key?: string; +} + +export interface ConnectionIceberg { + type: ConnectionType.ICEBERG; + auth_data: + | ConnectionIcebergRestBearer + | ConnectionIcebergRestClientCredentials + | ConnectionIcebergRestBearerS3Basic + | ConnectionIcebergRestClientCredentialsS3Basic; + connection_data: ConnectionIcebergRestS3Direct | ConnectionIcebergRestS3Delegated; +} + export interface ConnectionHdfs { type: ConnectionType.HDFS; auth_data: ConnectionAuthBasic; diff --git a/src/entities/connection/assets/iceberg.svg b/src/entities/connection/assets/iceberg.svg new file mode 100644 index 00000000..3a6406f9 --- /dev/null +++ b/src/entities/connection/assets/iceberg.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/entities/connection/assets/index.ts b/src/entities/connection/assets/index.ts index 6cd0f74e..c636adf5 100644 --- a/src/entities/connection/assets/index.ts +++ b/src/entities/connection/assets/index.ts @@ -2,6 +2,7 @@ import S3Icon from './s3.svg'; import ClickhouseIcon from './clickhouse.svg'; import HdfsIcon from './hdfs.svg'; import HiveIcon from './hive.svg'; +import IcebergIcon from './iceberg.svg'; import MssqlIcon from './mssql.svg'; import MysqlIcon from './mysql.svg'; import OracleIcon from './oracle.svg'; @@ -15,6 +16,7 @@ export { ClickhouseIcon, HdfsIcon, HiveIcon, + IcebergIcon, MssqlIcon, MysqlIcon, OracleIcon, diff --git a/src/entities/connection/constants.tsx b/src/entities/connection/constants.tsx index f75dc53b..03c64de2 100644 --- a/src/entities/connection/constants.tsx +++ b/src/entities/connection/constants.tsx @@ -7,6 +7,7 @@ import { FtpIcon, HdfsIcon, HiveIcon, + IcebergIcon, MssqlIcon, MysqlIcon, OracleIcon, @@ -23,6 +24,7 @@ export const CONNECTION_TYPE_NAMES: Record = { [ConnectionType.FTPS]: 'FTPS', [ConnectionType.HDFS]: 'HDFS', [ConnectionType.HIVE]: 'Hive', + [ConnectionType.ICEBERG]: 'Iceberg', [ConnectionType.MSSQL]: 'MSSQL', [ConnectionType.MYSQL]: 'MySQL', [ConnectionType.ORACLE]: 'Oracle', @@ -39,6 +41,7 @@ export const CONNECTION_ICONS: Record = { [ConnectionType.FTPS]: , [ConnectionType.HDFS]: , [ConnectionType.HIVE]: , + [ConnectionType.ICEBERG]: , [ConnectionType.MSSQL]: , [ConnectionType.MYSQL]: , [ConnectionType.ORACLE]: , @@ -56,6 +59,7 @@ export const CONNECTION_TYPE_SELECT_OPTIONS = prepareOptionsForSelect({ { value: ConnectionType.FTPS, label: CONNECTION_TYPE_NAMES[ConnectionType.FTPS] }, { value: ConnectionType.HDFS, label: CONNECTION_TYPE_NAMES[ConnectionType.HDFS] }, { value: ConnectionType.HIVE, label: CONNECTION_TYPE_NAMES[ConnectionType.HIVE] }, + { value: ConnectionType.ICEBERG, label: CONNECTION_TYPE_NAMES[ConnectionType.ICEBERG] }, { value: ConnectionType.MSSQL, label: CONNECTION_TYPE_NAMES[ConnectionType.MSSQL] }, { value: ConnectionType.MYSQL, label: CONNECTION_TYPE_NAMES[ConnectionType.MYSQL] }, { value: ConnectionType.ORACLE, label: CONNECTION_TYPE_NAMES[ConnectionType.ORACLE] }, diff --git a/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionHttpProtocol/index.tsx b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionHttpProtocol/index.tsx index fc33fb2d..37ddac4b 100644 --- a/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionHttpProtocol/index.tsx +++ b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionHttpProtocol/index.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { MAX_ALLOWED_PORT, MIN_ALLOWED_PORT } from '../../constants'; -export const ConnectionHttpProtocol = () => { +export const ConnectionHttpProtocol = ({ fieldsPrefix = '' }: { fieldsPrefix?: string }) => { const { t } = useTranslation('connection'); const formInstance = Form.useFormInstance(); @@ -24,7 +24,7 @@ export const ConnectionHttpProtocol = () => { <> @@ -33,7 +33,7 @@ export const ConnectionHttpProtocol = () => { HTTPS - + diff --git a/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergBearer.tsx b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergBearer.tsx new file mode 100644 index 00000000..6a9afab6 --- /dev/null +++ b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergBearer.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Form, Input } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import { useSensitiveFields } from '../../hooks'; + +export const ConnectionAuthIcebergBearer = () => { + const { t } = useTranslation('connection'); + const { isRequired } = useSensitiveFields(); + + return ( + <> + + + + + ); +}; diff --git a/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergOAuth2ClientCredentials.tsx b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergOAuth2ClientCredentials.tsx new file mode 100644 index 00000000..64c4d0f2 --- /dev/null +++ b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergOAuth2ClientCredentials.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Button, Form, Input, Space } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import { useSensitiveFields } from '../../hooks'; + +import { ConnectionAuthIcebergOAuth2Scope } from './ConnectionAuthIcebergOAuth2Scope'; + +export const ConnectionAuthIcebergOAuth2ClientCredentials = () => { + const { t } = useTranslation('connection'); + const { isRequired } = useSensitiveFields(); + + return ( + <> + + + + + + + + + + + + {(fields, { add, remove }) => ( + + + + {fields.map((field) => ( + + ))} + + + )} + + + + + + + + + + ); +}; diff --git a/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergOAuth2Scope.tsx b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergOAuth2Scope.tsx new file mode 100644 index 00000000..069863c6 --- /dev/null +++ b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionAuthIcebergOAuth2Scope.tsx @@ -0,0 +1,33 @@ +import React, { ChangeEvent, useMemo, useState } from 'react'; +import { Form, FormListFieldData, FormListOperation, Input, Space } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; + +export const ConnectionAuthIcebergOAuth2Scope = ({ + field, + remove, +}: { + field: FormListFieldData; + remove: FormListOperation['remove']; +}) => { + const formInstance = Form.useFormInstance(); + + /** Use custom type state, because Form.useWatch doesn't support dynamic fieldname like in Form.List */ + const initialValue = useMemo(() => { + return formInstance.getFieldValue(['auth_data', 'rest_catalog_oauth2_scopes', field.name]); + }, [formInstance, field]); + + const [value, setValue] = useState(() => initialValue); + + const handleValueChange = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + return ( + + + + remove(field.name)} /> + + + ); +}; diff --git a/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionIcebergS3Delegated.tsx b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionIcebergS3Delegated.tsx new file mode 100644 index 00000000..711f85d0 --- /dev/null +++ b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionIcebergS3Delegated.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Form, Input, Radio } from 'antd'; +import { useTranslation } from 'react-i18next'; + +export const ConnectionIcebergS3Delegated = () => { + const { t } = useTranslation('connection'); + + return ( + <> + + + + + + {t('s3.vendedCredentials')} + {t('s3.remoteSigning')} + + + + ); +}; diff --git a/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionIcebergS3Direct.tsx b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionIcebergS3Direct.tsx new file mode 100644 index 00000000..00b15691 --- /dev/null +++ b/src/entities/connection/ui/ConnectionTypeForm/components/ConnectionIceberg/ConnectionIcebergS3Direct.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Form, Input, Radio } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import { useSensitiveFields } from '../../hooks'; +import { ConnectionHttpProtocol } from '../ConnectionHttpProtocol'; + +export const ConnectionIcebergS3Direct = () => { + const { t } = useTranslation('connection'); + const { isRequired } = useSensitiveFields(); + + return ( + <> + + + + + + + + + + + + + {t('s3.bucketStyleDomain')} + {t('s3.bucketStylePath')} + + + + + + +