This commit is contained in:
Stephen Zhou 2026-05-08 15:32:53 +08:00
parent 7cad11c856
commit ab7650d568
No known key found for this signature in database
17 changed files with 111 additions and 96 deletions

View File

@ -1,7 +1,7 @@
'use client'
import type { DeploymentBindingOptionSlot, DeploymentRuntimeBinding } from '@dify/contracts/enterprise/types.gen'
import type { EnvironmentOption, ReleaseHistoryRow } from '@/features/deployments/types'
import type { DeploymentBindingOptionSlot, DeploymentRuntimeBinding, ReleaseRow } 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'
import { skipToken, useQuery } from '@tanstack/react-query'
@ -29,7 +29,7 @@ export type DeployFormSubmit = {
type DeployFormProps = {
appInstanceId: string
environments: EnvironmentOption[]
releases: ReleaseHistoryRow[]
releases: ReleaseRow[]
defaultReleaseId?: string
lockedEnvId?: string
presetReleaseId?: string
@ -219,7 +219,7 @@ export function DeployForm({
}: DeployFormProps) {
const { t } = useTranslation('deployments')
const presetRelease = presetReleaseId ? releases.find(r => r.id === presetReleaseId) : undefined
const displayedRelease: ReleaseHistoryRow | undefined = presetRelease ?? (presetReleaseId ? { id: presetReleaseId } : undefined)
const displayedRelease: ReleaseRow | undefined = presetRelease ?? (presetReleaseId ? { id: presetReleaseId } : undefined)
const isPromote = Boolean(presetReleaseId)
const [selectedEnvId, setSelectedEnvId] = useState<string>(

View File

@ -1,11 +1,11 @@
'use client'
import type {
AccessPermission,
AccessSubject,
ConsoleEnvironmentSummary,
DeveloperAPIKeySummary,
} from '@/features/deployments/types'
ConsoleEnvironment,
DeveloperApiKeyRow,
EnvironmentAccessRow,
} from '@dify/contracts/enterprise/types.gen'
import { useMutation, useQuery } from '@tanstack/react-query'
import { useState } from 'react'
import { consoleClient, consoleQuery } from '@/service/client'
@ -17,10 +17,10 @@ import { DeveloperApiSection } from './access-tab/developer-api-section'
import { AccessPermissionsSection } from './access-tab/permissions-section'
import { getUrlOrigin } from './access-tab/url'
const EMPTY_ACCESS_PERMISSIONS: AccessPermission[] = []
const EMPTY_ACCESS_PERMISSIONS: EnvironmentAccessRow[] = []
function uniqueEnvironments(environments: (ConsoleEnvironmentSummary | undefined)[]) {
return environments.filter((environment, index): environment is ConsoleEnvironmentSummary => {
function uniqueEnvironments(environments: (ConsoleEnvironment | undefined)[]) {
return environments.filter((environment, index): environment is ConsoleEnvironment => {
if (!environment?.id)
return false
return environments.findIndex(candidate => candidate?.id === environment.id) === index
@ -31,8 +31,8 @@ type DeveloperApiAccessSectionProps = {
appId: string
apiEnabled: boolean
apiUrl?: string
environments: ConsoleEnvironmentSummary[]
apiKeys: DeveloperAPIKeySummary[]
environments: ConsoleEnvironment[]
apiKeys: DeveloperApiKeyRow[]
}
function DeveloperApiAccessSection({

View File

@ -1,6 +1,6 @@
'use client'
import type { ConsoleEnvironmentSummary, DeveloperAPIKeySummary } from '@/features/deployments/types'
import type { ConsoleEnvironment, DeveloperApiKeyRow } from '@dify/contracts/enterprise/types.gen'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
@ -12,14 +12,15 @@ import { toast } from '@langgenius/dify-ui/toast'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { environmentName } from '../../utils'
import { useCopyFeedback } from './use-copy-feedback'
export function ApiKeyRow({ apiKey, onCopy, onRevoke }: {
apiKey: DeveloperAPIKeySummary
apiKey: DeveloperApiKeyRow
onCopy: (apiKeyId: string) => Promise<string>
onRevoke: (apiKeyId: string) => void
}) {
const { t } = useTranslation('deployments')
const [copied, setCopied] = useState(false)
const { copied, showCopied } = useCopyFeedback()
const displayValue = apiKey.maskedKey || apiKey.id || '—'
const environmentLabel = environmentName(apiKey.environment)
@ -30,9 +31,8 @@ export function ApiKeyRow({ apiKey, onCopy, onRevoke }: {
try {
const token = await onCopy(apiKey.id)
await navigator.clipboard.writeText(token)
setCopied(true)
showCopied()
toast.success(t('access.copyToast'))
window.setTimeout(() => setCopied(false), 1500)
}
catch {
toast.error(t('access.copyFailed'))
@ -73,7 +73,7 @@ export function ApiKeyRow({ apiKey, onCopy, onRevoke }: {
}
export function ApiKeyGenerateMenu({ environments, onGenerate }: {
environments: ConsoleEnvironmentSummary[]
environments: ConsoleEnvironment[]
onGenerate: (environmentId: string) => void
}) {
const { t } = useTranslation('deployments')

View File

@ -1,6 +1,6 @@
'use client'
import type { WebAppAccessRow } from '@/features/deployments/types'
import type { WebAppAccessRow } from '@dify/contracts/enterprise/types.gen'
import { Switch } from '@langgenius/dify-ui/switch'
import { useTranslation } from 'react-i18next'
import { environmentName, webappUrl } from '../../utils'

View File

@ -3,8 +3,8 @@
import type { ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useCopyFeedback } from './use-copy-feedback'
type SectionProps = {
title: string
@ -39,14 +39,13 @@ type CopyPillProps = {
export function CopyPill({ label, value, prefix, className }: CopyPillProps) {
const { t } = useTranslation('deployments')
const [copied, setCopied] = useState(false)
const { copied, showCopied } = useCopyFeedback()
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(value)
setCopied(true)
showCopied()
toast.success(t('access.copyToast'))
window.setTimeout(() => setCopied(false), 1500)
}
catch {
toast.error(t('access.copyFailed'))

View File

@ -1,6 +1,6 @@
'use client'
import type { ConsoleEnvironmentSummary, DeveloperAPIKeySummary } from '@/features/deployments/types'
import type { ConsoleEnvironment, DeveloperApiKeyRow } from '@dify/contracts/enterprise/types.gen'
import { Switch } from '@langgenius/dify-ui/switch'
import { useTranslation } from 'react-i18next'
import { ApiKeyGenerateMenu, ApiKeyRow } from './api-keys'
@ -9,8 +9,8 @@ import { CopyPill, Section } from './common'
type DeveloperApiSectionProps = {
apiEnabled: boolean
apiUrl?: string
environments: ConsoleEnvironmentSummary[]
apiKeys: DeveloperAPIKeySummary[]
environments: ConsoleEnvironment[]
apiKeys: DeveloperApiKeyRow[]
createdToken?: string
onToggle: (enabled: boolean) => void
onGenerate: (environmentId: string) => void

View File

@ -1,14 +1,14 @@
'use client'
import type { AccessPermission, AccessSubject, ConsoleEnvironmentSummary } from '@/features/deployments/types'
import type { AccessSubject, ConsoleEnvironment, EnvironmentAccessRow } from '@dify/contracts/enterprise/types.gen'
import { useTranslation } from 'react-i18next'
import { Section } from './common'
import { EnvironmentPermissionRow } from './permissions'
type AccessPermissionsSectionProps = {
appId: string
environments: ConsoleEnvironmentSummary[]
policies: AccessPermission[]
environments: ConsoleEnvironment[]
policies: EnvironmentAccessRow[]
onSetPolicy: (
environmentId: string,
accessMode: string,

View File

@ -1,13 +1,13 @@
'use client'
import type { AccessPermissionKind } from '../../types'
import type {
AccessPermission,
AccessPolicyDetail,
AccessSubject,
AccessSubjectDisplay,
ConsoleEnvironmentSummary,
} from '@/features/deployments/types'
ConsoleEnvironment,
EnvironmentAccessRow,
} from '@dify/contracts/enterprise/types.gen'
import type { AccessPermissionKind } from '../../types'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
@ -293,8 +293,8 @@ function SubjectPicker({
type EnvironmentPermissionRowProps = {
appId: string
environment: ConsoleEnvironmentSummary
summaryPolicy?: AccessPermission
environment: ConsoleEnvironment
summaryPolicy?: EnvironmentAccessRow
onSetPolicy: (
environmentId: string,
accessMode: string,

View File

@ -0,0 +1,31 @@
'use client'
import { useEffect, useRef, useState } from 'react'
export function useCopyFeedback(resetDelay = 1500) {
const [copied, setCopied] = useState(false)
const resetTimerRef = useRef<number | undefined>(undefined)
function showCopied() {
if (resetTimerRef.current !== undefined)
window.clearTimeout(resetTimerRef.current)
setCopied(true)
resetTimerRef.current = window.setTimeout(() => {
setCopied(false)
resetTimerRef.current = undefined
}, resetDelay)
}
useEffect(() => {
return () => {
if (resetTimerRef.current !== undefined)
window.clearTimeout(resetTimerRef.current)
}
}, [])
return {
copied,
showCopied,
}
}

View File

@ -1,6 +1,7 @@
'use client'
import type { RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
import type { KeyboardEvent } from 'react'
import type { EnvironmentDeploymentRow, EnvironmentOption } from '../types'
import type { EnvironmentOption } from '../types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import {
@ -97,7 +98,7 @@ function NewDeploymentMenu({ appInstanceId, availableEnvs }: {
function DeploymentRowActions({ appInstanceId, envId, row }: {
appInstanceId: string
envId: string
row: EnvironmentDeploymentRow
row: RuntimeInstanceRow
}) {
const { t } = useTranslation('deployments')
const [menuOpen, setMenuOpen] = useState(false)

View File

@ -1,7 +1,7 @@
'use client'
import type { ReleaseRuntimeBinding, RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
import type { ReactNode } from 'react'
import type { EnvironmentDeploymentRow, RuntimeBindingDisplay } from '@/features/deployments/types'
import { cn } from '@langgenius/dify-ui/cn'
import { useTranslation } from 'react-i18next'
import {
@ -50,7 +50,7 @@ function InfoRow({ label, value, mono, suffix }: InfoRowProps) {
}
function RuntimeBindingItem({ binding }: {
binding: RuntimeBindingDisplay
binding: ReleaseRuntimeBinding
}) {
const summary = runtimeBindingSummary(binding)
@ -64,7 +64,7 @@ function RuntimeBindingItem({ binding }: {
}
export function DeploymentPanel({ row }: {
row: EnvironmentDeploymentRow
row: RuntimeInstanceRow
}) {
const { t } = useTranslation('deployments')
const observed = activeRelease(row)

View File

@ -1,6 +1,6 @@
'use client'
import type { EnvironmentDeploymentRow } from '@/features/deployments/types'
import type { RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
import { useTranslation } from 'react-i18next'
import {
activeRelease,
@ -10,7 +10,7 @@ import {
} from '../../utils'
export function DeploymentStatusSummary({ row }: {
row: EnvironmentDeploymentRow
row: RuntimeInstanceRow
}) {
const { t } = useTranslation('deployments')
if (isUndeployedDeploymentRow(row)) {

View File

@ -1,6 +1,5 @@
'use client'
import type { AppInstanceBasicInfo } from '@dify/contracts/enterprise/types.gen'
import type { GetAppInstanceSettingsReply } from '@/features/deployments/types'
import type { AppInstanceBasicInfo, GetAppInstanceSettingsReply } from '@dify/contracts/enterprise/types.gen'
import {
AlertDialog,
AlertDialogActions,

View File

@ -1,4 +1,4 @@
import type { EnvironmentDeploymentRow, ReleaseHistoryRow } from '@/features/deployments/types'
import type { ReleaseRow, RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
import { describe, expect, it } from 'vitest'
import { getReleaseDeployments } from '../release-deployments'
@ -13,7 +13,7 @@ describe('getReleaseDeployments', () => {
environmentName: 'Production',
},
],
} satisfies ReleaseHistoryRow
} satisfies ReleaseRow
const deploymentRows = [
{
id: 'deployment-1',
@ -26,7 +26,7 @@ describe('getReleaseDeployments', () => {
id: 'release-1',
},
},
] satisfies EnvironmentDeploymentRow[]
] satisfies RuntimeInstanceRow[]
// Act
const result = getReleaseDeployments(releaseRow, deploymentRows)
@ -51,7 +51,7 @@ describe('getReleaseDeployments', () => {
environmentName: 'Production',
},
],
} satisfies ReleaseHistoryRow
} satisfies ReleaseRow
const deploymentRows = [
{
id: 'deployment-2',
@ -64,7 +64,7 @@ describe('getReleaseDeployments', () => {
id: 'release-1',
},
},
] satisfies EnvironmentDeploymentRow[]
] satisfies RuntimeInstanceRow[]
// Act
const result = getReleaseDeployments(releaseRow, deploymentRows)
@ -93,7 +93,7 @@ describe('getReleaseDeployments', () => {
environmentName: 'Production',
},
],
} satisfies ReleaseHistoryRow
} satisfies ReleaseRow
// Act
const result = getReleaseDeployments(releaseRow, [])

View File

@ -1,4 +1,4 @@
import type { DeployedToSummary, EnvironmentDeploymentRow, ReleaseHistoryRow } from '@/features/deployments/types'
import type { DeployedEnvironment, ReleaseRow, RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
import {
activeRelease,
deploymentStatus,
@ -23,7 +23,7 @@ function releaseDeploymentState(status?: string): ReleaseDeploymentState {
return 'active'
}
function fromDeployedTo(item: DeployedToSummary): ReleaseDeployment | undefined {
function fromDeployedTo(item: DeployedEnvironment): ReleaseDeployment | undefined {
if (!item.environmentId)
return undefined
@ -40,7 +40,7 @@ function dedupeReleaseDeployments(items: ReleaseDeployment[]) {
})
}
export function getReleaseDeployments(row: ReleaseHistoryRow, deploymentRows: EnvironmentDeploymentRow[]) {
export function getReleaseDeployments(row: ReleaseRow, deploymentRows: RuntimeInstanceRow[]) {
const releaseId = row.id
if (!releaseId)
return []

View File

@ -1,4 +1,4 @@
import type * as EnterpriseContract from '@dify/contracts/enterprise/types.gen'
import type { DeploymentEnvironmentOption } from '@dify/contracts/enterprise/types.gen'
export type EnvironmentMode = 'shared' | 'isolated'
export type EnvironmentHealth = 'ready' | 'degraded'
@ -7,23 +7,6 @@ export type DeployStatus = 'ready' | 'deploying' | 'deploy_failed'
export type AccessPermissionKind = 'organization' | 'specific' | 'anyone'
export type ConsoleEnvironmentSummary = EnterpriseContract.ConsoleEnvironment
export type ConsoleReleaseSummary = EnterpriseContract.ConsoleRelease
export type RuntimeBindingDisplay = EnterpriseContract.ReleaseRuntimeBinding
export type EnvironmentDeploymentRow = EnterpriseContract.RuntimeInstanceRow
export type ListDeploymentEnvironmentOptionsReply = EnterpriseContract.ListDeploymentEnvironmentOptionsReply
export type EnvironmentOption = EnterpriseContract.DeploymentEnvironmentOption & {
export type EnvironmentOption = DeploymentEnvironmentOption & {
disabled?: boolean
}
export type DeployedToSummary = EnterpriseContract.DeployedEnvironment
export type ReleaseHistoryRow = EnterpriseContract.ReleaseRow
export type AccessPermission = EnterpriseContract.EnvironmentAccessRow
export type WebAppAccessRow = EnterpriseContract.WebAppAccessRow
export type DeveloperAPIKeySummary = EnterpriseContract.DeveloperApiKeyRow
export type AccessSubjectDisplay = EnterpriseContract.AccessSubjectDisplay
export type AccessPolicyDetail = EnterpriseContract.AccessPolicyDetail
export type AccessSubject = EnterpriseContract.AccessSubject
export type GetAppInstanceSettingsReply = EnterpriseContract.GetAppInstanceSettingsReply
export type ListAppDeploymentsQuery = NonNullable<EnterpriseContract.EnterpriseAppDeployConsoleListAppInstancesData['query']>

View File

@ -1,12 +1,14 @@
import type {
ConsoleEnvironment,
ConsoleRelease,
ListDeploymentEnvironmentOptionsReply,
ReleaseRow,
ReleaseRuntimeBinding,
RuntimeInstanceRow,
} from '@dify/contracts/enterprise/types.gen'
import type {
AccessPermissionKind,
ConsoleEnvironmentSummary,
ConsoleReleaseSummary,
EnvironmentDeploymentRow,
EnvironmentOption,
ListDeploymentEnvironmentOptionsReply,
ReleaseHistoryRow,
RuntimeBindingDisplay,
} from './types'
import { PUBLIC_API_PREFIX } from '@/config'
@ -18,20 +20,20 @@ export function formatDate(value?: string) {
return value.replace('T', ' ').replace(/\.\d+Z?$/, '').replace(/Z$/, '').slice(0, 16)
}
export function environmentId(environment?: ConsoleEnvironmentSummary | EnvironmentOption) {
export function environmentId(environment?: ConsoleEnvironment | EnvironmentOption) {
return environment?.id ?? ''
}
export function environmentName(environment?: ConsoleEnvironmentSummary | EnvironmentOption) {
export function environmentName(environment?: ConsoleEnvironment | EnvironmentOption) {
return environment?.name || environment?.id || '—'
}
export function environmentMode(environment?: ConsoleEnvironmentSummary | EnvironmentOption) {
export function environmentMode(environment?: ConsoleEnvironment | EnvironmentOption) {
const type = environment?.type?.toLowerCase() ?? ''
return type.includes('isolated') ? 'isolated' : 'shared'
}
function environmentRuntimeName(environment?: ConsoleEnvironmentSummary | EnvironmentOption) {
function environmentRuntimeName(environment?: ConsoleEnvironment | EnvironmentOption) {
if (!environment)
return ''
if ('backend' in environment && environment.backend)
@ -41,37 +43,37 @@ function environmentRuntimeName(environment?: ConsoleEnvironmentSummary | Enviro
return ''
}
export function environmentBackend(environment?: ConsoleEnvironmentSummary | EnvironmentOption) {
export function environmentBackend(environment?: ConsoleEnvironment | EnvironmentOption) {
const runtime = environmentRuntimeName(environment).toLowerCase()
return runtime.includes('host') ? 'host' : 'k8s'
}
export function environmentHealth(environment?: ConsoleEnvironmentSummary | EnvironmentOption) {
export function environmentHealth(environment?: ConsoleEnvironment | EnvironmentOption) {
const status = environment?.status?.toLowerCase() ?? ''
return status.includes('ready') ? 'ready' : 'degraded'
}
export function releaseLabel(release?: ConsoleReleaseSummary | ReleaseHistoryRow) {
export function releaseLabel(release?: ConsoleRelease | ReleaseRow) {
return release?.name || release?.id || '—'
}
export function releaseCommit(release?: ConsoleReleaseSummary | ReleaseHistoryRow) {
export function releaseCommit(release?: ConsoleRelease | ReleaseRow) {
return release && 'shortCommitId' in release ? release.shortCommitId || '—' : '—'
}
export function runtimeBindingSummary(binding?: RuntimeBindingDisplay) {
export function runtimeBindingSummary(binding?: ReleaseRuntimeBinding) {
return binding?.label || binding?.displayValue || binding?.kind || '—'
}
export function isRuntimeEnvVarBinding(binding?: RuntimeBindingDisplay) {
export function isRuntimeEnvVarBinding(binding?: ReleaseRuntimeBinding) {
return (binding?.kind?.toLowerCase() ?? '').includes('env')
}
export function isRuntimeModelBinding(binding?: RuntimeBindingDisplay) {
export function isRuntimeModelBinding(binding?: ReleaseRuntimeBinding) {
return (binding?.kind?.toLowerCase() ?? '').includes('model')
}
export function isRuntimePluginBinding(binding?: RuntimeBindingDisplay) {
export function isRuntimePluginBinding(binding?: ReleaseRuntimeBinding) {
return !isRuntimeEnvVarBinding(binding) && !isRuntimeModelBinding(binding)
}
@ -100,19 +102,19 @@ export function webappUrl(url?: string) {
return `${origin}${withLeadingSlash(url)}`
}
export function deploymentId(row?: EnvironmentDeploymentRow) {
export function deploymentId(row?: RuntimeInstanceRow) {
return row?.id || ''
}
export function activeRelease(row?: EnvironmentDeploymentRow) {
export function activeRelease(row?: RuntimeInstanceRow) {
return row?.currentRelease
}
export function isUndeployedDeploymentRow(row?: EnvironmentDeploymentRow) {
export function isUndeployedDeploymentRow(row?: RuntimeInstanceRow) {
return (row?.status?.toLowerCase() ?? '').includes('undeployed') || (!row?.id && !row?.currentRelease && !row?.detail)
}
export function deploymentStatus(row: EnvironmentDeploymentRow): DeploymentUiStatus {
export function deploymentStatus(row: RuntimeInstanceRow): DeploymentUiStatus {
const runtimeStatus = row.status?.toLowerCase() ?? ''
if (runtimeStatus.includes('deploying') || runtimeStatus.includes('pending'))
return 'deploying'
@ -121,7 +123,7 @@ export function deploymentStatus(row: EnvironmentDeploymentRow): DeploymentUiSta
return 'ready'
}
export function deployedRows(rows?: EnvironmentDeploymentRow[]) {
export function deployedRows(rows?: RuntimeInstanceRow[]) {
return rows?.filter((row) => {
const runtimeStatus = row.status?.toLowerCase() ?? ''
return row.environment?.id