From caa2de334422830bd6ae730349701d7c52a13e04 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 21 Jul 2025 17:40:19 +0800 Subject: [PATCH] datasource oauth --- .../data-source-page-new/card.tsx | 115 ++++++++++++++- .../data-source-page-new/configure.tsx | 108 ++++++++++---- .../data-source-page-new/hooks/index.ts | 2 + .../hooks/use-data-source-auth-update.ts | 16 ++ .../use-marketplace-all-plugins.ts} | 0 .../data-source-page-new/item.tsx | 82 ++++++++++- .../data-source-page-new/operator.tsx | 114 +++++++++++---- .../data-source-page-new/types.ts | 24 ++- .../authorize/add-api-key-button.tsx | 6 +- .../authorize/add-oauth-button.tsx | 18 ++- .../plugin-auth/authorize/api-key-modal.tsx | 19 ++- .../authorized-in-data-source-node.tsx | 43 ++++++ .../plugins/plugin-auth/hooks/use-get-api.ts | 16 ++ .../hooks/use-plugin-auth-action.ts | 124 ++++++++++++++++ .../components/plugins/plugin-auth/index.tsx | 6 + .../plugin-auth-in-datasource-node.tsx | 39 +++++ .../components/plugins/plugin-auth/types.ts | 3 + .../_base/components/workflow-panel/index.tsx | 34 ++++- .../components/config-credential.tsx | 137 ------------------ .../workflow/nodes/data-source/panel.tsx | 54 +------ web/service/use-datasource.ts | 6 + web/service/use-pipeline.ts | 5 + web/service/use-plugins-auth.ts | 2 + 23 files changed, 701 insertions(+), 272 deletions(-) create mode 100644 web/app/components/header/account-setting/data-source-page-new/hooks/index.ts create mode 100644 web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts rename web/app/components/header/account-setting/data-source-page-new/{hooks.ts => hooks/use-marketplace-all-plugins.ts} (100%) create mode 100644 web/app/components/plugins/plugin-auth/authorized-in-data-source-node.tsx create mode 100644 web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts create mode 100644 web/app/components/plugins/plugin-auth/plugin-auth-in-datasource-node.tsx delete mode 100644 web/app/components/workflow/nodes/data-source/components/config-credential.tsx diff --git a/web/app/components/header/account-setting/data-source-page-new/card.tsx b/web/app/components/header/account-setting/data-source-page-new/card.tsx index 6593bd1a59..31f9ef6961 100644 --- a/web/app/components/header/account-setting/data-source-page-new/card.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/card.tsx @@ -1,15 +1,32 @@ -import { memo } from 'react' +import { + memo, + useCallback, +} from 'react' +import { useTranslation } from 'react-i18next' import Item from './item' import Configure from './configure' -import type { DataSourceAuth } from './types' +import type { + DataSourceAuth, + DataSourceCredential, +} from './types' import { useRenderI18nObject } from '@/hooks/use-i18n' +import { AuthCategory } from '@/app/components/plugins/plugin-auth/types' +import { + ApiKeyModal, + usePluginAuthAction, +} from '@/app/components/plugins/plugin-auth' +import { useDataSourceAuthUpdate } from './hooks' +import Confirm from '@/app/components/base/confirm' type CardProps = { item: DataSourceAuth + disabled?: boolean } const Card = ({ item, + disabled, }: CardProps) => { + const { t } = useTranslation() const renderI18nObject = useRenderI18nObject() const { icon, @@ -17,7 +34,56 @@ const Card = ({ author, provider, credentials_list, + credential_schema, } = item + const pluginPayload = { + category: AuthCategory.datasource, + provider: item.name, + } + const { handleAuthUpdate } = useDataSourceAuthUpdate() + const { + deleteCredentialId, + doingAction, + handleConfirm, + handleEdit, + handleRemove, + handleRename, + handleSetDefault, + editValues, + setEditValues, + openConfirm, + closeConfirm, + pendingOperationCredentialId, + } = usePluginAuthAction(pluginPayload, handleAuthUpdate) + const handleAction = useCallback(( + action: string, + credentialItem: DataSourceCredential, + renamePayload?: Record, + ) => { + if (action === 'edit') { + handleEdit( + credentialItem.id, + { + ...credentialItem.credential, + __name__: credentialItem.name, + __credential_id__: credentialItem.id, + }, + ) + } + if (action === 'delete') + openConfirm(credentialItem.id) + + if (action === 'setDefault') + handleSetDefault(credentialItem.id) + + if (action === 'rename') + handleRename(renamePayload as any) + }, [ + openConfirm, + handleEdit, + handleSetDefault, + handleRename, + ]) return (
@@ -36,7 +102,11 @@ const Card = ({ {provider}
- +
Connected workspace @@ -45,9 +115,15 @@ const Card = ({ { !!credentials_list.length && (
- - - + { + credentials_list.map(credentialItem => ( + + )) + }
) } @@ -60,6 +136,33 @@ const Card = ({
) } + { + deleteCredentialId && ( + + ) + } + { + !!editValues && ( + { + setEditValues(null) + pendingOperationCredentialId.current = null + }} + onUpdate={handleAuthUpdate} + formSchemas={credential_schema} + editValues={editValues} + onRemove={handleRemove} + disabled={disabled || doingAction} + /> + ) + } ) } diff --git a/web/app/components/header/account-setting/data-source-page-new/configure.tsx b/web/app/components/header/account-setting/data-source-page-new/configure.tsx index e612d20954..f0f98da550 100644 --- a/web/app/components/header/account-setting/data-source-page-new/configure.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/configure.tsx @@ -1,16 +1,58 @@ -import { memo } from 'react' +import { + memo, + useMemo, +} from 'react' import { RiAddLine, - RiEqualizer2Line, } from '@remixicon/react' +import { useTranslation } from 'react-i18next' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import Button from '@/app/components/base/button' +import { + AddApiKeyButton, + AddOAuthButton, +} from '@/app/components/plugins/plugin-auth' +import type { DataSourceAuth } from './types' +import type { + AddApiKeyButtonProps, + AddOAuthButtonProps, + PluginPayload, +} from '@/app/components/plugins/plugin-auth/types' + +type ConfigureProps = { + item: DataSourceAuth + pluginPayload: PluginPayload + onUpdate?: () => void + disabled?: boolean +} +const Configure = ({ + item, + pluginPayload, + onUpdate, + disabled, +}: ConfigureProps) => { + const { t } = useTranslation() + const canApiKey = item.credential_schema?.length + const oAuthData = item.oauth_schema || {} + const canOAuth = oAuthData.client_schema?.length + const oAuthButtonProps: AddOAuthButtonProps = useMemo(() => { + return { + buttonText: t('plugin.auth.addOAuth'), + pluginPayload, + } + }, [pluginPayload, t]) + + const apiKeyButtonProps: AddApiKeyButtonProps = useMemo(() => { + return { + pluginPayload, + buttonText: t('plugin.auth.addApi'), + } + }, [pluginPayload, t]) -const Configure = () => { return ( <> { variant='secondary-accent' > - Configure + {t('common.dataSource.configure')}
- -
-
- OR -
-
- + { + canOAuth && ( + + ) + } + { + canApiKey && canOAuth && ( +
+
+ OR +
+
+ ) + } + { + canApiKey && ( + + ) + }
diff --git a/web/app/components/header/account-setting/data-source-page-new/hooks/index.ts b/web/app/components/header/account-setting/data-source-page-new/hooks/index.ts new file mode 100644 index 0000000000..8ba0b234fd --- /dev/null +++ b/web/app/components/header/account-setting/data-source-page-new/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './use-marketplace-all-plugins' +export * from './use-data-source-auth-update' diff --git a/web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts b/web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts new file mode 100644 index 0000000000..46d383e331 --- /dev/null +++ b/web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts @@ -0,0 +1,16 @@ +import { useCallback } from 'react' +import { useInvalidDataSourceListAuth } from '@/service/use-datasource' +import { useInvalidDataSourceList } from '@/service/use-pipeline' + +export const useDataSourceAuthUpdate = () => { + const invalidateDataSourceListAuth = useInvalidDataSourceListAuth() + const invalidateDataSourceList = useInvalidDataSourceList() + const handleAuthUpdate = useCallback(() => { + invalidateDataSourceListAuth() + invalidateDataSourceList() + }, [invalidateDataSourceListAuth, invalidateDataSourceList]) + + return { + handleAuthUpdate, + } +} diff --git a/web/app/components/header/account-setting/data-source-page-new/hooks.ts b/web/app/components/header/account-setting/data-source-page-new/hooks/use-marketplace-all-plugins.ts similarity index 100% rename from web/app/components/header/account-setting/data-source-page-new/hooks.ts rename to web/app/components/header/account-setting/data-source-page-new/hooks/use-marketplace-all-plugins.ts diff --git a/web/app/components/header/account-setting/data-source-page-new/item.tsx b/web/app/components/header/account-setting/data-source-page-new/item.tsx index fdc73ae692..b70945d8f4 100644 --- a/web/app/components/header/account-setting/data-source-page-new/item.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/item.tsx @@ -1,14 +1,79 @@ -import { memo } from 'react' +import { + memo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' import Indicator from '@/app/components/header/indicator' import Operator from './operator' +import type { + DataSourceCredential, +} from './types' +import Input from '@/app/components/base/input' +import Button from '@/app/components/base/button' + +type ItemProps = { + credentialItem: DataSourceCredential + onAction: (action: string, credentialItem: DataSourceCredential, renamePayload?: Record) => void +} +const Item = ({ + credentialItem, + onAction, +}: ItemProps) => { + const { t } = useTranslation() + const [renaming, setRenaming] = useState(false) + const [renameValue, setRenameValue] = useState(credentialItem.name) -const Item = () => { return (
-
-
- Evan’s Notion -
+ {/*
*/} + { + renaming && ( +
+ setRenameValue(e.target.value)} + placeholder={t('common.placeholder.input')} + onClick={e => e.stopPropagation()} + /> + + +
+ ) + } + { + !renaming && ( +
+ {credentialItem.name} +
+ ) + }
@@ -18,7 +83,10 @@ const Item = () => {
- +
) } diff --git a/web/app/components/header/account-setting/data-source-page-new/operator.tsx b/web/app/components/header/account-setting/data-source-page-new/operator.tsx index c198a57b0b..9a4bc0c41a 100644 --- a/web/app/components/header/account-setting/data-source-page-new/operator.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/operator.tsx @@ -3,40 +3,94 @@ import { useCallback, useMemo, } from 'react' +import { useTranslation } from 'react-i18next' import { RiDeleteBinLine, + RiEditLine, + RiEqualizer2Line, + RiHome9Line, RiLoopLeftLine, RiStickyNoteAddLine, } from '@remixicon/react' import Dropdown from '@/app/components/base/dropdown' import type { Item } from '@/app/components/base/dropdown' +import type { + DataSourceCredential, +} from './types' +import { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth/types' -const Operator = () => { +type OperatorProps = { + credentialItem: DataSourceCredential + onAction: (action: string, credentialItem: DataSourceCredential) => void + onRename?: () => void +} +const Operator = ({ + credentialItem, + onAction, + onRename, +}: OperatorProps) => { + const { t } = useTranslation() + const { + type, + } = credentialItem const items = useMemo(() => { - return [ + const commonItems = [ { - value: 'change', + value: 'setDefault', text: ( -
- -
-
Change authorized pages
-
18 Pages authorized
-
+
+ +
{t('plugin.auth.setDefault')}
), }, { - value: 'sync', + value: 'rename', text: (
- -
Sync
+ +
{t('common.operation.rename')}
+
+ ), + }, + { + value: 'edit', + text: ( +
+ +
{t('common.operation.edit')}
), }, ] - }, []) + if (type === CredentialTypeEnum.OAUTH2) { + const oAuthItems = [ + { + value: 'change', + text: ( +
+ +
+
{t('common.dataSource.notion.changeAuthorizedPages')}
+
18 {t('common.dataSource.notion.pagesAuthorized')}
+
+
+ ), + }, + { + value: 'sync', + text: ( +
+ +
{t('common.dataSource.notion.sync')}
+
+ ), + }, + ] + commonItems.push(...oAuthItems) + } + return commonItems + }, [t, type]) const secondItems = useMemo(() => { return [ @@ -51,23 +105,29 @@ const Operator = () => { }, ] }, []) - const handleSelect = useCallback((item: Item) => { - console.log('Selected item:', item) - }, []) + if (item.value === 'rename') { + onRename?.() + return + } + onAction( + item.value as string, + credentialItem, + ) + }, [onAction, credentialItem, onRename]) return ( - + ) } diff --git a/web/app/components/header/account-setting/data-source-page-new/types.ts b/web/app/components/header/account-setting/data-source-page-new/types.ts index 7348c763e8..8812b9a045 100644 --- a/web/app/components/header/account-setting/data-source-page-new/types.ts +++ b/web/app/components/header/account-setting/data-source-page-new/types.ts @@ -1,6 +1,12 @@ +import type { + FormSchema, + TypeWithI18N, +} from '@/app/components/base/form/types' +import type { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth/types' + export type DataSourceCredential = { credential: Record - type: string + type: CredentialTypeEnum name: string id: string } @@ -9,14 +15,18 @@ export type DataSourceAuth = { provider: string plugin_id: string plugin_unique_identifier: string - icon: any + icon: string name: string - label: any - description: any - credential_schema?: any[] + label: TypeWithI18N + description: TypeWithI18N + credential_schema?: FormSchema[] oauth_schema?: { - client_schema?: any[] - credentials_schema?: any[] + client_schema?: FormSchema[] + credentials_schema?: FormSchema[] + is_oauth_custom_client_enabled?: boolean + is_system_oauth_params_exists?: boolean + oauth_custom_client_params?: Record + redirect_uri?: string } credentials_list: DataSourceCredential[] } diff --git a/web/app/components/plugins/plugin-auth/authorize/add-api-key-button.tsx b/web/app/components/plugins/plugin-auth/authorize/add-api-key-button.tsx index 295fc4fa9d..19f85b3c38 100644 --- a/web/app/components/plugins/plugin-auth/authorize/add-api-key-button.tsx +++ b/web/app/components/plugins/plugin-auth/authorize/add-api-key-button.tsx @@ -6,6 +6,7 @@ import Button from '@/app/components/base/button' import type { ButtonProps } from '@/app/components/base/button' import ApiKeyModal from './api-key-modal' import type { PluginPayload } from '../types' +import type { FormSchema } from '@/app/components/base/form/types' export type AddApiKeyButtonProps = { pluginPayload: PluginPayload @@ -13,13 +14,15 @@ export type AddApiKeyButtonProps = { buttonText?: string disabled?: boolean onUpdate?: () => void + formSchemas?: FormSchema[] } const AddApiKeyButton = ({ pluginPayload, buttonVariant = 'secondary-accent', - buttonText = 'use api key', + buttonText = 'Use Api Key', disabled, onUpdate, + formSchemas = [], }: AddApiKeyButtonProps) => { const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false) @@ -39,6 +42,7 @@ const AddApiKeyButton = ({ pluginPayload={pluginPayload} onClose={() => setIsApiKeyModalOpen(false)} onUpdate={onUpdate} + formSchemas={formSchemas} /> ) } diff --git a/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx b/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx index 599d701341..e59be4dd2d 100644 --- a/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx +++ b/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx @@ -36,6 +36,13 @@ export type AddOAuthButtonProps = { dividerClassName?: string disabled?: boolean onUpdate?: () => void + oAuthData?: { + schema?: FormSchema[] + is_oauth_custom_client_enabled?: boolean + is_system_oauth_params_exists?: boolean + client_params?: Record + redirect_uri?: string + } } const AddOAuthButton = ({ pluginPayload, @@ -47,19 +54,26 @@ const AddOAuthButton = ({ dividerClassName, disabled, onUpdate, + oAuthData, }: AddOAuthButtonProps) => { const { t } = useTranslation() const renderI18nObject = useRenderI18nObject() const [isOAuthSettingsOpen, setIsOAuthSettingsOpen] = useState(false) const { mutateAsync: getPluginOAuthUrl } = useGetPluginOAuthUrlHook(pluginPayload) const { data, isLoading } = useGetPluginOAuthClientSchemaHook(pluginPayload) + const mergedOAuthData = useMemo(() => { + if (oAuthData) + return oAuthData + + return data + }, [oAuthData, data]) const { schema = [], is_oauth_custom_client_enabled, is_system_oauth_params_exists, client_params, redirect_uri, - } = data || {} + } = mergedOAuthData as any const isConfigured = is_system_oauth_params_exists || is_oauth_custom_client_enabled const handleOAuth = useCallback(async () => { const { authorization_url } = await getPluginOAuthUrl() @@ -112,7 +126,7 @@ const AddOAuthButton = ({ ) }, [t, redirect_uri, renderI18nObject]) const memorizedSchemas = useMemo(() => { - const result: FormSchema[] = schema.map((item, index) => { + const result: FormSchema[] = (schema as FormSchema[]).map((item, index) => { return { ...item, label: index === 0 ? renderCustomLabel(item) : item.label, diff --git a/web/app/components/plugins/plugin-auth/authorize/api-key-modal.tsx b/web/app/components/plugins/plugin-auth/authorize/api-key-modal.tsx index d582c660b6..b43bfb1970 100644 --- a/web/app/components/plugins/plugin-auth/authorize/api-key-modal.tsx +++ b/web/app/components/plugins/plugin-auth/authorize/api-key-modal.tsx @@ -11,7 +11,10 @@ import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' import Modal from '@/app/components/base/modal/modal' import { CredentialTypeEnum } from '../types' import AuthForm from '@/app/components/base/form/form-scenarios/auth' -import type { FormRefObject } from '@/app/components/base/form/types' +import type { + FormRefObject, + FormSchema, +} from '@/app/components/base/form/types' import { FormTypeEnum } from '@/app/components/base/form/types' import { useToastContext } from '@/app/components/base/toast' import Loading from '@/app/components/base/loading' @@ -30,6 +33,7 @@ export type ApiKeyModalProps = { onRemove?: () => void disabled?: boolean onUpdate?: () => void + formSchemas?: FormSchema[] } const ApiKeyModal = ({ pluginPayload, @@ -38,6 +42,7 @@ const ApiKeyModal = ({ onRemove, disabled, onUpdate, + formSchemas: formSchemasFromProps = [], }: ApiKeyModalProps) => { const { t } = useTranslation() const { notify } = useToastContext() @@ -48,6 +53,12 @@ const ApiKeyModal = ({ setDoingAction(value) }, []) const { data = [], isLoading } = useGetPluginCredentialSchemaHook(pluginPayload, CredentialTypeEnum.API_KEY) + const mergedData = useMemo(() => { + if (formSchemasFromProps?.length) + return formSchemasFromProps + + return data + }, [formSchemasFromProps, data]) const formSchemas = useMemo(() => { return [ { @@ -56,9 +67,9 @@ const ApiKeyModal = ({ label: t('plugin.auth.authorizationName'), required: false, }, - ...data, + ...mergedData, ] - }, [data, t]) + }, [mergedData, t]) const defaultValues = formSchemas.reduce((acc, schema) => { if (schema.default) acc[schema.name] = schema.default @@ -165,7 +176,7 @@ const ApiKeyModal = ({ ) } { - !isLoading && !!data.length && ( + !isLoading && !!mergedData.length && ( void +} +const AuthorizedInDataSourceNode = ({ + authorizationsNum, + onJumpToDataSourcePage, +}: AuthorizedInDataSourceNodeProps) => { + const { t } = useTranslation() + + return ( + + ) +} + +export default memo(AuthorizedInDataSourceNode) diff --git a/web/app/components/plugins/plugin-auth/hooks/use-get-api.ts b/web/app/components/plugins/plugin-auth/hooks/use-get-api.ts index 14199ddc4d..fa0a21dcc1 100644 --- a/web/app/components/plugins/plugin-auth/hooks/use-get-api.ts +++ b/web/app/components/plugins/plugin-auth/hooks/use-get-api.ts @@ -24,6 +24,22 @@ export const useGetApi = ({ category = AuthCategory.tool, provider }: PluginPayl } } + if (category === AuthCategory.datasource) { + return { + getCredentialInfo: '', + setDefaultCredential: `/auth/plugin/datasource/${provider}/default`, + getCredentials: `/auth/plugin/datasource/${provider}`, + addCredential: `/auth/plugin/datasource/${provider}`, + updateCredential: `/auth/plugin/datasource/${provider}/update`, + deleteCredential: `/auth/plugin/datasource/${provider}/delete`, + getCredentialSchema: () => '', + getOauthUrl: `/oauth/plugin/${provider}/datasource/get-authorization-url`, + getOauthClientSchema: '', + setCustomOauthClient: `/auth/plugin/datasource/${provider}/custom-client`, + deleteCustomOAuthClient: `/auth/plugin/datasource/${provider}/custom-client`, + } + } + return { getCredentialInfo: '', setDefaultCredential: '', diff --git a/web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts b/web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts new file mode 100644 index 0000000000..f07b918c66 --- /dev/null +++ b/web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts @@ -0,0 +1,124 @@ +import { + useCallback, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useToastContext } from '@/app/components/base/toast' +import type { PluginPayload } from '../types' +import { + useDeletePluginCredentialHook, + useSetPluginDefaultCredentialHook, + useUpdatePluginCredentialHook, +} from '../hooks/use-credential' + +export const usePluginAuthAction = ( + pluginPayload: PluginPayload, + onUpdate?: () => void, +) => { + const { t } = useTranslation() + const { notify } = useToastContext() + const pendingOperationCredentialId = useRef(null) + const [deleteCredentialId, setDeleteCredentialId] = useState(null) + const { mutateAsync: deletePluginCredential } = useDeletePluginCredentialHook(pluginPayload) + const openConfirm = useCallback((credentialId?: string) => { + if (credentialId) + pendingOperationCredentialId.current = credentialId + + setDeleteCredentialId(pendingOperationCredentialId.current) + }, []) + const closeConfirm = useCallback(() => { + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + }, []) + const [doingAction, setDoingAction] = useState(false) + const doingActionRef = useRef(doingAction) + const handleSetDoingAction = useCallback((doing: boolean) => { + doingActionRef.current = doing + setDoingAction(doing) + }, []) + const handleConfirm = useCallback(async () => { + if (doingActionRef.current) + return + if (!pendingOperationCredentialId.current) { + setDeleteCredentialId(null) + return + } + try { + handleSetDoingAction(true) + await deletePluginCredential({ credential_id: pendingOperationCredentialId.current }) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + } + finally { + handleSetDoingAction(false) + } + }, [deletePluginCredential, onUpdate, notify, t, handleSetDoingAction]) + const [editValues, setEditValues] = useState | null>(null) + const handleEdit = useCallback((id: string, values: Record) => { + pendingOperationCredentialId.current = id + setEditValues(values) + }, []) + const handleRemove = useCallback(() => { + setDeleteCredentialId(pendingOperationCredentialId.current) + }, []) + const { mutateAsync: setPluginDefaultCredential } = useSetPluginDefaultCredentialHook(pluginPayload) + const handleSetDefault = useCallback(async (id: string) => { + if (doingActionRef.current) + return + try { + handleSetDoingAction(true) + await setPluginDefaultCredential(id) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + } + finally { + handleSetDoingAction(false) + } + }, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction]) + const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) + const handleRename = useCallback(async (payload: { + credential_id: string + name: string + }) => { + if (doingActionRef.current) + return + try { + handleSetDoingAction(true) + await updatePluginCredential(payload) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + } + finally { + handleSetDoingAction(false) + } + }, [updatePluginCredential, notify, t, handleSetDoingAction, onUpdate]) + + return { + doingAction, + handleSetDoingAction, + openConfirm, + closeConfirm, + deleteCredentialId, + setDeleteCredentialId, + handleConfirm, + editValues, + setEditValues, + handleEdit, + handleRemove, + handleSetDefault, + handleRename, + pendingOperationCredentialId, + } +} diff --git a/web/app/components/plugins/plugin-auth/index.tsx b/web/app/components/plugins/plugin-auth/index.tsx index e4f6ae8b2f..ee6f839590 100644 --- a/web/app/components/plugins/plugin-auth/index.tsx +++ b/web/app/components/plugins/plugin-auth/index.tsx @@ -3,4 +3,10 @@ export { default as Authorized } from './authorized' export { default as AuthorizedInNode } from './authorized-in-node' export { default as PluginAuthInAgent } from './plugin-auth-in-agent' export { usePluginAuth } from './hooks/use-plugin-auth' +export { default as PluginAuthInDataSourceNode } from './plugin-auth-in-datasource-node' +export { default as AuthorizedInDataSourceNode } from './authorized-in-data-source-node' +export { default as AddOAuthButton } from './authorize/add-oauth-button' +export { default as AddApiKeyButton } from './authorize/add-api-key-button' +export { default as ApiKeyModal } from './authorize/api-key-modal' +export * from './hooks/use-plugin-auth-action' export * from './types' diff --git a/web/app/components/plugins/plugin-auth/plugin-auth-in-datasource-node.tsx b/web/app/components/plugins/plugin-auth/plugin-auth-in-datasource-node.tsx new file mode 100644 index 0000000000..b592a377e4 --- /dev/null +++ b/web/app/components/plugins/plugin-auth/plugin-auth-in-datasource-node.tsx @@ -0,0 +1,39 @@ +import { memo } from 'react' +import type { ReactNode } from 'react' +import { useTranslation } from 'react-i18next' +import { RiAddLine } from '@remixicon/react' +import Button from '@/app/components/base/button' + +type PluginAuthInDataSourceNodeProps = { + children?: ReactNode + isAuthorized?: boolean + onJumpToDataSourcePage: () => void +} +const PluginAuthInDataSourceNode = ({ + children, + isAuthorized, + onJumpToDataSourcePage, +}: PluginAuthInDataSourceNodeProps) => { + const { t } = useTranslation() + return ( + <> + { + !isAuthorized && ( +
+ +
+ ) + } + {isAuthorized && children} + + ) +} + +export default memo(PluginAuthInDataSourceNode) diff --git a/web/app/components/plugins/plugin-auth/types.ts b/web/app/components/plugins/plugin-auth/types.ts index ad41733bde..fe9f7a144e 100644 --- a/web/app/components/plugins/plugin-auth/types.ts +++ b/web/app/components/plugins/plugin-auth/types.ts @@ -1,3 +1,6 @@ +export type { AddApiKeyButtonProps } from './authorize/add-api-key-button' +export type { AddOAuthButtonProps } from './authorize/add-oauth-button' + export enum AuthCategory { tool = 'tool', datasource = 'datasource', diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index aa070b9a6a..4ddcb13e96 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -62,11 +62,15 @@ import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevice import { useHooksStore } from '@/app/components/workflow/hooks-store' import { FlowType } from '@/types/common' import { + AuthorizedInDataSourceNode, AuthorizedInNode, PluginAuth, + PluginAuthInDataSourceNode, } from '@/app/components/plugins/plugin-auth' import { AuthCategory } from '@/app/components/plugins/plugin-auth' import { canFindTool } from '@/utils' +import { DataSourceClassification } from '@/app/components/workflow/nodes/data-source/types' +import { useModalContext } from '@/context/modal-context' type BasePanelProps = { children: ReactNode @@ -240,6 +244,11 @@ const BasePanel: FC = ({ const showPluginAuth = useMemo(() => { return data.type === BlockEnum.Tool && currCollection?.allow_delete }, [currCollection, data.type]) + const dataSourceList = useStore(s => s.dataSourceList) + const currentDataSource = useMemo(() => { + if (data.type === BlockEnum.DataSource && data.provider_type !== DataSourceClassification.localFile) + return dataSourceList?.find(item => item.plugin_id === data.plugin_id) + }, [dataSourceList, data.plugin_id, data.type, data.provider_type]) const handleAuthorizationItemClick = useCallback((credential_id: string) => { handleNodeDataUpdateWithSyncDraft({ id, @@ -248,6 +257,10 @@ const BasePanel: FC = ({ }, }) }, [handleNodeDataUpdateWithSyncDraft, id]) + const { setShowAccountSettingModal } = useModalContext() + const handleJumpToDataSourcePage = useCallback(() => { + setShowAccountSettingModal({ payload: 'data-source' }) + }, [setShowAccountSettingModal]) if (logParams.showSpecialResultPanel) { return ( @@ -413,7 +426,26 @@ const BasePanel: FC = ({ ) } { - !showPluginAuth && ( + !!currentDataSource && ( + +
+ + +
+
+ ) + } + { + !showPluginAuth && !currentDataSource && (
void - onSaved: (value: Record) => void - isHideRemoveBtn?: boolean - onRemove?: () => void - isSaving?: boolean -} - -const ConfigCredential: FC = ({ - dataSourceItem, - onCancel, - onSaved, - isHideRemoveBtn, - onRemove = noop, - isSaving, -}) => { - const { t } = useTranslation() - const language = useLanguage() - const { - provider, - plugin_id, - credentialsSchema = [], - is_authorized, - } = dataSourceItem - const transformedCredentialsSchema = useMemo(() => { - return toolCredentialToFormSchemas(credentialsSchema) - }, [credentialsSchema]) - const [isLoading, setIsLoading] = useState(false) - const [tempCredential, setTempCredential] = useState({}) - const handleUpdateCredentials = useCallback((credentialValue: ToolCredential[]) => { - const defaultCredentials = addDefaultValue(credentialValue, transformedCredentialsSchema) - setTempCredential(defaultCredentials) - }, [transformedCredentialsSchema]) - useDataSourceCredentials(provider, plugin_id, handleUpdateCredentials) - - const handleSave = async () => { - for (const field of transformedCredentialsSchema) { - if (field.required && !tempCredential[field.name]) { - Toast.notify({ type: 'error', message: t('common.errorMsg.fieldRequired', { field: field.label[language] || field.label.en_US }) }) - return - } - } - setIsLoading(true) - try { - await onSaved(tempCredential) - setIsLoading(false) - } - finally { - setIsLoading(false) - } - } - - return ( - - {!transformedCredentialsSchema.length - ? - : ( - <> -
{ - setTempCredential(v) - }} - formSchemas={transformedCredentialsSchema as any} - isEditMode={true} - showOnVariableMap={{}} - validating={false} - inputClassName='!bg-components-input-bg-normal' - fieldMoreInfo={item => item.url - ? ( - {t('tools.howToGet')} - - ) - : null} - /> -
- { - (is_authorized && !isHideRemoveBtn) && ( - - ) - } - < div className='flex space-x-2'> - - -
-
- - ) - } - -
- } - isShowMask={true} - clickOutsideNotOpen={false} - /> - ) -} -export default memo(ConfigCredential) diff --git a/web/app/components/workflow/nodes/data-source/panel.tsx b/web/app/components/workflow/nodes/data-source/panel.tsx index 3725f2b0e4..8d9d0d17ec 100644 --- a/web/app/components/workflow/nodes/data-source/panel.tsx +++ b/web/app/components/workflow/nodes/data-source/panel.tsx @@ -6,13 +6,11 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { memo } from 'react' -import { useBoolean } from 'ahooks' import type { DataSourceNodeType } from './types' import { DataSourceClassification } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' import { BoxGroupField, - Group, } from '@/app/components/workflow/nodes/_base/components/layout' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import TagInput from '@/app/components/base/tag-input' @@ -26,18 +24,13 @@ import { WEBSITE_CRAWL_OUTPUT, } from './constants' import { useStore } from '@/app/components/workflow/store' -import Button from '@/app/components/base/button' -import ConfigCredential from './components/config-credential' import InputVarList from '@/app/components/workflow/nodes/tool/components/input-var-list' import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import type { Var } from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types' -import { useToastContext } from '@/app/components/base/toast' -import { useUpdateDataSourceCredentials } from '@/service/use-pipeline' const Panel: FC> = ({ id, data }) => { const { t } = useTranslation() - const { notify } = useToastContext() const { nodesReadOnly } = useNodesReadOnly() const dataSourceList = useStore(s => s.dataSourceList) const { @@ -55,11 +48,6 @@ const Panel: FC> = ({ id, data }) => { const isOnlineDocument = provider_type === DataSourceClassification.onlineDocument const isOnlineDrive = provider_type === DataSourceClassification.onlineDrive const currentDataSource = dataSourceList?.find(ds => ds.plugin_id === plugin_id) - const isAuthorized = !!currentDataSource?.is_authorized - const [showAuthModal, { - setTrue: openAuthModal, - setFalse: hideAuthModal, - }] = useBoolean(false) const currentDataSourceItem: any = currentDataSource?.tools.find(tool => tool.name === data.datasource_name) const formSchemas = useMemo(() => { return currentDataSourceItem ? toolParametersToFormSchemas(currentDataSourceItem.parameters) : [] @@ -77,40 +65,10 @@ const Panel: FC> = ({ id, data }) => { return varPayload.type !== VarType.arrayFile }, [currVarType]) - const { mutateAsync } = useUpdateDataSourceCredentials() - const handleAuth = useCallback(async (value: any) => { - await mutateAsync({ - provider: currentDataSource?.provider || '', - pluginId: currentDataSource?.plugin_id || '', - credentials: value, - name: 'd14249c6-abe3-47ad-b0f1-1e65a591e790', // todo: fake name field, need to be removed later - }) - - notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - hideAuthModal() - }, [currentDataSource, mutateAsync, notify, t, hideAuthModal]) - return (
{ - !isAuthorized && !showAuthModal && !isLocalFile && currentDataSource && ( - - - - ) - } - { - isAuthorized && !isLocalFile && !!formSchemas?.length && ( + currentDataSource?.is_authorized && !isLocalFile && !!formSchemas?.length && ( > = ({ id, data }) => { )) } - { - showAuthModal && !isLocalFile && ( - - ) - }
) } diff --git a/web/service/use-datasource.ts b/web/service/use-datasource.ts index 94e2988487..fbf5c3a649 100644 --- a/web/service/use-datasource.ts +++ b/web/service/use-datasource.ts @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query' import { get } from './base' +import { useInvalid } from './use-base' import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' const NAME_SPACE = 'data-source-auth' @@ -11,3 +12,8 @@ export const useGetDataSourceListAuth = () => { retry: 0, }) } + +export const useInvalidDataSourceListAuth = ( +) => { + return useInvalid([NAME_SPACE, 'list']) +} diff --git a/web/service/use-pipeline.ts b/web/service/use-pipeline.ts index 7badda6e63..977db151f1 100644 --- a/web/service/use-pipeline.ts +++ b/web/service/use-pipeline.ts @@ -32,6 +32,7 @@ import type { import type { DataSourceItem } from '@/app/components/workflow/block-selector/types' import type { ToolCredential } from '@/app/components/tools/types' import type { IconInfo } from '@/models/datasets' +import { useInvalid } from './use-base' const NAME_SPACE = 'pipeline' @@ -180,6 +181,10 @@ export const useDataSourceList = (enabled: boolean, onSuccess?: (v: DataSourceIt }) } +export const useInvalidDataSourceList = () => { + return useInvalid([NAME_SPACE, 'datasource']) +} + export const publishedPipelineInfoQueryKeyPrefix = [NAME_SPACE, 'published-pipeline'] export const usePublishedPipelineInfo = (pipelineId: string) => { diff --git a/web/service/use-plugins-auth.ts b/web/service/use-plugins-auth.ts index 2dc0260647..36f2a80adf 100644 --- a/web/service/use-plugins-auth.ts +++ b/web/service/use-plugins-auth.ts @@ -94,6 +94,7 @@ export const useGetPluginCredentialSchema = ( url: string, ) => { return useQuery({ + enabled: !!url, queryKey: [NAME_SPACE, 'credential-schema', url], queryFn: () => get(url), }) @@ -119,6 +120,7 @@ export const useGetPluginOAuthClientSchema = ( url: string, ) => { return useQuery({ + enabled: !!url, queryKey: [NAME_SPACE, 'oauth-client-schema', url], queryFn: () => get<{ schema: FormSchema[]