fix: alignment api
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import Separator from '@/pages/llmodels/components/separator';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
@@ -61,20 +62,32 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const form = useRef<any>(null);
|
||||
const autoSelectedRef = useRef(false);
|
||||
const [selectedInstanceType, setSelectedInstanceType] = useState<string>();
|
||||
const [selectedManufacturer, setSelectedManufacturer] = useState<string>();
|
||||
const [templateId, setTemplateId] = useState<number>();
|
||||
const [instanceKeyword, setInstanceKeyword] = useState('');
|
||||
const [templateKeyword, setTemplateKeyword] = useState('');
|
||||
|
||||
const { detailData, fetchData } = useQueryInstanceTypes();
|
||||
const {
|
||||
detailData,
|
||||
loading: instanceTypesLoading,
|
||||
fetchData
|
||||
} = useQueryInstanceTypes();
|
||||
const { detailData: templatesData, fetchData: fetchTemplates } =
|
||||
useQueryTemplates();
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
autoSelectedRef.current = false;
|
||||
fetchData({});
|
||||
fetchTemplates({ page: 1, perPage: 100 });
|
||||
} else {
|
||||
setSelectedInstanceType(undefined);
|
||||
setSelectedManufacturer(undefined);
|
||||
setTemplateId(undefined);
|
||||
setInstanceKeyword('');
|
||||
setTemplateKeyword('');
|
||||
}
|
||||
}, [open, fetchData, fetchTemplates]);
|
||||
|
||||
@@ -119,6 +132,59 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
);
|
||||
}, [templateKeyword, manufacturerMatchedTemplates]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (action !== PageAction.CREATE) return;
|
||||
if (autoSelectedRef.current) return;
|
||||
if (!instanceTypeList.length) return;
|
||||
if (!templatesData) return;
|
||||
|
||||
const firstInstanceType = instanceTypeList[0];
|
||||
const manufacturer = firstInstanceType.spec?.manufacturer;
|
||||
const name = firstInstanceType.metadata?.name;
|
||||
const matchedTemplate = manufacturer
|
||||
? templateList.find((t) => t.manufacturer === manufacturer)
|
||||
: templateList[0];
|
||||
|
||||
autoSelectedRef.current = true;
|
||||
setSelectedInstanceType(name);
|
||||
setSelectedManufacturer(manufacturer);
|
||||
if (matchedTemplate) {
|
||||
setTemplateId(matchedTemplate.id);
|
||||
}
|
||||
|
||||
const applyToForm = () => {
|
||||
const currentSpec = form.current?.getFieldsValue()?.spec || {};
|
||||
const updatedSpec = {
|
||||
...currentSpec,
|
||||
type: name,
|
||||
resources: { ...(currentSpec.resources || {}), accelerator: '1' }
|
||||
};
|
||||
|
||||
if (matchedTemplate) {
|
||||
form.current?.setFieldsValue({
|
||||
manufacturer: matchedTemplate.manufacturer,
|
||||
spec: {
|
||||
...updatedSpec,
|
||||
...matchedTemplate.spec,
|
||||
resources: {
|
||||
...(updatedSpec.resources || {}),
|
||||
...(matchedTemplate.spec?.resources || {})
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
form.current?.setFieldsValue({ spec: updatedSpec });
|
||||
}
|
||||
};
|
||||
|
||||
if (form.current) {
|
||||
applyToForm();
|
||||
} else {
|
||||
queueMicrotask(applyToForm);
|
||||
}
|
||||
}, [open, action, instanceTypeList, templateList, templatesData]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
form.current?.submit();
|
||||
};
|
||||
@@ -229,15 +295,12 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
onChange={(e) => setInstanceKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{filteredInstanceTypes.length > 0 ? (
|
||||
<InstanceTypeList
|
||||
value={selectedInstanceType}
|
||||
dataList={filteredInstanceTypes}
|
||||
onChange={handleInstanceTypeChange}
|
||||
/>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
<InstanceTypeList
|
||||
value={selectedInstanceType}
|
||||
dataList={filteredInstanceTypes}
|
||||
loading={instanceTypesLoading}
|
||||
onChange={handleInstanceTypeChange}
|
||||
/>
|
||||
</PanelBody>
|
||||
</ColumnWrapper>
|
||||
<Separator></Separator>
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { AutoTooltip, IconFont, StatusTag, ThemeTag } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Flex } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
convertKiToGi,
|
||||
InstanceTypePhaseLabelMap,
|
||||
InstanceTypePhaseStatus
|
||||
} from '../config';
|
||||
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
|
||||
|
||||
const Title = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
color: var(--ant-color-text);
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const Meta = styled.div`
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-size: 13px;
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
.dot {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--ant-color-text-quaternary);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.meta-icon {
|
||||
font-size: 14px;
|
||||
color: var(--ant-color-text-quaternary);
|
||||
}
|
||||
`;
|
||||
|
||||
interface InstanceTypeItemProps {
|
||||
item: InstanceTypeItemModel;
|
||||
showStatus?: boolean;
|
||||
}
|
||||
|
||||
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
|
||||
item,
|
||||
showStatus = true
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const name = item.metadata?.name;
|
||||
const acceleratable = item.spec?.acceleratable;
|
||||
const product = item.spec?.product;
|
||||
const displayName = product || (!acceleratable ? 'CPU' : name);
|
||||
const manufacturer = item.spec?.manufacturer?.toUpperCase();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>
|
||||
<Flex gap={8} align="center">
|
||||
<AutoTooltip ghost minWidth={20} maxWidth={180}>
|
||||
{displayName}
|
||||
</AutoTooltip>
|
||||
{acceleratable && manufacturer && (
|
||||
<span
|
||||
style={{
|
||||
color: 'var(--ant-color-text-tertiary)',
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
<ThemeTag color="purple">{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>{convertKiToGi(item.spec?.memory) ?? '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
<span className="dot"></span>
|
||||
<span className="meta-item">
|
||||
<IconFont type="icon-cube" className="meta-icon" />
|
||||
<Flex align="center" gap={4}>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}
|
||||
</span>
|
||||
<span>{item.status?.accelerator?.remaining ?? '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
</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>{item.status?.ram?.capacity ?? '-'}</span>
|
||||
</Flex>
|
||||
</span>
|
||||
<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>
|
||||
</span>
|
||||
</Meta>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstanceTypeItem;
|
||||
@@ -1,13 +1,11 @@
|
||||
import { AutoTooltip, StatusTag, TemplateCard } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Flex, Tag } from 'antd';
|
||||
import FileSkeleton from '@/pages/llmodels/components/model-source/file-skeleton';
|
||||
import { TemplateCard } from '@gpustack/core-ui';
|
||||
import { Empty, Spin } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
InstanceTypePhaseLabelMap,
|
||||
InstanceTypePhaseStatus,
|
||||
InstanceTypePhaseValueMap
|
||||
} from '../config';
|
||||
import { InstanceTypeItem } from '../config/types';
|
||||
import { InstanceTypePhaseValueMap } from '../config';
|
||||
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
|
||||
import InstanceTypeItem from './instance-type-item';
|
||||
|
||||
const TypeGrid = styled.div`
|
||||
display: flex;
|
||||
@@ -15,53 +13,42 @@ const TypeGrid = styled.div`
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
const TypeName = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
color: var(--ant-color-text);
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const TypeMeta = styled.div`
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-size: 13px;
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--ant-color-text-quaternary);
|
||||
}
|
||||
`;
|
||||
|
||||
interface InstanceTypeListProps {
|
||||
value?: string;
|
||||
onChange?: (item: InstanceTypeItem) => void;
|
||||
dataList?: InstanceTypeItem[];
|
||||
onChange?: (item: InstanceTypeItemModel) => void;
|
||||
dataList?: InstanceTypeItemModel[];
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const isAvailable = (item: InstanceTypeItem) =>
|
||||
const isAvailable = (item: InstanceTypeItemModel) =>
|
||||
item.status?.phase === InstanceTypePhaseValueMap.Active;
|
||||
|
||||
const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
dataList = []
|
||||
dataList = [],
|
||||
loading
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const handleSelect = (item: InstanceTypeItem) => {
|
||||
const handleSelect = (item: InstanceTypeItemModel) => {
|
||||
if (!isAvailable(item)) return;
|
||||
onChange?.(item);
|
||||
};
|
||||
|
||||
if (!dataList.length) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Spin spinning size="middle">
|
||||
<TypeGrid style={{ minHeight: 200 }}>
|
||||
{_.times(6, (index: number) => (
|
||||
<FileSkeleton key={index} counts={3} itemHeight={106} />
|
||||
))}
|
||||
</TypeGrid>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TypeGrid>
|
||||
{dataList.map((item) => {
|
||||
@@ -78,58 +65,7 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
||||
disabled={disabled}
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
<TypeName>
|
||||
<Flex gap={8} align="center">
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{name}
|
||||
</AutoTooltip>
|
||||
</Flex>
|
||||
<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: ''
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{item.spec?.acceleratable && (
|
||||
<Tag
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
color: 'var(--ant-color-text-tertiary)'
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '}
|
||||
{item.status?.accelerator?.remaining ?? '-'}
|
||||
</span>
|
||||
</Tag>
|
||||
)}
|
||||
</Flex>
|
||||
</TypeName>
|
||||
<TypeMeta>
|
||||
<span style={{ display: 'flex', height: 15 }}>
|
||||
{item.spec?.acceleratable && (
|
||||
<span className="meta-row">
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '}
|
||||
{item.spec?.memory ?? '-'}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="meta-row gap-16">
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}{' '}
|
||||
{item.status?.ram?.capacity ?? '-'}
|
||||
</span>
|
||||
<span>vCPU {item.status?.cpu?.capacity ?? '-'}</span>
|
||||
</span>
|
||||
</TypeMeta>
|
||||
<InstanceTypeItem item={item} />
|
||||
</TemplateCard>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user