mirror of
https://github.com/langgenius/dify.git
synced 2026-05-11 23:18:39 +08:00
promote and rollback
This commit is contained in:
parent
c053549ece
commit
89a05c0665
113
web/features/deployments/__tests__/utils.spec.ts
Normal file
113
web/features/deployments/__tests__/utils.spec.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import type { ConsoleRelease, ReleaseRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { releaseDeploymentAction } from '../utils'
|
||||
|
||||
function release(overrides: ReleaseRow): ReleaseRow {
|
||||
return overrides
|
||||
}
|
||||
|
||||
function currentRelease(overrides: ConsoleRelease): ConsoleRelease {
|
||||
return overrides
|
||||
}
|
||||
|
||||
describe('releaseDeploymentAction', () => {
|
||||
describe('deploy actions', () => {
|
||||
it('should return deploy when the target environment has no current release', () => {
|
||||
// Arrange
|
||||
const releases = [
|
||||
release({ id: 'release-2', createdAt: '2026-01-02T00:00:00Z' }),
|
||||
]
|
||||
|
||||
// Act
|
||||
const action = releaseDeploymentAction({
|
||||
targetRelease: releases[0],
|
||||
releaseRows: releases,
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(action).toBe('deploy')
|
||||
})
|
||||
|
||||
it('should return deployExistingRelease when a preset release is deployed to a new environment', () => {
|
||||
// Arrange
|
||||
const releases = [
|
||||
release({ id: 'release-2', createdAt: '2026-01-02T00:00:00Z' }),
|
||||
]
|
||||
|
||||
// Act
|
||||
const action = releaseDeploymentAction({
|
||||
targetRelease: releases[0],
|
||||
releaseRows: releases,
|
||||
isExistingRelease: true,
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(action).toBe('deployExistingRelease')
|
||||
})
|
||||
})
|
||||
|
||||
describe('release direction', () => {
|
||||
it('should return promote when the target release is newer than the current release', () => {
|
||||
// Arrange
|
||||
const releases = [
|
||||
release({ id: 'release-3', createdAt: '2026-01-03T00:00:00Z' }),
|
||||
release({ id: 'release-2', createdAt: '2026-01-02T00:00:00Z' }),
|
||||
]
|
||||
|
||||
// Act
|
||||
const action = releaseDeploymentAction({
|
||||
targetRelease: releases[0],
|
||||
currentRelease: currentRelease({ id: 'release-2', createdAt: '2026-01-02T00:00:00Z' }),
|
||||
releaseRows: releases,
|
||||
isExistingRelease: true,
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(action).toBe('promote')
|
||||
})
|
||||
|
||||
it('should return rollback when the target release is older than the current release', () => {
|
||||
// Arrange
|
||||
const releases = [
|
||||
release({ id: 'release-3', createdAt: '2026-01-03T00:00:00Z' }),
|
||||
release({ id: 'release-2', createdAt: '2026-01-02T00:00:00Z' }),
|
||||
]
|
||||
|
||||
// Act
|
||||
const action = releaseDeploymentAction({
|
||||
targetRelease: releases[1],
|
||||
currentRelease: currentRelease({ id: 'release-3', createdAt: '2026-01-03T00:00:00Z' }),
|
||||
releaseRows: releases,
|
||||
isExistingRelease: true,
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(action).toBe('rollback')
|
||||
})
|
||||
|
||||
it('should fall back to release list order when release timestamps are unavailable', () => {
|
||||
// Arrange
|
||||
const releases = [
|
||||
release({ id: 'release-3' }),
|
||||
release({ id: 'release-2' }),
|
||||
release({ id: 'release-1' }),
|
||||
]
|
||||
|
||||
// Act
|
||||
const rollbackAction = releaseDeploymentAction({
|
||||
targetRelease: releases[2],
|
||||
currentRelease: currentRelease({ id: 'release-2' }),
|
||||
releaseRows: releases,
|
||||
})
|
||||
const promoteAction = releaseDeploymentAction({
|
||||
targetRelease: releases[0],
|
||||
currentRelease: currentRelease({ id: 'release-2' }),
|
||||
releaseRows: releases,
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(rollbackAction).toBe('rollback')
|
||||
expect(promoteAction).toBe('promote')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -13,7 +13,7 @@ import {
|
||||
deployDrawerOpenAtom,
|
||||
deployDrawerReleaseIdAtom,
|
||||
} from '../store'
|
||||
import { environmentOptionsFromOptionsReply } from '../utils'
|
||||
import { deployedRows, environmentOptionsFromOptionsReply } from '../utils'
|
||||
import { DeployForm } from './deploy-drawer/form'
|
||||
|
||||
export function DeployDrawer() {
|
||||
@ -38,9 +38,18 @@ export function DeployDrawer() {
|
||||
const { data: environmentOptionsReply } = useQuery(consoleQuery.enterprise.appDeploy.listDeploymentEnvironmentOptions.queryOptions({
|
||||
enabled: open,
|
||||
}))
|
||||
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
|
||||
input: drawerAppInstanceId
|
||||
? {
|
||||
params: { appInstanceId: drawerAppInstanceId },
|
||||
}
|
||||
: skipToken,
|
||||
enabled: open && Boolean(drawerAppInstanceId),
|
||||
}))
|
||||
|
||||
const environments = environmentOptionsFromOptionsReply(environmentOptionsReply)
|
||||
const releases = releaseHistory?.data?.filter(release => release.id) ?? []
|
||||
const deploymentRows = deployedRows(environmentDeployments?.data)
|
||||
const defaultReleaseId = releases[0]?.id
|
||||
const formKey = `${drawerAppInstanceId ?? 'none'}-${drawerEnvironmentId ?? 'any'}-${drawerReleaseId ?? 'new'}-${open ? '1' : '0'}`
|
||||
|
||||
@ -53,7 +62,7 @@ export function DeployDrawer() {
|
||||
<DialogCloseButton />
|
||||
{!drawerAppInstanceId
|
||||
? <div className="p-4 text-text-tertiary">{t('deployDrawer.notFound')}</div>
|
||||
: (!releaseHistory || !environmentOptionsReply)
|
||||
: (!releaseHistory || !environmentOptionsReply || !environmentDeployments)
|
||||
? (
|
||||
<div className="flex items-center gap-2 p-4 system-sm-regular text-text-tertiary">
|
||||
<span className="size-4 animate-spin rounded-full border-2 border-components-panel-border border-t-transparent" />
|
||||
@ -66,6 +75,7 @@ export function DeployDrawer() {
|
||||
appInstanceId={drawerAppInstanceId}
|
||||
environments={environments}
|
||||
releases={releases}
|
||||
deploymentRows={deploymentRows}
|
||||
defaultReleaseId={defaultReleaseId}
|
||||
lockedEnvId={drawerEnvironmentId}
|
||||
presetReleaseId={drawerReleaseId}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { DeploymentBindingOptionSlot, DeploymentRuntimeBinding, ReleaseRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { DeploymentBindingOptionSlot, DeploymentRuntimeBinding, ReleaseRow, RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { EnvironmentOption } from '@/features/deployments/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
@ -12,9 +12,12 @@ import { useTranslation } from 'react-i18next'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { closeDeployDrawerAtom } from '../../store'
|
||||
import {
|
||||
activeRelease,
|
||||
environmentId,
|
||||
environmentMode,
|
||||
environmentName,
|
||||
releaseCommit,
|
||||
releaseDeploymentAction,
|
||||
releaseLabel,
|
||||
} from '../../utils'
|
||||
import {
|
||||
@ -27,6 +30,7 @@ type DeployFormProps = {
|
||||
appInstanceId: string
|
||||
environments: EnvironmentOption[]
|
||||
releases: ReleaseRow[]
|
||||
deploymentRows: RuntimeInstanceRow[]
|
||||
defaultReleaseId?: string
|
||||
lockedEnvId?: string
|
||||
presetReleaseId?: string
|
||||
@ -204,6 +208,7 @@ export function DeployForm({
|
||||
appInstanceId,
|
||||
environments,
|
||||
releases,
|
||||
deploymentRows,
|
||||
defaultReleaseId,
|
||||
lockedEnvId,
|
||||
presetReleaseId,
|
||||
@ -213,7 +218,7 @@ export function DeployForm({
|
||||
const startDeploy = useMutation(consoleQuery.enterprise.appDeploy.createDeployment.mutationOptions())
|
||||
const presetRelease = presetReleaseId ? releases.find(r => r.id === presetReleaseId) : undefined
|
||||
const displayedRelease: ReleaseRow | undefined = presetRelease ?? (presetReleaseId ? { id: presetReleaseId } : undefined)
|
||||
const isPromote = Boolean(presetReleaseId)
|
||||
const isExistingRelease = Boolean(presetReleaseId)
|
||||
|
||||
const [selectedEnvId, setSelectedEnvId] = useState<string>(
|
||||
() => lockedEnvId ?? environments.find(env => !env.disabled)?.id ?? environments[0]?.id ?? '',
|
||||
@ -225,6 +230,14 @@ export function DeployForm({
|
||||
)
|
||||
const selectedRelease = releases.find(release => release.id === selectedReleaseId)
|
||||
const targetReleaseId = displayedRelease?.id ?? selectedRelease?.id ?? selectedReleaseId
|
||||
const targetRelease = displayedRelease ?? selectedRelease ?? (targetReleaseId ? { id: targetReleaseId } : undefined)
|
||||
const selectedDeploymentRow = deploymentRows.find(row => environmentId(row.environment) === selectedEnvironmentId)
|
||||
const action = releaseDeploymentAction({
|
||||
targetRelease,
|
||||
currentRelease: activeRelease(selectedDeploymentRow),
|
||||
releaseRows: releases,
|
||||
isExistingRelease,
|
||||
})
|
||||
const bindingOptions = useQuery(consoleQuery.enterprise.appDeploy.listDeploymentBindingOptions.queryOptions({
|
||||
input: appInstanceId && targetReleaseId
|
||||
? {
|
||||
@ -254,11 +267,29 @@ export function DeployForm({
|
||||
)
|
||||
|
||||
const lockedEnv = lockedEnvId ? environments.find(e => e.id === lockedEnvId) : undefined
|
||||
const actionTitle = action === 'rollback'
|
||||
? t('deployDrawer.rollbackTitle')
|
||||
: action === 'promote'
|
||||
? t('deployDrawer.promoteTitle')
|
||||
: action === 'deployExistingRelease'
|
||||
? t('deployDrawer.deployExistingReleaseTitle')
|
||||
: t('deployDrawer.title')
|
||||
const actionDescription = action === 'rollback'
|
||||
? t('deployDrawer.rollbackDescription')
|
||||
: action === 'promote'
|
||||
? t('deployDrawer.promoteDescription')
|
||||
: action === 'deployExistingRelease'
|
||||
? t('deployDrawer.deployExistingReleaseDescription')
|
||||
: t('deployDrawer.description')
|
||||
const submitLabel = isSubmitting
|
||||
? t('deployDrawer.deploying')
|
||||
: isPromote
|
||||
? t('deployDrawer.promote')
|
||||
: t('deployDrawer.deploy')
|
||||
: action === 'rollback'
|
||||
? t('deployDrawer.rollback')
|
||||
: action === 'promote'
|
||||
? t('deployDrawer.promote')
|
||||
: action === 'deployExistingRelease'
|
||||
? t('deployDrawer.deployExistingRelease')
|
||||
: t('deployDrawer.deploy')
|
||||
|
||||
const handleDeploy = () => {
|
||||
if (!canDeploy || !targetReleaseId)
|
||||
@ -288,15 +319,15 @@ export function DeployForm({
|
||||
<div className="flex flex-col gap-5">
|
||||
<div>
|
||||
<DialogTitle className="title-xl-semi-bold text-text-primary">
|
||||
{isPromote ? t('deployDrawer.promoteTitle') : t('deployDrawer.title')}
|
||||
{actionTitle}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="mt-1 system-sm-regular text-text-tertiary">
|
||||
{isPromote ? t('deployDrawer.promoteDescription') : t('deployDrawer.description')}
|
||||
{actionDescription}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
|
||||
<Field label={t('deployDrawer.releaseLabel')}>
|
||||
{isPromote && displayedRelease
|
||||
{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">
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ReleaseRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -20,11 +21,13 @@ import {
|
||||
environmentId,
|
||||
environmentName,
|
||||
environmentOptionsFromOptionsReply,
|
||||
releaseDeploymentAction,
|
||||
} from '../../utils'
|
||||
|
||||
export function DeployReleaseMenu({ appInstanceId, releaseId }: {
|
||||
export function DeployReleaseMenu({ appInstanceId, releaseId, releaseRows }: {
|
||||
appInstanceId: string
|
||||
releaseId: string
|
||||
releaseRows: ReleaseRow[]
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const openDeployDrawer = useSetAtom(openDeployDrawerAtom)
|
||||
@ -42,6 +45,7 @@ export function DeployReleaseMenu({ appInstanceId, releaseId }: {
|
||||
const environmentOptions = environmentOptionsFromOptionsReply(environmentOptionsReply)
|
||||
const environments = environmentOptions.filter(env => env.id)
|
||||
const deploymentRows = deployedRows(environmentDeployments?.data)
|
||||
const targetRelease = releaseRows.find(release => release.id === releaseId) ?? { id: releaseId }
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false} open={open} onOpenChange={setOpen}>
|
||||
@ -63,6 +67,12 @@ export function DeployReleaseMenu({ appInstanceId, releaseId }: {
|
||||
const isCurrent = activeRelease(row)?.id === releaseId
|
||||
const isEnvironmentDeploying = row ? deploymentStatus(row) === 'deploying' : false
|
||||
const disabled = Boolean(env.disabled || isCurrent || isEnvironmentDeploying)
|
||||
const action = releaseDeploymentAction({
|
||||
targetRelease,
|
||||
currentRelease: activeRelease(row),
|
||||
releaseRows,
|
||||
isExistingRelease: true,
|
||||
})
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={envId}
|
||||
@ -81,7 +91,7 @@ export function DeployReleaseMenu({ appInstanceId, releaseId }: {
|
||||
: isCurrent
|
||||
? t('versions.currentOn', { name: environmentName(env) })
|
||||
: row
|
||||
? t('versions.promoteTo', { name: environmentName(env) })
|
||||
? t(action === 'rollback' ? 'versions.rollbackTo' : 'versions.promoteTo', { name: environmentName(env) })
|
||||
: t('versions.deployTo', { name: environmentName(env) })}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@ -63,7 +63,7 @@ export function ReleaseHistoryTable({ appInstanceId, releaseRows, deploymentRows
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 justify-end gap-1">
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appInstanceId} />
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appInstanceId} releaseRows={releaseRows} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
@ -114,7 +114,7 @@ export function ReleaseHistoryTable({ appInstanceId, releaseRows, deploymentRows
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-1">
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appInstanceId} />
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appInstanceId} releaseRows={releaseRows} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -14,6 +14,7 @@ import { PUBLIC_API_PREFIX } from '@/config'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
export type DeploymentUiStatus = 'ready' | 'deploying' | 'deploy_failed'
|
||||
export type ReleaseDeploymentAction = 'deploy' | 'deployExistingRelease' | 'promote' | 'rollback'
|
||||
|
||||
const appModeValues = new Set<string>(Object.values(AppModeEnum))
|
||||
|
||||
@ -68,6 +69,72 @@ export function releaseCommit(release?: ConsoleRelease | ReleaseRow) {
|
||||
return release && 'shortCommitId' in release ? release.shortCommitId || '—' : '—'
|
||||
}
|
||||
|
||||
function releaseCreatedAt(release?: ConsoleRelease | ReleaseRow) {
|
||||
const value = release?.createdAt
|
||||
if (!value)
|
||||
return undefined
|
||||
|
||||
const time = Date.parse(value)
|
||||
return Number.isFinite(time) ? time : undefined
|
||||
}
|
||||
|
||||
function releaseById(releaseRows: ReleaseRow[], releaseId?: string) {
|
||||
return releaseRows.find(release => release.id === releaseId)
|
||||
}
|
||||
|
||||
function releaseOrderIndex(releaseRows: ReleaseRow[], releaseId?: string) {
|
||||
return releaseRows.findIndex(release => release.id === releaseId)
|
||||
}
|
||||
|
||||
function compareReleaseOrder(targetRelease: ConsoleRelease | ReleaseRow | undefined, currentRelease: ConsoleRelease, releaseRows: ReleaseRow[]) {
|
||||
if (!targetRelease?.id || !currentRelease.id)
|
||||
return undefined
|
||||
if (targetRelease.id === currentRelease.id)
|
||||
return 0
|
||||
|
||||
const normalizedTargetRelease = releaseById(releaseRows, targetRelease.id) ?? targetRelease
|
||||
const normalizedCurrentRelease = releaseById(releaseRows, currentRelease.id) ?? currentRelease
|
||||
const targetCreatedAt = releaseCreatedAt(normalizedTargetRelease)
|
||||
const currentCreatedAt = releaseCreatedAt(normalizedCurrentRelease)
|
||||
|
||||
if (targetCreatedAt !== undefined && currentCreatedAt !== undefined && targetCreatedAt !== currentCreatedAt)
|
||||
return targetCreatedAt > currentCreatedAt ? 1 : -1
|
||||
|
||||
const targetIndex = releaseOrderIndex(releaseRows, targetRelease.id)
|
||||
const currentIndex = releaseOrderIndex(releaseRows, currentRelease.id)
|
||||
if (targetIndex >= 0 && currentIndex >= 0 && targetIndex !== currentIndex)
|
||||
return targetIndex < currentIndex ? 1 : -1
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function releaseDeploymentAction({
|
||||
targetRelease,
|
||||
currentRelease,
|
||||
releaseRows,
|
||||
isExistingRelease,
|
||||
}: {
|
||||
targetRelease?: ConsoleRelease | ReleaseRow
|
||||
currentRelease?: ConsoleRelease
|
||||
releaseRows: ReleaseRow[]
|
||||
isExistingRelease?: boolean
|
||||
}): ReleaseDeploymentAction {
|
||||
if (!currentRelease?.id)
|
||||
return isExistingRelease ? 'deployExistingRelease' : 'deploy'
|
||||
|
||||
const order = compareReleaseOrder(targetRelease, currentRelease, releaseRows)
|
||||
if (order === -1)
|
||||
return 'rollback'
|
||||
if (order === 1)
|
||||
return 'promote'
|
||||
|
||||
return targetRelease?.id && targetRelease.id !== currentRelease.id
|
||||
? 'promote'
|
||||
: isExistingRelease
|
||||
? 'deployExistingRelease'
|
||||
: 'deploy'
|
||||
}
|
||||
|
||||
export function runtimeBindingSummary(binding?: ReleaseRuntimeBinding) {
|
||||
return binding?.label || binding?.displayValue || binding?.kind || '—'
|
||||
}
|
||||
|
||||
@ -113,6 +113,9 @@
|
||||
"deployDrawer.cancel": "Cancel",
|
||||
"deployDrawer.defaultSelect": "Select...",
|
||||
"deployDrawer.deploy": "Deploy",
|
||||
"deployDrawer.deployExistingRelease": "Deploy existing release",
|
||||
"deployDrawer.deployExistingReleaseDescription": "Deploy an existing release from the version history to a target environment.",
|
||||
"deployDrawer.deployExistingReleaseTitle": "Deploy existing release",
|
||||
"deployDrawer.deployFailed": "Failed to start deployment.",
|
||||
"deployDrawer.deploying": "Deploying...",
|
||||
"deployDrawer.description": "Select a release and deploy it to a target environment.",
|
||||
@ -132,11 +135,14 @@
|
||||
"deployDrawer.notePlaceholder": "e.g. Ship onboarding copy tweak",
|
||||
"deployDrawer.pluginCreds": "Plugin credentials",
|
||||
"deployDrawer.promote": "Promote",
|
||||
"deployDrawer.promoteDescription": "Deploy an existing release from the version history to a target environment.",
|
||||
"deployDrawer.promoteDescription": "Promote a newer release to the target environment.",
|
||||
"deployDrawer.promoteTitle": "Promote release",
|
||||
"deployDrawer.readOnly": "Read-only",
|
||||
"deployDrawer.releaseLabel": "Release",
|
||||
"deployDrawer.requiredBinding": "Required",
|
||||
"deployDrawer.rollback": "Rollback",
|
||||
"deployDrawer.rollbackDescription": "Rollback the target environment to a previous release.",
|
||||
"deployDrawer.rollbackTitle": "Rollback release",
|
||||
"deployDrawer.runtimeCredentials": "Runtime credentials",
|
||||
"deployDrawer.secretPlaceholder": "secret",
|
||||
"deployDrawer.selectCredential": "Select a credential",
|
||||
@ -312,6 +318,7 @@
|
||||
"versions.releaseHistory": "Release history",
|
||||
"versions.releaseNameLabel": "Release name",
|
||||
"versions.releaseNamePlaceholder": "Release name",
|
||||
"versions.rollbackTo": "Rollback to {{name}}",
|
||||
"versions.sourceAppUnavailable": "The source app was deleted. Existing releases are still deployable, but new releases cannot be created.",
|
||||
"versions.viewYaml": "View YAML"
|
||||
}
|
||||
|
||||
@ -113,6 +113,9 @@
|
||||
"deployDrawer.cancel": "取消",
|
||||
"deployDrawer.defaultSelect": "选择...",
|
||||
"deployDrawer.deploy": "部署",
|
||||
"deployDrawer.deployExistingRelease": "部署已有版本",
|
||||
"deployDrawer.deployExistingReleaseDescription": "从版本历史中选择一个已有发布版本,部署到目标环境。",
|
||||
"deployDrawer.deployExistingReleaseTitle": "部署已有版本",
|
||||
"deployDrawer.deployFailed": "启动部署失败。",
|
||||
"deployDrawer.deploying": "部署中...",
|
||||
"deployDrawer.description": "选择一个发布版本,并部署到目标环境。",
|
||||
@ -132,11 +135,14 @@
|
||||
"deployDrawer.notePlaceholder": "例如:优化引导文案",
|
||||
"deployDrawer.pluginCreds": "插件凭据",
|
||||
"deployDrawer.promote": "推送",
|
||||
"deployDrawer.promoteDescription": "从版本历史中选择一个已有发布版本,部署到目标环境。",
|
||||
"deployDrawer.promoteDescription": "将更新的发布版本推送到目标环境。",
|
||||
"deployDrawer.promoteTitle": "推送发布版本",
|
||||
"deployDrawer.readOnly": "只读",
|
||||
"deployDrawer.releaseLabel": "发布版本",
|
||||
"deployDrawer.requiredBinding": "必填",
|
||||
"deployDrawer.rollback": "回滚",
|
||||
"deployDrawer.rollbackDescription": "将目标环境回滚到之前的发布版本。",
|
||||
"deployDrawer.rollbackTitle": "回滚发布版本",
|
||||
"deployDrawer.runtimeCredentials": "运行时凭据",
|
||||
"deployDrawer.secretPlaceholder": "机密值",
|
||||
"deployDrawer.selectCredential": "选择凭据",
|
||||
@ -180,7 +186,7 @@
|
||||
"deployTab.panel.runtimeNote": "运行时备注",
|
||||
"deployTab.panel.targetRelease": "目标版本",
|
||||
"deployTab.panel.unknownError": "部署失败。",
|
||||
"deployTab.promote": "发布",
|
||||
"deployTab.promote": "推送",
|
||||
"deployTab.retry": "重试",
|
||||
"deployTab.shortcut": "快捷",
|
||||
"deployTab.status.deployFailed": "部署失败",
|
||||
@ -305,13 +311,14 @@
|
||||
"versions.hideYaml": "隐藏 YAML",
|
||||
"versions.moreActions": "更多操作",
|
||||
"versions.optional": "可选",
|
||||
"versions.promote": "发布",
|
||||
"versions.promoteTo": "发布到 {{name}}",
|
||||
"versions.promote": "推送",
|
||||
"versions.promoteTo": "推送到 {{name}}",
|
||||
"versions.releaseDescriptionLabel": "描述",
|
||||
"versions.releaseDescriptionPlaceholder": "描述这个版本",
|
||||
"versions.releaseHistory": "发布历史",
|
||||
"versions.releaseNameLabel": "版本名称",
|
||||
"versions.releaseNamePlaceholder": "版本名称",
|
||||
"versions.rollbackTo": "回滚到 {{name}}",
|
||||
"versions.sourceAppUnavailable": "源应用已删除。已有发布版本仍可部署,但无法创建新版本。",
|
||||
"versions.viewYaml": "查看 YAML"
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user