mirror of https://github.com/langgenius/dify.git
tool oauth
This commit is contained in:
parent
ce8bf7b5a2
commit
8968a3e254
|
|
@ -4,6 +4,7 @@ import {
|
|||
useMemo,
|
||||
} from 'react'
|
||||
import type { AnyFieldApi } from '@tanstack/react-form'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
import type { FormSchema } from '@/app/components/base/form/types'
|
||||
|
|
@ -17,6 +18,7 @@ export type BaseFieldProps = {
|
|||
inputClassName?: string
|
||||
formSchema: FormSchema
|
||||
field: AnyFieldApi
|
||||
disabled?: boolean
|
||||
}
|
||||
const BaseField = ({
|
||||
fieldClassName,
|
||||
|
|
@ -25,6 +27,7 @@ const BaseField = ({
|
|||
inputClassName,
|
||||
formSchema,
|
||||
field,
|
||||
disabled,
|
||||
}: BaseFieldProps) => {
|
||||
const renderI18nObject = useRenderI18nObject()
|
||||
const {
|
||||
|
|
@ -35,9 +38,13 @@ const BaseField = ({
|
|||
if (isValidElement(label))
|
||||
return label
|
||||
|
||||
if (typeof label === 'string')
|
||||
return label
|
||||
|
||||
if (typeof label === 'object' && label !== null)
|
||||
return renderI18nObject(label as Record<string, string>)
|
||||
}, [label, renderI18nObject])
|
||||
const value = useStore(field.form.store, s => s.values[field.name])
|
||||
|
||||
return (
|
||||
<div className={cn(fieldClassName)}>
|
||||
|
|
@ -48,23 +55,27 @@ const BaseField = ({
|
|||
{
|
||||
formSchema.type === FormTypeEnum.textInput && (
|
||||
<Input
|
||||
className={cn(inputClassName)}
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
name={field.name}
|
||||
className={cn(inputClassName)}
|
||||
value={value}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.secretInput && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type='password'
|
||||
className={cn(inputClassName)}
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
value={value}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export type BaseFormProps = {
|
|||
defaultValues?: Record<string, any>
|
||||
formClassName?: string
|
||||
ref?: FormRef
|
||||
disabled?: boolean
|
||||
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
|
||||
|
||||
const BaseForm = ({
|
||||
|
|
@ -35,6 +36,7 @@ const BaseForm = ({
|
|||
inputContainerClassName,
|
||||
inputClassName,
|
||||
ref,
|
||||
disabled,
|
||||
}: BaseFormProps) => {
|
||||
const form = useForm({
|
||||
defaultValues,
|
||||
|
|
@ -42,8 +44,8 @@ const BaseForm = ({
|
|||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
getFormStore() {
|
||||
return form.store
|
||||
getForm() {
|
||||
return form
|
||||
},
|
||||
}
|
||||
}, [form])
|
||||
|
|
@ -60,18 +62,21 @@ const BaseForm = ({
|
|||
labelClassName={labelClassName}
|
||||
inputContainerClassName={inputContainerClassName}
|
||||
inputClassName={inputClassName}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName])
|
||||
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled])
|
||||
|
||||
if (!formSchemas?.length)
|
||||
return null
|
||||
|
||||
return (
|
||||
<form className={cn(formClassName)}>
|
||||
<form
|
||||
className={cn(formClassName)}
|
||||
>
|
||||
{
|
||||
formSchemas.map((formSchema) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export type FormSchema = {
|
|||
required: boolean
|
||||
default?: any
|
||||
tooltip?: string | TypeWithI18N
|
||||
show_on: FormShowOnObject[]
|
||||
show_on?: FormShowOnObject[]
|
||||
url?: string
|
||||
scope?: string
|
||||
}
|
||||
|
|
@ -46,6 +46,6 @@ export type FormSchema = {
|
|||
export type FormValues = Record<string, any>
|
||||
|
||||
export type FromRefObject = {
|
||||
getFormStore: () => AnyFormApi['store']
|
||||
getForm: () => AnyFormApi
|
||||
}
|
||||
export type FormRef = ForwardedRef<FromRefObject>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ type ModalProps = {
|
|||
onExtraButtonClick?: () => void
|
||||
footerSlot?: React.ReactNode
|
||||
bottomSlot?: React.ReactNode
|
||||
disabled?: boolean
|
||||
}
|
||||
const Modal = ({
|
||||
onClose,
|
||||
|
|
@ -42,13 +43,14 @@ const Modal = ({
|
|||
onExtraButtonClick,
|
||||
footerSlot,
|
||||
bottomSlot,
|
||||
disabled,
|
||||
}: ModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent
|
||||
className='z-[999999] flex h-full w-full items-center justify-center bg-background-overlay'
|
||||
className='z-[9998] flex h-full w-full items-center justify-center bg-background-overlay'
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
|
|
@ -88,6 +90,7 @@ const Modal = ({
|
|||
<Button
|
||||
variant={extraButtonVariant}
|
||||
onClick={onExtraButtonClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{extraButtonText || t('common.operation.remove')}
|
||||
</Button>
|
||||
|
|
@ -97,6 +100,7 @@ const Modal = ({
|
|||
}
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
disabled={disabled}
|
||||
>
|
||||
{cancelButtonText || t('common.operation.cancel')}
|
||||
</Button>
|
||||
|
|
@ -104,6 +108,7 @@ const Modal = ({
|
|||
className='ml-2'
|
||||
variant='primary'
|
||||
onClick={onConfirm}
|
||||
disabled={disabled}
|
||||
>
|
||||
{confirmButtonText || t('common.operation.save')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -14,8 +15,10 @@ import {
|
|||
useUpdatePluginToolCredential,
|
||||
} from '@/service/use-plugins-auth'
|
||||
import { CredentialTypeEnum } from '../types'
|
||||
import { transformFormSchemasSecretInput } from '../utils'
|
||||
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
||||
import type { FromRefObject } from '@/app/components/base/form/types'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
|
||||
export type ApiKeyModalProps = {
|
||||
|
|
@ -23,34 +26,65 @@ export type ApiKeyModalProps = {
|
|||
onClose?: () => void
|
||||
editValues?: Record<string, any>
|
||||
onRemove?: () => void
|
||||
disabled?: boolean
|
||||
}
|
||||
const ApiKeyModal = ({
|
||||
provider,
|
||||
onClose,
|
||||
editValues,
|
||||
onRemove,
|
||||
disabled,
|
||||
}: ApiKeyModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const { data } = useGetPluginToolCredentialSchema(provider, CredentialTypeEnum.API_KEY)
|
||||
const { data = [] } = useGetPluginToolCredentialSchema(provider, CredentialTypeEnum.API_KEY)
|
||||
const formSchemas = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
type: FormTypeEnum.textInput,
|
||||
name: '__name__',
|
||||
label: 'Authorization name',
|
||||
required: false,
|
||||
},
|
||||
...data,
|
||||
]
|
||||
}, [data])
|
||||
const { mutateAsync: addPluginToolCredential } = useAddPluginToolCredential(provider)
|
||||
const { mutateAsync: updatePluginToolCredential } = useUpdatePluginToolCredential(provider)
|
||||
const invalidatePluginToolCredentialInfo = useInvalidPluginToolCredentialInfo(provider)
|
||||
const formRef = useRef<FromRefObject>(null)
|
||||
const handleConfirm = useCallback(async () => {
|
||||
const store = formRef.current?.getFormStore()
|
||||
const values = store?.state.values
|
||||
const form = formRef.current?.getForm()
|
||||
const store = form?.store
|
||||
const {
|
||||
__name__,
|
||||
__credential_id__,
|
||||
...values
|
||||
} = store?.state.values
|
||||
const isPristineSecretInputNames: string[] = []
|
||||
formSchemas.forEach((schema) => {
|
||||
if (schema.type === FormTypeEnum.secretInput) {
|
||||
const fieldMeta = form?.getFieldMeta(schema.name)
|
||||
if (fieldMeta?.isPristine)
|
||||
isPristineSecretInputNames.push(schema.name)
|
||||
}
|
||||
})
|
||||
|
||||
const transformedValues = transformFormSchemasSecretInput(isPristineSecretInputNames, values)
|
||||
|
||||
if (editValues) {
|
||||
await updatePluginToolCredential({
|
||||
credentials: values,
|
||||
credentials: transformedValues,
|
||||
credential_id: __credential_id__,
|
||||
type: CredentialTypeEnum.API_KEY,
|
||||
name: __name__ || '',
|
||||
})
|
||||
}
|
||||
else {
|
||||
await addPluginToolCredential({
|
||||
credentials: values,
|
||||
credentials: transformedValues,
|
||||
type: CredentialTypeEnum.API_KEY,
|
||||
name: __name__ || '',
|
||||
})
|
||||
}
|
||||
notify({
|
||||
|
|
@ -60,7 +94,7 @@ const ApiKeyModal = ({
|
|||
|
||||
onClose?.()
|
||||
invalidatePluginToolCredentialInfo()
|
||||
}, [addPluginToolCredential, onClose, invalidatePluginToolCredentialInfo, updatePluginToolCredential, notify, t, editValues])
|
||||
}, [addPluginToolCredential, onClose, invalidatePluginToolCredentialInfo, updatePluginToolCredential, notify, t, editValues, formSchemas])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
@ -96,11 +130,13 @@ const ApiKeyModal = ({
|
|||
onConfirm={handleConfirm}
|
||||
showExtraButton={!!editValues}
|
||||
onExtraButtonClick={onRemove}
|
||||
disabled={disabled}
|
||||
>
|
||||
<AuthForm
|
||||
ref={formRef}
|
||||
formSchemas={data}
|
||||
formSchemas={formSchemas}
|
||||
defaultValues={editValues}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const Authorize = ({
|
|||
}
|
||||
|
||||
return {
|
||||
buttonText: !canApiKey ? 'Use OAuth Authorization' : '',
|
||||
buttonText: !canApiKey ? 'Use OAuth Authorization' : 'Use OAuth',
|
||||
}
|
||||
}, [canApiKey, theme])
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ const Authorize = ({
|
|||
}
|
||||
return {
|
||||
provider,
|
||||
buttonText: !canOAuth ? 'API Key Authorization Configuration' : '',
|
||||
buttonText: !canOAuth ? 'API Key Authorization Configuration' : 'Use API Key',
|
||||
buttonVariant: !canOAuth ? 'primary' : 'secondary-accent',
|
||||
}
|
||||
}, [canOAuth, theme, provider])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Credential } from './types'
|
||||
import {
|
||||
Authorized,
|
||||
usePluginAuth,
|
||||
} from '.'
|
||||
|
||||
type AuthorizedInNodeProps = {
|
||||
provider: string
|
||||
onAuthorizationItemClick: (id: string) => void
|
||||
credentialId?: string
|
||||
}
|
||||
const AuthorizedInNode = ({
|
||||
provider = '',
|
||||
onAuthorizationItemClick,
|
||||
credentialId,
|
||||
}: AuthorizedInNodeProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const {
|
||||
canApiKey,
|
||||
canOAuth,
|
||||
credentials,
|
||||
disabled,
|
||||
} = usePluginAuth(provider, isOpen)
|
||||
const label = useMemo(() => {
|
||||
if (!credentialId)
|
||||
return 'Workspace default'
|
||||
const credential = credentials.find(c => c.id === credentialId)
|
||||
|
||||
if (!credential)
|
||||
return 'Auth removed'
|
||||
|
||||
return credential.name
|
||||
}, [credentials, credentialId])
|
||||
const renderTrigger = useCallback((open?: boolean) => {
|
||||
return (
|
||||
<Button
|
||||
size='small'
|
||||
className={cn(open && 'bg-components-button-ghost-bg-hover')}
|
||||
>
|
||||
<Indicator className='mr-1.5' />
|
||||
{label}
|
||||
<RiArrowDownSLine className='h-3.5 w-3.5 text-components-button-ghost-text' />
|
||||
</Button>
|
||||
)
|
||||
}, [label])
|
||||
const extraAuthorizationItems: Credential[] = [
|
||||
{
|
||||
id: '__workspace_default__',
|
||||
name: 'Workspace default',
|
||||
provider: '',
|
||||
is_default: false,
|
||||
isWorkspaceDefault: true,
|
||||
},
|
||||
]
|
||||
const handleAuthorizationItemClick = useCallback((id: string) => {
|
||||
onAuthorizationItemClick(id)
|
||||
setIsOpen(false)
|
||||
}, [
|
||||
onAuthorizationItemClick,
|
||||
setIsOpen,
|
||||
])
|
||||
|
||||
return (
|
||||
<Authorized
|
||||
provider={provider}
|
||||
credentials={credentials}
|
||||
canOAuth={canOAuth}
|
||||
canApiKey={canApiKey}
|
||||
renderTrigger={renderTrigger}
|
||||
isOpen={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
offset={4}
|
||||
placement='bottom-end'
|
||||
triggerPopupSameWidth={false}
|
||||
popupClassName='w-[360px]'
|
||||
disabled={disabled}
|
||||
disableSetDefault
|
||||
onItemClick={handleAuthorizationItemClick}
|
||||
extraAuthorizationItems={extraAuthorizationItems}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AuthorizedInNode)
|
||||
|
|
@ -13,6 +13,9 @@ import {
|
|||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type {
|
||||
PortalToFollowElemOptions,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import cn from '@/utils/classnames'
|
||||
|
|
@ -35,6 +38,16 @@ type AuthorizedProps = {
|
|||
canOAuth?: boolean
|
||||
canApiKey?: boolean
|
||||
disabled?: boolean
|
||||
renderTrigger?: (open?: boolean) => React.ReactNode
|
||||
isOpen?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
offset?: PortalToFollowElemOptions['offset']
|
||||
placement?: PortalToFollowElemOptions['placement']
|
||||
triggerPopupSameWidth?: boolean
|
||||
popupClassName?: string
|
||||
disableSetDefault?: boolean
|
||||
onItemClick?: (id: string) => void
|
||||
extraAuthorizationItems?: Credential[]
|
||||
}
|
||||
const Authorized = ({
|
||||
provider,
|
||||
|
|
@ -42,10 +55,27 @@ const Authorized = ({
|
|||
canOAuth,
|
||||
canApiKey,
|
||||
disabled,
|
||||
renderTrigger,
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
offset = 8,
|
||||
placement = 'bottom-start',
|
||||
triggerPopupSameWidth = true,
|
||||
popupClassName,
|
||||
disableSetDefault,
|
||||
onItemClick,
|
||||
extraAuthorizationItems,
|
||||
}: AuthorizedProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isLocalOpen, setIsLocalOpen] = useState(false)
|
||||
const mergedIsOpen = isOpen ?? isLocalOpen
|
||||
const setMergedIsOpen = useCallback((open: boolean) => {
|
||||
if (onOpenChange)
|
||||
onOpenChange(open)
|
||||
|
||||
setIsLocalOpen(open)
|
||||
}, [onOpenChange])
|
||||
const oAuthCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.OAUTH2)
|
||||
const apiKeyCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.API_KEY)
|
||||
const pendingOperationCredentialId = useRef<string | null>(null)
|
||||
|
|
@ -98,28 +128,57 @@ const Authorized = ({
|
|||
return (
|
||||
<>
|
||||
<PortalToFollowElem
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
placement='bottom-start'
|
||||
offset={8}
|
||||
triggerPopupSameWidth
|
||||
open={mergedIsOpen}
|
||||
onOpenChange={setMergedIsOpen}
|
||||
placement={placement}
|
||||
offset={offset}
|
||||
triggerPopupSameWidth={triggerPopupSameWidth}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
onClick={() => setMergedIsOpen(!mergedIsOpen)}
|
||||
asChild
|
||||
>
|
||||
<Button
|
||||
className={cn(
|
||||
'w-full',
|
||||
isOpen && 'bg-components-button-secondary-bg-hover',
|
||||
)}>
|
||||
<Indicator className='mr-2' />
|
||||
{credentials.length} Authorizations
|
||||
<RiArrowDownSLine className='ml-0.5 h-4 w-4' />
|
||||
</Button>
|
||||
{
|
||||
renderTrigger
|
||||
? renderTrigger(mergedIsOpen)
|
||||
: (
|
||||
<Button
|
||||
className={cn(
|
||||
'w-full',
|
||||
isOpen && 'bg-components-button-secondary-bg-hover',
|
||||
)}>
|
||||
<Indicator className='mr-2' />
|
||||
{credentials.length} Authorizations
|
||||
<RiArrowDownSLine className='ml-0.5 h-4 w-4' />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[100]'>
|
||||
<div className='max-h-[360px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
<div className={cn(
|
||||
'max-h-[360px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg',
|
||||
popupClassName,
|
||||
)}>
|
||||
{
|
||||
!!extraAuthorizationItems?.length && (
|
||||
<div className='p-1'>
|
||||
{
|
||||
extraAuthorizationItems.map(credential => (
|
||||
<Item
|
||||
key={credential.id}
|
||||
credential={credential}
|
||||
disabled={disabled}
|
||||
onItemClick={onItemClick}
|
||||
disableRename
|
||||
disableEdit
|
||||
disableDelete
|
||||
disableSetDefault
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='py-1'>
|
||||
{
|
||||
!!oAuthCredentials.length && (
|
||||
|
|
@ -153,6 +212,8 @@ const Authorized = ({
|
|||
onDelete={openConfirm}
|
||||
onEdit={handleEdit}
|
||||
onSetDefault={handleSetDefault}
|
||||
disableSetDefault={disableSetDefault}
|
||||
onItemClick={onItemClick}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
|
@ -195,6 +256,7 @@ const Authorized = ({
|
|||
pendingOperationCredentialId.current = null
|
||||
}}
|
||||
onRemove={handleRemove}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
|
|
@ -20,6 +21,11 @@ type ItemProps = {
|
|||
onDelete?: (id: string) => void
|
||||
onEdit?: (id: string, values: Record<string, any>) => void
|
||||
onSetDefault?: (id: string) => void
|
||||
disableRename?: boolean
|
||||
disableEdit?: boolean
|
||||
disableDelete?: boolean
|
||||
disableSetDefault?: boolean
|
||||
onItemClick?: (id: string) => void
|
||||
}
|
||||
const Item = ({
|
||||
credential,
|
||||
|
|
@ -27,16 +33,25 @@ const Item = ({
|
|||
onDelete,
|
||||
onEdit,
|
||||
onSetDefault,
|
||||
disableRename,
|
||||
disableEdit,
|
||||
disableDelete,
|
||||
disableSetDefault,
|
||||
onItemClick,
|
||||
}: ItemProps) => {
|
||||
const isOAuth = credential.credential_type === CredentialTypeEnum.OAUTH2
|
||||
const showAction = useMemo(() => {
|
||||
return !(disableRename && disableEdit && disableDelete && disableSetDefault)
|
||||
}, [disableRename, disableEdit, disableDelete, disableSetDefault])
|
||||
|
||||
return (
|
||||
<div
|
||||
key={credential.id}
|
||||
className='group flex h-8 items-center rounded-lg p-1 hover:bg-state-base-hover'
|
||||
onClick={() => onItemClick?.(credential.id)}
|
||||
>
|
||||
<div className='flex grow items-center space-x-1.5 pl-2'>
|
||||
<Indicator className='mr-1.5' />
|
||||
<div className='flex w-0 grow items-center space-x-1.5 pl-2'>
|
||||
<Indicator className='mr-1.5 shrink-0' />
|
||||
<div
|
||||
className='system-md-regular truncate text-text-secondary'
|
||||
title={credential.name}
|
||||
|
|
@ -51,44 +66,72 @@ const Item = ({
|
|||
)
|
||||
}
|
||||
</div>
|
||||
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'>
|
||||
<Button
|
||||
size='small'
|
||||
disabled={disabled}
|
||||
onClick={() => onSetDefault?.(credential.id)}
|
||||
>
|
||||
Set as default
|
||||
</Button>
|
||||
{
|
||||
isOAuth && (
|
||||
<Tooltip popupContent='rename'>
|
||||
<ActionButton>
|
||||
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isOAuth && (
|
||||
<Tooltip popupContent='edit'>
|
||||
<ActionButton
|
||||
disabled={disabled}
|
||||
onClick={() => onEdit?.(credential.id, credential.credentials)}
|
||||
>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
<Tooltip popupContent='delete'>
|
||||
<ActionButton
|
||||
disabled={disabled}
|
||||
onClick={() => onDelete?.(credential.id)}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{
|
||||
showAction && (
|
||||
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'>
|
||||
{
|
||||
!credential.is_default && !disableSetDefault && (
|
||||
<Button
|
||||
size='small'
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onSetDefault?.(credential.id)
|
||||
}}
|
||||
>
|
||||
Set as default
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
isOAuth && !disableRename && (
|
||||
<Tooltip popupContent='rename'>
|
||||
<ActionButton>
|
||||
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isOAuth && !disableEdit && (
|
||||
<Tooltip popupContent='edit'>
|
||||
<ActionButton
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onEdit?.(
|
||||
credential.id,
|
||||
{
|
||||
...credential.credentials,
|
||||
__name__: credential.name,
|
||||
__credential_id__: credential.id,
|
||||
},
|
||||
)
|
||||
}}
|
||||
>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
{
|
||||
!disableDelete && (
|
||||
<Tooltip popupContent='delete'>
|
||||
<ActionButton
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onDelete?.(credential.id)
|
||||
}}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { useAppContext } from '@/context/app-context'
|
||||
import { useGetPluginToolCredentialInfo } from '@/service/use-plugins-auth'
|
||||
import { CredentialTypeEnum } from './types'
|
||||
|
||||
export const usePluginAuth = (provider: string, enable?: boolean) => {
|
||||
const { data } = useGetPluginToolCredentialInfo(enable ? provider : '')
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const isAuthorized = !!data?.credentials.length
|
||||
const canOAuth = data?.supported_credential_types.includes(CredentialTypeEnum.OAUTH2)
|
||||
const canApiKey = data?.supported_credential_types.includes(CredentialTypeEnum.API_KEY)
|
||||
|
||||
return {
|
||||
isAuthorized,
|
||||
canOAuth,
|
||||
canApiKey,
|
||||
credentials: data?.credentials || [],
|
||||
provider,
|
||||
disabled: !isCurrentWorkspaceManager,
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,4 @@
|
|||
export { default as PluginAuth } from './plugin-auth'
|
||||
export { default as Authorized } from './authorized'
|
||||
export { default as AuthorizedInNode } from './authorized-in-node'
|
||||
export { usePluginAuth } from './hooks'
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ import { CredentialTypeEnum } from './types'
|
|||
|
||||
type PluginAuthProps = {
|
||||
provider?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
const PluginAuth = ({
|
||||
provider = '',
|
||||
children,
|
||||
}: PluginAuthProps) => {
|
||||
const { data } = useGetPluginToolCredentialInfo(provider)
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
|
|
@ -30,7 +32,7 @@ const PluginAuth = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
isAuthorized && (
|
||||
isAuthorized && !children && (
|
||||
<Authorized
|
||||
provider={provider}
|
||||
credentials={data?.credentials}
|
||||
|
|
@ -40,6 +42,9 @@ const PluginAuth = ({
|
|||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
isAuthorized && children
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ export type Credential = {
|
|||
id: string
|
||||
name: string
|
||||
provider: string
|
||||
credential_type: CredentialTypeEnum
|
||||
credential_type?: CredentialTypeEnum
|
||||
is_default: boolean
|
||||
credentials: Record<string, any>
|
||||
credentials?: Record<string, any>
|
||||
isWorkspaceDefault?: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
export const transformFormSchemasSecretInput = (isPristineSecretInputNames: string[], values: Record<string, any>) => {
|
||||
const transformedValues: Record<string, any> = { ...values }
|
||||
|
||||
isPristineSecretInputNames.forEach((name) => {
|
||||
if (transformedValues[name])
|
||||
transformedValues[name] = '[__HIDDEN__]'
|
||||
})
|
||||
|
||||
return transformedValues
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ export type ToolDefaultValue = {
|
|||
params: Record<string, any>
|
||||
paramSchemas: Record<string, any>[]
|
||||
output_schema: Record<string, any>
|
||||
credential_id?: string
|
||||
}
|
||||
|
||||
export type ToolValue = {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ import { useLogs } from '@/app/components/workflow/run/hooks'
|
|||
import PanelWrap from '../before-run-form/panel-wrap'
|
||||
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
||||
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import {
|
||||
AuthorizedInNode,
|
||||
PluginAuth,
|
||||
} from '@/app/components/plugins/plugin-auth'
|
||||
import { canFindTool } from '@/utils'
|
||||
|
||||
type BasePanelProps = {
|
||||
children: ReactNode
|
||||
|
|
@ -215,6 +220,22 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||
return {}
|
||||
})()
|
||||
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const currCollection = useMemo(() => {
|
||||
return buildInTools.find(item => canFindTool(item.id, data.provider_id))
|
||||
}, [buildInTools, data.provider_id])
|
||||
const showPluginAuth = useMemo(() => {
|
||||
return data.type === BlockEnum.Tool && currCollection?.allow_delete && !currCollection.is_team_authorization
|
||||
}, [currCollection, data.type])
|
||||
const handleAuthorizationItemClick = useCallback((id: string) => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
credential_id: id === '__workspace_default__' ? undefined : id,
|
||||
},
|
||||
})
|
||||
}, [handleNodeDataUpdate])
|
||||
|
||||
if(logParams.showSpecialResultPanel) {
|
||||
return (
|
||||
<div className={cn(
|
||||
|
|
@ -347,12 +368,39 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='pl-4'>
|
||||
<Tab
|
||||
value={tabType}
|
||||
onChange={setTabType}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
showPluginAuth && (
|
||||
<PluginAuth
|
||||
provider={currCollection?.name}
|
||||
>
|
||||
<div className='pl-4'>
|
||||
<Tab
|
||||
value={tabType}
|
||||
onChange={setTabType}
|
||||
/>
|
||||
</div>
|
||||
</PluginAuth>
|
||||
)
|
||||
}
|
||||
{
|
||||
!showPluginAuth && (
|
||||
<div className='flex items-center justify-between pl-4 pr-3'>
|
||||
<Tab
|
||||
value={tabType}
|
||||
onChange={setTabType}
|
||||
/>
|
||||
{
|
||||
currCollection?.allow_delete && (
|
||||
<AuthorizedInNode
|
||||
provider={currCollection?.name}
|
||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
||||
credentialId={data.credential_id}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<Split />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,18 +5,13 @@ import Split from '../_base/components/split'
|
|||
import type { ToolNodeType } from './types'
|
||||
import useConfig from './use-config'
|
||||
import InputVarList from './components/input-var-list'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
|
||||
import { Type } from '../llm/types'
|
||||
import {
|
||||
PluginAuth,
|
||||
} from '@/app/components/plugins/plugin-auth'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.tool'
|
||||
|
||||
|
|
@ -38,10 +33,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||
setToolSettingValue,
|
||||
currCollection,
|
||||
isShowAuthBtn,
|
||||
showSetAuth,
|
||||
showSetAuthModal,
|
||||
hideSetAuthModal,
|
||||
handleSaveAuth,
|
||||
isLoading,
|
||||
outputSchema,
|
||||
hasObjectOutput,
|
||||
|
|
@ -56,24 +47,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||
|
||||
return (
|
||||
<div className='pt-2'>
|
||||
<div className='px-4 py-2'>
|
||||
<PluginAuth
|
||||
provider={currCollection?.name}
|
||||
/>
|
||||
</div>
|
||||
{!readOnly && isShowAuthBtn && (
|
||||
<>
|
||||
<div className='px-4'>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-full'
|
||||
onClick={showSetAuthModal}
|
||||
>
|
||||
{t(`${i18nPrefix}.authorize`)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!isShowAuthBtn && <>
|
||||
<div className='space-y-4 px-4'>
|
||||
{toolInputVarSchema.length > 0 && (
|
||||
|
|
@ -114,16 +87,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||
/>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
{showSetAuth && (
|
||||
<ConfigCredential
|
||||
collection={currCollection!}
|
||||
onCancel={hideSetAuthModal}
|
||||
onSaved={handleSaveAuth}
|
||||
isHideRemoveBtn
|
||||
/>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export type CommonNodeType<T = {}> = {
|
|||
error_strategy?: ErrorHandleTypeEnum
|
||||
retry_config?: WorkflowRetryConfig
|
||||
default_value?: DefaultValueForm[]
|
||||
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
||||
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name' | 'credential_id'>>
|
||||
|
||||
export type CommonEdgeType = {
|
||||
_hovering?: boolean
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export const useUpdatePluginToolCredential = (
|
|||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (params: {
|
||||
credential_id: string
|
||||
credentials: Record<string, any>
|
||||
type: CredentialTypeEnum
|
||||
name?: string
|
||||
|
|
|
|||
Loading…
Reference in New Issue