mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 11:56:55 +08:00
datasource oauth
This commit is contained in:
parent
039a053027
commit
caa2de3344
@ -1,15 +1,32 @@
|
|||||||
import { memo } from 'react'
|
import {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import Item from './item'
|
import Item from './item'
|
||||||
import Configure from './configure'
|
import Configure from './configure'
|
||||||
import type { DataSourceAuth } from './types'
|
import type {
|
||||||
|
DataSourceAuth,
|
||||||
|
DataSourceCredential,
|
||||||
|
} from './types'
|
||||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
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 = {
|
type CardProps = {
|
||||||
item: DataSourceAuth
|
item: DataSourceAuth
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
const Card = ({
|
const Card = ({
|
||||||
item,
|
item,
|
||||||
|
disabled,
|
||||||
}: CardProps) => {
|
}: CardProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const renderI18nObject = useRenderI18nObject()
|
const renderI18nObject = useRenderI18nObject()
|
||||||
const {
|
const {
|
||||||
icon,
|
icon,
|
||||||
@ -17,7 +34,56 @@ const Card = ({
|
|||||||
author,
|
author,
|
||||||
provider,
|
provider,
|
||||||
credentials_list,
|
credentials_list,
|
||||||
|
credential_schema,
|
||||||
} = item
|
} = 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<string, any>,
|
||||||
|
) => {
|
||||||
|
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 (
|
return (
|
||||||
<div className='rounded-xl bg-background-section-burn'>
|
<div className='rounded-xl bg-background-section-burn'>
|
||||||
@ -36,7 +102,11 @@ const Card = ({
|
|||||||
{provider}
|
{provider}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Configure />
|
<Configure
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
item={item}
|
||||||
|
onUpdate={handleAuthUpdate}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='system-xs-medium flex h-4 items-center pl-3 text-text-tertiary'>
|
<div className='system-xs-medium flex h-4 items-center pl-3 text-text-tertiary'>
|
||||||
Connected workspace
|
Connected workspace
|
||||||
@ -45,9 +115,15 @@ const Card = ({
|
|||||||
{
|
{
|
||||||
!!credentials_list.length && (
|
!!credentials_list.length && (
|
||||||
<div className='space-y-1 p-3 pt-2'>
|
<div className='space-y-1 p-3 pt-2'>
|
||||||
<Item />
|
{
|
||||||
<Item />
|
credentials_list.map(credentialItem => (
|
||||||
<Item />
|
<Item
|
||||||
|
key={credentialItem.id}
|
||||||
|
credentialItem={credentialItem}
|
||||||
|
onAction={handleAction}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -60,6 +136,33 @@ const Card = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
deleteCredentialId && (
|
||||||
|
<Confirm
|
||||||
|
isShow
|
||||||
|
title={t('datasetDocuments.list.delete.title')}
|
||||||
|
isDisabled={doingAction}
|
||||||
|
onCancel={closeConfirm}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!!editValues && (
|
||||||
|
<ApiKeyModal
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
onClose={() => {
|
||||||
|
setEditValues(null)
|
||||||
|
pendingOperationCredentialId.current = null
|
||||||
|
}}
|
||||||
|
onUpdate={handleAuthUpdate}
|
||||||
|
formSchemas={credential_schema}
|
||||||
|
editValues={editValues}
|
||||||
|
onRemove={handleRemove}
|
||||||
|
disabled={disabled || doingAction}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,58 @@
|
|||||||
import { memo } from 'react'
|
import {
|
||||||
|
memo,
|
||||||
|
useMemo,
|
||||||
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
RiAddLine,
|
RiAddLine,
|
||||||
RiEqualizer2Line,
|
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
PortalToFollowElemTrigger,
|
PortalToFollowElemTrigger,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
import Button from '@/app/components/base/button'
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
@ -25,34 +67,46 @@ const Configure = () => {
|
|||||||
variant='secondary-accent'
|
variant='secondary-accent'
|
||||||
>
|
>
|
||||||
<RiAddLine className='h-4 w-4' />
|
<RiAddLine className='h-4 w-4' />
|
||||||
Configure
|
{t('common.dataSource.configure')}
|
||||||
</Button>
|
</Button>
|
||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className='z-[61]'>
|
<PortalToFollowElemContent className='z-[61]'>
|
||||||
<div className='w-[240px] space-y-1.5 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-lg'>
|
<div className='w-[240px] space-y-1.5 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-lg'>
|
||||||
<Button
|
{
|
||||||
variant='primary'
|
canOAuth && (
|
||||||
className='w-full px-0'
|
<AddOAuthButton
|
||||||
>
|
{...oAuthButtonProps}
|
||||||
<div className='grow'>
|
onUpdate={onUpdate}
|
||||||
use oauth
|
oAuthData={{
|
||||||
</div>
|
schema: oAuthData.client_schema || [],
|
||||||
<div className='h-4 w-[1px] bg-text-primary-on-surface opacity-[0.15]'></div>
|
is_oauth_custom_client_enabled: oAuthData.is_oauth_custom_client_enabled,
|
||||||
<div className='flex h-8 w-8 shrink-0 items-center justify-center'>
|
is_system_oauth_params_exists: oAuthData.is_system_oauth_params_exists,
|
||||||
<RiEqualizer2Line className='h-4 w-4' />
|
client_params: oAuthData.oauth_custom_client_params,
|
||||||
</div>
|
redirect_uri: oAuthData.redirect_uri,
|
||||||
</Button>
|
}}
|
||||||
<div className='system-2xs-medium-uppercase flex h-4 items-center p-2 text-text-quaternary'>
|
disabled={disabled}
|
||||||
<div className='mr-2 h-[1px] grow bg-gradient-to-l from-[rgba(16,24,40,0.08)]' />
|
/>
|
||||||
OR
|
)
|
||||||
<div className='ml-2 h-[1px] grow bg-gradient-to-r from-[rgba(16,24,40,0.08)]' />
|
}
|
||||||
</div>
|
{
|
||||||
<Button
|
canApiKey && canOAuth && (
|
||||||
className='w-full'
|
<div className='system-2xs-medium-uppercase flex h-4 items-center p-2 text-text-quaternary'>
|
||||||
variant='secondary-accent'
|
<div className='mr-2 h-[1px] grow bg-gradient-to-l from-[rgba(16,24,40,0.08)]' />
|
||||||
>
|
OR
|
||||||
Use API Key
|
<div className='ml-2 h-[1px] grow bg-gradient-to-r from-[rgba(16,24,40,0.08)]' />
|
||||||
</Button>
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
canApiKey && (
|
||||||
|
<AddApiKeyButton
|
||||||
|
{...apiKeyButtonProps}
|
||||||
|
formSchemas={item.credential_schema}
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './use-marketplace-all-plugins'
|
||||||
|
export * from './use-data-source-auth-update'
|
||||||
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 Indicator from '@/app/components/header/indicator'
|
||||||
import Operator from './operator'
|
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<string, any>) => void
|
||||||
|
}
|
||||||
|
const Item = ({
|
||||||
|
credentialItem,
|
||||||
|
onAction,
|
||||||
|
}: ItemProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [renaming, setRenaming] = useState(false)
|
||||||
|
const [renameValue, setRenameValue] = useState(credentialItem.name)
|
||||||
|
|
||||||
const Item = () => {
|
|
||||||
return (
|
return (
|
||||||
<div className='flex h-10 items-center rounded-lg bg-components-panel-on-panel-item-bg pl-3 pr-1'>
|
<div className='flex h-10 items-center rounded-lg bg-components-panel-on-panel-item-bg pl-3 pr-1'>
|
||||||
<div className='mr-2 h-5 w-5 shrink-0'></div>
|
{/* <div className='mr-2 h-5 w-5 shrink-0'></div> */}
|
||||||
<div className='system-sm-medium grow text-text-secondary'>
|
{
|
||||||
Evan’s Notion
|
renaming && (
|
||||||
</div>
|
<div className='flex w-full items-center space-x-1'>
|
||||||
|
<Input
|
||||||
|
wrapperClassName='grow rounded-[6px]'
|
||||||
|
className='h-6'
|
||||||
|
value={renameValue}
|
||||||
|
onChange={e => setRenameValue(e.target.value)}
|
||||||
|
placeholder={t('common.placeholder.input')}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
variant='primary'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onAction?.(
|
||||||
|
'rename',
|
||||||
|
credentialItem,
|
||||||
|
{
|
||||||
|
credential_id: credentialItem.id,
|
||||||
|
name: renameValue,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
setRenaming(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common.operation.save')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setRenaming(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common.operation.cancel')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!renaming && (
|
||||||
|
<div className='system-sm-medium grow text-text-secondary'>
|
||||||
|
{credentialItem.name}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
<div className='flex shrink-0 items-center'>
|
<div className='flex shrink-0 items-center'>
|
||||||
<div className='mr-1 flex h-3 w-3 items-center justify-center'>
|
<div className='mr-1 flex h-3 w-3 items-center justify-center'>
|
||||||
<Indicator color='green' />
|
<Indicator color='green' />
|
||||||
@ -18,7 +83,10 @@ const Item = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='ml-3 mr-2 h-3 w-[1px] bg-divider-regular'></div>
|
<div className='ml-3 mr-2 h-3 w-[1px] bg-divider-regular'></div>
|
||||||
<Operator />
|
<Operator
|
||||||
|
credentialItem={credentialItem}
|
||||||
|
onAction={onAction}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,40 +3,94 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
RiDeleteBinLine,
|
RiDeleteBinLine,
|
||||||
|
RiEditLine,
|
||||||
|
RiEqualizer2Line,
|
||||||
|
RiHome9Line,
|
||||||
RiLoopLeftLine,
|
RiLoopLeftLine,
|
||||||
RiStickyNoteAddLine,
|
RiStickyNoteAddLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import Dropdown from '@/app/components/base/dropdown'
|
import Dropdown from '@/app/components/base/dropdown'
|
||||||
import type { Item } 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(() => {
|
const items = useMemo(() => {
|
||||||
return [
|
const commonItems = [
|
||||||
{
|
{
|
||||||
value: 'change',
|
value: 'setDefault',
|
||||||
text: (
|
text: (
|
||||||
<div className='flex'>
|
<div className='flex items-center'>
|
||||||
<RiStickyNoteAddLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
<RiHome9Line className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||||
<div>
|
<div className='system-sm-semibold text-text-secondary'>{t('plugin.auth.setDefault')}</div>
|
||||||
<div className='system-sm-semibold mb-1 text-text-secondary'>Change authorized pages</div>
|
|
||||||
<div className='system-xs-regular text-text-tertiary'>18 Pages authorized</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'sync',
|
value: 'rename',
|
||||||
text: (
|
text: (
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<RiLoopLeftLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
<RiEditLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||||
<div className='system-sm-semibold text-text-secondary'>Sync</div>
|
<div className='system-sm-semibold text-text-secondary'>{t('common.operation.rename')}</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'edit',
|
||||||
|
text: (
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<RiEqualizer2Line className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||||
|
<div className='system-sm-semibold text-text-secondary'>{t('common.operation.edit')}</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}, [])
|
if (type === CredentialTypeEnum.OAUTH2) {
|
||||||
|
const oAuthItems = [
|
||||||
|
{
|
||||||
|
value: 'change',
|
||||||
|
text: (
|
||||||
|
<div className='flex'>
|
||||||
|
<RiStickyNoteAddLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||||
|
<div>
|
||||||
|
<div className='system-sm-semibold mb-1 text-text-secondary'>{t('common.dataSource.notion.changeAuthorizedPages')}</div>
|
||||||
|
<div className='system-xs-regular text-text-tertiary'>18 {t('common.dataSource.notion.pagesAuthorized')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'sync',
|
||||||
|
text: (
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<RiLoopLeftLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||||
|
<div className='system-sm-semibold text-text-secondary'>{t('common.dataSource.notion.sync')}</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
commonItems.push(...oAuthItems)
|
||||||
|
}
|
||||||
|
return commonItems
|
||||||
|
}, [t, type])
|
||||||
|
|
||||||
const secondItems = useMemo(() => {
|
const secondItems = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@ -51,23 +105,29 @@ const Operator = () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleSelect = useCallback((item: Item) => {
|
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 (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
items={items}
|
items={items}
|
||||||
secondItems={secondItems}
|
secondItems={secondItems}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
popupClassName='z-[61]'
|
popupClassName='z-[61]'
|
||||||
triggerProps={{
|
triggerProps={{
|
||||||
size: 'l',
|
size: 'l',
|
||||||
}}
|
}}
|
||||||
itemClassName='py-2 h-auto hover:bg-state-base-hover'
|
itemClassName='py-2 h-auto hover:bg-state-base-hover'
|
||||||
secondItemClassName='py-2 h-auto hover:bg-state-base-hover'
|
secondItemClassName='py-2 h-auto hover:bg-state-base-hover'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 = {
|
export type DataSourceCredential = {
|
||||||
credential: Record<string, any>
|
credential: Record<string, any>
|
||||||
type: string
|
type: CredentialTypeEnum
|
||||||
name: string
|
name: string
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
@ -9,14 +15,18 @@ export type DataSourceAuth = {
|
|||||||
provider: string
|
provider: string
|
||||||
plugin_id: string
|
plugin_id: string
|
||||||
plugin_unique_identifier: string
|
plugin_unique_identifier: string
|
||||||
icon: any
|
icon: string
|
||||||
name: string
|
name: string
|
||||||
label: any
|
label: TypeWithI18N
|
||||||
description: any
|
description: TypeWithI18N
|
||||||
credential_schema?: any[]
|
credential_schema?: FormSchema[]
|
||||||
oauth_schema?: {
|
oauth_schema?: {
|
||||||
client_schema?: any[]
|
client_schema?: FormSchema[]
|
||||||
credentials_schema?: any[]
|
credentials_schema?: FormSchema[]
|
||||||
|
is_oauth_custom_client_enabled?: boolean
|
||||||
|
is_system_oauth_params_exists?: boolean
|
||||||
|
oauth_custom_client_params?: Record<string, any>
|
||||||
|
redirect_uri?: string
|
||||||
}
|
}
|
||||||
credentials_list: DataSourceCredential[]
|
credentials_list: DataSourceCredential[]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Button from '@/app/components/base/button'
|
|||||||
import type { ButtonProps } from '@/app/components/base/button'
|
import type { ButtonProps } from '@/app/components/base/button'
|
||||||
import ApiKeyModal from './api-key-modal'
|
import ApiKeyModal from './api-key-modal'
|
||||||
import type { PluginPayload } from '../types'
|
import type { PluginPayload } from '../types'
|
||||||
|
import type { FormSchema } from '@/app/components/base/form/types'
|
||||||
|
|
||||||
export type AddApiKeyButtonProps = {
|
export type AddApiKeyButtonProps = {
|
||||||
pluginPayload: PluginPayload
|
pluginPayload: PluginPayload
|
||||||
@ -13,13 +14,15 @@ export type AddApiKeyButtonProps = {
|
|||||||
buttonText?: string
|
buttonText?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onUpdate?: () => void
|
onUpdate?: () => void
|
||||||
|
formSchemas?: FormSchema[]
|
||||||
}
|
}
|
||||||
const AddApiKeyButton = ({
|
const AddApiKeyButton = ({
|
||||||
pluginPayload,
|
pluginPayload,
|
||||||
buttonVariant = 'secondary-accent',
|
buttonVariant = 'secondary-accent',
|
||||||
buttonText = 'use api key',
|
buttonText = 'Use Api Key',
|
||||||
disabled,
|
disabled,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
formSchemas = [],
|
||||||
}: AddApiKeyButtonProps) => {
|
}: AddApiKeyButtonProps) => {
|
||||||
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
||||||
|
|
||||||
@ -39,6 +42,7 @@ const AddApiKeyButton = ({
|
|||||||
pluginPayload={pluginPayload}
|
pluginPayload={pluginPayload}
|
||||||
onClose={() => setIsApiKeyModalOpen(false)}
|
onClose={() => setIsApiKeyModalOpen(false)}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
|
formSchemas={formSchemas}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,13 @@ export type AddOAuthButtonProps = {
|
|||||||
dividerClassName?: string
|
dividerClassName?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onUpdate?: () => void
|
onUpdate?: () => void
|
||||||
|
oAuthData?: {
|
||||||
|
schema?: FormSchema[]
|
||||||
|
is_oauth_custom_client_enabled?: boolean
|
||||||
|
is_system_oauth_params_exists?: boolean
|
||||||
|
client_params?: Record<string, any>
|
||||||
|
redirect_uri?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const AddOAuthButton = ({
|
const AddOAuthButton = ({
|
||||||
pluginPayload,
|
pluginPayload,
|
||||||
@ -47,19 +54,26 @@ const AddOAuthButton = ({
|
|||||||
dividerClassName,
|
dividerClassName,
|
||||||
disabled,
|
disabled,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
oAuthData,
|
||||||
}: AddOAuthButtonProps) => {
|
}: AddOAuthButtonProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const renderI18nObject = useRenderI18nObject()
|
const renderI18nObject = useRenderI18nObject()
|
||||||
const [isOAuthSettingsOpen, setIsOAuthSettingsOpen] = useState(false)
|
const [isOAuthSettingsOpen, setIsOAuthSettingsOpen] = useState(false)
|
||||||
const { mutateAsync: getPluginOAuthUrl } = useGetPluginOAuthUrlHook(pluginPayload)
|
const { mutateAsync: getPluginOAuthUrl } = useGetPluginOAuthUrlHook(pluginPayload)
|
||||||
const { data, isLoading } = useGetPluginOAuthClientSchemaHook(pluginPayload)
|
const { data, isLoading } = useGetPluginOAuthClientSchemaHook(pluginPayload)
|
||||||
|
const mergedOAuthData = useMemo(() => {
|
||||||
|
if (oAuthData)
|
||||||
|
return oAuthData
|
||||||
|
|
||||||
|
return data
|
||||||
|
}, [oAuthData, data])
|
||||||
const {
|
const {
|
||||||
schema = [],
|
schema = [],
|
||||||
is_oauth_custom_client_enabled,
|
is_oauth_custom_client_enabled,
|
||||||
is_system_oauth_params_exists,
|
is_system_oauth_params_exists,
|
||||||
client_params,
|
client_params,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
} = data || {}
|
} = mergedOAuthData as any
|
||||||
const isConfigured = is_system_oauth_params_exists || is_oauth_custom_client_enabled
|
const isConfigured = is_system_oauth_params_exists || is_oauth_custom_client_enabled
|
||||||
const handleOAuth = useCallback(async () => {
|
const handleOAuth = useCallback(async () => {
|
||||||
const { authorization_url } = await getPluginOAuthUrl()
|
const { authorization_url } = await getPluginOAuthUrl()
|
||||||
@ -112,7 +126,7 @@ const AddOAuthButton = ({
|
|||||||
)
|
)
|
||||||
}, [t, redirect_uri, renderI18nObject])
|
}, [t, redirect_uri, renderI18nObject])
|
||||||
const memorizedSchemas = useMemo(() => {
|
const memorizedSchemas = useMemo(() => {
|
||||||
const result: FormSchema[] = schema.map((item, index) => {
|
const result: FormSchema[] = (schema as FormSchema[]).map((item, index) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
label: index === 0 ? renderCustomLabel(item) : item.label,
|
label: index === 0 ? renderCustomLabel(item) : item.label,
|
||||||
|
|||||||
@ -11,7 +11,10 @@ import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
|||||||
import Modal from '@/app/components/base/modal/modal'
|
import Modal from '@/app/components/base/modal/modal'
|
||||||
import { CredentialTypeEnum } from '../types'
|
import { CredentialTypeEnum } from '../types'
|
||||||
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
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 { FormTypeEnum } from '@/app/components/base/form/types'
|
||||||
import { useToastContext } from '@/app/components/base/toast'
|
import { useToastContext } from '@/app/components/base/toast'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
@ -30,6 +33,7 @@ export type ApiKeyModalProps = {
|
|||||||
onRemove?: () => void
|
onRemove?: () => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onUpdate?: () => void
|
onUpdate?: () => void
|
||||||
|
formSchemas?: FormSchema[]
|
||||||
}
|
}
|
||||||
const ApiKeyModal = ({
|
const ApiKeyModal = ({
|
||||||
pluginPayload,
|
pluginPayload,
|
||||||
@ -38,6 +42,7 @@ const ApiKeyModal = ({
|
|||||||
onRemove,
|
onRemove,
|
||||||
disabled,
|
disabled,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
formSchemas: formSchemasFromProps = [],
|
||||||
}: ApiKeyModalProps) => {
|
}: ApiKeyModalProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useToastContext()
|
const { notify } = useToastContext()
|
||||||
@ -48,6 +53,12 @@ const ApiKeyModal = ({
|
|||||||
setDoingAction(value)
|
setDoingAction(value)
|
||||||
}, [])
|
}, [])
|
||||||
const { data = [], isLoading } = useGetPluginCredentialSchemaHook(pluginPayload, CredentialTypeEnum.API_KEY)
|
const { data = [], isLoading } = useGetPluginCredentialSchemaHook(pluginPayload, CredentialTypeEnum.API_KEY)
|
||||||
|
const mergedData = useMemo(() => {
|
||||||
|
if (formSchemasFromProps?.length)
|
||||||
|
return formSchemasFromProps
|
||||||
|
|
||||||
|
return data
|
||||||
|
}, [formSchemasFromProps, data])
|
||||||
const formSchemas = useMemo(() => {
|
const formSchemas = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -56,9 +67,9 @@ const ApiKeyModal = ({
|
|||||||
label: t('plugin.auth.authorizationName'),
|
label: t('plugin.auth.authorizationName'),
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
...data,
|
...mergedData,
|
||||||
]
|
]
|
||||||
}, [data, t])
|
}, [mergedData, t])
|
||||||
const defaultValues = formSchemas.reduce((acc, schema) => {
|
const defaultValues = formSchemas.reduce((acc, schema) => {
|
||||||
if (schema.default)
|
if (schema.default)
|
||||||
acc[schema.name] = schema.default
|
acc[schema.name] = schema.default
|
||||||
@ -165,7 +176,7 @@ const ApiKeyModal = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!isLoading && !!data.length && (
|
!isLoading && !!mergedData.length && (
|
||||||
<AuthForm
|
<AuthForm
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
formSchemas={formSchemas}
|
formSchemas={formSchemas}
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RiEqualizer2Line } from '@remixicon/react'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import Indicator from '@/app/components/header/indicator'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type AuthorizedInDataSourceNodeProps = {
|
||||||
|
authorizationsNum: number
|
||||||
|
onJumpToDataSourcePage: () => void
|
||||||
|
}
|
||||||
|
const AuthorizedInDataSourceNode = ({
|
||||||
|
authorizationsNum,
|
||||||
|
onJumpToDataSourcePage,
|
||||||
|
}: AuthorizedInDataSourceNodeProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
onClick={onJumpToDataSourcePage}
|
||||||
|
>
|
||||||
|
<Indicator
|
||||||
|
className='mr-1.5'
|
||||||
|
color='green'
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
authorizationsNum > 1
|
||||||
|
? t('plugin.auth.authorizations')
|
||||||
|
: t('plugin.auth.authorization')
|
||||||
|
}
|
||||||
|
<RiEqualizer2Line
|
||||||
|
className={cn(
|
||||||
|
'h-3.5 w-3.5 text-components-button-ghost-text',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AuthorizedInDataSourceNode)
|
||||||
@ -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 {
|
return {
|
||||||
getCredentialInfo: '',
|
getCredentialInfo: '',
|
||||||
setDefaultCredential: '',
|
setDefaultCredential: '',
|
||||||
|
|||||||
@ -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<string | null>(null)
|
||||||
|
const [deleteCredentialId, setDeleteCredentialId] = useState<string | null>(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<Record<string, any> | null>(null)
|
||||||
|
const handleEdit = useCallback((id: string, values: Record<string, any>) => {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,4 +3,10 @@ export { default as Authorized } from './authorized'
|
|||||||
export { default as AuthorizedInNode } from './authorized-in-node'
|
export { default as AuthorizedInNode } from './authorized-in-node'
|
||||||
export { default as PluginAuthInAgent } from './plugin-auth-in-agent'
|
export { default as PluginAuthInAgent } from './plugin-auth-in-agent'
|
||||||
export { usePluginAuth } from './hooks/use-plugin-auth'
|
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'
|
export * from './types'
|
||||||
|
|||||||
@ -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 && (
|
||||||
|
<div className='px-4 pb-2'>
|
||||||
|
<Button
|
||||||
|
className='w-full'
|
||||||
|
variant='primary'
|
||||||
|
onClick={onJumpToDataSourcePage}
|
||||||
|
>
|
||||||
|
<RiAddLine className='mr-1 h-4 w-4' />
|
||||||
|
{t('common.integrations.connect')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{isAuthorized && children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(PluginAuthInDataSourceNode)
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
export type { AddApiKeyButtonProps } from './authorize/add-api-key-button'
|
||||||
|
export type { AddOAuthButtonProps } from './authorize/add-oauth-button'
|
||||||
|
|
||||||
export enum AuthCategory {
|
export enum AuthCategory {
|
||||||
tool = 'tool',
|
tool = 'tool',
|
||||||
datasource = 'datasource',
|
datasource = 'datasource',
|
||||||
|
|||||||
@ -62,11 +62,15 @@ import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevice
|
|||||||
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||||
import { FlowType } from '@/types/common'
|
import { FlowType } from '@/types/common'
|
||||||
import {
|
import {
|
||||||
|
AuthorizedInDataSourceNode,
|
||||||
AuthorizedInNode,
|
AuthorizedInNode,
|
||||||
PluginAuth,
|
PluginAuth,
|
||||||
|
PluginAuthInDataSourceNode,
|
||||||
} from '@/app/components/plugins/plugin-auth'
|
} from '@/app/components/plugins/plugin-auth'
|
||||||
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
||||||
import { canFindTool } from '@/utils'
|
import { canFindTool } from '@/utils'
|
||||||
|
import { DataSourceClassification } from '@/app/components/workflow/nodes/data-source/types'
|
||||||
|
import { useModalContext } from '@/context/modal-context'
|
||||||
|
|
||||||
type BasePanelProps = {
|
type BasePanelProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -240,6 +244,11 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
const showPluginAuth = useMemo(() => {
|
const showPluginAuth = useMemo(() => {
|
||||||
return data.type === BlockEnum.Tool && currCollection?.allow_delete
|
return data.type === BlockEnum.Tool && currCollection?.allow_delete
|
||||||
}, [currCollection, data.type])
|
}, [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) => {
|
const handleAuthorizationItemClick = useCallback((credential_id: string) => {
|
||||||
handleNodeDataUpdateWithSyncDraft({
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
id,
|
id,
|
||||||
@ -248,6 +257,10 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [handleNodeDataUpdateWithSyncDraft, id])
|
}, [handleNodeDataUpdateWithSyncDraft, id])
|
||||||
|
const { setShowAccountSettingModal } = useModalContext()
|
||||||
|
const handleJumpToDataSourcePage = useCallback(() => {
|
||||||
|
setShowAccountSettingModal({ payload: 'data-source' })
|
||||||
|
}, [setShowAccountSettingModal])
|
||||||
|
|
||||||
if (logParams.showSpecialResultPanel) {
|
if (logParams.showSpecialResultPanel) {
|
||||||
return (
|
return (
|
||||||
@ -413,7 +426,26 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!showPluginAuth && (
|
!!currentDataSource && (
|
||||||
|
<PluginAuthInDataSourceNode
|
||||||
|
onJumpToDataSourcePage={handleJumpToDataSourcePage}
|
||||||
|
isAuthorized={currentDataSource.is_authorized}
|
||||||
|
>
|
||||||
|
<div className='flex items-center justify-between pl-4 pr-3'>
|
||||||
|
<Tab
|
||||||
|
value={tabType}
|
||||||
|
onChange={setTabType}
|
||||||
|
/>
|
||||||
|
<AuthorizedInDataSourceNode
|
||||||
|
onJumpToDataSourcePage={handleJumpToDataSourcePage}
|
||||||
|
authorizationsNum={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PluginAuthInDataSourceNode>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!showPluginAuth && !currentDataSource && (
|
||||||
<div className='flex items-center justify-between pl-4 pr-3'>
|
<div className='flex items-center justify-between pl-4 pr-3'>
|
||||||
<Tab
|
<Tab
|
||||||
value={tabType}
|
value={tabType}
|
||||||
|
|||||||
@ -1,137 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import {
|
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
|
||||||
import cn from '@/utils/classnames'
|
|
||||||
import Drawer from '@/app/components/base/drawer-plus'
|
|
||||||
import Button from '@/app/components/base/button'
|
|
||||||
import Toast from '@/app/components/base/toast'
|
|
||||||
import Loading from '@/app/components/base/loading'
|
|
||||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
|
||||||
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
|
|
||||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
|
||||||
import { noop } from 'lodash-es'
|
|
||||||
import { useDataSourceCredentials } from '@/service/use-pipeline'
|
|
||||||
import type { ToolCredential } from '@/app/components/tools/types'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
dataSourceItem: any
|
|
||||||
onCancel: () => void
|
|
||||||
onSaved: (value: Record<string, any>) => void
|
|
||||||
isHideRemoveBtn?: boolean
|
|
||||||
onRemove?: () => void
|
|
||||||
isSaving?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConfigCredential: FC<Props> = ({
|
|
||||||
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<any>({})
|
|
||||||
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 (
|
|
||||||
<Drawer
|
|
||||||
isShow
|
|
||||||
onHide={onCancel}
|
|
||||||
title={t('tools.auth.setupModalTitle') as string}
|
|
||||||
titleDescription={t('tools.auth.setupModalTitleDescription') as string}
|
|
||||||
panelClassName='mt-[64px] mb-2 !w-[420px] border-components-panel-border'
|
|
||||||
maxWidthClassName='!max-w-[420px]'
|
|
||||||
height='calc(100vh - 64px)'
|
|
||||||
contentClassName='!bg-components-panel-bg'
|
|
||||||
headerClassName='!border-b-divider-subtle'
|
|
||||||
body={
|
|
||||||
|
|
||||||
<div className='h-full px-6 py-3'>
|
|
||||||
{!transformedCredentialsSchema.length
|
|
||||||
? <Loading type='app' />
|
|
||||||
: (
|
|
||||||
<>
|
|
||||||
<Form
|
|
||||||
value={tempCredential}
|
|
||||||
onChange={(v) => {
|
|
||||||
setTempCredential(v)
|
|
||||||
}}
|
|
||||||
formSchemas={transformedCredentialsSchema as any}
|
|
||||||
isEditMode={true}
|
|
||||||
showOnVariableMap={{}}
|
|
||||||
validating={false}
|
|
||||||
inputClassName='!bg-components-input-bg-normal'
|
|
||||||
fieldMoreInfo={item => item.url
|
|
||||||
? (<a
|
|
||||||
href={item.url}
|
|
||||||
target='_blank' rel='noopener noreferrer'
|
|
||||||
className='inline-flex items-center text-xs text-text-accent'
|
|
||||||
>
|
|
||||||
{t('tools.howToGet')}
|
|
||||||
<LinkExternal02 className='ml-1 h-3 w-3' />
|
|
||||||
</a>)
|
|
||||||
: null}
|
|
||||||
/>
|
|
||||||
<div className={cn((is_authorized && !isHideRemoveBtn) ? 'justify-between' : 'justify-end', 'mt-2 flex ')} >
|
|
||||||
{
|
|
||||||
(is_authorized && !isHideRemoveBtn) && (
|
|
||||||
<Button onClick={onRemove}>{t('common.operation.remove')}</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
< div className='flex space-x-2'>
|
|
||||||
<Button onClick={onCancel}>{t('common.operation.cancel')}</Button>
|
|
||||||
<Button loading={isLoading || isSaving} disabled={isLoading || isSaving} variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
</div >
|
|
||||||
}
|
|
||||||
isShowMask={true}
|
|
||||||
clickOutsideNotOpen={false}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default memo(ConfigCredential)
|
|
||||||
@ -6,13 +6,11 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { useBoolean } from 'ahooks'
|
|
||||||
import type { DataSourceNodeType } from './types'
|
import type { DataSourceNodeType } from './types'
|
||||||
import { DataSourceClassification } from './types'
|
import { DataSourceClassification } from './types'
|
||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import {
|
import {
|
||||||
BoxGroupField,
|
BoxGroupField,
|
||||||
Group,
|
|
||||||
} from '@/app/components/workflow/nodes/_base/components/layout'
|
} from '@/app/components/workflow/nodes/_base/components/layout'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import TagInput from '@/app/components/base/tag-input'
|
import TagInput from '@/app/components/base/tag-input'
|
||||||
@ -26,18 +24,13 @@ import {
|
|||||||
WEBSITE_CRAWL_OUTPUT,
|
WEBSITE_CRAWL_OUTPUT,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
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 InputVarList from '@/app/components/workflow/nodes/tool/components/input-var-list'
|
||||||
import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||||
import type { Var } from '@/app/components/workflow/types'
|
import type { Var } from '@/app/components/workflow/types'
|
||||||
import { VarType } 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<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
|
const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useToastContext()
|
|
||||||
const { nodesReadOnly } = useNodesReadOnly()
|
const { nodesReadOnly } = useNodesReadOnly()
|
||||||
const dataSourceList = useStore(s => s.dataSourceList)
|
const dataSourceList = useStore(s => s.dataSourceList)
|
||||||
const {
|
const {
|
||||||
@ -55,11 +48,6 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
|
|||||||
const isOnlineDocument = provider_type === DataSourceClassification.onlineDocument
|
const isOnlineDocument = provider_type === DataSourceClassification.onlineDocument
|
||||||
const isOnlineDrive = provider_type === DataSourceClassification.onlineDrive
|
const isOnlineDrive = provider_type === DataSourceClassification.onlineDrive
|
||||||
const currentDataSource = dataSourceList?.find(ds => ds.plugin_id === plugin_id)
|
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 currentDataSourceItem: any = currentDataSource?.tools.find(tool => tool.name === data.datasource_name)
|
||||||
const formSchemas = useMemo(() => {
|
const formSchemas = useMemo(() => {
|
||||||
return currentDataSourceItem ? toolParametersToFormSchemas(currentDataSourceItem.parameters) : []
|
return currentDataSourceItem ? toolParametersToFormSchemas(currentDataSourceItem.parameters) : []
|
||||||
@ -77,40 +65,10 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
|
|||||||
return varPayload.type !== VarType.arrayFile
|
return varPayload.type !== VarType.arrayFile
|
||||||
}, [currVarType])
|
}, [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 (
|
return (
|
||||||
<div >
|
<div >
|
||||||
{
|
{
|
||||||
!isAuthorized && !showAuthModal && !isLocalFile && currentDataSource && (
|
currentDataSource?.is_authorized && !isLocalFile && !!formSchemas?.length && (
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
variant='primary'
|
|
||||||
className='w-full'
|
|
||||||
onClick={openAuthModal}
|
|
||||||
disabled={nodesReadOnly}
|
|
||||||
>
|
|
||||||
{t('workflow.nodes.tool.authorize')}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
isAuthorized && !isLocalFile && !!formSchemas?.length && (
|
|
||||||
<BoxGroupField
|
<BoxGroupField
|
||||||
boxGroupProps={{
|
boxGroupProps={{
|
||||||
boxProps: { withBorderBottom: true },
|
boxProps: { withBorderBottom: true },
|
||||||
@ -222,16 +180,6 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
{
|
|
||||||
showAuthModal && !isLocalFile && (
|
|
||||||
<ConfigCredential
|
|
||||||
dataSourceItem={currentDataSource!}
|
|
||||||
onCancel={hideAuthModal}
|
|
||||||
onSaved={handleAuth}
|
|
||||||
isHideRemoveBtn
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { get } from './base'
|
import { get } from './base'
|
||||||
|
import { useInvalid } from './use-base'
|
||||||
import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types'
|
import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types'
|
||||||
|
|
||||||
const NAME_SPACE = 'data-source-auth'
|
const NAME_SPACE = 'data-source-auth'
|
||||||
@ -11,3 +12,8 @@ export const useGetDataSourceListAuth = () => {
|
|||||||
retry: 0,
|
retry: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useInvalidDataSourceListAuth = (
|
||||||
|
) => {
|
||||||
|
return useInvalid([NAME_SPACE, 'list'])
|
||||||
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import type {
|
|||||||
import type { DataSourceItem } from '@/app/components/workflow/block-selector/types'
|
import type { DataSourceItem } from '@/app/components/workflow/block-selector/types'
|
||||||
import type { ToolCredential } from '@/app/components/tools/types'
|
import type { ToolCredential } from '@/app/components/tools/types'
|
||||||
import type { IconInfo } from '@/models/datasets'
|
import type { IconInfo } from '@/models/datasets'
|
||||||
|
import { useInvalid } from './use-base'
|
||||||
|
|
||||||
const NAME_SPACE = 'pipeline'
|
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 publishedPipelineInfoQueryKeyPrefix = [NAME_SPACE, 'published-pipeline']
|
||||||
|
|
||||||
export const usePublishedPipelineInfo = (pipelineId: string) => {
|
export const usePublishedPipelineInfo = (pipelineId: string) => {
|
||||||
|
|||||||
@ -94,6 +94,7 @@ export const useGetPluginCredentialSchema = (
|
|||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
enabled: !!url,
|
||||||
queryKey: [NAME_SPACE, 'credential-schema', url],
|
queryKey: [NAME_SPACE, 'credential-schema', url],
|
||||||
queryFn: () => get<FormSchema[]>(url),
|
queryFn: () => get<FormSchema[]>(url),
|
||||||
})
|
})
|
||||||
@ -119,6 +120,7 @@ export const useGetPluginOAuthClientSchema = (
|
|||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
enabled: !!url,
|
||||||
queryKey: [NAME_SPACE, 'oauth-client-schema', url],
|
queryKey: [NAME_SPACE, 'oauth-client-schema', url],
|
||||||
queryFn: () => get<{
|
queryFn: () => get<{
|
||||||
schema: FormSchema[]
|
schema: FormSchema[]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user