mirror of
https://github.com/langgenius/dify.git
synced 2026-05-27 20:36:18 +08:00
Remove source app left border highlight
This commit is contained in:
@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import Link from '@/next/link'
|
||||
import { DoneStep } from './done-step'
|
||||
import { GuideActions, GuideCard, GuideFrame } from './layout'
|
||||
import { DeploymentSummaryPreview, ReviewStep } from './review-step'
|
||||
import { CreationSections } from './source-release-sections'
|
||||
import { TargetReviewSections } from './target-step'
|
||||
import { useCreateDeploymentGuide } from './use-create-deployment-guide'
|
||||
@ -15,7 +14,6 @@ export function CreateDeploymentGuide() {
|
||||
canContinue,
|
||||
creationSectionsProps,
|
||||
deployedEnvironmentName,
|
||||
deploymentPreviewProps,
|
||||
handleBack,
|
||||
handlePrimaryAction,
|
||||
isDeploying,
|
||||
@ -25,29 +23,21 @@ export function CreateDeploymentGuide() {
|
||||
targetReviewSectionsProps,
|
||||
} = useCreateDeploymentGuide()
|
||||
|
||||
const deploymentPreview = (
|
||||
<DeploymentSummaryPreview {...deploymentPreviewProps} />
|
||||
)
|
||||
|
||||
const guideContent = (
|
||||
<>
|
||||
{step === 'done'
|
||||
? (
|
||||
<DoneStep environmentName={deployedEnvironmentName || selectedTargetEnvironmentName} />
|
||||
)
|
||||
: step === 'review'
|
||||
: showTargetConfiguration
|
||||
? (
|
||||
<ReviewStep preview={deploymentPreview} />
|
||||
<div className="flex flex-col gap-7 pb-4">
|
||||
<TargetReviewSections {...targetReviewSectionsProps} />
|
||||
</div>
|
||||
)
|
||||
: showTargetConfiguration
|
||||
? (
|
||||
<div className="flex flex-col gap-7 pb-4">
|
||||
<TargetReviewSections {...targetReviewSectionsProps} />
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<CreationSections {...creationSectionsProps} />
|
||||
)}
|
||||
: (
|
||||
<CreationSections {...creationSectionsProps} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
|
||||
@ -6,46 +6,71 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const GUIDE_PROGRESS_STEPS: GuideStep[] = ['source', 'release', 'target', 'review']
|
||||
const GUIDE_PROGRESS_STEPS: GuideStep[] = ['source', 'release', 'target']
|
||||
|
||||
function GuideStepIntro({ activeStep }: {
|
||||
activeStep: GuideStep
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
|
||||
let title: string
|
||||
let description: string
|
||||
|
||||
if (activeStep === 'source') {
|
||||
title = t('createGuide.source.title')
|
||||
description = t('createGuide.method.description')
|
||||
}
|
||||
else if (activeStep === 'release') {
|
||||
title = t('createGuide.release.title')
|
||||
description = t('createGuide.release.description')
|
||||
}
|
||||
else if (activeStep === 'target') {
|
||||
title = t('createGuide.target.title')
|
||||
description = t('createGuide.target.description')
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pb-4">
|
||||
<h2 className="system-md-semibold text-text-primary">{title}</h2>
|
||||
<p className="mt-1 max-w-150 system-sm-regular text-text-tertiary">{description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GuideProgress({ activeStep }: {
|
||||
activeStep: GuideStep
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const activeIndex = GUIDE_PROGRESS_STEPS.indexOf(activeStep)
|
||||
|
||||
return (
|
||||
<ol className="mb-6 grid grid-cols-2 gap-2 sm:grid-cols-4">
|
||||
{GUIDE_PROGRESS_STEPS.map((step, index) => {
|
||||
<ol className="grid grid-cols-2 gap-1.5 sm:grid-cols-4">
|
||||
{GUIDE_PROGRESS_STEPS.map((step) => {
|
||||
const isActive = step === activeStep
|
||||
const isComplete = activeIndex > index || activeStep === 'done'
|
||||
const label = t(`createGuide.steps.${step}`)
|
||||
|
||||
return (
|
||||
<li
|
||||
key={step}
|
||||
aria-current={isActive ? 'step' : undefined}
|
||||
title={label}
|
||||
className={cn(
|
||||
'flex min-w-0 items-center gap-2 rounded-lg border px-3 py-2 system-xs-medium',
|
||||
isActive
|
||||
? 'border-state-accent-solid bg-state-accent-hover text-text-accent'
|
||||
: isComplete
|
||||
? 'border-util-colors-green-green-200 bg-util-colors-green-green-50 text-util-colors-green-green-700'
|
||||
: 'border-divider-subtle bg-background-default text-text-tertiary',
|
||||
'flex min-w-0 items-center gap-2 px-2 py-1.5 system-xs-medium',
|
||||
isActive ? 'text-text-primary' : 'text-text-quaternary',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'flex size-5 shrink-0 items-center justify-center rounded-full system-2xs-medium',
|
||||
isComplete
|
||||
? 'bg-util-colors-green-green-600 text-text-primary-on-surface'
|
||||
: isActive
|
||||
? 'bg-primary-600 text-text-primary-on-surface'
|
||||
: 'bg-background-section-burn text-text-tertiary',
|
||||
'size-2 shrink-0 rounded-full border-[1.5px]',
|
||||
isActive
|
||||
? 'border-text-primary bg-text-primary'
|
||||
: 'border-text-quaternary bg-transparent',
|
||||
)}
|
||||
>
|
||||
{isComplete ? <span className="i-ri-check-line size-3.5" /> : index + 1}
|
||||
</span>
|
||||
<span className="truncate">{t(`createGuide.steps.${step}`)}</span>
|
||||
/>
|
||||
<span className="truncate">{label}</span>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
@ -53,6 +78,51 @@ function GuideProgress({ activeStep }: {
|
||||
)
|
||||
}
|
||||
|
||||
function GuideProgressSummary({ activeStep }: {
|
||||
activeStep: GuideStep
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const activeIndex = GUIDE_PROGRESS_STEPS.indexOf(activeStep)
|
||||
const activeStepNumber = activeIndex + 1
|
||||
|
||||
let activeStepLabel: string
|
||||
if (activeStep === 'source')
|
||||
activeStepLabel = t('createGuide.steps.source')
|
||||
else if (activeStep === 'release')
|
||||
activeStepLabel = t('createGuide.steps.release')
|
||||
else if (activeStep === 'target')
|
||||
activeStepLabel = t('createGuide.steps.target')
|
||||
else
|
||||
return null
|
||||
|
||||
if (activeIndex < 0)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className="flex w-full min-w-0 flex-col gap-2">
|
||||
<div className="flex min-w-0 items-baseline justify-between gap-3">
|
||||
<span className="truncate system-sm-medium text-text-secondary">{activeStepLabel}</span>
|
||||
<span className="shrink-0 system-xs-regular text-text-quaternary">
|
||||
{activeStepNumber}
|
||||
/
|
||||
{GUIDE_PROGRESS_STEPS.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1" aria-hidden="true">
|
||||
{GUIDE_PROGRESS_STEPS.map((step, index) => (
|
||||
<span
|
||||
key={step}
|
||||
className={cn(
|
||||
'h-1 rounded-full',
|
||||
index <= activeIndex ? 'bg-text-primary' : 'bg-divider-subtle',
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function StepShell({ title, description, descriptionClassName, hideHeader, children }: {
|
||||
title: string
|
||||
description: string
|
||||
@ -94,23 +164,30 @@ export function GuideFrame({ activeStep, children }: {
|
||||
const { t } = useTranslation('deployments')
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 overflow-hidden bg-background-default-subtle">
|
||||
<div className="relative flex h-full min-h-0 overflow-hidden bg-background-default-subtle">
|
||||
<div className="flex min-w-0 flex-1 shrink-0 justify-center overflow-y-auto">
|
||||
<section
|
||||
aria-label={t('createGuide.title')}
|
||||
className="w-full max-w-[840px] px-5 sm:px-8 lg:px-10"
|
||||
>
|
||||
<div className="h-5 sm:h-8 lg:h-12" />
|
||||
<div className="pt-1 pb-5">
|
||||
<div className="pt-1 pb-4">
|
||||
<h1 className="title-2xl-semi-bold text-text-primary">{t('createGuide.title')}</h1>
|
||||
<p className="mt-1 max-w-150 system-sm-regular text-text-tertiary">
|
||||
{t('createGuide.review.description')}
|
||||
</p>
|
||||
</div>
|
||||
{activeStep !== 'done' && <GuideProgress activeStep={activeStep} />}
|
||||
<GuideStepIntro activeStep={activeStep} />
|
||||
{activeStep !== 'done' && (
|
||||
<div className="mb-6 lg:hidden">
|
||||
<GuideProgress activeStep={activeStep} />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
{activeStep !== 'done' && (
|
||||
<aside className="pointer-events-none absolute top-24 left-[calc(50%+390px)] hidden w-[148px] min-[1120px]:block">
|
||||
<GuideProgressSummary activeStep={activeStep} />
|
||||
</aside>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -130,19 +207,17 @@ export function GuideActions({
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const primaryLabel = step === 'target'
|
||||
? t('createGuide.actions.review')
|
||||
: step === 'review'
|
||||
? isDeploying ? t('createGuide.actions.deploying') : t('createGuide.actions.deploy')
|
||||
: step === 'release' && isDeploying
|
||||
? t('createGuide.actions.creating')
|
||||
: t('createGuide.actions.next')
|
||||
? isDeploying ? t('createGuide.actions.deploying') : t('createGuide.actions.deploy')
|
||||
: step === 'release' && isDeploying
|
||||
? t('createGuide.actions.creating')
|
||||
: t('createGuide.actions.next')
|
||||
|
||||
if (step === 'method' || step === 'done')
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-2 pt-5 pb-10">
|
||||
{(step === 'release' || step === 'target' || step === 'review') && (
|
||||
<div className="flex items-center justify-end gap-2 pt-4 pb-8">
|
||||
{(step === 'release' || step === 'target') && (
|
||||
<Button type="button" variant="secondary" onClick={onBack} disabled={isDeploying}>
|
||||
{t('createGuide.actions.back')}
|
||||
</Button>
|
||||
|
||||
@ -1,122 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
CredentialSlot,
|
||||
} from '@dify/contracts/enterprise/types.gen'
|
||||
import type { ReactNode } from 'react'
|
||||
import type { BindingSelections } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
runtimeCredentialCandidateOptions,
|
||||
runtimeCredentialSlotKey,
|
||||
} from '../components/runtime-credential-bindings-utils'
|
||||
import { StepShell } from './layout'
|
||||
|
||||
export function DeploymentSummaryPreview({
|
||||
sourceName,
|
||||
instanceName,
|
||||
releaseName,
|
||||
releaseDescription,
|
||||
targetEnvironmentName,
|
||||
bindingSlots,
|
||||
bindingSelections,
|
||||
}: {
|
||||
sourceName: string
|
||||
instanceName: string
|
||||
releaseName: string
|
||||
releaseDescription: string
|
||||
targetEnvironmentName: string
|
||||
bindingSlots: CredentialSlot[]
|
||||
bindingSelections: BindingSelections
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const displayValue = (value: string) => value || '—'
|
||||
const sourceDisplayName = displayValue(sourceName)
|
||||
const instanceDisplayName = displayValue(instanceName)
|
||||
const releaseDisplayName = displayValue(releaseName)
|
||||
const environmentDisplayName = displayValue(targetEnvironmentName)
|
||||
const routeItems = [
|
||||
{
|
||||
icon: 'i-ri-apps-2-line',
|
||||
label: t('createGuide.review.source'),
|
||||
meta: `${t('createGuide.review.instance')} ${instanceDisplayName}`,
|
||||
value: sourceDisplayName,
|
||||
},
|
||||
{
|
||||
icon: 'i-ri-price-tag-3-line',
|
||||
label: t('createGuide.review.release'),
|
||||
value: releaseDisplayName,
|
||||
},
|
||||
{
|
||||
icon: 'i-ri-cloud-line',
|
||||
label: t('createGuide.review.environment'),
|
||||
value: environmentDisplayName,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-5 rounded-xl border border-components-panel-border bg-components-panel-bg p-4 sm:p-5">
|
||||
<div className="flex flex-col">
|
||||
{routeItems.map((item, index) => (
|
||||
<div key={item.label} className="flex min-w-0 gap-3">
|
||||
<div className="flex w-8 shrink-0 flex-col items-center">
|
||||
<span className="flex size-8 items-center justify-center rounded-lg border border-divider-subtle bg-background-default-subtle">
|
||||
<span className={cn('size-4 text-text-tertiary', item.icon)} aria-hidden="true" />
|
||||
</span>
|
||||
{index < routeItems.length - 1 && <span className="my-1 h-5 w-px bg-divider-subtle" aria-hidden="true" />}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 pb-3">
|
||||
<div className="system-2xs-medium-uppercase text-text-tertiary">{item.label}</div>
|
||||
<div className="system-sm-semibold break-words text-text-primary" title={item.value}>{item.value}</div>
|
||||
{item.meta && <div className="mt-0.5 system-xs-regular break-words text-text-tertiary" title={item.meta}>{item.meta}</div>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{t('createGuide.review.bindings')}</div>
|
||||
<div className="mt-2 flex flex-col gap-1.5">
|
||||
{bindingSlots.length === 0
|
||||
? (
|
||||
<div className="rounded-lg bg-background-default-subtle px-3 py-2 system-xs-regular text-text-tertiary">
|
||||
{t('createGuide.target.noBindingRequired')}
|
||||
</div>
|
||||
)
|
||||
: bindingSlots.map((slot) => {
|
||||
const slotKey = runtimeCredentialSlotKey(slot)
|
||||
const selectedValue = bindingSelections[slotKey] ?? ''
|
||||
const selectedCandidate = runtimeCredentialCandidateOptions(slot).find(candidate => candidate.value === selectedValue)
|
||||
return (
|
||||
<div key={slotKey} className="grid min-w-0 grid-cols-1 gap-1 rounded-lg bg-background-default-subtle px-3 py-2 sm:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] sm:gap-2">
|
||||
<span className="system-xs-medium break-words text-text-secondary">{slot.providerId || slotKey}</span>
|
||||
<span className="system-xs-regular break-words text-text-tertiary sm:text-right">{selectedCandidate?.label || '—'}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{t('createGuide.review.releaseNote')}</div>
|
||||
<div className="mt-1 line-clamp-3 system-xs-regular whitespace-pre-wrap text-text-secondary">{releaseDescription || '—'}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ReviewStep({
|
||||
preview,
|
||||
}: {
|
||||
preview: ReactNode
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
|
||||
return (
|
||||
<StepShell
|
||||
title={t('createGuide.review.title')}
|
||||
description={t('createGuide.review.description')}
|
||||
>
|
||||
{preview}
|
||||
</StepShell>
|
||||
)
|
||||
}
|
||||
@ -42,10 +42,10 @@ function SourceAppOption({ app, selected, onSelect }: {
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'group flex min-h-14 cursor-pointer items-center gap-3 border-b border-l-2 border-b-divider-subtle px-3 py-2 transition-colors first:rounded-t-lg last:rounded-b-lg last:border-b-0',
|
||||
'group flex min-h-14 cursor-pointer items-center gap-3 border-b border-b-divider-subtle px-3 py-2 transition-colors first:rounded-t-lg last:rounded-b-lg last:border-b-0',
|
||||
selected
|
||||
? 'border-l-state-accent-solid bg-state-accent-hover hover:bg-state-accent-hover'
|
||||
: 'border-l-transparent bg-background-default hover:bg-state-base-hover',
|
||||
? 'bg-state-accent-hover hover:bg-state-accent-hover'
|
||||
: 'bg-background-default hover:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<AppIcon
|
||||
|
||||
@ -4,6 +4,6 @@ import type {
|
||||
import type { RuntimeCredentialBindingSelections } from '../components/runtime-credential-bindings-utils'
|
||||
|
||||
export type GuideMethod = 'bindApp' | 'importDsl'
|
||||
export type GuideStep = 'method' | 'source' | 'release' | 'target' | 'review' | 'done'
|
||||
export type GuideStep = 'method' | 'source' | 'release' | 'target' | 'done'
|
||||
export type EnvironmentOption = Environment & { id: string }
|
||||
export type BindingSelections = RuntimeCredentialBindingSelections
|
||||
|
||||
@ -102,7 +102,7 @@ export function useCreateDeploymentGuide() {
|
||||
const effectiveSelectedApp = selectedApp ?? sourceApps[0]
|
||||
const hasDslContent = Boolean(dslContent.trim())
|
||||
const encodedDslContent = hasDslContent ? encodeUtf8Base64(dslContent) : ''
|
||||
const shouldResolveDeploymentTarget = step === 'target' || step === 'review'
|
||||
const shouldResolveDeploymentTarget = step === 'target'
|
||||
const shouldLoadSourceDeploymentTarget = method === 'bindApp' && Boolean(effectiveSelectedApp?.id) && shouldResolveDeploymentTarget
|
||||
const shouldLoadDslDeploymentTarget = method === 'importDsl' && hasDslContent && shouldResolveDeploymentTarget
|
||||
const shouldLoadDeploymentTarget = shouldLoadSourceDeploymentTarget || shouldLoadDslDeploymentTarget
|
||||
@ -234,17 +234,6 @@ export function useCreateDeploymentGuide() {
|
||||
&& requiredBindingsReady,
|
||||
)
|
||||
}
|
||||
if (step === 'review') {
|
||||
return Boolean(
|
||||
selectedEnvironment?.id
|
||||
&& shouldLoadDeploymentTarget
|
||||
&& !isEnvironmentLoading
|
||||
&& !deployableEnvironmentsQuery.isError
|
||||
&& !isBindingLoading
|
||||
&& !deploymentOptionsQuery.isError
|
||||
&& requiredBindingsReady,
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -255,8 +244,6 @@ export function useCreateDeploymentGuide() {
|
||||
setStep('source')
|
||||
else if (step === 'target')
|
||||
setStep('release')
|
||||
else if (step === 'review')
|
||||
setStep('target')
|
||||
}
|
||||
|
||||
async function createReleaseArtifactsAndContinue() {
|
||||
@ -344,10 +331,6 @@ export function useCreateDeploymentGuide() {
|
||||
return
|
||||
}
|
||||
if (step === 'target') {
|
||||
setStep('review')
|
||||
return
|
||||
}
|
||||
if (step === 'review') {
|
||||
void handleDeploy()
|
||||
}
|
||||
}
|
||||
@ -399,15 +382,6 @@ export function useCreateDeploymentGuide() {
|
||||
stage: step === 'release' ? 'release' as const : 'source' as const,
|
||||
},
|
||||
deployedEnvironmentName,
|
||||
deploymentPreviewProps: {
|
||||
bindingSelections,
|
||||
bindingSlots,
|
||||
instanceName: displayedInstanceName,
|
||||
releaseDescription: displayedReleaseDescription,
|
||||
releaseName: displayedReleaseName,
|
||||
sourceName,
|
||||
targetEnvironmentName: selectedTargetEnvironmentName,
|
||||
},
|
||||
handleBack,
|
||||
handlePrimaryAction,
|
||||
isDeploying,
|
||||
|
||||
Reference in New Issue
Block a user