feat: add intances list
This commit is contained in:
+1
-1
@@ -13,4 +13,4 @@
|
|||||||
.swc
|
.swc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.idea
|
.idea
|
||||||
.claude
|
.claude
|
||||||
|
|||||||
@@ -179,6 +179,47 @@ const baseRoutes = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'gpuService',
|
||||||
|
path: '/gpu-service',
|
||||||
|
key: 'gpuService',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/gpu-service',
|
||||||
|
redirect: '/gpu-service/list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'instances',
|
||||||
|
path: '/gpu-service/list',
|
||||||
|
key: 'gpuServiceList',
|
||||||
|
icon: 'icon-instances-outlined',
|
||||||
|
selectedIcon: 'icon-instances-filled',
|
||||||
|
defaultIcon: 'icon-instances-outlined',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
component: './gpu-service/instances'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'templates',
|
||||||
|
path: '/gpu-service/templates',
|
||||||
|
key: 'gpuServiceTemplates',
|
||||||
|
icon: 'icon-instance-template-outlined',
|
||||||
|
selectedIcon: 'icon-instance-template-filled',
|
||||||
|
defaultIcon: 'icon-instance-template-outlined',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
component: './gpu-service/templates'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'storage',
|
||||||
|
path: '/gpu-service/storage',
|
||||||
|
key: 'gpuServiceStorage',
|
||||||
|
icon: 'icon-storage-outlined',
|
||||||
|
selectedIcon: 'icon-storage-filled',
|
||||||
|
defaultIcon: 'icon-storage-outlined',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
component: './gpu-service/storage'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'resources',
|
name: 'resources',
|
||||||
path: '/resources',
|
path: '/resources',
|
||||||
|
|||||||
@@ -40,5 +40,9 @@ export default {
|
|||||||
'menu.clusterManagement.credentials': 'Cloud Credentials',
|
'menu.clusterManagement.credentials': 'Cloud Credentials',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
'menu.clusterManagement.clusterCreate': 'Create Cluster',
|
'menu.clusterManagement.clusterCreate': 'Create Cluster',
|
||||||
'menu.resources.backendsList': 'Inference Backends'
|
'menu.resources.backendsList': 'Inference Backends',
|
||||||
|
'menu.gpuService': 'GPU Service',
|
||||||
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
|
'menu.gpuService.templates': 'Instance Templates',
|
||||||
|
'menu.gpuService.storage': 'Storage Management'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ export default {
|
|||||||
'menu.models.userModels': 'My Models',
|
'menu.models.userModels': 'My Models',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
'menu.clusterManagement.clusterCreate': 'Create Cluster',
|
'menu.clusterManagement.clusterCreate': 'Create Cluster',
|
||||||
'menu.resources.backendsList': 'Inference Backends'
|
'menu.resources.backendsList': 'Inference Backends',
|
||||||
|
'menu.gpuService': 'GPU Service',
|
||||||
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
|
'menu.gpuService.templates': 'Instance Templates',
|
||||||
|
'menu.gpuService.storage': 'Storage Management'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ export default {
|
|||||||
'menu.clusterManagement.clusterDetail': 'Детали кластера',
|
'menu.clusterManagement.clusterDetail': 'Детали кластера',
|
||||||
'menu.clusterManagement.clusterCreate': 'Создать кластер',
|
'menu.clusterManagement.clusterCreate': 'Создать кластер',
|
||||||
'menu.resources.backendsList': 'Бэкенды запуска',
|
'menu.resources.backendsList': 'Бэкенды запуска',
|
||||||
'menu.settings': 'Settings'
|
'menu.settings': 'Settings',
|
||||||
|
'menu.gpuService': 'GPU Service',
|
||||||
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
|
'menu.gpuService.templates': 'Instance Templates',
|
||||||
|
'menu.gpuService.storage': 'Storage Management'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ export default {
|
|||||||
'menu.clusterManagement.clusterCreate': 'Küme Oluştur',
|
'menu.clusterManagement.clusterCreate': 'Küme Oluştur',
|
||||||
'menu.resources.backendsList': 'Çıkarım Altyapıları',
|
'menu.resources.backendsList': 'Çıkarım Altyapıları',
|
||||||
'menu.models.instances': 'Instances',
|
'menu.models.instances': 'Instances',
|
||||||
'menu.settings': 'Settings'
|
'menu.settings': 'Settings',
|
||||||
|
'menu.gpuService': 'GPU Service',
|
||||||
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
|
'menu.gpuService.templates': 'Instance Templates',
|
||||||
|
'menu.gpuService.storage': 'Storage Management'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -40,5 +40,9 @@ export default {
|
|||||||
'menu.clusterManagement.credentials': '云凭证',
|
'menu.clusterManagement.credentials': '云凭证',
|
||||||
'menu.clusterManagement.clusterDetail': '集群详情',
|
'menu.clusterManagement.clusterDetail': '集群详情',
|
||||||
'menu.clusterManagement.clusterCreate': '创建集群',
|
'menu.clusterManagement.clusterCreate': '创建集群',
|
||||||
'menu.resources.backendsList': '推理后端'
|
'menu.resources.backendsList': '推理后端',
|
||||||
|
'menu.gpuService': 'GPU 服务',
|
||||||
|
'menu.gpuService.instances': 'GPU 实例',
|
||||||
|
'menu.gpuService.templates': '实例模板',
|
||||||
|
'menu.gpuService.storage': '存储管理'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
import { FormData, ListItem } from '../config/types';
|
||||||
|
|
||||||
|
export const GPU_SERVICE_INSTANCES_API = '/gpu-service-instances';
|
||||||
|
|
||||||
|
export async function queryGPUServiceInstances(
|
||||||
|
params: Global.SearchParams,
|
||||||
|
options?: any
|
||||||
|
) {
|
||||||
|
return request<Global.PageResponse<ListItem>>(GPU_SERVICE_INSTANCES_API, {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
cancelToken: options?.token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createGPUServiceInstance(params: { data: FormData }) {
|
||||||
|
return request<ListItem>(GPU_SERVICE_INSTANCES_API, {
|
||||||
|
method: 'POST',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateGPUServiceInstance(params: {
|
||||||
|
id: number;
|
||||||
|
data: FormData;
|
||||||
|
}) {
|
||||||
|
return request<ListItem>(`${GPU_SERVICE_INSTANCES_API}/${params.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteGPUServiceInstance(id: number) {
|
||||||
|
return request(`${GPU_SERVICE_INSTANCES_API}/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { ModalFooter } from '@gpustack/core-ui';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import FormDrawer from '../../../_components/form-drawer';
|
||||||
|
import { FormData, ListItem } from '../config/types';
|
||||||
|
import GPUServiceInstanceForm from '../forms';
|
||||||
|
|
||||||
|
type AddModalProps = {
|
||||||
|
title: string;
|
||||||
|
action: PageActionType;
|
||||||
|
open: boolean;
|
||||||
|
onOk: (values: FormData) => void;
|
||||||
|
data?: ListItem | null;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddModal: React.FC<AddModalProps> = ({
|
||||||
|
title,
|
||||||
|
action,
|
||||||
|
open,
|
||||||
|
onOk,
|
||||||
|
data,
|
||||||
|
onCancel
|
||||||
|
}) => {
|
||||||
|
const form = useRef<any>(null);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
form.current?.submit();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
form.current?.resetFields();
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinish = async (values: FormData) => {
|
||||||
|
onOk({
|
||||||
|
...values
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormDrawer
|
||||||
|
title={title}
|
||||||
|
open={open}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
width={600}
|
||||||
|
footer={
|
||||||
|
<ModalFooter
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
style={{
|
||||||
|
padding: '16px 24px 8px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<GPUServiceInstanceForm
|
||||||
|
ref={form}
|
||||||
|
action={action}
|
||||||
|
currentData={data}
|
||||||
|
onFinish={onFinish}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
</FormDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddModal;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { icons } from '@gpustack/core-ui';
|
||||||
|
|
||||||
|
export const InstanceStatusValueMap = {
|
||||||
|
Running: 'running',
|
||||||
|
Pending: 'pending',
|
||||||
|
Stopped: 'stopped',
|
||||||
|
Failed: 'failed'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InstanceStatusLabelMap: Record<string, string> = {
|
||||||
|
[InstanceStatusValueMap.Running]: '运行中',
|
||||||
|
[InstanceStatusValueMap.Pending]: '等待中',
|
||||||
|
[InstanceStatusValueMap.Stopped]: '已停止',
|
||||||
|
[InstanceStatusValueMap.Failed]: '失败'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rowActionList = [
|
||||||
|
{
|
||||||
|
label: '编辑',
|
||||||
|
key: 'edit',
|
||||||
|
icon: icons.EditOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
key: 'delete',
|
||||||
|
icon: icons.DeleteOutlined,
|
||||||
|
props: {
|
||||||
|
danger: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export interface FormData {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
image?: string;
|
||||||
|
gpu_count?: number;
|
||||||
|
replicas?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListItem extends FormData {
|
||||||
|
id: number;
|
||||||
|
status?: string;
|
||||||
|
endpoint?: string;
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
Input as CInput,
|
||||||
|
InputNumber as CInputNumber
|
||||||
|
} from '@gpustack/core-ui';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
const Basic = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="name"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入实例名称'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CInput.Input label="实例名称" required />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="image"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入镜像'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CInput.Input label="镜像" required />
|
||||||
|
</Form.Item>
|
||||||
|
<div style={{ display: 'flex', gap: 16 }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="gpu_count"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入 GPU 数量'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CInputNumber min={0} precision={0} label="GPU 数量" required />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="replicas"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入副本数'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CInputNumber min={1} precision={0} label="副本数" required />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Form.Item<FormData> name="description">
|
||||||
|
<CInput.TextArea label="描述" scaleSize />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Basic;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { PageAction } from '@/config';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||||
|
import { FormData, ListItem } from '../config/types';
|
||||||
|
import Basic from './basic';
|
||||||
|
|
||||||
|
interface InstanceFormProps {
|
||||||
|
ref?: any;
|
||||||
|
open: boolean;
|
||||||
|
action: PageActionType;
|
||||||
|
currentData?: ListItem | null;
|
||||||
|
onFinish: (values: FormData) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||||
|
(props, ref) => {
|
||||||
|
const { action, currentData, open, onFinish } = props;
|
||||||
|
const [form] = Form.useForm<FormData>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
form.resetFields();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === PageAction.EDIT && currentData) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
name: currentData.name,
|
||||||
|
description: currentData.description,
|
||||||
|
image: currentData.image,
|
||||||
|
gpu_count: currentData.gpu_count,
|
||||||
|
replicas: currentData.replicas
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setFieldsValue({
|
||||||
|
gpu_count: 1,
|
||||||
|
replicas: 1
|
||||||
|
});
|
||||||
|
}, [action, currentData, form, open]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
submit: () => {
|
||||||
|
form.submit();
|
||||||
|
},
|
||||||
|
resetFields: () => {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
name="gpuServiceInstanceForm"
|
||||||
|
form={form}
|
||||||
|
onFinish={onFinish}
|
||||||
|
preserve={false}
|
||||||
|
>
|
||||||
|
<Basic />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default GPUServiceInstanceForm;
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { tableSorter } from '@/config/settings';
|
||||||
|
import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui';
|
||||||
|
import { Tag } from 'antd';
|
||||||
|
import type { ColumnsType } from 'antd/lib/table';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
InstanceStatusLabelMap,
|
||||||
|
InstanceStatusValueMap,
|
||||||
|
rowActionList
|
||||||
|
} from '../config';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
interface ColumnsHookProps {
|
||||||
|
handleSelect: (val: string, record: ListItem) => void;
|
||||||
|
sortOrder: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusColorMap: Record<string, string> = {
|
||||||
|
[InstanceStatusValueMap.Running]: 'success',
|
||||||
|
[InstanceStatusValueMap.Pending]: 'processing',
|
||||||
|
[InstanceStatusValueMap.Stopped]: 'default',
|
||||||
|
[InstanceStatusValueMap.Failed]: 'error'
|
||||||
|
};
|
||||||
|
|
||||||
|
const useInstancesColumns = ({
|
||||||
|
handleSelect,
|
||||||
|
sortOrder
|
||||||
|
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
||||||
|
return useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '实例名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
sorter: tableSorter(1),
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (text: string) => (
|
||||||
|
<AutoTooltip ghost style={{ maxWidth: 360 }}>
|
||||||
|
<span className="text-primary">{text}</span>
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
sorter: tableSorter(2),
|
||||||
|
render: (status: string) => {
|
||||||
|
const value = status || InstanceStatusValueMap.Pending;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
color={statusColorMap[value] || 'default'}
|
||||||
|
style={{ marginRight: 0 }}
|
||||||
|
>
|
||||||
|
{InstanceStatusLabelMap[value] || value}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '镜像',
|
||||||
|
dataIndex: 'image',
|
||||||
|
key: 'image',
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (text: string) => (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text || '-'}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'GPU 数量',
|
||||||
|
dataIndex: 'gpu_count',
|
||||||
|
key: 'gpu_count',
|
||||||
|
sorter: tableSorter(3),
|
||||||
|
render: (value: number) => value ?? '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '副本数',
|
||||||
|
dataIndex: 'replicas',
|
||||||
|
key: 'replicas',
|
||||||
|
sorter: tableSorter(4),
|
||||||
|
render: (value: number) => value ?? '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '访问端点',
|
||||||
|
dataIndex: 'endpoint',
|
||||||
|
key: 'endpoint',
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (text: string) => (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text || '-'}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
key: 'created_at',
|
||||||
|
sorter: tableSorter(5),
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (text: string) => (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'operation',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
render: (_text, record) => (
|
||||||
|
<DropdownButtons
|
||||||
|
items={rowActionList}
|
||||||
|
onSelect={(val) => handleSelect(val, record)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, [handleSelect, sortOrder]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useInstancesColumns;
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
import { PageAction } from '@/config';
|
||||||
|
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
||||||
|
import type { PageActionType } from '@/config/types';
|
||||||
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
|
import { DeleteModal, FilterBar, IconFont, NoResult } from '@gpustack/core-ui';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
import { ConfigProvider, message, Table } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import PageBox from '../../_components/page-box';
|
||||||
|
import {
|
||||||
|
createGPUServiceInstance,
|
||||||
|
deleteGPUServiceInstance,
|
||||||
|
GPU_SERVICE_INSTANCES_API,
|
||||||
|
queryGPUServiceInstances,
|
||||||
|
updateGPUServiceInstance
|
||||||
|
} from './apis';
|
||||||
|
import AddModal from './components/add-modal';
|
||||||
|
import { FormData, ListItem } from './config/types';
|
||||||
|
import useInstancesColumns from './hooks/use-instances-columns';
|
||||||
|
|
||||||
|
const GPUService: React.FC = () => {
|
||||||
|
const {
|
||||||
|
dataSource,
|
||||||
|
rowSelection,
|
||||||
|
queryParams,
|
||||||
|
sortOrder,
|
||||||
|
modalRef,
|
||||||
|
handleDelete,
|
||||||
|
handleDeleteBatch,
|
||||||
|
fetchData,
|
||||||
|
handlePageChange,
|
||||||
|
handleTableChange,
|
||||||
|
handleSearch,
|
||||||
|
handleNameChange
|
||||||
|
} = useTableFetch<ListItem>({
|
||||||
|
key: PaginationKey.Instances,
|
||||||
|
fetchAPI: queryGPUServiceInstances,
|
||||||
|
deleteAPI: deleteGPUServiceInstance,
|
||||||
|
watch: false,
|
||||||
|
API: GPU_SERVICE_INSTANCES_API,
|
||||||
|
contentForDelete: 'GPU 实例'
|
||||||
|
});
|
||||||
|
|
||||||
|
const [openAddModalStatus, setOpenAddModalStatus] = useState<{
|
||||||
|
action: PageActionType;
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
currentData?: ListItem | null;
|
||||||
|
}>({
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
open: false,
|
||||||
|
currentData: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddInstance = () => {
|
||||||
|
setOpenAddModalStatus({
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '创建 GPU 实例',
|
||||||
|
open: true,
|
||||||
|
currentData: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditInstance = (row: ListItem) => {
|
||||||
|
setOpenAddModalStatus({
|
||||||
|
action: PageAction.EDIT,
|
||||||
|
title: '编辑 GPU 实例',
|
||||||
|
open: true,
|
||||||
|
currentData: row
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setOpenAddModalStatus({
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
open: false,
|
||||||
|
currentData: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalOk = async (data: FormData) => {
|
||||||
|
try {
|
||||||
|
if (openAddModalStatus.action === PageAction.EDIT) {
|
||||||
|
await updateGPUServiceInstance({
|
||||||
|
id: openAddModalStatus.currentData!.id,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createGPUServiceInstance({ data });
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
closeModal();
|
||||||
|
message.success('操作成功');
|
||||||
|
} catch (error) {
|
||||||
|
message.error('操作失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = useMemoizedFn((val: string, row: ListItem) => {
|
||||||
|
if (val === 'edit') {
|
||||||
|
handleEditInstance(row);
|
||||||
|
} else if (val === 'delete') {
|
||||||
|
handleDelete({ ...row, name: row.name });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderEmpty = (type?: string) => {
|
||||||
|
if (type !== 'Table') return;
|
||||||
|
return (
|
||||||
|
<NoResult
|
||||||
|
loading={dataSource.loading}
|
||||||
|
loadend={dataSource.loadend}
|
||||||
|
dataSource={dataSource.dataList}
|
||||||
|
image={<IconFont type="icon-layers" />}
|
||||||
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
|
noFoundText="未找到匹配的 GPU 实例"
|
||||||
|
title="暂无 GPU 实例"
|
||||||
|
subTitle="创建一个 GPU 实例后会显示在这里"
|
||||||
|
onClick={handleAddInstance}
|
||||||
|
buttonText="创建"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useInstancesColumns({
|
||||||
|
handleSelect,
|
||||||
|
sortOrder
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageBox>
|
||||||
|
<FilterBar
|
||||||
|
marginBottom={22}
|
||||||
|
marginTop={30}
|
||||||
|
buttonText="创建"
|
||||||
|
handleSearch={handleSearch}
|
||||||
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
|
handleClickPrimary={handleAddInstance}
|
||||||
|
handleInputChange={handleNameChange}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
widths={{ input: 300 }}
|
||||||
|
/>
|
||||||
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource.dataList}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
loading={{
|
||||||
|
spinning: dataSource.loading,
|
||||||
|
size: 'middle'
|
||||||
|
}}
|
||||||
|
sortDirections={TABLE_SORT_DIRECTIONS}
|
||||||
|
showSorterTooltip={false}
|
||||||
|
rowKey="id"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
pagination={{
|
||||||
|
size: 'middle',
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSize: queryParams.perPage,
|
||||||
|
current: queryParams.page,
|
||||||
|
total: dataSource.total,
|
||||||
|
hideOnSinglePage: queryParams.perPage === 10,
|
||||||
|
onChange: handlePageChange
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
</PageBox>
|
||||||
|
<AddModal
|
||||||
|
open={openAddModalStatus.open}
|
||||||
|
action={openAddModalStatus.action}
|
||||||
|
title={openAddModalStatus.title}
|
||||||
|
data={openAddModalStatus.currentData}
|
||||||
|
onCancel={closeModal}
|
||||||
|
onOk={handleModalOk}
|
||||||
|
/>
|
||||||
|
<DeleteModal ref={modalRef} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GPUService;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import PageBox from '../../_components/page-box';
|
||||||
|
|
||||||
|
const GPUServiceStorage: React.FC = () => {
|
||||||
|
return <PageBox> GPU Service Storage Page</PageBox>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GPUServiceStorage;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import PageBox from '../../_components/page-box';
|
||||||
|
|
||||||
|
const GPUServiceTemplates: React.FC = () => {
|
||||||
|
return <PageBox> GPU Service Templates Page</PageBox>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GPUServiceTemplates;
|
||||||
Reference in New Issue
Block a user