diff --git a/src/pages/gpu-service/templates/apis/index.ts b/src/pages/gpu-service/templates/apis/index.ts index baf6e12f..a206d357 100644 --- a/src/pages/gpu-service/templates/apis/index.ts +++ b/src/pages/gpu-service/templates/apis/index.ts @@ -1,5 +1,4 @@ import { request } from '@umijs/max'; -import { mockTemplateData } from '../config/mock-data'; import { FormData, ListItem } from '../config/types'; export const GPU_SERVICE_TEMPLATES_API = '/gpu-service-templates'; @@ -8,53 +7,28 @@ export async function queryGPUServiceTemplates( params: Global.SearchParams, options?: any ) { - // return request>(GPU_SERVICE_TEMPLATES_API, { - // method: 'GET', - // params, - // cancelToken: options?.token - // }); - const page = params.page || 1; - const perPage = params.perPage || 24; - const search = params.search?.toLowerCase(); - const vendor = params.vendor; - const filteredData = mockTemplateData.filter((item) => { - const matchSearch = search - ? item.name.toLowerCase().includes(search) - : true; - const matchVendor = vendor ? item.vendor === vendor : true; - return matchSearch && matchVendor; + return request>(GPU_SERVICE_TEMPLATES_API, { + method: 'GET', + params, + cancelToken: options?.token }); - const start = (page - 1) * perPage; - const items = filteredData.slice(start, start + perPage); - - return { - items, - pagination: { - total: filteredData.length, - totalPage: Math.ceil(filteredData.length / perPage), - page, - perPage - } - } as Global.PageResponse; } export async function createGPUServiceTemplate(params: { data: FormData }) { - // return request(GPU_SERVICE_TEMPLATES_API, { - // method: 'POST', - // data: params.data - // }); - return true; + return request(GPU_SERVICE_TEMPLATES_API, { + method: 'POST', + data: params.data + }); } export async function updateGPUServiceTemplate(params: { id: number; data: FormData; }) { - // return request(`${GPU_SERVICE_TEMPLATES_API}/${params.id}`, { - // method: 'PUT', - // data: params.data - // }); - return true; + return request(`${GPU_SERVICE_TEMPLATES_API}/${params.id}`, { + method: 'PUT', + data: params.data + }); } export async function deleteGPUServiceTemplate(id: number) { diff --git a/src/pages/gpu-service/templates/components/template-card.tsx b/src/pages/gpu-service/templates/components/template-card.tsx index 2135ffaa..ad4a5f15 100644 --- a/src/pages/gpu-service/templates/components/template-card.tsx +++ b/src/pages/gpu-service/templates/components/template-card.tsx @@ -1,23 +1,12 @@ -import ascendLogo from '@/assets/logo/ascend.png'; -import CambriconPNG from '@/assets/logo/cambricon.png'; -import hygonPNG from '@/assets/logo/hygon.png'; -import iluvatarLogo from '@/assets/logo/Iluvatar.png'; -import metaxLogo from '@/assets/logo/metax.png'; -import mooreLogo from '@/assets/logo/moore-logo.png'; -import nvidiaLogo from '@/assets/logo/nvidia.png'; -import theadLogoEN from '@/assets/logo/t-head-en.png'; -import theadLogoZH from '@/assets/logo/t-head-zh.png'; -import { GPUDriverMap, GPUsConfigs } from '@/pages/resources/config/gpu-driver'; import { AutoTooltip, DropdownActions, IconFont, TemplateCard } from '@gpustack/core-ui'; -import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import styled from 'styled-components'; -import { templateActions, TemplateStatusValueMap } from '../config'; +import { templateActions } from '../config'; import { ListItem } from '../config/types'; const StyledCard = styled(TemplateCard)` @@ -37,32 +26,15 @@ const Header = styled.div` width: 100%; `; -const VendorLogo = styled.span` - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 48px; - max-width: 88px; - height: 18px; - .logo-img { - max-width: 100%; - object-fit: contain; - } - .amd-logo { - font-size: 28px; - line-height: 1; - color: var(--ant-color-text); - } -`; - -const CardName = styled.div` +const CardTitle = styled.div` font-weight: 500; font-size: 14px; display: flex; align-items: center; color: var(--ant-color-text); - margin-bottom: 8px; gap: 8px; + flex: 1; + min-width: 0; `; const Content = styled.div` @@ -94,55 +66,7 @@ interface TemplateCardProps { onSelect?: (item: { action: string; data: ListItem }) => void; } -const vendorLogoMap: Record = { - [GPUDriverMap.NVIDIA]: { src: nvidiaLogo, height: 16 }, - [GPUDriverMap.ASCEND]: { src: ascendLogo, height: 18 }, - [GPUDriverMap.HYGON]: { src: hygonPNG, height: 16 }, - [GPUDriverMap.MOORE_THREADS]: { src: mooreLogo, height: 18 }, - [GPUDriverMap.ILUVATAR]: { src: iluvatarLogo, height: 18 }, - [GPUDriverMap.CAMBRICON]: { src: CambriconPNG, height: 18 }, - [GPUDriverMap.METAX]: { src: metaxLogo, height: 18 } -}; - const TemplateCardItem: React.FC = ({ data, onSelect }) => { - const intl = useIntl(); - const vendorLabel = data.vendor - ? GPUsConfigs[data.vendor]?.label || data.vendor - : '-'; - - const renderVendor = () => { - if (data.vendor === GPUDriverMap.AMD) { - return ( - - - - ); - } - - const logo = - data.vendor === GPUDriverMap.THEAD - ? { - src: intl.locale === 'zh-CN' ? theadLogoZH : theadLogoEN, - height: 18 - } - : vendorLogoMap[data.vendor || '']; - - if (!logo) { - return vendorLabel; - } - - return ( - - {vendorLabel} - - ); - }; - const handleOnSelect = (item: any) => { onSelect?.({ action: item.key, data }); }; @@ -170,7 +94,14 @@ const TemplateCardItem: React.FC = ({ data, onSelect }) => { ); }; - const status = data.status || TemplateStatusValueMap.Enabled; + const ports = data.ports || []; + const portText = ports.length + ? ports.map((p) => `${p.protocol?.toUpperCase()}:${p.port}`).join(', ') + : '-'; + + const resources = [data.resources?.cpu, data.resources?.ram] + .filter(Boolean) + .join(' / '); return ( = ({ data, onSelect }) => { ghost header={
- {renderVendor()} + + + {data.name || '-'} + + {renderActions()}
} > - - - {data.name} - - 镜像: @@ -200,6 +130,30 @@ const TemplateCardItem: React.FC = ({ data, onSelect }) => { {data.image || '-'} + + + 挂载: + + + {data.volumeMount || '-'} + + + + + 资源: + + + {resources || '-'} + + + + + 端口: + + + {portText} + +
); diff --git a/src/pages/gpu-service/templates/config/mock-data.ts b/src/pages/gpu-service/templates/config/mock-data.ts index 05c61341..2c538419 100644 --- a/src/pages/gpu-service/templates/config/mock-data.ts +++ b/src/pages/gpu-service/templates/config/mock-data.ts @@ -5,26 +5,17 @@ export const mockTemplateData: ListItem[] = [ id: 1, name: 'Ubuntu CUDA Dev', image: 'nvidia/cuda:12.4.1-devel-ubuntu22.04', - vendor: 'cuda', - run_command: '/bin/bash', - boot_disk_size_gb: 30, - volume_size_gb: 100, - volume_mount_path: '/workspace', - ports: [ - { - protocol: 'tcp', - value: 22 - }, - { - protocol: 'udp', - value: 8888 - } - ], - env: { - NVIDIA_VISIBLE_DEVICES: 'all' + command: ['/bin/bash'], + volumeMount: '/workspace', + resources: { + cpu: '4', + ram: '16Gi' }, - gpu_count: 1, - replicas: 1, + ports: [ + { protocol: 'tcp', port: 22 }, + { protocol: 'udp', port: 8888 } + ], + env: [{ name: 'NVIDIA_VISIBLE_DEVICES', value: 'all' }], status: 'enabled', created_at: '2026-04-01T10:00:00Z', updated_at: '2026-04-10T10:00:00Z' @@ -33,22 +24,14 @@ export const mockTemplateData: ListItem[] = [ id: 2, name: 'PyTorch Training', image: 'pytorch/pytorch:2.5.1-cuda12.4-cudnn9-devel', - vendor: 'cuda', - run_command: 'python train.py', - boot_disk_size_gb: 50, - volume_size_gb: 200, - volume_mount_path: '/data', - ports: [ - { - protocol: 'tcp', - value: 6006 - } - ], - env: { - PYTHONUNBUFFERED: '1' + command: ['python', 'train.py'], + volumeMount: '/data', + resources: { + cpu: '8', + ram: '32Gi' }, - gpu_count: 2, - replicas: 1, + ports: [{ protocol: 'tcp', port: 6006 }], + env: [{ name: 'PYTHONUNBUFFERED', value: '1' }], status: 'enabled', created_at: '2026-04-02T10:00:00Z', updated_at: '2026-04-11T10:00:00Z' @@ -57,22 +40,14 @@ export const mockTemplateData: ListItem[] = [ id: 3, name: 'ROCm Notebook', image: 'rocm/pytorch:latest', - vendor: 'rocm', - run_command: 'jupyter lab --ip=0.0.0.0 --allow-root', - boot_disk_size_gb: 40, - volume_size_gb: 120, - volume_mount_path: '/notebooks', - ports: [ - { - protocol: 'http', - value: 8888 - } - ], - env: { - HSA_OVERRIDE_GFX_VERSION: '10.3.0' + command: ['jupyter', 'lab', '--ip=0.0.0.0', '--allow-root'], + volumeMount: '/notebooks', + resources: { + cpu: '4', + ram: '16Gi' }, - gpu_count: 1, - replicas: 1, + ports: [{ protocol: 'tcp', port: 8888 }], + env: [{ name: 'HSA_OVERRIDE_GFX_VERSION', value: '10.3.0' }], status: 'enabled', created_at: '2026-04-03T10:00:00Z', updated_at: '2026-04-12T10:00:00Z' @@ -81,22 +56,14 @@ export const mockTemplateData: ListItem[] = [ id: 4, name: 'Ascend MindIE', image: 'ascend/mindie:latest', - vendor: 'cann', - run_command: '/usr/local/Ascend/mindie/latest/bin/mindieservice_daemon', - boot_disk_size_gb: 60, - volume_size_gb: 160, - volume_mount_path: '/models', - ports: [ - { - protocol: 'http', - value: 1025 - } - ], - env: { - ASCEND_VISIBLE_DEVICES: '0' + command: ['/usr/local/Ascend/mindie/latest/bin/mindieservice_daemon'], + volumeMount: '/models', + resources: { + cpu: '4', + ram: '16Gi' }, - gpu_count: 1, - replicas: 1, + ports: [{ protocol: 'tcp', port: 1025 }], + env: [{ name: 'ASCEND_VISIBLE_DEVICES', value: '0' }], status: 'enabled', created_at: '2026-04-04T10:00:00Z', updated_at: '2026-04-13T10:00:00Z' @@ -105,20 +72,14 @@ export const mockTemplateData: ListItem[] = [ id: 5, name: 'CPU Utility', image: 'ubuntu:22.04', - vendor: 'cuda', - run_command: 'sleep infinity', - boot_disk_size_gb: 20, - volume_size_gb: 50, - volume_mount_path: '/mnt/data', - ports: [ - { - protocol: 'tcp', - value: 22 - } - ], - env: {}, - gpu_count: 0, - replicas: 1, + command: ['sleep', 'infinity'], + volumeMount: '/mnt/data', + resources: { + cpu: '2', + ram: '4Gi' + }, + ports: [{ protocol: 'tcp', port: 22 }], + env: [], status: 'disabled', created_at: '2026-04-05T10:00:00Z', updated_at: '2026-04-14T10:00:00Z' @@ -127,26 +88,17 @@ export const mockTemplateData: ListItem[] = [ id: 6, name: 'Inference Server', image: 'vllm/vllm-openai:latest', - vendor: 'cuda', - run_command: 'python -m vllm.entrypoints.openai.api_server', - boot_disk_size_gb: 80, - volume_size_gb: 300, - volume_mount_path: '/models', - ports: [ - { - protocol: 'udp', - value: 8000 - }, - { - protocol: 'tcp', - value: 8080 - } - ], - env: { - VLLM_WORKER_MULTIPROC_METHOD: 'spawn' + command: ['python', '-m', 'vllm.entrypoints.openai.api_server'], + volumeMount: '/models', + resources: { + cpu: '16', + ram: '64Gi' }, - gpu_count: 4, - replicas: 2, + ports: [ + { protocol: 'udp', port: 8000 }, + { protocol: 'tcp', port: 8080 } + ], + env: [{ name: 'VLLM_WORKER_MULTIPROC_METHOD', value: 'spawn' }], status: 'enabled', created_at: '2026-04-06T10:00:00Z', updated_at: '2026-04-15T10:00:00Z' diff --git a/src/pages/gpu-service/templates/config/types.ts b/src/pages/gpu-service/templates/config/types.ts index 0fcafc3f..bf27195c 100644 --- a/src/pages/gpu-service/templates/config/types.ts +++ b/src/pages/gpu-service/templates/config/types.ts @@ -9,13 +9,13 @@ export interface EnvItem { } export interface FormData { + name: string; image: string; - imagePullPolicy: string; - imagePullSecret: { - name: string; - }; + // imagePullPolicy: string; + // imagePullSecret: { + // name: string; + // }; command: string[]; - privileged: boolean; ports: PortItem[]; env: EnvItem[]; volumeMount: string; diff --git a/src/pages/gpu-service/templates/forms/basic.tsx b/src/pages/gpu-service/templates/forms/basic.tsx index 247ca091..d65a034f 100644 --- a/src/pages/gpu-service/templates/forms/basic.tsx +++ b/src/pages/gpu-service/templates/forms/basic.tsx @@ -1,53 +1,40 @@ -import { GPUsConfigs } from '@/pages/resources/config/gpu-driver'; -import { - Input as CInput, - InputNumber as CInputNumber, - LabelSelector, - Select as SealSelect, - Textarea -} from '@gpustack/core-ui'; +import { Input as CInput, Textarea } from '@gpustack/core-ui'; import { Form } from 'antd'; import { FormData } from '../config/types'; +import Env from './env'; import Ports from './ports'; interface BasicProps { page?: 'template' | 'instance'; } -const Basic: React.FC = ({ page = 'template' }) => { +const Basic: React.FC = () => { const form = Form.useFormInstance(); - const gpuVendorOptions = Object.values(GPUsConfigs); + + const handleCommandChange = (e: React.ChangeEvent) => { + const value = e.target.value; + const list = value + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0); + form.setFieldValue('command', list); + }; + + const command = Form.useWatch('command', form); + const commandText = Array.isArray(command) ? command.join('\n') : ''; return ( <> - {page === 'template' && ( - - name="name" - rules={[ - { - required: true, - message: '请输入模板名称' - } - ]} - > - - - )} - name="vendor" - data-field="vendor" + name="name" rules={[ { required: true, - message: '请选择适用设备厂商' + message: '请输入模板名称' } ]} > - + name="image" @@ -60,22 +47,34 @@ const Basic: React.FC = ({ page = 'template' }) => { > - name="run_command"> +