index.tsx 6.6 KB

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