feat(gpu-service): refine instance type card layout in create-instance drawer

- Regroup meta into aligned rows (CPU/RAM/Memory, Arch/Max/Sliced) and drop the OS row
- Use aggregated endpoint for instance types
- Share formatManufacturer helper for consistent vendor casing
This commit is contained in:
jialin
2026-07-22 15:52:17 +08:00
parent 9386f0872b
commit 15f457a0e1
3 changed files with 88 additions and 104 deletions
@@ -12,7 +12,7 @@ import {
export const GPU_SERVICE_INSTANCES_API = '/gpu-instances';
export const GPU_SERVICE_INSTANCES_TYPE_API = '/gpu-instance-types';
export const GPU_SERVICE_INSTANCES_TYPE_API = '/gpu-instance-types/aggregated';
// View logs / events still go through the K8s proxy until the /v2
// /gpu-instances API exposes equivalents. clusterID and namespace come
@@ -5,11 +5,10 @@ import { Flex, Tag } from 'antd';
import _ from 'lodash';
import styled from 'styled-components';
import { manufactureColorMap } from '../../templates/config';
import { formatManufacturer } from '../../utils';
import { formatMemoryDisplay } from '../config';
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
const Vendors = ['intel'] as const;
const Title = styled.div`
display: flex;
align-items: center;
@@ -120,122 +119,95 @@ function getInstanceDerived(item: InstanceTypeItemModel) {
ramUnit: spec.unitResourcesParsed?.ram?.value,
os: _.capitalize(spec.os) || '',
arch: spec.arch,
cpuManufacturer: Vendors.includes(cpuManufacturer as any)
? _.capitalize(cpuManufacturer)
: _.toUpper(cpuManufacturer),
cpuManufacturer: formatManufacturer(cpuManufacturer),
cpuUnitCores: spec.unitResourcesParsed?.cpu?.cores
};
}
type MetaEntry = { icon: string; label?: string; value: React.ReactNode };
// All rows share a single grid so columns — and therefore icons — line up
// vertically. Each item is 3 cells (icon/label/value); every item past the
// first adds a leading dot cell, so a row of k items spans 4k-1 cells. A short
// row is padded with a spanning spacer so the next row restarts at column 1.
const renderMetaRow = (items: MetaEntry[], columns: number, rowKey: string) => {
const cells = items.map((item, index) => (
<MetaItem
key={`${rowKey}-${item.icon}`}
showDot={index > 0}
icon={item.icon}
label={item.label}
value={item.value}
/>
));
const remaining = columns - (4 * items.length - 1);
if (remaining > 0) {
cells.push(
<span
key={`${rowKey}-spacer`}
style={{ gridColumn: `span ${remaining}` }}
/>
);
}
return cells;
};
export const InstanceMetadataSection: React.FC<MetadataSectionProps> = ({
spec,
slicedMaxPercentage
}) => {
const intl = useIntl();
const { ramUnit, cpuUnitCores, isGPU, os, arch } = getInstanceDerived({
const { ramUnit, cpuUnitCores, isGPU, arch } = getInstanceDerived({
spec
} as InstanceTypeItemModel);
// Sliceable types get a dedicated "Sliceable {n}%" row (on its own grid row)
// rather than crowding it into the Max cell.
// Sliceable types append a "Sliceable {n}%" cell to the second row.
const showSliceable = !!spec.sliceable && (slicedMaxPercentage ?? 0) > 0;
return (
<Meta $columns={isGPU ? 11 : 7}>
{isGPU && (
<>
{/* row 1: Memory | Max | RAM */}
<MetaItem
show={isGPU}
showDot={false}
icon="icon-gpu1"
label={intl.formatMessage({ id: 'gpuservice.instance.memory' })}
value={formatMemoryDisplay(spec?.memory ?? undefined) ?? '-'}
/>
<MetaItem
showDot={true}
icon="icon-ram-02"
label={intl.formatMessage({ id: 'gpuservice.instance.ram' })}
value={ramUnit ? `${ramUnit} GB` : '-'}
/>
<MetaItem
icon="icon-database"
label={intl.formatMessage(
{
id: 'common.max'
},
{ count: '' }
)}
value={`${spec.maxComputeUnitCount || 0}`}
/>
{/* row 2: OS | Arch | CPU */}
<MetaItem
showDot={false}
icon="icon-server02"
label={intl.formatMessage({ id: 'gpuservice.instance.os' })}
value={os || '-'}
/>
<MetaItem
icon="icon-cube"
label={intl.formatMessage({ id: 'gpuservice.instance.arch' })}
value={_.toUpper(arch) || '-'}
/>
<MetaItem
show={isGPU}
showDot={true}
icon="icon-cpu"
label="CPU"
value={
<Flex gap={4} align="center">
<span>{cpuUnitCores || '-'}</span>
</Flex>
}
/>
{/* row 3 (sliceable types only): Sliceable {n}% on its own row */}
<MetaItem
show={showSliceable}
showDot={false}
icon="icon-sliced"
label={intl.formatMessage({ id: 'gpuservice.instance.sliceable' })}
value={`${slicedMaxPercentage}%`}
/>
</>
)}
const cpuItem: MetaEntry = {
icon: 'icon-cpu',
label: 'CPU',
value: cpuUnitCores || '-'
};
const ramItem: MetaEntry = {
icon: 'icon-ram-02',
label: intl.formatMessage({ id: 'gpuservice.instance.ram' }),
value: ramUnit ? `${ramUnit} GB` : '-'
};
const memoryItem: MetaEntry = {
icon: 'icon-gpu1',
label: intl.formatMessage({ id: 'gpuservice.instance.memory' }),
value: formatMemoryDisplay(spec?.memory ?? undefined) ?? '-'
};
const archItem: MetaEntry = {
icon: 'icon-cube',
label: intl.formatMessage({ id: 'gpuservice.instance.arch' }),
value: _.toUpper(arch) || '-'
};
const maxItem: MetaEntry = {
icon: 'icon-database',
label: intl.formatMessage({ id: 'common.max' }, { count: '' }),
value: `${spec.maxComputeUnitCount || 0}`
};
const slicedItem: MetaEntry = {
icon: 'icon-sliced',
label: intl.formatMessage({ id: 'gpuservice.instance.sliceable' }),
value: `${slicedMaxPercentage}%`
};
{!isGPU && (
<>
{/* row 1: RAM | Max */}
<MetaItem
showDot={false}
icon="icon-ram-02"
label={intl.formatMessage({ id: 'gpuservice.instance.ram' })}
value={ramUnit ? `${ramUnit} GB` : '-'}
/>
<MetaItem
icon="icon-database"
label={intl.formatMessage(
{
id: 'common.max'
},
{ count: '' }
)}
value={`${spec.maxComputeUnitCount || 0}`}
/>
{/* row 2: OS | Arch */}
<MetaItem
showDot={false}
icon="icon-server02"
label={intl.formatMessage({ id: 'gpuservice.instance.os' })}
value={os || '-'}
/>
<MetaItem
icon="icon-cube"
label={intl.formatMessage({ id: 'gpuservice.instance.arch' })}
value={_.toUpper(arch) || '-'}
/>
</>
)}
// GPU: 3 items/row → 11 cols. CPU: 2 items/row → 7 cols.
const columns = isGPU ? 11 : 7;
const rows: MetaEntry[][] = isGPU
? [
[ramItem, memoryItem, cpuItem],
showSliceable ? [archItem, maxItem, slicedItem] : [archItem, maxItem]
]
: [[ramItem], [archItem, maxItem]];
return (
<Meta $columns={columns}>
{rows.map((row, index) => renderMetaRow(row, columns, `row-${index}`))}
</Meta>
);
};
@@ -276,7 +248,7 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
disabled={false}
style={{ fontWeight: 400 }}
>
{manufacturer?.toUpperCase()}
{formatManufacturer(manufacturer)}
</ThemeTag>
)}
{showCpuManufacturerTag && (
+12
View File
@@ -1,5 +1,17 @@
import _ from 'lodash';
// Manufacturer display: most vendors read best all-caps (NVIDIA, AMD), but a
// few read better capitalized (Intel). Shared by the instance-type list and the
// create-instance drawer so both render the vendor identically.
const CapitalizedVendors = ['intel'];
export const formatManufacturer = (manufacturer?: string | null): string => {
if (!manufacturer) return '';
return CapitalizedVendors.includes(manufacturer.toLowerCase())
? _.capitalize(manufacturer)
: _.toUpper(manufacturer);
};
export const omitPathParams = <T extends Record<string, any>>(
params: T
): Omit<T, 'namespace' | 'clusterID'> => {