fix: display instance type spec
This commit is contained in:
@@ -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
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user