Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion src/entities/connection/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export type ConnectionData =
| ConnectionPostgres
| ConnectionClickhouse
| ConnectionMySql
| ConnectionMsSql;
| ConnectionMsSql
| ConnectionIceberg;

export type ConnectionBucketStyle = 'domain' | 'path';

Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions src/entities/connection/assets/iceberg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/entities/connection/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -15,6 +16,7 @@ export {
ClickhouseIcon,
HdfsIcon,
HiveIcon,
IcebergIcon,
MssqlIcon,
MysqlIcon,
OracleIcon,
Expand Down
4 changes: 4 additions & 0 deletions src/entities/connection/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
FtpIcon,
HdfsIcon,
HiveIcon,
IcebergIcon,
MssqlIcon,
MysqlIcon,
OracleIcon,
Expand All @@ -23,6 +24,7 @@ export const CONNECTION_TYPE_NAMES: Record<ConnectionType, string> = {
[ConnectionType.FTPS]: 'FTPS',
[ConnectionType.HDFS]: 'HDFS',
[ConnectionType.HIVE]: 'Hive',
[ConnectionType.ICEBERG]: 'Iceberg',
[ConnectionType.MSSQL]: 'MSSQL',
[ConnectionType.MYSQL]: 'MySQL',
[ConnectionType.ORACLE]: 'Oracle',
Expand All @@ -39,6 +41,7 @@ export const CONNECTION_ICONS: Record<ConnectionType, ReactNode> = {
[ConnectionType.FTPS]: <FtpIcon />,
[ConnectionType.HDFS]: <HdfsIcon />,
[ConnectionType.HIVE]: <HiveIcon />,
[ConnectionType.ICEBERG]: <IcebergIcon />,
[ConnectionType.MSSQL]: <MssqlIcon />,
[ConnectionType.MYSQL]: <MysqlIcon />,
[ConnectionType.ORACLE]: <OracleIcon />,
Expand All @@ -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] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -24,7 +24,7 @@ export const ConnectionHttpProtocol = () => {
<>
<Form.Item
label={t('protocol')}
name={['connection_data', 'protocol']}
name={['connection_data', `${fieldsPrefix}protocol`]}
rules={[{ required: true }]}
initialValue="https"
>
Expand All @@ -33,7 +33,7 @@ export const ConnectionHttpProtocol = () => {
<Radio.Button value="https">HTTPS</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label={t('port')} name={['connection_data', 'port']}>
<Form.Item label={t('port')} name={['connection_data', `${fieldsPrefix}port`]}>
<InputNumber size="large" min={MIN_ALLOWED_PORT} max={MAX_ALLOWED_PORT} placeholder={defaultPort} />
</Form.Item>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Form.Item
label={t('iceberg.token')}
name={['auth_data', 'rest_catalog_token']}
rules={[{ required: isRequired }]}
>
<Input.Password size="large" />
</Form.Item>
</>
);
};
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Form.Item
label={t('iceberg.oauth2ClientId')}
name={['auth_data', 'rest_catalog_oauth2_client_id']}
rules={[{ required: true }]}
>
<Input size="large" />
</Form.Item>
<Form.Item
label={t('iceberg.oauth2ClientSecret')}
name={['auth_data', 'rest_catalog_oauth2_client_secret']}
rules={[{ required: isRequired }]}
>
<Input.Password size="large" />
</Form.Item>
<Form.Item
label={t('iceberg.oauth2TokenEndpoint')}
name={['auth_data', 'rest_catalog_oauth2_token_endpoint']}
rules={[{ type: 'url' }]}
>
<Input
size="large"
placeholder="http://keycloak.mycompany.com/auth/realms/myrealm/protocol/openid-connect/token"
/>
</Form.Item>

<Form.List name={['auth_data', 'rest_catalog_oauth2_scopes']}>
{(fields, { add, remove }) => (
<Form.Item label={t('iceberg.oauth2Scopes')}>
<Button size="large" onClick={() => add()}>
{t('add', { ns: 'shared' })}
</Button>
<Space direction="vertical">
{fields.map((field) => (
<ConnectionAuthIcebergOAuth2Scope key={field.key} field={field} remove={remove} />
))}
</Space>
</Form.Item>
)}
</Form.List>

<Form.Item
label={t('iceberg.oauth2Resource')}
name={['auth_data', 'rest_catalog_oauth2_resource']}
rules={[{ type: 'string', whitespace: true }]}
>
<Input size="large" />
</Form.Item>
<Form.Item label={t('iceberg.oauth2Audience')} name={['auth_data', 'rest_catalog_oauth2_audience']}>
<Input size="large" />
</Form.Item>
</>
);
};
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>) => {
setValue(e.target.value);
};

return (
<Form.Item {...field} noStyle>
<Space>
<Input size="large" value={value} onChange={handleValueChange} />
<CloseOutlined onClick={() => remove(field.name)} />
</Space>
</Form.Item>
);
};
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Form.Item label={t('iceberg.warehouseName')} name={['connection_data', 's3_warehouse_name']}>
<Input size="large" placeholder="my-warehouse" />
</Form.Item>
<Form.Item
label={t('s3.accessDelegation')}
name={['connection_data', 's3_access_delegation']}
rules={[{ required: true }]}
initialValue="vended-credentials"
>
<Radio.Group>
<Radio.Button value="vended-credentials">{t('s3.vendedCredentials')}</Radio.Button>
<Radio.Button value="remote-signing">{t('s3.remoteSigning')}</Radio.Button>
</Radio.Group>
</Form.Item>
</>
);
};
Loading