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:
@@ -12,7 +12,7 @@ import {
|
|||||||
|
|
||||||
export const GPU_SERVICE_INSTANCES_API = '/gpu-instances';
|
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
|
// View logs / events still go through the K8s proxy until the /v2
|
||||||
// /gpu-instances API exposes equivalents. clusterID and namespace come
|
// /gpu-instances API exposes equivalents. clusterID and namespace come
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import { Flex, Tag } from 'antd';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { manufactureColorMap } from '../../templates/config';
|
import { manufactureColorMap } from '../../templates/config';
|
||||||
|
import { formatManufacturer } from '../../utils';
|
||||||
import { formatMemoryDisplay } from '../config';
|
import { formatMemoryDisplay } from '../config';
|
||||||
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
|
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
|
||||||
|
|
||||||
const Vendors = ['intel'] as const;
|
|
||||||
|
|
||||||
const Title = styled.div`
|
const Title = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -120,122 +119,95 @@ function getInstanceDerived(item: InstanceTypeItemModel) {
|
|||||||
ramUnit: spec.unitResourcesParsed?.ram?.value,
|
ramUnit: spec.unitResourcesParsed?.ram?.value,
|
||||||
os: _.capitalize(spec.os) || '',
|
os: _.capitalize(spec.os) || '',
|
||||||
arch: spec.arch,
|
arch: spec.arch,
|
||||||
cpuManufacturer: Vendors.includes(cpuManufacturer as any)
|
cpuManufacturer: formatManufacturer(cpuManufacturer),
|
||||||
? _.capitalize(cpuManufacturer)
|
|
||||||
: _.toUpper(cpuManufacturer),
|
|
||||||
cpuUnitCores: spec.unitResourcesParsed?.cpu?.cores
|
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> = ({
|
export const InstanceMetadataSection: React.FC<MetadataSectionProps> = ({
|
||||||
spec,
|
spec,
|
||||||
slicedMaxPercentage
|
slicedMaxPercentage
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { ramUnit, cpuUnitCores, isGPU, os, arch } = getInstanceDerived({
|
const { ramUnit, cpuUnitCores, isGPU, arch } = getInstanceDerived({
|
||||||
spec
|
spec
|
||||||
} as InstanceTypeItemModel);
|
} as InstanceTypeItemModel);
|
||||||
|
|
||||||
// Sliceable types get a dedicated "Sliceable {n}%" row (on its own grid row)
|
// Sliceable types append a "Sliceable {n}%" cell to the second row.
|
||||||
// rather than crowding it into the Max cell.
|
|
||||||
const showSliceable = !!spec.sliceable && (slicedMaxPercentage ?? 0) > 0;
|
const showSliceable = !!spec.sliceable && (slicedMaxPercentage ?? 0) > 0;
|
||||||
|
|
||||||
return (
|
const cpuItem: MetaEntry = {
|
||||||
<Meta $columns={isGPU ? 11 : 7}>
|
icon: 'icon-cpu',
|
||||||
{isGPU && (
|
label: 'CPU',
|
||||||
<>
|
value: cpuUnitCores || '-'
|
||||||
{/* row 1: Memory | Max | RAM */}
|
};
|
||||||
<MetaItem
|
const ramItem: MetaEntry = {
|
||||||
show={isGPU}
|
icon: 'icon-ram-02',
|
||||||
showDot={false}
|
label: intl.formatMessage({ id: 'gpuservice.instance.ram' }),
|
||||||
icon="icon-gpu1"
|
value: ramUnit ? `${ramUnit} GB` : '-'
|
||||||
label={intl.formatMessage({ id: 'gpuservice.instance.memory' })}
|
};
|
||||||
value={formatMemoryDisplay(spec?.memory ?? undefined) ?? '-'}
|
const memoryItem: MetaEntry = {
|
||||||
/>
|
icon: 'icon-gpu1',
|
||||||
<MetaItem
|
label: intl.formatMessage({ id: 'gpuservice.instance.memory' }),
|
||||||
showDot={true}
|
value: formatMemoryDisplay(spec?.memory ?? undefined) ?? '-'
|
||||||
icon="icon-ram-02"
|
};
|
||||||
label={intl.formatMessage({ id: 'gpuservice.instance.ram' })}
|
const archItem: MetaEntry = {
|
||||||
value={ramUnit ? `${ramUnit} GB` : '-'}
|
icon: 'icon-cube',
|
||||||
/>
|
label: intl.formatMessage({ id: 'gpuservice.instance.arch' }),
|
||||||
<MetaItem
|
value: _.toUpper(arch) || '-'
|
||||||
icon="icon-database"
|
};
|
||||||
label={intl.formatMessage(
|
const maxItem: MetaEntry = {
|
||||||
{
|
icon: 'icon-database',
|
||||||
id: 'common.max'
|
label: intl.formatMessage({ id: 'common.max' }, { count: '' }),
|
||||||
},
|
value: `${spec.maxComputeUnitCount || 0}`
|
||||||
{ count: '' }
|
};
|
||||||
)}
|
const slicedItem: MetaEntry = {
|
||||||
value={`${spec.maxComputeUnitCount || 0}`}
|
icon: 'icon-sliced',
|
||||||
/>
|
label: intl.formatMessage({ id: 'gpuservice.instance.sliceable' }),
|
||||||
{/* row 2: OS | Arch | CPU */}
|
value: `${slicedMaxPercentage}%`
|
||||||
<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}%`}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isGPU && (
|
// GPU: 3 items/row → 11 cols. CPU: 2 items/row → 7 cols.
|
||||||
<>
|
const columns = isGPU ? 11 : 7;
|
||||||
{/* row 1: RAM | Max */}
|
const rows: MetaEntry[][] = isGPU
|
||||||
<MetaItem
|
? [
|
||||||
showDot={false}
|
[ramItem, memoryItem, cpuItem],
|
||||||
icon="icon-ram-02"
|
showSliceable ? [archItem, maxItem, slicedItem] : [archItem, maxItem]
|
||||||
label={intl.formatMessage({ id: 'gpuservice.instance.ram' })}
|
]
|
||||||
value={ramUnit ? `${ramUnit} GB` : '-'}
|
: [[ramItem], [archItem, maxItem]];
|
||||||
/>
|
|
||||||
<MetaItem
|
return (
|
||||||
icon="icon-database"
|
<Meta $columns={columns}>
|
||||||
label={intl.formatMessage(
|
{rows.map((row, index) => renderMetaRow(row, columns, `row-${index}`))}
|
||||||
{
|
|
||||||
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) || '-'}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Meta>
|
</Meta>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -276,7 +248,7 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
style={{ fontWeight: 400 }}
|
style={{ fontWeight: 400 }}
|
||||||
>
|
>
|
||||||
{manufacturer?.toUpperCase()}
|
{formatManufacturer(manufacturer)}
|
||||||
</ThemeTag>
|
</ThemeTag>
|
||||||
)}
|
)}
|
||||||
{showCpuManufacturerTag && (
|
{showCpuManufacturerTag && (
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import _ from 'lodash';
|
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>>(
|
export const omitPathParams = <T extends Record<string, any>>(
|
||||||
params: T
|
params: T
|
||||||
): Omit<T, 'namespace' | 'clusterID'> => {
|
): Omit<T, 'namespace' | 'clusterID'> => {
|
||||||
|
|||||||
Reference in New Issue
Block a user