dify
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Item from './item'
|
||||
import Configure from './configure'
|
||||
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'
|
||||
import { useGetDataSourceOAuthUrl } from '@/service/use-datasource'
|
||||
import { openOAuthPopup } from '@/hooks/use-oauth'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
|
||||
type CardProps = {
|
||||
item: DataSourceAuth
|
||||
disabled?: boolean
|
||||
}
|
||||
const Card = ({
|
||||
item,
|
||||
disabled,
|
||||
}: CardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const renderI18nObject = useRenderI18nObject()
|
||||
const {
|
||||
icon,
|
||||
label,
|
||||
author,
|
||||
name,
|
||||
credentials_list,
|
||||
credential_schema,
|
||||
} = item
|
||||
const pluginPayload = {
|
||||
category: AuthCategory.datasource,
|
||||
provider: `${item.plugin_id}/${item.name}`,
|
||||
providerType: CollectionType.datasource,
|
||||
}
|
||||
const { handleAuthUpdate } = useDataSourceAuthUpdate({
|
||||
pluginId: item.plugin_id,
|
||||
provider: item.name,
|
||||
})
|
||||
const {
|
||||
deleteCredentialId,
|
||||
doingAction,
|
||||
handleConfirm,
|
||||
handleEdit,
|
||||
handleRemove,
|
||||
handleRename,
|
||||
handleSetDefault,
|
||||
editValues,
|
||||
setEditValues,
|
||||
openConfirm,
|
||||
closeConfirm,
|
||||
pendingOperationCredentialId,
|
||||
} = usePluginAuthAction(pluginPayload, handleAuthUpdate)
|
||||
const changeCredentialIdRef = useRef<string | undefined>(undefined)
|
||||
const {
|
||||
mutateAsync: getPluginOAuthUrl,
|
||||
} = useGetDataSourceOAuthUrl(pluginPayload.provider)
|
||||
const handleOAuth = useCallback(async () => {
|
||||
const { authorization_url } = await getPluginOAuthUrl(changeCredentialIdRef.current)
|
||||
|
||||
if (authorization_url) {
|
||||
openOAuthPopup(
|
||||
authorization_url,
|
||||
handleAuthUpdate,
|
||||
)
|
||||
}
|
||||
}, [getPluginOAuthUrl, 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)
|
||||
|
||||
if (action === 'change') {
|
||||
changeCredentialIdRef.current = credentialItem.id
|
||||
handleOAuth()
|
||||
}
|
||||
}, [
|
||||
openConfirm,
|
||||
handleEdit,
|
||||
handleSetDefault,
|
||||
handleRename,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className='rounded-xl bg-background-section-burn'>
|
||||
<div className='flex items-center p-3 pb-2'>
|
||||
<img
|
||||
src={icon}
|
||||
className='mr-3 flex h-10 w-10 shrink-0 items-center justify-center'
|
||||
/>
|
||||
<div className='grow'>
|
||||
<div className='system-md-semibold text-text-primary'>
|
||||
{renderI18nObject(label)}
|
||||
</div>
|
||||
<div className='system-xs-regular flex h-4 items-center text-text-tertiary'>
|
||||
{author}
|
||||
<div className='mx-0.5 text-text-quaternary'>/</div>
|
||||
{name}
|
||||
</div>
|
||||
</div>
|
||||
<Configure
|
||||
pluginPayload={pluginPayload}
|
||||
item={item}
|
||||
onUpdate={handleAuthUpdate}
|
||||
/>
|
||||
</div>
|
||||
<div className='system-xs-medium flex h-4 items-center pl-3 text-text-tertiary'>
|
||||
{t('plugin.auth.connectedWorkspace')}
|
||||
<div className='ml-3 h-[1px] grow bg-divider-subtle'></div>
|
||||
</div>
|
||||
{
|
||||
!!credentials_list.length && (
|
||||
<div className='space-y-1 p-3 pt-2'>
|
||||
{
|
||||
credentials_list.map(credentialItem => (
|
||||
<Item
|
||||
key={credentialItem.id}
|
||||
credentialItem={credentialItem}
|
||||
onAction={handleAction}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!credentials_list.length && (
|
||||
<div className='p-3 pt-1'>
|
||||
<div className='system-xs-regular flex h-10 items-center justify-center rounded-[10px] bg-background-section text-text-tertiary'>
|
||||
{t('plugin.auth.emptyAuth')}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Card)
|
||||
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
RiAddLine,
|
||||
} 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 [open, setOpen] = useState(false)
|
||||
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 handleToggle = useCallback(() => {
|
||||
setOpen(v => !v)
|
||||
}, [])
|
||||
|
||||
const handleUpdate = useCallback(() => {
|
||||
setOpen(false)
|
||||
onUpdate?.()
|
||||
}, [onUpdate])
|
||||
|
||||
return (
|
||||
<>
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleToggle}>
|
||||
<Button
|
||||
variant='secondary-accent'
|
||||
>
|
||||
<RiAddLine className='h-4 w-4' />
|
||||
{t('common.dataSource.configure')}
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<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'>
|
||||
{
|
||||
!!canOAuth && (
|
||||
<AddOAuthButton
|
||||
{...oAuthButtonProps}
|
||||
onUpdate={handleUpdate}
|
||||
oAuthData={{
|
||||
schema: oAuthData.client_schema || [],
|
||||
is_oauth_custom_client_enabled: oAuthData.is_oauth_custom_client_enabled,
|
||||
is_system_oauth_params_exists: oAuthData.is_system_oauth_params_exists,
|
||||
client_params: oAuthData.oauth_custom_client_params,
|
||||
redirect_uri: oAuthData.redirect_uri,
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!canApiKey && !!canOAuth && (
|
||||
<div className='system-2xs-medium-uppercase flex h-4 items-center p-2 text-text-quaternary'>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!canApiKey && (
|
||||
<AddApiKeyButton
|
||||
{...apiKeyButtonProps}
|
||||
formSchemas={item.credential_schema}
|
||||
onUpdate={handleUpdate}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Configure)
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './use-marketplace-all-plugins'
|
||||
export * from './use-data-source-auth-update'
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useInvalidDataSourceAuth, useInvalidDataSourceListAuth } from '@/service/use-datasource'
|
||||
import { useInvalidDefaultDataSourceListAuth } from '@/service/use-datasource'
|
||||
import { useInvalidDataSourceList } from '@/service/use-pipeline'
|
||||
|
||||
export const useDataSourceAuthUpdate = ({
|
||||
pluginId,
|
||||
provider,
|
||||
}: {
|
||||
pluginId: string
|
||||
provider: string
|
||||
}) => {
|
||||
const invalidateDataSourceListAuth = useInvalidDataSourceListAuth()
|
||||
const invalidDefaultDataSourceListAuth = useInvalidDefaultDataSourceListAuth()
|
||||
const invalidateDataSourceList = useInvalidDataSourceList()
|
||||
const invalidateDataSourceAuth = useInvalidDataSourceAuth({
|
||||
pluginId,
|
||||
provider,
|
||||
})
|
||||
const handleAuthUpdate = useCallback(() => {
|
||||
invalidateDataSourceListAuth()
|
||||
invalidDefaultDataSourceListAuth()
|
||||
invalidateDataSourceList()
|
||||
invalidateDataSourceAuth()
|
||||
}, [invalidateDataSourceListAuth, invalidateDataSourceList, invalidateDataSourceAuth, invalidDefaultDataSourceListAuth])
|
||||
|
||||
return {
|
||||
handleAuthUpdate,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
useMarketplacePlugins,
|
||||
} from '@/app/components/plugins/marketplace/hooks'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { getMarketplacePluginsByCollectionId } from '@/app/components/plugins/marketplace/utils'
|
||||
|
||||
export const useMarketplaceAllPlugins = (providers: any[], searchText: string) => {
|
||||
const exclude = useMemo(() => {
|
||||
return providers.map(provider => provider.plugin_id)
|
||||
}, [providers])
|
||||
const [collectionPlugins, setCollectionPlugins] = useState<Plugin[]>([])
|
||||
|
||||
const {
|
||||
plugins,
|
||||
queryPlugins,
|
||||
queryPluginsWithDebounced,
|
||||
isLoading,
|
||||
} = useMarketplacePlugins()
|
||||
|
||||
const getCollectionPlugins = useCallback(async () => {
|
||||
const collectionPlugins = await getMarketplacePluginsByCollectionId('__datasource-settings-pinned-datasources')
|
||||
|
||||
setCollectionPlugins(collectionPlugins)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
getCollectionPlugins()
|
||||
}, [getCollectionPlugins])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText) {
|
||||
queryPluginsWithDebounced({
|
||||
query: searchText,
|
||||
category: PluginCategoryEnum.datasource,
|
||||
exclude,
|
||||
type: 'plugin',
|
||||
sortBy: 'install_count',
|
||||
sortOrder: 'DESC',
|
||||
})
|
||||
}
|
||||
else {
|
||||
queryPlugins({
|
||||
query: '',
|
||||
category: PluginCategoryEnum.datasource,
|
||||
type: 'plugin',
|
||||
pageSize: 1000,
|
||||
exclude,
|
||||
sortBy: 'install_count',
|
||||
sortOrder: 'DESC',
|
||||
})
|
||||
}
|
||||
}, [queryPlugins, queryPluginsWithDebounced, searchText, exclude])
|
||||
|
||||
const allPlugins = useMemo(() => {
|
||||
const allPlugins = collectionPlugins.filter(plugin => !exclude.includes(plugin.plugin_id))
|
||||
|
||||
if (plugins?.length) {
|
||||
for (let i = 0; i < plugins.length; i++) {
|
||||
const plugin = plugins[i]
|
||||
|
||||
if (plugin.type !== 'bundle' && !allPlugins.find(p => p.plugin_id === plugin.plugin_id))
|
||||
allPlugins.push(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
return allPlugins
|
||||
}, [plugins, collectionPlugins, exclude])
|
||||
|
||||
return {
|
||||
plugins: allPlugins,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { memo } from 'react'
|
||||
import Card from './card'
|
||||
import InstallFromMarketplace from './install-from-marketplace'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useGetDataSourceListAuth } from '@/service/use-datasource'
|
||||
|
||||
const DataSourcePage = () => {
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { data } = useGetDataSourceListAuth()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='space-y-2'>
|
||||
{
|
||||
data?.result.map(item => (
|
||||
<Card
|
||||
key={item.plugin_unique_identifier}
|
||||
item={item}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{
|
||||
enable_marketplace && (
|
||||
<InstallFromMarketplace
|
||||
providers={data?.result || []}
|
||||
searchText={''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(DataSourcePage)
|
||||
@@ -0,0 +1,84 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightUpLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
useMarketplaceAllPlugins,
|
||||
} from './hooks'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import cn from '@/utils/classnames'
|
||||
import { getLocaleOnClient } from '@/i18n-config'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
type InstallFromMarketplaceProps = {
|
||||
providers: any[]
|
||||
searchText: string
|
||||
}
|
||||
const InstallFromMarketplace = ({
|
||||
providers,
|
||||
searchText,
|
||||
}: InstallFromMarketplaceProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
const locale = getLocaleOnClient()
|
||||
const {
|
||||
plugins: allPlugins,
|
||||
isLoading: isAllPluginsLoading,
|
||||
} = useMarketplaceAllPlugins(providers, searchText)
|
||||
|
||||
const cardRender = useCallback((plugin: Plugin) => {
|
||||
if (plugin.type === 'bundle')
|
||||
return null
|
||||
|
||||
return <ProviderCard key={plugin.plugin_id} payload={plugin} />
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='mb-2'>
|
||||
<Divider className='!mt-4 h-px' />
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='system-md-semibold flex cursor-pointer items-center gap-1 text-text-primary' onClick={() => setCollapse(!collapse)}>
|
||||
<RiArrowDownSLine className={cn('h-4 w-4', collapse && '-rotate-90')} />
|
||||
{t('common.modelProvider.installDataSourceProvider')}
|
||||
</div>
|
||||
<div className='mb-2 flex items-center pt-2'>
|
||||
<span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span>
|
||||
<Link target="_blank" href={getMarketplaceUrl('', { theme })} className='system-sm-medium inline-flex items-center text-text-accent'>
|
||||
{t('plugin.marketplace.difyMarketplace')}
|
||||
<RiArrowRightUpLine className='h-4 w-4' />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!collapse && isAllPluginsLoading && <Loading type='area' />}
|
||||
{
|
||||
!isAllPluginsLoading && !collapse && (
|
||||
<List
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={allPlugins}
|
||||
showInstallButton
|
||||
locale={locale}
|
||||
cardContainerClassName='grid grid-cols-2 gap-2'
|
||||
cardRender={cardRender}
|
||||
emptyClassName='h-auto'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(InstallFromMarketplace)
|
||||
@@ -0,0 +1,95 @@
|
||||
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<string, any>) => void
|
||||
}
|
||||
const Item = ({
|
||||
credentialItem,
|
||||
onAction,
|
||||
}: ItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [renaming, setRenaming] = useState(false)
|
||||
const [renameValue, setRenameValue] = useState(credentialItem.name)
|
||||
|
||||
return (
|
||||
<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> */}
|
||||
{
|
||||
renaming && (
|
||||
<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='mr-1 flex h-3 w-3 items-center justify-center'>
|
||||
<Indicator color='green' />
|
||||
</div>
|
||||
<div className='system-xs-semibold-uppercase text-util-colors-green-green-600'>
|
||||
connected
|
||||
</div>
|
||||
</div>
|
||||
<div className='ml-3 mr-2 h-3 w-[1px] bg-divider-regular'></div>
|
||||
<Operator
|
||||
credentialItem={credentialItem}
|
||||
onAction={onAction}
|
||||
onRename={() => setRenaming(true)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Item)
|
||||
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
RiEqualizer2Line,
|
||||
RiHome9Line,
|
||||
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'
|
||||
|
||||
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 commonItems = [
|
||||
{
|
||||
value: 'setDefault',
|
||||
text: (
|
||||
<div className='flex items-center'>
|
||||
<RiHome9Line className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('plugin.auth.setDefault')}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
...(
|
||||
type === CredentialTypeEnum.OAUTH2
|
||||
? [
|
||||
{
|
||||
value: 'rename',
|
||||
text: (
|
||||
<div className='flex items-center'>
|
||||
<RiEditLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('common.operation.rename')}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []
|
||||
),
|
||||
...(
|
||||
type === CredentialTypeEnum.API_KEY
|
||||
? [
|
||||
{
|
||||
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>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []
|
||||
),
|
||||
]
|
||||
if (type === CredentialTypeEnum.OAUTH2) {
|
||||
const oAuthItems = [
|
||||
{
|
||||
value: 'change',
|
||||
text: (
|
||||
<div className='flex items-center'>
|
||||
<RiStickyNoteAddLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||
<div className='system-sm-semibold mb-1 text-text-secondary'>{t('common.dataSource.notion.changeAuthorizedPages')}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
commonItems.push(...oAuthItems)
|
||||
}
|
||||
return commonItems
|
||||
}, [t, type])
|
||||
|
||||
const secondItems = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
value: 'delete',
|
||||
text: (
|
||||
<div className='flex items-center'>
|
||||
<RiDeleteBinLine className='mr-2 h-4 w-4 text-text-tertiary' />
|
||||
<div className='system-sm-semibold text-text-secondary'>
|
||||
{t('common.operation.remove')}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
const handleSelect = useCallback((item: Item) => {
|
||||
if (item.value === 'rename') {
|
||||
onRename?.()
|
||||
return
|
||||
}
|
||||
onAction(
|
||||
item.value as string,
|
||||
credentialItem,
|
||||
)
|
||||
}, [onAction, credentialItem, onRename])
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
items={items}
|
||||
secondItems={secondItems}
|
||||
onSelect={handleSelect}
|
||||
popupClassName='z-[61]'
|
||||
triggerProps={{
|
||||
size: 'l',
|
||||
}}
|
||||
itemClassName='py-2 h-auto hover:bg-state-base-hover'
|
||||
secondItemClassName='py-2 h-auto hover:bg-state-base-hover'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Operator)
|
||||
@@ -0,0 +1,34 @@
|
||||
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<string, any>
|
||||
type: CredentialTypeEnum
|
||||
name: string
|
||||
id: string
|
||||
is_default: boolean
|
||||
avatar_url: string
|
||||
}
|
||||
export type DataSourceAuth = {
|
||||
author: string
|
||||
provider: string
|
||||
plugin_id: string
|
||||
plugin_unique_identifier: string
|
||||
icon: string
|
||||
name: string
|
||||
label: TypeWithI18N
|
||||
description: TypeWithI18N
|
||||
credential_schema?: FormSchema[]
|
||||
oauth_schema?: {
|
||||
client_schema?: FormSchema[]
|
||||
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[]
|
||||
}
|
||||
Reference in New Issue
Block a user