fix: instance types switch between gpu and cpu

This commit is contained in:
jialin
2026-06-12 12:10:14 +08:00
committed by jialin
parent 7b18a92388
commit ddf723525a
10 changed files with 270 additions and 60 deletions
+2
View File
@@ -120,6 +120,8 @@ export default {
'gpuservice.instance.sliced': 'Sliced', 'gpuservice.instance.sliced': 'Sliced',
'gpuservice.instance.memory': 'VRAM', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.os': 'OS',
'gpuservice.instance.arch': 'Arch',
'gpuservice.instance.disk': 'Disk', 'gpuservice.instance.disk': 'Disk',
'gpuservice.table.count': 'Count', 'gpuservice.table.count': 'Count',
'gpuservice.instance.disk.system': 'System Disk', 'gpuservice.instance.disk.system': 'System Disk',
+2
View File
@@ -119,6 +119,8 @@ export default {
'gpuservice.instance.sliced': '分割', 'gpuservice.instance.sliced': '分割',
'gpuservice.instance.memory': 'VRAM', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.os': 'OS',
'gpuservice.instance.arch': 'アーキテクチャ',
'gpuservice.instance.disk': 'ディスク', 'gpuservice.instance.disk': 'ディスク',
'gpuservice.table.count': '数量', 'gpuservice.table.count': '数量',
'gpuservice.instance.disk.system': 'システムディスク', 'gpuservice.instance.disk.system': 'システムディスク',
+2
View File
@@ -118,6 +118,8 @@ export default {
'gpuservice.instance.sliced': 'Разделено', 'gpuservice.instance.sliced': 'Разделено',
'gpuservice.instance.memory': 'VRAM', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.os': 'ОС',
'gpuservice.instance.arch': 'Архитектура',
'gpuservice.instance.disk': 'Диск', 'gpuservice.instance.disk': 'Диск',
'gpuservice.table.count': 'Количество', 'gpuservice.table.count': 'Количество',
'gpuservice.instance.disk.system': 'Системный диск', 'gpuservice.instance.disk.system': 'Системный диск',
+2
View File
@@ -114,6 +114,8 @@ export default {
'gpuservice.instance.sliced': 'Bölünmüş', 'gpuservice.instance.sliced': 'Bölünmüş',
'gpuservice.instance.memory': 'VRAM', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.os': 'OS',
'gpuservice.instance.arch': 'Mimari',
'gpuservice.instance.disk': 'Disk', 'gpuservice.instance.disk': 'Disk',
'gpuservice.table.count': 'Sayı', 'gpuservice.table.count': 'Sayı',
'gpuservice.instance.disk.system': 'Sistem Diski', 'gpuservice.instance.disk.system': 'Sistem Diski',
+2
View File
@@ -109,6 +109,8 @@ export default {
'gpuservice.instance.sliced': '切分', 'gpuservice.instance.sliced': '切分',
'gpuservice.instance.memory': '显存', 'gpuservice.instance.memory': '显存',
'gpuservice.instance.ram': '内存', 'gpuservice.instance.ram': '内存',
'gpuservice.instance.os': '系统',
'gpuservice.instance.arch': '架构',
'gpuservice.instance.disk': '磁盘', 'gpuservice.instance.disk': '磁盘',
'gpuservice.table.count': '数量', 'gpuservice.table.count': '数量',
'gpuservice.instance.disk.system': '系统盘', 'gpuservice.instance.disk.system': '系统盘',
@@ -8,10 +8,12 @@ import {
AlertBlockInfo, AlertBlockInfo,
ColumnWrapper, ColumnWrapper,
GSDrawer, GSDrawer,
IconFont,
ModalFooter ModalFooter
} from '@gpustack/core-ui'; } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Empty, Input, Typography } from 'antd'; import { Empty, Flex, Input, Segmented, Typography } from 'antd';
import _ from 'lodash';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { ListItem as TemplateItem } from '../../templates/config/types'; import { ListItem as TemplateItem } from '../../templates/config/types';
import useQueryTemplates from '../../templates/services/use-query-templates'; import useQueryTemplates from '../../templates/services/use-query-templates';
@@ -88,6 +90,7 @@ const AddModal: React.FC<AddModalProps> = ({
manufacturer: undefined manufacturer: undefined
}); });
const [templateId, setTemplateId] = useState<number | undefined>(); const [templateId, setTemplateId] = useState<number | undefined>();
const [resourceType, setResourceType] = useState<'gpu' | 'cpu'>('gpu');
const [instanceKeyword, setInstanceKeyword] = useState(''); const [instanceKeyword, setInstanceKeyword] = useState('');
const [templateKeyword, setTemplateKeyword] = useState(''); const [templateKeyword, setTemplateKeyword] = useState('');
const { loading, guard, run, release } = useSubmitLock(); const { loading, guard, run, release } = useSubmitLock();
@@ -171,17 +174,35 @@ const AddModal: React.FC<AddModalProps> = ({
return JSON.stringify({ return JSON.stringify({
name: instanceType.name, name: instanceType.name,
spec: { spec: {
...instanceType.spec ..._.omit(instanceType.spec, ['cache', 'cpu']),
cpu: _.pick(instanceType.spec?.cpu, [
'manufacturer',
'product',
'family'
])
} }
}); });
}; };
// GPU types carry their accelerator vendor; non-acceleratable (CPU) types
// all map to the single 'cpu' bucket used to match templates.
const manufacturerOf = (instanceType: InstanceTypeItem) =>
instanceType.spec.acceleratable ? instanceType.spec?.manufacturer : 'cpu';
const matchesResourceType = (
instanceType: InstanceTypeItem,
type: 'gpu' | 'cpu'
) =>
type === 'gpu'
? !!instanceType.spec.acceleratable
: !instanceType.spec.acceleratable;
// apply the selection of instance type and template // apply the selection of instance type and template
const applySelection = ( const applySelection = (
instanceType: InstanceTypeItem, instanceType: InstanceTypeItem,
template: TemplateItem | undefined template: TemplateItem | undefined
) => { ) => {
const manufacturer = instanceType.spec?.manufacturer; const manufacturer = manufacturerOf(instanceType);
setInstanceTypeSelection({ setInstanceTypeSelection({
instanceType: instanceType.name, instanceType: instanceType.name,
@@ -212,6 +233,35 @@ const AddModal: React.FC<AddModalProps> = ({
form.current?.applyInstanceType?.(instanceType); form.current?.applyInstanceType?.(instanceType);
}; };
// Drop the instance-type-derived selection + form state. Used when no
// candidate is available (empty segment / org with no clusters) so a stale
// type / cluster never survives a switch or reload.
const clearSelection = () => {
setInstanceTypeSelection({
instanceType: undefined,
manufacturer: undefined
});
setTemplateId(undefined);
form.current?.applyInstanceType?.(undefined);
form.current?.setFieldValue?.('clusterId', null);
form.current?.setFieldValue?.(['spec', 'type'], undefined);
};
const autoSelectFirst = (
types: InstanceTypeItem[],
templates: TemplateItem[]
) => {
const first = types.find((item) => !item.disabled);
if (!first) {
clearSelection();
return;
}
applySelection(
first,
findTemplateByManufacturer(manufacturerOf(first), templates)
);
};
const findAggregateOf = ( const findAggregateOf = (
candidateName: string | undefined, candidateName: string | undefined,
clusterId: number | null | undefined, clusterId: number | null | undefined,
@@ -248,41 +298,34 @@ const AddModal: React.FC<AddModalProps> = ({
if (aggregate) { if (aggregate) {
setInstanceTypeSelection({ setInstanceTypeSelection({
instanceType: aggregate.name, instanceType: aggregate.name,
manufacturer: aggregate.spec?.manufacturer manufacturer: manufacturerOf(aggregate)
}); });
// Surface the persisted pick under the matching segment.
setResourceType(aggregate.spec.acceleratable ? 'gpu' : 'cpu');
} }
return; return;
} }
// Scope to clusters the chosen org owns (admin "All" view). // Scope to clusters the chosen org owns (admin "All" view).
const owned = filterTypesByOwner(instanceTypes, clusters || [], orgId); const owned = filterTypesByOwner(instanceTypes, clusters || [], orgId);
const first = owned.find((item) => !item.disabled);
if (!first) { // Prefer the active segment, but fall back to the other kind when it has
// The chosen org has no clusters (hence no instance types). Clear any // no enabled candidate so the drawer never opens on an empty list.
// prior pick so a stale instance type / cross-org cluster isn't left const hasEnabled = (type: 'gpu' | 'cpu') =>
// on the form. owned.some((it) => matchesResourceType(it, type) && !it.disabled);
setInstanceTypeSelection({ const other = resourceType === 'gpu' ? 'cpu' : 'gpu';
instanceType: undefined, const nextType = hasEnabled(resourceType)
manufacturer: undefined ? resourceType
}); : hasEnabled(other)
setTemplateId(undefined); ? other
form.current?.applyInstanceType?.(undefined); : resourceType;
form.current?.setFieldValue?.('clusterId', null); setResourceType(nextType);
form.current?.setFieldValue?.(['spec', 'type'], undefined);
return;
}
// On create, auto-select the first instance type in the list // On create, auto-select the first instance type of the chosen kind.
autoSelectFirst(
const template = findTemplateByManufacturer( owned.filter((it) => matchesResourceType(it, nextType)),
first.spec?.manufacturer,
templates templates
); );
applySelection(first, template);
// initially finise
}; };
// Fetch the (tenant-scoped) instance types + templates and auto-select. // Fetch the (tenant-scoped) instance types + templates and auto-select.
@@ -319,21 +362,14 @@ const AddModal: React.FC<AddModalProps> = ({
const handleScopeChange = (orgId?: number | null) => { const handleScopeChange = (orgId?: number | null) => {
if (!open || action !== PageAction.CREATE) return; if (!open || action !== PageAction.CREATE) return;
setScopeOrgId(orgId); setScopeOrgId(orgId);
setInstanceTypeSelection({ // Drop the instance-type-derived selection + form state (the selected type
instanceType: undefined,
manufacturer: undefined
});
setTemplateId(undefined);
// Also clear the instance-type-derived form state (the selected type
// card + its limits, the cluster, and spec.type). The cluster decides // card + its limits, the cluster, and spec.type). The cluster decides
// where the instance is scheduled, so a stale pick from the previous // where the instance is scheduled, so a stale pick from the previous
// scope must not survive — otherwise an instance owned by the newly // scope must not survive — otherwise an instance owned by the newly
// chosen org could land on the old org's cluster. The reload's // chosen org could land on the old org's cluster. The reload's
// owner-scoped auto-selection re-fills them from the new org, or leaves // owner-scoped auto-selection re-fills them from the new org, or leaves
// them empty (blocking submit) when the chosen org has no clusters. // them empty (blocking submit) when the chosen org has no clusters.
form.current?.applyInstanceType?.(undefined); clearSelection();
form.current?.setFieldValue?.('clusterId', null);
form.current?.setFieldValue?.(['spec', 'type'], undefined);
initializedRef.current = false; initializedRef.current = false;
loadCreateResources(orgId); loadCreateResources(orgId);
}; };
@@ -347,6 +383,7 @@ const AddModal: React.FC<AddModalProps> = ({
manufacturer: undefined manufacturer: undefined
}); });
setTemplateId(undefined); setTemplateId(undefined);
setResourceType('gpu');
setInstanceKeyword(''); setInstanceKeyword('');
setTemplateKeyword(''); setTemplateKeyword('');
setScopeOrgId(undefined); setScopeOrgId(undefined);
@@ -358,10 +395,20 @@ const AddModal: React.FC<AddModalProps> = ({
} }
}, [open, shouldAutoSelectResource, action]); }, [open, shouldAutoSelectResource, action]);
// filter instance types (already scoped to the chosen org's clusters) // Which kinds the chosen org actually offers — drives the GPU/CPU segment
const filteredInstanceTypes = ownedInstanceTypes.filter((item) => // availability so a user can't switch to an empty list.
matchKeyword([item.name], instanceKeyword) const hasGPUTypes = ownedInstanceTypes.some(
(item) => item.spec.acceleratable
); );
const hasCPUTypes = ownedInstanceTypes.some(
(item) => !item.spec.acceleratable
);
// filter instance types (already scoped to the chosen org's clusters) by the
// active GPU/CPU segment, then by the search keyword.
const filteredInstanceTypes = ownedInstanceTypes
.filter((item) => matchesResourceType(item, resourceType))
.filter((item) => matchKeyword([item.name], instanceKeyword));
// No instance types for the chosen org (e.g. it owns no clusters), and not // No instance types for the chosen org (e.g. it owns no clusters), and not
// mid-fetch — drives the "no available instance type" message in the form. // mid-fetch — drives the "no available instance type" message in the form.
@@ -405,7 +452,7 @@ const AddModal: React.FC<AddModalProps> = ({
const handleInstanceTypeChange = (item: InstanceTypeItem) => { const handleInstanceTypeChange = (item: InstanceTypeItem) => {
const template = findTemplateByManufacturer( const template = findTemplateByManufacturer(
item.spec?.manufacturer, manufacturerOf(item),
templateList templateList
); );
applySelection(item, template); applySelection(item, template);
@@ -430,6 +477,14 @@ const AddModal: React.FC<AddModalProps> = ({
}); });
}; };
const handleOnTypeChange = (next: 'gpu' | 'cpu') => {
setResourceType(next);
autoSelectFirst(
ownedInstanceTypes.filter((item) => matchesResourceType(item, next)),
templateList
);
};
return ( return (
<GSDrawer <GSDrawer
title={title} title={title}
@@ -465,7 +520,34 @@ const AddModal: React.FC<AddModalProps> = ({
}} }}
> >
<ColTitle style={{ paddingBottom: 0 }}> <ColTitle style={{ paddingBottom: 0 }}>
{intl.formatMessage({ id: 'gpuservice.instance.types' })} <Flex justify="space-between" align="center">
<span>
{intl.formatMessage({
id: 'gpuservice.instance.types'
})}
</span>
<Segmented
size="small"
shape="round"
className={styles.segmented}
value={resourceType}
onChange={handleOnTypeChange}
options={[
{
label: 'GPU',
value: 'gpu',
icon: <IconFont type="icon-gpu1" />,
disabled: !hasGPUTypes
},
{
label: 'CPU',
value: 'cpu',
icon: <IconFont type="icon-cpu" />,
disabled: !hasCPUTypes
}
]}
></Segmented>
</Flex>
</ColTitle> </ColTitle>
<Input <Input
allowClear allowClear
@@ -1,11 +1,14 @@
import { AutoTooltip, IconFont, ThemeTag } from '@gpustack/core-ui'; import { AutoTooltip, IconFont, ThemeTag } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Flex } from 'antd'; import { Flex, Tag } from 'antd';
import _ from 'lodash';
import styled from 'styled-components'; import styled from 'styled-components';
import { manufactureColorMap } from '../../templates/config'; import { manufactureColorMap } from '../../templates/config';
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;
@@ -16,9 +19,9 @@ const Title = styled.div`
font-weight: 500; font-weight: 500;
`; `;
const Meta = styled.div` const Meta = styled.div<{ $columns?: number }>`
display: grid; display: grid;
grid-template-columns: repeat(7, auto); grid-template-columns: repeat(${(props) => props.$columns ?? 7}, auto);
grid-auto-rows: minmax(15px, auto); grid-auto-rows: minmax(15px, auto);
justify-content: start; justify-content: start;
column-gap: 4px; column-gap: 4px;
@@ -32,10 +35,17 @@ const Meta = styled.div`
height: 3px; height: 3px;
border-radius: 50%; border-radius: 50%;
background-color: var(--ant-color-text-quaternary); background-color: var(--ant-color-text-quaternary);
margin: 0 6px; margin: 0 4px;
justify-self: center; justify-self: center;
} }
.meta-label {
font-size: 12px;
}
.meta-value {
font-size: 12px;
}
.meta-icon { .meta-icon {
font-size: 14px; font-size: 14px;
color: var(--ant-color-text-quaternary); color: var(--ant-color-text-quaternary);
@@ -53,7 +63,7 @@ interface MetadataSectionProps {
const MetaItem: React.FC<{ const MetaItem: React.FC<{
icon: string; icon: string;
label?: string; label?: string;
value?: string | null | number; value?: React.ReactNode;
showDot?: boolean; showDot?: boolean;
show?: boolean; show?: boolean;
}> = ({ icon, label, value, showDot = true, show = true }) => { }> = ({ icon, label, value, showDot = true, show = true }) => {
@@ -68,16 +78,47 @@ const MetaItem: React.FC<{
); );
}; };
const CPUManufacturerTag: React.FC<{ manufacturer?: string }> = ({
manufacturer
}) => {
return (
<Tag
color="blue"
disabled={false}
style={{
fontWeight: 400,
margin: 0,
marginLeft: 0,
display: 'flex',
alignItems: 'center',
lineHeight: 1.5
}}
variant="outlined"
>
{manufacturer}
</Tag>
);
};
function getInstanceDerived(item: InstanceTypeItemModel) { function getInstanceDerived(item: InstanceTypeItemModel) {
const spec = item.spec || {}; const spec = item.spec || {};
const acceleratable = spec.acceleratable; const acceleratable = spec.acceleratable;
const cpuManufacturer = acceleratable
? spec.cpu?.manufacturer
: spec.manufacturer;
return { return {
acceleratable, acceleratable,
isGPU: acceleratable, isGPU: acceleratable,
manufacturer: acceleratable ? spec.manufacturer || '' : 'cpu', manufacturer: acceleratable ? spec.manufacturer || '' : 'cpu', // GPU manufacturer or 'cpu' for non-acceleratable types
displayName: acceleratable ? spec.product || item.name : 'CPU', displayName: spec.product || item.name,
ramUnit: spec.unitResourcesParsed?.ram?.value, ramUnit: spec.unitResourcesParsed?.ram?.value,
os: _.capitalize(spec.os) || '',
arch: spec.arch,
cpuManufacturer: Vendors.includes(cpuManufacturer as any)
? _.capitalize(cpuManufacturer)
: _.toUpper(cpuManufacturer),
cpuUnitCores: spec.unitResourcesParsed?.cpu?.cores cpuUnitCores: spec.unitResourcesParsed?.cpu?.cores
}; };
} }
@@ -87,14 +128,15 @@ export const InstanceMetadataSection: React.FC<MetadataSectionProps> = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const { ramUnit, cpuUnitCores, isGPU } = getInstanceDerived({ const { ramUnit, cpuUnitCores, isGPU, os, arch } = getInstanceDerived({
spec spec
} as InstanceTypeItemModel); } as InstanceTypeItemModel);
return ( return (
<Meta> <Meta $columns={isGPU ? 11 : 7}>
{isGPU && ( {isGPU && (
<> <>
{/* row 1: Memory | Max | RAM */}
<MetaItem <MetaItem
show={isGPU} show={isGPU}
showDot={false} showDot={false}
@@ -102,6 +144,12 @@ export const InstanceMetadataSection: React.FC<MetadataSectionProps> = ({
label={intl.formatMessage({ id: 'gpuservice.instance.memory' })} label={intl.formatMessage({ id: 'gpuservice.instance.memory' })}
value={formatMemoryDisplay(spec?.memory ?? undefined) ?? '-'} value={formatMemoryDisplay(spec?.memory ?? undefined) ?? '-'}
/> />
<MetaItem
showDot={true}
icon="icon-ram-02"
label={intl.formatMessage({ id: 'gpuservice.instance.ram' })}
value={ramUnit ? `${ramUnit} GB` : '-'}
/>
<MetaItem <MetaItem
icon="icon-database" icon="icon-database"
label={intl.formatMessage( label={intl.formatMessage(
@@ -112,24 +160,35 @@ export const InstanceMetadataSection: React.FC<MetadataSectionProps> = ({
)} )}
value={`${spec.maxComputeUnitCount || 0}`} value={`${spec.maxComputeUnitCount || 0}`}
/> />
{/* row 2: OS | Arch | CPU */}
<MetaItem <MetaItem
showDot={false} showDot={false}
icon="icon-ram-02" icon="icon-server02"
label={intl.formatMessage({ id: 'gpuservice.instance.ram' })} label={intl.formatMessage({ id: 'gpuservice.instance.os' })}
value={ramUnit ? `${ramUnit} GB` : '-'} value={os || '-'}
/>
<MetaItem
icon="icon-cube"
label={intl.formatMessage({ id: 'gpuservice.instance.arch' })}
value={_.toUpper(arch) || '-'}
/> />
<MetaItem <MetaItem
show={isGPU} show={isGPU}
showDot={true} showDot={true}
icon="icon-cpu" icon="icon-cpu"
label="CPU" label="CPU"
value={cpuUnitCores || '-'} value={
<Flex gap={4} align="center">
<span>{cpuUnitCores || '-'}</span>
</Flex>
}
/> />
</> </>
)} )}
{!isGPU && ( {!isGPU && (
<> <>
{/* row 1: RAM | Max */}
<MetaItem <MetaItem
showDot={false} showDot={false}
icon="icon-ram-02" icon="icon-ram-02"
@@ -146,6 +205,18 @@ export const InstanceMetadataSection: React.FC<MetadataSectionProps> = ({
)} )}
value={`${spec.maxComputeUnitCount || 0}`} 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>
@@ -155,10 +226,12 @@ export const InstanceMetadataSection: React.FC<MetadataSectionProps> = ({
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => { const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
const specData = item.spec || {}; const specData = item.spec || {};
const { acceleratable, manufacturer, displayName } = getInstanceDerived(item); const { acceleratable, manufacturer, displayName, cpuManufacturer } =
getInstanceDerived(item);
const manufacturerColor = manufactureColorMap[manufacturer] ?? 'purple'; const manufacturerColor = manufactureColorMap[manufacturer] ?? 'purple';
const showManufacturerTag = acceleratable && manufacturer; const showManufacturerTag = acceleratable && !!manufacturer;
const showCPUManufacturerTag = !acceleratable && !!cpuManufacturer;
return ( return (
<Flex <Flex
@@ -167,10 +240,19 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
style={{ height: '100%' }} style={{ height: '100%' }}
> >
<Title> <Title>
<Flex gap={8} align="center"> <Flex gap={8} align="center" style={{ width: '100%', minWidth: 0 }}>
<AutoTooltip ghost minWidth={20} maxWidth={200}> <div
{displayName || '-'} className="instance-type-name"
</AutoTooltip> style={{
flex: 1,
minWidth: 0
}}
>
<AutoTooltip ghost minWidth={20} maxWidth={'100%'}>
{displayName || '-'}
</AutoTooltip>
</div>
{showManufacturerTag && ( {showManufacturerTag && (
<ThemeTag <ThemeTag
color={manufacturerColor} color={manufacturerColor}
@@ -180,6 +262,11 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
{manufacturer?.toUpperCase()} {manufacturer?.toUpperCase()}
</ThemeTag> </ThemeTag>
)} )}
{showCPUManufacturerTag && (
<CPUManufacturerTag
manufacturer={`${cpuManufacturer}`}
></CPUManufacturerTag>
)}
</Flex> </Flex>
</Title> </Title>
<InstanceMetadataSection spec={specData}></InstanceMetadataSection> <InstanceMetadataSection spec={specData}></InstanceMetadataSection>
@@ -149,6 +149,27 @@ export interface InstanceTypeOnceMaxRequestResource {
localStorage: QuanityLocalStorage; localStorage: QuanityLocalStorage;
} }
export interface CPUCache {
l1i: string;
l1d: string;
l2: string;
l3: string;
}
export interface CPUInfo {
physicalCores: string;
threadsPerPhysicalCore: string;
logicalCores: string;
stepping: string | null;
clockSpeed: string | null;
maxClockSpeed: string | null;
cacheLine: string;
cache: CPUCache;
manufacturer: string;
product: string;
family: string;
}
export interface InstanceTypeSpec { export interface InstanceTypeSpec {
group: string; group: string;
acceleratable: boolean; acceleratable: boolean;
@@ -163,6 +184,10 @@ export interface InstanceTypeSpec {
cpu: QuanityCPU; cpu: QuanityCPU;
ram: QuanityMemory; ram: QuanityMemory;
}; };
os?: string;
arch?: string;
cpu?: CPUInfo;
cache?: Record<string, string>;
unitResourcesParsed?: { unitResourcesParsed?: {
cpu: { cpu: {
cores?: number; cores?: number;
@@ -87,6 +87,7 @@ export default function useQueryInstanceTypes() {
return { return {
detailData: dataList, detailData: dataList,
setDataList,
loading, loading,
cancelRequest, cancelRequest,
fetchData: queryInstanceTypes fetchData: queryInstanceTypes
@@ -8,6 +8,7 @@
display: flex; display: flex;
flex: 1; flex: 1;
max-width: 33%; max-width: 33%;
min-width: 0;
min-height: 0; min-height: 0;
} }
@@ -40,3 +41,7 @@
} }
} }
} }
.segmented {
font-weight: 400;
font-size: 12px;
}