From 4128de61f95139ce09fe063b594508f56c0ad8a7 Mon Sep 17 00:00:00 2001 From: jialin Date: Sat, 9 May 2026 21:09:56 +0800 Subject: [PATCH] fix: api test --- src/atoms/gpuservice.ts | 6 + src/pages/gpu-service/instances/apis/index.ts | 59 +++++++--- .../instances/components/add-modal.tsx | 31 +++-- .../components/instance-type-list.tsx | 59 +++++++--- .../gpu-service/instances/config/index.ts | 109 ++++++++++++++++-- .../gpu-service/instances/config/types.ts | 33 ++++-- .../gpu-service/instances/forms/index.tsx | 103 ++++++----------- .../instances/forms/instance-type.tsx | 108 +++++++---------- .../instances/forms/storage-volume.tsx | 97 ++++++++-------- .../instances/forms/template-selector.tsx | 1 - .../instances/hooks/use-instances-columns.tsx | 16 +-- src/pages/gpu-service/instances/index.tsx | 61 ++++++++-- .../instances/services/use-create-instance.ts | 7 +- .../services/use-query-instance-types.ts | 22 +++- .../instances/services/use-update-instance.ts | 7 +- .../gpu-service/public-keys/apis/index.ts | 5 +- src/pages/gpu-service/public-keys/index.tsx | 4 +- .../public-keys/services/use-get-sshkey.ts | 3 +- src/pages/gpu-service/storage/apis/index.ts | 74 +++++++++--- src/pages/gpu-service/storage/config/types.ts | 32 ++++- src/pages/gpu-service/storage/forms/basic.tsx | 59 +++++----- src/pages/gpu-service/storage/forms/index.tsx | 19 +-- .../storage/hooks/use-storage-columns.tsx | 4 +- src/pages/gpu-service/storage/index.tsx | 57 +++++++-- .../storage/services/use-create-storage.ts | 7 +- .../services/use-query-storage-class.ts | 34 ++++++ .../storage/services/use-query-storage.ts | 8 +- .../storage/services/use-update-storage.ts | 7 +- .../gpu-service/templates/forms/basic.tsx | 35 +++--- 29 files changed, 687 insertions(+), 380 deletions(-) create mode 100644 src/atoms/gpuservice.ts create mode 100644 src/pages/gpu-service/storage/services/use-query-storage-class.ts diff --git a/src/atoms/gpuservice.ts b/src/atoms/gpuservice.ts new file mode 100644 index 00000000..f7f9df55 --- /dev/null +++ b/src/atoms/gpuservice.ts @@ -0,0 +1,6 @@ +import { ClusterListItem } from '@/pages/cluster-management/config/types'; +import { atom } from 'jotai'; + +export const currentClusterAtom = atom< + (Partial & { label?: string; value?: number }) | null +>(null); diff --git a/src/pages/gpu-service/instances/apis/index.ts b/src/pages/gpu-service/instances/apis/index.ts index a7e93176..3f84ac7b 100644 --- a/src/pages/gpu-service/instances/apis/index.ts +++ b/src/pages/gpu-service/instances/apis/index.ts @@ -1,18 +1,29 @@ import { request } from '@umijs/max'; import { InstanceTypeItem, ListItem } from '../config/types'; -export const GPU_SERVICE_INSTANCES_API = (namespace: string) => - `/proxy/apis/worker.gpustack.ai/v1/namespaces/${namespace}/instances`; +export const GPU_SERVICE_INSTANCES_API = (params: { + namespace: string; + clusterID?: number; +}) => + `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/namespaces/${params.namespace}/instances`; -export const GPU_SERVICE_INSTANCES_TYPE_API = - '/proxy/apis/worker.gpustack.ai/v1/instancetypes'; +export const GPU_SERVICE_INSTANCES_TYPE_API = (params: { + clusterID?: number; +}) => + `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/instancetypes`; export async function queryGPUServiceInstances( - params: Global.K8sSearchParams, + params: Global.K8sSearchParams & { + namespace: string; + clusterID?: number; + }, options?: any ) { return request>( - GPU_SERVICE_INSTANCES_API(params.namespace || ''), + GPU_SERVICE_INSTANCES_API({ + namespace: params.namespace, + clusterID: params.clusterID + }), { method: 'GET', params, @@ -24,27 +35,38 @@ export async function queryGPUServiceInstances( export async function createGPUServiceInstance( params: { namespace: string; + clusterID?: number; data: Global.K8sCommonData; }, option?: any ) { - return request(GPU_SERVICE_INSTANCES_API(params.namespace), { - method: 'POST', - data: params.data, - cancelToken: option?.token - }); + return request( + GPU_SERVICE_INSTANCES_API({ + namespace: params.namespace, + clusterID: params.clusterID + }), + { + method: 'POST', + data: params.data, + cancelToken: option?.token + } + ); } export async function updateGPUServiceInstance( params: { namespace: string; + clusterID?: number; id: number; data: Global.K8sCommonData; }, option?: any ) { return request( - `${GPU_SERVICE_INSTANCES_API(params.namespace)}/${params.id}`, + `${GPU_SERVICE_INSTANCES_API({ + namespace: params.namespace, + clusterID: params.clusterID + })}/${params.id}`, { method: 'PUT', data: params.data, @@ -56,12 +78,16 @@ export async function updateGPUServiceInstance( export async function deleteGPUServiceInstance( params: { namespace: string; + clusterID?: number; id: number; }, option?: any ) { return request( - `${GPU_SERVICE_INSTANCES_API(params.namespace)}/${params.id}`, + `${GPU_SERVICE_INSTANCES_API({ + namespace: params.namespace, + clusterID: params.clusterID + })}/${params.id}`, { method: 'DELETE', cancelToken: option?.token @@ -72,11 +98,11 @@ export async function deleteGPUServiceInstance( // =========== Instance Types =========== export async function queryGPUServiceInstanceTypes( - params: Global.K8sSearchParams, + params: Global.K8sSearchParams & { clusterID?: number }, options?: any ) { return request>( - GPU_SERVICE_INSTANCES_TYPE_API, + GPU_SERVICE_INSTANCES_TYPE_API({ clusterID: params.clusterID }), { method: 'GET', params, @@ -88,11 +114,12 @@ export async function queryGPUServiceInstanceTypes( export async function queryGPUServiceInstanceTypeItems( params: { name: string; + clusterID?: number; }, options?: any ) { return request( - `${GPU_SERVICE_INSTANCES_TYPE_API}/${params.name}`, + `${GPU_SERVICE_INSTANCES_TYPE_API({ clusterID: params.clusterID })}/${params.name}`, { method: 'GET', params, diff --git a/src/pages/gpu-service/instances/components/add-modal.tsx b/src/pages/gpu-service/instances/components/add-modal.tsx index 1f40e274..c8338030 100644 --- a/src/pages/gpu-service/instances/components/add-modal.tsx +++ b/src/pages/gpu-service/instances/components/add-modal.tsx @@ -5,8 +5,8 @@ import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui'; import { Empty, Input, Typography } from 'antd'; import { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; -import useQueryTemplates from '../../templates/services/use-query-templates'; import { ListItem as TemplateItem } from '../../templates/config/types'; +import useQueryTemplates from '../../templates/services/use-query-templates'; import { FormData, InstanceTypeItem, ListItem } from '../config/types'; import GPUServiceInstanceForm from '../forms'; import TemplateSelector from '../forms/template-selector'; @@ -59,7 +59,7 @@ const AddModal: React.FC = ({ onCancel }) => { const form = useRef(null); - const [instanceTypeName, setInstanceTypeName] = useState(); + const [selectedInstanceType, setSelectedInstanceType] = useState(); const [templateId, setTemplateId] = useState(); const [instanceKeyword, setInstanceKeyword] = useState(''); const [templateKeyword, setTemplateKeyword] = useState(''); @@ -84,7 +84,7 @@ const AddModal: React.FC = ({ return instanceTypeList; } return instanceTypeList.filter((item) => { - const name = item.metadata?.name || item.name || ''; + const name = item.metadata?.name || ''; return [ name, item.spec?.memory ?? '', @@ -123,28 +123,23 @@ const AddModal: React.FC = ({ }; const handleInstanceTypeChange = (item: InstanceTypeItem) => { - const name = item.metadata?.name || item.name; - setInstanceTypeName(name); + const name = item.metadata?.name; + setSelectedInstanceType(name); form.current?.setFieldsValue({ - type: name, - spec: { resources: { accelerator: '1' } } + spec: { + type: name, + resources: { accelerator: '1' } + } }); }; const handleTemplateChange = (id: number, item: TemplateItem) => { setTemplateId(id); form.current?.setFieldsValue({ + manufacturer: item.manufacturer, spec: { - image: item.spec?.image, - imagePullPolicy: item.spec?.imagePullPolicy, - command: item.spec?.command || [], - ports: item.spec?.ports || [], - env: item.spec?.env || [], - volumeMount: item.spec?.volumeMount, - resources: { - cpu: item.spec?.resources?.cpu, - ram: item.spec?.resources?.ram - } + ...form.current?.getFieldsValue()?.spec, + ...item.spec } }); }; @@ -188,7 +183,7 @@ const AddModal: React.FC = ({ {filteredInstanceTypes.length > 0 ? ( diff --git a/src/pages/gpu-service/instances/components/instance-type-list.tsx b/src/pages/gpu-service/instances/components/instance-type-list.tsx index 5373a3d6..203d6c03 100644 --- a/src/pages/gpu-service/instances/components/instance-type-list.tsx +++ b/src/pages/gpu-service/instances/components/instance-type-list.tsx @@ -1,7 +1,11 @@ -import { AutoTooltip, TemplateCard } from '@gpustack/core-ui'; +import { AutoTooltip, StatusTag, TemplateCard } from '@gpustack/core-ui'; import { Flex, Tag } from 'antd'; import styled from 'styled-components'; -import { InstanceTypePhaseValueMap } from '../config'; +import { + InstanceTypePhaseLabelMap, + InstanceTypePhaseStatus, + InstanceTypePhaseValueMap +} from '../config'; import { InstanceTypeItem } from '../config/types'; const TypeGrid = styled.div` @@ -44,7 +48,7 @@ interface InstanceTypeListProps { } const isAvailable = (item: InstanceTypeItem) => - item.status?.phase === InstanceTypePhaseValueMap.Available; + item.status?.phase === InstanceTypePhaseValueMap.Active; const InstanceTypeList: React.FC = ({ value, @@ -60,35 +64,60 @@ const InstanceTypeList: React.FC = ({ {dataList.map((item) => { const disabled = !isAvailable(item); - const name = item.metadata?.name || item.name; + const name = item.metadata?.name; return ( handleSelect(item)} > - + {name} - - 库存 {item.status?.accelerator?.remaining ?? '-'} - + + {item.status?.phase && ( + + )} + {item.spec?.acceleratable && ( + + + 库存 {item.status?.accelerator?.remaining ?? '-'} + + + )} + - 显存 {item.spec?.memory ?? '-'} + + {item.spec?.acceleratable && ( + + 显存 {item.spec?.memory ?? '-'} + + )} + 内存 {item.status?.ram?.capacity ?? '-'} vCPU {item.status?.cpu?.capacity ?? '-'} diff --git a/src/pages/gpu-service/instances/config/index.ts b/src/pages/gpu-service/instances/config/index.ts index 4db02bbc..4d3179e0 100644 --- a/src/pages/gpu-service/instances/config/index.ts +++ b/src/pages/gpu-service/instances/config/index.ts @@ -1,23 +1,47 @@ import { StatusMaps } from '@/config'; import { StatusType } from '@/config/types'; import { icons } from '@gpustack/core-ui'; +import _ from 'lodash'; +import { + InstanceTypeItem, + InstanceTypeResource, + InstanceTypeStatus +} from './types'; export const InstanceStatusValueMap = { - Ready: 'ready', - Pending: 'pending', - Error: 'error' + Scheduling: 'Scheduling', + Pending: 'Pending', + Scheduled: 'Scheduled', + Initializing: 'Initializing', + InitializeFailed: 'InitializeFailed', + Initialized: 'Initialized', + Preparing: 'Preparing', + NotReady: 'NotReady', + Ready: 'Ready' }; export const InstanceStatusLabelMap: Record = { - [InstanceStatusValueMap.Ready]: 'Ready', + [InstanceStatusValueMap.Scheduling]: 'Scheduling', [InstanceStatusValueMap.Pending]: 'Pending', - [InstanceStatusValueMap.Error]: 'Error' + [InstanceStatusValueMap.Scheduled]: 'Scheduled', + [InstanceStatusValueMap.Initializing]: 'Initializing', + [InstanceStatusValueMap.InitializeFailed]: 'InitializeFailed', + [InstanceStatusValueMap.Initialized]: 'Initialized', + [InstanceStatusValueMap.Preparing]: 'Preparing', + [InstanceStatusValueMap.NotReady]: 'NotReady', + [InstanceStatusValueMap.Ready]: 'Ready' }; export const status: Record = { - [InstanceStatusValueMap.Ready]: StatusMaps.success, + [InstanceStatusValueMap.Scheduling]: StatusMaps.transitioning, [InstanceStatusValueMap.Pending]: StatusMaps.transitioning, - [InstanceStatusValueMap.Error]: StatusMaps.error + [InstanceStatusValueMap.Scheduled]: StatusMaps.transitioning, + [InstanceStatusValueMap.Initializing]: StatusMaps.transitioning, + [InstanceStatusValueMap.InitializeFailed]: StatusMaps.error, + [InstanceStatusValueMap.Initialized]: StatusMaps.transitioning, + [InstanceStatusValueMap.Preparing]: StatusMaps.transitioning, + [InstanceStatusValueMap.NotReady]: StatusMaps.warning, + [InstanceStatusValueMap.Ready]: StatusMaps.success }; export const rowActionList = [ @@ -37,8 +61,23 @@ export const rowActionList = [ ]; export const InstanceTypePhaseValueMap = { - Available: 'Available', - Unavailable: 'Unavailable' + // Available: 'Available', + // Unavailable: 'Unavailable' + PreParing: 'Preparing', + Inactive: 'Inactive', + Active: 'Active' +}; + +export const InstanceTypePhaseLabelMap: Record = { + [InstanceTypePhaseValueMap.PreParing]: 'Preparing', + [InstanceTypePhaseValueMap.Inactive]: 'Inactive', + [InstanceTypePhaseValueMap.Active]: 'Active' +}; + +export const InstanceTypePhaseStatus: Record = { + [InstanceTypePhaseValueMap.PreParing]: StatusMaps.transitioning, + [InstanceTypePhaseValueMap.Inactive]: StatusMaps.inactive, + [InstanceTypePhaseValueMap.Active]: StatusMaps.success }; export const StorageModeValueMap = { @@ -48,3 +87,55 @@ export const StorageModeValueMap = { // Constant SSH public key resource name used when SSH is enabled export const DEFAULT_SSH_PUBLIC_KEY_NAME = 'default'; + +const KI_TO_GI = 1024 * 1024; + +export const convertKiToGi = (value?: string): string | undefined => { + if (!value) return value; + const match = /^(-?\d+(?:\.\d+)?)Ki$/.exec(value); + if (!match) return value; + return `${_.round(Number(match[1]) / KI_TO_GI, 2)}Gi`; +}; + +export const transformInstanceTypeResource = ( + resource?: InstanceTypeResource +): InstanceTypeResource | undefined => { + if (!resource) return resource; + return { + ...resource, + capacity: convertKiToGi( + resource.capacity + ) as InstanceTypeResource['capacity'], + onceMaxRequest: convertKiToGi( + resource.onceMaxRequest + ) as InstanceTypeResource['onceMaxRequest'], + remaining: convertKiToGi( + resource.remaining + ) as InstanceTypeResource['remaining'] + }; +}; + +export const transformInstanceType = ( + item: InstanceTypeItem +): InstanceTypeItem => { + if (!item?.status) return item; + const status = item.status; + return { + ...item, + status: { + ...status, + accelerator: transformInstanceTypeResource( + status.accelerator + ) as InstanceTypeStatus['accelerator'], + cpu: transformInstanceTypeResource( + status.cpu + ) as InstanceTypeStatus['cpu'], + localStorage: transformInstanceTypeResource( + status.localStorage + ) as InstanceTypeStatus['localStorage'], + ram: transformInstanceTypeResource( + status.ram + ) as InstanceTypeStatus['ram'] + } + }; +}; diff --git a/src/pages/gpu-service/instances/config/types.ts b/src/pages/gpu-service/instances/config/types.ts index cd978086..c9fd4df4 100644 --- a/src/pages/gpu-service/instances/config/types.ts +++ b/src/pages/gpu-service/instances/config/types.ts @@ -50,12 +50,30 @@ export interface FormData { }; } +export interface ManagedField { + manager: string; + operation: string; + apiVersion: string; + time: string; + fieldsType: string; + fieldsV1: Record; +} + +export interface Metadata { + name: string; + namespace?: string; + uid: string; + resourceVersion: string; + creationTimestamp: string; + annotations?: Record; + managedFields?: ManagedField[]; +} + // instance list item -export interface ListItem extends FormData { +export interface ListItem extends Omit { id: number; status?: string; - created_at?: string; - updated_at?: string; + metadata: Metadata; } export interface InstanceItem { @@ -79,10 +97,12 @@ export interface InstanceTypeSpec { sliced?: number; } +type Quality = `${number}Ki` | `${number}Gi`; + export interface InstanceTypeResource { - capacity?: string; - onceMaxRequest?: string; - remaining?: string; + capacity?: Quality; + onceMaxRequest?: Quality; + remaining?: Quality; } export interface InstanceTypeStatus { @@ -102,7 +122,6 @@ export interface InstanceTypeItem { namespace: string; }; id: number; - name: string; spec: InstanceTypeSpec; status: InstanceTypeStatus; } diff --git a/src/pages/gpu-service/instances/forms/index.tsx b/src/pages/gpu-service/instances/forms/index.tsx index 28fab11b..74e9d52c 100644 --- a/src/pages/gpu-service/instances/forms/index.tsx +++ b/src/pages/gpu-service/instances/forms/index.tsx @@ -1,7 +1,6 @@ import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import { - CheckboxField, Input as CInput, CollapsePanel, IconFont, @@ -18,9 +17,10 @@ import { useMemo, useRef } from 'react'; +import useGetSshkey from '../../public-keys/services/use-get-sshkey'; import { DefaultImagePullPolicy } from '../../templates/config'; import TemplateBasicForm from '../../templates/forms/basic'; -import { DEFAULT_SSH_PUBLIC_KEY_NAME, StorageModeValueMap } from '../config'; +import { DEFAULT_SSH_PUBLIC_KEY_NAME } from '../config'; import { FormData, InstancePort, ListItem } from '../config/types'; import Basic from './basic'; import InstanceTypeFormItem from './instance-type'; @@ -29,10 +29,6 @@ import StorageVolume from './storage-volume'; const SSH_PORT = 22; type InstanceFormValues = FormData & { - // storage holders - storage_mode?: string; - storage_name?: string; - local_storage_size_gb?: number; // ssh holder enable_ssh?: boolean; }; @@ -68,7 +64,7 @@ const requiredFields = { }, [TABKeysMap.STORAGE]: { sort: 4, - fields: ['storage_mode', 'storage_name', 'local_storage_size_gb'] + fields: ['volume'] } }; @@ -83,6 +79,7 @@ const GPUServiceInstanceForm: React.FC = forwardRef( } = props; const [form] = Form.useForm(); const scrollTabsRef = useRef(null); + const { detailData, fetchData } = useGetSshkey(); const { getScrollElementScrollableHeight } = useWrapperContext(); const { activeKey, @@ -99,6 +96,14 @@ const GPUServiceInstanceForm: React.FC = forwardRef( ] }); + useEffect(() => { + if (open) { + fetchData({}).then((res) => { + form?.setFieldValue(['spec', 'sshPublicKey', 'name'], res.name); + }); + } + }, [open]); + const segmentOptions = useMemo( () => [ { @@ -153,14 +158,6 @@ const GPUServiceInstanceForm: React.FC = forwardRef( } if (action === PageAction.EDIT && currentData) { - const persistentName = currentData.spec?.volume?.persistent?.name; - const ephemeralCapacity = currentData.spec?.volume?.ephemeral?.capacity; - const isExisting = !!persistentName; - const isTemporary = !!ephemeralCapacity; - const localSizeGb = ephemeralCapacity - ? Number(String(ephemeralCapacity).replace(/Gi$/i, '')) || undefined - : undefined; - form.setFieldsValue({ metadata: { name: currentData.metadata?.name, @@ -171,39 +168,10 @@ const GPUServiceInstanceForm: React.FC = forwardRef( imagePullPolicy: currentData.spec?.imagePullPolicy || DefaultImagePullPolicy }, - storage_mode: isExisting - ? StorageModeValueMap.Existing - : isTemporary - ? StorageModeValueMap.Temporary - : StorageModeValueMap.Existing, - storage_name: persistentName, - local_storage_size_gb: localSizeGb, enable_ssh: !!currentData.spec?.sshPublicKey?.name }); return; } - - form.setFieldsValue({ - metadata: { - namespace - }, - spec: { - description: '', - displayName: '', - image: '', - imagePullPolicy: DefaultImagePullPolicy, - command: [], - ports: [], - env: [], - volumeMount: '', - resources: { - accelerator: '1' - } - } as Partial as FormData['spec'], - storage_mode: StorageModeValueMap.Existing, - local_storage_size_gb: 50, - enable_ssh: false - }); }, [action, currentData, form, open, namespace]); const buildPayload = (values: InstanceFormValues): FormData => { @@ -223,19 +191,6 @@ const GPUServiceInstanceForm: React.FC = forwardRef( } } as FormData['spec']; - // Storage volume mapping - if (values.storage_mode === StorageModeValueMap.Existing) { - spec.volume = { persistent: { name: values.storage_name || '' } }; - } else if (values.storage_mode === StorageModeValueMap.Temporary) { - spec.volume = { - ephemeral: { - capacity: values.local_storage_size_gb - ? `${values.local_storage_size_gb}Gi` - : '' - } - }; - } - // SSH public key — include only when checkbox is checked AND port 22 is in ports const portsList = (values.spec?.ports || []) as InstancePort[]; const has22 = portsList.some((p) => Number(p?.port) === SSH_PORT); @@ -268,7 +223,8 @@ const GPUServiceInstanceForm: React.FC = forwardRef( }, setFieldsValue: (values: Partial) => { form.setFieldsValue(values as any); - } + }, + getFieldsValue: () => form.getFieldsValue() })); return ( @@ -290,6 +246,26 @@ const GPUServiceInstanceForm: React.FC = forwardRef( onFinish={handleFinish} onFinishFailed={handleOnFinishFailed} preserve={false} + initialValues={{ + spec: { + description: '', + displayName: '', + image: '', + imagePullPolicy: DefaultImagePullPolicy, + command: [], + ports: [], + env: [], + volumeMount: '', + resources: { + accelerator: '1' + }, + volume: { persistent: { name: '' } }, + sshPublicKey: { + name: 'gpustack-organization-ssh-public-key' + } + }, + enable_ssh: false + }} > = forwardRef( ]} /> {hasSshPort && ( - - + hidden name={['spec', 'sshPublicKey', 'name']}> + )} - hidden name={['spec', 'sshPublicKey', 'name']}> - - ); diff --git a/src/pages/gpu-service/instances/forms/instance-type.tsx b/src/pages/gpu-service/instances/forms/instance-type.tsx index 5e54d4f7..43c6784d 100644 --- a/src/pages/gpu-service/instances/forms/instance-type.tsx +++ b/src/pages/gpu-service/instances/forms/instance-type.tsx @@ -1,8 +1,4 @@ -import { - AutoTooltip, - Input as CInput, - InputNumber as CInputNumber -} from '@gpustack/core-ui'; +import { AutoTooltip, InputNumber as CInputNumber } from '@gpustack/core-ui'; import { Flex, Form, Tag } from 'antd'; import { useEffect, useMemo } from 'react'; import styled from 'styled-components'; @@ -44,19 +40,15 @@ const SummaryMeta = styled.div` interface InstanceTypePickerProps { value?: string; - onChange?: (value: string) => void; dataList: InstanceTypeItem[]; } const InstanceTypePicker: React.FC = ({ value, - onChange, dataList }) => { const selected = useMemo(() => { - return dataList.find( - (item) => (item.metadata?.name || item.name) === value - ); + return dataList.find((item) => (item.metadata?.name || '') === value); }, [value, dataList]); return ( @@ -66,10 +58,9 @@ const InstanceTypePicker: React.FC = ({ - {(selected?.metadata?.name || selected?.name) ?? - '请选择实例类型'} + {selected?.metadata?.name || '-'} - {selected && ( + {selected && selected.spec?.acceleratable && ( = ({ - 显存 {selected?.spec?.memory ?? '-'} + {selected?.spec?.acceleratable && ( + 显存 {selected?.spec?.memory ?? '-'} + )} 内存 {selected?.status?.ram?.capacity ?? '-'} vCPU {selected?.status?.cpu?.capacity ?? '-'} @@ -95,8 +88,8 @@ const InstanceTypePicker: React.FC = ({ }; const InstanceTypeFormItem = () => { - const form = Form.useFormInstance(); - const typeName = Form.useWatch('type', form) as string | undefined; + const form = Form.useFormInstance(); + const typeName = Form.useWatch(['spec', 'type'], form) as string | undefined; const { detailData, fetchData } = useQueryInstanceTypes(); @@ -107,9 +100,7 @@ const InstanceTypeFormItem = () => { const dataList = detailData?.items || []; const selectedInstanceType = useMemo(() => { - return dataList.find( - (item) => (item.metadata?.name || item.name) === typeName - ); + return dataList.find((item) => (item.metadata?.name || '') === typeName); }, [dataList, typeName]); const maxGpuCount = useMemo(() => { @@ -121,13 +112,10 @@ const InstanceTypeFormItem = () => { useEffect(() => { if (!typeName && dataList.length > 0) { const defaultType = dataList.find( - (item) => item.status?.phase === InstanceTypePhaseValueMap.Available + (item) => item.status?.phase === InstanceTypePhaseValueMap.Active ); if (defaultType) { - form.setFieldValue( - 'type', - defaultType.metadata?.name || defaultType.name - ); + form.setFieldValue(['spec', 'type'], defaultType.metadata?.name || ''); } return; } @@ -147,7 +135,7 @@ const InstanceTypeFormItem = () => { <> { } ]} > - + - { - if (value > maxGpuCount) { - return Promise.reject( - new Error(`当前实例类型最多支持 ${maxGpuCount} 个 GPU`) - ); + {selectedInstanceType?.spec?.acceleratable && ( + { + if (value > maxGpuCount) { + return Promise.reject( + new Error(`当前实例类型最多支持 ${maxGpuCount} 个 GPU`) + ); + } + return Promise.resolve(); } - return Promise.resolve(); } - } - ]} - > - - - + ]} + > + + + )} ); }; -export const useSelectedInstanceType = () => { - const form = Form.useFormInstance(); - const typeName = Form.useWatch('type', form) as string | undefined; - const { detailData } = useQueryInstanceTypes(); - const dataList = detailData?.items || []; - - return useMemo(() => { - return dataList.find( - (item) => (item.metadata?.name || item.name) === typeName - ); - }, [dataList, typeName]); -}; - export default InstanceTypeFormItem; diff --git a/src/pages/gpu-service/instances/forms/storage-volume.tsx b/src/pages/gpu-service/instances/forms/storage-volume.tsx index 9e1ac252..4c5e9398 100644 --- a/src/pages/gpu-service/instances/forms/storage-volume.tsx +++ b/src/pages/gpu-service/instances/forms/storage-volume.tsx @@ -1,44 +1,48 @@ import { useModel } from '@@/plugin-model'; import { InputNumber as CInputNumber, Select } from '@gpustack/core-ui'; -import { Button, Flex, Form, message, Radio } from 'antd'; +import { Button, Flex, Form, Radio } from 'antd'; import { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { FormData as StorageFormData } from '../../storage/config/types'; import useCreateStorage from '../../storage/services/use-create-storage'; import useQueryStorage from '../../storage/services/use-query-storage'; import { StorageModeValueMap } from '../config'; -import { FormData } from '../config/types'; +import { FormData, InstanceVolume } from '../config/types'; import StorageOverlay from './storage-overlay'; const FieldBlock = styled.div` margin-bottom: 24px; `; -type StorageHolderFields = { - storage_mode?: string; - storage_name?: string; - local_storage_size_gb?: number; -}; +const DEFAULT_TEMP_CAPACITY_GB = 50; const StorageVolume = () => { const { initialState } = useModel('@@initialState'); - const namespace = initialState?.currentUser?.org_name || 'default'; + const { fetchData: createStorage } = useCreateStorage(); + const { detailData: storageData, fetchData: fetchStorage } = + useQueryStorage(); + const [overlayOpen, setOverlayOpen] = useState(false); + const [storageMode, setStorageMode] = useState( + StorageModeValueMap.Existing + ); - const form = Form.useFormInstance(); - const storageMode = Form.useWatch('storage_mode', form) as string | undefined; - const storageName = Form.useWatch('storage_name', form) as string | undefined; - const localSize = Form.useWatch('local_storage_size_gb', form) as - | number + const form = Form.useFormInstance(); + const volume = Form.useWatch(['spec', 'volume'], form) as + | InstanceVolume | undefined; - const [overlayOpen, setOverlayOpen] = useState(false); - - const { fetchData: createStorage } = useCreateStorage(); - const { detailData: storageData, fetchData: fetchStorage } = useQueryStorage(); + const namespace = initialState?.currentUser?.org_name || 'default'; useEffect(() => { fetchStorage({}); - }, [fetchStorage]); + }, []); + + // const storageMode = useMemo(() => { + // if (volume?.ephemeral && !volume?.persistent) { + // return StorageModeValueMap.Temporary; + // } + // return StorageModeValueMap.Existing; + // }, [volume?.ephemeral, volume?.persistent]); const storageOptions = useMemo( () => @@ -49,29 +53,28 @@ const StorageVolume = () => { [storageData] ); - useEffect(() => { - if (storageMode === StorageModeValueMap.Existing) { + const handleModeChange = (mode: string) => { + console.log('selected storage mode', mode); + if (mode === StorageModeValueMap.Existing) { + form.setFieldValue(['spec', 'volume'], { persistent: { name: '' } }); + } else { form.setFieldValue(['spec', 'volume'], { - persistent: { name: storageName || '' } - }); - } else if (storageMode === StorageModeValueMap.Temporary) { - form.setFieldValue(['spec', 'volume'], { - ephemeral: { - capacity: localSize ? `${localSize}Gi` : '' - } + ephemeral: { capacity: `${DEFAULT_TEMP_CAPACITY_GB}Gi` } }); } - }, [form, storageMode, storageName, localSize]); + setStorageMode(mode); + }; const handleCreateStorage = async (values: StorageFormData) => { try { await createStorage({ data: values }); await fetchStorage({}); - form.setFieldValue('storage_name', values.metadata.name); + form.setFieldValue(['spec', 'volume'], { + persistent: { name: values.metadata.name } + }); setOverlayOpen(false); - message.success('操作成功'); } catch (error) { - message.error('操作失败'); + // ignore } }; @@ -84,23 +87,15 @@ const StorageVolume = () => { marginBottom: 6 }} > - handleModeChange(e.target.value)} + options={[ + { label: '持久存储', value: StorageModeValueMap.Existing }, + { label: '临时存储', value: StorageModeValueMap.Temporary } ]} - > - - + />