'use client' import type { Environment } from '@dify/contracts/enterprise/types.gen' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { RadioControl, RadioRoot } from '@langgenius/dify-ui/radio' import { RadioGroup } from '@langgenius/dify-ui/radio-group' import { toast } from '@langgenius/dify-ui/toast' import { useAtomValue, useSetAtom } from 'jotai' import { useTranslation } from 'react-i18next' import { SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton' import { EnvVarBindingsPanel, } from '@/features/deployments/components/env-var-bindings' import { RuntimeCredentialBindingsPanel, } from '@/features/deployments/components/runtime-credential-bindings' import { TitleTooltip } from '@/features/deployments/components/title-tooltip' import { UnsupportedDslNodesAlert } from '@/features/deployments/components/unsupported-dsl-nodes-alert' import { canDeployAtom, canSkipDeploymentAtom, createDeploymentGuideSubmissionAtom, CreateDeploymentGuideSubmissionBlockedError, deployableEnvironmentsAtom, deployableEnvironmentsQueryAtom, deploymentOptionsQueryAtom, deploymentTargetBindingSelectionsAtom, deploymentTargetBindingSlotsAtom, deploymentTargetEnvVarSlotsAtom, effectiveSelectedEnvironmentIdAtom, envVarValuesAtom, isCreatingReleaseOnlyAtom, isSubmittingDeploymentGuideAtom, selectBindingAtom, selectedEnvironmentIdAtom, setEnvVarAtom, stepAtom, unsupportedDslNodesAtom, } from '@/features/deployments/create-guide/state' import { deploymentErrorMessage } from '@/features/deployments/shared/domain/error' import { useRouter } from '@/next/navigation' import { StepShell } from './layout' const targetEnvironmentSkeletonKeys = ['first-target-environment', 'second-target-environment'] const targetBindingSkeletonKeys = ['first-target-binding', 'second-target-binding'] export function TargetStepContent() { const { t } = useTranslation('deployments') const unsupportedDslNodes = useAtomValue(unsupportedDslNodesAtom) return (
) } function TargetEnvironmentSection() { const { t } = useTranslation('deployments') const environmentsQuery = useAtomValue(deployableEnvironmentsQueryAtom) const environments = useAtomValue(deployableEnvironmentsAtom) const effectiveSelectedEnvironmentId = useAtomValue(effectiveSelectedEnvironmentIdAtom) const isEnvironmentError = environmentsQuery.isError const isEnvironmentLoading = environmentsQuery.isLoading || (environmentsQuery.isFetching && !environmentsQuery.data) const selectEnvironment = useSetAtom(selectedEnvironmentIdAtom) const hasEnvironmentOptions = environments.length > 0 return (
{t('createGuide.target.environment')}
{hasEnvironmentOptions ? ( value={effectiveSelectedEnvironmentId} onValueChange={selectEnvironment} className="grid grid-cols-1 items-stretch gap-3 lg:grid-cols-2" > {environments.map(environment => ( ))} ) : isEnvironmentLoading ? : (
{isEnvironmentError ? t('createGuide.target.loadEnvironmentsFailed') : t('createGuide.target.noEnvironmentOptions')}
)}
) } function EnvironmentOptionRow({ environment }: { environment: Environment }) { const { t } = useTranslation('deployments') const summary = environment.description.trim() || `${t(`mode.${environment.mode}`)} ยท ${t(`backend.${environment.backend}`)}` return ( value={environment.id} variant="unstyled" className={cn( 'group flex cursor-pointer items-center gap-3 rounded-xl border p-3 outline-hidden', 'border-components-option-card-option-border bg-components-option-card-option-bg hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs', 'focus-visible:ring-2 focus-visible:ring-state-accent-solid', 'data-checked:border-state-accent-solid data-checked:bg-state-accent-hover data-checked:shadow-xs', )} > {environment.displayName} {summary} ) } function TargetBindingSection() { const { t } = useTranslation('deployments') const deploymentOptionsQuery = useAtomValue(deploymentOptionsQueryAtom) const bindingSlots = useAtomValue(deploymentTargetBindingSlotsAtom) const bindingSelections = useAtomValue(deploymentTargetBindingSelectionsAtom) const isBindingError = deploymentOptionsQuery.isError const isBindingLoading = deploymentOptionsQuery.isLoading || (deploymentOptionsQuery.isFetching && !deploymentOptionsQuery.data) const selectBinding = useSetAtom(selectBindingAtom) const unsupportedDslNodes = useAtomValue(unsupportedDslNodesAtom) const shouldRender = !(isBindingError && unsupportedDslNodes.length > 0) if (!shouldRender) return null if (isBindingLoading || isBindingError) { return (
{t('createGuide.target.bindings')}
{t('createGuide.target.bindingHint')}
{isBindingLoading ? : (
{t('createGuide.target.loadBindingsFailed')}
)}
) } return ( ) } function TargetEnvVarSection() { const { t } = useTranslation('deployments') const setEnvVar = useSetAtom(setEnvVarAtom) const envVarValues = useAtomValue(envVarValuesAtom) const deploymentOptionsQuery = useAtomValue(deploymentOptionsQueryAtom) const envVarSlots = useAtomValue(deploymentTargetEnvVarSlotsAtom) const isBindingError = deploymentOptionsQuery.isError const isBindingLoading = deploymentOptionsQuery.isLoading || (deploymentOptionsQuery.isFetching && !deploymentOptionsQuery.data) if (isBindingLoading || isBindingError) return null return ( t('createGuide.target.envVarSource.ariaLabel', { key })} envVarCountLabel={t('createGuide.target.envVarCount', { count: envVarSlots.length })} onChange={setEnvVar} listScrollable={false} className="border-components-option-card-option-border bg-components-option-card-option-bg" /> ) } function TargetEnvironmentSkeleton() { return (
{targetEnvironmentSkeletonKeys.map(key => (
))}
) } function TargetBindingSkeleton() { return (
{targetBindingSkeletonKeys.map(key => (
))}
) } export function TargetBackButton() { const { t } = useTranslation('deployments') const setStep = useSetAtom(stepAtom) const isSubmitting = useAtomValue(isSubmittingDeploymentGuideAtom) return ( ) } export function TargetSkipDeploymentButton() { const { t } = useTranslation('deployments') const router = useRouter() const canSkipDeployment = useAtomValue(canSkipDeploymentAtom) const submitCreateDeploymentGuide = useSetAtom(createDeploymentGuideSubmissionAtom) const isSubmitting = useAtomValue(isSubmittingDeploymentGuideAtom) const isSkippingDeployment = useAtomValue(isCreatingReleaseOnlyAtom) const label = isSkippingDeployment ? t('createGuide.actions.creating') : t('createGuide.actions.skipDeploy') async function handleSkipDeployment() { if (!canSkipDeployment) return try { const appInstanceId = await submitCreateDeploymentGuide({ deployToEnvironment: false }) if (appInstanceId) router.push(`/deployments/${appInstanceId}/overview`) } catch (error) { await showSubmissionError({ error, fallbackMessage: t('createGuide.errors.createReleaseFailed'), unsupportedDslModeMessage: t('createGuide.dsl.unsupportedMode'), }) } } return ( ) } export function TargetDeployButton() { const { t } = useTranslation('deployments') const router = useRouter() const canDeploy = useAtomValue(canDeployAtom) const submitCreateDeploymentGuide = useSetAtom(createDeploymentGuideSubmissionAtom) const isSubmitting = useAtomValue(isSubmittingDeploymentGuideAtom) const isSkippingDeployment = useAtomValue(isCreatingReleaseOnlyAtom) const label = isSubmitting && !isSkippingDeployment ? t('createGuide.actions.deploying') : t('createGuide.actions.createAndDeploy') async function handleDeploy() { if (!canDeploy) return try { const appInstanceId = await submitCreateDeploymentGuide({ deployToEnvironment: true }) if (appInstanceId) router.push(`/deployments/${appInstanceId}/overview`) } catch (error) { await showSubmissionError({ error, fallbackMessage: t('createGuide.errors.deployFailed'), unsupportedDslModeMessage: t('createGuide.dsl.unsupportedMode'), }) } } return ( ) } async function showSubmissionError({ error, fallbackMessage, unsupportedDslModeMessage, }: { error: unknown fallbackMessage: string unsupportedDslModeMessage: string }) { if (error instanceof CreateDeploymentGuideSubmissionBlockedError) { toast.error(error.reason === 'unsupportedDslMode' ? unsupportedDslModeMessage : fallbackMessage) return } toast.error(await deploymentErrorMessage(error) || fallbackMessage) }