mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 03:36:36 +08:00
feat: tool oauth
This commit is contained in:
parent
31eb8548ef
commit
c53d5c105b
118
web/app/components/base/modal/modal.tsx
Normal file
118
web/app/components/base/modal/modal.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import type { ButtonProps } from '@/app/components/base/button'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type ModalProps = {
|
||||||
|
onClose?: () => void
|
||||||
|
size?: 'sm' | 'md'
|
||||||
|
title: string
|
||||||
|
subTitle?: string
|
||||||
|
children?: React.ReactNode
|
||||||
|
confirmButtonText?: string
|
||||||
|
onConfirm?: () => void
|
||||||
|
cancelButtonText?: string
|
||||||
|
onCancel?: () => void
|
||||||
|
showExtraButton?: boolean
|
||||||
|
extraButtonText?: string
|
||||||
|
extraButtonVariant?: ButtonProps['variant']
|
||||||
|
onExtraButtonClick?: () => void
|
||||||
|
footerSlot?: React.ReactNode
|
||||||
|
bottomSlot?: React.ReactNode
|
||||||
|
}
|
||||||
|
const Modal = ({
|
||||||
|
onClose,
|
||||||
|
size = 'sm',
|
||||||
|
title,
|
||||||
|
subTitle,
|
||||||
|
children,
|
||||||
|
confirmButtonText,
|
||||||
|
onConfirm,
|
||||||
|
cancelButtonText,
|
||||||
|
onCancel,
|
||||||
|
showExtraButton,
|
||||||
|
extraButtonVariant = 'warning',
|
||||||
|
extraButtonText,
|
||||||
|
onExtraButtonClick,
|
||||||
|
footerSlot,
|
||||||
|
bottomSlot,
|
||||||
|
}: ModalProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem open>
|
||||||
|
<PortalToFollowElemContent
|
||||||
|
className='z-[999999] flex h-full w-full items-center justify-center bg-background-overlay'
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'max-h-[80%] w-[480px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xs',
|
||||||
|
size === 'sm' && 'w-[480px',
|
||||||
|
size === 'md' && 'w-[640px]',
|
||||||
|
)}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className='title-2xl-semi-bold relative p-6 pb-3 pr-14 text-text-primary'>
|
||||||
|
{title}
|
||||||
|
{
|
||||||
|
subTitle && (
|
||||||
|
<div className='system-xs-regular mt-1 text-text-tertiary'>
|
||||||
|
{subTitle}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
className='absolute right-5 top-5 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg'
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<RiCloseLine className='h-5 w-5 text-text-tertiary' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
children && (
|
||||||
|
<div className='px-6 py-3'>{children}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div className='flex items-center justify-end p-6 pt-5'>
|
||||||
|
{footerSlot}
|
||||||
|
{
|
||||||
|
showExtraButton && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant={extraButtonVariant}
|
||||||
|
onClick={onExtraButtonClick}
|
||||||
|
>
|
||||||
|
{extraButtonText || t('common.operation.remove')}
|
||||||
|
</Button>
|
||||||
|
<div className='mx-3 h-4 w-[1px] bg-divider-regular'></div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Button
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
{cancelButtonText || t('common.operation.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='ml-2'
|
||||||
|
variant='primary'
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
{confirmButtonText || t('common.operation.save')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{bottomSlot}
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Modal)
|
||||||
40
web/app/components/tools/tool-auth/add-api-key-button.tsx
Normal file
40
web/app/components/tools/tool-auth/add-api-key-button.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import type { ButtonProps } from '@/app/components/base/button'
|
||||||
|
import ApiKeyModal from './api-key-modal'
|
||||||
|
|
||||||
|
type AddApiKeyButtonProps = {
|
||||||
|
buttonVariant?: ButtonProps['variant']
|
||||||
|
buttonText?: string
|
||||||
|
}
|
||||||
|
const AddApiKeyButton = ({
|
||||||
|
buttonVariant = 'secondary-accent',
|
||||||
|
buttonText = 'use api key',
|
||||||
|
}: AddApiKeyButtonProps) => {
|
||||||
|
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
className='grow'
|
||||||
|
variant={buttonVariant}
|
||||||
|
onClick={() => setIsApiKeyModalOpen(true)}
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
{
|
||||||
|
isApiKeyModalOpen && (
|
||||||
|
<ApiKeyModal
|
||||||
|
onClose={() => setIsApiKeyModalOpen(false)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AddApiKeyButton)
|
||||||
69
web/app/components/tools/tool-auth/add-oauth-button.tsx
Normal file
69
web/app/components/tools/tool-auth/add-oauth-button.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { RiEqualizer2Line } from '@remixicon/react'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import type { ButtonProps } from '@/app/components/base/button'
|
||||||
|
import OAuthClientSettings from './oauth-client-settings'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type AddOAuthButtonProps = {
|
||||||
|
buttonVariant?: ButtonProps['variant']
|
||||||
|
buttonText?: string
|
||||||
|
className?: string
|
||||||
|
buttonLeftClassName?: string
|
||||||
|
buttonRightClassName?: string
|
||||||
|
dividerClassName?: string
|
||||||
|
}
|
||||||
|
const AddOAuthButton = ({
|
||||||
|
buttonVariant = 'primary',
|
||||||
|
buttonText = 'use oauth',
|
||||||
|
className,
|
||||||
|
buttonLeftClassName,
|
||||||
|
buttonRightClassName,
|
||||||
|
dividerClassName,
|
||||||
|
}: AddOAuthButtonProps) => {
|
||||||
|
const [isOAuthSettingsOpen, setIsOAuthSettingsOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant={buttonVariant}
|
||||||
|
className={cn(
|
||||||
|
'grow px-0 py-0 hover:bg-components-button-primary-bg',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={cn(
|
||||||
|
'flex h-full grow items-center justify-center rounded-l-lg hover:bg-components-button-primary-bg-hover',
|
||||||
|
buttonLeftClassName,
|
||||||
|
)}>
|
||||||
|
{buttonText}
|
||||||
|
</div>
|
||||||
|
<div className={cn(
|
||||||
|
'h-4 w-[1px] bg-text-primary-on-surface opacity-[0.15]',
|
||||||
|
dividerClassName,
|
||||||
|
)}></div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex h-full w-8 shrink-0 items-center justify-center rounded-r-lg hover:bg-components-button-primary-bg-hover',
|
||||||
|
buttonRightClassName,
|
||||||
|
)}
|
||||||
|
onClick={() => setIsOAuthSettingsOpen(true)}
|
||||||
|
>
|
||||||
|
<RiEqualizer2Line className='h-4 w-4' />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
{
|
||||||
|
isOAuthSettingsOpen && (
|
||||||
|
<OAuthClientSettings
|
||||||
|
onClose={() => setIsOAuthSettingsOpen(false)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AddOAuthButton)
|
||||||
52
web/app/components/tools/tool-auth/api-key-modal.tsx
Normal file
52
web/app/components/tools/tool-auth/api-key-modal.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RiExternalLinkLine } from '@remixicon/react'
|
||||||
|
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
||||||
|
import Modal from '@/app/components/base/modal/modal'
|
||||||
|
|
||||||
|
export type ApiKeyModalProps = {
|
||||||
|
onClose?: () => void
|
||||||
|
}
|
||||||
|
const ApiKeyModal = ({
|
||||||
|
onClose,
|
||||||
|
}: ApiKeyModalProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size='md'
|
||||||
|
title='API Key Authorization Configuration'
|
||||||
|
subTitle='After configuring credentials, all members within the workspace can use this tool when orchestrating applications.'
|
||||||
|
onClose={onClose}
|
||||||
|
onCancel={onClose}
|
||||||
|
footerSlot={
|
||||||
|
<a
|
||||||
|
className='system-xs-regular flex h-8 grow items-center text-text-accent'
|
||||||
|
href=''
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
Get your API Key from OpenAI
|
||||||
|
<RiExternalLinkLine className='ml-1 h-3 w-3' />
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
bottomSlot={
|
||||||
|
<div className='flex items-center justify-center bg-background-section-burn py-3 text-xs text-text-tertiary'>
|
||||||
|
<Lock01 className='mr-1 h-3 w-3 text-text-tertiary' />
|
||||||
|
{t('common.modelProvider.encrypted.front')}
|
||||||
|
<a
|
||||||
|
className='mx-1 text-text-accent'
|
||||||
|
target='_blank' rel='noopener noreferrer'
|
||||||
|
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
|
||||||
|
>
|
||||||
|
PKCS1_OAEP
|
||||||
|
</a>
|
||||||
|
{t('common.modelProvider.encrypted.back')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>oauth</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ApiKeyModal)
|
||||||
124
web/app/components/tools/tool-auth/authorization.tsx
Normal file
124
web/app/components/tools/tool-auth/authorization.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import {
|
||||||
|
RiArrowDownSLine,
|
||||||
|
RiDeleteBinLine,
|
||||||
|
RiEditLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
PortalToFollowElemTrigger,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import Indicator from '@/app/components/header/indicator'
|
||||||
|
import Badge from '@/app/components/base/badge'
|
||||||
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import AddOauthButton from './add-oauth-button'
|
||||||
|
import AddApiKeyButton from './add-api-key-button'
|
||||||
|
|
||||||
|
const Authorization = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
placement='bottom-start'
|
||||||
|
offset={8}
|
||||||
|
triggerPopupSameWidth
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={cn(
|
||||||
|
'w-full',
|
||||||
|
isOpen && 'bg-components-button-secondary-bg-hover',
|
||||||
|
)}>
|
||||||
|
<Indicator className='mr-2' />
|
||||||
|
4 Authorizations
|
||||||
|
<RiArrowDownSLine className='ml-0.5 h-4 w-4' />
|
||||||
|
</Button>
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[11]'>
|
||||||
|
<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='py-1'>
|
||||||
|
<div className='p-1'>
|
||||||
|
<div className='system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary'>
|
||||||
|
OAuth
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center rounded-lg p-1 hover:bg-state-base-hover'>
|
||||||
|
<div className='flex grow items-center space-x-1.5 pl-2'>
|
||||||
|
<Indicator className='mr-1.5' />
|
||||||
|
<div
|
||||||
|
className='system-md-regular truncate text-text-secondary'
|
||||||
|
title='Auth 1'
|
||||||
|
>
|
||||||
|
Auth 1
|
||||||
|
</div>
|
||||||
|
<Badge>
|
||||||
|
Default
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className='ml-2 flex shrink-0 items-center'>
|
||||||
|
<ActionButton>
|
||||||
|
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton>
|
||||||
|
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='p-1'>
|
||||||
|
<div className='system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary'>
|
||||||
|
API Keys
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center rounded-lg p-1 hover:bg-state-base-hover'>
|
||||||
|
<div className='flex grow items-center space-x-1.5 pl-2'>
|
||||||
|
<Indicator className='mr-1.5' />
|
||||||
|
<div
|
||||||
|
className='system-md-regular truncate text-text-secondary'
|
||||||
|
title='Production'
|
||||||
|
>
|
||||||
|
Production
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='ml-2 flex shrink-0 items-center space-x-1'>
|
||||||
|
<Badge>
|
||||||
|
0.2
|
||||||
|
</Badge>
|
||||||
|
<Badge>
|
||||||
|
ENTERPRISE
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-[1px] bg-divider-subtle'></div>
|
||||||
|
<div className='flex items-center space-x-1.5 p-2'>
|
||||||
|
<AddOauthButton
|
||||||
|
buttonVariant='secondary'
|
||||||
|
buttonText='Add OAuth'
|
||||||
|
className='hover:bg-components-button-secondary-bg'
|
||||||
|
buttonLeftClassName='hover:bg-components-button-secondary-bg-hover'
|
||||||
|
buttonRightClassName='hover:bg-components-button-secondary-bg-hover'
|
||||||
|
dividerClassName='bg-divider-regular opacity-100'
|
||||||
|
/>
|
||||||
|
<AddApiKeyButton
|
||||||
|
buttonVariant='secondary'
|
||||||
|
buttonText='Add API Key'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Authorization)
|
||||||
23
web/app/components/tools/tool-auth/index.tsx
Normal file
23
web/app/components/tools/tool-auth/index.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
} from 'react'
|
||||||
|
import AddOAuthButton from './add-oauth-button'
|
||||||
|
import AddApiKeyButton from './add-api-key-button'
|
||||||
|
|
||||||
|
const ToolAuth = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='flex items-center space-x-1.5'>
|
||||||
|
<AddOAuthButton />
|
||||||
|
<div className='system-2xs-medium-uppercase flex shrink-0 flex-col items-center justify-between text-text-tertiary'>
|
||||||
|
<div className='h-2 w-[1px] bg-divider-subtle'></div>
|
||||||
|
or
|
||||||
|
<div className='h-2 w-[1px] bg-divider-subtle'></div>
|
||||||
|
</div>
|
||||||
|
<AddApiKeyButton />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ToolAuth)
|
||||||
26
web/app/components/tools/tool-auth/oauth-client-settings.tsx
Normal file
26
web/app/components/tools/tool-auth/oauth-client-settings.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import Modal from '@/app/components/base/modal/modal'
|
||||||
|
|
||||||
|
type OAuthClientSettingsProps = {
|
||||||
|
onClose?: () => void
|
||||||
|
}
|
||||||
|
const OAuthClientSettings = ({
|
||||||
|
onClose,
|
||||||
|
}: OAuthClientSettingsProps) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title='Oauth client settings'
|
||||||
|
confirmButtonText='Save & Authorize'
|
||||||
|
cancelButtonText='Save only'
|
||||||
|
extraButtonText='Cancel'
|
||||||
|
showExtraButton
|
||||||
|
extraButtonVariant='secondary'
|
||||||
|
onExtraButtonClick={onClose}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<div>oauth</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(OAuthClientSettings)
|
||||||
@ -14,6 +14,8 @@ 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 ToolAuth from '@/app/components/tools/tool-auth'
|
||||||
|
import Authorization from '@/app/components/tools/tool-auth/authorization'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.tool'
|
const i18nPrefix = 'workflow.nodes.tool'
|
||||||
|
|
||||||
@ -53,6 +55,10 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='pt-2'>
|
<div className='pt-2'>
|
||||||
|
<div className='px-4 py-2'>
|
||||||
|
<ToolAuth />
|
||||||
|
<Authorization />
|
||||||
|
</div>
|
||||||
{!readOnly && isShowAuthBtn && (
|
{!readOnly && isShowAuthBtn && (
|
||||||
<>
|
<>
|
||||||
<div className='px-4'>
|
<div className='px-4'>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user