mirror of
https://github.com/langgenius/dify.git
synced 2026-07-25 21:48:30 +08:00
198 lines
7.2 KiB
TypeScript
198 lines
7.2 KiB
TypeScript
'use client'
|
|
|
|
import type { CredentialSlot, Environment } from '@dify/contracts/enterprise/types.gen'
|
|
import type { RuntimeCredentialBindingSelections } from '../../shared/components/runtime-credential-bindings-utils'
|
|
import { DrawerDescription, DrawerTitle } from '@langgenius/dify-ui/drawer'
|
|
import { useAtomValue, useSetAtom } from 'jotai'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { SkeletonContainer, SkeletonRectangle } from '@/app/components/base/skeleton'
|
|
import { DeploymentStateMessage } from '../../shared/components/empty-state'
|
|
import { RuntimeCredentialBindingsPanel } from '../../shared/components/runtime-credential-bindings'
|
|
import { formatDate, releaseCommit } from '../../shared/domain/release'
|
|
import {
|
|
deployDisplayedReleaseAtom,
|
|
deployEnvironmentRowsAtom,
|
|
deployIsExistingReleaseAtom,
|
|
deployLockedEnvironmentAtom,
|
|
deployLockedEnvironmentIdAtom,
|
|
deployReleaseEmptyLabelAtom,
|
|
deployReleaseRowsAtom,
|
|
deploySelectedEnvironmentIdAtom,
|
|
deploySelectedReleaseIdAtom,
|
|
selectDeployEnvironmentAtom,
|
|
selectDeployReleaseAtom,
|
|
} from '../state'
|
|
import {
|
|
DeploymentSelect,
|
|
EnvironmentRow,
|
|
Field,
|
|
} from './select'
|
|
|
|
export const DEPLOY_DRAWER_BINDING_LIST_CLASS_NAME = 'max-h-none overflow-visible'
|
|
|
|
function environmentOptionLabel(env: Environment, t: ReturnType<typeof useTranslation<'deployments'>>['t']) {
|
|
const description = env.description.trim()
|
|
if (description)
|
|
return `${env.displayName} · ${description}`
|
|
|
|
return `${env.displayName} · ${t(`mode.${env.mode}`)} · ${t(`backend.${env.backend}`)}`
|
|
}
|
|
|
|
export function BindingOptionsPanel({
|
|
slots,
|
|
selections,
|
|
isLoading,
|
|
hasError,
|
|
bindingCountLabel,
|
|
showMissingRequired,
|
|
onChange,
|
|
}: {
|
|
slots: CredentialSlot[]
|
|
selections: RuntimeCredentialBindingSelections
|
|
isLoading: boolean
|
|
hasError: boolean
|
|
bindingCountLabel: string
|
|
showMissingRequired: boolean
|
|
onChange: (slot: string, value: string) => void
|
|
}) {
|
|
const { t } = useTranslation('deployments')
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="rounded-xl border border-divider-subtle bg-background-default-subtle px-3 py-4">
|
|
<SkeletonContainer className="gap-2">
|
|
<SkeletonRectangle className="h-3 w-32 animate-pulse" />
|
|
<SkeletonRectangle className="h-3 w-2/3 animate-pulse" />
|
|
<SkeletonRectangle className="my-0 h-8 w-full animate-pulse rounded-lg" />
|
|
</SkeletonContainer>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (hasError) {
|
|
return (
|
|
<div className="rounded-xl border border-divider-subtle bg-background-default-subtle px-3 py-4 system-sm-regular text-text-destructive">
|
|
{t('deployDrawer.bindingOptionsFailed')}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<RuntimeCredentialBindingsPanel
|
|
slots={slots}
|
|
selections={selections}
|
|
title={t('deployDrawer.runtimeCredentials')}
|
|
hint={t('deployDrawer.bindingSelectionHint')}
|
|
noBindingRequiredLabel={t('deployDrawer.noBindingRequired')}
|
|
noCredentialCandidatesLabel={t('deployDrawer.noCredentialCandidates')}
|
|
selectCredentialLabel={t('deployDrawer.selectCredential')}
|
|
missingRequiredLabel={t('deployDrawer.missingRequiredBinding')}
|
|
bindingCountLabel={bindingCountLabel}
|
|
showMissingRequired={showMissingRequired}
|
|
listClassName={DEPLOY_DRAWER_BINDING_LIST_CLASS_NAME}
|
|
onChange={onChange}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export function DeployFormHeader() {
|
|
const { t } = useTranslation('deployments')
|
|
|
|
return (
|
|
<div className="shrink-0 border-b border-divider-subtle px-6 py-5 pr-14">
|
|
<DrawerTitle className="title-xl-semi-bold text-text-primary">
|
|
{t('deployDrawer.title')}
|
|
</DrawerTitle>
|
|
<DrawerDescription className="mt-1 system-sm-regular text-text-tertiary">
|
|
{t('deployDrawer.description')}
|
|
</DrawerDescription>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function ReleaseField() {
|
|
const { t } = useTranslation('deployments')
|
|
const displayedRelease = useAtomValue(deployDisplayedReleaseAtom)
|
|
const emptyLabel = useAtomValue(deployReleaseEmptyLabelAtom)
|
|
const isExistingRelease = useAtomValue(deployIsExistingReleaseAtom)
|
|
const releases = useAtomValue(deployReleaseRowsAtom)
|
|
const selectedReleaseId = useAtomValue(deploySelectedReleaseIdAtom)
|
|
const selectRelease = useSetAtom(selectDeployReleaseAtom)
|
|
|
|
return (
|
|
<Field label={t('deployDrawer.releaseLabel')}>
|
|
{isExistingRelease && displayedRelease
|
|
? (
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center justify-between rounded-lg border border-components-panel-border bg-components-panel-bg-blur px-3 py-2">
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<span className="shrink-0 font-mono system-sm-semibold text-text-primary">{displayedRelease.displayName}</span>
|
|
<span className="shrink-0 system-xs-regular text-text-tertiary">·</span>
|
|
<span className="shrink-0 font-mono system-xs-regular text-text-tertiary">{releaseCommit(displayedRelease)}</span>
|
|
</div>
|
|
<span className="shrink-0 system-xs-regular text-text-quaternary">{formatDate(displayedRelease.createdAt)}</span>
|
|
</div>
|
|
<span className="system-xs-regular text-text-tertiary">
|
|
{t('deployDrawer.existingReleaseHint')}
|
|
</span>
|
|
</div>
|
|
)
|
|
: releases.length === 0
|
|
? (
|
|
<DeploymentStateMessage variant="compact">
|
|
{emptyLabel ?? t('deployDrawer.noReleaseAvailable')}
|
|
</DeploymentStateMessage>
|
|
)
|
|
: (
|
|
<DeploymentSelect
|
|
value={selectedReleaseId}
|
|
onChange={selectRelease}
|
|
options={releases.map(release => ({
|
|
value: release.id,
|
|
label: `${release.displayName} · ${releaseCommit(release)}`,
|
|
}))}
|
|
ariaLabel={t('deployDrawer.releaseLabel')}
|
|
placeholder={t('deployDrawer.selectRelease')}
|
|
/>
|
|
)}
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
export function EnvironmentField() {
|
|
const { t } = useTranslation('deployments')
|
|
const environments = useAtomValue(deployEnvironmentRowsAtom)
|
|
const lockedEnv = useAtomValue(deployLockedEnvironmentAtom)
|
|
const lockedEnvId = useAtomValue(deployLockedEnvironmentIdAtom)
|
|
const selectedEnvironmentId = useAtomValue(deploySelectedEnvironmentIdAtom)
|
|
const selectEnvironment = useSetAtom(selectDeployEnvironmentAtom)
|
|
|
|
return (
|
|
<Field
|
|
label={t('deployDrawer.targetEnv')}
|
|
hint={lockedEnvId ? t('deployDrawer.lockedHint') : undefined}
|
|
>
|
|
{lockedEnv
|
|
? <EnvironmentRow env={lockedEnv} />
|
|
: environments.length === 0
|
|
? (
|
|
<DeploymentStateMessage variant="compact">
|
|
{t('deployDrawer.noNewEnvironmentAvailable')}
|
|
</DeploymentStateMessage>
|
|
)
|
|
: (
|
|
<DeploymentSelect
|
|
value={selectedEnvironmentId}
|
|
onChange={selectEnvironment}
|
|
options={environments.map(env => ({
|
|
value: env.id,
|
|
label: environmentOptionLabel(env, t),
|
|
}))}
|
|
ariaLabel={t('deployDrawer.targetEnv')}
|
|
placeholder={t('deployDrawer.selectEnv')}
|
|
/>
|
|
)}
|
|
</Field>
|
|
)
|
|
}
|