fix: instance types switch between gpu and cpu
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -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': 'システムディスク',
|
||||||
|
|||||||
@@ -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': 'Системный диск',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user