Skip to content

Commit 1eded45

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

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
@@ -128,6 +136,7 @@ const DeploymentDetailPage: React.FC = () => {
128136
createdUser {
129137
email
130138
}
139+
...DeploymentModifyModalFragment
131140
}
132141
}
133142
`,
@@ -151,6 +160,19 @@ const DeploymentDetailPage: React.FC = () => {
151160
`,
152161
);
153162

163+
const [commitDeleteDeployment, isInFlightDeleteDeployment] =
164+
useMutation<DeploymentDetailPageDeleteMutation>(graphql`
165+
mutation DeploymentDetailPageDeleteMutation(
166+
$input: DeleteModelDeploymentInput!
167+
) {
168+
deleteModelDeployment(input: $input) {
169+
deployment {
170+
id
171+
}
172+
}
173+
}
174+
`);
175+
154176
const deploymentInfoItems: DescriptionsProps['items'] = [
155177
{
156178
key: 'name',
@@ -255,7 +277,7 @@ const DeploymentDetailPage: React.FC = () => {
255277
<Typography.Title level={3} style={{ margin: 0 }}>
256278
{deployment?.metadata?.name || ''}
257279
</Typography.Title>
258-
<BAIFlex gap={'xxs'}>
280+
<BAIFlex gap={'xs'}>
259281
<Tooltip title={t('button.Refresh')}>
260282
<Button
261283
loading={isPendingRefetch}
@@ -267,6 +289,34 @@ const DeploymentDetailPage: React.FC = () => {
267289
}}
268290
/>
269291
</Tooltip>
292+
<Button
293+
onClick={() => {
294+
toggleModifyModal();
295+
}}
296+
>
297+
{t('button.Edit')}
298+
</Button>
299+
<Dropdown
300+
menu={{
301+
items: [
302+
{
303+
key: 'delete',
304+
label: t('button.Delete'),
305+
onClick: () => {
306+
toggleDeleteModal();
307+
},
308+
},
309+
],
310+
}}
311+
trigger={['click']}
312+
>
313+
<Button>
314+
<BAIFlex gap={'xxs'}>
315+
{t('button.Action')}
316+
<DownOutlined />
317+
</BAIFlex>
318+
</Button>
319+
</Dropdown>
270320
</BAIFlex>
271321
</BAIFlex>
272322

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

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)