refactor: gpu service API alignment
This commit is contained in:
@@ -101,7 +101,8 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
|
||||
const instanceTypeList = detailData?.items || [];
|
||||
const templateList = templatesData?.items || [];
|
||||
const readonly = action === PageAction.VIEW;
|
||||
// const readonly = action === PageAction.VIEW;
|
||||
const readonly = false;
|
||||
const isRecreate = realAction === PageAction.CREATE;
|
||||
const showResourceSelectors = action === PageAction.CREATE || isRecreate;
|
||||
const shouldAutoSelectResource = action === PageAction.CREATE && !isRecreate;
|
||||
@@ -119,27 +120,33 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
instanceType: InstanceTypeItem,
|
||||
template: TemplateItem | undefined
|
||||
) => {
|
||||
const name = instanceType.metadata?.name;
|
||||
const manufacturer = instanceType.spec?.manufacturer;
|
||||
|
||||
setInstanceTypeSelection({ instanceType: name, manufacturer });
|
||||
setInstanceTypeSelection({
|
||||
instanceType: instanceType.name,
|
||||
manufacturer
|
||||
});
|
||||
setTemplateId(template?.id);
|
||||
|
||||
const currentSpec = form.current?.getFieldsValue()?.spec || {};
|
||||
const updatedSpec = {
|
||||
...currentSpec,
|
||||
type: name,
|
||||
resources: { ...(currentSpec.resources || {}), accelerator: '1' }
|
||||
};
|
||||
|
||||
if (template) {
|
||||
const currentSpec = form.current?.getFieldsValue()?.spec || {};
|
||||
form.current?.setFieldsValue({
|
||||
manufacturer: template.manufacturer,
|
||||
description: JSON.stringify(
|
||||
{
|
||||
name: instanceType.name,
|
||||
spec: {
|
||||
...instanceType.spec
|
||||
}
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
spec: {
|
||||
...updatedSpec,
|
||||
...currentSpec,
|
||||
...template.spec,
|
||||
resources: {
|
||||
...(updatedSpec.resources || {}),
|
||||
...(currentSpec.resources || {}),
|
||||
...(template.spec?.resources || {})
|
||||
}
|
||||
}
|
||||
@@ -147,16 +154,46 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
} else {
|
||||
form.current?.setFieldsValue({
|
||||
manufacturer: undefined,
|
||||
spec: updatedSpec
|
||||
description: JSON.stringify(instanceType.spec, null, 2)
|
||||
});
|
||||
}
|
||||
form.current?.applyInstanceType?.(instanceType);
|
||||
};
|
||||
|
||||
const findAggregateOf = (
|
||||
candidateName: string | undefined,
|
||||
clusterId: number | null | undefined,
|
||||
instanceTypes: InstanceTypeItem[]
|
||||
): InstanceTypeItem | undefined => {
|
||||
if (!candidateName) return undefined;
|
||||
return instanceTypes.find((item) =>
|
||||
(item.status?.acceleratorTiers ?? []).some((tier) =>
|
||||
(tier.candidates ?? []).some(
|
||||
(c) => c.name === candidateName && Number(c.cluster) === clusterId
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const applyAutoSelection = (
|
||||
instanceTypes: InstanceTypeItem[],
|
||||
templates: TemplateItem[]
|
||||
) => {
|
||||
if (!shouldAutoSelectResource) return;
|
||||
// On edit / view, surface the persisted selection in the card list.
|
||||
if (!shouldAutoSelectResource) {
|
||||
const aggregate = findAggregateOf(
|
||||
data?.spec?.type,
|
||||
data?.clusterId,
|
||||
instanceTypes
|
||||
);
|
||||
if (aggregate) {
|
||||
setInstanceTypeSelection({
|
||||
instanceType: aggregate.name,
|
||||
manufacturer: aggregate.spec?.manufacturer
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!instanceTypes.length) return;
|
||||
|
||||
const first = instanceTypes[0];
|
||||
@@ -178,18 +215,21 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const session = ++sessionRef.current;
|
||||
Promise.all([fetchData({}), fetchTemplates({ page: -1 })]).then(
|
||||
([instanceRes, templatesRes]) => {
|
||||
if (action === PageAction.CREATE) {
|
||||
const session = ++sessionRef.current;
|
||||
Promise.all([
|
||||
fetchData({ page: 1, perPage: 100 }),
|
||||
fetchTemplates({ page: -1 })
|
||||
]).then(([instanceRes, templatesRes]) => {
|
||||
if (sessionRef.current !== session) return;
|
||||
applyAutoSelection(instanceRes?.items || [], templatesRes?.items || []);
|
||||
}
|
||||
);
|
||||
}, [open, shouldAutoSelectResource]);
|
||||
});
|
||||
}
|
||||
}, [open, shouldAutoSelectResource, action]);
|
||||
|
||||
// filter instance types
|
||||
const filteredInstanceTypes = instanceTypeList.filter((item) =>
|
||||
matchKeyword([item.metadata?.name], instanceKeyword)
|
||||
matchKeyword([item.name], instanceKeyword)
|
||||
);
|
||||
|
||||
// filter templates based on selection and keyword
|
||||
@@ -370,7 +410,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
}
|
||||
>
|
||||
<>
|
||||
{action !== PageAction.VIEW && (
|
||||
{action !== PageAction.EDIT && (
|
||||
<ColTitle>
|
||||
{intl.formatMessage({ id: 'common.title.config' })}
|
||||
</ColTitle>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { AutoTooltip, IconFont, StatusTag, ThemeTag } from '@gpustack/core-ui';
|
||||
import { AutoTooltip, IconFont, ThemeTag } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Flex } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { manufactureColorMap } from '../../templates/config';
|
||||
import {
|
||||
convertKiToGi,
|
||||
InstanceTypePhaseLabelMap,
|
||||
InstanceTypePhaseStatus
|
||||
} from '../config';
|
||||
import { convertKiToGi } from '../config';
|
||||
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
|
||||
|
||||
const toDisplayUnit = (value?: string) =>
|
||||
const toDisplayUnit = (value?: string | null) =>
|
||||
value ? value.replace(/Gi$/, 'GB').replace(/Ti$/, 'TB') : value;
|
||||
|
||||
const Title = styled.div`
|
||||
@@ -60,24 +56,48 @@ interface InstanceTypeItemProps {
|
||||
showStatus?: boolean;
|
||||
}
|
||||
|
||||
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
|
||||
item,
|
||||
showStatus = true
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const name = item.metadata?.name;
|
||||
const acceleratable = item.spec?.acceleratable;
|
||||
const MetaItem: React.FC<{
|
||||
icon: string;
|
||||
label: string;
|
||||
value?: string | null;
|
||||
showDot?: boolean;
|
||||
show?: boolean;
|
||||
}> = ({ icon, label, value, showDot = true, show = true }) => {
|
||||
if (!show) return null;
|
||||
return (
|
||||
<>
|
||||
{showDot && <span className="dot"></span>}
|
||||
<span className="meta-item">
|
||||
<IconFont type={icon} className="meta-icon" />
|
||||
<Flex align="center" gap={4}>
|
||||
<span>{label}</span>
|
||||
<span>{value || '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const manufacturerKey = item.spec?.manufacturer;
|
||||
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
|
||||
const intl = useIntl();
|
||||
const name = item.name;
|
||||
const specData = item.spec || {};
|
||||
const acceleratable = specData.acceleratable;
|
||||
|
||||
const manufacturerKey = specData.manufacturer || (acceleratable ? '' : 'cpu');
|
||||
const manufacturer = manufacturerKey?.toUpperCase();
|
||||
const manufacturerColor =
|
||||
(manufacturerKey && manufactureColorMap[manufacturerKey]) ?? 'purple';
|
||||
|
||||
// resource remaining status
|
||||
const remainingData = item.status?.remaining || {};
|
||||
|
||||
const renderName = () => {
|
||||
const product = item.spec?.product;
|
||||
const product = specData.product;
|
||||
const cpuCapacity = remainingData.cpu;
|
||||
const displayName =
|
||||
product ||
|
||||
(!acceleratable ? `${item.status?.cpu?.capacity} vCPUs` : name);
|
||||
(!acceleratable && cpuCapacity ? `${cpuCapacity} vCPUs` : name);
|
||||
return displayName;
|
||||
};
|
||||
|
||||
@@ -85,8 +105,8 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
|
||||
<>
|
||||
<Title>
|
||||
<Flex gap={8} align="center">
|
||||
<AutoTooltip ghost minWidth={20} maxWidth={180}>
|
||||
{item.spec ? renderName() : '-'}
|
||||
<AutoTooltip ghost minWidth={20} maxWidth={200}>
|
||||
{renderName() || '-'}
|
||||
</AutoTooltip>
|
||||
{acceleratable && manufacturer && (
|
||||
<span
|
||||
@@ -95,99 +115,59 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
<ThemeTag color={manufacturerColor}>{manufacturer}</ThemeTag>
|
||||
<ThemeTag color={manufacturerColor} disabled={false}>
|
||||
{manufacturer}
|
||||
</ThemeTag>
|
||||
</span>
|
||||
)}
|
||||
</Flex>
|
||||
{showStatus && (
|
||||
<Flex gap={8} align="center">
|
||||
{item.status?.phase && (
|
||||
<StatusTag
|
||||
statusValue={{
|
||||
status:
|
||||
InstanceTypePhaseStatus[item.status.phase] ?? 'inactive',
|
||||
text:
|
||||
InstanceTypePhaseLabelMap[item.status.phase] ??
|
||||
item.status.phase,
|
||||
message: ''
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Title>
|
||||
<Meta>
|
||||
<span style={{ display: 'flex', height: 15 }}>
|
||||
{acceleratable && (
|
||||
<span className="meta-row">
|
||||
<span className="meta-item">
|
||||
<IconFont type="icon-gpu1" className="meta-icon" />
|
||||
<Flex align="center" gap={4}>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}
|
||||
</span>
|
||||
<span>
|
||||
{toDisplayUnit(convertKiToGi(item.spec?.memory)) ?? '-'}
|
||||
</span>
|
||||
</Flex>
|
||||
</span>
|
||||
{item.spec?.sliced && (
|
||||
<>
|
||||
<span className="dot"></span>
|
||||
<span className="meta-item">
|
||||
<IconFont type="icon-sliced" className="meta-icon" />
|
||||
<Flex align="center" gap={4}>
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.instance.sliced'
|
||||
})}
|
||||
</span>
|
||||
<span>{item.spec?.sliced ?? '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{item.status?.accelerator?.remaining && (
|
||||
<>
|
||||
<span className="dot"></span>
|
||||
<span className="meta-item">
|
||||
<IconFont type="icon-database" className="meta-icon" />
|
||||
<Flex align="center" gap={4}>
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.instance.stock'
|
||||
})}
|
||||
</span>
|
||||
<span>{item.status?.accelerator?.remaining ?? '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<MetaItem
|
||||
showDot={false}
|
||||
icon="icon-gpu1"
|
||||
label={intl.formatMessage({ id: 'gpuservice.instance.memory' })}
|
||||
value={
|
||||
toDisplayUnit(convertKiToGi(specData?.memory ?? undefined)) ??
|
||||
'-'
|
||||
}
|
||||
></MetaItem>
|
||||
<MetaItem
|
||||
show={!!specData?.sliced}
|
||||
icon="icon-sliced"
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.instance.sliced'
|
||||
})}
|
||||
value={specData?.sliced}
|
||||
></MetaItem>
|
||||
<MetaItem
|
||||
show={!!remainingData.accelerator}
|
||||
icon="icon-database"
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.instance.stock'
|
||||
})}
|
||||
value={remainingData.accelerator}
|
||||
></MetaItem>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="meta-row">
|
||||
<span className="meta-item">
|
||||
<IconFont type="icon-ram-02" className="meta-icon" />
|
||||
<Flex align="center" gap={4}>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}
|
||||
</span>
|
||||
<span>{toDisplayUnit(item.status?.ram?.capacity) ?? '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
{acceleratable && (
|
||||
<>
|
||||
<span className="dot"></span>
|
||||
<span className="meta-item">
|
||||
<IconFont type="icon-cpu" className="meta-icon" />
|
||||
<Flex align="center" gap={4}>
|
||||
<span>vCPU</span>
|
||||
<span>{item.status?.cpu?.capacity ?? '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<MetaItem
|
||||
showDot={false}
|
||||
icon="icon-ram-02"
|
||||
label={intl.formatMessage({ id: 'gpuservice.instance.ram' })}
|
||||
value={toDisplayUnit(convertKiToGi(remainingData.ram)) ?? '-'}
|
||||
></MetaItem>
|
||||
<MetaItem
|
||||
show={acceleratable}
|
||||
showDot={true}
|
||||
icon="icon-cpu"
|
||||
label="vCPU"
|
||||
value={remainingData.cpu ?? '-'}
|
||||
></MetaItem>
|
||||
</span>
|
||||
</Meta>
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TemplateCard } from '@gpustack/core-ui';
|
||||
import { Empty, Spin } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
import { InstanceTypePhaseValueMap } from '../config';
|
||||
import { getAcceleratorMax } from '../config';
|
||||
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
|
||||
import InstanceTypeItem from './instance-type-item';
|
||||
|
||||
@@ -20,8 +20,12 @@ interface InstanceTypeListProps {
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const isAvailable = (item: InstanceTypeItemModel) =>
|
||||
item.status?.phase === InstanceTypePhaseValueMap.Active;
|
||||
// CPU types always have stock (no accelerator). Accelerated types are
|
||||
// considered available when at least one tier offers onceMaxRequest > 0.
|
||||
const isAvailable = (item: InstanceTypeItemModel) => {
|
||||
if (!item.spec?.acceleratable) return true;
|
||||
return getAcceleratorMax(item.status?.acceleratorTiers) > 0;
|
||||
};
|
||||
|
||||
const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
||||
value,
|
||||
@@ -53,7 +57,7 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
||||
<TypeGrid>
|
||||
{dataList.map((item) => {
|
||||
const disabled = !isAvailable(item);
|
||||
const name = item.metadata?.name;
|
||||
const name = item.name;
|
||||
return (
|
||||
<TemplateCard
|
||||
key={name}
|
||||
|
||||
@@ -31,7 +31,7 @@ const eventTypeStatus: Record<string, 'success' | 'warning' | 'error'> = {
|
||||
|
||||
const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const { open, onCancel, name } = props || {};
|
||||
const { open, onCancel, name, namespace, clusterID } = props || {};
|
||||
const [activeKey, setActiveKey] = useState('instance');
|
||||
|
||||
const {
|
||||
@@ -52,9 +52,9 @@ const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
||||
const volumeEvents = volumeEventsData?.items ?? [];
|
||||
|
||||
const refreshAll = () => {
|
||||
if (!name) return;
|
||||
fetchInstanceEvents({ name });
|
||||
fetchVolumeEvents({ name });
|
||||
if (!name || !namespace || !clusterID) return;
|
||||
fetchInstanceEvents({ name, namespace, clusterID });
|
||||
fetchVolumeEvents({ name, namespace, clusterID });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user