fix: keep uniform for instance type render content

This commit is contained in:
jialin
2026-06-12 22:20:31 +08:00
committed by jialin
parent d5d0749b72
commit 48159171c2
5 changed files with 277 additions and 257 deletions
@@ -10,7 +10,10 @@
* Talks to the new ``/usage/gpu-instances/{meta,breakdown}`` endpoints.
*/
import useCoolColors from '@/hooks/use-cool-colors';
import InstanceTypeCell from '@/pages/gpu-service/instances/components/instance-type-cell';
import {
buildInstanceTypeRecordFromMiB,
renderInstanceType
} from '@/pages/gpu-service/instances/utils/render-instance-type';
import { formatLargeNumber } from '@/utils';
import { SimpleCard } from '@gpustack/core-ui';
import { useAccess, useIntl } from '@umijs/max';
@@ -24,11 +27,7 @@ import {
ResourceBreakdownResponse
} from '../apis/resource';
import useResourceMeta from '../hooks/use-resource-meta';
import {
instanceTypeLabel,
instanceTypeSections,
instanceTypeTitle
} from '../utils/format-instance-type';
import { instanceTypeLabel } from '../utils/format-instance-type';
import {
bucketKey,
generateBucketRange,
@@ -315,22 +314,28 @@ const GpuInstancesTab: React.FC = () => {
key: 'gpu_type',
render: (_v: string, row: ResourceBreakdownItem) => instanceTypeLabel(row)
};
// Instances breakdown: render exactly like the GPU Instances list
// "<product> x <count>" plus the categorized spec popover behind the icon.
// Instances breakdown: render through the canonical GPU Instances list
// renderer so the label + spec popover are identical. The breakdown row
// carries flat MiB fields, so adapt it into the ListItem shape first.
const instanceTypeColInstance = {
title: intl.formatMessage({ id: 'usage.table.instanceType' }),
dataIndex: 'gpu_type',
key: 'gpu_type',
render: (_v: string, row: ResourceBreakdownItem) => (
<InstanceTypeCell
title={instanceTypeTitle(row)}
name={row.instance_name}
sections={instanceTypeSections(row, {
vram: intl.formatMessage({ id: 'gpuservice.instance.memory' }),
disk: intl.formatMessage({ id: 'gpuservice.instance.disk' })
})}
/>
)
render: (_v: string, row: ResourceBreakdownItem) =>
renderInstanceType(
buildInstanceTypeRecordFromMiB({
name: row.instance_name,
product: row.product || row.gpu_type,
gpuCount: row.gpu_count,
unitCpuMilli: row.unit_cpu_milli,
unitMemoryMib: row.unit_memory_mib,
vramMib: row.vram_mib,
localStorageMib: row.local_storage_mib,
ephemeralMib: row.ephemeral_mib,
persistentMib: row.persistent_mib
}),
{ intl }
)
};
// Last Active = the last active day. The backend sends a rollup-tz instant
// with its offset; parseRollup keeps that wall clock (no browser-tz convert),
@@ -511,6 +516,7 @@ const GpuInstancesTab: React.FC = () => {
rowKey={(row) =>
`${row.gpu_type ?? ''}|${row.instance_id ?? ''}|${row.user_id ?? ''}`
}
key={t.key}
dataSource={tableRows}
columns={tableColumns as any}
onChange={(_pagination, _filters, sorter: any) => {
@@ -532,6 +538,7 @@ const GpuInstancesTab: React.FC = () => {
}
}}
pagination={{
size: 'middle',
current: tablePage,
pageSize: tableData?.pagination.perPage ?? 50,
total: tableData?.pagination.total ?? 0,
+9 -88
View File
@@ -1,97 +1,18 @@
/**
* Instance-type display helpers — render the Usage "Instance Type" the same way
* the GPU Instances list does: a pretty product name (e.g.
* "NVIDIA-GeForce-RTX-5090-D") plus a per-card spec line, instead of the raw
* kueue flavor slug.
* Instance-type display helper for the Usage tables.
*
* The product name + per-card specs ride on the breakdown rows via
* ``dimensions`` (instance-type / per-instance groupings only); older rows that
* predate the enrichment fall back to the flavor slug (``gpu_type``).
* The Instances breakdown renders its Instance Type column through the
* canonical GPU Instances renderer (``renderInstanceType`` +
* ``buildInstanceTypeRecordFromMiB``), so the label + spec popover stay
* identical to the GPU Instances list. Only the Instance Types breakdown — a
* plain product label with no popover — still uses the helper below.
*
* The product name rides on the breakdown rows via ``dimensions``; older rows
* that predate the enrichment fall back to the flavor slug (``gpu_type``).
*/
import { InstanceTypeSection } from '@/pages/gpu-service/instances/components/instance-type-cell';
import { formatMemoryDisplay } from '@/pages/gpu-service/instances/config';
import { ResourceBreakdownItem } from '../apis/resource';
// Primary label: GPU product name when known, else the flavor slug.
export const instanceTypeLabel = (
row?: Partial<ResourceBreakdownItem>
): string => row?.product || row?.gpu_type || '-';
// Round to ≤2 decimals, stripping trailing zeros, so fractional CPU allocations
// (e.g. 0.5C / 500m) aren't misrounded up to "1C". Memory uses the shared
// formatMemoryDisplay so sizes match the GPU Instances list exactly.
const fmt = (n: number): number => parseFloat(n.toFixed(2));
// Secondary spec line "18C · 54GB RAM · 31GB VRAM" (per card). Storage is
// intentionally excluded — it's user-customizable and not part of the type.
// Empty string when no specs are known (fall back to label only).
export const instanceTypeSpecs = (
row?: Partial<ResourceBreakdownItem>
): string => {
if (!row) return '';
const parts: string[] = [];
if (row.unit_cpu_milli) {
parts.push(`${fmt(row.unit_cpu_milli / 1000)}C`);
}
if (row.unit_memory_mib) {
parts.push(`${formatMemoryDisplay(row.unit_memory_mib)} RAM`);
}
if (row.vram_mib) {
parts.push(`${formatMemoryDisplay(row.vram_mib)} VRAM`);
}
return parts.join(' · ');
};
// Per-instance title for the Instances table: "<product> x <count>", matching
// the GPU Instances list (count carried in dimensions per instance).
export const instanceTypeTitle = (
row?: Partial<ResourceBreakdownItem>
): string => {
const label = instanceTypeLabel(row);
return row?.gpu_count ? `${label} x ${row.gpu_count}` : label;
};
// Spec-popover sections for the Instances table, fed to the shared
// InstanceTypeCell so it renders exactly like the GPU Instances list:
// GPU (Count / Instance Type / per-card VRAM), CPU + Memory as whole-instance
// totals (count × per-card, as the list shows), and the ephemeral data disk.
// Empty rows are dropped by the cell. ``labels`` carries the i18n VRAM / Disk
// captions so this util stays intl-free.
export const instanceTypeSections = (
row: Partial<ResourceBreakdownItem> | undefined,
labels: { vram: string; disk: string }
): InstanceTypeSection[] => {
if (!row) return [];
const count = row.gpu_count || 0;
const cpu =
row.unit_cpu_milli && count
? `${fmt((row.unit_cpu_milli / 1000) * count)}C`
: undefined;
// RAM is the whole-instance total (per-card × count), as the list shows.
const ram =
row.unit_memory_mib && count
? formatMemoryDisplay(row.unit_memory_mib * count)
: undefined;
return [
{
icon: 'icon-gpu',
name: 'GPU',
rows: [
['Count', count ? `${count}` : undefined],
['Instance Type', row.product],
[labels.vram, formatMemoryDisplay(row.vram_mib)]
]
},
{ icon: 'icon-cpu', name: 'CPU', rows: [[null, cpu]] },
{ icon: 'icon-ram-02', name: 'Memory', rows: [[null, ram]] },
{
icon: 'icon-hard-disk',
name: labels.disk,
rows: [
['System', formatMemoryDisplay(row.local_storage_mib)],
['Data', formatMemoryDisplay(row.ephemeral_mib)],
['Persistent', formatMemoryDisplay(row.persistent_mib)]
]
}
];
};