diff --git a/config-ui/src/plugins/register/q-dev/config.tsx b/config-ui/src/plugins/register/q-dev/config.tsx index cc1e8951184..836d6417d1e 100644 --- a/config-ui/src/plugins/register/q-dev/config.tsx +++ b/config-ui/src/plugins/register/q-dev/config.tsx @@ -31,6 +31,7 @@ export const QDevConfig: IPluginConfig = { docLink: 'https://devlake.apache.org/docs/UserManual/plugins/qdev', initialValues: { name: '', + authType: 'access_key', accessKeyId: '', secretAccessKey: '', region: 'us-east-1', diff --git a/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx b/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx index c5fd1de1087..19953d4459b 100644 --- a/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx +++ b/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx @@ -17,7 +17,7 @@ */ import { ChangeEvent, useEffect, useMemo, useRef } from 'react'; -import { Input } from 'antd'; +import { Input, Radio } from 'antd'; import { Block } from '@/components'; @@ -32,7 +32,12 @@ interface Props { const ACCESS_KEY_PATTERN = /^[A-Z0-9]{16,32}$/; const REGION_PATTERN = /^[a-z]{2}-[a-z]+-\d$/; -const syncError = (key: string, error: string, setErrors: (errors: any) => void, ref: React.MutableRefObject) => { +const syncError = ( + key: string, + error: string, + setErrors: (errors: any) => void, + ref: React.MutableRefObject, +) => { if (ref.current !== error) { ref.current = error; setErrors({ [key]: error }); @@ -42,9 +47,18 @@ const syncError = (key: string, error: string, setErrors: (errors: any) => void, export const AwsCredentials = ({ type, initialValues, values, setValues, setErrors }: Props) => { const isUpdate = type === 'update'; + const authType = values.authType ?? 'access_key'; const accessKeyId = values.accessKeyId ?? ''; const secretAccessKey = values.secretAccessKey ?? ''; const region = values.region ?? ''; + + const isAccessKeyAuth = authType === 'access_key'; + + useEffect(() => { + if (values.authType === undefined) { + setValues({ authType: initialValues.authType ?? 'access_key' }); + } + }, [initialValues.authType, values.authType, setValues]); useEffect(() => { if (values.accessKeyId === undefined) { @@ -65,31 +79,33 @@ export const AwsCredentials = ({ type, initialValues, values, setValues, setErro }, [initialValues.region, values.region, setValues]); const accessKeyError = useMemo(() => { + if (!isAccessKeyAuth) return ''; // Not required for IAM role auth if (!accessKeyId) { - return isUpdate ? '' : 'AWS Access Key ID is required.'; + return isUpdate ? '' : 'AWS Access Key ID is required'; } if (!ACCESS_KEY_PATTERN.test(accessKeyId)) { - return 'AWS Access Key ID must contain 16-32 upper case letters or digits.'; + return 'AWS Access Key ID must contain 16-32 uppercase letters or digits'; } return ''; - }, [accessKeyId, isUpdate]); + }, [accessKeyId, isUpdate, isAccessKeyAuth]); const secretKeyError = useMemo(() => { + if (!isAccessKeyAuth) return ''; // Not required for IAM role auth if (!secretAccessKey) { - return isUpdate ? '' : 'AWS Secret Access Key is required.'; + return isUpdate ? '' : 'AWS Secret Access Key is required'; } if (secretAccessKey && secretAccessKey.length < 40) { - return 'AWS Secret Access Key looks too short.'; + return 'AWS Secret Access Key looks too short'; } return ''; - }, [secretAccessKey, isUpdate]); + }, [secretAccessKey, isUpdate, isAccessKeyAuth]); const regionError = useMemo(() => { if (!region) { - return 'AWS Region is required.'; + return 'AWS Region is required'; } if (!REGION_PATTERN.test(region)) { - return 'AWS Region should look like us-east-1.'; + return 'AWS Region should look like us-east-1'; } return ''; }, [region]); @@ -122,31 +138,67 @@ export const AwsCredentials = ({ type, initialValues, values, setValues, setErro setValues({ region: e.target.value.trim() }); }; + const handleAuthTypeChange = (e: any) => { + const newAuthType = e.target.value; + setValues({ authType: newAuthType }); + + // Clear access key fields when switching to IAM role + if (newAuthType === 'iam_role') { + setValues({ + authType: newAuthType, + accessKeyId: '', + secretAccessKey: '' + }); + } + }; + return ( <> - - - {accessKeyError &&
{accessKeyError}
} -
- - - - {secretKeyError &&
{secretKeyError}
} + + + Access Key & Secret + IAM Role (for EC2/ECS/Lambda) + - + {isAccessKeyAuth && ( + <> + + + {accessKeyError &&
{accessKeyError}
} +
+ + + + {secretKeyError &&
{secretKeyError}
} +
+ + )} + + {!isAccessKeyAuth && ( + +
+

+ Make sure the IAM role has the necessary S3 permissions to access your bucket. + No additional credentials are required when using IAM role authentication. +

+
+
+ )} + + { +export const QDevDataScope = ({ + connectionId: _connectionId, + disabledItems, + selectedItems, + onChangeSelectedItems, +}: Props) => { const [form] = Form.useForm(); const disabledIds = useMemo(() => new Set(disabledItems?.map((it) => String(it.id)) ?? []), [disabledItems]); @@ -234,7 +239,9 @@ export const QDevDataScope = ({ connectionId: _connectionId, disabledItems, sele return; } - const uniqueMonths = Array.from(new Set(months)).map((m) => Number(m)).filter((m) => !Number.isNaN(m)); + const uniqueMonths = Array.from(new Set(months)) + .map((m) => Number(m)) + .filter((m) => !Number.isNaN(m)); uniqueMonths.sort((a, b) => a - b); uniqueMonths.forEach((month) => { @@ -289,7 +296,11 @@ export const QDevDataScope = ({ connectionId: _connectionId, disabledItems, sele key: 'basePath', render: (_: unknown, item) => { const meta = extractScopeMeta(item); - return meta.basePath ? {meta.basePath} : (bucket root); + return meta.basePath ? ( + {meta.basePath} + ) : ( + (bucket root) + ); }, }, { @@ -340,12 +351,7 @@ export const QDevDataScope = ({ connectionId: _connectionId, disabledItems, sele - + diff --git a/config-ui/src/plugins/register/q-dev/index.ts b/config-ui/src/plugins/register/q-dev/index.ts index 257c4bc7dd5..de415db39ab 100644 --- a/config-ui/src/plugins/register/q-dev/index.ts +++ b/config-ui/src/plugins/register/q-dev/index.ts @@ -16,4 +16,4 @@ * */ -export * from './config'; \ No newline at end of file +export * from './config';