Skip to content

Commit 049546a

Browse files
committed
feat(FR-1407): add modify and delete action buttons for deployment detail page
1 parent cec5b54 commit 049546a

27 files changed

+303
-8
lines changed

packages/backend.ai-ui/src/components/Table/BAITable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ const BAITable = <RecordType extends object = any>({
331331
opacity: loading ? 0.7 : 1,
332332
transition: 'opacity 0.3s ease',
333333
}}
334+
scroll={tableProps.scroll || { x: 'max-content' }}
334335
components={
335336
resizable
336337
? _.merge(components || {}, {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import BAIModal, { BAIModalProps } from './BAIModal';
2+
import DeploymentMetadataFormItem from './DeploymentMetadataFormItem';
3+
import DeploymentNetworkAccessFormItem from './DeploymentNetworkAccessFormItem';
4+
import DeploymentStrategyFormItem from './DeploymentStrategyFormItem';
5+
import { App, Form, FormInstance } from 'antd';
6+
import { toLocalId } from 'backend.ai-ui';
7+
import { useRef } from 'react';
8+
import { useTranslation } from 'react-i18next';
9+
import { graphql, useFragment, useMutation } from 'react-relay';
10+
import {
11+
DeploymentModifyModalFragment$data,
12+
DeploymentModifyModalFragment$key,
13+
} from 'src/__generated__/DeploymentModifyModalFragment.graphql';
14+
import { DeploymentModifyModalMutation } from 'src/__generated__/DeploymentModifyModalMutation.graphql';
15+
16+
interface DeploymentModifyModalProps extends BAIModalProps {
17+
deploymentFrgmt?: DeploymentModifyModalFragment$key | null;
18+
onRequestClose: (success?: boolean) => void;
19+
}
20+
21+
const DeploymentModifyModal: React.FC<DeploymentModifyModalProps> = ({
22+
onRequestClose,
23+
deploymentFrgmt,
24+
...baiModalProps
25+
}) => {
26+
const { t } = useTranslation();
27+
const { message } = App.useApp();
28+
const formRef =
29+
useRef<FormInstance<DeploymentModifyModalFragment$data>>(null);
30+
31+
const deployment = useFragment(
32+
graphql`
33+
fragment DeploymentModifyModalFragment on ModelDeployment {
34+
id
35+
metadata {
36+
name
37+
tags
38+
}
39+
networkAccess {
40+
openToPublic
41+
preferredDomainName
42+
}
43+
defaultDeploymentStrategy {
44+
type
45+
}
46+
replicaState {
47+
desiredReplicaCount
48+
}
49+
}
50+
`,
51+
deploymentFrgmt,
52+
);
53+
54+
const [commitUpdateDeployment, isInFlightUpdateDeployment] =
55+
useMutation<DeploymentModifyModalMutation>(graphql`
56+
mutation DeploymentModifyModalMutation(
57+
$input: UpdateModelDeploymentInput!
58+
) {
59+
updateModelDeployment(input: $input) {
60+
deployment {
61+
id
62+
metadata {
63+
name
64+
tags
65+
}
66+
networkAccess {
67+
openToPublic
68+
}
69+
defaultDeploymentStrategy {
70+
type
71+
}
72+
}
73+
}
74+
}
75+
`);
76+
77+
const handleOk = () => {
78+
formRef.current?.validateFields().then((values) => {
79+
commitUpdateDeployment({
80+
variables: {
81+
input: {
82+
id: toLocalId(deployment?.id || ''),
83+
name: values.metadata?.name,
84+
tags: values.metadata?.tags,
85+
defaultDeploymentStrategy: values?.defaultDeploymentStrategy,
86+
desiredReplicaCount: values.replicaState?.desiredReplicaCount,
87+
preferredDomainName: values.networkAccess?.preferredDomainName,
88+
openToPublic: values.networkAccess?.openToPublic,
89+
},
90+
},
91+
onCompleted: (res, errors) => {
92+
if (!res?.updateModelDeployment?.deployment?.id) {
93+
message.error(t('message.FailedToUpdate'));
94+
return;
95+
}
96+
if (errors && errors.length > 0) {
97+
const errorMsgList = errors.map((error) => error.message);
98+
for (const error of errorMsgList) {
99+
message.error(error);
100+
}
101+
} else {
102+
message.success(t('message.SuccessfullyUpdated'));
103+
onRequestClose(true);
104+
}
105+
},
106+
onError: (err) => {
107+
message.error(err.message || t('message.FailedToUpdate'));
108+
},
109+
});
110+
});
111+
};
112+
113+
return (
114+
<BAIModal
115+
{...baiModalProps}
116+
destroyOnClose
117+
onOk={handleOk}
118+
onCancel={() => onRequestClose(false)}
119+
okText={t('button.Update')}
120+
confirmLoading={isInFlightUpdateDeployment}
121+
title={t('deployment.ModifyDeployment')}
122+
>
123+
<Form
124+
ref={formRef}
125+
layout="vertical"
126+
initialValues={{
127+
...deployment,
128+
}}
129+
style={{ maxWidth: '100%' }}
130+
>
131+
<DeploymentMetadataFormItem />
132+
<DeploymentStrategyFormItem />
133+
<DeploymentNetworkAccessFormItem />
134+
</Form>
135+
</BAIModal>
136+
);
137+
};
138+
139+
export default DeploymentModifyModal;

react/src/components/DeploymentNetworkAccessFormItem.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ const DeploymentNetworkAccessFormItem: React.FC = () => {
1818
name={['networkAccess', 'preferredDomainName']}
1919
label={t('deployment.launcher.PreferredDomainName')}
2020
>
21-
<Input
22-
placeholder={t('deployment.launcher.PreferredDomainNamePlaceholder')}
23-
/>
21+
<Input placeholder={'my-model.example.com'} />
2422
</Form.Item>
2523

2624
<Form.Item

react/src/components/DeploymentStrategyFormItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const DeploymentStrategyFormItem: React.FC = () => {
103103
}}
104104
</Form.Item>
105105
<Form.Item
106-
name={['desiredReplicaCount']}
106+
name={['replicaState', 'desiredReplicaCount']}
107107
label={t('deployment.NumberOfDesiredReplicas')}
108108
rules={[
109109
{

react/src/components/DeploymentTokenGenerationModal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ const DeploymentTokenGenerationModal: React.FC<
8181
onCancel={() => onRequestClose(false)}
8282
okText={t('button.Generate')}
8383
confirmLoading={isInFlightCreateAccessToken}
84-
centered
8584
title={t('deployment.GenerateNewToken')}
8685
>
8786
<Form

react/src/pages/DeploymentDetailPage.tsx

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import AccessTokenList from '../components/AccessTokenList';
2+
import BAIConfirmModalWithInput from '../components/BAIConfirmModalWithInput';
23
import DeploymentRevisionList from '../components/DeploymentRevisionList';
34
import FlexActivityIndicator from '../components/FlexActivityIndicator';
45
import {
56
CheckOutlined,
67
CloseOutlined,
8+
DownOutlined,
79
ReloadOutlined,
810
} from '@ant-design/icons';
911
import { useToggle } from 'ahooks';
1012
import {
13+
Alert,
1114
App,
1215
Button,
1316
Descriptions,
17+
Dropdown,
1418
Tag,
1519
theme,
1620
Tooltip,
@@ -30,10 +34,12 @@ import { Suspense, useState, useTransition } from 'react';
3034
import { Trans, useTranslation } from 'react-i18next';
3135
import { graphql, useLazyLoadQuery, useMutation } from 'react-relay';
3236
import { useParams } from 'react-router-dom';
37+
import { DeploymentDetailPageDeleteMutation } from 'src/__generated__/DeploymentDetailPageDeleteMutation.graphql';
3338
import {
3439
DeploymentDetailPageQuery,
3540
DeploymentDetailPageQuery$data,
3641
} from 'src/__generated__/DeploymentDetailPageQuery.graphql';
42+
import DeploymentModifyModal from 'src/components/DeploymentModifyModal';
3743
import DeploymentTokenGenerationModal from 'src/components/DeploymentTokenGenerationModal';
3844
import RevisionCreationModal from 'src/components/RevisionCreationModal';
3945
import RouteList from 'src/components/RouteList';
@@ -60,12 +66,14 @@ const DeploymentDetailPage: React.FC = () => {
6066
useToggle();
6167
const [isTokenGenerationModalOpen, { toggle: toggleTokenGenerationModal }] =
6268
useToggle();
69+
const [isModifyModalOpen, { toggle: toggleModifyModal }] = useToggle();
70+
const [isDeleteModalOpen, { toggle: toggleDeleteModal }] = useToggle();
6371

6472
const { deployment } = useLazyLoadQuery<DeploymentDetailPageQuery>(
6573
graphql`
6674
query DeploymentDetailPageQuery($deploymentId: ID!) {
6775
deployment(id: $deploymentId) {
68-
id
76+
id @required(action: THROW)
6977
metadata {
7078
name
7179
status
@@ -131,6 +139,7 @@ const DeploymentDetailPage: React.FC = () => {
131139
createdUser {
132140
email
133141
}
142+
...DeploymentModifyModalFragment
134143
}
135144
}
136145
`,
@@ -154,6 +163,19 @@ const DeploymentDetailPage: React.FC = () => {
154163
`,
155164
);
156165

166+
const [commitDeleteDeployment, isInFlightDeleteDeployment] =
167+
useMutation<DeploymentDetailPageDeleteMutation>(graphql`
168+
mutation DeploymentDetailPageDeleteMutation(
169+
$input: DeleteModelDeploymentInput!
170+
) {
171+
deleteModelDeployment(input: $input) {
172+
deployment {
173+
id
174+
}
175+
}
176+
}
177+
`);
178+
157179
const deploymentInfoItems: DescriptionsProps['items'] = [
158180
{
159181
key: 'name',
@@ -258,7 +280,7 @@ const DeploymentDetailPage: React.FC = () => {
258280
<Typography.Title level={3} style={{ margin: 0 }}>
259281
{deployment?.metadata?.name || ''}
260282
</Typography.Title>
261-
<BAIFlex gap={'xxs'}>
283+
<BAIFlex gap={'xs'}>
262284
<Tooltip title={t('button.Refresh')}>
263285
<Button
264286
loading={isPendingRefetch}
@@ -270,6 +292,34 @@ const DeploymentDetailPage: React.FC = () => {
270292
}}
271293
/>
272294
</Tooltip>
295+
<Button
296+
onClick={() => {
297+
toggleModifyModal();
298+
}}
299+
>
300+
{t('button.Edit')}
301+
</Button>
302+
<Dropdown
303+
menu={{
304+
items: [
305+
{
306+
key: 'delete',
307+
label: t('button.Delete'),
308+
onClick: () => {
309+
toggleDeleteModal();
310+
},
311+
},
312+
],
313+
}}
314+
trigger={['click']}
315+
>
316+
<Button>
317+
<BAIFlex gap={'xxs'}>
318+
{t('button.Action')}
319+
<DownOutlined />
320+
</BAIFlex>
321+
</Button>
322+
</Dropdown>
273323
</BAIFlex>
274324
</BAIFlex>
275325

@@ -492,6 +542,85 @@ const DeploymentDetailPage: React.FC = () => {
492542
toggleTokenGenerationModal();
493543
}}
494544
/>
545+
546+
<Suspense>
547+
<DeploymentModifyModal
548+
deploymentFrgmt={deployment}
549+
open={isModifyModalOpen}
550+
onRequestClose={(success) => {
551+
if (success) {
552+
startRefetchTransition(() => {
553+
updateFetchKey();
554+
});
555+
}
556+
toggleModifyModal();
557+
}}
558+
/>
559+
</Suspense>
560+
561+
<BAIConfirmModalWithInput
562+
open={isDeleteModalOpen}
563+
onOk={() => {
564+
commitDeleteDeployment({
565+
variables: {
566+
input: {
567+
id: deploymentId || '',
568+
},
569+
},
570+
onCompleted: (res, errors) => {
571+
if (!res?.deleteModelDeployment?.deployment?.id) {
572+
message.error(t('message.FailedToDelete'));
573+
return;
574+
}
575+
if (errors && errors.length > 0) {
576+
const errorMsgList = _.map(errors, (error) => error.message);
577+
for (const error of errorMsgList) {
578+
message.error(error);
579+
}
580+
} else {
581+
message.success(t('message.SuccessfullyDeleted'));
582+
}
583+
},
584+
onError: (err) => {
585+
message.error(err.message || t('message.FailedToDelete'));
586+
},
587+
});
588+
toggleDeleteModal();
589+
}}
590+
onCancel={() => {
591+
toggleDeleteModal();
592+
}}
593+
confirmText={deployment?.metadata?.name ?? ''}
594+
content={
595+
<BAIFlex
596+
direction="column"
597+
gap="md"
598+
align="stretch"
599+
style={{ marginBottom: token.marginXS, width: '100%' }}
600+
>
601+
<Alert
602+
type="warning"
603+
message={t('dialog.warning.DeleteForeverDesc')}
604+
style={{ width: '100%' }}
605+
/>
606+
<BAIFlex>
607+
<Typography.Text style={{ marginRight: token.marginXXS }}>
608+
{t('deployment.TypeDeploymentNameToDelete')}
609+
</Typography.Text>
610+
(
611+
<Typography.Text code>
612+
{deployment?.metadata?.name}
613+
</Typography.Text>
614+
)
615+
</BAIFlex>
616+
</BAIFlex>
617+
}
618+
title={t('deployment.DeleteDeployment')}
619+
okText={t('button.Delete')}
620+
okButtonProps={{
621+
loading: isInFlightDeleteDeployment,
622+
}}
623+
/>
495624
</BAIFlex>
496625
);
497626
};

resources/i18n/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"Threshold": "Schwelle"
5151
},
5252
"button": {
53+
"Action": "Aktion",
5354
"Add": "Hinzufügen",
5455
"Apply": "Bewerbung",
5556
"Cancel": "Stornieren",

resources/i18n/el.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"Threshold": "Κατώφλι"
5151
},
5252
"button": {
53+
"Action": "Δράση",
5354
"Add": "Προσθήκη",
5455
"Apply": "Εφαρμογή",
5556
"Cancel": "Ματαίωση",

0 commit comments

Comments
 (0)