From 7d0cfeff77b6d6a3826679e1c8cd396ad28bceb0 Mon Sep 17 00:00:00 2001 From: jialin Date: Fri, 8 May 2026 15:09:01 +0800 Subject: [PATCH] fix: storage api alignment --- config/routes.ts | 16 ++- src/config/global.d.ts | 38 +++++++ src/config/index.ts | 13 +++ src/layouts/index.tsx | 2 +- src/locales/en-US/menu.ts | 3 +- src/locales/ja-JP/menu.ts | 3 +- src/locales/ru-RU/menu.ts | 3 +- src/locales/tr-TR/menu.ts | 3 +- src/locales/zh-CN/menu.ts | 3 +- src/pages/gpu-service/constants.ts | 8 ++ src/pages/gpu-service/instances/apis/index.ts | 44 +++++++- .../gpu-service/instances/config/types.ts | 101 +++++++++++++++--- .../gpu-service/instances/forms/index.tsx | 16 +++ .../instances/forms/storage-volume.tsx | 60 ++++------- .../gpu-service/public-keys/apis/index.ts | 55 ++++++++++ src/pages/gpu-service/public-keys/index.tsx | 63 +++++++++++ .../public-keys/services/use-create-sshkey.ts | 48 +++++++++ .../public-keys/services/use-get-sshkey.ts | 31 ++++++ .../public-keys/services/use-update-sshkey.ts | 50 +++++++++ src/pages/gpu-service/public-keys/types.ts | 9 ++ src/pages/gpu-service/storage/apis/index.ts | 92 ++++++++-------- src/pages/gpu-service/storage/config/index.ts | 84 +++++++++------ .../gpu-service/storage/config/mock-data.ts | 80 +++++++------- src/pages/gpu-service/storage/config/types.ts | 25 +++-- src/pages/gpu-service/storage/forms/basic.tsx | 54 ++++------ src/pages/gpu-service/storage/forms/index.tsx | 41 ++++--- .../storage/hooks/use-storage-columns.tsx | 53 +++------ src/pages/gpu-service/storage/index.tsx | 57 ++++++++-- .../storage/services/use-create-storage.ts | 50 +++++++++ .../storage/services/use-update-storage.ts | 52 +++++++++ .../gpu-service/templates/config/types.ts | 34 +++--- .../gpu-service/templates/forms/basic.tsx | 2 +- .../gpu-service/templates/forms/ports.tsx | 1 - src/pages/profile/index.tsx | 11 -- 34 files changed, 881 insertions(+), 324 deletions(-) create mode 100644 src/pages/gpu-service/constants.ts create mode 100644 src/pages/gpu-service/public-keys/apis/index.ts create mode 100644 src/pages/gpu-service/public-keys/index.tsx create mode 100644 src/pages/gpu-service/public-keys/services/use-create-sshkey.ts create mode 100644 src/pages/gpu-service/public-keys/services/use-get-sshkey.ts create mode 100644 src/pages/gpu-service/public-keys/services/use-update-sshkey.ts create mode 100644 src/pages/gpu-service/public-keys/types.ts create mode 100644 src/pages/gpu-service/storage/services/use-create-storage.ts create mode 100644 src/pages/gpu-service/storage/services/use-update-storage.ts diff --git a/config/routes.ts b/config/routes.ts index d0760cce..1956cb63 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -192,9 +192,9 @@ const baseRoutes = [ name: 'instances', path: '/gpu-service/instances', key: 'gpuServiceList', - icon: 'icon-instances-outlined', - selectedIcon: 'icon-instances-filled', - defaultIcon: 'icon-instances-outlined', + icon: 'icon-cloud-outlined', + selectedIcon: 'icon-cloud-filled', + defaultIcon: 'icon-cloud-outlined', access: 'canSeeAdmin', component: './gpu-service/instances' }, @@ -217,6 +217,16 @@ const baseRoutes = [ defaultIcon: 'icon-storage-outlined', access: 'canSeeAdmin', component: './gpu-service/storage' + }, + { + name: 'publicKeys', + path: '/gpu-service/public-keys', + key: 'gpuServicePublicKeys', + icon: 'icon-ssh-outlined', + selectedIcon: 'icon-ssh-filled', + defaultIcon: 'icon-ssh-outlined', + access: 'canSeeAdmin', + component: './gpu-service/public-keys' } ] }, diff --git a/src/config/global.d.ts b/src/config/global.d.ts index b1d17d96..162e8b63 100644 --- a/src/config/global.d.ts +++ b/src/config/global.d.ts @@ -16,6 +16,43 @@ declare namespace Global { }; } + interface K8sPageResponse { + apiVersion: string; + items: T[]; + kind: string; + metadata: { + continue?: string; + resourceVersion: string; + selfLink?: string; + [key: string]: any; + }; + } + + interface K8sCommonData { + kind: string; + apiVersion: string; + metadata: { + name: string; + namespace: string; + [key: string]: any; + }; + [key: string]: any; + } + + interface K8sSearchParams { + allowWatchBookmarks?: boolean; + continue?: string; + fieldSelector?: string; + labelSelector?: string; + limit?: number; + resourceVersion?: string; + resourceVersionMatch?: string; + sendInitialEvents?: boolean; + timeoutSeconds?: number; + watch?: boolean; + [key: string]: any; + } + interface UserInfo { username: string; is_admin: boolean; @@ -24,6 +61,7 @@ declare namespace Global { id: number; source: string; avatar_url: string; + org_name?: string; } type EmptyObject = Record; diff --git a/src/config/index.ts b/src/config/index.ts index 34e24a9a..b6e7d15b 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -72,3 +72,16 @@ export const lengthReg = /^.{6,64}$/; */ export const modelNameReg = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,61}[A-Za-z0-9])?$/g; + +/** + * Label name rules: + * 1. no more than 63 characters + * 2. contain only lowercase alphanumeric characters and '-' + * 3. start and end with an alphanumeric character + * 4. must not contain consecutive '-' characters + * 5. must not start with a digit + * 6. must not end with a '-' + */ + +export const validateLabelNameRegxFor63 = + /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/; diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 1c9b9ccf..3e5cd1d4 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -356,7 +356,7 @@ export default (props: any) => { config={{ apiBaseUrl: GPUSTACK_API_BASE_URL, theme: userSettings.theme, - iconUrl: '//at.alicdn.com/t/c/font_4613488_tzcsatubq4f.js', + iconUrl: '//at.alicdn.com/t/c/font_4613488_ieeghfm8o3o.js', isDarkTheme: userSettings.isDarkTheme, defaultColorPrimary: COLOR_PRIMARY }} diff --git a/src/locales/en-US/menu.ts b/src/locales/en-US/menu.ts index 1cafc921..bd57b9a4 100644 --- a/src/locales/en-US/menu.ts +++ b/src/locales/en-US/menu.ts @@ -44,5 +44,6 @@ export default { 'menu.gpuService': 'GPU Service', 'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.templates': 'Instance Templates', - 'menu.gpuService.storage': 'Storage' + 'menu.gpuService.storage': 'Storage', + 'menu.gpuService.publicKeys': 'SSH Public Keys' }; diff --git a/src/locales/ja-JP/menu.ts b/src/locales/ja-JP/menu.ts index 01ccf72a..2c7ce74c 100644 --- a/src/locales/ja-JP/menu.ts +++ b/src/locales/ja-JP/menu.ts @@ -44,7 +44,8 @@ export default { 'menu.gpuService': 'GPU Service', 'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.templates': 'Instance Templates', - 'menu.gpuService.storage': 'Storage' + 'menu.gpuService.storage': 'Storage', + 'menu.gpuService.publicKeys': 'SSH Public Keys' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== diff --git a/src/locales/ru-RU/menu.ts b/src/locales/ru-RU/menu.ts index e6ede7b0..53b4629a 100644 --- a/src/locales/ru-RU/menu.ts +++ b/src/locales/ru-RU/menu.ts @@ -44,7 +44,8 @@ export default { 'menu.gpuService': 'GPU Service', 'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.templates': 'Instance Templates', - 'menu.gpuService.storage': 'Storage' + 'menu.gpuService.storage': 'Storage', + 'menu.gpuService.publicKeys': 'SSH Public Keys' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== diff --git a/src/locales/tr-TR/menu.ts b/src/locales/tr-TR/menu.ts index 10d30c09..90136081 100644 --- a/src/locales/tr-TR/menu.ts +++ b/src/locales/tr-TR/menu.ts @@ -44,7 +44,8 @@ export default { 'menu.gpuService': 'GPU Service', 'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.templates': 'Instance Templates', - 'menu.gpuService.storage': 'Storage' + 'menu.gpuService.storage': 'Storage', + 'menu.gpuService.publicKeys': 'SSH Public Keys' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts index 8b93ccbd..5b01150a 100644 --- a/src/locales/zh-CN/menu.ts +++ b/src/locales/zh-CN/menu.ts @@ -44,5 +44,6 @@ export default { 'menu.gpuService': 'GPU 服务', 'menu.gpuService.instances': 'GPU 实例', 'menu.gpuService.templates': '实例模板', - 'menu.gpuService.storage': '存储' + 'menu.gpuService.storage': '存储', + 'menu.gpuService.publicKeys': 'SSH 公钥' }; diff --git a/src/pages/gpu-service/constants.ts b/src/pages/gpu-service/constants.ts new file mode 100644 index 00000000..1267cd22 --- /dev/null +++ b/src/pages/gpu-service/constants.ts @@ -0,0 +1,8 @@ +export const apiVersion = 'worker.gpustack.ai/v1'; + +export const KindMapping = { + sshPublicKey: 'InstanceSSHPublicKey', + instance: 'Instance', + instancePersistentVolume: 'InstancePersistentVolume', + instanceTemplate: 'InstanceTemplate' +}; diff --git a/src/pages/gpu-service/instances/apis/index.ts b/src/pages/gpu-service/instances/apis/index.ts index cc7b763f..249e6ff4 100644 --- a/src/pages/gpu-service/instances/apis/index.ts +++ b/src/pages/gpu-service/instances/apis/index.ts @@ -1,11 +1,15 @@ import { request } from '@umijs/max'; import { mockInstanceData } from '../config/mock-data'; -import { FormData, ListItem } from '../config/types'; +import { FormData, InstanceTypeItem, ListItem } from '../config/types'; -export const GPU_SERVICE_INSTANCES_API = '/gpu-service-instances'; +export const GPU_SERVICE_INSTANCES_API = (namespace: string) => + `/proxy/apis/worker.gpustack.ai/v1/namespaces/${namespace}/instances`; + +export const GPU_SERVICE_INSTANCES_TYPE_API = + '/proxy/apis/worker.gpustack.ai/v1/instancetypes'; export async function queryGPUServiceInstances( - params: Global.SearchParams, + params: Global.K8sSearchParams, options?: any ) { // return request>(GPU_SERVICE_INSTANCES_API, { @@ -35,7 +39,7 @@ export async function queryGPUServiceInstances( page, perPage } - } as Global.PageResponse; + } as Global.K8sPageResponse; } export async function createGPUServiceInstance(params: { data: FormData }) { @@ -62,3 +66,35 @@ export async function deleteGPUServiceInstance(id: number) { method: 'DELETE' }); } + +// =========== Instance Types =========== + +export async function queryGPUServiceInstanceTypes( + params: Global.K8sSearchParams, + options?: any +) { + return request>( + GPU_SERVICE_INSTANCES_TYPE_API, + { + method: 'GET', + params, + cancelToken: options?.token + } + ); +} + +export async function queryGPUServiceInstanceTypeItems( + params: { + name: string; + }, + options?: any +) { + return request( + `${GPU_SERVICE_INSTANCES_TYPE_API}/${params.name}`, + { + method: 'GET', + params, + cancelToken: options?.token + } + ); +} diff --git a/src/pages/gpu-service/instances/config/types.ts b/src/pages/gpu-service/instances/config/types.ts index b3b51a33..f139b01d 100644 --- a/src/pages/gpu-service/instances/config/types.ts +++ b/src/pages/gpu-service/instances/config/types.ts @@ -1,23 +1,57 @@ -export interface FormData { +export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never'; + +export interface InstanceEnvVar { name: string; - description?: string; - image?: string; - instance_type?: string; - instance_type_id?: number; - template_id?: number; - gpu_count?: number; - replicas?: number; - storage_mode?: string; - storage_id?: number; - local_storage_size_gb?: number; - cluster_id?: number; - mount_path?: string; + value?: string; } +export interface InstancePort { + port: number; + protocol?: string; +} + +export interface InstanceResources { + cpu?: string; + ram?: string; + localStorage?: string; + accelerator?: string; +} + +export interface InstanceVolume { + ephemeral?: { + capacity?: string; + }; + persistent?: { + name: string; + }; +} + +// instance form data +export interface FormData { + metadata: { + name: string; + namespace: string; + }; + spec: { + image: string; + displayName: string; + command: string[]; + ports: InstancePort[]; + env: InstanceEnvVar[]; + volumeMount: string; + resources: InstanceResources; + description: string; + volume: InstanceVolume; + sshPublicKey: { + name: string; + }; + }; +} + +// instance list item export interface ListItem extends FormData { id: number; status?: string; - endpoint?: string; created_at?: string; updated_at?: string; } @@ -31,3 +65,42 @@ export interface InstanceItem { gpu_count: number; status: string; // available, unavailable } + +export interface InstanceTypeSpec { + acceleratable: boolean; + computeCapability?: string; + family?: string; + group: string; + manufacturer?: string; + memory?: string; + product?: string; + sliced?: number; +} + +export interface InstanceTypeResource { + capacity?: string; + onceMaxRequest?: string; + remaining?: string; +} + +export interface InstanceTypeStatus { + accelerator: InstanceTypeResource; + cpu: InstanceTypeResource; + localStorage: InstanceTypeResource; + phase: string; + phaseMessage?: string; + ram: InstanceTypeResource; +} + +export interface InstanceTypeItem { + apiVersion: string; + kind: string; + metadata: { + name: string; + 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 dae07b7e..f4722b60 100644 --- a/src/pages/gpu-service/instances/forms/index.tsx +++ b/src/pages/gpu-service/instances/forms/index.tsx @@ -1,6 +1,8 @@ import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import { + CheckboxField, + Input as CInput, CollapsePanel, IconFont, ScrollSpyTabs, @@ -206,6 +208,20 @@ const GPUServiceInstanceForm: React.FC = forwardRef( } ]} /> + + name="enable_ssh" + valuePropName="checked" + style={{ marginBottom: 8 }} + > + + + + data-field="name" + hidden + name={['spec', 'sshPublicKey', 'name']} + > + + ); diff --git a/src/pages/gpu-service/instances/forms/storage-volume.tsx b/src/pages/gpu-service/instances/forms/storage-volume.tsx index 245cc1ae..e9ee94fa 100644 --- a/src/pages/gpu-service/instances/forms/storage-volume.tsx +++ b/src/pages/gpu-service/instances/forms/storage-volume.tsx @@ -1,7 +1,6 @@ -import { InputNumber as CInputNumber, Input, Select } from '@gpustack/core-ui'; +import { InputNumber as CInputNumber, Select } from '@gpustack/core-ui'; import { Button, Flex, Form, Radio } from 'antd'; import styled from 'styled-components'; -import { StorageStatusValueMap } from '../../storage/config'; import { mockStorageData } from '../../storage/config/mock-data'; import { StorageModeValueMap } from '../config'; import { FormData } from '../config/types'; @@ -57,29 +56,24 @@ const StorageVolume = () => { {storageMode === StorageModeValueMap.Existing && ( -
-
- - name="storage_id" - rules={[ - { - required: true, - message: '请选择预存储卷' - } - ]} - > - ({ + label: `${item.metadata?.name} / ${item.spec?.capacity ?? '-'}`, + value: item.id + }))} + /> + )} {storageMode === StorageModeValueMap.Temporary && ( @@ -95,22 +89,6 @@ const StorageVolume = () => { )} - {/* 挂载路径 */} - - name="mount_path" - rules={[ - { - required: true, - message: '请输入存储挂载路径' - } - ]} - > - - ); }; diff --git a/src/pages/gpu-service/public-keys/apis/index.ts b/src/pages/gpu-service/public-keys/apis/index.ts new file mode 100644 index 00000000..3313994f --- /dev/null +++ b/src/pages/gpu-service/public-keys/apis/index.ts @@ -0,0 +1,55 @@ +import { request } from '@umijs/max'; +import { FormData, ListItem } from '../types'; + +export const GPU_SERVICE_PUBLIC_KEY_API = (namespace: string) => + `/proxy/apis/worker.gpustack.ai/v1/namespaces/${namespace}/instancesshpublickeys`; + +export async function queryGPUServicePublicKeys( + params: Global.K8sSearchParams, + options?: any +) { + return request>( + GPU_SERVICE_PUBLIC_KEY_API(params.namespace || ''), + { + method: 'GET', + params, + cancelToken: options?.token + } + ); +} + +export async function createGPUServicePublicKey(params: { + namespace: string; + data: FormData; +}) { + return request(GPU_SERVICE_PUBLIC_KEY_API(params.namespace), { + method: 'POST', + data: params.data + }); +} + +export async function updateGPUServicePublicKey(params: { + namespace: string; + id: number; + data: FormData; +}) { + return request( + `${GPU_SERVICE_PUBLIC_KEY_API(params.namespace)}/${params.id}`, + { + method: 'PUT', + data: params.data + } + ); +} + +export async function deleteGPUServicePublicKey(params: { + namespace: string; + id: number; +}) { + return request( + `${GPU_SERVICE_PUBLIC_KEY_API(params.namespace)}/${params.id}`, + { + method: 'DELETE' + } + ); +} diff --git a/src/pages/gpu-service/public-keys/index.tsx b/src/pages/gpu-service/public-keys/index.tsx new file mode 100644 index 00000000..fdf73d6d --- /dev/null +++ b/src/pages/gpu-service/public-keys/index.tsx @@ -0,0 +1,63 @@ +import { INPUT_WIDTH } from '@/constants'; +import { Textarea } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; +import { Button, Form, message } from 'antd'; +import React from 'react'; +import PageBox from '../../_components/page-box'; + +interface PublicKeyFormData { + public_key?: string; +} + +const PLACEHOLDER = `以 ssh-rsa 或 ssh-ed25519 开头,每个 Public Key 单独一行 + +查看 Public Key: +- RSA +cat ~/.ssh/id_rsa.pub +- Ed25519 +cat ~/.ssh/id_ed25519.pub`; + +const GPUServicePublicKeys: React.FC = () => { + const intl = useIntl(); + const [form] = Form.useForm(); + + const handleSave = async () => { + try { + await form.validateFields(); + message.success('操作成功'); + } catch { + // ignore + } + }; + + return ( + +
+ name="public_key"> +