fix: display instance type spec

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent deeee70807
commit c467280170
54 changed files with 1058 additions and 204 deletions
@@ -25,7 +25,7 @@ const setActionsItems = (row: ClusterListItem) => {
const useClusterColumns = (
handleSelect: (val: string, record: ClusterListItem) => void
): ColumnsType<ClusterListItem> => {
): ColumnsType<ClusterListItem & { dataIndex: string; span: number }> => {
const intl = useIntl();
return useMemo(() => {
@@ -47,13 +47,35 @@ const useClusterColumns = (
{
title: intl.formatMessage({ id: 'clusters.table.provider' }),
dataIndex: 'provider',
span: 3,
span: 4,
render: (value: string) => (
<AutoTooltip ghost minWidth={20}>
{ProviderLabelMap[value]}
</AutoTooltip>
)
},
{
title: 'GPUs',
dataIndex: 'gpus',
span: 2,
render: (value: number) => <span>{value}</span>
},
{
title: intl.formatMessage({ id: 'clusters.table.deployments' }),
dataIndex: 'models',
span: 2,
render: (value: number) => <span>{value}</span>
},
{
title: 'Workers',
dataIndex: 'workers',
span: 3,
render: (value: number, record: ClusterListItem) => (
<span>
{record.ready_workers} / {record.workers}
</span>
)
},
{
title: intl.formatMessage({ id: 'common.table.status' }),
dataIndex: 'state',
@@ -67,28 +89,6 @@ const useClusterColumns = (
/>
)
},
{
title: 'Workers',
dataIndex: 'workers',
span: 3,
render: (value: number, record: ClusterListItem) => (
<span>
{record.ready_workers} / {record.workers}
</span>
)
},
{
title: 'GPUs',
dataIndex: 'gpus',
span: 3,
render: (value: number) => <span>{value}</span>
},
{
title: intl.formatMessage({ id: 'clusters.table.deployments' }),
dataIndex: 'models',
span: 2,
render: (value: number) => <span>{value}</span>
},
{
title: intl.formatMessage({ id: 'common.table.createTime' }),
dataIndex: 'created_at',
@@ -5,7 +5,9 @@ import { useIntl } from '@umijs/max';
import { ColumnsType } from 'antd/es/table';
import type { SortOrder } from 'antd/es/table/interface';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useMemo } from 'react';
import { RenderInstanceOption } from '../components/pool-form';
import { NodePoolListItem as ListItem } from '../config/types';
const actionItems = [
@@ -24,10 +26,11 @@ const actionItems = [
}
}
];
const usePoolsColumns = (
handleSelect: (val: string, record: ListItem) => void,
sortOrder?: SortOrder
): ColumnsType<ListItem> => {
): ColumnsType<ListItem & { dataIndex: string }> => {
const intl = useIntl();
return useMemo(() => {
@@ -39,7 +42,7 @@ const usePoolsColumns = (
ellipsis: {
showTitle: false
},
span: 4,
span: 3,
style: {
paddingInline: 'var(--ant-table-cell-padding-inline)'
},
@@ -56,29 +59,64 @@ const usePoolsColumns = (
ellipsis: {
showTitle: false
},
span: 3,
span: 4,
style: {
paddingLeft: 12
paddingLeft: 62
},
render: (text: string) => (
<AutoTooltip title={text} ghost minWidth={20}>
{text}
render: (text: string, record: ListItem) => (
<AutoTooltip
title={
<RenderInstanceOption
styles={{
description: {
color: 'var(--color-white-quaternary)'
}
}}
data={{
vendor: record.instance_spec.vendor,
description: record.instance_spec.description,
specInfo: _.omit(record.instance_spec, [
'label',
'vendor',
'description'
])
}}
/>
}
showTitle
ghost
minWidth={20}
>
{record.instance_spec.description || record.instance_spec.label}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'clusters.workerpool.osImage' }),
dataIndex: 'os_image',
key: 'os_image',
span: 3,
dataIndex: 'image_name',
key: 'image_name',
span: 4,
ellipsis: {
showTitle: false
},
style: {
paddingLeft: 16
paddingLeft: 56
},
render: (text: string) => (
<AutoTooltip title={text} ghost minWidth={20}>
<AutoTooltip
title={
<span className="flex-column">
<span className="text-tertiary">
{intl.formatMessage({ id: 'clusters.workerpool.osImage' })}
:{' '}
</span>
{text}
</span>
}
showTitle
ghost
minWidth={20}
>
{text}
</AutoTooltip>
)
@@ -86,11 +124,10 @@ const usePoolsColumns = (
{
title: 'Workers',
dataIndex: 'replicas',
span: 3,
span: 6,
key: 'replicas',
style: {
// textAlign: 'center'
paddingLeft: 4
paddingLeft: 50
},
editable: {
valueType: 'number',
@@ -102,12 +139,13 @@ const usePoolsColumns = (
</span>
)
},
{
title: intl.formatMessage({ id: 'clusters.workerpool.batchSize' }),
dataIndex: 'batch_size',
key: 'batch_size',
span: 4
},
// {
// title: intl.formatMessage({ id: 'clusters.workerpool.batchSize' }),
// dataIndex: 'batch_size',
// key: 'batch_size',
// span: 4
// },
// {
// title: intl.formatMessage({ id: 'resources.table.labels' }),
// dataIndex: 'labels',
@@ -0,0 +1,183 @@
import {
allRegionInstanceTypeListAtom,
allRegionOSImageListAtom,
regionInstanceTypeListAtom,
regionListAtom,
regionOSImageListAtom
} from '@/atoms/clusters';
import { convertFileSizeByUnit } from '@/utils';
import { useAtom } from 'jotai';
import _ from 'lodash';
import { useState } from 'react';
import {
queryDigitalOceanInstanceTypes,
queryDigitalOceanOSImages,
queryDigitalOceanRegions
} from '../apis';
import { RegionIcons } from '../config/region-icons';
const parseCityDatacenter = (
input: string
): { label: string; datacenter: string } => {
const parts = input.trim().split(' ');
const number = parts.pop();
const city = parts.join(' ');
return {
label: city,
datacenter: `Datacenter ${number}`
};
};
type ParsedSpec = {
vram: string | number;
vcpus: number;
ram: string | number;
bootDisk: string | number;
scratchDisk: string | number;
};
export const parseSpec = (obj: any): ParsedSpec => {
const gpuInfo = obj.gpu_info;
const diskInfo = obj.disk_info;
return {
vram: convertFileSizeByUnit({
sizeInBytes: gpuInfo?.vram?.amount || 0,
defaultUnit: 'GiB'
}),
vcpus: obj.vcpus || 0,
ram: convertFileSizeByUnit({
sizeInBytes: obj.memory || 0,
defaultUnit: 'MiB'
}),
bootDisk: convertFileSizeByUnit({
sizeInBytes:
diskInfo?.find((d: any) => d.type === 'local')?.size.amount || 0,
defaultUnit: 'GiB'
}),
scratchDisk: convertFileSizeByUnit({
sizeInBytes:
diskInfo?.find((d: any) => d.type === 'scratch')?.size.amount || 0,
defaultUnit: 'GiB'
})
};
};
const formatSpec = (spec: ParsedSpec): string => {
const parts: string[] = [];
if (spec.vram) parts.push(`${spec.vram} VRAM`);
if (spec.vcpus) parts.push(`${spec.vcpus} vCPUs`);
if (spec.bootDisk) parts.push(`${spec.bootDisk} Boot disk`);
if (spec.ram) parts.push(`${spec.ram} RAM`);
if (spec.scratchDisk) {
parts.push(`${spec.scratchDisk} Scratch disk`);
}
return parts.join(' / ');
};
export const useProviderRegions = () => {
const [regions, setRegions] = useAtom(regionListAtom);
const [, setInstanceTypes] = useAtom(regionInstanceTypeListAtom);
const [, setOSImageList] = useAtom(regionOSImageListAtom);
const [allOSImageList, setAllOSImageList] = useAtom(allRegionOSImageListAtom);
const [allInstanceTypes, setAllInstanceTypes] = useAtom(
allRegionInstanceTypeListAtom
);
const [loading, setLoading] = useState(false);
const getRegions = async (credential: number) => {
try {
setLoading(true);
const res = await queryDigitalOceanRegions({ id: credential });
const list = res?.regions
?.filter?.(
(sItem: any) =>
sItem.sizes.some((size: string) => size.includes('gpu')) &&
sItem.available
)
.map((item: any) => {
return {
...parseCityDatacenter(item.name),
value: item.slug,
icon: RegionIcons[item.slug],
sizes: item.sizes || []
};
});
setRegions(list);
} catch (error) {
setRegions([]);
} finally {
setLoading(false);
}
};
const getInstanceTypes = async (credential: number) => {
try {
const res = await queryDigitalOceanInstanceTypes({ id: credential });
const list = res?.sizes
?.filter((sItem: any) => sItem.gpu_info && sItem.available)
.map((item: any) => {
const specInfo = parseSpec(item);
return {
label: formatSpec(specInfo),
value: item.slug,
description: item.description,
specInfo: specInfo,
vendor: _.get(_.split(item.gpu_info?.model, '_'), 0),
available: item.available,
regions: item.regions || []
};
});
setAllInstanceTypes(list);
} catch (error) {
setAllInstanceTypes([]);
}
};
const getOSImages = async (credential: number) => {
try {
const res = await queryDigitalOceanOSImages({ id: credential });
const list = res.images
?.filter((sItem: any) => sItem.status === 'available')
.map((item: any) => {
return {
label: item.description,
value: item.slug,
name: item.name,
description: item.description,
vendor: _.camelCase(item.distribution),
specInfo: {
size: `${item.size_gigabytes} GiB`,
minDiskSize: `${item.min_disk_size} GiB`
},
regions: item.regions || []
};
});
setAllOSImageList(list);
} catch (error) {}
};
const updateInstanceTypes = (region: string) => {
const sizes = allInstanceTypes.filter((item) =>
item.regions.includes(region)
);
setInstanceTypes(sizes);
};
const updateOSImages = (region: string) => {
const list = allOSImageList.filter((item) => item.regions.includes(region));
setOSImageList(list);
};
return {
regions,
loading,
setLoading,
getRegions,
getInstanceTypes,
getOSImages,
updateOSImages,
updateInstanceTypes
};
};