From f1fa2c0787c9797a301d0783c3a0344b58ffc3f8 Mon Sep 17 00:00:00 2001 From: agatha197 Date: Thu, 30 Apr 2026 20:57:49 +0900 Subject: [PATCH] fix(FR-2787): deployment CRUD UI/UX improvements - Fix responsive column layout in DeploymentRevisionDetailDrawerContent (Grid.useBreakpoint, md+ 2 cols, otherwise 1) - Fix column layout in DeploymentCurrentRevisionModalContent (column: 2) - Add folder explorer link in revision detail drawer and config section - Show vfolderId as fallback when vfolder name is unavailable - Remove cancel button from deployment launcher - Update DeploymentStatusTag to use SemanticColor system (aligned with EndpointStatusTag) - Replace modal.confirm with BAIDeleteConfirmModal in DeploymentAccessTokensTab - Set default replica sort to -createdAt (DESC) --- data/schema.graphql | 1 - .../components/DeploymentAccessTokensTab.tsx | 193 +++++++++--------- .../DeploymentConfigurationSection.tsx | 44 +++- .../DeploymentLauncherPageContent.tsx | 16 +- .../src/components/DeploymentReplicasTab.tsx | 8 +- .../DeploymentRevisionDetailDrawer.tsx | 56 ++++- react/src/components/DeploymentStatusTag.tsx | 38 ++-- react/src/pages/DeploymentLauncherPage.tsx | 29 --- 8 files changed, 205 insertions(+), 180 deletions(-) diff --git a/data/schema.graphql b/data/schema.graphql index adac8a28a8..77cae0a99c 100644 --- a/data/schema.graphql +++ b/data/schema.graphql @@ -17906,7 +17906,6 @@ input UpdateDeploymentInput openToPublic: Boolean tags: [String!] defaultDeploymentStrategy: DeploymentStrategyInput - activeRevisionId: ID replicaCount: Int name: String preferredDomainName: String diff --git a/react/src/components/DeploymentAccessTokensTab.tsx b/react/src/components/DeploymentAccessTokensTab.tsx index 99a060e500..a468cec43e 100644 --- a/react/src/components/DeploymentAccessTokensTab.tsx +++ b/react/src/components/DeploymentAccessTokensTab.tsx @@ -24,6 +24,7 @@ import { } from 'antd'; import { BAICard, + BAIDeleteConfirmModal, BAIFetchKeyButton, BAIFlex, BAIModal, @@ -253,8 +254,12 @@ const DeploymentAccessTokensTable: React.FC< }) => { 'use memo'; const { t } = useTranslation(); - const { message, modal } = App.useApp(); + const { message } = App.useApp(); const { logger } = useBAILogger(); + const [deletingToken, setDeletingToken] = useState<{ + id: string; + token: string; + } | null>(null); const { deployment: listData } = useLazyLoadQuery( @@ -301,100 +306,102 @@ const DeploymentAccessTokensTable: React.FC< `); return ( - - scroll={{ x: 'max-content' }} - rowKey="id" - loading={isPendingRefetch || isDeletingToken} - dataSource={accessTokens} - pagination={false} - columns={[ - { - key: 'token', - title: t('deployment.accessToken.Token'), - dataIndex: 'token', - render: (_text, row) => { - if (!row) return '-'; - return ( - - {row.token} - - } - showActions="always" - actions={[ - { - key: 'delete', - title: t('deployment.accessToken.Delete'), - icon: , - type: 'danger', - disabled: isDeleteDisabled, - onClick: () => { - modal.confirm({ - title: t('deployment.accessToken.Delete'), - content: t('deployment.accessToken.DeleteConfirm'), - okText: t('button.Delete'), - okButtonProps: { danger: true }, - onOk: () => { - commitDelete({ - variables: { - input: { - id: toLocalId(row.id) ?? row.id, - }, - }, - onCompleted: (_res, errors) => { - if (errors && errors.length > 0) { - logger.error(errors[0]); - message.error( - errors[0]?.message ?? - t('dialog.ErrorOccurred'), - ); - return; - } - message.success( - t('deployment.accessToken.Deleted'), - ); - onAfterDelete(); - }, - onError: (err) => { - logger.error(err); - message.error( - err.message ?? t('dialog.ErrorOccurred'), - ); - }, - }); - }, - }); + <> + + scroll={{ x: 'max-content' }} + rowKey="id" + loading={isPendingRefetch || isDeletingToken} + dataSource={accessTokens} + pagination={false} + columns={[ + { + key: 'token', + title: t('deployment.accessToken.Token'), + dataIndex: 'token', + render: (_text, row) => { + if (!row) return '-'; + return ( + + {row.token} + + } + showActions="always" + actions={[ + { + key: 'delete', + title: t('deployment.accessToken.Delete'), + icon: , + type: 'danger', + disabled: isDeleteDisabled, + onClick: () => + setDeletingToken({ + id: row.id, + token: row.token ?? '', + }), }, - }, - ]} - /> - ); + ]} + /> + ); + }, }, - }, - { - key: 'createdAt', - title: t('deployment.CreatedAt'), - dataIndex: 'createdAt', - render: (_text, row) => - row?.createdAt ? dayjs(row.createdAt).format('ll LT') : '-', - }, - { - key: 'expiresAt', - title: t('deployment.accessToken.Expiration'), - dataIndex: 'expiresAt', - render: (_text, row) => - row?.expiresAt - ? dayjs(row.expiresAt).format('ll LT') - : t('deployment.accessToken.NoExpiration'), - }, - ]} - /> + { + key: 'createdAt', + title: t('deployment.CreatedAt'), + dataIndex: 'createdAt', + render: (_text, row) => + row?.createdAt ? dayjs(row.createdAt).format('ll LT') : '-', + }, + { + key: 'expiresAt', + title: t('deployment.accessToken.Expiration'), + dataIndex: 'expiresAt', + render: (_text, row) => + row?.expiresAt + ? dayjs(row.expiresAt).format('ll LT') + : t('deployment.accessToken.NoExpiration'), + }, + ]} + /> + { + if (!deletingToken) return; + commitDelete({ + variables: { + input: { id: toLocalId(deletingToken.id) ?? deletingToken.id }, + }, + onCompleted: (_res, errors) => { + if (errors && errors.length > 0) { + logger.error(errors[0]); + message.error(errors[0]?.message ?? t('dialog.ErrorOccurred')); + return; + } + message.success(t('deployment.accessToken.Deleted')); + setDeletingToken(null); + onAfterDelete(); + }, + onError: (err) => { + logger.error(err); + message.error(err.message ?? t('dialog.ErrorOccurred')); + }, + }); + }} + onCancel={() => setDeletingToken(null)} + /> + ); }; diff --git a/react/src/components/DeploymentConfigurationSection.tsx b/react/src/components/DeploymentConfigurationSection.tsx index 5f61e16393..8629b8ee50 100644 --- a/react/src/components/DeploymentConfigurationSection.tsx +++ b/react/src/components/DeploymentConfigurationSection.tsx @@ -5,11 +5,13 @@ import { DeploymentConfigurationSectionQuery } from '../__generated__/DeploymentConfigurationSectionQuery.graphql'; import { useWebUINavigate } from '../hooks'; import DeploymentRevisionDetailDrawer from './DeploymentRevisionDetailDrawer'; +import { useFolderExplorerOpener } from './FolderExplorerOpener'; import SourceCodeView from './SourceCodeView'; import { CheckOutlined, CloseOutlined, EditOutlined, + FolderOpenOutlined, LoadingOutlined, } from '@ant-design/icons'; import { @@ -58,7 +60,7 @@ type ModelDef = { const descriptionsProps = { bordered: true, - column: { xxl: 3, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }, + column: { xs: 1, sm: 1, md: 2, lg: 2, xl: 2, xxl: 2 }, } as const; const renderFallback = () => ( @@ -220,6 +222,7 @@ const DeploymentRevisionInfoContent: React.FC<{ 'use memo'; const { t } = useTranslation(); const { token } = theme.useToken(); + const { open: openFolderExplorer } = useFolderExplorerOpener(); const currentRevision = deployment?.currentRevision; const deployingRevision = deployment?.deployingRevision; @@ -249,14 +252,37 @@ const DeploymentRevisionInfoContent: React.FC<{ key: 'model-folder', label: t('deployment.ModelFolder'), children: mountConfig?.vfolder?.name ? ( - - {mountConfig.vfolder.name} - {mountConfig.mountDestination && ( - - {mountConfig.mountDestination} - - )} - + (() => { + const localId = toLocalId(mountConfig.vfolder.id); + return ( + + + {mountConfig.vfolder.name} + - )} - {!isFirstStep && (