'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, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio' 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 { envVarValuesAtom, isCreatingReleaseOnlyAtom, isSubmittingDeploymentGuideAtom, selectedEnvironmentIdAtom, stepAtom, } from '@/features/deployments/create-guide/state/primitives' import { deployableEnvironmentsIsErrorAtom, deployableEnvironmentsIsFetchingAtom, deployableEnvironmentsIsLoadingAtom, deploymentOptionsDataAtom, deploymentOptionsIsErrorAtom, deploymentOptionsIsFetchingAtom, deploymentOptionsIsLoadingAtom, unsupportedDslNodesAtom, } from '@/features/deployments/create-guide/state/queries' import { createDeploymentGuideSubmissionAtom, CreateDeploymentGuideSubmissionBlockedError, } from '@/features/deployments/create-guide/state/submission' import { canDeployAtom, canSkipDeploymentAtom, deployableEnvironmentsAtom, deploymentTargetBindingSelectionsAtom, deploymentTargetBindingSlotsAtom, deploymentTargetEnvVarSlotsAtom, effectiveSelectedEnvironmentIdAtom, selectBindingAtom, setEnvVarAtom, } from '@/features/deployments/create-guide/state/target' import { EnvVarBindingsPanel, } from '@/features/deployments/shared/components/env-var-bindings' import { RuntimeCredentialBindingsPanel, } from '@/features/deployments/shared/components/runtime-credential-bindings' import { TitleTooltip } from '@/features/deployments/shared/components/title-tooltip' import { UnsupportedDslNodesAlert } from '@/features/deployments/shared/components/unsupported-dsl-nodes-alert' 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 environmentsIsError = useAtomValue(deployableEnvironmentsIsErrorAtom) const environmentsIsFetching = useAtomValue(deployableEnvironmentsIsFetchingAtom) const environmentsIsLoading = useAtomValue(deployableEnvironmentsIsLoadingAtom) const environments = useAtomValue(deployableEnvironmentsAtom) const effectiveSelectedEnvironmentId = useAtomValue(effectiveSelectedEnvironmentIdAtom) const isEnvironmentLoading = environmentsIsLoading || (environmentsIsFetching && environments.length === 0) 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 ? : (
{environmentsIsError ? 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} nativeButton render={ ) } 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) }