tool oauth

pull/22338/head^2
zxhlyh 7 months ago
parent ce8bf7b5a2
commit 8968a3e254

@ -4,6 +4,7 @@ import {
useMemo, useMemo,
} from 'react' } from 'react'
import type { AnyFieldApi } from '@tanstack/react-form' import type { AnyFieldApi } from '@tanstack/react-form'
import { useStore } from '@tanstack/react-form'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import type { FormSchema } from '@/app/components/base/form/types' import type { FormSchema } from '@/app/components/base/form/types'
@ -17,6 +18,7 @@ export type BaseFieldProps = {
inputClassName?: string inputClassName?: string
formSchema: FormSchema formSchema: FormSchema
field: AnyFieldApi field: AnyFieldApi
disabled?: boolean
} }
const BaseField = ({ const BaseField = ({
fieldClassName, fieldClassName,
@ -25,6 +27,7 @@ const BaseField = ({
inputClassName, inputClassName,
formSchema, formSchema,
field, field,
disabled,
}: BaseFieldProps) => { }: BaseFieldProps) => {
const renderI18nObject = useRenderI18nObject() const renderI18nObject = useRenderI18nObject()
const { const {
@ -35,9 +38,13 @@ const BaseField = ({
if (isValidElement(label)) if (isValidElement(label))
return label return label
if (typeof label === 'string')
return label
if (typeof label === 'object' && label !== null) if (typeof label === 'object' && label !== null)
return renderI18nObject(label as Record<string, string>) return renderI18nObject(label as Record<string, string>)
}, [label, renderI18nObject]) }, [label, renderI18nObject])
const value = useStore(field.form.store, s => s.values[field.name])
return ( return (
<div className={cn(fieldClassName)}> <div className={cn(fieldClassName)}>
@ -48,23 +55,27 @@ const BaseField = ({
{ {
formSchema.type === FormTypeEnum.textInput && ( formSchema.type === FormTypeEnum.textInput && (
<Input <Input
className={cn(inputClassName)}
id={field.name} id={field.name}
value={field.state.value} name={field.name}
className={cn(inputClassName)}
value={value}
onChange={e => field.handleChange(e.target.value)} onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur} onBlur={field.handleBlur}
disabled={disabled}
/> />
) )
} }
{ {
formSchema.type === FormTypeEnum.secretInput && ( formSchema.type === FormTypeEnum.secretInput && (
<Input <Input
id={field.name}
name={field.name}
type='password' type='password'
className={cn(inputClassName)} className={cn(inputClassName)}
id={field.name} value={value}
value={field.state.value}
onChange={e => field.handleChange(e.target.value)} onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur} onBlur={field.handleBlur}
disabled={disabled}
/> />
) )
} }

@ -24,6 +24,7 @@ export type BaseFormProps = {
defaultValues?: Record<string, any> defaultValues?: Record<string, any>
formClassName?: string formClassName?: string
ref?: FormRef ref?: FormRef
disabled?: boolean
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'> } & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
const BaseForm = ({ const BaseForm = ({
@ -35,6 +36,7 @@ const BaseForm = ({
inputContainerClassName, inputContainerClassName,
inputClassName, inputClassName,
ref, ref,
disabled,
}: BaseFormProps) => { }: BaseFormProps) => {
const form = useForm({ const form = useForm({
defaultValues, defaultValues,
@ -42,8 +44,8 @@ const BaseForm = ({
useImperativeHandle(ref, () => { useImperativeHandle(ref, () => {
return { return {
getFormStore() { getForm() {
return form.store return form
}, },
} }
}, [form]) }, [form])
@ -60,18 +62,21 @@ const BaseForm = ({
labelClassName={labelClassName} labelClassName={labelClassName}
inputContainerClassName={inputContainerClassName} inputContainerClassName={inputContainerClassName}
inputClassName={inputClassName} inputClassName={inputClassName}
disabled={disabled}
/> />
) )
} }
return null return null
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName]) }, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled])
if (!formSchemas?.length) if (!formSchemas?.length)
return null return null
return ( return (
<form className={cn(formClassName)}> <form
className={cn(formClassName)}
>
{ {
formSchemas.map((formSchema) => { formSchemas.map((formSchema) => {
return ( return (

@ -38,7 +38,7 @@ export type FormSchema = {
required: boolean required: boolean
default?: any default?: any
tooltip?: string | TypeWithI18N tooltip?: string | TypeWithI18N
show_on: FormShowOnObject[] show_on?: FormShowOnObject[]
url?: string url?: string
scope?: string scope?: string
} }
@ -46,6 +46,6 @@ export type FormSchema = {
export type FormValues = Record<string, any> export type FormValues = Record<string, any>
export type FromRefObject = { export type FromRefObject = {
getFormStore: () => AnyFormApi['store'] getForm: () => AnyFormApi
} }
export type FormRef = ForwardedRef<FromRefObject> export type FormRef = ForwardedRef<FromRefObject>

@ -25,6 +25,7 @@ type ModalProps = {
onExtraButtonClick?: () => void onExtraButtonClick?: () => void
footerSlot?: React.ReactNode footerSlot?: React.ReactNode
bottomSlot?: React.ReactNode bottomSlot?: React.ReactNode
disabled?: boolean
} }
const Modal = ({ const Modal = ({
onClose, onClose,
@ -42,13 +43,14 @@ const Modal = ({
onExtraButtonClick, onExtraButtonClick,
footerSlot, footerSlot,
bottomSlot, bottomSlot,
disabled,
}: ModalProps) => { }: ModalProps) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<PortalToFollowElem open> <PortalToFollowElem open>
<PortalToFollowElemContent <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} onClick={onClose}
> >
<div <div
@ -88,6 +90,7 @@ const Modal = ({
<Button <Button
variant={extraButtonVariant} variant={extraButtonVariant}
onClick={onExtraButtonClick} onClick={onExtraButtonClick}
disabled={disabled}
> >
{extraButtonText || t('common.operation.remove')} {extraButtonText || t('common.operation.remove')}
</Button> </Button>
@ -97,6 +100,7 @@ const Modal = ({
} }
<Button <Button
onClick={onCancel} onClick={onCancel}
disabled={disabled}
> >
{cancelButtonText || t('common.operation.cancel')} {cancelButtonText || t('common.operation.cancel')}
</Button> </Button>
@ -104,6 +108,7 @@ const Modal = ({
className='ml-2' className='ml-2'
variant='primary' variant='primary'
onClick={onConfirm} onClick={onConfirm}
disabled={disabled}
> >
{confirmButtonText || t('common.operation.save')} {confirmButtonText || t('common.operation.save')}
</Button> </Button>

@ -1,6 +1,7 @@
import { import {
memo, memo,
useCallback, useCallback,
useMemo,
useRef, useRef,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -14,8 +15,10 @@ import {
useUpdatePluginToolCredential, useUpdatePluginToolCredential,
} from '@/service/use-plugins-auth' } from '@/service/use-plugins-auth'
import { CredentialTypeEnum } from '../types' import { CredentialTypeEnum } from '../types'
import { transformFormSchemasSecretInput } from '../utils'
import AuthForm from '@/app/components/base/form/form-scenarios/auth' import AuthForm from '@/app/components/base/form/form-scenarios/auth'
import type { FromRefObject } from '@/app/components/base/form/types' import type { FromRefObject } 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'
export type ApiKeyModalProps = { export type ApiKeyModalProps = {
@ -23,34 +26,65 @@ export type ApiKeyModalProps = {
onClose?: () => void onClose?: () => void
editValues?: Record<string, any> editValues?: Record<string, any>
onRemove?: () => void onRemove?: () => void
disabled?: boolean
} }
const ApiKeyModal = ({ const ApiKeyModal = ({
provider, provider,
onClose, onClose,
editValues, editValues,
onRemove, onRemove,
disabled,
}: ApiKeyModalProps) => { }: ApiKeyModalProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useToastContext() 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: addPluginToolCredential } = useAddPluginToolCredential(provider)
const { mutateAsync: updatePluginToolCredential } = useUpdatePluginToolCredential(provider) const { mutateAsync: updatePluginToolCredential } = useUpdatePluginToolCredential(provider)
const invalidatePluginToolCredentialInfo = useInvalidPluginToolCredentialInfo(provider) const invalidatePluginToolCredentialInfo = useInvalidPluginToolCredentialInfo(provider)
const formRef = useRef<FromRefObject>(null) const formRef = useRef<FromRefObject>(null)
const handleConfirm = useCallback(async () => { const handleConfirm = useCallback(async () => {
const store = formRef.current?.getFormStore() const form = formRef.current?.getForm()
const values = store?.state.values 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) { if (editValues) {
await updatePluginToolCredential({ await updatePluginToolCredential({
credentials: values, credentials: transformedValues,
credential_id: __credential_id__,
type: CredentialTypeEnum.API_KEY, type: CredentialTypeEnum.API_KEY,
name: __name__ || '',
}) })
} }
else { else {
await addPluginToolCredential({ await addPluginToolCredential({
credentials: values, credentials: transformedValues,
type: CredentialTypeEnum.API_KEY, type: CredentialTypeEnum.API_KEY,
name: __name__ || '',
}) })
} }
notify({ notify({
@ -60,7 +94,7 @@ const ApiKeyModal = ({
onClose?.() onClose?.()
invalidatePluginToolCredentialInfo() invalidatePluginToolCredentialInfo()
}, [addPluginToolCredential, onClose, invalidatePluginToolCredentialInfo, updatePluginToolCredential, notify, t, editValues]) }, [addPluginToolCredential, onClose, invalidatePluginToolCredentialInfo, updatePluginToolCredential, notify, t, editValues, formSchemas])
return ( return (
<Modal <Modal
@ -96,11 +130,13 @@ const ApiKeyModal = ({
onConfirm={handleConfirm} onConfirm={handleConfirm}
showExtraButton={!!editValues} showExtraButton={!!editValues}
onExtraButtonClick={onRemove} onExtraButtonClick={onRemove}
disabled={disabled}
> >
<AuthForm <AuthForm
ref={formRef} ref={formRef}
formSchemas={data} formSchemas={formSchemas}
defaultValues={editValues} defaultValues={editValues}
disabled={disabled}
/> />
</Modal> </Modal>
) )

@ -36,7 +36,7 @@ const Authorize = ({
} }
return { return {
buttonText: !canApiKey ? 'Use OAuth Authorization' : '', buttonText: !canApiKey ? 'Use OAuth Authorization' : 'Use OAuth',
} }
}, [canApiKey, theme]) }, [canApiKey, theme])
@ -50,7 +50,7 @@ const Authorize = ({
} }
return { return {
provider, provider,
buttonText: !canOAuth ? 'API Key Authorization Configuration' : '', buttonText: !canOAuth ? 'API Key Authorization Configuration' : 'Use API Key',
buttonVariant: !canOAuth ? 'primary' : 'secondary-accent', buttonVariant: !canOAuth ? 'primary' : 'secondary-accent',
} }
}, [canOAuth, theme, provider]) }, [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, PortalToFollowElemContent,
PortalToFollowElemTrigger, PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem' } 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 Button from '@/app/components/base/button'
import Indicator from '@/app/components/header/indicator' import Indicator from '@/app/components/header/indicator'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
@ -35,6 +38,16 @@ type AuthorizedProps = {
canOAuth?: boolean canOAuth?: boolean
canApiKey?: boolean canApiKey?: boolean
disabled?: 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 = ({ const Authorized = ({
provider, provider,
@ -42,10 +55,27 @@ const Authorized = ({
canOAuth, canOAuth,
canApiKey, canApiKey,
disabled, disabled,
renderTrigger,
isOpen,
onOpenChange,
offset = 8,
placement = 'bottom-start',
triggerPopupSameWidth = true,
popupClassName,
disableSetDefault,
onItemClick,
extraAuthorizationItems,
}: AuthorizedProps) => { }: AuthorizedProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useToastContext() 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 oAuthCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.OAUTH2)
const apiKeyCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.API_KEY) const apiKeyCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.API_KEY)
const pendingOperationCredentialId = useRef<string | null>(null) const pendingOperationCredentialId = useRef<string | null>(null)
@ -98,28 +128,57 @@ const Authorized = ({
return ( return (
<> <>
<PortalToFollowElem <PortalToFollowElem
open={isOpen} open={mergedIsOpen}
onOpenChange={setIsOpen} onOpenChange={setMergedIsOpen}
placement='bottom-start' placement={placement}
offset={8} offset={offset}
triggerPopupSameWidth triggerPopupSameWidth={triggerPopupSameWidth}
> >
<PortalToFollowElemTrigger <PortalToFollowElemTrigger
onClick={() => setIsOpen(!isOpen)} onClick={() => setMergedIsOpen(!mergedIsOpen)}
asChild asChild
> >
<Button {
className={cn( renderTrigger
'w-full', ? renderTrigger(mergedIsOpen)
isOpen && 'bg-components-button-secondary-bg-hover', : (
)}> <Button
<Indicator className='mr-2' /> className={cn(
{credentials.length} Authorizations 'w-full',
<RiArrowDownSLine className='ml-0.5 h-4 w-4' /> isOpen && 'bg-components-button-secondary-bg-hover',
</Button> )}>
<Indicator className='mr-2' />
{credentials.length} Authorizations
<RiArrowDownSLine className='ml-0.5 h-4 w-4' />
</Button>
)
}
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[100]'> <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'> <div className='py-1'>
{ {
!!oAuthCredentials.length && ( !!oAuthCredentials.length && (
@ -153,6 +212,8 @@ const Authorized = ({
onDelete={openConfirm} onDelete={openConfirm}
onEdit={handleEdit} onEdit={handleEdit}
onSetDefault={handleSetDefault} onSetDefault={handleSetDefault}
disableSetDefault={disableSetDefault}
onItemClick={onItemClick}
/> />
)) ))
} }
@ -195,6 +256,7 @@ const Authorized = ({
pendingOperationCredentialId.current = null pendingOperationCredentialId.current = null
}} }}
onRemove={handleRemove} onRemove={handleRemove}
disabled={disabled}
/> />
) )
} }

@ -1,5 +1,6 @@
import { import {
memo, memo,
useMemo,
} from 'react' } from 'react'
import { import {
RiDeleteBinLine, RiDeleteBinLine,
@ -20,6 +21,11 @@ type ItemProps = {
onDelete?: (id: string) => void onDelete?: (id: string) => void
onEdit?: (id: string, values: Record<string, any>) => void onEdit?: (id: string, values: Record<string, any>) => void
onSetDefault?: (id: string) => void onSetDefault?: (id: string) => void
disableRename?: boolean
disableEdit?: boolean
disableDelete?: boolean
disableSetDefault?: boolean
onItemClick?: (id: string) => void
} }
const Item = ({ const Item = ({
credential, credential,
@ -27,16 +33,25 @@ const Item = ({
onDelete, onDelete,
onEdit, onEdit,
onSetDefault, onSetDefault,
disableRename,
disableEdit,
disableDelete,
disableSetDefault,
onItemClick,
}: ItemProps) => { }: ItemProps) => {
const isOAuth = credential.credential_type === CredentialTypeEnum.OAUTH2 const isOAuth = credential.credential_type === CredentialTypeEnum.OAUTH2
const showAction = useMemo(() => {
return !(disableRename && disableEdit && disableDelete && disableSetDefault)
}, [disableRename, disableEdit, disableDelete, disableSetDefault])
return ( return (
<div <div
key={credential.id} key={credential.id}
className='group flex h-8 items-center rounded-lg p-1 hover:bg-state-base-hover' 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'> <div className='flex w-0 grow items-center space-x-1.5 pl-2'>
<Indicator className='mr-1.5' /> <Indicator className='mr-1.5 shrink-0' />
<div <div
className='system-md-regular truncate text-text-secondary' className='system-md-regular truncate text-text-secondary'
title={credential.name} title={credential.name}
@ -51,44 +66,72 @@ const Item = ({
) )
} }
</div> </div>
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'> {
<Button showAction && (
size='small' <div className='ml-2 hidden shrink-0 items-center group-hover:flex'>
disabled={disabled} {
onClick={() => onSetDefault?.(credential.id)} !credential.is_default && !disableSetDefault && (
> <Button
Set as default size='small'
</Button> disabled={disabled}
{ onClick={(e) => {
isOAuth && ( e.stopPropagation()
<Tooltip popupContent='rename'> onSetDefault?.(credential.id)
<ActionButton> }}
<RiEditLine className='h-4 w-4 text-text-tertiary' /> >
</ActionButton> Set as default
</Tooltip> </Button>
) )
} }
{ {
!isOAuth && ( isOAuth && !disableRename && (
<Tooltip popupContent='edit'> <Tooltip popupContent='rename'>
<ActionButton <ActionButton>
disabled={disabled} <RiEditLine className='h-4 w-4 text-text-tertiary' />
onClick={() => onEdit?.(credential.id, credential.credentials)} </ActionButton>
> </Tooltip>
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' /> )
</ActionButton> }
</Tooltip> {
) !isOAuth && !disableEdit && (
} <Tooltip popupContent='edit'>
<Tooltip popupContent='delete'> <ActionButton
<ActionButton disabled={disabled}
disabled={disabled} onClick={(e) => {
onClick={() => onDelete?.(credential.id)} e.stopPropagation()
> onEdit?.(
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' /> credential.id,
</ActionButton> {
</Tooltip> ...credential.credentials,
</div> __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> </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 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 = { type PluginAuthProps = {
provider?: string provider?: string
children?: React.ReactNode
} }
const PluginAuth = ({ const PluginAuth = ({
provider = '', provider = '',
children,
}: PluginAuthProps) => { }: PluginAuthProps) => {
const { data } = useGetPluginToolCredentialInfo(provider) const { data } = useGetPluginToolCredentialInfo(provider)
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
@ -30,7 +32,7 @@ const PluginAuth = ({
) )
} }
{ {
isAuthorized && ( isAuthorized && !children && (
<Authorized <Authorized
provider={provider} provider={provider}
credentials={data?.credentials} credentials={data?.credentials}
@ -40,6 +42,9 @@ const PluginAuth = ({
/> />
) )
} }
{
isAuthorized && children
}
</> </>
) )
} }

@ -7,7 +7,8 @@ export type Credential = {
id: string id: string
name: string name: string
provider: string provider: string
credential_type: CredentialTypeEnum credential_type?: CredentialTypeEnum
is_default: boolean 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> params: Record<string, any>
paramSchemas: Record<string, any>[] paramSchemas: Record<string, any>[]
output_schema: Record<string, any> output_schema: Record<string, any>
credential_id?: string
} }
export type ToolValue = { export type ToolValue = {

@ -59,6 +59,11 @@ import { useLogs } from '@/app/components/workflow/run/hooks'
import PanelWrap from '../before-run-form/panel-wrap' import PanelWrap from '../before-run-form/panel-wrap'
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel' import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' 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 = { type BasePanelProps = {
children: ReactNode children: ReactNode
@ -215,6 +220,22 @@ const BasePanel: FC<BasePanelProps> = ({
return {} 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) { if(logParams.showSpecialResultPanel) {
return ( return (
<div className={cn( <div className={cn(
@ -347,12 +368,39 @@ const BasePanel: FC<BasePanelProps> = ({
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
/> />
</div> </div>
<div className='pl-4'> {
<Tab showPluginAuth && (
value={tabType} <PluginAuth
onChange={setTabType} provider={currCollection?.name}
/> >
</div> <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 /> <Split />
</div> </div>

@ -5,18 +5,13 @@ import Split from '../_base/components/split'
import type { ToolNodeType } from './types' import type { ToolNodeType } from './types'
import useConfig from './use-config' import useConfig from './use-config'
import InputVarList from './components/input-var-list' 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 Field from '@/app/components/workflow/nodes/_base/components/field'
import type { NodePanelProps } from '@/app/components/workflow/types' import type { NodePanelProps } from '@/app/components/workflow/types'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' 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 Loading from '@/app/components/base/loading'
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 StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
import { Type } from '../llm/types' import { Type } from '../llm/types'
import {
PluginAuth,
} from '@/app/components/plugins/plugin-auth'
const i18nPrefix = 'workflow.nodes.tool' const i18nPrefix = 'workflow.nodes.tool'
@ -38,10 +33,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
setToolSettingValue, setToolSettingValue,
currCollection, currCollection,
isShowAuthBtn, isShowAuthBtn,
showSetAuth,
showSetAuthModal,
hideSetAuthModal,
handleSaveAuth,
isLoading, isLoading,
outputSchema, outputSchema,
hasObjectOutput, hasObjectOutput,
@ -56,24 +47,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
return ( return (
<div className='pt-2'> <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 && <> {!isShowAuthBtn && <>
<div className='space-y-4 px-4'> <div className='space-y-4 px-4'>
{toolInputVarSchema.length > 0 && ( {toolInputVarSchema.length > 0 && (
@ -114,16 +87,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
/> />
</div> </div>
</>} </>}
{showSetAuth && (
<ConfigCredential
collection={currCollection!}
onCancel={hideSetAuthModal}
onSaved={handleSaveAuth}
isHideRemoveBtn
/>
)}
<div> <div>
<OutputVars> <OutputVars>
<> <>

@ -92,7 +92,7 @@ export type CommonNodeType<T = {}> = {
error_strategy?: ErrorHandleTypeEnum error_strategy?: ErrorHandleTypeEnum
retry_config?: WorkflowRetryConfig retry_config?: WorkflowRetryConfig
default_value?: DefaultValueForm[] 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 = { export type CommonEdgeType = {
_hovering?: boolean _hovering?: boolean

@ -71,6 +71,7 @@ export const useUpdatePluginToolCredential = (
) => { ) => {
return useMutation({ return useMutation({
mutationFn: (params: { mutationFn: (params: {
credential_id: string
credentials: Record<string, any> credentials: Record<string, any>
type: CredentialTypeEnum type: CredentialTypeEnum
name?: string name?: string

Loading…
Cancel
Save