mirror of
https://github.com/langgenius/dify.git
synced 2026-07-24 13:08:34 +08:00
286 lines
9.0 KiB
TypeScript
286 lines
9.0 KiB
TypeScript
'use client'
|
|
|
|
import type {
|
|
Environment,
|
|
EnvironmentDeployment,
|
|
Release,
|
|
} from '@dify/contracts/enterprise/types.gen'
|
|
import { Button } from '@langgenius/dify-ui/button'
|
|
import { useAtomValue, useSetAtom } from 'jotai'
|
|
import { ScopeProvider } from 'jotai-scope'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { EnvVarBindingsPanel } from '../../shared/components/env-var-bindings'
|
|
import { isAvailableDeploymentTarget } from '../../shared/domain/runtime-status'
|
|
import {
|
|
canAttemptDeployAtom,
|
|
canSubmitDeployAtom,
|
|
closeDeployDrawerAtom,
|
|
deployBindingSlotsAtom,
|
|
deployEnvVarSlotsAtom,
|
|
deployEnvVarValuesAtom,
|
|
deployFormAppInstanceIdAtom,
|
|
deployHasBindingOptionsErrorAtom,
|
|
deployHasSelectedEnvironmentAtom,
|
|
deployIsBindingOptionsLoadingAtom,
|
|
deployReadyFormConfigAtom,
|
|
deployReadyFormLocalAtoms,
|
|
deployReleaseSubmissionAtom,
|
|
deploySelectedBindingsAtom,
|
|
deployShowValidationErrorsAtom,
|
|
deployTargetReleaseIdAtom,
|
|
isDeployReleaseSubmittingAtom,
|
|
releaseDeploymentViewAtom,
|
|
releaseDeploymentViewIsErrorAtom,
|
|
releaseDeploymentViewIsLoadingAtom,
|
|
selectDeployBindingAtom,
|
|
setDeployEnvVarAtom,
|
|
showDeployValidationErrorsAtom,
|
|
} from '../state'
|
|
import { currentReleaseIdForEnvironment, selectableDeployReleases } from '../state/release-options'
|
|
import {
|
|
BindingOptionsPanel,
|
|
DEPLOY_DRAWER_BINDING_LIST_CLASS_NAME,
|
|
DeployFormHeader,
|
|
EnvironmentField,
|
|
ReleaseField,
|
|
} from './form-sections'
|
|
import { DeployFormSkeleton } from './form-skeleton'
|
|
|
|
type DeployFormProps = {
|
|
appInstanceId: string
|
|
lockedEnvId?: string
|
|
presetReleaseId?: string
|
|
}
|
|
|
|
type DeployReadyFormProps = DeployFormProps & {
|
|
environments: Environment[]
|
|
releases: Release[]
|
|
runtimeRows: EnvironmentDeployment[]
|
|
defaultReleaseId?: string
|
|
releaseEmptyLabel?: string
|
|
}
|
|
|
|
function DeployRuntimeCredentialBindingsSection() {
|
|
const { t } = useTranslation('deployments')
|
|
const bindingSlots = useAtomValue(deployBindingSlotsAtom)
|
|
const selectedBindings = useAtomValue(deploySelectedBindingsAtom)
|
|
const isBindingOptionsLoading = useAtomValue(deployIsBindingOptionsLoadingAtom)
|
|
const hasBindingOptionsError = useAtomValue(deployHasBindingOptionsErrorAtom)
|
|
const showValidationErrors = useAtomValue(deployShowValidationErrorsAtom)
|
|
const selectBinding = useSetAtom(selectDeployBindingAtom)
|
|
|
|
return (
|
|
<BindingOptionsPanel
|
|
slots={bindingSlots}
|
|
selections={selectedBindings}
|
|
isLoading={isBindingOptionsLoading}
|
|
hasError={hasBindingOptionsError}
|
|
bindingCountLabel={t(($) => $['deployDrawer.bindingCount'], { count: bindingSlots.length })}
|
|
showMissingRequired={showValidationErrors}
|
|
onChange={selectBinding}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DeployEnvVarBindingsSection() {
|
|
const { t } = useTranslation('deployments')
|
|
const envVarSlots = useAtomValue(deployEnvVarSlotsAtom)
|
|
const envVarValues = useAtomValue(deployEnvVarValuesAtom)
|
|
const isBindingOptionsLoading = useAtomValue(deployIsBindingOptionsLoadingAtom)
|
|
const hasBindingOptionsError = useAtomValue(deployHasBindingOptionsErrorAtom)
|
|
const showValidationErrors = useAtomValue(deployShowValidationErrorsAtom)
|
|
const setDeployEnvVar = useSetAtom(setDeployEnvVarAtom)
|
|
|
|
if (isBindingOptionsLoading || hasBindingOptionsError) return null
|
|
|
|
return (
|
|
<EnvVarBindingsPanel
|
|
slots={envVarSlots}
|
|
values={envVarValues}
|
|
title={t(($) => $['deployDrawer.envVars'])}
|
|
hint={t(($) => $['deployDrawer.envVarHint'])}
|
|
envVarPlaceholder={t(($) => $['deployDrawer.envVarPlaceholder'])}
|
|
literalSourceLabel={t(($) => $['deployDrawer.envVarSource.literal'])}
|
|
defaultSourceLabel={t(($) => $['deployDrawer.envVarSource.default'])}
|
|
lastDeploymentSourceLabel={t(($) => $['deployDrawer.envVarSource.lastDeployment'])}
|
|
valueTypeLabels={{
|
|
string: t(($) => $['deployDrawer.envVarType.string']),
|
|
number: t(($) => $['deployDrawer.envVarType.number']),
|
|
secret: t(($) => $['deployDrawer.envVarType.secret']),
|
|
}}
|
|
sourceAriaLabel={(key) => t(($) => $['deployDrawer.envVarSource.ariaLabel'], { key })}
|
|
defaultSourcePriority="lastDeployment"
|
|
envVarCountLabel={t(($) => $['deployDrawer.envVarCount'], { count: envVarSlots.length })}
|
|
missingRequiredLabel={t(($) => $['deployDrawer.missingRequiredEnvVar'])}
|
|
listClassName={DEPLOY_DRAWER_BINDING_LIST_CLASS_NAME}
|
|
showMissingRequired={showValidationErrors}
|
|
onChange={setDeployEnvVar}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DeployBindingsSection() {
|
|
const targetReleaseId = useAtomValue(deployTargetReleaseIdAtom)
|
|
const hasSelectedEnvironment = useAtomValue(deployHasSelectedEnvironmentAtom)
|
|
|
|
if (!targetReleaseId || !hasSelectedEnvironment) return null
|
|
|
|
return (
|
|
<>
|
|
<DeployRuntimeCredentialBindingsSection />
|
|
<DeployEnvVarBindingsSection />
|
|
</>
|
|
)
|
|
}
|
|
|
|
function DeployFormBody() {
|
|
return (
|
|
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-5">
|
|
<div className="flex flex-col gap-5">
|
|
<ReleaseField />
|
|
<EnvironmentField />
|
|
<DeployBindingsSection />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function DeployFooter() {
|
|
const { t } = useTranslation('deployments')
|
|
const closeDeployDrawer = useSetAtom(closeDeployDrawerAtom)
|
|
const showValidationErrors = useSetAtom(showDeployValidationErrorsAtom)
|
|
const submitDeployRelease = useSetAtom(deployReleaseSubmissionAtom)
|
|
const canAttemptDeploy = useAtomValue(canAttemptDeployAtom)
|
|
const canDeploy = useAtomValue(canSubmitDeployAtom)
|
|
const isSubmitting = useAtomValue(isDeployReleaseSubmittingAtom)
|
|
const submitLabel = isSubmitting
|
|
? t(($) => $['deployDrawer.deploying'])
|
|
: t(($) => $['deployDrawer.deploy'])
|
|
|
|
function handleDeploy() {
|
|
showValidationErrors()
|
|
|
|
if (!canDeploy) return
|
|
|
|
submitDeployRelease({
|
|
deployFailedMessage: t(($) => $['deployDrawer.deployFailed']),
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div className="flex shrink-0 justify-end gap-2 border-t border-divider-subtle bg-background-default-subtle px-6 py-4">
|
|
<Button type="button" variant="secondary" onClick={closeDeployDrawer}>
|
|
{t(($) => $['deployDrawer.cancel'])}
|
|
</Button>
|
|
<Button variant="primary" disabled={!canAttemptDeploy} onClick={handleDeploy}>
|
|
{submitLabel}
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function deployReadyFormStoreKey({
|
|
appInstanceId,
|
|
environments,
|
|
releases,
|
|
runtimeRows,
|
|
defaultReleaseId,
|
|
lockedEnvId,
|
|
presetReleaseId,
|
|
}: DeployReadyFormProps) {
|
|
return [
|
|
appInstanceId,
|
|
lockedEnvId ?? 'any',
|
|
presetReleaseId ?? 'new',
|
|
defaultReleaseId ?? 'none',
|
|
environments.map((env) => env.id).join(','),
|
|
releases.map((release) => release.id).join(','),
|
|
runtimeRows.map((row) => `${row.environment.id}:${row.currentRelease?.id ?? 'none'}`).join(','),
|
|
].join('|')
|
|
}
|
|
|
|
function DeployReadyForm(config: DeployReadyFormProps) {
|
|
return (
|
|
<ScopeProvider
|
|
atoms={[[deployReadyFormConfigAtom, config], ...deployReadyFormLocalAtoms]}
|
|
name="DeployReadyForm"
|
|
>
|
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
<DeployFormHeader />
|
|
<DeployFormBody />
|
|
<DeployFooter />
|
|
</div>
|
|
</ScopeProvider>
|
|
)
|
|
}
|
|
|
|
function DeployFormContent({ appInstanceId, lockedEnvId, presetReleaseId }: DeployFormProps) {
|
|
const { t } = useTranslation('deployments')
|
|
const deploymentView = useAtomValue(releaseDeploymentViewAtom)
|
|
const isLoading = useAtomValue(releaseDeploymentViewIsLoadingAtom)
|
|
const isError = useAtomValue(releaseDeploymentViewIsErrorAtom)
|
|
|
|
if (isLoading) {
|
|
return <DeployFormSkeleton />
|
|
}
|
|
|
|
if (isError) {
|
|
return (
|
|
<div className="p-4 system-sm-regular text-text-destructive">
|
|
{t(($) => $['common.loadFailed'])}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!deploymentView) {
|
|
return (
|
|
<div className="p-4 system-sm-regular text-text-destructive">
|
|
{t(($) => $['common.loadFailed'])}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const runtimeRows = deploymentView.environmentDeployments
|
|
const environments = runtimeRows
|
|
.filter((row) => lockedEnvId || isAvailableDeploymentTarget(row))
|
|
.map((row) => row.environment)
|
|
const releaseRows = deploymentView.releases
|
|
const currentReleaseId = currentReleaseIdForEnvironment(runtimeRows, lockedEnvId)
|
|
const releases = selectableDeployReleases({
|
|
releases: releaseRows,
|
|
lockedEnvId,
|
|
currentReleaseId,
|
|
presetReleaseId,
|
|
})
|
|
const defaultReleaseId = releases[0]?.id
|
|
const releaseEmptyLabel =
|
|
lockedEnvId && !presetReleaseId && currentReleaseId
|
|
? t(($) => $['deployDrawer.noOtherReleaseAvailable'])
|
|
: undefined
|
|
const readyFormConfig = {
|
|
appInstanceId,
|
|
environments,
|
|
releases,
|
|
runtimeRows,
|
|
defaultReleaseId,
|
|
releaseEmptyLabel,
|
|
lockedEnvId,
|
|
presetReleaseId,
|
|
}
|
|
const formKey = deployReadyFormStoreKey(readyFormConfig)
|
|
|
|
return <DeployReadyForm key={formKey} {...readyFormConfig} />
|
|
}
|
|
|
|
export function DeployForm(props: DeployFormProps) {
|
|
return (
|
|
<ScopeProvider
|
|
key={props.appInstanceId}
|
|
atoms={[[deployFormAppInstanceIdAtom, props.appInstanceId]]}
|
|
name="DeployForm"
|
|
>
|
|
<DeployFormContent {...props} />
|
|
</ScopeProvider>
|
|
)
|
|
}
|