From 15f457a0e134de633ba1b817c6ad54f23461a31e Mon Sep 17 00:00:00 2001 From: jialin Date: Sun, 12 Jul 2026 16:41:58 +0800 Subject: [PATCH] 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 --- src/pages/gpu-service/instances/apis/index.ts | 2 +- .../components/instance-type-item.tsx | 178 ++++++++---------- src/pages/gpu-service/utils.ts | 12 ++ 3 files changed, 88 insertions(+), 104 deletions(-) diff --git a/src/pages/gpu-service/instances/apis/index.ts b/src/pages/gpu-service/instances/apis/index.ts index a9159c4d..7a027826 100644 --- a/src/pages/gpu-service/instances/apis/index.ts +++ b/src/pages/gpu-service/instances/apis/index.ts @@ -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 diff --git a/src/pages/gpu-service/instances/components/instance-type-item.tsx b/src/pages/gpu-service/instances/components/instance-type-item.tsx index 52f3c8e5..dd91b71d 100644 --- a/src/pages/gpu-service/instances/components/instance-type-item.tsx +++ b/src/pages/gpu-service/instances/components/instance-type-item.tsx @@ -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) => ( + 0} + icon={item.icon} + label={item.label} + value={item.value} + /> + )); + const remaining = columns - (4 * items.length - 1); + if (remaining > 0) { + cells.push( + + ); + } + return cells; +}; + export const InstanceMetadataSection: React.FC = ({ 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 ( - - {isGPU && ( - <> - {/* row 1: Memory | Max | RAM */} - - - - {/* row 2: OS | Arch | CPU */} - - - - {cpuUnitCores || '-'} - - } - /> - {/* row 3 (sliceable types only): Sliceable {n}% on its own row */} - - - )} + 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 */} - - - {/* row 2: OS | 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 ( + + {rows.map((row, index) => renderMetaRow(row, columns, `row-${index}`))} ); }; @@ -276,7 +248,7 @@ const InstanceTypeItem: React.FC = ({ item }) => { disabled={false} style={{ fontWeight: 400 }} > - {manufacturer?.toUpperCase()} + {formatManufacturer(manufacturer)} )} {showCpuManufacturerTag && ( diff --git a/src/pages/gpu-service/utils.ts b/src/pages/gpu-service/utils.ts index 09168539..b4b8cb56 100644 --- a/src/pages/gpu-service/utils.ts +++ b/src/pages/gpu-service/utils.ts @@ -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 = >( params: T ): Omit => {