index.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import {
  2. memo,
  3. useCallback,
  4. useState,
  5. } from 'react'
  6. import { capitalize } from 'lodash-es'
  7. import {
  8. useStoreApi,
  9. } from 'reactflow'
  10. import { RiCloseLine, RiDeleteBinLine, RiEditLine, RiLock2Line } from '@remixicon/react'
  11. import { useTranslation } from 'react-i18next'
  12. import { useStore } from '@/app/components/workflow/store'
  13. import { Env } from '@/app/components/base/icons/src/vender/line/others'
  14. import VariableTrigger from '@/app/components/workflow/panel/env-panel/variable-trigger'
  15. import type {
  16. EnvironmentVariable,
  17. } from '@/app/components/workflow/types'
  18. import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
  19. import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm'
  20. import cn from '@/utils/classnames'
  21. import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
  22. const EnvPanel = () => {
  23. const { t } = useTranslation()
  24. const store = useStoreApi()
  25. const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
  26. const envList = useStore(s => s.environmentVariables) as EnvironmentVariable[]
  27. const envSecrets = useStore(s => s.envSecrets)
  28. const updateEnvList = useStore(s => s.setEnvironmentVariables)
  29. const setEnvSecrets = useStore(s => s.setEnvSecrets)
  30. const { doSyncWorkflowDraft } = useNodesSyncDraft()
  31. const [showVariableModal, setShowVariableModal] = useState(false)
  32. const [currentVar, setCurrentVar] = useState<EnvironmentVariable>()
  33. const [showRemoveVarConfirm, setShowRemoveConfirm] = useState(false)
  34. const [cacheForDelete, setCacheForDelete] = useState<EnvironmentVariable>()
  35. const formatSecret = (s: string) => {
  36. return s.length > 8 ? `${s.slice(0, 6)}************${s.slice(-2)}` : '********************'
  37. }
  38. const getEffectedNodes = useCallback((env: EnvironmentVariable) => {
  39. const { getNodes } = store.getState()
  40. const allNodes = getNodes()
  41. return findUsedVarNodes(
  42. ['env', env.name],
  43. allNodes,
  44. )
  45. }, [store])
  46. const removeUsedVarInNodes = useCallback((env: EnvironmentVariable) => {
  47. const { getNodes, setNodes } = store.getState()
  48. const effectedNodes = getEffectedNodes(env)
  49. const newNodes = getNodes().map((node) => {
  50. if (effectedNodes.find(n => n.id === node.id))
  51. return updateNodeVars(node, ['env', env.name], [])
  52. return node
  53. })
  54. setNodes(newNodes)
  55. }, [getEffectedNodes, store])
  56. const handleDelete = useCallback((env: EnvironmentVariable) => {
  57. removeUsedVarInNodes(env)
  58. updateEnvList(envList.filter(e => e.id !== env.id))
  59. setCacheForDelete(undefined)
  60. setShowRemoveConfirm(false)
  61. doSyncWorkflowDraft()
  62. if (env.value_type === 'secret') {
  63. const newMap = { ...envSecrets }
  64. delete newMap[env.id]
  65. setEnvSecrets(newMap)
  66. }
  67. }, [doSyncWorkflowDraft, envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList])
  68. const deleteCheck = useCallback((env: EnvironmentVariable) => {
  69. const effectedNodes = getEffectedNodes(env)
  70. if (effectedNodes.length > 0) {
  71. setCacheForDelete(env)
  72. setShowRemoveConfirm(true)
  73. }
  74. else {
  75. handleDelete(env)
  76. }
  77. }, [getEffectedNodes, handleDelete])
  78. const handleSave = useCallback(async (env: EnvironmentVariable) => {
  79. // add env
  80. let newEnv = env
  81. if (!currentVar) {
  82. if (env.value_type === 'secret') {
  83. setEnvSecrets({
  84. ...envSecrets,
  85. [env.id]: formatSecret(env.value),
  86. })
  87. }
  88. const newList = [env, ...envList]
  89. updateEnvList(newList)
  90. await doSyncWorkflowDraft()
  91. updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
  92. return
  93. }
  94. else if (currentVar.value_type === 'secret') {
  95. if (env.value_type === 'secret') {
  96. if (envSecrets[currentVar.id] !== env.value) {
  97. newEnv = env
  98. setEnvSecrets({
  99. ...envSecrets,
  100. [env.id]: formatSecret(env.value),
  101. })
  102. }
  103. else {
  104. newEnv = { ...env, value: '[__HIDDEN__]' }
  105. }
  106. }
  107. }
  108. else {
  109. if (env.value_type === 'secret') {
  110. newEnv = env
  111. setEnvSecrets({
  112. ...envSecrets,
  113. [env.id]: formatSecret(env.value),
  114. })
  115. }
  116. }
  117. const newList = envList.map(e => e.id === currentVar.id ? newEnv : e)
  118. updateEnvList(newList)
  119. // side effects of rename env
  120. if (currentVar.name !== env.name) {
  121. const { getNodes, setNodes } = store.getState()
  122. const effectedNodes = getEffectedNodes(currentVar)
  123. const newNodes = getNodes().map((node) => {
  124. if (effectedNodes.find(n => n.id === node.id))
  125. return updateNodeVars(node, ['env', currentVar.name], ['env', env.name])
  126. return node
  127. })
  128. setNodes(newNodes)
  129. }
  130. await doSyncWorkflowDraft()
  131. updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
  132. }, [currentVar, doSyncWorkflowDraft, envList, envSecrets, getEffectedNodes, setEnvSecrets, store, updateEnvList])
  133. return (
  134. <div
  135. className={cn(
  136. 'relative flex flex-col w-[400px] bg-components-panel-bg-alt rounded-l-2xl h-full border border-components-panel-border',
  137. )}
  138. >
  139. <div className='shrink-0 flex items-center justify-between p-4 pb-0 text-text-primary system-xl-semibold'>
  140. {t('workflow.env.envPanelTitle')}
  141. <div className='flex items-center'>
  142. <div
  143. className='flex items-center justify-center w-6 h-6 cursor-pointer'
  144. onClick={() => setShowEnvPanel(false)}
  145. >
  146. <RiCloseLine className='w-4 h-4 text-text-tertiary' />
  147. </div>
  148. </div>
  149. </div>
  150. <div className='shrink-0 py-1 px-4 system-sm-regular text-text-tertiary'>{t('workflow.env.envDescription')}</div>
  151. <div className='shrink-0 px-4 pt-2 pb-3'>
  152. <VariableTrigger
  153. open={showVariableModal}
  154. setOpen={setShowVariableModal}
  155. env={currentVar}
  156. onSave={handleSave}
  157. onClose={() => setCurrentVar(undefined)}
  158. />
  159. </div>
  160. <div className='grow px-4 rounded-b-2xl overflow-y-auto'>
  161. {envList.map(env => (
  162. <div
  163. key={env.name}
  164. className='mb-1 px-2.5 py-2 bg-components-panel-on-panel-item-bg radius-md border-[0.5px] border-components-panel-border-subtle shadow-xs'
  165. >
  166. <div className='flex items-center justify-between'>
  167. <div className='grow flex gap-1 items-center'>
  168. <Env className='w-4 h-4 text-util-colors-violet-violet-600' />
  169. <div className='text-text-primary system-sm-medium'>{env.name}</div>
  170. <div className='text-text-tertiary system-xs-medium'>{capitalize(env.value_type)}</div>
  171. {env.value_type === 'secret' && <RiLock2Line className='w-3 h-3 text-text-tertiary' />}
  172. </div>
  173. <div className='shrink-0 flex gap-1 items-center text-text-tertiary'>
  174. <div className='p-1 radius-md cursor-pointer hover:bg-state-base-hover hover:text-text-secondary'>
  175. <RiEditLine className='w-4 h-4' onClick={() => {
  176. setCurrentVar(env)
  177. setShowVariableModal(true)
  178. }}/>
  179. </div>
  180. <div className='p-1 radius-md cursor-pointer hover:bg-state-destructive-hover hover:text-text-destructive'>
  181. <RiDeleteBinLine className='w-4 h-4' onClick={() => deleteCheck(env)} />
  182. </div>
  183. </div>
  184. </div>
  185. <div className='text-text-tertiary system-xs-regular truncate'>{env.value_type === 'secret' ? envSecrets[env.id] : env.value}</div>
  186. </div>
  187. ))}
  188. </div>
  189. <RemoveEffectVarConfirm
  190. isShow={showRemoveVarConfirm}
  191. onCancel={() => setShowRemoveConfirm(false)}
  192. onConfirm={() => cacheForDelete && handleDelete(cacheForDelete)}
  193. />
  194. </div>
  195. )
  196. }
  197. export default memo(EnvPanel)