Skip to content

Commit 0497f90

Browse files
committed
feat(FR-1371): Implement model deployment UI components
1 parent 841aace commit 0497f90

File tree

8 files changed

+631
-31
lines changed

8 files changed

+631
-31
lines changed

data/schema.graphql

Lines changed: 159 additions & 27 deletions
Large diffs are not rendered by default.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { Resizable, ResizeCallbackData } from 'react-resizable';
2424
* Configuration interface for BAITable pagination
2525
* Extends Ant Design's TablePaginationConfig but omits 'position' property
2626
*/
27-
interface BAITablePaginationConfig
27+
export interface BAITablePaginationConfig
2828
extends Omit<TablePaginationConfig, 'position'> {
2929
/** Additional content to display in the pagination area */
3030
extraContent?: ReactNode;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ const BAITableSettingModal: React.FC<TableSettingProps> = ({
442442
style={{
443443
height: 330,
444444
}}
445-
scroll={{ x: 'max-content' }}
445+
scroll={{ x: 'max-content', y: 330 }}
446446
/>
447447
</SortableContext>
448448
</DndContext>

packages/backend.ai-ui/src/components/Table/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type {
44
BAIColumnType,
55
BAIColumnsType,
66
BAITableSettings,
7+
BAITablePaginationConfig,
78
BAITableColumnOverrideItem,
89
} from './BAITable';
910
export {

react/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,11 @@ const ChatPage = React.lazy(() => import('./pages/ChatPage'));
8383
const AIAgentPage = React.lazy(() => import('./pages/AIAgentPage'));
8484

8585
// Deployment pages
86+
// const DeploymentListPage = React.lazy(
87+
// () => import('./pages/Deployments/DeploymentListPage'),
88+
// );
8689
const DeploymentListPage = React.lazy(
87-
() => import('./pages/Deployments/DeploymentListPage'),
90+
() => import('./pages/DeploymentListPage'),
8891
);
8992
const DeploymentDetailPage = React.lazy(
9093
() => import('./pages/Deployments/DeploymentDetailPage'),
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import ResourceNumber from './ResourceNumber';
2+
import WebUILink from './WebUILink';
3+
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
4+
import { Tag, theme, Tooltip, Typography } from 'antd';
5+
import {
6+
BAIColumnType,
7+
BAIFlex,
8+
BAITable,
9+
BAITablePaginationConfig,
10+
BAITableProps,
11+
filterOutEmpty,
12+
filterOutNullAndUndefined,
13+
} from 'backend.ai-ui';
14+
import dayjs from 'dayjs';
15+
import _ from 'lodash';
16+
import { ExternalLinkIcon } from 'lucide-react';
17+
import { useTranslation } from 'react-i18next';
18+
import { useFragment } from 'react-relay';
19+
import { graphql } from 'relay-runtime';
20+
import {
21+
DeploymentListFragment$data,
22+
DeploymentListFragment$key,
23+
} from 'src/__generated__/DeploymentListFragment.graphql';
24+
import { useSuspendedBackendaiClient } from 'src/hooks';
25+
26+
type ModelDeployment = NonNullable<
27+
NonNullable<DeploymentListFragment$data>[number]
28+
>;
29+
interface DeploymentListProps
30+
extends Omit<BAITableProps<ModelDeployment>, 'dataSource' | 'columns'> {
31+
deploymentsFragment: DeploymentListFragment$key;
32+
pagination: BAITablePaginationConfig;
33+
}
34+
35+
const DeploymentList: React.FC<DeploymentListProps> = ({
36+
deploymentsFragment,
37+
pagination,
38+
...tableProps
39+
}) => {
40+
const { t } = useTranslation();
41+
const { token } = theme.useToken();
42+
const baiClient = useSuspendedBackendaiClient();
43+
44+
const deployments = useFragment(
45+
graphql`
46+
fragment DeploymentListFragment on ModelDeployment @relay(plural: true) {
47+
id
48+
metadata {
49+
name
50+
createdAt
51+
updatedAt
52+
tags
53+
}
54+
networkAccess {
55+
endpointUrl
56+
openToPublic
57+
}
58+
revision {
59+
id
60+
name
61+
clusterConfig {
62+
size
63+
}
64+
resourceConfig {
65+
resourceGroup {
66+
name
67+
}
68+
resourceSlots
69+
resourceOpts
70+
}
71+
}
72+
replicaState {
73+
desiredReplicaCount
74+
}
75+
deploymentStrategy {
76+
type
77+
}
78+
createdUser {
79+
email
80+
}
81+
}
82+
`,
83+
deploymentsFragment,
84+
);
85+
86+
const filteredDeployments = filterOutNullAndUndefined(deployments);
87+
const columns = _.map(
88+
filterOutEmpty<BAIColumnType<ModelDeployment>>([
89+
{
90+
title: t('deployment.DeploymentName'),
91+
key: 'name',
92+
dataIndex: ['metadata', 'name'],
93+
fixed: 'left',
94+
render: (name, row) => (
95+
<WebUILink to={`/deployment/${row.id}`}>{name}</WebUILink>
96+
),
97+
},
98+
{
99+
title: t('deployment.EndpointURL'),
100+
key: 'endpointUrl',
101+
dataIndex: ['networkAccess', 'endpointUrl'],
102+
render: (url) => (
103+
<BAIFlex gap={'xxs'}>
104+
{url ? (
105+
<>
106+
<Typography.Text copyable>{url}</Typography.Text>
107+
<a
108+
href={url}
109+
title=""
110+
target="_blank"
111+
rel="noopener noreferrer"
112+
>
113+
<Tooltip title={t('common.OpenInNewTab')}>
114+
<ExternalLinkIcon />
115+
</Tooltip>
116+
</a>
117+
</>
118+
) : (
119+
'-'
120+
)}
121+
</BAIFlex>
122+
),
123+
},
124+
{
125+
title: t('deployment.Public'),
126+
key: 'openToPublic',
127+
dataIndex: ['networkAccess', 'openToPublic'],
128+
render: (openToPublic) =>
129+
openToPublic ? (
130+
<CheckOutlined style={{ color: token.colorSuccess }} />
131+
) : (
132+
<CloseOutlined style={{ color: token.colorTextSecondary }} />
133+
),
134+
},
135+
{
136+
title: t('deployment.Tags'),
137+
dataIndex: ['metadata', 'tags'],
138+
key: 'tags',
139+
render: (tags) => _.map(tags, (tag) => <Tag key={tag}>{tag}</Tag>),
140+
},
141+
{
142+
title: t('deployment.NumberOfDesiredReplicas'),
143+
key: 'desiredReplicaCount',
144+
dataIndex: ['replicaState', 'desiredReplicaCount'],
145+
render: (count) => count,
146+
defaultHidden: true,
147+
},
148+
{
149+
title: t('deployment.Resources'),
150+
dataIndex: ['revision', 'resourceConfig', 'resourceSlots'],
151+
key: 'resourceSlots',
152+
render: (resourceSlots) => (
153+
<BAIFlex direction="row" gap="xs">
154+
{_.map(JSON.parse(resourceSlots || '{}'), (value, key) => (
155+
<ResourceNumber key={key} type={key} value={value.toString()} />
156+
))}
157+
</BAIFlex>
158+
),
159+
defaultHidden: true,
160+
},
161+
{
162+
title: t('deployment.ClusterSize'),
163+
dataIndex: ['revision', 'clusterConfig', 'size'],
164+
key: 'clusterSize',
165+
render: (size) => <Typography.Text>{size}</Typography.Text>,
166+
167+
defaultHidden: true,
168+
},
169+
{
170+
title: t('deployment.DeploymentStrategy'),
171+
dataIndex: ['deploymentStrategy', 'type'],
172+
key: 'type',
173+
render: (type) => (
174+
<Tag
175+
color={
176+
type === 'ROLLING'
177+
? 'default'
178+
: type === 'BLUE_GREEN'
179+
? 'blue'
180+
: 'yellow'
181+
}
182+
>
183+
{type}
184+
</Tag>
185+
),
186+
},
187+
{
188+
title: t('deployment.RevisionName'),
189+
dataIndex: ['revision', 'name'],
190+
key: 'revisionName',
191+
render: (name, row) => (
192+
<WebUILink to={`/deployment/revision/${row.id}`}>{name}</WebUILink>
193+
),
194+
defaultHidden: true,
195+
},
196+
{
197+
title: t('deployment.CreatedAt'),
198+
dataIndex: ['metadata', 'createdAt'],
199+
key: 'createdAt',
200+
render: (createdAt) => {
201+
return dayjs(createdAt).format('ll LT');
202+
},
203+
},
204+
{
205+
title: t('deployment.UpdatedAt'),
206+
dataIndex: ['metadata', 'updatedAt'],
207+
key: 'updatedAt',
208+
render: (updatedAt) => {
209+
return dayjs(updatedAt).format('ll LT');
210+
},
211+
defaultHidden: true,
212+
},
213+
baiClient.is_admin && {
214+
title: t('deployment.CreatedBy'),
215+
dataIndex: ['createdUser', 'email'],
216+
key: 'createdBy',
217+
render: (email) => <Typography.Text>{email}</Typography.Text>,
218+
},
219+
]),
220+
);
221+
222+
return (
223+
<BAITable
224+
resizable
225+
rowKey={'id'}
226+
size="small"
227+
dataSource={filteredDeployments}
228+
columns={columns}
229+
pagination={pagination}
230+
scroll={{ x: 'max-content' }}
231+
{...tableProps}
232+
/>
233+
);
234+
};
235+
236+
export default DeploymentList;

0 commit comments

Comments
 (0)