'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 ( $['createGuide.target.title'])} description={t(($) => $['createGuide.target.description'])} hideHeader >
) } 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) }