Skip to content

Commit b2cc42d

Browse files
committed
feat(FR-1691): migrate project table to the webui
1 parent 5ac5242 commit b2cc42d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+24711
-12755
lines changed

packages/backend.ai-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"@storybook/react-vite": "^9.1.1",
137137
"@testing-library/jest-dom": "^6.6.3",
138138
"@testing-library/react": "^16.2.0",
139+
"@testing-library/user-event": "^14.6.1",
139140
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
140141
"@types/big.js": "^6.2.2",
141142
"@types/lodash": "^4.17.20",

react/src/components/NumberWithUnit.tsx renamed to packages/backend.ai-ui/src/components/BAINumberWithUnit.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { convertToBinaryUnit, convertToDecimalUnit, SizeUnit } from '../helper';
2+
import BAIFlex from './BAIFlex';
23
import { Typography } from 'antd';
3-
import { BAIFlex } from 'backend.ai-ui';
44

5-
interface NumberWithUnitProps {
5+
interface BAINumberWithUnitProps {
66
numberUnit: string;
77
targetUnit: SizeUnit;
88
unitType: 'binary' | 'decimal';
99
postfix?: string;
1010
}
1111

12-
const NumberWithUnit = ({
12+
const BAINumberWithUnit = ({
1313
numberUnit,
1414
targetUnit,
1515
unitType,
1616
postfix,
17-
}: NumberWithUnitProps) => {
17+
}: BAINumberWithUnitProps) => {
1818
const convertedByTargetUnit =
1919
unitType === 'binary'
2020
? convertToBinaryUnit(numberUnit, targetUnit, 2, true)
@@ -41,4 +41,4 @@ const NumberWithUnit = ({
4141
);
4242
};
4343

44-
export default NumberWithUnit;
44+
export default BAINumberWithUnit;
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { convertToBinaryUnit } from '../helper';
2+
import { BAINvidiaIcon } from '../icons';
3+
import BAIFuriosaIcon from '../icons/BAIFuriosaIcon';
4+
import BAIGaudiIcon from '../icons/BAIGaudiIcon';
5+
import BAIIpuIcon from '../icons/BAIIpuIcon';
6+
import BAIRebelIcon from '../icons/BAIRebelIcon';
7+
import BAIRocmIcon from '../icons/BAIRocmIcon';
8+
import BAITpuIcon from '../icons/BAITpuIcon';
9+
import BAIFlex from './BAIFlex';
10+
import NumberWithUnit from './BAINumberWithUnit';
11+
import BAIText from './BAIText';
12+
import { ResourceSlotName, useBAIDeviceMetaData } from './provider';
13+
import { theme, Tooltip } from 'antd';
14+
import _ from 'lodash';
15+
import { CpuIcon, MemoryStickIcon, MicrochipIcon } from 'lucide-react';
16+
import { ReactNode } from 'react';
17+
18+
export type ResourceOpts = {
19+
shmem?: number;
20+
};
21+
22+
export interface BAIResourceNumberWithIconProps {
23+
type: string;
24+
extra?: ReactNode;
25+
opts?: ResourceOpts;
26+
value: string;
27+
hideTooltip?: boolean;
28+
max?: string;
29+
}
30+
31+
const BAIResourceNumberWithIcon = ({
32+
type,
33+
extra,
34+
opts,
35+
value: amount,
36+
max,
37+
hideTooltip = false,
38+
}: BAIResourceNumberWithIconProps) => {
39+
'use memo';
40+
41+
const deviceMetaData = useBAIDeviceMetaData();
42+
const { token } = theme.useToken();
43+
44+
const formatAmount = (amount: string) => {
45+
return deviceMetaData?.[type]?.number_format.binary
46+
? Number(
47+
convertToBinaryUnit(amount, 'g', 2, true)?.numberFixed,
48+
).toString()
49+
: (deviceMetaData?.[type]?.number_format.round_length || 0) > 0
50+
? parseFloat(amount).toFixed(2)
51+
: amount;
52+
};
53+
54+
return (
55+
<BAIFlex direction="row" gap="xxs">
56+
{deviceMetaData?.[type] ? (
57+
<ResourceTypeIcon type={type} showTooltip={!hideTooltip} />
58+
) : (
59+
type
60+
)}
61+
{deviceMetaData?.[type]?.number_format.binary ? (
62+
<NumberWithUnit
63+
numberUnit={amount}
64+
targetUnit="g"
65+
unitType="binary"
66+
postfix={
67+
_.isUndefined(max)
68+
? ''
69+
: max === 'Infinity'
70+
? '~∞'
71+
: `~${formatAmount(max)}`
72+
}
73+
/>
74+
) : (
75+
<>
76+
<BAIText>
77+
{formatAmount(amount)}
78+
{_.isUndefined(max)
79+
? null
80+
: max === 'Infinity'
81+
? '~∞'
82+
: `~${formatAmount(max)}`}
83+
</BAIText>
84+
<BAIText type="secondary" style={{ whiteSpace: 'nowrap' }}>
85+
{deviceMetaData?.[type]?.display_unit || ''}
86+
</BAIText>
87+
</>
88+
)}
89+
90+
{type === 'mem' && opts?.shmem && opts?.shmem > 0 ? (
91+
<BAIText type="secondary" style={{ fontSize: token.fontSizeSM }}>
92+
(SHM: {convertToBinaryUnit(opts.shmem, 'g', 2, true)?.numberFixed}
93+
GiB)
94+
</BAIText>
95+
) : null}
96+
{extra}
97+
</BAIFlex>
98+
);
99+
};
100+
101+
const knownDeviceIcons = {
102+
gaudi: <BAIGaudiIcon />,
103+
furiosa: <BAIFuriosaIcon />,
104+
tpu: <BAITpuIcon />,
105+
ipu: <BAIIpuIcon />,
106+
nvidia: <BAINvidiaIcon />,
107+
rocm: <BAIRocmIcon />,
108+
rebel: <BAIRebelIcon />,
109+
} as const;
110+
111+
interface ResourceTypeIconProps {
112+
type: ResourceSlotName | string;
113+
showTooltip?: boolean;
114+
size?: number;
115+
}
116+
117+
const ResourceTypeIcon = ({
118+
type,
119+
showTooltip = true,
120+
size = 16,
121+
}: ResourceTypeIconProps) => {
122+
'use memo';
123+
124+
const deviceMetaData = useBAIDeviceMetaData();
125+
126+
const getIconContent = () => {
127+
if (type === 'cpu') {
128+
return (
129+
<BAIFlex style={{ width: size, height: size }}>
130+
<CpuIcon />
131+
</BAIFlex>
132+
);
133+
}
134+
if (type === 'mem') {
135+
return (
136+
<BAIFlex style={{ width: size, height: size }}>
137+
<MemoryStickIcon />
138+
</BAIFlex>
139+
);
140+
}
141+
142+
const displayIcon = deviceMetaData[type]?.display_icon;
143+
144+
if (displayIcon && _.keys(knownDeviceIcons).includes(displayIcon)) {
145+
return (
146+
knownDeviceIcons[displayIcon as keyof typeof knownDeviceIcons] ?? null
147+
);
148+
}
149+
150+
return (
151+
<BAIFlex style={{ width: size, height: size }}>
152+
<MicrochipIcon />
153+
</BAIFlex>
154+
);
155+
};
156+
157+
const content = getIconContent();
158+
159+
return showTooltip ? (
160+
<Tooltip title={deviceMetaData[type]?.description || type}>
161+
{content}
162+
</Tooltip>
163+
) : (
164+
<BAIFlex style={{ pointerEvents: 'none' }}>{content}</BAIFlex>
165+
);
166+
};
167+
168+
export default BAIResourceNumberWithIcon;

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,18 @@ export interface BAIColumnType<RecordType = any>
7575
required?: boolean;
7676
}
7777

78+
export interface BAIColumnGroupType<RecordType = AnyObject>
79+
extends Omit<BAIColumnType<RecordType>, 'dataIndex'> {
80+
children: ColumnsType<RecordType>;
81+
}
82+
7883
/**
7984
* Array type for BAI table columns
8085
*/
81-
export type BAIColumnsType<RecordType = any> = BAIColumnType<RecordType>[];
86+
export type BAIColumnsType<RecordType = any> = (
87+
| BAIColumnGroupType<RecordType>
88+
| BAIColumnType<RecordType>
89+
)[];
8290

8391
/**
8492
* Utility function to determine if a column should be visible

react/src/components/AllowedVfolderHostsWithPermission.tsx renamed to packages/backend.ai-ui/src/components/fragments/BAIAllowedVfolderHostsWithPermission.tsx

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,67 @@
1-
import { AllowedVfolderHostsWithPermissionFragment$key } from '../__generated__/AllowedVfolderHostsWithPermissionFragment.graphql';
2-
import { AllowedVfolderHostsWithPermissionQuery } from '../__generated__/AllowedVfolderHostsWithPermissionQuery.graphql';
1+
import { BAIAllowedVfolderHostsWithPermissionFromGroupFragment$key } from '../../__generated__/BAIAllowedVfolderHostsWithPermissionFromGroupFragment.graphql';
2+
import { BAIAllowedVfolderHostsWithPermissionFromKeyPairResourcePolicyFragment$key } from '../../__generated__/BAIAllowedVfolderHostsWithPermissionFromKeyPairResourcePolicyFragment.graphql';
3+
import { BAIAllowedVfolderHostsWithPermissionQuery } from '../../__generated__/BAIAllowedVfolderHostsWithPermissionQuery.graphql';
4+
import BAIFlex from '../BAIFlex';
5+
import BAILink from '../BAILink';
6+
import BAIModal from '../BAIModal';
7+
import { BAITable } from '../Table';
38
import { CheckCircleFilled, StopFilled } from '@ant-design/icons';
49
import { Badge, theme } from 'antd';
5-
import { BAITable, BAIFlex, BAILink, BAIModal } from 'backend.ai-ui';
610
import _ from 'lodash';
711
import React from 'react';
812
import { useTranslation } from 'react-i18next';
913
import { graphql, useFragment, useLazyLoadQuery } from 'react-relay';
1014

11-
interface AllowedVfolderHostsWithPermissionProps {
12-
allowedVfolderHostsWithPermissionFrgmt: AllowedVfolderHostsWithPermissionFragment$key;
13-
}
15+
export type BAIAllowedVfolderHostsWithPermissionProps =
16+
| {
17+
allowedHostPermissionFrgmtFromKeyPair: BAIAllowedVfolderHostsWithPermissionFromKeyPairResourcePolicyFragment$key;
18+
allowedHostPermissionFrgmtFromGroup?: never;
19+
}
20+
| {
21+
allowedHostPermissionFrgmtFromKeyPair?: never;
22+
allowedHostPermissionFrgmtFromGroup: BAIAllowedVfolderHostsWithPermissionFromGroupFragment$key;
23+
};
1424

15-
const AllowedVfolderHostsWithPermission: React.FC<
16-
AllowedVfolderHostsWithPermissionProps
17-
> = ({ allowedVfolderHostsWithPermissionFrgmt }) => {
25+
const BAIAllowedVfolderHostsWithPermission: React.FC<
26+
BAIAllowedVfolderHostsWithPermissionProps
27+
> = ({
28+
allowedHostPermissionFrgmtFromKeyPair,
29+
allowedHostPermissionFrgmtFromGroup,
30+
}) => {
1831
const { t } = useTranslation();
1932
const { token } = theme.useToken();
2033
const [storageHost, setStorageHost] = React.useState<string | null>();
2134

22-
const keypairResourcePolicy = useFragment(
23-
graphql`
24-
fragment AllowedVfolderHostsWithPermissionFragment on KeyPairResourcePolicy {
25-
allowed_vfolder_hosts
26-
}
27-
`,
28-
allowedVfolderHostsWithPermissionFrgmt,
29-
);
35+
const keypairResourcePolicy =
36+
useFragment<BAIAllowedVfolderHostsWithPermissionFromKeyPairResourcePolicyFragment$key>(
37+
graphql`
38+
fragment BAIAllowedVfolderHostsWithPermissionFromKeyPairResourcePolicyFragment on KeyPairResourcePolicy {
39+
allowed_vfolder_hosts
40+
}
41+
`,
42+
allowedHostPermissionFrgmtFromKeyPair,
43+
);
44+
45+
const groupNode =
46+
useFragment<BAIAllowedVfolderHostsWithPermissionFromGroupFragment$key>(
47+
graphql`
48+
fragment BAIAllowedVfolderHostsWithPermissionFromGroupFragment on GroupNode {
49+
allowed_vfolder_hosts
50+
}
51+
`,
52+
allowedHostPermissionFrgmtFromGroup,
53+
);
3054

3155
const allowedVfolderHosts = JSON.parse(
32-
keypairResourcePolicy?.allowed_vfolder_hosts || '{}',
56+
keypairResourcePolicy?.allowed_vfolder_hosts ||
57+
groupNode?.allowed_vfolder_hosts ||
58+
'{}',
3359
);
3460

3561
const { vfolder_host_permissions } =
36-
useLazyLoadQuery<AllowedVfolderHostsWithPermissionQuery>(
62+
useLazyLoadQuery<BAIAllowedVfolderHostsWithPermissionQuery>(
3763
graphql`
38-
query AllowedVfolderHostsWithPermissionQuery {
64+
query BAIAllowedVfolderHostsWithPermissionQuery {
3965
vfolder_host_permissions {
4066
vfolder_host_permission_list
4167
}
@@ -77,7 +103,7 @@ const AllowedVfolderHostsWithPermission: React.FC<
77103
</BAIFlex>
78104
<BAIModal
79105
centered
80-
title={`${storageHost} ${t('data.explorer.Permission')}`}
106+
title={`${storageHost} ${t('comp:AllowedVfolderHostsWithPermission.Permission')}`}
81107
open={!_.isEmpty(storageHost)}
82108
onCancel={() => setStorageHost(null)}
83109
footer={null}
@@ -116,12 +142,12 @@ const AllowedVfolderHostsWithPermission: React.FC<
116142
)}
117143
columns={[
118144
{
119-
title: t('data.explorer.Permission'),
145+
title: t('comp:AllowedVfolderHostsWithPermission.Permission'),
120146
dataIndex: 'permission',
121147
key: 'permission',
122148
},
123149
{
124-
title: t('data.explorer.Allowed'),
150+
title: t('comp:AllowedVfolderHostsWithPermission.Allowed'),
125151
dataIndex: 'isAllowed',
126152
key: 'isAllowed',
127153
},
@@ -132,4 +158,4 @@ const AllowedVfolderHostsWithPermission: React.FC<
132158
);
133159
};
134160

135-
export default AllowedVfolderHostsWithPermission;
161+
export default BAIAllowedVfolderHostsWithPermission;

0 commit comments

Comments
 (0)