+
+
+
{t('access.permissions.col.environment')}
-
+
{environmentName(environment)}
-
-
-
+
+
+
{t('access.permissions.col.permission')}
-
-
-
+
+
+
{t('access.permissions.col.subjects')}
{permissionKind === 'specific'
@@ -458,7 +462,7 @@ export function EnvironmentPermissionRow({
{t(`access.permission.${permissionKind}Desc`)}
)}
-
-
+
+
)
}
diff --git a/web/features/deployments/detail/table-styles.ts b/web/features/deployments/detail/table-styles.ts
new file mode 100644
index 0000000000..42c16bc708
--- /dev/null
+++ b/web/features/deployments/detail/table-styles.ts
@@ -0,0 +1,36 @@
+import { cn } from '@langgenius/dify-ui/cn'
+
+export const DEPLOYMENT_DETAIL_TABLE_COLUMN_CLASS_NAMES = {
+ actions: 'w-14',
+ currentRelease: 'w-[34%]',
+ environment: 'w-[34%]',
+ status: 'w-[24%]',
+}
+
+export const RELEASE_DETAIL_TABLE_COLUMN_CLASS_NAMES = {
+ action: 'w-14',
+ author: 'w-[18%]',
+ createdAt: 'w-[18%]',
+ deployedTo: 'w-[28%]',
+ release: 'w-[28%]',
+}
+
+export const ACCESS_PERMISSION_DETAIL_TABLE_COLUMN_CLASS_NAMES = {
+ environment: 'w-[22%]',
+ permission: 'w-[28%]',
+ subjects: 'w-[50%]',
+}
+
+export const API_KEY_DETAIL_TABLE_COLUMN_CLASS_NAMES = {
+ action: 'w-16',
+ environment: 'w-[20%]',
+ key: 'w-[38%]',
+ name: 'w-[28%]',
+}
+
+export const DETAIL_TABLE_ACTION_TRIGGER_CLASS_NAME = cn(
+ 'inline-flex size-8 items-center justify-center rounded-md text-text-tertiary outline-hidden',
+ 'hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
+ 'data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary',
+ 'disabled:cursor-not-allowed disabled:opacity-50',
+)
diff --git a/web/features/deployments/detail/table.tsx b/web/features/deployments/detail/table.tsx
new file mode 100644
index 0000000000..0da29ec991
--- /dev/null
+++ b/web/features/deployments/detail/table.tsx
@@ -0,0 +1,91 @@
+import type { ComponentProps } from 'react'
+import { cn } from '@langgenius/dify-ui/cn'
+
+type DetailTableProps = ComponentProps<'table'> & {
+ containerClassName?: string
+}
+
+export function DetailTable({ className, containerClassName, ...props }: DetailTableProps) {
+ return (
+
+ )
+}
+
+export function DetailTableHeader({ className, ...props }: ComponentProps<'thead'>) {
+ return (
+
+ )
+}
+
+export function DetailTableBody({ className, ...props }: ComponentProps<'tbody'>) {
+ return (
+
+ )
+}
+
+export function DetailTableRow({ className, ...props }: ComponentProps<'tr'>) {
+ return (
+
|
+ )
+}
+
+export function DetailTableHead({ className, ...props }: ComponentProps<'th'>) {
+ return (
+
|
+ )
+}
+
+export function DetailTableCell({ className, ...props }: ComponentProps<'td'>) {
+ return (
+
|
+ )
+}
+
+export function DetailTableCardList({ className, ...props }: ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+export function DetailTableCard({ className, ...props }: ComponentProps<'div'>) {
+ return (
+
+ )
+}
diff --git a/web/features/deployments/detail/versions-tab/__tests__/release-history-table.spec.tsx b/web/features/deployments/detail/versions-tab/__tests__/release-history-table.spec.tsx
index 1ec4be03d4..8b547b3b54 100644
--- a/web/features/deployments/detail/versions-tab/__tests__/release-history-table.spec.tsx
+++ b/web/features/deployments/detail/versions-tab/__tests__/release-history-table.spec.tsx
@@ -100,31 +100,61 @@ describe('ReleaseHistoryTable', () => {
})
})
- // The desktop release history should use the same compact table shell as knowledge documents.
+ // The desktop release history should use the shared semantic deployment detail table.
describe('Rendering', () => {
- it('should render the desktop release history as a compact document-style table', () => {
+ it('should render the desktop release history with the shared detail table design', () => {
// Arrange & Act
const { container } = render(
)
// Assert
- const table = screen.getByRole('table')
- expect(table).toHaveClass('border-collapse', 'border-0', 'text-sm', 'min-w-[700px]')
- expect(container.querySelector('thead')).toHaveClass(
- 'h-8',
- 'border-b',
+ const desktopWrapper = container.querySelector('.hidden.pc\\:block')
+ const tableContainer = desktopWrapper?.querySelector('[data-slot="deployment-detail-table-container"]')
+ const tableShell = desktopWrapper?.querySelector('[data-slot="deployment-detail-table"]')
+ const header = tableShell?.querySelector('[data-slot="deployment-detail-table-header"]')
+ const body = tableShell?.querySelector('[data-slot="deployment-detail-table-body"]')
+ const row = body?.querySelector('[data-slot="deployment-detail-table-row"]')
+ const head = header?.querySelector('[data-slot="deployment-detail-table-head"]')
+ const cell = row?.querySelector('[data-slot="deployment-detail-table-cell"]')
+
+ expect(tableContainer).toHaveClass(
+ 'overflow-hidden',
+ 'rounded-lg',
+ 'border',
'border-divider-subtle',
- 'text-xs',
- 'leading-8',
- 'font-medium',
- 'text-text-tertiary',
- 'uppercase',
+ 'bg-background-default',
)
- expect(container.querySelector('tbody tr')).toHaveClass(
- 'h-8',
+ expect(tableShell?.tagName).toBe('TABLE')
+ expect(header?.tagName).toBe('THEAD')
+ expect(body?.tagName).toBe('TBODY')
+ expect(row?.tagName).toBe('TR')
+ expect(head?.tagName).toBe('TH')
+ expect(cell?.tagName).toBe('TD')
+ expect(tableShell).toHaveClass(
+ 'w-full',
+ 'table-fixed',
+ 'border-collapse',
+ 'caption-bottom',
+ )
+ expect(head).toHaveClass(
+ 'h-9',
+ 'px-4',
+ 'py-2',
+ 'system-sm-medium-uppercase',
+ 'text-text-tertiary',
+ )
+ expect(row).toHaveClass(
'border-b',
'border-divider-subtle',
'hover:bg-background-default-hover',
)
+ expect(cell).toHaveClass(
+ 'h-12',
+ 'min-w-0',
+ 'px-4',
+ 'py-2',
+ )
+ expect(row?.querySelector('[data-slot="deployment-detail-table-row-content"]')).toBeNull()
+ expect(screen.getAllByText('R-001')).toHaveLength(2)
})
})
})
diff --git a/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx b/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx
index 5d140d978d..b59d46d2fa 100644
--- a/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx
+++ b/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx
@@ -17,7 +17,7 @@ import { environmentId, environmentName } from '../../environment'
import { releaseDeploymentAction } from '../../release-action'
import { deploymentStatus, isUndeployedDeploymentRow } from '../../runtime-status'
import { openDeployDrawerAtom } from '../../store'
-import { DETAIL_LIST_ACTION_TRIGGER_CLASS_NAME } from '../list-styles'
+import { DETAIL_TABLE_ACTION_TRIGGER_CLASS_NAME } from '../table-styles'
type EnvironmentOption = AppDeployEnvironment & {
id: string
@@ -132,7 +132,7 @@ export function DeployReleaseMenu({ appInstanceId, releaseId, releaseRows }: {
diff --git a/web/features/deployments/detail/versions-tab/release-history-table.tsx b/web/features/deployments/detail/versions-tab/release-history-table.tsx
index 6520c72540..34cf0e422b 100644
--- a/web/features/deployments/detail/versions-tab/release-history-table.tsx
+++ b/web/features/deployments/detail/versions-tab/release-history-table.tsx
@@ -21,12 +21,18 @@ import {
DetailListState,
} from '../common'
import {
- DETAIL_LIST_CLASS_NAME,
- DETAIL_LIST_DESKTOP_ROW_CLASS_NAME,
- DETAIL_LIST_HEADER_ROW_CLASS_NAME,
- DETAIL_LIST_ROW_CLASS_NAME,
- RELEASE_DETAIL_LIST_GRID_CLASS_NAME,
-} from '../list-styles'
+ DetailTable,
+ DetailTableBody,
+ DetailTableCard,
+ DetailTableCardList,
+ DetailTableCell,
+ DetailTableHead,
+ DetailTableHeader,
+ DetailTableRow,
+} from '../table'
+import {
+ RELEASE_DETAIL_TABLE_COLUMN_CLASS_NAMES,
+} from '../table-styles'
import { DeployReleaseMenu } from './deploy-release-menu'
import { DeployedToBadge } from './deployed-to-badge'
import { getReleaseDeployments } from './release-deployments'
@@ -46,9 +52,9 @@ function ReleaseHistoryTableSkeleton() {
return (
<>
-
+
{RELEASE_TABLE_ROW_SKELETON_KEYS.map(key => (
-
+
@@ -64,40 +70,44 @@ function ReleaseHistoryTableSkeleton() {
-
+
))}
-
+
-
-
-
{t('versions.col.release')}
-
{t('versions.col.createdAt')}
-
{t('versions.col.author')}
-
{t('versions.col.deployedTo')}
-
{t('versions.col.action')}
-
- {RELEASE_TABLE_ROW_SKELETON_KEYS.map(key => (
-
-
-
+
+
+
+ {t('versions.col.release')}
+ {t('versions.col.createdAt')}
+ {t('versions.col.author')}
+ {t('versions.col.deployedTo')}
+ {t('versions.col.action')}
+
+
+
+ {RELEASE_TABLE_ROW_SKELETON_KEYS.map(key => (
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
- ))}
-
+
+
+
+
+
+
+
+ ))}
+
+
>
)
@@ -113,14 +123,14 @@ function ReleaseHistoryMobileRows({ appInstanceId, releaseRows, deploymentRows,
const { t } = useTranslation('deployments')
return (
-
+
{releaseRows.map((row) => {
const release = row
const releaseDeployments = getReleaseDeployments(row, deploymentRows)
const hasDeployments = releaseDeployments.length > 0 || deployedToLoading || deployedToHasError
return (
-
+
@@ -157,10 +167,10 @@ function ReleaseHistoryMobileRows({ appInstanceId, releaseRows, deploymentRows,
)}
-
+
)
})}
-
+
)
}
@@ -243,22 +253,24 @@ function ReleaseHistoryRows({ appInstanceId, releaseRows, deploymentRows, deploy
deployedToHasError={deployedToHasError}
/>
-
-
-
{t('versions.col.release')}
-
{t('versions.col.createdAt')}
-
{t('versions.col.author')}
-
{t('versions.col.deployedTo')}
-
{t('versions.col.action')}
-
- {releaseRows.map((row) => {
- const release = row
- const releaseDeployments = getReleaseDeployments(row, deploymentRows)
+
+
+
+ {t('versions.col.release')}
+ {t('versions.col.createdAt')}
+ {t('versions.col.author')}
+ {t('versions.col.deployedTo')}
+ {t('versions.col.action')}
+
+
+
+ {releaseRows.map((row) => {
+ const release = row
+ const releaseDeployments = getReleaseDeployments(row, deploymentRows)
- return (
-
-
-
+ return (
+
+
-
-
+
+
-
-
+
+
{row.createdBy?.name ?? '—'}
-
-
-
-
-
-
-
- )
- })}
-
+
+
+
+
+
+
+
+ )
+ })}
+
+
>
)