fix: storage api alignment
This commit is contained in:
+13
-3
@@ -192,9 +192,9 @@ const baseRoutes = [
|
|||||||
name: 'instances',
|
name: 'instances',
|
||||||
path: '/gpu-service/instances',
|
path: '/gpu-service/instances',
|
||||||
key: 'gpuServiceList',
|
key: 'gpuServiceList',
|
||||||
icon: 'icon-instances-outlined',
|
icon: 'icon-cloud-outlined',
|
||||||
selectedIcon: 'icon-instances-filled',
|
selectedIcon: 'icon-cloud-filled',
|
||||||
defaultIcon: 'icon-instances-outlined',
|
defaultIcon: 'icon-cloud-outlined',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './gpu-service/instances'
|
component: './gpu-service/instances'
|
||||||
},
|
},
|
||||||
@@ -217,6 +217,16 @@ const baseRoutes = [
|
|||||||
defaultIcon: 'icon-storage-outlined',
|
defaultIcon: 'icon-storage-outlined',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './gpu-service/storage'
|
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'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Vendored
+38
@@ -16,6 +16,43 @@ declare namespace Global {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface K8sPageResponse<T> {
|
||||||
|
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 {
|
interface UserInfo {
|
||||||
username: string;
|
username: string;
|
||||||
is_admin: boolean;
|
is_admin: boolean;
|
||||||
@@ -24,6 +61,7 @@ declare namespace Global {
|
|||||||
id: number;
|
id: number;
|
||||||
source: string;
|
source: string;
|
||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
|
org_name?: string;
|
||||||
}
|
}
|
||||||
type EmptyObject = Record<never, never>;
|
type EmptyObject = Record<never, never>;
|
||||||
|
|
||||||
|
|||||||
@@ -72,3 +72,16 @@ export const lengthReg = /^.{6,64}$/;
|
|||||||
*/
|
*/
|
||||||
export const modelNameReg =
|
export const modelNameReg =
|
||||||
/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,61}[A-Za-z0-9])?$/g;
|
/^[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])?$/;
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ export default (props: any) => {
|
|||||||
config={{
|
config={{
|
||||||
apiBaseUrl: GPUSTACK_API_BASE_URL,
|
apiBaseUrl: GPUSTACK_API_BASE_URL,
|
||||||
theme: userSettings.theme,
|
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,
|
isDarkTheme: userSettings.isDarkTheme,
|
||||||
defaultColorPrimary: COLOR_PRIMARY
|
defaultColorPrimary: COLOR_PRIMARY
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -44,5 +44,6 @@ export default {
|
|||||||
'menu.gpuService': 'GPU Service',
|
'menu.gpuService': 'GPU Service',
|
||||||
'menu.gpuService.instances': 'GPU Instances',
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
'menu.gpuService.templates': 'Instance Templates',
|
'menu.gpuService.templates': 'Instance Templates',
|
||||||
'menu.gpuService.storage': 'Storage'
|
'menu.gpuService.storage': 'Storage',
|
||||||
|
'menu.gpuService.publicKeys': 'SSH Public Keys'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export default {
|
|||||||
'menu.gpuService': 'GPU Service',
|
'menu.gpuService': 'GPU Service',
|
||||||
'menu.gpuService.instances': 'GPU Instances',
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
'menu.gpuService.templates': 'Instance Templates',
|
'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) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export default {
|
|||||||
'menu.gpuService': 'GPU Service',
|
'menu.gpuService': 'GPU Service',
|
||||||
'menu.gpuService.instances': 'GPU Instances',
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
'menu.gpuService.templates': 'Instance Templates',
|
'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) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export default {
|
|||||||
'menu.gpuService': 'GPU Service',
|
'menu.gpuService': 'GPU Service',
|
||||||
'menu.gpuService.instances': 'GPU Instances',
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
'menu.gpuService.templates': 'Instance Templates',
|
'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) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -44,5 +44,6 @@ export default {
|
|||||||
'menu.gpuService': 'GPU 服务',
|
'menu.gpuService': 'GPU 服务',
|
||||||
'menu.gpuService.instances': 'GPU 实例',
|
'menu.gpuService.instances': 'GPU 实例',
|
||||||
'menu.gpuService.templates': '实例模板',
|
'menu.gpuService.templates': '实例模板',
|
||||||
'menu.gpuService.storage': '存储'
|
'menu.gpuService.storage': '存储',
|
||||||
|
'menu.gpuService.publicKeys': 'SSH 公钥'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const apiVersion = 'worker.gpustack.ai/v1';
|
||||||
|
|
||||||
|
export const KindMapping = {
|
||||||
|
sshPublicKey: 'InstanceSSHPublicKey',
|
||||||
|
instance: 'Instance',
|
||||||
|
instancePersistentVolume: 'InstancePersistentVolume',
|
||||||
|
instanceTemplate: 'InstanceTemplate'
|
||||||
|
};
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import { mockInstanceData } from '../config/mock-data';
|
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(
|
export async function queryGPUServiceInstances(
|
||||||
params: Global.SearchParams,
|
params: Global.K8sSearchParams,
|
||||||
options?: any
|
options?: any
|
||||||
) {
|
) {
|
||||||
// return request<Global.PageResponse<ListItem>>(GPU_SERVICE_INSTANCES_API, {
|
// return request<Global.PageResponse<ListItem>>(GPU_SERVICE_INSTANCES_API, {
|
||||||
@@ -35,7 +39,7 @@ export async function queryGPUServiceInstances(
|
|||||||
page,
|
page,
|
||||||
perPage
|
perPage
|
||||||
}
|
}
|
||||||
} as Global.PageResponse<ListItem>;
|
} as Global.K8sPageResponse<ListItem>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createGPUServiceInstance(params: { data: FormData }) {
|
export async function createGPUServiceInstance(params: { data: FormData }) {
|
||||||
@@ -62,3 +66,35 @@ export async function deleteGPUServiceInstance(id: number) {
|
|||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========== Instance Types ===========
|
||||||
|
|
||||||
|
export async function queryGPUServiceInstanceTypes(
|
||||||
|
params: Global.K8sSearchParams,
|
||||||
|
options?: any
|
||||||
|
) {
|
||||||
|
return request<Global.K8sPageResponse<InstanceTypeItem>>(
|
||||||
|
GPU_SERVICE_INSTANCES_TYPE_API,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
cancelToken: options?.token
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function queryGPUServiceInstanceTypeItems(
|
||||||
|
params: {
|
||||||
|
name: string;
|
||||||
|
},
|
||||||
|
options?: any
|
||||||
|
) {
|
||||||
|
return request<InstanceTypeItem>(
|
||||||
|
`${GPU_SERVICE_INSTANCES_TYPE_API}/${params.name}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
cancelToken: options?.token
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,23 +1,57 @@
|
|||||||
export interface FormData {
|
export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never';
|
||||||
|
|
||||||
|
export interface InstanceEnvVar {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
value?: 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
export interface ListItem extends FormData {
|
||||||
id: number;
|
id: number;
|
||||||
status?: string;
|
status?: string;
|
||||||
endpoint?: string;
|
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
}
|
}
|
||||||
@@ -31,3 +65,42 @@ export interface InstanceItem {
|
|||||||
gpu_count: number;
|
gpu_count: number;
|
||||||
status: string; // available, unavailable
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import {
|
import {
|
||||||
|
CheckboxField,
|
||||||
|
Input as CInput,
|
||||||
CollapsePanel,
|
CollapsePanel,
|
||||||
IconFont,
|
IconFont,
|
||||||
ScrollSpyTabs,
|
ScrollSpyTabs,
|
||||||
@@ -206,6 +208,20 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="enable_ssh"
|
||||||
|
valuePropName="checked"
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
>
|
||||||
|
<CheckboxField label={'SSH Terminal Access'}></CheckboxField>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
data-field="name"
|
||||||
|
hidden
|
||||||
|
name={['spec', 'sshPublicKey', 'name']}
|
||||||
|
>
|
||||||
|
<CInput.Input />
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</ScrollSpyTabs>
|
</ScrollSpyTabs>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 { Button, Flex, Form, Radio } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { StorageStatusValueMap } from '../../storage/config';
|
|
||||||
import { mockStorageData } from '../../storage/config/mock-data';
|
import { mockStorageData } from '../../storage/config/mock-data';
|
||||||
import { StorageModeValueMap } from '../config';
|
import { StorageModeValueMap } from '../config';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
@@ -57,29 +56,24 @@ const StorageVolume = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{storageMode === StorageModeValueMap.Existing && (
|
{storageMode === StorageModeValueMap.Existing && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
|
<Form.Item<FormData>
|
||||||
<div style={{ flex: 1 }}>
|
name="storage_id"
|
||||||
<Form.Item<FormData>
|
rules={[
|
||||||
name="storage_id"
|
{
|
||||||
rules={[
|
required: true,
|
||||||
{
|
message: '请选择预存储卷'
|
||||||
required: true,
|
}
|
||||||
message: '请选择预存储卷'
|
]}
|
||||||
}
|
>
|
||||||
]}
|
<Select
|
||||||
>
|
label="持久卷"
|
||||||
<Select
|
required
|
||||||
label="持久卷"
|
options={mockStorageData.map((item) => ({
|
||||||
required
|
label: `${item.metadata?.name} / ${item.spec?.capacity ?? '-'}`,
|
||||||
options={mockStorageData.map((item) => ({
|
value: item.id
|
||||||
label: `${item.name} / ${item.capacity_gb ?? '-'} GB`,
|
}))}
|
||||||
value: item.id,
|
/>
|
||||||
disabled: item.status !== StorageStatusValueMap.Available
|
</Form.Item>
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{storageMode === StorageModeValueMap.Temporary && (
|
{storageMode === StorageModeValueMap.Temporary && (
|
||||||
@@ -95,22 +89,6 @@ const StorageVolume = () => {
|
|||||||
<CInputNumber min={1} precision={0} label="存储容量 (GB)" required />
|
<CInputNumber min={1} precision={0} label="存储容量 (GB)" required />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
{/* 挂载路径 */}
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="mount_path"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入存储挂载路径'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input.Input
|
|
||||||
label="挂载路径"
|
|
||||||
required
|
|
||||||
placeholder="挂载路径须以 / 开头"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</FieldBlock>
|
</FieldBlock>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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<Global.K8sPageResponse<ListItem>>(
|
||||||
|
GPU_SERVICE_PUBLIC_KEY_API(params.namespace || ''),
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
cancelToken: options?.token
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createGPUServicePublicKey(params: {
|
||||||
|
namespace: string;
|
||||||
|
data: FormData;
|
||||||
|
}) {
|
||||||
|
return request<ListItem>(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<ListItem>(
|
||||||
|
`${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'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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<PublicKeyFormData>();
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
await form.validateFields();
|
||||||
|
message.success('操作成功');
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageBox>
|
||||||
|
<Form
|
||||||
|
style={{ width: INPUT_WIDTH.large }}
|
||||||
|
name="gpuServicePublicKeyForm"
|
||||||
|
form={form}
|
||||||
|
>
|
||||||
|
<Form.Item<PublicKeyFormData> name="public_key">
|
||||||
|
<Textarea
|
||||||
|
required
|
||||||
|
label="SSH Public Key"
|
||||||
|
placeholder={PLACEHOLDER}
|
||||||
|
trim={false}
|
||||||
|
alwaysFocus
|
||||||
|
autoSize={{ minRows: 8, maxRows: 16 }}
|
||||||
|
style={{ width: INPUT_WIDTH.large }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ width: 120, marginTop: 24 }}
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'common.button.save' })}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</PageBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GPUServicePublicKeys;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
|
import { useQueryData } from '@gpustack/core-ui';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { apiVersion, KindMapping } from '../../constants';
|
||||||
|
import { createGPUServicePublicKey } from '../apis';
|
||||||
|
import { ListItem } from '../types';
|
||||||
|
|
||||||
|
interface CreateSshkeyParams {
|
||||||
|
name: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useCreateSshkey() {
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
|
|
||||||
|
const fetchDetail = useCallback(
|
||||||
|
(params: CreateSshkeyParams) =>
|
||||||
|
createGPUServicePublicKey({
|
||||||
|
namespace,
|
||||||
|
data: {
|
||||||
|
apiVersion,
|
||||||
|
kind: KindMapping.sshPublicKey,
|
||||||
|
metadata: {
|
||||||
|
name: params.name,
|
||||||
|
namespace
|
||||||
|
},
|
||||||
|
data: params.data
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[namespace]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
|
||||||
|
ListItem,
|
||||||
|
CreateSshkeyParams
|
||||||
|
>({
|
||||||
|
fetchDetail,
|
||||||
|
key: 'createSshkey'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
|
import { useQueryData } from '@gpustack/core-ui';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { queryGPUServicePublicKeys } from '../apis';
|
||||||
|
import { ListItem } from '../types';
|
||||||
|
|
||||||
|
export default function useGetSshkey() {
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
|
|
||||||
|
const fetchDetail = useCallback(
|
||||||
|
(params: Global.K8sSearchParams = {}, options?: any) =>
|
||||||
|
queryGPUServicePublicKeys({ ...params, namespace }, options),
|
||||||
|
[namespace]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
|
||||||
|
Global.K8sPageResponse<ListItem>,
|
||||||
|
Global.K8sSearchParams
|
||||||
|
>({
|
||||||
|
fetchDetail,
|
||||||
|
key: 'sshkey'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
|
import { useQueryData } from '@gpustack/core-ui';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { apiVersion, KindMapping } from '../../constants';
|
||||||
|
import { updateGPUServicePublicKey } from '../apis';
|
||||||
|
import { ListItem } from '../types';
|
||||||
|
|
||||||
|
interface UpdateSshkeyParams {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useUpdateSshkey() {
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
|
|
||||||
|
const fetchDetail = useCallback(
|
||||||
|
(params: UpdateSshkeyParams) =>
|
||||||
|
updateGPUServicePublicKey({
|
||||||
|
namespace,
|
||||||
|
id: params.id,
|
||||||
|
data: {
|
||||||
|
apiVersion,
|
||||||
|
kind: KindMapping.sshPublicKey,
|
||||||
|
metadata: {
|
||||||
|
name: params.name,
|
||||||
|
namespace
|
||||||
|
},
|
||||||
|
data: params.data
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[namespace]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
|
||||||
|
ListItem,
|
||||||
|
UpdateSshkeyParams
|
||||||
|
>({
|
||||||
|
fetchDetail,
|
||||||
|
key: 'updateSshkey'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface FormData extends Global.K8sCommonData {
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListItem extends FormData {
|
||||||
|
id: number;
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
}
|
||||||
@@ -1,64 +1,60 @@
|
|||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import { mockStorageData } from '../config/mock-data';
|
import { ListItem } from '../config/types';
|
||||||
import { FormData, ListItem } from '../config/types';
|
|
||||||
|
|
||||||
export const GPU_SERVICE_STORAGE_API = '/gpu-service-storage';
|
export const GPU_SERVICE_STORAGE_API = (namespace: string) =>
|
||||||
|
`/proxy/apis/worker.gpustack.ai/v1/namespaces/${namespace}/instancepersistentvolumes`;
|
||||||
|
|
||||||
export async function queryGPUServiceStorage(
|
export async function queryGPUServiceStorage(
|
||||||
params: Global.SearchParams,
|
params: Global.K8sSearchParams,
|
||||||
options?: any
|
options?: any
|
||||||
) {
|
) {
|
||||||
// return request<Global.PageResponse<ListItem>>(GPU_SERVICE_STORAGE_API, {
|
return request<Global.K8sPageResponse<ListItem>>(
|
||||||
// method: 'GET',
|
GPU_SERVICE_STORAGE_API(params.namespace || ''),
|
||||||
// params,
|
{
|
||||||
// cancelToken: options?.token
|
method: 'GET',
|
||||||
// });
|
params,
|
||||||
const page = params.page || 1;
|
cancelToken: options?.token
|
||||||
const perPage = params.perPage || 10;
|
|
||||||
const search = params.search?.toLowerCase();
|
|
||||||
const clusterId = params.cluster_id;
|
|
||||||
const filteredData = mockStorageData.filter((item) => {
|
|
||||||
const matchSearch = search
|
|
||||||
? item.name.toLowerCase().includes(search)
|
|
||||||
: true;
|
|
||||||
const matchCluster = clusterId ? item.cluster_id === clusterId : true;
|
|
||||||
return matchSearch && matchCluster;
|
|
||||||
});
|
|
||||||
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<ListItem>;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createGPUServiceStorage(params: { data: FormData }) {
|
export async function createGPUServiceStorage(
|
||||||
// return request<ListItem>(GPU_SERVICE_STORAGE_API, {
|
params: {
|
||||||
// method: 'POST',
|
namespace: string;
|
||||||
// data: params.data
|
data: Global.K8sCommonData;
|
||||||
// });
|
},
|
||||||
return true;
|
option?: any
|
||||||
|
) {
|
||||||
|
return request<ListItem>(GPU_SERVICE_STORAGE_API(params.namespace), {
|
||||||
|
method: 'POST',
|
||||||
|
data: params.data,
|
||||||
|
cancelToken: option?.token
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateGPUServiceStorage(params: {
|
export async function updateGPUServiceStorage(
|
||||||
|
params: {
|
||||||
|
namespace: string;
|
||||||
|
id: number;
|
||||||
|
data: Global.K8sCommonData;
|
||||||
|
},
|
||||||
|
option?: any
|
||||||
|
) {
|
||||||
|
return request<ListItem>(
|
||||||
|
`${GPU_SERVICE_STORAGE_API(params.namespace)}/${params.id}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
data: params.data,
|
||||||
|
cancelToken: option?.token
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteGPUServiceStorage(params: {
|
||||||
|
namespace: string;
|
||||||
id: number;
|
id: number;
|
||||||
data: FormData;
|
|
||||||
}) {
|
}) {
|
||||||
// return request<ListItem>(`${GPU_SERVICE_STORAGE_API}/${params.id}`, {
|
return request(`${GPU_SERVICE_STORAGE_API(params.namespace)}/${params.id}`, {
|
||||||
// method: 'PUT',
|
|
||||||
// data: params.data
|
|
||||||
// });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteGPUServiceStorage(id: number) {
|
|
||||||
return request(`${GPU_SERVICE_STORAGE_API}/${id}`, {
|
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { StatusMaps } from '@/config';
|
|
||||||
import { StatusType } from '@/config/types';
|
|
||||||
import { icons } from '@gpustack/core-ui';
|
import { icons } from '@gpustack/core-ui';
|
||||||
|
|
||||||
export const StorageTypeValueMap = {
|
export const StorageTypeValueMap = {
|
||||||
@@ -9,52 +7,72 @@ export const StorageTypeValueMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const StorageTypeLabelMap: Record<string, string> = {
|
export const StorageTypeLabelMap: Record<string, string> = {
|
||||||
[StorageTypeValueMap.Local]: '默认存储',
|
[StorageTypeValueMap.Local]: '本地存储',
|
||||||
[StorageTypeValueMap.Shared]: '默认存储',
|
[StorageTypeValueMap.Shared]: '共享存储',
|
||||||
[StorageTypeValueMap.Object]: '对象存储'
|
[StorageTypeValueMap.Object]: '对象存储'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StorageAccessModeValueMap = {
|
export const StorageTypeOptions = [
|
||||||
ReadWriteOnce: 'ReadWriteOnce',
|
|
||||||
ReadOnlyMany: 'ReadOnlyMany',
|
|
||||||
ReadWriteMany: 'ReadWriteMany'
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StorageAccessModeOptions = [
|
|
||||||
{
|
{
|
||||||
label: 'ReadWriteOnce',
|
label: StorageTypeLabelMap[StorageTypeValueMap.Local],
|
||||||
value: StorageAccessModeValueMap.ReadWriteOnce
|
value: StorageTypeValueMap.Local
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ReadWriteMany',
|
label: StorageTypeLabelMap[StorageTypeValueMap.Shared],
|
||||||
value: StorageAccessModeValueMap.ReadWriteMany
|
value: StorageTypeValueMap.Shared
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ReadOnlyMany',
|
label: StorageTypeLabelMap[StorageTypeValueMap.Object],
|
||||||
value: StorageAccessModeValueMap.ReadOnlyMany
|
value: StorageTypeValueMap.Object
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const StorageStatusValueMap = {
|
export const AccessModeValueMap = {
|
||||||
Available: 'Available',
|
ReadWriteOnce: 'ReadWriteOnce',
|
||||||
Bound: 'Bound',
|
ReadOnlyMany: 'ReadOnlyMany',
|
||||||
Released: 'Released',
|
ReadWriteMany: 'ReadWriteMany',
|
||||||
Failed: 'Failed'
|
ReadWriteOncePod: 'ReadWriteOncePod'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StorageStatusLabelMap: Record<string, string> = {
|
export const AccessModeOptions = [
|
||||||
[StorageStatusValueMap.Available]: 'Available',
|
{
|
||||||
[StorageStatusValueMap.Bound]: 'Bound',
|
label: 'ReadWriteOnce',
|
||||||
[StorageStatusValueMap.Released]: 'Released',
|
value: AccessModeValueMap.ReadWriteOnce
|
||||||
[StorageStatusValueMap.Failed]: 'Failed'
|
},
|
||||||
|
{
|
||||||
|
label: 'ReadWriteMany',
|
||||||
|
value: AccessModeValueMap.ReadWriteMany
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ReadOnlyMany',
|
||||||
|
value: AccessModeValueMap.ReadOnlyMany
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ReadWriteOncePod',
|
||||||
|
value: AccessModeValueMap.ReadWriteOncePod
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ReClaimPolicyValueMap = {
|
||||||
|
Retain: 'Retain',
|
||||||
|
Delete: 'Delete',
|
||||||
|
Recycle: 'Recycle'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const status: Record<string, StatusType> = {
|
export const ReClaimPolicyOptions = [
|
||||||
[StorageStatusValueMap.Available]: StatusMaps.success,
|
{
|
||||||
[StorageStatusValueMap.Bound]: StatusMaps.transitioning,
|
label: 'Retain',
|
||||||
[StorageStatusValueMap.Released]: StatusMaps.inactive,
|
value: ReClaimPolicyValueMap.Retain
|
||||||
[StorageStatusValueMap.Failed]: StatusMaps.error
|
},
|
||||||
};
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
value: ReClaimPolicyValueMap.Delete
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Recycle',
|
||||||
|
value: ReClaimPolicyValueMap.Recycle
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export const rowActionList = [
|
export const rowActionList = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,68 +1,64 @@
|
|||||||
import { StorageStatusValueMap, StorageTypeValueMap } from '.';
|
import { StorageTypeValueMap } from '.';
|
||||||
import { ListItem } from './types';
|
import { ListItem } from './types';
|
||||||
|
|
||||||
export const mockStorageData: ListItem[] = [
|
export const mockStorageData: (ListItem & { cluster_id: number })[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'local-nvme-cache',
|
|
||||||
type: StorageTypeValueMap.Local,
|
|
||||||
capacity_gb: 500,
|
|
||||||
mount_path: '/mnt/nvme',
|
|
||||||
access_modes: ['ReadWriteOnce'],
|
|
||||||
parameters: {
|
|
||||||
path: '/mnt/nvme'
|
|
||||||
},
|
|
||||||
status: StorageStatusValueMap.Available,
|
|
||||||
cluster_id: 1,
|
cluster_id: 1,
|
||||||
description: 'Local NVMe cache for hot model files',
|
metadata: {
|
||||||
|
name: 'local-nvme-cache',
|
||||||
|
namespace: 'default'
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
accessMode: 'ReadWriteOnce',
|
||||||
|
capacity: '500Gi',
|
||||||
|
type: StorageTypeValueMap.Local
|
||||||
|
},
|
||||||
created_at: '2026-04-01T10:00:00Z',
|
created_at: '2026-04-01T10:00:00Z',
|
||||||
updated_at: '2026-04-10T10:00:00Z'
|
updated_at: '2026-04-10T10:00:00Z'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'shared-model-store',
|
|
||||||
type: StorageTypeValueMap.Shared,
|
|
||||||
capacity_gb: 2048,
|
|
||||||
mount_path: '/models',
|
|
||||||
access_modes: ['ReadWriteMany'],
|
|
||||||
parameters: {
|
|
||||||
storageClassName: 'nfs-client'
|
|
||||||
},
|
|
||||||
status: StorageStatusValueMap.Bound,
|
|
||||||
cluster_id: 1,
|
cluster_id: 1,
|
||||||
description: 'Shared storage for model weights',
|
metadata: {
|
||||||
|
name: 'shared-model-store',
|
||||||
|
namespace: 'default'
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
accessMode: 'ReadWriteMany',
|
||||||
|
capacity: '2Ti',
|
||||||
|
type: StorageTypeValueMap.Shared
|
||||||
|
},
|
||||||
created_at: '2026-04-02T10:00:00Z',
|
created_at: '2026-04-02T10:00:00Z',
|
||||||
updated_at: '2026-04-11T10:00:00Z'
|
updated_at: '2026-04-11T10:00:00Z'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: 'object-dataset-bucket',
|
|
||||||
type: StorageTypeValueMap.Object,
|
|
||||||
capacity_gb: 10240,
|
|
||||||
mount_path: '/datasets',
|
|
||||||
access_modes: ['ReadOnlyMany'],
|
|
||||||
parameters: {
|
|
||||||
bucket: 'datasets'
|
|
||||||
},
|
|
||||||
status: StorageStatusValueMap.Released,
|
|
||||||
cluster_id: 2,
|
cluster_id: 2,
|
||||||
description: 'Object storage mounted for datasets',
|
metadata: {
|
||||||
|
name: 'object-dataset-bucket',
|
||||||
|
namespace: 'default'
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
accessMode: 'ReadOnlyMany',
|
||||||
|
capacity: '10Ti',
|
||||||
|
type: StorageTypeValueMap.Object
|
||||||
|
},
|
||||||
created_at: '2026-04-03T10:00:00Z',
|
created_at: '2026-04-03T10:00:00Z',
|
||||||
updated_at: '2026-04-12T10:00:00Z'
|
updated_at: '2026-04-12T10:00:00Z'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
name: 'failed-shared-cache',
|
|
||||||
type: StorageTypeValueMap.Shared,
|
|
||||||
capacity_gb: 1024,
|
|
||||||
mount_path: '/failed-cache',
|
|
||||||
access_modes: ['ReadWriteMany'],
|
|
||||||
parameters: {
|
|
||||||
storageClassName: 'nfs-client'
|
|
||||||
},
|
|
||||||
status: StorageStatusValueMap.Failed,
|
|
||||||
cluster_id: 2,
|
cluster_id: 2,
|
||||||
description: 'Shared cache waiting for storage backend recovery',
|
metadata: {
|
||||||
|
name: 'failed-shared-cache',
|
||||||
|
namespace: 'default'
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
accessMode: 'ReadWriteMany',
|
||||||
|
capacity: '1Ti',
|
||||||
|
type: StorageTypeValueMap.Shared
|
||||||
|
},
|
||||||
created_at: '2026-04-04T10:00:00Z',
|
created_at: '2026-04-04T10:00:00Z',
|
||||||
updated_at: '2026-04-13T10:00:00Z'
|
updated_at: '2026-04-13T10:00:00Z'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
|
export type AccessModeType =
|
||||||
|
| 'ReadOnlyMany'
|
||||||
|
| 'ReadWriteMany'
|
||||||
|
| 'ReadWriteOnce'
|
||||||
|
| 'ReadWriteOncePod';
|
||||||
|
|
||||||
|
export type ReClaimPolicyType = 'Retain' | 'Delete' | 'Recycle';
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
name: string;
|
metadata: {
|
||||||
description?: string;
|
name: string;
|
||||||
type?: string;
|
namespace: string;
|
||||||
capacity_gb?: number;
|
};
|
||||||
mount_path?: string;
|
spec: {
|
||||||
access_modes?: string[];
|
accessMode: AccessModeType;
|
||||||
parameters?: Record<string, any>;
|
capacity: string;
|
||||||
status?: string;
|
type: string;
|
||||||
cluster_id?: number;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListItem extends FormData {
|
export interface ListItem extends FormData {
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import {
|
import { Input as CInput, Select as SealSelect } from '@gpustack/core-ui';
|
||||||
Input as CInput,
|
|
||||||
InputNumber as CInputNumber,
|
|
||||||
Select as SealSelect
|
|
||||||
} from '@gpustack/core-ui';
|
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import { StorageAccessModeOptions } from '../config';
|
import { AccessModeOptions, StorageTypeOptions } from '../config';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
const Basic = () => {
|
const Basic = () => {
|
||||||
const form = Form.useFormInstance<FormData>();
|
|
||||||
const parameters = Form.useWatch('parameters', form);
|
|
||||||
|
|
||||||
const handleParametersChange = (value: Record<string, any>) => {
|
|
||||||
form.setFieldValue('parameters', value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="name"
|
name={['metadata', 'name']}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -31,7 +20,7 @@ const Basic = () => {
|
|||||||
<div style={{ display: 'flex', gap: 16 }}>
|
<div style={{ display: 'flex', gap: 16 }}>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="type"
|
name={['spec', 'type']}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -39,12 +28,16 @@ const Basic = () => {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect label="存储类型" required options={[]}></SealSelect>
|
<SealSelect
|
||||||
|
label="存储类型"
|
||||||
|
required
|
||||||
|
options={StorageTypeOptions}
|
||||||
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="capacity_gb"
|
name={['spec', 'capacity']}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -52,28 +45,25 @@ const Basic = () => {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<CInputNumber min={1} precision={0} label="容量 (GB)" required />
|
<CInput.Input label="容量" required placeholder="例如:10Gi" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Form.Item<FormData> name="access_modes">
|
<Form.Item<FormData>
|
||||||
|
name={['spec', 'accessMode']}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择访问模式'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
label="存储卷访问方式"
|
label="存储卷访问方式"
|
||||||
allowClear
|
required
|
||||||
options={StorageAccessModeOptions}
|
options={AccessModeOptions}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{/* <Form.Item<FormData> name="parameters">
|
|
||||||
<LabelSelector
|
|
||||||
label="存储卷配置参数"
|
|
||||||
labels={parameters || {}}
|
|
||||||
btnText="添加参数"
|
|
||||||
onChange={handleParametersChange}
|
|
||||||
/>
|
|
||||||
</Form.Item> */}
|
|
||||||
<Form.Item<FormData> name="description">
|
|
||||||
<CInput.TextArea label="描述" scaleSize />
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { PageAction } from '@/config';
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||||
import { StorageStatusValueMap } from '../config';
|
import { AccessModeValueMap, StorageTypeValueMap } from '../config';
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import Basic from './basic';
|
import Basic from './basic';
|
||||||
|
|
||||||
@@ -11,12 +11,19 @@ interface StorageFormProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
currentData?: ListItem | null;
|
currentData?: ListItem | null;
|
||||||
|
namespace?: string;
|
||||||
onFinish: (values: FormData) => Promise<void>;
|
onFinish: (values: FormData) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GPUServiceStorageForm: React.FC<StorageFormProps> = forwardRef(
|
const GPUServiceStorageForm: React.FC<StorageFormProps> = forwardRef(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { action, currentData, open, onFinish } = props;
|
const {
|
||||||
|
action,
|
||||||
|
currentData,
|
||||||
|
open,
|
||||||
|
namespace = 'default',
|
||||||
|
onFinish
|
||||||
|
} = props;
|
||||||
const [form] = Form.useForm<FormData>();
|
const [form] = Form.useForm<FormData>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -27,24 +34,30 @@ const GPUServiceStorageForm: React.FC<StorageFormProps> = forwardRef(
|
|||||||
|
|
||||||
if (action === PageAction.EDIT && currentData) {
|
if (action === PageAction.EDIT && currentData) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
name: currentData.name,
|
metadata: {
|
||||||
description: currentData.description,
|
name: currentData.metadata?.name,
|
||||||
type: currentData.type,
|
namespace: currentData.metadata?.namespace
|
||||||
capacity_gb: currentData.capacity_gb,
|
},
|
||||||
mount_path: currentData.mount_path,
|
spec: {
|
||||||
access_modes: currentData.access_modes,
|
accessMode: currentData.spec?.accessMode,
|
||||||
parameters: currentData.parameters,
|
capacity: currentData.spec?.capacity,
|
||||||
status: currentData.status
|
type: currentData.spec?.type
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
type: '默认存储',
|
metadata: {
|
||||||
parameters: {},
|
namespace
|
||||||
status: StorageStatusValueMap.Available
|
},
|
||||||
|
spec: {
|
||||||
|
accessMode:
|
||||||
|
AccessModeValueMap.ReadWriteOnce as FormData['spec']['accessMode'],
|
||||||
|
type: StorageTypeValueMap.Local
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [action, currentData, form, open]);
|
}, [action, currentData, form, open, namespace]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
submit: () => {
|
submit: () => {
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import { tableSorter } from '@/config/settings';
|
import { tableSorter } from '@/config/settings';
|
||||||
import { AutoTooltip, DropdownButtons, StatusTag } from '@gpustack/core-ui';
|
import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui';
|
||||||
import type { ColumnsType } from 'antd/lib/table';
|
import type { ColumnsType } from 'antd/lib/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import {
|
import { rowActionList, StorageTypeLabelMap } from '../config';
|
||||||
rowActionList,
|
|
||||||
status,
|
|
||||||
StorageStatusLabelMap,
|
|
||||||
StorageStatusValueMap,
|
|
||||||
StorageTypeLabelMap
|
|
||||||
} from '../config';
|
|
||||||
import { ListItem } from '../config/types';
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
interface ColumnsHookProps {
|
interface ColumnsHookProps {
|
||||||
@@ -25,7 +19,7 @@ const useStorageColumns = ({
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: ['metadata', 'name'],
|
||||||
key: 'name',
|
key: 'name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
@@ -39,48 +33,25 @@ const useStorageColumns = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '类型',
|
title: '类型',
|
||||||
dataIndex: 'type',
|
dataIndex: ['spec', 'type'],
|
||||||
key: 'type',
|
key: 'type',
|
||||||
sorter: tableSorter(2),
|
sorter: tableSorter(2),
|
||||||
render: (value: string) => StorageTypeLabelMap[value] || value || '-'
|
render: (value: string) => StorageTypeLabelMap[value] || value || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '容量',
|
||||||
dataIndex: 'status',
|
dataIndex: ['spec', 'capacity'],
|
||||||
key: 'status',
|
key: 'capacity',
|
||||||
sorter: tableSorter(3),
|
sorter: tableSorter(3),
|
||||||
render: (statusValue: string) => {
|
render: (value: string) => value ?? '-'
|
||||||
const value = statusValue || StorageStatusValueMap.Available;
|
|
||||||
return (
|
|
||||||
<StatusTag
|
|
||||||
statusValue={{
|
|
||||||
status: status[value],
|
|
||||||
text: StorageStatusLabelMap[value] || value
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '容量 (GB)',
|
title: '访问模式',
|
||||||
dataIndex: 'capacity_gb',
|
dataIndex: ['spec', 'accessMode'],
|
||||||
key: 'capacity_gb',
|
key: 'accessMode',
|
||||||
sorter: tableSorter(4),
|
sorter: tableSorter(4),
|
||||||
render: (value: number) => value ?? '-'
|
render: (value: string) => value || '-'
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// title: '挂载路径',
|
|
||||||
// dataIndex: 'mount_path',
|
|
||||||
// key: 'mount_path',
|
|
||||||
// ellipsis: {
|
|
||||||
// showTitle: false
|
|
||||||
// },
|
|
||||||
// render: (text: string) => (
|
|
||||||
// <AutoTooltip ghost minWidth={20}>
|
|
||||||
// {text || '-'}
|
|
||||||
// </AutoTooltip>
|
|
||||||
// )
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
|||||||
import type { PageActionType } from '@/config/types';
|
import type { PageActionType } from '@/config/types';
|
||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
import {
|
import {
|
||||||
BaseSelect,
|
BaseSelect,
|
||||||
DeleteModal,
|
DeleteModal,
|
||||||
@@ -14,20 +15,53 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { ConfigProvider, Divider, Flex, message, Table } from 'antd';
|
import { ConfigProvider, Divider, Flex, message, Table } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { PageContainerInner } from '../../_components/page-box';
|
import { PageContainerInner } from '../../_components/page-box';
|
||||||
import {
|
import {
|
||||||
createGPUServiceStorage,
|
|
||||||
deleteGPUServiceStorage,
|
deleteGPUServiceStorage,
|
||||||
GPU_SERVICE_STORAGE_API,
|
GPU_SERVICE_STORAGE_API,
|
||||||
queryGPUServiceStorage,
|
queryGPUServiceStorage
|
||||||
updateGPUServiceStorage
|
|
||||||
} from './apis';
|
} from './apis';
|
||||||
|
|
||||||
import AddModal from './components/add-modal';
|
import AddModal from './components/add-modal';
|
||||||
import { FormData, ListItem } from './config/types';
|
import { FormData, ListItem } from './config/types';
|
||||||
import useStorageColumns from './hooks/use-storage-columns';
|
import useStorageColumns from './hooks/use-storage-columns';
|
||||||
|
import useCreateStorage from './services/use-create-storage';
|
||||||
|
import useUpdateStorage from './services/use-update-storage';
|
||||||
|
|
||||||
const GPUServiceStorage: React.FC = () => {
|
const GPUServiceStorage: React.FC = () => {
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
|
|
||||||
|
const deleteStorage = useCallback(
|
||||||
|
(id: number) => deleteGPUServiceStorage({ namespace, id }),
|
||||||
|
[namespace]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchStorage = useCallback(
|
||||||
|
async (
|
||||||
|
params: any,
|
||||||
|
options?: any
|
||||||
|
): Promise<Global.PageResponse<ListItem>> => {
|
||||||
|
const res = await queryGPUServiceStorage(
|
||||||
|
{ ...params, namespace },
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const total = res.items?.length ?? 0;
|
||||||
|
const perPage = params.perPage || 10;
|
||||||
|
return {
|
||||||
|
items: res.items ?? [],
|
||||||
|
pagination: {
|
||||||
|
total,
|
||||||
|
totalPage: Math.ceil(total / perPage),
|
||||||
|
page: params.page || 1,
|
||||||
|
perPage
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[namespace]
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dataSource,
|
dataSource,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
@@ -44,12 +78,15 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
handleNameChange
|
handleNameChange
|
||||||
} = useTableFetch<ListItem>({
|
} = useTableFetch<ListItem>({
|
||||||
key: PaginationKey.Storage,
|
key: PaginationKey.Storage,
|
||||||
fetchAPI: queryGPUServiceStorage,
|
fetchAPI: fetchStorage,
|
||||||
deleteAPI: deleteGPUServiceStorage,
|
deleteAPI: deleteStorage,
|
||||||
watch: false,
|
watch: false,
|
||||||
API: GPU_SERVICE_STORAGE_API,
|
API: GPU_SERVICE_STORAGE_API(namespace),
|
||||||
contentForDelete: '存储'
|
contentForDelete: '存储'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { fetchData: createStorage } = useCreateStorage();
|
||||||
|
const { fetchData: updateStorage } = useUpdateStorage();
|
||||||
const {
|
const {
|
||||||
fetchClusterList,
|
fetchClusterList,
|
||||||
cancelRequest: cancelClusterRequest,
|
cancelRequest: cancelClusterRequest,
|
||||||
@@ -113,12 +150,12 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
const handleModalOk = async (data: FormData) => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (openAddModalStatus.action === PageAction.EDIT) {
|
if (openAddModalStatus.action === PageAction.EDIT) {
|
||||||
await updateGPUServiceStorage({
|
await updateStorage({
|
||||||
id: openAddModalStatus.currentData!.id,
|
id: openAddModalStatus.currentData!.id,
|
||||||
data
|
data
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await createGPUServiceStorage({ data });
|
await createStorage({ data });
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
@@ -133,7 +170,7 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
if (val === 'edit') {
|
if (val === 'edit') {
|
||||||
handleEditStorage(row);
|
handleEditStorage(row);
|
||||||
} else if (val === 'delete') {
|
} else if (val === 'delete') {
|
||||||
handleDelete({ ...row, name: row.name });
|
handleDelete({ ...row, name: row.metadata?.name });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
|
import { useQueryData } from '@gpustack/core-ui';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { apiVersion, KindMapping } from '../../constants';
|
||||||
|
import { createGPUServiceStorage } from '../apis';
|
||||||
|
import { FormData, ListItem } from '../config/types';
|
||||||
|
|
||||||
|
interface CreateStorageParams {
|
||||||
|
data: FormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useCreateStorage() {
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
|
|
||||||
|
const fetchDetail = useCallback(
|
||||||
|
(params: CreateStorageParams, option?: any) =>
|
||||||
|
createGPUServiceStorage(
|
||||||
|
{
|
||||||
|
namespace,
|
||||||
|
data: {
|
||||||
|
apiVersion,
|
||||||
|
kind: KindMapping.instancePersistentVolume,
|
||||||
|
metadata: {
|
||||||
|
name: params.data.metadata.name,
|
||||||
|
namespace
|
||||||
|
},
|
||||||
|
spec: params.data.spec
|
||||||
|
}
|
||||||
|
},
|
||||||
|
option
|
||||||
|
),
|
||||||
|
[namespace]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
|
||||||
|
ListItem,
|
||||||
|
CreateStorageParams
|
||||||
|
>({
|
||||||
|
fetchDetail,
|
||||||
|
key: 'createStorage'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
|
import { useQueryData } from '@gpustack/core-ui';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { apiVersion, KindMapping } from '../../constants';
|
||||||
|
import { updateGPUServiceStorage } from '../apis';
|
||||||
|
import { FormData, ListItem } from '../config/types';
|
||||||
|
|
||||||
|
interface UpdateStorageParams {
|
||||||
|
id: number;
|
||||||
|
data: FormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useUpdateStorage() {
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
|
|
||||||
|
const fetchDetail = useCallback(
|
||||||
|
(params: UpdateStorageParams, option?: any) =>
|
||||||
|
updateGPUServiceStorage(
|
||||||
|
{
|
||||||
|
namespace,
|
||||||
|
id: params.id,
|
||||||
|
data: {
|
||||||
|
apiVersion,
|
||||||
|
kind: KindMapping.instancePersistentVolume,
|
||||||
|
metadata: {
|
||||||
|
name: params.data.metadata.name,
|
||||||
|
namespace
|
||||||
|
},
|
||||||
|
spec: params.data.spec
|
||||||
|
}
|
||||||
|
},
|
||||||
|
option
|
||||||
|
),
|
||||||
|
[namespace]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
|
||||||
|
ListItem,
|
||||||
|
UpdateStorageParams
|
||||||
|
>({
|
||||||
|
fetchDetail,
|
||||||
|
key: 'updateStorage'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
export interface PortItem {
|
export interface PortItem {
|
||||||
protocol: 'udp' | 'tcp';
|
protocol: 'udp' | 'tcp';
|
||||||
value?: number;
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvItem {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
name: string;
|
image: string;
|
||||||
image?: string;
|
imagePullPolicy: string;
|
||||||
vendor?: string;
|
imagePullSecret: {
|
||||||
image_pull_policy?: string;
|
name: string;
|
||||||
run_command?: string;
|
};
|
||||||
boot_disk_size_gb?: number;
|
command: string[];
|
||||||
volume_size_gb?: number;
|
privileged: boolean;
|
||||||
volume_mount_path?: string;
|
ports: PortItem[];
|
||||||
ports?: PortItem[];
|
env: EnvItem[];
|
||||||
env?: Record<string, any>;
|
volumeMount: string;
|
||||||
|
resources: {
|
||||||
|
cpu: string;
|
||||||
|
ram: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListItem extends FormData {
|
export interface ListItem extends FormData {
|
||||||
id: number;
|
id: number;
|
||||||
description?: string;
|
|
||||||
gpu_count?: number;
|
|
||||||
replicas?: number;
|
|
||||||
status?: string;
|
status?: string;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { FormData } from '../config/types';
|
|||||||
import Ports from './ports';
|
import Ports from './ports';
|
||||||
|
|
||||||
interface BasicProps {
|
interface BasicProps {
|
||||||
page: 'template' | 'instance';
|
page?: 'template' | 'instance';
|
||||||
}
|
}
|
||||||
|
|
||||||
const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
|
const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ const PortItem: React.FC<PortItemProps> = ({ item, index, onChange }) => {
|
|||||||
<CInputNumber
|
<CInputNumber
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
disabled={index === 0}
|
|
||||||
precision={0}
|
precision={0}
|
||||||
value={item.value}
|
value={item.value}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import styled from 'styled-components';
|
|||||||
import PageBox from '../_components/page-box';
|
import PageBox from '../_components/page-box';
|
||||||
import Appearance from './components/appearance';
|
import Appearance from './components/appearance';
|
||||||
import ModifyPasswordn from './components/modify-password';
|
import ModifyPasswordn from './components/modify-password';
|
||||||
import PublicKey from './components/public-key';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
.ant-page-header-heading {
|
.ant-page-header-heading {
|
||||||
@@ -27,11 +26,6 @@ const Profile: React.FC = () => {
|
|||||||
const items: TabsProps['items'] = useMemo(() => {
|
const items: TabsProps['items'] = useMemo(() => {
|
||||||
if (initialState?.currentUser?.source !== 'Local') {
|
if (initialState?.currentUser?.source !== 'Local') {
|
||||||
return [
|
return [
|
||||||
{
|
|
||||||
key: 'public-key',
|
|
||||||
label: 'SSH Public Key',
|
|
||||||
children: <PublicKey />
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'appearance',
|
key: 'appearance',
|
||||||
label: intl.formatMessage({ id: 'common.appearance' }),
|
label: intl.formatMessage({ id: 'common.appearance' }),
|
||||||
@@ -45,11 +39,6 @@ const Profile: React.FC = () => {
|
|||||||
label: intl.formatMessage({ id: 'users.form.updatepassword' }),
|
label: intl.formatMessage({ id: 'users.form.updatepassword' }),
|
||||||
children: <ModifyPasswordn />
|
children: <ModifyPasswordn />
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'public-key',
|
|
||||||
label: 'SSH 公钥',
|
|
||||||
children: <PublicKey />
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'appearance',
|
key: 'appearance',
|
||||||
label: intl.formatMessage({ id: 'common.appearance' }),
|
label: intl.formatMessage({ id: 'common.appearance' }),
|
||||||
|
|||||||
Reference in New Issue
Block a user