This commit is contained in:
Stephen Zhou 2026-05-11 20:51:35 +08:00
parent 0a32344504
commit bf8a587a1d
No known key found for this signature in database
2 changed files with 116 additions and 52 deletions

View File

@ -7,17 +7,20 @@ import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import { consoleQuery } from '@/service/client'
import { DEPLOYMENT_PAGE_SIZE } from '../data'
import { deployedRows } from '../utils'
import { ReleaseHistoryTable } from './versions-tab/release-history-table'
function CreateReleaseControl({ appInstanceId, canCreateRelease }: {
function CreateReleaseControl({ appInstanceId }: {
appInstanceId: string
canCreateRelease: boolean
}) {
const { t } = useTranslation('deployments')
const createRelease = useMutation(consoleQuery.enterprise.appDeploy.createRelease.mutationOptions())
const [isCreating, setIsCreating] = useState(false)
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
input: {
params: { appInstanceId },
},
}))
const canCreateRelease = overview ? overview.instance?.canCreateRelease ?? true : false
async function handleCreateRelease(form: HTMLFormElement) {
if (!canCreateRelease || createRelease.isPending)
@ -149,64 +152,42 @@ function CreateReleaseControl({ appInstanceId, canCreateRelease }: {
)
}
function SourceAppUnavailableNotice({ appInstanceId }: {
appInstanceId: string
}) {
const { t } = useTranslation('deployments')
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
input: {
params: { appInstanceId },
},
}))
if (overview?.instance?.canCreateRelease !== false)
return null
return (
<div className="rounded-lg border border-divider-subtle bg-background-default-subtle px-3 py-2 system-sm-regular text-text-tertiary">
{t('versions.sourceAppUnavailable')}
</div>
)
}
export function VersionsTab({ appInstanceId }: {
appInstanceId: string
}) {
const { t } = useTranslation('deployments')
const input = { params: { appInstanceId } }
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
input,
}))
const { data: releaseHistory } = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({
input: {
...input,
query: {
pageNumber: 1,
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
},
},
}))
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
input,
}))
const releaseRows = releaseHistory?.data?.filter(row => row.id) ?? []
const deploymentRows = deployedRows(environmentDeployments?.data)
const canCreateRelease = overview?.instance?.canCreateRelease ?? true
return (
<div className="flex w-full max-w-240 flex-col gap-4 p-6">
<div className="flex items-center justify-between gap-3">
<div className="system-sm-semibold text-text-primary">
{t('versions.releaseHistory')}
{' '}
<span className="system-sm-regular text-text-tertiary">
(
{releaseRows.length}
)
</span>
</div>
<CreateReleaseControl appInstanceId={appInstanceId} canCreateRelease={canCreateRelease} />
<CreateReleaseControl appInstanceId={appInstanceId} />
</div>
{!canCreateRelease && (
<div className="rounded-lg border border-divider-subtle bg-background-default-subtle px-3 py-2 system-sm-regular text-text-tertiary">
{t('versions.sourceAppUnavailable')}
</div>
)}
<SourceAppUnavailableNotice appInstanceId={appInstanceId} />
{releaseRows.length === 0
? (
<div className="rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur px-4 py-12 text-center system-sm-regular text-text-tertiary">
{canCreateRelease ? t('versions.emptyWithCreate') : t('versions.emptySourceUnavailable')}
</div>
)
: (
<ReleaseHistoryTable
appInstanceId={appInstanceId}
releaseRows={releaseRows}
deploymentRows={deploymentRows}
/>
)}
<ReleaseHistoryTable appInstanceId={appInstanceId} />
</div>
)
}

View File

@ -1,10 +1,15 @@
'use client'
import type { ReleaseRow, RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
import type { ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import { DEPLOYMENT_PAGE_SIZE } from '../../data'
import {
deployedRows,
formatDate,
releaseCommit,
releaseLabel,
@ -15,9 +20,27 @@ import { getReleaseDeployments } from './release-deployments'
const GRID_TEMPLATE = 'grid-cols-[minmax(0,0.9fr)_minmax(0,1fr)_minmax(0,0.8fr)_minmax(0,1.5fr)_96px]'
export function ReleaseHistoryTable({ appInstanceId, releaseRows, deploymentRows }: {
type ReleaseRowWithId = ReleaseRow & {
id: string
}
function hasReleaseId(row: ReleaseRow): row is ReleaseRowWithId {
return Boolean(row.id)
}
function ReleaseHistoryTableState({ children }: {
children: ReactNode
}) {
return (
<div className="rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur px-4 py-12 text-center system-sm-regular text-text-tertiary">
{children}
</div>
)
}
function ReleaseHistoryRows({ appInstanceId, releaseRows, deploymentRows }: {
appInstanceId: string
releaseRows: ReleaseRow[]
releaseRows: ReleaseRowWithId[]
deploymentRows: RuntimeInstanceRow[]
}) {
const { t } = useTranslation('deployments')
@ -63,7 +86,7 @@ export function ReleaseHistoryTable({ appInstanceId, releaseRows, deploymentRows
</div>
</div>
<div className="flex shrink-0 justify-end gap-1">
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appInstanceId} releaseRows={releaseRows} />
<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 +137,7 @@ export function ReleaseHistoryTable({ appInstanceId, releaseRows, deploymentRows
))}
</div>
<div className="flex justify-end gap-1">
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appInstanceId} releaseRows={releaseRows} />
<DeployReleaseMenu releaseId={release.id} appInstanceId={appInstanceId} releaseRows={releaseRows} />
</div>
</div>
</div>
@ -123,3 +146,63 @@ export function ReleaseHistoryTable({ appInstanceId, releaseRows, deploymentRows
</div>
)
}
export function ReleaseHistoryTable({ appInstanceId }: {
appInstanceId: string
}) {
const { t } = useTranslation('deployments')
const input = { params: { appInstanceId } }
const overviewQuery = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
input,
}))
const releaseHistoryQuery = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({
input: {
...input,
query: {
pageNumber: 1,
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
},
},
}))
const releaseRows = releaseHistoryQuery.data?.data?.filter(hasReleaseId) ?? []
const shouldLoadRuntimeInstances = releaseRows.length > 0
const environmentDeploymentsQuery = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
input,
enabled: shouldLoadRuntimeInstances,
}))
const isLoading = releaseHistoryQuery.isLoading
|| (releaseRows.length === 0 && overviewQuery.isLoading)
|| (shouldLoadRuntimeInstances && environmentDeploymentsQuery.isLoading)
const sourceAppUnavailable = overviewQuery.data?.instance?.canCreateRelease === false
const deploymentRows = deployedRows(environmentDeploymentsQuery.data?.data)
if (isLoading) {
return (
<ReleaseHistoryTableState>
<span
aria-label={t('versions.releaseHistory')}
className="inline-flex items-center"
role="status"
>
<span className="size-4 animate-spin rounded-full border-2 border-components-panel-border border-t-transparent" />
</span>
</ReleaseHistoryTableState>
)
}
if (releaseRows.length === 0) {
return (
<ReleaseHistoryTableState>
{sourceAppUnavailable ? t('versions.emptySourceUnavailable') : t('versions.emptyWithCreate')}
</ReleaseHistoryTableState>
)
}
return (
<ReleaseHistoryRows
appInstanceId={appInstanceId}
releaseRows={releaseRows}
deploymentRows={deploymentRows}
/>
)
}