This commit is contained in:
Stephen Zhou 2026-05-07 19:23:49 +08:00
parent ea6e7a9ed0
commit 3f36471ec0
No known key found for this signature in database
8 changed files with 52 additions and 84 deletions

View File

@ -1,5 +1,4 @@
'use client'
import type { AppInfo, AppMode } from '../types'
import type { App, AppModeEnum } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
@ -18,11 +17,22 @@ import { useDeploymentsStore } from '../store'
const MAX_STUDIO_SOURCE_APPS = 100
function toStudioSourceAppInfo(app: App): AppInfo {
type StudioSourceApp = {
id: string
name: string
mode: string
iconType?: App['icon_type']
icon?: string
iconBackground?: string
iconUrl?: string | null
description?: string
}
function toStudioSourceAppInfo(app: App): StudioSourceApp {
return {
id: app.id,
name: app.name,
mode: (app.mode || 'workflow') as AppMode,
mode: app.mode || 'workflow',
iconType: app.icon_type,
icon: app.icon,
iconBackground: app.icon_background ?? undefined,
@ -32,13 +42,13 @@ function toStudioSourceAppInfo(app: App): AppInfo {
}
type AppPickerProps = {
apps: AppInfo[]
apps: StudioSourceApp[]
isLoading: boolean
value: string
onChange: (appId: string) => void
}
export function AppPicker({ apps, isLoading, value, onChange }: AppPickerProps) {
function AppPicker({ apps, isLoading, value, onChange }: AppPickerProps) {
const { t } = useTranslation('deployments')
const [open, setOpen] = useState(false)
const [keywords, setKeywords] = useState('')

View File

@ -21,7 +21,6 @@ import {
environmentOptionsFromOptionsReply,
releaseCommit,
releaseLabel,
toAppInfoFromOverview,
} from '../utils'
function InfoRow({ label, value }: {
@ -77,7 +76,9 @@ export function RollbackModal() {
const currentRelease = activeRelease(currentRow)
const environment = currentRow?.environment
?? environmentOptions.find(env => env.id === modal.environmentId)
const app = toAppInfoFromOverview(overview?.instance)
const app = overview?.instance
const appName = app?.name ?? '-'
const sourceAppName = app?.sourceAppName ?? appName
const confirm = () => {
if (!modal.appInstanceId || !modal.environmentId || !modal.targetReleaseId)
@ -110,8 +111,8 @@ export function RollbackModal() {
</AlertDialogDescription>
<div className="mt-2 flex flex-col gap-2 rounded-lg border border-components-panel-border bg-components-panel-bg-blur p-3">
<InfoRow label={t('rollback.instance')} value={app?.name ?? '-'} />
<InfoRow label={t('rollback.sourceApp')} value={app?.name ?? '-'} />
<InfoRow label={t('rollback.instance')} value={appName} />
<InfoRow label={t('rollback.sourceApp')} value={sourceAppName} />
<InfoRow label={t('rollback.environment')} value={environmentName(environment)} />
<InfoRow
label={t('rollback.currentRelease')}

View File

@ -1,7 +1,7 @@
'use client'
import type { AppInstanceBasicInfo } from '@dify/contracts/enterprise/types.gen'
import type { ComponentProps, PropsWithoutRef } from 'react'
import type { AppInfo } from '../types'
import type { InstanceDetailTabKey } from './tabs'
import type { NavIcon } from '@/app/components/app-sidebar/nav-link'
import { cn } from '@langgenius/dify-ui/cn'
@ -81,7 +81,7 @@ type DeploymentSidebarProps = {
instanceName: string
instanceDescription?: string
appModeLabel: string
app?: AppInfo
app?: AppInstanceBasicInfo
}
export function DeploymentSidebar({
@ -140,10 +140,9 @@ export function DeploymentSidebar({
? (
<AppIcon
size={expand ? 'large' : 'medium'}
iconType={app.iconType}
iconType="emoji"
icon={app.icon}
background={app.iconBackground}
imageUrl={app.iconUrl}
/>
)
: (

View File

@ -11,7 +11,6 @@ import { useRouter, useSelectedLayoutSegment } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { DeployDrawer } from '../components/deploy-drawer'
import { RollbackModal } from '../components/rollback-modal'
import { toAppInfoFromOverview } from '../utils'
import { DeploymentSidebar } from './deployment-sidebar'
import { isInstanceDetailTabKey } from './tabs'
@ -33,9 +32,10 @@ export function InstanceDetail({ instanceId, children }: {
useDocumentTitle(t('documentTitle.detail'))
const app = toAppInfoFromOverview(overviewQuery.data?.instance)
const app = overviewQuery.data?.instance
const appId = app?.id
if (!app && overviewQuery.isLoading) {
if (!appId && overviewQuery.isLoading) {
return (
<div className="flex h-full items-center justify-center bg-background-body">
<span className="h-6 w-6 animate-spin rounded-full border-2 border-components-panel-border border-t-transparent" />
@ -43,7 +43,7 @@ export function InstanceDetail({ instanceId, children }: {
)
}
if (!app) {
if (!appId || !app) {
return (
<div className="flex h-full flex-col items-center justify-center gap-3 bg-background-body">
<div className="title-xl-semi-bold text-text-primary">{t('detail.notFound')}</div>
@ -54,14 +54,15 @@ export function InstanceDetail({ instanceId, children }: {
</div>
)
}
const appModeLabel = app ? getAppModeLabel(app.mode, tCommon) : t('detail.sourceAppDeleted')
const appName = app.name ?? appId
const appModeLabel = getAppModeLabel(app.mode ?? 'workflow', tCommon)
return (
<>
<div className="relative flex h-full overflow-hidden rounded-t-2xl shadow-[0_0_5px_rgba(0,0,0,0.05),0_0_2px_-1px_rgba(0,0,0,0.03)]">
<DeploymentSidebar
instanceId={instanceId}
instanceName={app.name}
instanceId={appId}
instanceName={appName}
instanceDescription={app.description}
appModeLabel={appModeLabel}
app={app}

View File

@ -12,7 +12,6 @@ import { DEPLOYMENT_PAGE_SIZE } from '../data'
import { useDeploymentsStore } from '../store'
import {
releaseLabel,
toAppInfoFromOverview,
webappUrl,
} from '../utils'
@ -112,20 +111,21 @@ export function OverviewTab({ instanceId }: {
input,
}))
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
const app = toAppInfoFromOverview(overview?.instance)
const overviewApp = overview?.instance
const deployments = overview?.deployments?.filter(row => row.environment?.id && row.status?.toLowerCase() !== 'undeployed') ?? []
const releaseRows = releaseHistory?.data?.filter(row => row.id) ?? []
const canCreateRelease = overviewApp?.canCreateRelease ?? true
if (!app)
if (!overviewApp?.id)
return null
const appId = overviewApp.id
const appName = overviewApp.name ?? appId
const switchTab = (tab: SwitchableTab) => {
router.push(`/deployments/${instanceId}/${tab}`)
router.push(`/deployments/${appId}/${tab}`)
}
const appModeLabel = getAppModeLabel(overviewApp?.mode ?? app.mode, tCommon)
const appModeLabel = getAppModeLabel(overviewApp.mode ?? 'workflow', tCommon)
const webappAccessUrl = webappUrl(overview?.access?.webappUrl)
const cliUrl = overview?.access?.cliUrl
const apiUrl = overview?.access?.apiUrl ?? accessConfig?.developerApi?.apiUrl
@ -135,9 +135,9 @@ export function OverviewTab({ instanceId }: {
<div className="flex w-full max-w-[960px] flex-col gap-5 p-6">
<Section title={t('overview.basicInfo')}>
<div className="flex flex-col divide-y divide-divider-subtle">
<InfoRow label={t('overview.name')} value={overviewApp?.name ?? app.name} />
<InfoRow label={t('overview.description')} value={overviewApp?.description ?? app.description ?? t('overview.emptyValue')} />
<InfoRow label={t('overview.sourceApp')} value={overviewApp?.sourceAppName ?? app.sourceAppName ?? app.name} />
<InfoRow label={t('overview.name')} value={appName} />
<InfoRow label={t('overview.description')} value={overviewApp.description ?? t('overview.emptyValue')} />
<InfoRow label={t('overview.sourceApp')} value={overviewApp.sourceAppName ?? appName} />
<InfoRow label={t('overview.appMode')} value={appModeLabel} />
</div>
</Section>
@ -169,7 +169,7 @@ export function OverviewTab({ instanceId }: {
switchTab('versions')
return
}
openDeployDrawer({ appInstanceId: app.id })
openDeployDrawer({ appInstanceId: appId })
}}
>
{releaseRows.length === 0 ? t('overview.createRelease') : t('overview.deploy')}

View File

@ -1,5 +1,5 @@
'use client'
import type { AppInfo } from '../types'
import type { AppInstanceBasicInfo } from '@dify/contracts/enterprise/types.gen'
import type { GetAppInstanceSettingsReply } from '@/features/deployments/types'
import {
AlertDialog,
@ -17,27 +17,25 @@ import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useRouter } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import {
deployedRows,
toAppInfoFromOverview,
} from '../utils'
import { deployedRows } from '../utils'
type SettingsFormProps = {
app: AppInfo
app: AppInstanceBasicInfo
settings?: GetAppInstanceSettingsReply
hasDeployments: boolean
onSave: (patch: Pick<AppInfo, 'name' | 'description'>) => Promise<void>
onSave: (patch: Pick<AppInstanceBasicInfo, 'name' | 'description'>) => Promise<void>
onDelete: () => Promise<void>
}
function SettingsForm({ app, settings, hasDeployments, onSave, onDelete }: SettingsFormProps) {
const { t } = useTranslation('deployments')
const [name, setName] = useState(settings?.name ?? app.name)
const appName = app.name ?? app.id ?? ''
const [name, setName] = useState(settings?.name ?? appName)
const [description, setDescription] = useState(settings?.description ?? app.description ?? '')
const [isSaving, setIsSaving] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const initialName = settings?.name ?? app.name
const initialName = settings?.name ?? appName
const initialDescription = settings?.description ?? app.description ?? ''
const canSave = Boolean(name.trim() && (name !== initialName || description !== initialDescription) && !isSaving)
const canDelete = !hasDeployments && Boolean(settings) && settings?.deleteGuard?.canDelete !== false
@ -153,7 +151,7 @@ function SettingsForm({ app, settings, hasDeployments, onSave, onDelete }: Setti
{t('settings.deleteConfirmTitle')}
</AlertDialogTitle>
<AlertDialogDescription className="system-md-regular text-text-tertiary">
{t('settings.deleteConfirmDesc', { name: app.name })}
{t('settings.deleteConfirmDesc', { name: appName })}
</AlertDialogDescription>
</div>
<AlertDialogActions>
@ -183,16 +181,17 @@ export function SettingsTab({ instanceId }: {
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
input: appInput,
}))
const app = toAppInfoFromOverview(overview?.instance)
const app = overview?.instance
const settingsQuery = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceSettings.queryOptions({
input: appInput,
}))
if (!app)
if (!app?.id)
return null
const hasDeployments = deployedRows(environmentDeployments?.data).length > 0
const formKey = `${app.id}-${settingsQuery.data?.name ?? app.name}-${settingsQuery.data?.description ?? app.description ?? ''}`
const appName = app.name ?? app.id
const formKey = `${app.id}-${settingsQuery.data?.name ?? appName}-${settingsQuery.data?.description ?? app.description ?? ''}`
return (
<SettingsForm

View File

@ -1,5 +1,4 @@
import type * as EnterpriseContract from '@dify/contracts/enterprise/types.gen'
import type { AppIconType } from '@/types/app'
type Timestamp = string
@ -8,25 +7,8 @@ export type EnvironmentHealth = 'ready' | 'degraded'
export type DeployStatus = 'ready' | 'deploying' | 'deploy_failed'
export type AppMode = 'chat' | 'agent-chat' | 'workflow' | 'completion' | 'advanced-chat' | (string & {})
export type AccessPermissionKind = 'organization' | 'specific' | 'anyone'
export type AppInfo = {
id: string
name: string
mode: AppMode
iconType?: AppIconType | null
icon?: string
iconBackground?: string
iconUrl?: string | null
description?: string
sourceAppId?: string
sourceAppName?: string
sourceAppAvailable?: boolean
canCreateRelease?: boolean
}
export type ConsoleEnvironmentSummary = EnterpriseContract.ConsoleEnvironment & {
backend?: string
description?: string
@ -44,8 +26,6 @@ type ConsoleUser = EnterpriseContract.ConsoleUser & {
displayName?: string
}
export type AppInstanceOverview = EnterpriseContract.AppInstanceBasicInfo
export type RuntimeBindingDisplay = EnterpriseContract.ReleaseRuntimeBinding & {
displayName?: string
maskedValue?: string

View File

@ -1,8 +1,5 @@
import type {
AccessPermissionKind,
AppInfo,
AppInstanceOverview,
AppMode,
ConsoleEnvironmentSummary,
ConsoleReleaseSummary,
EnvironmentDeploymentRow,
@ -134,25 +131,6 @@ export function deployedRows(rows?: EnvironmentDeploymentRow[]) {
}) ?? []
}
export function toAppInfoFromOverview(instance?: AppInstanceOverview): AppInfo | undefined {
if (!instance?.id)
return undefined
return {
id: instance.id,
name: instance.name ?? instance.id,
mode: (instance.mode || 'workflow') as AppMode,
iconType: 'emoji',
icon: instance.icon,
iconBackground: instance.iconBackground,
description: instance.description ?? undefined,
sourceAppId: instance.sourceAppId,
sourceAppName: instance.sourceAppName,
sourceAppAvailable: instance.sourceAppAvailable,
canCreateRelease: instance.canCreateRelease,
}
}
export function environmentOptionsFromOptionsReply(response?: ListDeploymentEnvironmentOptionsReply): EnvironmentOption[] {
return response?.environments
?.filter(environment => environment.id)