mirror of
https://github.com/langgenius/dify.git
synced 2026-02-24 18:05:11 +00:00
Compare commits
1 Commits
7b3b3dbe52
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c29af7a15f |
@@ -0,0 +1,65 @@
|
|||||||
|
import type { PluginPayload } from '../types'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Confirm from '@/app/components/base/confirm'
|
||||||
|
import ApiKeyModal from '../authorize/api-key-modal'
|
||||||
|
|
||||||
|
type AuthorizedModalsProps = {
|
||||||
|
pluginPayload: PluginPayload
|
||||||
|
// Delete confirmation
|
||||||
|
deleteCredentialId: string | null
|
||||||
|
doingAction: boolean
|
||||||
|
onDeleteConfirm: () => void
|
||||||
|
onDeleteCancel: () => void
|
||||||
|
// Edit modal
|
||||||
|
editValues: Record<string, unknown> | null
|
||||||
|
disabled?: boolean
|
||||||
|
onEditClose: () => void
|
||||||
|
onRemove: () => void
|
||||||
|
onUpdate?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for managing authorized modals (delete confirmation and edit modal)
|
||||||
|
* Extracted to reduce complexity in the main Authorized component
|
||||||
|
*/
|
||||||
|
const AuthorizedModals = ({
|
||||||
|
pluginPayload,
|
||||||
|
deleteCredentialId,
|
||||||
|
doingAction,
|
||||||
|
onDeleteConfirm,
|
||||||
|
onDeleteCancel,
|
||||||
|
editValues,
|
||||||
|
disabled,
|
||||||
|
onEditClose,
|
||||||
|
onRemove,
|
||||||
|
onUpdate,
|
||||||
|
}: AuthorizedModalsProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{deleteCredentialId && (
|
||||||
|
<Confirm
|
||||||
|
isShow
|
||||||
|
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||||
|
isDisabled={doingAction}
|
||||||
|
onCancel={onDeleteCancel}
|
||||||
|
onConfirm={onDeleteConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!!editValues && (
|
||||||
|
<ApiKeyModal
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
editValues={editValues}
|
||||||
|
onClose={onEditClose}
|
||||||
|
onRemove={onRemove}
|
||||||
|
disabled={disabled || doingAction}
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AuthorizedModals)
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import type { Credential } from '../types'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { cn } from '@/utils/classnames'
|
||||||
|
import Item from './item'
|
||||||
|
|
||||||
|
type CredentialItemHandlers = {
|
||||||
|
onDelete?: (id: string) => void
|
||||||
|
onEdit?: (id: string, values: Record<string, unknown>) => void
|
||||||
|
onSetDefault?: (id: string) => void
|
||||||
|
onRename?: (payload: { credential_id: string, name: string }) => void
|
||||||
|
onItemClick?: (id: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type CredentialSectionProps = CredentialItemHandlers & {
|
||||||
|
title: string
|
||||||
|
credentials: Credential[]
|
||||||
|
disabled?: boolean
|
||||||
|
disableRename?: boolean
|
||||||
|
disableEdit?: boolean
|
||||||
|
disableDelete?: boolean
|
||||||
|
disableSetDefault?: boolean
|
||||||
|
showSelectedIcon?: boolean
|
||||||
|
selectedCredentialId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable component for rendering a section of credentials
|
||||||
|
* Used for OAuth, API Key, and extra authorization items
|
||||||
|
*/
|
||||||
|
const CredentialSection = ({
|
||||||
|
title,
|
||||||
|
credentials,
|
||||||
|
disabled,
|
||||||
|
disableRename,
|
||||||
|
disableEdit,
|
||||||
|
disableDelete,
|
||||||
|
disableSetDefault,
|
||||||
|
showSelectedIcon,
|
||||||
|
selectedCredentialId,
|
||||||
|
onDelete,
|
||||||
|
onEdit,
|
||||||
|
onSetDefault,
|
||||||
|
onRename,
|
||||||
|
onItemClick,
|
||||||
|
}: CredentialSectionProps) => {
|
||||||
|
if (!credentials.length)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-1">
|
||||||
|
<div className={cn(
|
||||||
|
'system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary',
|
||||||
|
showSelectedIcon && 'pl-7',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
{credentials.map(credential => (
|
||||||
|
<Item
|
||||||
|
key={credential.id}
|
||||||
|
credential={credential}
|
||||||
|
disabled={disabled}
|
||||||
|
disableRename={disableRename}
|
||||||
|
disableEdit={disableEdit}
|
||||||
|
disableDelete={disableDelete}
|
||||||
|
disableSetDefault={disableSetDefault}
|
||||||
|
showSelectedIcon={showSelectedIcon}
|
||||||
|
selectedCredentialId={selectedCredentialId}
|
||||||
|
onDelete={onDelete}
|
||||||
|
onEdit={onEdit}
|
||||||
|
onSetDefault={onSetDefault}
|
||||||
|
onRename={onRename}
|
||||||
|
onItemClick={onItemClick}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(CredentialSection)
|
||||||
|
|
||||||
|
type ExtraCredentialSectionProps = {
|
||||||
|
credentials?: Credential[]
|
||||||
|
disabled?: boolean
|
||||||
|
onItemClick?: (id: string) => void
|
||||||
|
showSelectedIcon?: boolean
|
||||||
|
selectedCredentialId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized section for extra authorization items (read-only)
|
||||||
|
*/
|
||||||
|
export const ExtraCredentialSection = memo(({
|
||||||
|
credentials,
|
||||||
|
disabled,
|
||||||
|
onItemClick,
|
||||||
|
showSelectedIcon,
|
||||||
|
selectedCredentialId,
|
||||||
|
}: ExtraCredentialSectionProps) => {
|
||||||
|
if (!credentials?.length)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-1">
|
||||||
|
{credentials.map(credential => (
|
||||||
|
<Item
|
||||||
|
key={credential.id}
|
||||||
|
credential={credential}
|
||||||
|
disabled={disabled}
|
||||||
|
onItemClick={onItemClick}
|
||||||
|
disableRename
|
||||||
|
disableEdit
|
||||||
|
disableDelete
|
||||||
|
disableSetDefault
|
||||||
|
showSelectedIcon={showSelectedIcon}
|
||||||
|
selectedCredentialId={selectedCredentialId}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
ExtraCredentialSection.displayName = 'ExtraCredentialSection'
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { useCredentialActions } from './use-credential-actions'
|
||||||
|
export { useModalState } from './use-modal-state'
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { PluginPayload } from '../../types'
|
||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useToastContext } from '@/app/components/base/toast'
|
||||||
|
import {
|
||||||
|
useDeletePluginCredentialHook,
|
||||||
|
useSetPluginDefaultCredentialHook,
|
||||||
|
useUpdatePluginCredentialHook,
|
||||||
|
} from '../../hooks/use-credential'
|
||||||
|
|
||||||
|
type UseCredentialActionsOptions = {
|
||||||
|
pluginPayload: PluginPayload
|
||||||
|
onUpdate?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseCredentialActionsReturn = {
|
||||||
|
doingAction: boolean
|
||||||
|
doingActionRef: MutableRefObject<boolean>
|
||||||
|
pendingOperationCredentialIdRef: MutableRefObject<string | null>
|
||||||
|
handleSetDoingAction: (doing: boolean) => void
|
||||||
|
handleDelete: (credentialId: string) => Promise<void>
|
||||||
|
handleSetDefault: (id: string) => Promise<void>
|
||||||
|
handleRename: (payload: { credential_id: string, name: string }) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for credential CRUD operations
|
||||||
|
* Consolidates delete, setDefault, rename actions with shared loading state
|
||||||
|
*/
|
||||||
|
export const useCredentialActions = ({
|
||||||
|
pluginPayload,
|
||||||
|
onUpdate,
|
||||||
|
}: UseCredentialActionsOptions): UseCredentialActionsReturn => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { notify } = useToastContext()
|
||||||
|
|
||||||
|
const [doingAction, setDoingAction] = useState(false)
|
||||||
|
const doingActionRef = useRef(doingAction)
|
||||||
|
const pendingOperationCredentialIdRef = useRef<string | null>(null)
|
||||||
|
|
||||||
|
const handleSetDoingAction = useCallback((doing: boolean) => {
|
||||||
|
doingActionRef.current = doing
|
||||||
|
setDoingAction(doing)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { mutateAsync: deletePluginCredential } = useDeletePluginCredentialHook(pluginPayload)
|
||||||
|
const { mutateAsync: setPluginDefaultCredential } = useSetPluginDefaultCredentialHook(pluginPayload)
|
||||||
|
const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload)
|
||||||
|
|
||||||
|
const showSuccessNotification = useCallback(() => {
|
||||||
|
notify({
|
||||||
|
type: 'success',
|
||||||
|
message: t('api.actionSuccess', { ns: 'common' }),
|
||||||
|
})
|
||||||
|
}, [notify, t])
|
||||||
|
|
||||||
|
const handleDelete = useCallback(async (credentialId: string) => {
|
||||||
|
if (doingActionRef.current)
|
||||||
|
return
|
||||||
|
try {
|
||||||
|
handleSetDoingAction(true)
|
||||||
|
await deletePluginCredential({ credential_id: credentialId })
|
||||||
|
showSuccessNotification()
|
||||||
|
onUpdate?.()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
handleSetDoingAction(false)
|
||||||
|
}
|
||||||
|
}, [deletePluginCredential, onUpdate, showSuccessNotification, handleSetDoingAction])
|
||||||
|
|
||||||
|
const handleSetDefault = useCallback(async (id: string) => {
|
||||||
|
if (doingActionRef.current)
|
||||||
|
return
|
||||||
|
try {
|
||||||
|
handleSetDoingAction(true)
|
||||||
|
await setPluginDefaultCredential(id)
|
||||||
|
showSuccessNotification()
|
||||||
|
onUpdate?.()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
handleSetDoingAction(false)
|
||||||
|
}
|
||||||
|
}, [setPluginDefaultCredential, onUpdate, showSuccessNotification, handleSetDoingAction])
|
||||||
|
|
||||||
|
const handleRename = useCallback(async (payload: {
|
||||||
|
credential_id: string
|
||||||
|
name: string
|
||||||
|
}) => {
|
||||||
|
if (doingActionRef.current)
|
||||||
|
return
|
||||||
|
try {
|
||||||
|
handleSetDoingAction(true)
|
||||||
|
await updatePluginCredential(payload)
|
||||||
|
showSuccessNotification()
|
||||||
|
onUpdate?.()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
handleSetDoingAction(false)
|
||||||
|
}
|
||||||
|
}, [updatePluginCredential, showSuccessNotification, handleSetDoingAction, onUpdate])
|
||||||
|
|
||||||
|
return {
|
||||||
|
doingAction,
|
||||||
|
doingActionRef,
|
||||||
|
pendingOperationCredentialIdRef,
|
||||||
|
handleSetDoingAction,
|
||||||
|
handleDelete,
|
||||||
|
handleSetDefault,
|
||||||
|
handleRename,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
|
type CredentialValues = Record<string, unknown>
|
||||||
|
|
||||||
|
type UseModalStateOptions = {
|
||||||
|
pendingOperationCredentialIdRef: MutableRefObject<string | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseModalStateReturn = {
|
||||||
|
// Delete modal state
|
||||||
|
deleteCredentialId: string | null
|
||||||
|
openDeleteConfirm: (credentialId?: string) => void
|
||||||
|
closeDeleteConfirm: () => void
|
||||||
|
// Edit modal state
|
||||||
|
editValues: CredentialValues | null
|
||||||
|
openEditModal: (id: string, values: CredentialValues) => void
|
||||||
|
closeEditModal: () => void
|
||||||
|
// Remove action (used from edit modal)
|
||||||
|
handleRemoveFromEdit: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for managing modal states
|
||||||
|
* Handles delete confirmation and edit modal with shared pending credential tracking
|
||||||
|
*/
|
||||||
|
export const useModalState = ({
|
||||||
|
pendingOperationCredentialIdRef,
|
||||||
|
}: UseModalStateOptions): UseModalStateReturn => {
|
||||||
|
const [deleteCredentialId, setDeleteCredentialId] = useState<string | null>(null)
|
||||||
|
const [editValues, setEditValues] = useState<CredentialValues | null>(null)
|
||||||
|
|
||||||
|
const openDeleteConfirm = useCallback((credentialId?: string) => {
|
||||||
|
if (credentialId)
|
||||||
|
pendingOperationCredentialIdRef.current = credentialId
|
||||||
|
|
||||||
|
setDeleteCredentialId(pendingOperationCredentialIdRef.current)
|
||||||
|
}, [pendingOperationCredentialIdRef])
|
||||||
|
|
||||||
|
const closeDeleteConfirm = useCallback(() => {
|
||||||
|
setDeleteCredentialId(null)
|
||||||
|
pendingOperationCredentialIdRef.current = null
|
||||||
|
}, [pendingOperationCredentialIdRef])
|
||||||
|
|
||||||
|
const openEditModal = useCallback((id: string, values: CredentialValues) => {
|
||||||
|
pendingOperationCredentialIdRef.current = id
|
||||||
|
setEditValues(values)
|
||||||
|
}, [pendingOperationCredentialIdRef])
|
||||||
|
|
||||||
|
const closeEditModal = useCallback(() => {
|
||||||
|
setEditValues(null)
|
||||||
|
pendingOperationCredentialIdRef.current = null
|
||||||
|
}, [pendingOperationCredentialIdRef])
|
||||||
|
|
||||||
|
const handleRemoveFromEdit = useCallback(() => {
|
||||||
|
setDeleteCredentialId(pendingOperationCredentialIdRef.current)
|
||||||
|
}, [pendingOperationCredentialIdRef])
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleteCredentialId,
|
||||||
|
openDeleteConfirm,
|
||||||
|
closeDeleteConfirm,
|
||||||
|
editValues,
|
||||||
|
openEditModal,
|
||||||
|
closeEditModal,
|
||||||
|
handleRemoveFromEdit,
|
||||||
|
}
|
||||||
|
}
|
||||||
1496
web/app/components/plugins/plugin-auth/authorized/index.spec.tsx
Normal file
1496
web/app/components/plugins/plugin-auth/authorized/index.spec.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,29 +8,23 @@ import {
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useRef,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
PortalToFollowElemTrigger,
|
PortalToFollowElemTrigger,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
import { useToastContext } from '@/app/components/base/toast'
|
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
import Authorize from '../authorize'
|
import Authorize from '../authorize'
|
||||||
import ApiKeyModal from '../authorize/api-key-modal'
|
|
||||||
import {
|
|
||||||
useDeletePluginCredentialHook,
|
|
||||||
useSetPluginDefaultCredentialHook,
|
|
||||||
useUpdatePluginCredentialHook,
|
|
||||||
} from '../hooks/use-credential'
|
|
||||||
import { CredentialTypeEnum } from '../types'
|
import { CredentialTypeEnum } from '../types'
|
||||||
import Item from './item'
|
import AuthorizedModals from './authorized-modals'
|
||||||
|
import CredentialSection, { ExtraCredentialSection } from './credential-section'
|
||||||
|
import { useCredentialActions, useModalState } from './hooks'
|
||||||
|
|
||||||
type AuthorizedProps = {
|
type AuthorizedProps = {
|
||||||
pluginPayload: PluginPayload
|
pluginPayload: PluginPayload
|
||||||
@@ -53,6 +47,7 @@ type AuthorizedProps = {
|
|||||||
onUpdate?: () => void
|
onUpdate?: () => void
|
||||||
notAllowCustomCredential?: boolean
|
notAllowCustomCredential?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Authorized = ({
|
const Authorized = ({
|
||||||
pluginPayload,
|
pluginPayload,
|
||||||
credentials,
|
credentials,
|
||||||
@@ -75,105 +70,55 @@ const Authorized = ({
|
|||||||
notAllowCustomCredential,
|
notAllowCustomCredential,
|
||||||
}: AuthorizedProps) => {
|
}: AuthorizedProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useToastContext()
|
|
||||||
|
// Dropdown open state
|
||||||
const [isLocalOpen, setIsLocalOpen] = useState(false)
|
const [isLocalOpen, setIsLocalOpen] = useState(false)
|
||||||
const mergedIsOpen = isOpen ?? isLocalOpen
|
const mergedIsOpen = isOpen ?? isLocalOpen
|
||||||
const setMergedIsOpen = useCallback((open: boolean) => {
|
const setMergedIsOpen = useCallback((open: boolean) => {
|
||||||
if (onOpenChange)
|
onOpenChange?.(open)
|
||||||
onOpenChange(open)
|
|
||||||
|
|
||||||
setIsLocalOpen(open)
|
setIsLocalOpen(open)
|
||||||
}, [onOpenChange])
|
}, [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)
|
|
||||||
const [deleteCredentialId, setDeleteCredentialId] = useState<string | null>(null)
|
|
||||||
const { mutateAsync: deletePluginCredential } = useDeletePluginCredentialHook(pluginPayload)
|
|
||||||
const openConfirm = useCallback((credentialId?: string) => {
|
|
||||||
if (credentialId)
|
|
||||||
pendingOperationCredentialId.current = credentialId
|
|
||||||
|
|
||||||
setDeleteCredentialId(pendingOperationCredentialId.current)
|
// Credential actions hook
|
||||||
}, [])
|
const {
|
||||||
const closeConfirm = useCallback(() => {
|
doingAction,
|
||||||
setDeleteCredentialId(null)
|
doingActionRef,
|
||||||
pendingOperationCredentialId.current = null
|
pendingOperationCredentialIdRef,
|
||||||
}, [])
|
handleSetDefault,
|
||||||
const [doingAction, setDoingAction] = useState(false)
|
handleRename,
|
||||||
const doingActionRef = useRef(doingAction)
|
handleDelete,
|
||||||
const handleSetDoingAction = useCallback((doing: boolean) => {
|
} = useCredentialActions({ pluginPayload, onUpdate })
|
||||||
doingActionRef.current = doing
|
|
||||||
setDoingAction(doing)
|
// Modal state management hook
|
||||||
}, [])
|
const {
|
||||||
const handleConfirm = useCallback(async () => {
|
deleteCredentialId,
|
||||||
if (doingActionRef.current)
|
openDeleteConfirm,
|
||||||
|
closeDeleteConfirm,
|
||||||
|
editValues,
|
||||||
|
openEditModal,
|
||||||
|
closeEditModal,
|
||||||
|
handleRemoveFromEdit,
|
||||||
|
} = useModalState({ pendingOperationCredentialIdRef })
|
||||||
|
|
||||||
|
// Handle delete confirmation
|
||||||
|
const handleDeleteConfirm = useCallback(async () => {
|
||||||
|
if (doingActionRef.current || !pendingOperationCredentialIdRef.current)
|
||||||
return
|
return
|
||||||
if (!pendingOperationCredentialId.current) {
|
await handleDelete(pendingOperationCredentialIdRef.current)
|
||||||
setDeleteCredentialId(null)
|
closeDeleteConfirm()
|
||||||
return
|
}, [doingActionRef, pendingOperationCredentialIdRef, handleDelete, closeDeleteConfirm])
|
||||||
}
|
|
||||||
try {
|
// Filter credentials by type
|
||||||
handleSetDoingAction(true)
|
const { oAuthCredentials, apiKeyCredentials } = useMemo(() => ({
|
||||||
await deletePluginCredential({ credential_id: pendingOperationCredentialId.current })
|
oAuthCredentials: credentials.filter(c => c.credential_type === CredentialTypeEnum.OAUTH2),
|
||||||
notify({
|
apiKeyCredentials: credentials.filter(c => c.credential_type === CredentialTypeEnum.API_KEY),
|
||||||
type: 'success',
|
}), [credentials])
|
||||||
message: t('api.actionSuccess', { ns: 'common' }),
|
|
||||||
})
|
// Unavailable credentials info
|
||||||
onUpdate?.()
|
const { unavailableCredentials, hasUnavailableDefault } = useMemo(() => ({
|
||||||
setDeleteCredentialId(null)
|
unavailableCredentials: credentials.filter(c => c.not_allowed_to_use),
|
||||||
pendingOperationCredentialId.current = null
|
hasUnavailableDefault: credentials.some(c => c.not_allowed_to_use && c.is_default),
|
||||||
}
|
}), [credentials])
|
||||||
finally {
|
|
||||||
handleSetDoingAction(false)
|
|
||||||
}
|
|
||||||
}, [deletePluginCredential, onUpdate, notify, t, handleSetDoingAction])
|
|
||||||
const [editValues, setEditValues] = useState<Record<string, any> | null>(null)
|
|
||||||
const handleEdit = useCallback((id: string, values: Record<string, any>) => {
|
|
||||||
pendingOperationCredentialId.current = id
|
|
||||||
setEditValues(values)
|
|
||||||
}, [])
|
|
||||||
const handleRemove = useCallback(() => {
|
|
||||||
setDeleteCredentialId(pendingOperationCredentialId.current)
|
|
||||||
}, [])
|
|
||||||
const { mutateAsync: setPluginDefaultCredential } = useSetPluginDefaultCredentialHook(pluginPayload)
|
|
||||||
const handleSetDefault = useCallback(async (id: string) => {
|
|
||||||
if (doingActionRef.current)
|
|
||||||
return
|
|
||||||
try {
|
|
||||||
handleSetDoingAction(true)
|
|
||||||
await setPluginDefaultCredential(id)
|
|
||||||
notify({
|
|
||||||
type: 'success',
|
|
||||||
message: t('api.actionSuccess', { ns: 'common' }),
|
|
||||||
})
|
|
||||||
onUpdate?.()
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
handleSetDoingAction(false)
|
|
||||||
}
|
|
||||||
}, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction])
|
|
||||||
const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload)
|
|
||||||
const handleRename = useCallback(async (payload: {
|
|
||||||
credential_id: string
|
|
||||||
name: string
|
|
||||||
}) => {
|
|
||||||
if (doingActionRef.current)
|
|
||||||
return
|
|
||||||
try {
|
|
||||||
handleSetDoingAction(true)
|
|
||||||
await updatePluginCredential(payload)
|
|
||||||
notify({
|
|
||||||
type: 'success',
|
|
||||||
message: t('api.actionSuccess', { ns: 'common' }),
|
|
||||||
})
|
|
||||||
onUpdate?.()
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
handleSetDoingAction(false)
|
|
||||||
}
|
|
||||||
}, [updatePluginCredential, notify, t, handleSetDoingAction, onUpdate])
|
|
||||||
const unavailableCredentials = credentials.filter(credential => credential.not_allowed_to_use)
|
|
||||||
const unavailableCredential = credentials.find(credential => credential.not_allowed_to_use && credential.is_default)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -188,33 +133,27 @@ const Authorized = ({
|
|||||||
onClick={() => setMergedIsOpen(!mergedIsOpen)}
|
onClick={() => setMergedIsOpen(!mergedIsOpen)}
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
{
|
{renderTrigger
|
||||||
renderTrigger
|
? renderTrigger(mergedIsOpen)
|
||||||
? renderTrigger(mergedIsOpen)
|
: (
|
||||||
: (
|
<Button
|
||||||
<Button
|
className={cn(
|
||||||
className={cn(
|
'w-full',
|
||||||
'w-full',
|
isOpen && 'bg-components-button-secondary-bg-hover',
|
||||||
isOpen && 'bg-components-button-secondary-bg-hover',
|
)}
|
||||||
)}
|
>
|
||||||
>
|
<Indicator className="mr-2" color={hasUnavailableDefault ? 'gray' : 'green'} />
|
||||||
<Indicator className="mr-2" color={unavailableCredential ? 'gray' : 'green'} />
|
{credentials.length}
|
||||||
{credentials.length}
|
|
||||||
|
{credentials.length > 1
|
||||||
{
|
? t('auth.authorizations', { ns: 'plugin' })
|
||||||
credentials.length > 1
|
: t('auth.authorization', { ns: 'plugin' })}
|
||||||
? t('auth.authorizations', { ns: 'plugin' })
|
{!!unavailableCredentials.length && (
|
||||||
: t('auth.authorization', { ns: 'plugin' })
|
` (${unavailableCredentials.length} ${t('auth.unavailable', { ns: 'plugin' })})`
|
||||||
}
|
)}
|
||||||
{
|
<RiArrowDownSLine className="ml-0.5 h-4 w-4" />
|
||||||
!!unavailableCredentials.length && (
|
</Button>
|
||||||
` (${unavailableCredentials.length} ${t('auth.unavailable', { ns: 'plugin' })})`
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
<RiArrowDownSLine className="ml-0.5 h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className="z-[100]">
|
<PortalToFollowElemContent className="z-[100]">
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
@@ -223,137 +162,72 @@ const Authorized = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
{
|
<ExtraCredentialSection
|
||||||
!!extraAuthorizationItems?.length && (
|
credentials={extraAuthorizationItems}
|
||||||
<div className="p-1">
|
disabled={disabled}
|
||||||
{
|
onItemClick={onItemClick}
|
||||||
extraAuthorizationItems.map(credential => (
|
showSelectedIcon={showItemSelectedIcon}
|
||||||
<Item
|
selectedCredentialId={selectedCredentialId}
|
||||||
key={credential.id}
|
/>
|
||||||
credential={credential}
|
<CredentialSection
|
||||||
disabled={disabled}
|
title="OAuth"
|
||||||
onItemClick={onItemClick}
|
credentials={oAuthCredentials}
|
||||||
disableRename
|
disabled={disabled}
|
||||||
disableEdit
|
disableEdit
|
||||||
disableDelete
|
disableSetDefault={disableSetDefault}
|
||||||
disableSetDefault
|
showSelectedIcon={showItemSelectedIcon}
|
||||||
showSelectedIcon={showItemSelectedIcon}
|
selectedCredentialId={selectedCredentialId}
|
||||||
selectedCredentialId={selectedCredentialId}
|
onDelete={openDeleteConfirm}
|
||||||
/>
|
onSetDefault={handleSetDefault}
|
||||||
))
|
onRename={handleRename}
|
||||||
}
|
onItemClick={onItemClick}
|
||||||
</div>
|
/>
|
||||||
)
|
<CredentialSection
|
||||||
}
|
title="API Keys"
|
||||||
{
|
credentials={apiKeyCredentials}
|
||||||
!!oAuthCredentials.length && (
|
disabled={disabled}
|
||||||
<div className="p-1">
|
disableRename
|
||||||
<div className={cn(
|
disableSetDefault={disableSetDefault}
|
||||||
'system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary',
|
showSelectedIcon={showItemSelectedIcon}
|
||||||
showItemSelectedIcon && 'pl-7',
|
selectedCredentialId={selectedCredentialId}
|
||||||
)}
|
onDelete={openDeleteConfirm}
|
||||||
>
|
onEdit={openEditModal}
|
||||||
OAuth
|
onSetDefault={handleSetDefault}
|
||||||
</div>
|
onRename={handleRename}
|
||||||
{
|
onItemClick={onItemClick}
|
||||||
oAuthCredentials.map(credential => (
|
/>
|
||||||
<Item
|
|
||||||
key={credential.id}
|
|
||||||
credential={credential}
|
|
||||||
disabled={disabled}
|
|
||||||
disableEdit
|
|
||||||
onDelete={openConfirm}
|
|
||||||
onSetDefault={handleSetDefault}
|
|
||||||
onRename={handleRename}
|
|
||||||
disableSetDefault={disableSetDefault}
|
|
||||||
onItemClick={onItemClick}
|
|
||||||
showSelectedIcon={showItemSelectedIcon}
|
|
||||||
selectedCredentialId={selectedCredentialId}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!!apiKeyCredentials.length && (
|
|
||||||
<div className="p-1">
|
|
||||||
<div className={cn(
|
|
||||||
'system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary',
|
|
||||||
showItemSelectedIcon && 'pl-7',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
API Keys
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
apiKeyCredentials.map(credential => (
|
|
||||||
<Item
|
|
||||||
key={credential.id}
|
|
||||||
credential={credential}
|
|
||||||
disabled={disabled}
|
|
||||||
onDelete={openConfirm}
|
|
||||||
onEdit={handleEdit}
|
|
||||||
onSetDefault={handleSetDefault}
|
|
||||||
disableSetDefault={disableSetDefault}
|
|
||||||
disableRename
|
|
||||||
onItemClick={onItemClick}
|
|
||||||
onRename={handleRename}
|
|
||||||
showSelectedIcon={showItemSelectedIcon}
|
|
||||||
selectedCredentialId={selectedCredentialId}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
{
|
{!notAllowCustomCredential && (
|
||||||
!notAllowCustomCredential && (
|
<>
|
||||||
<>
|
<div className="h-[1px] bg-divider-subtle"></div>
|
||||||
<div className="h-[1px] bg-divider-subtle"></div>
|
<div className="p-2">
|
||||||
<div className="p-2">
|
<Authorize
|
||||||
<Authorize
|
pluginPayload={pluginPayload}
|
||||||
pluginPayload={pluginPayload}
|
theme="secondary"
|
||||||
theme="secondary"
|
showDivider={false}
|
||||||
showDivider={false}
|
canOAuth={canOAuth}
|
||||||
canOAuth={canOAuth}
|
canApiKey={canApiKey}
|
||||||
canApiKey={canApiKey}
|
disabled={disabled}
|
||||||
disabled={disabled}
|
onUpdate={onUpdate}
|
||||||
onUpdate={onUpdate}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
{
|
<AuthorizedModals
|
||||||
deleteCredentialId && (
|
pluginPayload={pluginPayload}
|
||||||
<Confirm
|
deleteCredentialId={deleteCredentialId}
|
||||||
isShow
|
doingAction={doingAction}
|
||||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
onDeleteConfirm={handleDeleteConfirm}
|
||||||
isDisabled={doingAction}
|
onDeleteCancel={closeDeleteConfirm}
|
||||||
onCancel={closeConfirm}
|
editValues={editValues}
|
||||||
onConfirm={handleConfirm}
|
disabled={disabled}
|
||||||
/>
|
onEditClose={closeEditModal}
|
||||||
)
|
onRemove={handleRemoveFromEdit}
|
||||||
}
|
onUpdate={onUpdate}
|
||||||
{
|
/>
|
||||||
!!editValues && (
|
|
||||||
<ApiKeyModal
|
|
||||||
pluginPayload={pluginPayload}
|
|
||||||
editValues={editValues}
|
|
||||||
onClose={() => {
|
|
||||||
setEditValues(null)
|
|
||||||
pendingOperationCredentialId.current = null
|
|
||||||
}}
|
|
||||||
onRemove={handleRemove}
|
|
||||||
disabled={disabled || doingAction}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user