mirror of
https://github.com/langgenius/dify.git
synced 2026-07-24 04:58:32 +08:00
522 lines
16 KiB
TypeScript
522 lines
16 KiB
TypeScript
'use client'
|
|
|
|
import type {
|
|
Environment,
|
|
EnvironmentDeployment,
|
|
EnvVarInput,
|
|
Release,
|
|
} from '@dify/contracts/enterprise/types.gen'
|
|
import type { Getter } from 'jotai'
|
|
import type {
|
|
EnvVarBindingSlot,
|
|
EnvVarValues,
|
|
EnvVarValueSelection,
|
|
} from '../../shared/components/env-var-bindings'
|
|
import type { RuntimeCredentialBindingSelections } from '../../shared/components/runtime-credential-bindings-utils'
|
|
import { EnvVarValueSource as ApiEnvVarValueSource } from '@dify/contracts/enterprise/types.gen'
|
|
import { toast } from '@langgenius/dify-ui/toast'
|
|
import { skipToken } from '@tanstack/react-query'
|
|
import { atom } from 'jotai'
|
|
import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query'
|
|
import { selectAtom } from 'jotai/utils'
|
|
import { consoleQuery } from '@/service/client'
|
|
import { envVarBindingSlotFromContract } from '../../shared/components/env-var-bindings-utils'
|
|
import {
|
|
hasMissingRequiredRuntimeCredentialBinding,
|
|
runtimeCredentialSlotKey,
|
|
selectedDeploymentRuntimeCredentials,
|
|
selectedRuntimeCredentialSelections,
|
|
} from '../../shared/components/runtime-credential-bindings-utils'
|
|
import { createDeploymentIdempotencyKey } from '../../shared/domain/idempotency'
|
|
import { releaseDeploymentAction } from '../../shared/domain/release-action'
|
|
|
|
type OpenDeployDrawerParams = {
|
|
appInstanceId: string
|
|
environmentId?: string
|
|
releaseId?: string
|
|
}
|
|
|
|
export const deployDrawerOpenAtom = atom(false)
|
|
export const deployDrawerAppInstanceIdAtom = atom<string | undefined>(undefined)
|
|
export const deployDrawerEnvironmentIdAtom = atom<string | undefined>(undefined)
|
|
export const deployDrawerReleaseIdAtom = atom<string | undefined>(undefined)
|
|
export const deployFormAppInstanceIdAtom = atom<string | undefined>(undefined)
|
|
|
|
export const openDeployDrawerAtom = atom(null, (_get, set, params: OpenDeployDrawerParams) => {
|
|
set(deployDrawerAppInstanceIdAtom, params.appInstanceId)
|
|
set(deployDrawerEnvironmentIdAtom, params.environmentId)
|
|
set(deployDrawerReleaseIdAtom, params.releaseId)
|
|
set(deployDrawerOpenAtom, true)
|
|
})
|
|
|
|
export const closeDeployDrawerAtom = atom(null, (_get, set) => {
|
|
set(deployDrawerOpenAtom, false)
|
|
set(deployDrawerAppInstanceIdAtom, undefined)
|
|
set(deployDrawerEnvironmentIdAtom, undefined)
|
|
set(deployDrawerReleaseIdAtom, undefined)
|
|
})
|
|
|
|
export type DeployReadyFormConfig = {
|
|
appInstanceId: string
|
|
environments: Environment[]
|
|
releases: Release[]
|
|
runtimeRows: EnvironmentDeployment[]
|
|
defaultReleaseId?: string
|
|
lockedEnvId?: string
|
|
presetReleaseId?: string
|
|
releaseEmptyLabel?: string
|
|
}
|
|
|
|
export const deployReadyFormConfigAtom = atom<DeployReadyFormConfig | undefined>(undefined)
|
|
|
|
export const releaseDeploymentViewQueryAtom = atomWithQuery((get) => {
|
|
const appInstanceId = get(deployFormAppInstanceIdAtom)
|
|
|
|
return consoleQuery.enterprise.releaseService.computeReleaseDeploymentView.queryOptions({
|
|
input: appInstanceId
|
|
? {
|
|
params: { appInstanceId },
|
|
}
|
|
: skipToken,
|
|
enabled: Boolean(appInstanceId),
|
|
})
|
|
})
|
|
|
|
export const releaseDeploymentViewAtom = selectAtom(
|
|
releaseDeploymentViewQueryAtom,
|
|
(query) => query.data,
|
|
)
|
|
export const releaseDeploymentViewIsLoadingAtom = selectAtom(
|
|
releaseDeploymentViewQueryAtom,
|
|
(query) => query.isLoading,
|
|
)
|
|
export const releaseDeploymentViewIsErrorAtom = selectAtom(
|
|
releaseDeploymentViewQueryAtom,
|
|
(query) => query.isError,
|
|
)
|
|
|
|
const selectedEnvIdAtom = atom<string | undefined>(undefined)
|
|
const selectedReleaseIdAtom = atom<string | undefined>(undefined)
|
|
const manualBindingsAtom = atom<RuntimeCredentialBindingSelections>({})
|
|
export const deployEnvVarValuesAtom = atom<EnvVarValues>({})
|
|
const showValidationErrorsAtom = atom(false)
|
|
export const deployReadyFormLocalAtoms = [
|
|
selectedEnvIdAtom,
|
|
selectedReleaseIdAtom,
|
|
manualBindingsAtom,
|
|
deployEnvVarValuesAtom,
|
|
showValidationErrorsAtom,
|
|
] as const
|
|
|
|
function formConfig(get: Getter) {
|
|
const config = get(deployReadyFormConfigAtom)
|
|
if (!config) throw new Error('Missing deploy ready form config.')
|
|
|
|
return config
|
|
}
|
|
|
|
const resetDeployBindingsAtom = atom(null, (_get, set) => {
|
|
set(manualBindingsAtom, {})
|
|
set(deployEnvVarValuesAtom, {})
|
|
set(showValidationErrorsAtom, false)
|
|
})
|
|
|
|
export const selectDeployEnvironmentAtom = atom(null, (_get, set, environmentId: string) => {
|
|
set(selectedEnvIdAtom, environmentId)
|
|
set(resetDeployBindingsAtom)
|
|
})
|
|
|
|
export const selectDeployReleaseAtom = atom(null, (_get, set, releaseId: string) => {
|
|
set(selectedReleaseIdAtom, releaseId)
|
|
set(resetDeployBindingsAtom)
|
|
})
|
|
|
|
export const showDeployValidationErrorsAtom = atom(null, (_get, set) => {
|
|
set(showValidationErrorsAtom, true)
|
|
})
|
|
|
|
export const deployShowValidationErrorsAtom = atom((get) => {
|
|
return get(showValidationErrorsAtom)
|
|
})
|
|
|
|
const deployPresetReleaseAtom = atom((get) => {
|
|
const config = formConfig(get)
|
|
return config.presetReleaseId
|
|
? config.releases.find((r) => r.id === config.presetReleaseId)
|
|
: undefined
|
|
})
|
|
|
|
export const deployDisplayedReleaseAtom = atom((get): Release | undefined => {
|
|
return get(deployPresetReleaseAtom)
|
|
})
|
|
|
|
export const deployIsExistingReleaseAtom = atom((get) => {
|
|
const config = formConfig(get)
|
|
return Boolean(config.presetReleaseId)
|
|
})
|
|
|
|
export const deployReleaseRowsAtom = atom((get) => {
|
|
return formConfig(get).releases
|
|
})
|
|
|
|
export const deployReleaseEmptyLabelAtom = atom((get) => {
|
|
return formConfig(get).releaseEmptyLabel
|
|
})
|
|
|
|
export const deployEnvironmentRowsAtom = atom((get) => {
|
|
return formConfig(get).environments
|
|
})
|
|
|
|
export const deployLockedEnvironmentIdAtom = atom((get) => {
|
|
return formConfig(get).lockedEnvId
|
|
})
|
|
|
|
export const deploySelectedEnvironmentIdAtom = atom((get) => {
|
|
const config = formConfig(get)
|
|
return get(selectedEnvIdAtom) ?? config.lockedEnvId ?? config.environments[0]?.id
|
|
})
|
|
|
|
const deploySelectedEnvironmentAtom = atom((get) => {
|
|
const config = formConfig(get)
|
|
const selectedEnvironmentId = get(deploySelectedEnvironmentIdAtom)
|
|
return selectedEnvironmentId
|
|
? config.environments.find((env) => env.id === selectedEnvironmentId)
|
|
: undefined
|
|
})
|
|
|
|
export const deploySelectedReleaseIdAtom = atom((get) => {
|
|
const config = formConfig(get)
|
|
const displayedRelease = get(deployDisplayedReleaseAtom)
|
|
return get(selectedReleaseIdAtom) ?? displayedRelease?.id ?? config.defaultReleaseId
|
|
})
|
|
|
|
const deploySelectedReleaseAtom = atom((get) => {
|
|
const config = formConfig(get)
|
|
const selectedReleaseId = get(deploySelectedReleaseIdAtom)
|
|
return selectedReleaseId
|
|
? config.releases.find((release) => release.id === selectedReleaseId)
|
|
: undefined
|
|
})
|
|
|
|
const deployTargetReleaseAtom = atom((get) => {
|
|
return get(deployDisplayedReleaseAtom) ?? get(deploySelectedReleaseAtom)
|
|
})
|
|
|
|
export const deployLockedEnvironmentAtom = atom((get) => {
|
|
const config = formConfig(get)
|
|
return config.lockedEnvId
|
|
? config.environments.find((environment) => environment.id === config.lockedEnvId)
|
|
: undefined
|
|
})
|
|
|
|
export const deployTargetReleaseIdAtom = atom((get) => {
|
|
const targetRelease = get(deployTargetReleaseAtom)
|
|
return targetRelease?.id ?? get(deploySelectedReleaseIdAtom)
|
|
})
|
|
|
|
export const deployHasSelectedEnvironmentAtom = atom((get) => {
|
|
return Boolean(get(deploySelectedEnvironmentAtom))
|
|
})
|
|
|
|
const releaseDeploymentOptionsQueryAtom = atomWithQuery((get) => {
|
|
const hasSelectedEnvironment = get(deployHasSelectedEnvironmentAtom)
|
|
const releaseId = get(deployTargetReleaseIdAtom)
|
|
const selectedEnvironmentId = get(deploySelectedEnvironmentIdAtom)
|
|
const hasRequiredInput = Boolean(releaseId && selectedEnvironmentId)
|
|
|
|
return consoleQuery.enterprise.releaseService.computeDeploymentOptions.queryOptions({
|
|
input:
|
|
releaseId && selectedEnvironmentId
|
|
? {
|
|
body: {
|
|
releaseId,
|
|
environmentId: selectedEnvironmentId,
|
|
},
|
|
}
|
|
: skipToken,
|
|
enabled: hasRequiredInput && hasSelectedEnvironment,
|
|
retry: false,
|
|
})
|
|
})
|
|
|
|
const releaseDeploymentOptionsAtom = selectAtom(
|
|
releaseDeploymentOptionsQueryAtom,
|
|
(query) => query.data,
|
|
)
|
|
const releaseDeploymentOptionsIsLoadingAtom = selectAtom(
|
|
releaseDeploymentOptionsQueryAtom,
|
|
(query) => query.isLoading,
|
|
)
|
|
const releaseDeploymentOptionsIsFetchingAtom = selectAtom(
|
|
releaseDeploymentOptionsQueryAtom,
|
|
(query) => query.isFetching,
|
|
)
|
|
const releaseDeploymentOptionsIsErrorAtom = selectAtom(
|
|
releaseDeploymentOptionsQueryAtom,
|
|
(query) => query.isError,
|
|
)
|
|
|
|
export const deployBindingSlotsAtom = atom((get) => {
|
|
const deploymentOptions = get(releaseDeploymentOptionsAtom)
|
|
|
|
return (
|
|
deploymentOptions?.options.credentialSlots.filter((slot) => runtimeCredentialSlotKey(slot)) ??
|
|
[]
|
|
)
|
|
})
|
|
|
|
export const deployEnvVarSlotsAtom = atom((get): EnvVarBindingSlot[] => {
|
|
const deploymentOptions = get(releaseDeploymentOptionsAtom)
|
|
|
|
return (
|
|
deploymentOptions?.options.envVarSlots.flatMap((slot): EnvVarBindingSlot[] => {
|
|
const bindingSlot = envVarBindingSlotFromContract(slot)
|
|
return bindingSlot ? [bindingSlot] : []
|
|
}) ?? []
|
|
)
|
|
})
|
|
|
|
export const deployIsBindingOptionsLoadingAtom = atom((get) => {
|
|
const releaseId = get(deployTargetReleaseIdAtom)
|
|
|
|
return Boolean(
|
|
releaseId &&
|
|
get(deployHasSelectedEnvironmentAtom) &&
|
|
(get(releaseDeploymentOptionsIsLoadingAtom) || get(releaseDeploymentOptionsIsFetchingAtom)),
|
|
)
|
|
})
|
|
|
|
export const deployHasBindingOptionsErrorAtom = atom((get) => {
|
|
return get(releaseDeploymentOptionsIsErrorAtom)
|
|
})
|
|
|
|
const deployIsBindingOptionsReadyAtom = atom((get) => {
|
|
const releaseId = get(deployTargetReleaseIdAtom)
|
|
|
|
return Boolean(
|
|
releaseId &&
|
|
get(deployHasSelectedEnvironmentAtom) &&
|
|
get(releaseDeploymentOptionsAtom) &&
|
|
!get(deployIsBindingOptionsLoadingAtom) &&
|
|
!get(deployHasBindingOptionsErrorAtom),
|
|
)
|
|
})
|
|
|
|
function deployEnvVarValueSource(
|
|
slot: EnvVarBindingSlot,
|
|
selection: EnvVarValueSelection | undefined,
|
|
) {
|
|
return (
|
|
selection?.valueSource ??
|
|
(slot.hasLastValue
|
|
? ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LAST_DEPLOYMENT
|
|
: slot.hasDefaultValue
|
|
? ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_DSL_DEFAULT
|
|
: ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LITERAL)
|
|
)
|
|
}
|
|
|
|
function deployEnvVarInput(
|
|
slot: EnvVarBindingSlot,
|
|
selection: EnvVarValueSelection | undefined,
|
|
): EnvVarInput[] {
|
|
const valueSource = deployEnvVarValueSource(slot, selection)
|
|
|
|
if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LAST_DEPLOYMENT) {
|
|
return slot.hasLastValue ? [{ key: slot.key, valueSource }] : []
|
|
}
|
|
|
|
if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_DSL_DEFAULT) {
|
|
return slot.hasDefaultValue ? [{ key: slot.key, valueSource }] : []
|
|
}
|
|
|
|
if (!selection?.value || (slot.valueType === 'number' && Number.isNaN(Number(selection.value))))
|
|
return []
|
|
|
|
return [
|
|
{
|
|
key: slot.key,
|
|
value: selection.value,
|
|
valueSource,
|
|
},
|
|
]
|
|
}
|
|
|
|
function deployEnvVarSelectionReady(
|
|
slot: EnvVarBindingSlot,
|
|
selection: EnvVarValueSelection | undefined,
|
|
) {
|
|
const valueSource = deployEnvVarValueSource(slot, selection)
|
|
|
|
if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LAST_DEPLOYMENT)
|
|
return Boolean(slot.hasLastValue)
|
|
if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_DSL_DEFAULT)
|
|
return Boolean(slot.hasDefaultValue)
|
|
if (!selection?.value) return false
|
|
|
|
return slot.valueType !== 'number' || !Number.isNaN(Number(selection.value))
|
|
}
|
|
|
|
export const deploySelectedBindingsAtom = atom((get) => {
|
|
return selectedRuntimeCredentialSelections(get(deployBindingSlotsAtom), get(manualBindingsAtom))
|
|
})
|
|
|
|
const deployDeploymentCredentialsAtom = atom((get) => {
|
|
return selectedDeploymentRuntimeCredentials(
|
|
get(deployBindingSlotsAtom),
|
|
get(deploySelectedBindingsAtom),
|
|
)
|
|
})
|
|
|
|
const deployDeploymentEnvVarsAtom = atom((get) => {
|
|
const envVarValues = get(deployEnvVarValuesAtom)
|
|
|
|
return get(deployEnvVarSlotsAtom).flatMap((slot) =>
|
|
deployEnvVarInput(slot, envVarValues[slot.key]),
|
|
)
|
|
})
|
|
|
|
const deployRequiredBindingsReadyAtom = atom((get) => {
|
|
const selectedBindings = get(deploySelectedBindingsAtom)
|
|
|
|
return get(deployBindingSlotsAtom).every(
|
|
(slot) =>
|
|
!hasMissingRequiredRuntimeCredentialBinding(
|
|
slot,
|
|
selectedBindings[runtimeCredentialSlotKey(slot)],
|
|
),
|
|
)
|
|
})
|
|
|
|
const deployRequiredEnvVarsReadyAtom = atom((get) => {
|
|
const envVarValues = get(deployEnvVarValuesAtom)
|
|
|
|
return get(deployEnvVarSlotsAtom).every((slot) =>
|
|
deployEnvVarSelectionReady(slot, envVarValues[slot.key]),
|
|
)
|
|
})
|
|
|
|
export const selectDeployBindingAtom = atom(null, (get, set, slot: string, value: string) => {
|
|
set(manualBindingsAtom, {
|
|
...get(manualBindingsAtom),
|
|
[slot]: value,
|
|
})
|
|
})
|
|
|
|
export const setDeployEnvVarAtom = atom(
|
|
null,
|
|
(get, set, key: string, value: EnvVarValueSelection) => {
|
|
set(deployEnvVarValuesAtom, {
|
|
...get(deployEnvVarValuesAtom),
|
|
[key]: value,
|
|
})
|
|
},
|
|
)
|
|
|
|
const promoteReleaseMutationAtom = atomWithMutation(() =>
|
|
consoleQuery.enterprise.deploymentService.promote.mutationOptions(),
|
|
)
|
|
|
|
const rollbackReleaseMutationAtom = atomWithMutation(() =>
|
|
consoleQuery.enterprise.deploymentService.rollback.mutationOptions(),
|
|
)
|
|
|
|
export const isDeployReleaseSubmittingAtom = atom((get) => {
|
|
return get(promoteReleaseMutationAtom).isPending || get(rollbackReleaseMutationAtom).isPending
|
|
})
|
|
|
|
export const canAttemptDeployAtom = atom((get) => {
|
|
return Boolean(
|
|
get(deploySelectedEnvironmentIdAtom) &&
|
|
get(deploySelectedEnvironmentAtom) &&
|
|
get(deployTargetReleaseIdAtom) &&
|
|
get(deployIsBindingOptionsReadyAtom) &&
|
|
!get(isDeployReleaseSubmittingAtom),
|
|
)
|
|
})
|
|
|
|
export const canSubmitDeployAtom = atom((get) => {
|
|
return Boolean(
|
|
get(canAttemptDeployAtom) &&
|
|
get(deployRequiredBindingsReadyAtom) &&
|
|
get(deployRequiredEnvVarsReadyAtom),
|
|
)
|
|
})
|
|
|
|
export const deployReleaseSubmissionAtom = atom(
|
|
null,
|
|
(
|
|
get,
|
|
set,
|
|
{
|
|
deployFailedMessage,
|
|
}: {
|
|
deployFailedMessage: string
|
|
},
|
|
) => {
|
|
const config = formConfig(get)
|
|
const selectedEnvironmentId = get(deploySelectedEnvironmentIdAtom)
|
|
const targetRelease = get(deployTargetReleaseAtom)
|
|
const targetReleaseId = get(deployTargetReleaseIdAtom)
|
|
|
|
if (!targetReleaseId || !selectedEnvironmentId) return
|
|
|
|
const idempotencyKey = createDeploymentIdempotencyKey()
|
|
const currentRelease = config.runtimeRows.find(
|
|
(row) => row.environment.id === selectedEnvironmentId,
|
|
)?.currentRelease
|
|
const action = releaseDeploymentAction({
|
|
targetRelease,
|
|
currentRelease,
|
|
releaseRows: config.releases,
|
|
isExistingRelease: true,
|
|
})
|
|
const mutationOptions = {
|
|
onSuccess: () => {
|
|
set(closeDeployDrawerAtom)
|
|
},
|
|
onError: () => {
|
|
toast.error(deployFailedMessage)
|
|
},
|
|
}
|
|
|
|
if (action === 'rollback') {
|
|
get(rollbackReleaseMutationAtom).mutate(
|
|
{
|
|
params: {
|
|
appInstanceId: config.appInstanceId,
|
|
environmentId: selectedEnvironmentId,
|
|
},
|
|
body: {
|
|
appInstanceId: config.appInstanceId,
|
|
environmentId: selectedEnvironmentId,
|
|
targetReleaseId,
|
|
idempotencyKey,
|
|
},
|
|
},
|
|
mutationOptions,
|
|
)
|
|
return
|
|
}
|
|
|
|
const deploymentEnvVars = get(deployDeploymentEnvVarsAtom)
|
|
get(promoteReleaseMutationAtom).mutate(
|
|
{
|
|
params: {
|
|
appInstanceId: config.appInstanceId,
|
|
environmentId: selectedEnvironmentId,
|
|
},
|
|
body: {
|
|
appInstanceId: config.appInstanceId,
|
|
environmentId: selectedEnvironmentId,
|
|
releaseId: targetReleaseId,
|
|
credentials: get(deployDeploymentCredentialsAtom),
|
|
envVars: deploymentEnvVars.length > 0 ? deploymentEnvVars : undefined,
|
|
idempotencyKey,
|
|
},
|
|
},
|
|
mutationOptions,
|
|
)
|
|
},
|
|
)
|