feat: templates, storage
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { request } from '@umijs/max';
|
||||
import { mockStorageData } from '../config/mock-data';
|
||||
import { FormData, ListItem } from '../config/types';
|
||||
|
||||
export const GPU_SERVICE_STORAGE_API = '/gpu-service-storage';
|
||||
|
||||
export async function queryGPUServiceStorage(
|
||||
params: Global.SearchParams,
|
||||
options?: any
|
||||
) {
|
||||
// return request<Global.PageResponse<ListItem>>(GPU_SERVICE_STORAGE_API, {
|
||||
// method: 'GET',
|
||||
// params,
|
||||
// cancelToken: options?.token
|
||||
// });
|
||||
const page = params.page || 1;
|
||||
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 }) {
|
||||
// return request<ListItem>(GPU_SERVICE_STORAGE_API, {
|
||||
// method: 'POST',
|
||||
// data: params.data
|
||||
// });
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function updateGPUServiceStorage(params: {
|
||||
id: number;
|
||||
data: FormData;
|
||||
}) {
|
||||
// return request<ListItem>(`${GPU_SERVICE_STORAGE_API}/${params.id}`, {
|
||||
// method: 'PUT',
|
||||
// data: params.data
|
||||
// });
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function deleteGPUServiceStorage(id: number) {
|
||||
return request(`${GPU_SERVICE_STORAGE_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 GPUServiceStorageForm 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'
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<GPUServiceStorageForm
|
||||
ref={form}
|
||||
action={action}
|
||||
currentData={data}
|
||||
onFinish={onFinish}
|
||||
open={open}
|
||||
/>
|
||||
</FormDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddModal;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { StatusMaps } from '@/config';
|
||||
import { StatusType } from '@/config/types';
|
||||
import { icons } from '@gpustack/core-ui';
|
||||
|
||||
export const StorageTypeValueMap = {
|
||||
Local: 'local',
|
||||
Shared: 'shared',
|
||||
Object: 'object'
|
||||
};
|
||||
|
||||
export const StorageTypeLabelMap: Record<string, string> = {
|
||||
[StorageTypeValueMap.Local]: '默认存储',
|
||||
[StorageTypeValueMap.Shared]: '默认存储',
|
||||
[StorageTypeValueMap.Object]: '对象存储'
|
||||
};
|
||||
|
||||
export const StorageAccessModeValueMap = {
|
||||
ReadWriteOnce: 'ReadWriteOnce',
|
||||
ReadOnlyMany: 'ReadOnlyMany',
|
||||
ReadWriteMany: 'ReadWriteMany'
|
||||
};
|
||||
|
||||
export const StorageAccessModeOptions = [
|
||||
{
|
||||
label: 'ReadWriteOnce',
|
||||
value: StorageAccessModeValueMap.ReadWriteOnce
|
||||
},
|
||||
{
|
||||
label: 'ReadWriteMany',
|
||||
value: StorageAccessModeValueMap.ReadWriteMany
|
||||
},
|
||||
{
|
||||
label: 'ReadOnlyMany',
|
||||
value: StorageAccessModeValueMap.ReadOnlyMany
|
||||
}
|
||||
];
|
||||
|
||||
export const StorageStatusValueMap = {
|
||||
Available: 'Available',
|
||||
Bound: 'Bound',
|
||||
Released: 'Released',
|
||||
Failed: 'Failed'
|
||||
};
|
||||
|
||||
export const StorageStatusLabelMap: Record<string, string> = {
|
||||
[StorageStatusValueMap.Available]: 'Available',
|
||||
[StorageStatusValueMap.Bound]: 'Bound',
|
||||
[StorageStatusValueMap.Released]: 'Released',
|
||||
[StorageStatusValueMap.Failed]: 'Failed'
|
||||
};
|
||||
|
||||
export const status: Record<string, StatusType> = {
|
||||
[StorageStatusValueMap.Available]: StatusMaps.success,
|
||||
[StorageStatusValueMap.Bound]: StatusMaps.transitioning,
|
||||
[StorageStatusValueMap.Released]: StatusMaps.inactive,
|
||||
[StorageStatusValueMap.Failed]: StatusMaps.error
|
||||
};
|
||||
|
||||
export const rowActionList = [
|
||||
{
|
||||
label: '编辑',
|
||||
key: 'edit',
|
||||
icon: icons.EditOutlined
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
key: 'delete',
|
||||
icon: icons.DeleteOutlined,
|
||||
props: {
|
||||
danger: true
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,69 @@
|
||||
import { StorageStatusValueMap, StorageTypeValueMap } from '.';
|
||||
import { ListItem } from './types';
|
||||
|
||||
export const mockStorageData: ListItem[] = [
|
||||
{
|
||||
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,
|
||||
description: 'Local NVMe cache for hot model files',
|
||||
created_at: '2026-04-01T10:00:00Z',
|
||||
updated_at: '2026-04-10T10:00:00Z'
|
||||
},
|
||||
{
|
||||
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,
|
||||
description: 'Shared storage for model weights',
|
||||
created_at: '2026-04-02T10:00:00Z',
|
||||
updated_at: '2026-04-11T10:00:00Z'
|
||||
},
|
||||
{
|
||||
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,
|
||||
description: 'Object storage mounted for datasets',
|
||||
created_at: '2026-04-03T10:00:00Z',
|
||||
updated_at: '2026-04-12T10:00:00Z'
|
||||
},
|
||||
{
|
||||
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,
|
||||
description: 'Shared cache waiting for storage backend recovery',
|
||||
created_at: '2026-04-04T10:00:00Z',
|
||||
updated_at: '2026-04-13T10:00:00Z'
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface FormData {
|
||||
name: string;
|
||||
description?: string;
|
||||
type?: string;
|
||||
capacity_gb?: number;
|
||||
mount_path?: string;
|
||||
access_modes?: string[];
|
||||
parameters?: Record<string, any>;
|
||||
status?: string;
|
||||
cluster_id?: number;
|
||||
}
|
||||
|
||||
export interface ListItem extends FormData {
|
||||
id: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
Input as CInput,
|
||||
InputNumber as CInputNumber,
|
||||
Select as SealSelect
|
||||
} from '@gpustack/core-ui';
|
||||
import { Form } from 'antd';
|
||||
import { StorageAccessModeOptions } from '../config';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
const Basic = () => {
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const parameters = Form.useWatch('parameters', form);
|
||||
|
||||
const handleParametersChange = (value: Record<string, any>) => {
|
||||
form.setFieldValue('parameters', value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
name="name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入存储名称'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CInput.Input label="名称" required />
|
||||
</Form.Item>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Form.Item<FormData>
|
||||
name="type"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择存储类型'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealSelect label="存储类型" required options={[]}></SealSelect>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Form.Item<FormData>
|
||||
name="capacity_gb"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入容量'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CInputNumber min={1} precision={0} label="容量 (GB)" required />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
<Form.Item<FormData> name="access_modes">
|
||||
<SealSelect
|
||||
label="存储卷访问方式"
|
||||
allowClear
|
||||
options={StorageAccessModeOptions}
|
||||
></SealSelect>
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Basic;
|
||||
@@ -0,0 +1,71 @@
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { Form } from 'antd';
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||
import { StorageStatusValueMap } from '../config';
|
||||
import { FormData, ListItem } from '../config/types';
|
||||
import Basic from './basic';
|
||||
|
||||
interface StorageFormProps {
|
||||
ref?: any;
|
||||
open: boolean;
|
||||
action: PageActionType;
|
||||
currentData?: ListItem | null;
|
||||
onFinish: (values: FormData) => Promise<void>;
|
||||
}
|
||||
|
||||
const GPUServiceStorageForm: React.FC<StorageFormProps> = 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,
|
||||
type: currentData.type,
|
||||
capacity_gb: currentData.capacity_gb,
|
||||
mount_path: currentData.mount_path,
|
||||
access_modes: currentData.access_modes,
|
||||
parameters: currentData.parameters,
|
||||
status: currentData.status
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
type: '默认存储',
|
||||
parameters: {},
|
||||
status: StorageStatusValueMap.Available
|
||||
});
|
||||
}, [action, currentData, form, open]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submit: () => {
|
||||
form.submit();
|
||||
},
|
||||
resetFields: () => {
|
||||
form.resetFields();
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<Form
|
||||
name="gpuServiceStorageForm"
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
preserve={false}
|
||||
>
|
||||
<Basic />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default GPUServiceStorageForm;
|
||||
@@ -0,0 +1,113 @@
|
||||
import { tableSorter } from '@/config/settings';
|
||||
import { AutoTooltip, DropdownButtons, StatusTag } from '@gpustack/core-ui';
|
||||
import type { ColumnsType } from 'antd/lib/table';
|
||||
import dayjs from 'dayjs';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
rowActionList,
|
||||
status,
|
||||
StorageStatusLabelMap,
|
||||
StorageStatusValueMap,
|
||||
StorageTypeLabelMap
|
||||
} from '../config';
|
||||
import { ListItem } from '../config/types';
|
||||
|
||||
interface ColumnsHookProps {
|
||||
handleSelect: (val: string, record: ListItem) => void;
|
||||
sortOrder: string[];
|
||||
}
|
||||
|
||||
const useStorageColumns = ({
|
||||
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: 'type',
|
||||
key: 'type',
|
||||
sorter: tableSorter(2),
|
||||
render: (value: string) => StorageTypeLabelMap[value] || value || '-'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
sorter: tableSorter(3),
|
||||
render: (statusValue: string) => {
|
||||
const value = statusValue || StorageStatusValueMap.Available;
|
||||
return (
|
||||
<StatusTag
|
||||
statusValue={{
|
||||
status: status[value],
|
||||
text: StorageStatusLabelMap[value] || value
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '容量 (GB)',
|
||||
dataIndex: 'capacity_gb',
|
||||
key: 'capacity_gb',
|
||||
sorter: tableSorter(4),
|
||||
render: (value: number) => value ?? '-'
|
||||
},
|
||||
// {
|
||||
// title: '挂载路径',
|
||||
// dataIndex: 'mount_path',
|
||||
// key: 'mount_path',
|
||||
// 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 useStorageColumns;
|
||||
@@ -1,7 +1,246 @@
|
||||
import PageBox from '../../_components/page-box';
|
||||
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 { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
||||
import {
|
||||
BaseSelect,
|
||||
DeleteModal,
|
||||
FilterBar,
|
||||
IconFont,
|
||||
NoResult
|
||||
} from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { ConfigProvider, Divider, Flex, message, Table } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PageContainerInner } from '../../_components/page-box';
|
||||
import {
|
||||
createGPUServiceStorage,
|
||||
deleteGPUServiceStorage,
|
||||
GPU_SERVICE_STORAGE_API,
|
||||
queryGPUServiceStorage,
|
||||
updateGPUServiceStorage
|
||||
} from './apis';
|
||||
import AddModal from './components/add-modal';
|
||||
import { FormData, ListItem } from './config/types';
|
||||
import useStorageColumns from './hooks/use-storage-columns';
|
||||
|
||||
const GPUServiceStorage: React.FC = () => {
|
||||
return <PageBox> GPU Service Storage Page</PageBox>;
|
||||
const {
|
||||
dataSource,
|
||||
rowSelection,
|
||||
queryParams,
|
||||
sortOrder,
|
||||
modalRef,
|
||||
handleDelete,
|
||||
handleDeleteBatch,
|
||||
fetchData,
|
||||
handlePageChange,
|
||||
handleTableChange,
|
||||
handleQueryChange,
|
||||
handleSearch,
|
||||
handleNameChange
|
||||
} = useTableFetch<ListItem>({
|
||||
key: PaginationKey.Storage,
|
||||
fetchAPI: queryGPUServiceStorage,
|
||||
deleteAPI: deleteGPUServiceStorage,
|
||||
watch: false,
|
||||
API: GPU_SERVICE_STORAGE_API,
|
||||
contentForDelete: '存储'
|
||||
});
|
||||
const {
|
||||
fetchClusterList,
|
||||
cancelRequest: cancelClusterRequest,
|
||||
clusterList
|
||||
} = useQueryClusterList();
|
||||
const intl = useIntl();
|
||||
|
||||
const [openAddModalStatus, setOpenAddModalStatus] = useState<{
|
||||
action: PageActionType;
|
||||
open: boolean;
|
||||
title: string;
|
||||
currentData?: ListItem | null;
|
||||
}>({
|
||||
action: PageAction.CREATE,
|
||||
title: '',
|
||||
open: false,
|
||||
currentData: null
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchClusterList({ page: -1 }).then((clusters) => {
|
||||
if (clusters.length > 0) {
|
||||
handleQueryChange({
|
||||
cluster_id: clusters[0].id,
|
||||
page: 1
|
||||
});
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelClusterRequest();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleAddStorage = () => {
|
||||
setOpenAddModalStatus({
|
||||
action: PageAction.CREATE,
|
||||
title: '添加存储',
|
||||
open: true,
|
||||
currentData: null
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditStorage = (row: ListItem) => {
|
||||
setOpenAddModalStatus({
|
||||
action: PageAction.EDIT,
|
||||
title: '编辑存储',
|
||||
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 updateGPUServiceStorage({
|
||||
id: openAddModalStatus.currentData!.id,
|
||||
data
|
||||
});
|
||||
} else {
|
||||
await createGPUServiceStorage({ data });
|
||||
}
|
||||
|
||||
fetchData();
|
||||
closeModal();
|
||||
message.success('操作成功');
|
||||
} catch (error) {
|
||||
message.error('操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = useMemoizedFn((val: string, row: ListItem) => {
|
||||
if (val === 'edit') {
|
||||
handleEditStorage(row);
|
||||
} else if (val === 'delete') {
|
||||
handleDelete({ ...row, name: row.name });
|
||||
}
|
||||
});
|
||||
|
||||
const handleClusterChange = (value: number) => {
|
||||
handleQueryChange({
|
||||
cluster_id: value,
|
||||
page: 1
|
||||
});
|
||||
};
|
||||
|
||||
const renderEmpty = (type?: string) => {
|
||||
if (type !== 'Table') return;
|
||||
return (
|
||||
<NoResult
|
||||
loading={dataSource.loading}
|
||||
loadend={dataSource.loadend}
|
||||
dataSource={dataSource.dataList}
|
||||
image={<IconFont type="icon-storage-outlined" />}
|
||||
filters={_.omit(queryParams, ['sort_by'])}
|
||||
noFoundText="未找到匹配的存储"
|
||||
title="暂无存储"
|
||||
subTitle="创建一个存储后会显示在这里"
|
||||
onClick={handleAddStorage}
|
||||
buttonText="立即添加"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const columns = useStorageColumns({
|
||||
handleSelect,
|
||||
sortOrder
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainerInner
|
||||
leftContent={
|
||||
<Flex align="center">
|
||||
<span>{intl.formatMessage({ id: 'menu.gpuService.storage' })}</span>
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
style={{
|
||||
marginLeft: 16
|
||||
}}
|
||||
/>
|
||||
<BaseSelect
|
||||
variant="borderless"
|
||||
value={queryParams.cluster_id}
|
||||
options={clusterList}
|
||||
style={{ minWidth: 120, fontWeight: 500 }}
|
||||
onChange={handleClusterChange}
|
||||
></BaseSelect>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<FilterBar
|
||||
marginBottom={22}
|
||||
marginTop={30}
|
||||
showSelect={false}
|
||||
selectOptions={clusterList}
|
||||
select={{ showSearch: true }}
|
||||
selectHolder="按集群过滤"
|
||||
buttonText="添加存储"
|
||||
handleSearch={handleSearch}
|
||||
handleSelectChange={handleClusterChange}
|
||||
handleDeleteByBatch={handleDeleteBatch}
|
||||
handleClickPrimary={handleAddStorage}
|
||||
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>
|
||||
</PageContainerInner>
|
||||
<AddModal
|
||||
open={openAddModalStatus.open}
|
||||
action={openAddModalStatus.action}
|
||||
title={openAddModalStatus.title}
|
||||
data={openAddModalStatus.currentData}
|
||||
onCancel={closeModal}
|
||||
onOk={handleModalOk}
|
||||
/>
|
||||
<DeleteModal ref={modalRef} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GPUServiceStorage;
|
||||
|
||||
Reference in New Issue
Block a user