From 3214332961008d014a41255f564cc13a93b17154 Mon Sep 17 00:00:00 2001 From: agatha197 Date: Tue, 26 Aug 2025 16:52:16 +0900 Subject: [PATCH] feat(FR-1407): add modify and delete action buttons for deployment detail page --- .../src/components/Table/BAITable.tsx | 1 + react/src/components/AccessTokenList.tsx | 2 +- .../src/components/DeploymentModifyModal.tsx | 139 +++++++++++++++ .../DeploymentNetworkAccessFormItem.tsx | 4 +- .../components/DeploymentStrategyFormItem.tsx | 2 +- .../DeploymentTokenGenerationModal.tsx | 165 +++++++++++------- react/src/pages/DeploymentDetailPage.tsx | 136 ++++++++++++++- resources/i18n/de.json | 1 + resources/i18n/el.json | 1 + resources/i18n/en.json | 17 +- resources/i18n/es.json | 1 + resources/i18n/fi.json | 1 + resources/i18n/fr.json | 1 + resources/i18n/id.json | 1 + resources/i18n/it.json | 1 + resources/i18n/ja.json | 1 + resources/i18n/ko.json | 1 + resources/i18n/mn.json | 1 + resources/i18n/ms.json | 1 + resources/i18n/pl.json | 1 + resources/i18n/pt-BR.json | 1 + resources/i18n/pt.json | 1 + resources/i18n/ru.json | 1 + resources/i18n/th.json | 1 + resources/i18n/tr.json | 1 + resources/i18n/vi.json | 1 + resources/i18n/zh-CN.json | 1 + resources/i18n/zh-TW.json | 1 + 28 files changed, 415 insertions(+), 71 deletions(-) create mode 100644 react/src/components/DeploymentModifyModal.tsx diff --git a/packages/backend.ai-ui/src/components/Table/BAITable.tsx b/packages/backend.ai-ui/src/components/Table/BAITable.tsx index 9ed1255732..a06c280b4e 100644 --- a/packages/backend.ai-ui/src/components/Table/BAITable.tsx +++ b/packages/backend.ai-ui/src/components/Table/BAITable.tsx @@ -331,6 +331,7 @@ const BAITable = ({ opacity: loading ? 0.7 : 1, transition: 'opacity 0.3s ease', }} + scroll={tableProps.scroll || { x: 'max-content' }} components={ resizable ? _.merge(components || {}, { diff --git a/react/src/components/AccessTokenList.tsx b/react/src/components/AccessTokenList.tsx index d29f9be6e2..f30403a469 100644 --- a/react/src/components/AccessTokenList.tsx +++ b/react/src/components/AccessTokenList.tsx @@ -65,7 +65,7 @@ const AccessTokenList: React.FC = ({ }, { key: 'validUntil', - title: t('deployment.ExpiredDate'), + title: t('deployment.ExpirationDate'), dataIndex: 'validUntil', render: (value) => dayjs(value).format('LLL'), }, diff --git a/react/src/components/DeploymentModifyModal.tsx b/react/src/components/DeploymentModifyModal.tsx new file mode 100644 index 0000000000..655d9cebb4 --- /dev/null +++ b/react/src/components/DeploymentModifyModal.tsx @@ -0,0 +1,139 @@ +import BAIModal, { BAIModalProps } from './BAIModal'; +import DeploymentMetadataFormItem from './DeploymentMetadataFormItem'; +import DeploymentNetworkAccessFormItem from './DeploymentNetworkAccessFormItem'; +import DeploymentStrategyFormItem from './DeploymentStrategyFormItem'; +import { App, Form, FormInstance } from 'antd'; +import { toLocalId } from 'backend.ai-ui'; +import { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { graphql, useFragment, useMutation } from 'react-relay'; +import { + DeploymentModifyModalFragment$data, + DeploymentModifyModalFragment$key, +} from 'src/__generated__/DeploymentModifyModalFragment.graphql'; +import { DeploymentModifyModalMutation } from 'src/__generated__/DeploymentModifyModalMutation.graphql'; + +interface DeploymentModifyModalProps extends BAIModalProps { + deploymentFrgmt?: DeploymentModifyModalFragment$key | null; + onRequestClose: (success?: boolean) => void; +} + +const DeploymentModifyModal: React.FC = ({ + onRequestClose, + deploymentFrgmt, + ...baiModalProps +}) => { + const { t } = useTranslation(); + const { message } = App.useApp(); + const formRef = + useRef>(null); + + const deployment = useFragment( + graphql` + fragment DeploymentModifyModalFragment on ModelDeployment { + id + metadata { + name + tags + } + networkAccess { + openToPublic + preferredDomainName + } + defaultDeploymentStrategy { + type + } + replicaState { + desiredReplicaCount + } + } + `, + deploymentFrgmt, + ); + + const [commitUpdateDeployment, isInFlightUpdateDeployment] = + useMutation(graphql` + mutation DeploymentModifyModalMutation( + $input: UpdateModelDeploymentInput! + ) { + updateModelDeployment(input: $input) { + deployment { + id + metadata { + name + tags + } + networkAccess { + openToPublic + } + defaultDeploymentStrategy { + type + } + } + } + } + `); + + const handleOk = () => { + formRef.current?.validateFields().then((values) => { + commitUpdateDeployment({ + variables: { + input: { + id: toLocalId(deployment?.id || ''), + name: values.metadata?.name, + tags: values.metadata?.tags, + defaultDeploymentStrategy: values?.defaultDeploymentStrategy, + desiredReplicaCount: values.replicaState?.desiredReplicaCount, + preferredDomainName: values.networkAccess?.preferredDomainName, + openToPublic: values.networkAccess?.openToPublic, + }, + }, + onCompleted: (res, errors) => { + if (!res?.updateModelDeployment?.deployment?.id) { + message.error(t('message.FailedToUpdate')); + return; + } + if (errors && errors.length > 0) { + const errorMsgList = errors.map((error) => error.message); + for (const error of errorMsgList) { + message.error(error); + } + } else { + message.success(t('message.SuccessfullyUpdated')); + onRequestClose(true); + } + }, + onError: (err) => { + message.error(err.message || t('message.FailedToUpdate')); + }, + }); + }); + }; + + return ( + onRequestClose(false)} + okText={t('button.Update')} + confirmLoading={isInFlightUpdateDeployment} + title={t('deployment.ModifyDeployment')} + > +
+ + + + +
+ ); +}; + +export default DeploymentModifyModal; diff --git a/react/src/components/DeploymentNetworkAccessFormItem.tsx b/react/src/components/DeploymentNetworkAccessFormItem.tsx index 0cfc605c4d..060a557472 100644 --- a/react/src/components/DeploymentNetworkAccessFormItem.tsx +++ b/react/src/components/DeploymentNetworkAccessFormItem.tsx @@ -18,9 +18,7 @@ const DeploymentNetworkAccessFormItem: React.FC = () => { name={['networkAccess', 'preferredDomainName']} label={t('deployment.launcher.PreferredDomainName')} > - + { }} = ({ onRequestClose, onCancel, deploymentId, ...baiModalProps }) => { const { t } = useTranslation(); const formRef = useRef(null); + // expiryOption은 Form.Item에서 관리 const [commitCreateAccessToken, isInFlightCreateAccessToken] = useMutation(graphql` @@ -36,41 +37,51 @@ const DeploymentTokenGenerationModal: React.FC< `); const handleOk = () => { - formRef.current?.validateFields().then((values) => { - const validUntil = values.datetime.unix(); - commitCreateAccessToken({ - variables: { - input: { - validUntil: validUntil, - modelDeploymentId: deploymentId, + formRef.current + ?.validateFields() + .then((values) => { + let validUntil; + if (values.expiryOption === 'custom') { + validUntil = values.datetime.unix(); + } else { + const daysToAdd = parseInt(values.expiryOption.replace('days', '')); + validUntil = dayjs().add(daysToAdd, 'day').unix(); + } + + commitCreateAccessToken({ + variables: { + input: { + validUntil: validUntil, + modelDeploymentId: deploymentId, + }, }, - }, - onCompleted: (res, errors) => { - if (!res?.createAccessToken?.accessToken) { - message.error(t('deployment.TokenGenerationFailed')); - return; - } - if (errors && errors.length > 0) { - const errorMsgList = errors.map((error) => error.message); - for (const error of errorMsgList) { - message.error(error); + onCompleted: (res, errors) => { + if (!res?.createAccessToken?.accessToken) { + message.error(t('deployment.TokenGenerationFailed')); + return; } - } else { - message.success(t('deployment.TokenGenerated')); - onRequestClose(true); - } - }, - onError: (err) => { - if (err?.message?.includes('valid_until is older than now')) { - message.error(t('deployment.TokenExpiredDateError')); - return; - } else { - message.error(t('deployment.TokenGenerationFailed')); - console.log(err); - } - }, - }); - }); + if (errors && errors.length > 0) { + const errorMsgList = errors.map((error) => error.message); + for (const error of errorMsgList) { + message.error(error); + } + } else { + message.success(t('deployment.TokenGenerated')); + onRequestClose(true); + } + }, + onError: (err) => { + if (err?.message?.includes('valid_until is older than now')) { + message.error(t('deployment.TokenExpiredDateError')); + return; + } else { + message.error(t('deployment.TokenGenerationFailed')); + console.log(err); + } + }, + }); + }) + .catch(() => {}); }; return ( @@ -81,45 +92,75 @@ const DeploymentTokenGenerationModal: React.FC< onCancel={() => onRequestClose(false)} okText={t('button.Generate')} confirmLoading={isInFlightCreateAccessToken} - centered title={t('deployment.GenerateNewToken')} >
- ({ - validator(_, value) { - if (value.isAfter(dayjs())) { - return Promise.resolve(); - } - return Promise.reject( - new Error(t('deployment.TokenExpiredDateError')), - ); + + - + ]} + > +