endpoint-card.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import React, { useEffect, useMemo, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import { useBoolean } from 'ahooks'
  4. import copy from 'copy-to-clipboard'
  5. import { RiClipboardLine, RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
  6. import type { EndpointListItem } from '../types'
  7. import EndpointModal from './endpoint-modal'
  8. import { NAME_FIELD } from './utils'
  9. import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
  10. import { ClipboardCheck } from '@/app/components/base/icons/src/vender/line/files'
  11. import ActionButton from '@/app/components/base/action-button'
  12. import Confirm from '@/app/components/base/confirm'
  13. import Indicator from '@/app/components/header/indicator'
  14. import Switch from '@/app/components/base/switch'
  15. import Toast from '@/app/components/base/toast'
  16. import Tooltip from '@/app/components/base/tooltip'
  17. import {
  18. useDeleteEndpoint,
  19. useDisableEndpoint,
  20. useEnableEndpoint,
  21. useUpdateEndpoint,
  22. } from '@/service/use-endpoints'
  23. type Props = {
  24. data: EndpointListItem
  25. handleChange: () => void
  26. }
  27. const EndpointCard = ({
  28. data,
  29. handleChange,
  30. }: Props) => {
  31. const { t } = useTranslation()
  32. const [active, setActive] = useState(data.enabled)
  33. const endpointID = data.id
  34. // switch
  35. const [isShowDisableConfirm, {
  36. setTrue: showDisableConfirm,
  37. setFalse: hideDisableConfirm,
  38. }] = useBoolean(false)
  39. const { mutate: enableEndpoint } = useEnableEndpoint({
  40. onSuccess: async () => {
  41. await handleChange()
  42. },
  43. onError: () => {
  44. Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  45. setActive(false)
  46. },
  47. })
  48. const { mutate: disableEndpoint } = useDisableEndpoint({
  49. onSuccess: async () => {
  50. await handleChange()
  51. hideDisableConfirm()
  52. },
  53. onError: () => {
  54. Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  55. setActive(false)
  56. },
  57. })
  58. const handleSwitch = (state: boolean) => {
  59. if (state) {
  60. setActive(true)
  61. enableEndpoint(endpointID)
  62. }
  63. else {
  64. setActive(false)
  65. showDisableConfirm()
  66. }
  67. }
  68. // delete
  69. const [isShowDeleteConfirm, {
  70. setTrue: showDeleteConfirm,
  71. setFalse: hideDeleteConfirm,
  72. }] = useBoolean(false)
  73. const { mutate: deleteEndpoint } = useDeleteEndpoint({
  74. onSuccess: async () => {
  75. await handleChange()
  76. hideDeleteConfirm()
  77. },
  78. onError: () => {
  79. Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  80. },
  81. })
  82. // update
  83. const [isShowEndpointModal, {
  84. setTrue: showEndpointModalConfirm,
  85. setFalse: hideEndpointModalConfirm,
  86. }] = useBoolean(false)
  87. const formSchemas = useMemo(() => {
  88. return toolCredentialToFormSchemas([NAME_FIELD, ...data.declaration.settings])
  89. }, [data.declaration.settings])
  90. const formValue = useMemo(() => {
  91. const formValue = {
  92. name: data.name,
  93. ...data.settings,
  94. }
  95. return addDefaultValue(formValue, formSchemas)
  96. }, [data.name, data.settings, formSchemas])
  97. const { mutate: updateEndpoint } = useUpdateEndpoint({
  98. onSuccess: async () => {
  99. await handleChange()
  100. hideEndpointModalConfirm()
  101. },
  102. onError: () => {
  103. Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  104. },
  105. })
  106. const handleUpdate = (state: any) => updateEndpoint({
  107. endpointID,
  108. state,
  109. })
  110. const [isCopied, setIsCopied] = useState(false)
  111. const handleCopy = (value: string) => {
  112. copy(value)
  113. setIsCopied(true)
  114. }
  115. useEffect(() => {
  116. if (isCopied) {
  117. const timer = setTimeout(() => {
  118. setIsCopied(false)
  119. }, 2000)
  120. return () => {
  121. clearTimeout(timer)
  122. }
  123. }
  124. }, [isCopied])
  125. const CopyIcon = isCopied ? ClipboardCheck : RiClipboardLine
  126. return (
  127. <div className='p-0.5 bg-background-section-burn rounded-xl'>
  128. <div className='group p-2.5 pl-3 bg-components-panel-on-panel-item-bg rounded-[10px] border-[0.5px] border-components-panel-border'>
  129. <div className='flex items-center'>
  130. <div className='grow mb-1 h-6 flex items-center gap-1 text-text-secondary system-md-semibold'>
  131. <RiLoginCircleLine className='w-4 h-4' />
  132. <div>{data.name}</div>
  133. </div>
  134. <div className='hidden group-hover:flex items-center'>
  135. <ActionButton onClick={showEndpointModalConfirm}>
  136. <RiEditLine className='w-4 h-4' />
  137. </ActionButton>
  138. <ActionButton onClick={showDeleteConfirm} className='hover:bg-state-destructive-hover text-text-tertiary hover:text-text-destructive'>
  139. <RiDeleteBinLine className='w-4 h-4' />
  140. </ActionButton>
  141. </div>
  142. </div>
  143. {data.declaration.endpoints.map((endpoint, index) => (
  144. <div key={index} className='h-6 flex items-center'>
  145. <div className='shrink-0 w-12 text-text-tertiary system-xs-regular'>{endpoint.method}</div>
  146. <div className='group/item grow flex items-center text-text-secondary system-xs-regular truncate'>
  147. <div title={`${data.url}${endpoint.path}`} className='truncate'>{`${data.url}${endpoint.path}`}</div>
  148. <Tooltip popupContent={t(`common.operation.${isCopied ? 'copied' : 'copy'}`)} position='top'>
  149. <ActionButton className='hidden shrink-0 ml-2 group-hover/item:flex' onClick={() => handleCopy(`${data.url}${endpoint.path}`)}>
  150. <CopyIcon className='w-3.5 h-3.5 text-text-tertiary' />
  151. </ActionButton>
  152. </Tooltip>
  153. </div>
  154. </div>
  155. ))}
  156. </div>
  157. <div className='p-2 pl-3 flex items-center justify-between'>
  158. {active && (
  159. <div className='flex items-center gap-1 system-xs-semibold-uppercase text-util-colors-green-green-600'>
  160. <Indicator color='green' />
  161. {t('plugin.detailPanel.serviceOk')}
  162. </div>
  163. )}
  164. {!active && (
  165. <div className='flex items-center gap-1 system-xs-semibold-uppercase text-text-tertiary'>
  166. <Indicator color='gray' />
  167. {t('plugin.detailPanel.disabled')}
  168. </div>
  169. )}
  170. <Switch
  171. className='ml-3'
  172. defaultValue={active}
  173. onChange={handleSwitch}
  174. size='sm'
  175. />
  176. </div>
  177. {isShowDisableConfirm && (
  178. <Confirm
  179. isShow
  180. title={t('plugin.detailPanel.endpointDisableTip')}
  181. content={<div>{t('plugin.detailPanel.endpointDisableContent', { name: data.name })}</div>}
  182. onCancel={() => {
  183. hideDisableConfirm()
  184. setActive(true)
  185. }}
  186. onConfirm={() => disableEndpoint(endpointID)}
  187. />
  188. )}
  189. {isShowDeleteConfirm && (
  190. <Confirm
  191. isShow
  192. title={t('plugin.detailPanel.endpointDeleteTip')}
  193. content={<div>{t('plugin.detailPanel.endpointDeleteContent', { name: data.name })}</div>}
  194. onCancel={hideDeleteConfirm}
  195. onConfirm={() => deleteEndpoint(endpointID)}
  196. />
  197. )}
  198. {isShowEndpointModal && (
  199. <EndpointModal
  200. formSchemas={formSchemas}
  201. defaultValues={formValue}
  202. onCancel={hideEndpointModalConfirm}
  203. onSaved={handleUpdate}
  204. />
  205. )}
  206. </div>
  207. )
  208. }
  209. export default EndpointCard