Refine deployment creation flow and restore method cards

This commit is contained in:
Stephen Zhou
2026-05-27 22:46:29 +08:00
parent cee6d5eeb9
commit 082b223ab1
11 changed files with 62 additions and 151 deletions

View File

@ -33,6 +33,7 @@ type RuntimeCredentialBindingsPanelProps = {
noCredentialCandidatesLabel: string
selectCredentialLabel: string
missingRequiredLabel: string
bindingCountLabel?: string
onChange: (slotKey: string, value: string) => void
className?: string
listClassName?: string
@ -66,7 +67,7 @@ function RuntimeCredentialSelect({
<SelectTrigger
aria-label={ariaLabel}
className={cn(
'h-8 min-w-0 border border-components-input-border-active px-2 text-left system-sm-medium',
'h-8 min-w-0 border border-divider-subtle px-2 text-left system-sm-medium hover:border-components-input-border-hover focus:border-components-input-border-active',
!selectedOption && 'text-text-quaternary',
)}
>
@ -94,6 +95,7 @@ export function RuntimeCredentialBindingsPanel({
noCredentialCandidatesLabel,
selectCredentialLabel,
missingRequiredLabel,
bindingCountLabel,
onChange,
className,
listClassName,
@ -107,7 +109,7 @@ export function RuntimeCredentialBindingsPanel({
<div className="system-xs-medium-uppercase text-text-tertiary">{title}</div>
{slots.length > 0 && (
<span className="shrink-0 rounded-md bg-background-default px-1.5 py-0.5 system-2xs-medium text-text-quaternary">
{slots.length}
{bindingCountLabel ?? slots.length}
</span>
)}
</div>

View File

@ -1,35 +0,0 @@
'use client'
import { useTranslation } from 'react-i18next'
import Link from '@/next/link'
import { StepShell } from './layout'
export function DoneStep({ environmentName }: {
environmentName: string
}) {
const { t } = useTranslation('deployments')
return (
<StepShell title={t('createGuide.done.title')} description={t('createGuide.done.description', { environment: environmentName })}>
<div className="flex flex-col gap-4 rounded-lg bg-background-default-subtle p-4">
<div className="flex items-center gap-3">
<span className="flex size-10 items-center justify-center rounded-full bg-util-colors-green-green-600 text-text-primary-on-surface">
<span className="i-ri-check-line size-5" aria-hidden="true" />
</span>
<div className="flex min-w-0 flex-col gap-1">
<div className="system-md-semibold text-text-primary">{t('createGuide.done.ready')}</div>
<div className="system-sm-regular text-text-tertiary">{t('createGuide.done.next')}</div>
</div>
</div>
<div className="flex justify-end">
<Link
href="/deployments"
className="inline-flex h-8 items-center rounded-lg bg-primary-600 px-3 system-sm-medium text-text-primary-on-surface hover:bg-primary-700"
>
{t('createGuide.done.backToList')}
</Link>
</div>
</div>
</StepShell>
)
}

View File

@ -2,7 +2,6 @@
import { useTranslation } from 'react-i18next'
import Link from '@/next/link'
import { DoneStep } from './done-step'
import { GuideActions, GuideCard, GuideFrame } from './layout'
import { CreationSections } from './source-release-sections'
import { TargetReviewSections } from './target-step'
@ -13,11 +12,9 @@ export function CreateDeploymentGuide() {
const {
canContinue,
creationSectionsProps,
deployedEnvironmentName,
handleBack,
handlePrimaryAction,
isDeploying,
selectedTargetEnvironmentName,
showTargetConfiguration,
step,
targetReviewSectionsProps,
@ -25,25 +22,21 @@ export function CreateDeploymentGuide() {
const guideContent = (
<>
{step === 'done'
{showTargetConfiguration
? (
<DoneStep environmentName={deployedEnvironmentName || selectedTargetEnvironmentName} />
<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} />
)}
</>
)
return (
<div className="fixed inset-0 z-50 bg-background-overlay-backdrop p-4 backdrop-blur-[6px]">
<div className="h-full w-full overflow-hidden rounded-2xl border border-effects-highlight bg-background-default-subtle">
<div className="mx-auto h-full w-full max-w-[1120px] overflow-hidden rounded-2xl border border-effects-highlight bg-background-default-subtle">
<main className="relative flex h-full min-w-0 grow flex-col overflow-hidden">
<Link
href="/deployments"

View File

@ -44,11 +44,13 @@ function GuideProgress({ activeStep }: {
activeStep: GuideStep
}) {
const { t } = useTranslation('deployments')
const activeIndex = GUIDE_PROGRESS_STEPS.indexOf(activeStep)
return (
<ol className="grid grid-cols-2 gap-1.5 sm:grid-cols-4">
{GUIDE_PROGRESS_STEPS.map((step) => {
<ol className="grid grid-cols-3 gap-1.5">
{GUIDE_PROGRESS_STEPS.map((step, index) => {
const isActive = step === activeStep
const isComplete = index < activeIndex
const label = t(`createGuide.steps.${step}`)
return (
@ -57,20 +59,26 @@ function GuideProgress({ activeStep }: {
aria-current={isActive ? 'step' : undefined}
title={label}
className={cn(
'flex min-w-0 items-center gap-2 px-2 py-1.5 system-xs-medium',
isActive ? 'text-text-primary' : 'text-text-quaternary',
'flex min-w-0 items-start gap-1.5 px-1 py-1.5 system-xs-medium sm:items-center sm:gap-2 sm:px-2',
isActive
? 'text-text-primary'
: isComplete
? 'text-text-secondary'
: 'text-text-quaternary',
)}
>
<span
aria-hidden
className={cn(
'size-2 shrink-0 rounded-full border-[1.5px]',
'mt-1 size-2 shrink-0 rounded-full border-[1.5px] sm:mt-0',
isActive
? 'border-text-primary bg-text-primary'
: 'border-text-quaternary bg-transparent',
: isComplete
? 'border-text-secondary bg-text-secondary'
: 'border-text-quaternary bg-transparent',
)}
/>
<span className="truncate">{label}</span>
<span className="line-clamp-2 min-w-0 leading-4">{label}</span>
</li>
)
})}
@ -173,18 +181,14 @@ export function GuideFrame({ activeStep, children }: {
<div className="h-5 sm:h-8 lg:h-12" />
<div className="flex min-w-0 items-start justify-between gap-6 pt-1 pb-4">
<h1 className="title-2xl-semi-bold text-text-primary">{t('createGuide.title')}</h1>
{activeStep !== 'done' && (
<div className="hidden w-[148px] shrink-0 min-[1120px]:block">
<GuideProgressSummary activeStep={activeStep} />
</div>
)}
<div className="hidden w-[148px] shrink-0 min-[1120px]:block">
<GuideProgressSummary activeStep={activeStep} />
</div>
</div>
<GuideStepIntro activeStep={activeStep} />
{activeStep !== 'done' && (
<div className="mb-6 lg:hidden">
<GuideProgress activeStep={activeStep} />
</div>
)}
<div className="mb-6 lg:hidden">
<GuideProgress activeStep={activeStep} />
</div>
{children}
</section>
</div>
@ -212,9 +216,6 @@ export function GuideActions({
? t('createGuide.actions.creating')
: t('createGuide.actions.next')
if (step === 'method' || step === 'done')
return null
return (
<div className="sticky bottom-0 z-10 -mx-5 mt-auto flex items-center justify-end gap-2 border-t border-divider-subtle bg-background-default-subtle/95 px-5 py-4 backdrop-blur-sm sm:-mx-8 sm:px-8 lg:-mx-10 lg:px-10">
{(step === 'release' || step === 'target') && (

View File

@ -50,14 +50,18 @@ export function ReleaseStep({
/>
</div>
<div className="flex flex-col gap-2">
<label className="system-xs-medium-uppercase text-text-tertiary" htmlFor="create-guide-instance-description">
{t('createGuide.release.instanceDescription')}
</label>
<div className="flex items-center gap-1.5">
<label className="system-xs-medium-uppercase text-text-tertiary" htmlFor="create-guide-instance-description">
{t('createGuide.release.instanceDescription')}
</label>
<span className="system-xs-regular text-text-quaternary">{t('versions.optional')}</span>
</div>
<textarea
id="create-guide-instance-description"
value={instanceDescription}
onChange={event => onInstanceDescriptionChange(event.target.value)}
className="min-h-20 w-full resize-none appearance-none rounded-md border border-transparent bg-components-input-bg-normal p-2 px-3 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
placeholder={t('createGuide.release.instanceDescriptionPlaceholder')}
className="min-h-16 w-full resize-none appearance-none rounded-md border border-transparent bg-components-input-bg-normal p-2 px-3 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
/>
</div>
<div className="flex flex-col gap-2">
@ -74,14 +78,18 @@ export function ReleaseStep({
/>
</div>
<div className="flex flex-col gap-2">
<label className="system-xs-medium-uppercase text-text-tertiary" htmlFor="create-guide-release-description">
{t('createGuide.release.releaseDescription')}
</label>
<div className="flex items-center gap-1.5">
<label className="system-xs-medium-uppercase text-text-tertiary" htmlFor="create-guide-release-description">
{t('createGuide.release.releaseDescription')}
</label>
<span className="system-xs-regular text-text-quaternary">{t('versions.optional')}</span>
</div>
<textarea
id="create-guide-release-description"
value={releaseDescription}
onChange={event => onReleaseDescriptionChange(event.target.value)}
className="min-h-20 w-full resize-none appearance-none rounded-md border border-transparent bg-components-input-bg-normal p-2 px-3 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
placeholder={t('createGuide.release.releaseDescriptionPlaceholder')}
className="min-h-16 w-full resize-none appearance-none rounded-md border border-transparent bg-components-input-bg-normal p-2 px-3 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
/>
</div>
</div>

View File

@ -169,6 +169,7 @@ function TargetStep({
noCredentialCandidatesLabel={t('createGuide.target.noCredentialCandidates')}
selectCredentialLabel={t('createGuide.target.selectCredential')}
missingRequiredLabel={t('createGuide.target.missingRequiredBinding')}
bindingCountLabel={t('createGuide.target.bindingCount', { count: bindingSlots.length })}
onChange={onSelectBinding}
className="border-components-option-card-option-border bg-components-option-card-option-bg"
/>

View File

@ -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' | 'done'
export type GuideStep = 'source' | 'release' | 'target'
export type EnvironmentOption = Environment & { id: string }
export type BindingSelections = RuntimeCredentialBindingSelections

View File

@ -24,7 +24,6 @@ import {
selectedRuntimeCredentialSelections,
} from '../components/runtime-credential-bindings-utils'
import { SOURCE_APPS_PAGE_SIZE } from '../data'
import { environmentName } from '../environment'
import { createDeploymentIdempotencyKey } from '../idempotency'
type DslMetadata = {
@ -81,7 +80,6 @@ export function useCreateDeploymentGuide() {
const [releaseDescription, setReleaseDescription] = useState('')
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState('')
const [manualBindingSelections, setManualBindingSelections] = useState<BindingSelections>({})
const [deployedEnvironmentName, setDeployedEnvironmentName] = useState('')
const dslReadTokenRef = useRef(0)
const sourceAppsQuery = useInfiniteQuery({
@ -140,7 +138,6 @@ export function useCreateDeploymentGuide() {
: []
const effectiveSelectedEnvironmentId = selectedEnvironmentId || environments[0]?.id || ''
const selectedEnvironment = environments.find(env => env.id === effectiveSelectedEnvironmentId) ?? environments[0]
const selectedTargetEnvironmentName = selectedEnvironment ? environmentName(selectedEnvironment) : ''
const bindingSelections = selectedRuntimeCredentialSelections(bindingSlots, manualBindingSelections)
const requiredBindingsReady = bindingSlots.every(slot => !hasMissingRequiredRuntimeCredentialBinding(slot, bindingSelections[runtimeCredentialSlotKey(slot)]))
const isEnvironmentLoading = shouldLoadDeploymentTarget && (deployableEnvironmentsQuery.isLoading || (deployableEnvironmentsQuery.isFetching && !deployableEnvironmentsQuery.data))
@ -157,13 +154,8 @@ export function useCreateDeploymentGuide() {
const displayedReleaseDescription = releaseDescription.trim()
const showTargetConfiguration = Boolean(method && step === 'target')
function resetCreatedArtifacts() {
setDeployedEnvironmentName('')
}
function selectMethod(nextMethod: GuideMethod) {
setMethod(nextMethod)
resetCreatedArtifacts()
setSelectedEnvironmentId('')
setManualBindingSelections({})
}
@ -177,7 +169,6 @@ export function useCreateDeploymentGuide() {
setDslReadError(false)
setSelectedEnvironmentId('')
setManualBindingSelections({})
resetCreatedArtifacts()
if (!file) {
setIsReadingDsl(false)
@ -210,8 +201,6 @@ export function useCreateDeploymentGuide() {
}
function canContinueCurrentStep() {
if (step === 'method')
return Boolean(method)
if (step === 'source')
return Boolean(method && (method === 'importDsl' ? hasDslContent && !isReadingDsl && !dslReadError : effectiveSelectedApp?.id))
if (step === 'release') {
@ -254,7 +243,6 @@ export function useCreateDeploymentGuide() {
setSelectedEnvironmentId('')
setManualBindingSelections({})
setDeployedEnvironmentName('')
setStep('target')
}
@ -310,8 +298,6 @@ export function useCreateDeploymentGuide() {
if (!appInstanceId)
throw new Error('Create initial deployment did not return an app instance.')
setSelectedEnvironmentId(selectedEnvironment.id)
setDeployedEnvironmentName(environmentName(selectedEnvironment))
router.push(`/deployments/${appInstanceId}/overview`)
}
catch {
@ -323,10 +309,6 @@ export function useCreateDeploymentGuide() {
if (!canContinueCurrentStep())
return
if (step === 'method') {
setStep('source')
return
}
if (step === 'source') {
if (method === 'bindApp' && effectiveSelectedApp)
setSelectedApp(effectiveSelectedApp)
@ -358,29 +340,24 @@ export function useCreateDeploymentGuide() {
onDslFileChange: handleDslFileChange,
onInstanceDescriptionChange: (value: string) => {
setInstanceDescription(value)
resetCreatedArtifacts()
setStep('release')
},
onInstanceNameChange: (value: string) => {
setInstanceName(value)
resetCreatedArtifacts()
setStep('release')
},
onReleaseDescriptionChange: (value: string) => {
setReleaseDescription(value)
resetCreatedArtifacts()
setStep('release')
},
onReleaseNameChange: (value: string) => {
setReleaseName(value)
resetCreatedArtifacts()
setStep('release')
},
onSearchTextChange: setSourceSearchText,
onSelectMethod: handleSelectMethod,
onSelectSourceApp: (app: App) => {
setSelectedApp(app)
resetCreatedArtifacts()
},
releaseDescription,
releaseName,
@ -391,11 +368,9 @@ export function useCreateDeploymentGuide() {
sourceSearchText,
stage: step === 'release' ? 'release' as const : 'source' as const,
},
deployedEnvironmentName,
handleBack,
handlePrimaryAction,
isDeploying,
selectedTargetEnvironmentName,
showTargetConfiguration,
step,
targetReviewSectionsProps: {

View File

@ -114,7 +114,7 @@ function DeleteInstanceButton({
</div>
<dl className="mt-2 grid gap-1.5 system-sm-regular">
<div className="flex min-w-0 justify-between gap-3">
<dt className="shrink-0 text-util-colors-red-red-600">{t('createGuide.review.instance')}</dt>
<dt className="shrink-0 text-util-colors-red-red-600">{t('settings.deleteImpactInstance')}</dt>
<dd className="min-w-0 text-right break-words text-util-colors-red-red-700">{appName}</dd>
</div>
<div className="flex min-w-0 justify-between gap-3">

View File

@ -135,13 +135,7 @@
"createGuide.actions.deploy": "Deploy",
"createGuide.actions.deploying": "Deploying...",
"createGuide.actions.next": "Next",
"createGuide.actions.review": "Review",
"createGuide.description": "Create a usable deployment through source, release, bindings, and environment selection.",
"createGuide.done.backToList": "Back to deployments",
"createGuide.done.description": "Deployment has started for {{environment}}.",
"createGuide.done.next": "You can review status, access, and release history from the deployments list.",
"createGuide.done.ready": "The deployment request has been submitted.",
"createGuide.done.title": "Deployment started",
"createGuide.dsl.defaultAppName": "Imported DSL app",
"createGuide.dsl.description": "Upload a DSL package to create the app instance, first release, and deployment.",
"createGuide.dsl.dropDescription": "Upload a YAML DSL package. Deployment options are resolved from this file before deploy.",
@ -161,38 +155,26 @@
"createGuide.release.defaultName": "initial release",
"createGuide.release.description": "Fill in the app instance details and the first release details.",
"createGuide.release.instanceDescription": "Instance description",
"createGuide.release.instanceDescriptionPlaceholder": "Describe what this instance is used for",
"createGuide.release.instanceName": "Instance name",
"createGuide.release.releaseDescription": "Release description",
"createGuide.release.releaseDescriptionPlaceholder": "Describe this release",
"createGuide.release.releaseName": "Release name",
"createGuide.release.releaseNote": "Release description",
"createGuide.release.title": "Create instance and release",
"createGuide.review.bindings": "Runtime bindings",
"createGuide.review.description": "Confirm the source, release, credentials, and target environment before deployment.",
"createGuide.review.environment": "Environment",
"createGuide.review.instance": "Instance",
"createGuide.review.plan.createInstance": "Use the created app instance",
"createGuide.review.plan.createRelease": "Use release {{release}}",
"createGuide.review.plan.deployTo": "Deploy to {{environment}}",
"createGuide.review.plan.resolveBindings": "Resolve runtime credentials",
"createGuide.review.planTitle": "Execution plan",
"createGuide.review.release": "Release",
"createGuide.review.releaseNote": "Release description",
"createGuide.review.source": "Source",
"createGuide.review.summary": "Deployment summary",
"createGuide.review.title": "Review deployment",
"createGuide.source.clearSearch": "Clear app search",
"createGuide.source.description": "Choose the Studio app that will become the first release source.",
"createGuide.source.empty": "No Studio apps found.",
"createGuide.source.searchPlaceholder": "Search apps",
"createGuide.source.sourceApp": "Source app",
"createGuide.source.title": "Choose source",
"createGuide.steps.done": "Done",
"createGuide.steps.method": "Select a method",
"createGuide.steps.release": "Create instance and release",
"createGuide.steps.review": "Review deployment",
"createGuide.steps.source": "Choose source",
"createGuide.steps.target": "Deploy target",
"createGuide.target.bindingHint": "Pick the credentials that will be used when this release runs.",
"createGuide.target.bindingCount_one": "{{count}} binding",
"createGuide.target.bindingCount_other": "{{count}} bindings",
"createGuide.target.bindings": "Runtime bindings",
"createGuide.target.deferredBindingHint": "Runtime credentials will be resolved from the real deployment plan during the final deploy action.",
"createGuide.target.deferredEnvironmentHint": "The name is matched against the real environments after the app instance and release are created.",
@ -437,6 +419,7 @@
"settings.deleteConfirmTitle": "Delete app instance",
"settings.deleteFailed": "Failed to delete instance.",
"settings.deleteImpact": "Impact",
"settings.deleteImpactInstance": "Instance",
"settings.deleteImpactTitle": "Affected instance",
"settings.deleteImpactValue": "The instance is removed from the deployment list.",
"settings.deleted": "Instance deleted",

View File

@ -135,13 +135,7 @@
"createGuide.actions.deploy": "部署",
"createGuide.actions.deploying": "部署中…",
"createGuide.actions.next": "下一步",
"createGuide.actions.review": "确认",
"createGuide.description": "通过源应用、发布版本、运行时绑定和环境选择创建可用部署。",
"createGuide.done.backToList": "返回部署列表",
"createGuide.done.description": "已开始部署到 {{environment}}。",
"createGuide.done.next": "你可以在部署列表中查看状态、访问配置和发布历史。",
"createGuide.done.ready": "部署请求已提交。",
"createGuide.done.title": "部署已开始",
"createGuide.dsl.defaultAppName": "导入的 DSL 应用",
"createGuide.dsl.description": "上传 DSL 包来创建应用实例、第一个发布版本和部署。",
"createGuide.dsl.dropDescription": "上传 YAML DSL 包。部署前会根据该文件解析部署选项。",
@ -161,38 +155,26 @@
"createGuide.release.defaultName": "初始发布版本",
"createGuide.release.description": "填写应用实例信息和第一个发布版本的信息。",
"createGuide.release.instanceDescription": "实例描述",
"createGuide.release.instanceDescriptionPlaceholder": "描述该实例的用途",
"createGuide.release.instanceName": "实例名称",
"createGuide.release.releaseDescription": "发布描述",
"createGuide.release.releaseDescriptionPlaceholder": "描述这个发布版本",
"createGuide.release.releaseName": "发布名称",
"createGuide.release.releaseNote": "发布描述",
"createGuide.release.title": "创建实例和发布版本",
"createGuide.review.bindings": "运行时绑定",
"createGuide.review.description": "部署前确认源应用、发布版本、凭据和目标环境。",
"createGuide.review.environment": "环境",
"createGuide.review.instance": "实例",
"createGuide.review.plan.createInstance": "使用已创建的应用实例",
"createGuide.review.plan.createRelease": "使用发布版本 {{release}}",
"createGuide.review.plan.deployTo": "部署到 {{environment}}",
"createGuide.review.plan.resolveBindings": "解析运行时凭据",
"createGuide.review.planTitle": "执行计划",
"createGuide.review.release": "发布版本",
"createGuide.review.releaseNote": "发布描述",
"createGuide.review.source": "源应用",
"createGuide.review.summary": "部署摘要",
"createGuide.review.title": "确认部署",
"createGuide.source.clearSearch": "清空应用搜索",
"createGuide.source.description": "选择将作为第一个发布来源的 Studio 应用。",
"createGuide.source.empty": "未找到 Studio 应用。",
"createGuide.source.searchPlaceholder": "搜索应用",
"createGuide.source.sourceApp": "源应用",
"createGuide.source.title": "选择来源",
"createGuide.steps.done": "完成",
"createGuide.steps.method": "选择方式",
"createGuide.steps.release": "创建实例和发布版本",
"createGuide.steps.review": "确认部署",
"createGuide.steps.source": "选择来源",
"createGuide.steps.target": "部署目标",
"createGuide.target.bindingHint": "选择该发布版本运行时使用的凭据。",
"createGuide.target.bindingCount_one": "{{count}} 项",
"createGuide.target.bindingCount_other": "{{count}} 项",
"createGuide.target.bindings": "运行时绑定",
"createGuide.target.deferredBindingHint": "运行时凭据会在最终部署时根据真实部署计划解析。",
"createGuide.target.deferredEnvironmentHint": "创建应用实例和发布版本后,会根据真实环境进行匹配。",
@ -437,6 +419,7 @@
"settings.deleteConfirmTitle": "删除应用实例",
"settings.deleteFailed": "删除实例失败。",
"settings.deleteImpact": "影响",
"settings.deleteImpactInstance": "实例",
"settings.deleteImpactTitle": "受影响的实例",
"settings.deleteImpactValue": "该实例会从部署列表中移除。",
"settings.deleted": "实例已删除",