refactor: gpu service API alignment
This commit is contained in:
@@ -1,14 +1,8 @@
|
||||
import { currentClusterAtom } from '@/atoms/gpuservice';
|
||||
import { getCurrentOrgNamespace } from '@/atoms/user';
|
||||
import { PageAction } from '@/config';
|
||||
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
||||
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
||||
import { handleBatchRequest } from '@/utils';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
BaseSelect,
|
||||
DeleteModal,
|
||||
DropdownButtons,
|
||||
FilterBar,
|
||||
@@ -17,26 +11,13 @@ import {
|
||||
} from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import {
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Divider,
|
||||
Flex,
|
||||
message,
|
||||
Modal,
|
||||
Space,
|
||||
Table
|
||||
} from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import { Button, ConfigProvider, message, Modal, Space, Table } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { PageContainerInner } from '../../_components/page-box';
|
||||
import PageBox from '../../_components/page-box';
|
||||
import {
|
||||
deleteGPUServiceInstance,
|
||||
GPU_SERVICE_INSTANCES_API,
|
||||
queryGPUServiceInstances,
|
||||
startGPUServiceInstance,
|
||||
stopGPUServiceInstance
|
||||
queryGPUServiceInstances
|
||||
} from './apis';
|
||||
import AddModal from './components/add-modal';
|
||||
import ViewEventsModal from './components/view-events-modal';
|
||||
@@ -52,72 +33,7 @@ import useUpdateInstance from './services/use-update-instance';
|
||||
|
||||
const GPUService: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const [modal, modalContextHolder] = Modal.useModal();
|
||||
const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom);
|
||||
const clusterID = currentCluster?.id;
|
||||
// In admin "All" view there's no Org context, so the helper falls
|
||||
// back to the selected cluster's owner Org name.
|
||||
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
|
||||
|
||||
const deleteInstance = useCallback(
|
||||
(id: number) => deleteGPUServiceInstance({ namespace, clusterID, id }),
|
||||
[namespace, clusterID]
|
||||
);
|
||||
|
||||
const startInstance = useCallback(
|
||||
(row: ListItem) =>
|
||||
startGPUServiceInstance({
|
||||
namespace: row.metadata?.namespace || namespace,
|
||||
clusterID,
|
||||
name: row.metadata?.name as string
|
||||
}),
|
||||
[namespace, clusterID]
|
||||
);
|
||||
|
||||
const stopInstance = useCallback(
|
||||
(row: ListItem) =>
|
||||
stopGPUServiceInstance({
|
||||
namespace: row.metadata?.namespace || namespace,
|
||||
clusterID,
|
||||
name: row.metadata?.name as string
|
||||
}),
|
||||
[namespace, clusterID]
|
||||
);
|
||||
|
||||
const fetchInstances = useMemoizedFn(
|
||||
async (
|
||||
params: any,
|
||||
options?: any
|
||||
): Promise<Global.PageResponse<ListItem>> => {
|
||||
const effectiveClusterID = params.cluster_id ?? clusterID;
|
||||
if (!effectiveClusterID) {
|
||||
return {
|
||||
items: [],
|
||||
pagination: {
|
||||
total: 0,
|
||||
totalPage: 0,
|
||||
page: 1,
|
||||
perPage: params.perPage || 10
|
||||
}
|
||||
} as Global.PageResponse<ListItem>;
|
||||
}
|
||||
const res = await queryGPUServiceInstances(
|
||||
{ ...params, namespace, clusterID: effectiveClusterID },
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
const [, modalContextHolder] = Modal.useModal();
|
||||
|
||||
const {
|
||||
dataSource,
|
||||
@@ -130,16 +46,14 @@ const GPUService: React.FC = () => {
|
||||
fetchData,
|
||||
handlePageChange,
|
||||
handleTableChange,
|
||||
handleQueryChange,
|
||||
handleSearch,
|
||||
handleNameChange
|
||||
} = useTableFetch<ListItem>({
|
||||
key: PaginationKey.Instances,
|
||||
fetchAPI: fetchInstances,
|
||||
deleteAPI: deleteInstance,
|
||||
watch: false,
|
||||
polling: true,
|
||||
API: GPU_SERVICE_INSTANCES_API({ namespace, clusterID }),
|
||||
fetchAPI: queryGPUServiceInstances,
|
||||
deleteAPI: deleteGPUServiceInstance,
|
||||
watch: true,
|
||||
API: GPU_SERVICE_INSTANCES_API,
|
||||
contentForDelete: intl.formatMessage({ id: 'gpuservice.instance' })
|
||||
});
|
||||
|
||||
@@ -160,55 +74,11 @@ const GPUService: React.FC = () => {
|
||||
closeViewEventsModal,
|
||||
openViewEventsModalStatus
|
||||
} = useViewEvents();
|
||||
const {
|
||||
fetchClusterList,
|
||||
cancelRequest: cancelClusterRequest,
|
||||
clusterList
|
||||
} = useQueryClusterList();
|
||||
|
||||
const k8sClusterList = useMemo(
|
||||
() =>
|
||||
clusterList.filter(
|
||||
(item) => item.provider === ProviderValueMap.Kubernetes
|
||||
),
|
||||
[clusterList]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchClusterList({ page: -1 }).then((clusters) => {
|
||||
const k8sClusters = clusters.filter(
|
||||
(item: any) => item.provider === ProviderValueMap.Kubernetes
|
||||
);
|
||||
if (k8sClusters.length === 0) {
|
||||
return;
|
||||
}
|
||||
const storedCluster = k8sClusters.find(
|
||||
(item: any) => item.id === currentCluster?.id
|
||||
);
|
||||
const targetCluster = storedCluster ?? k8sClusters[0];
|
||||
if (!storedCluster) {
|
||||
setCurrentCluster({
|
||||
...targetCluster,
|
||||
label: targetCluster.name,
|
||||
value: targetCluster.id
|
||||
});
|
||||
}
|
||||
handleQueryChange({
|
||||
cluster_id: targetCluster.id,
|
||||
page: 1
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
cancelClusterRequest();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleModalOk = async (data: FormData) => {
|
||||
try {
|
||||
if (openInstanceModalStatus.realAction === PageAction.CREATE) {
|
||||
await deleteInstance(
|
||||
openInstanceModalStatus.currentData!.metadata?.name as any
|
||||
);
|
||||
await deleteGPUServiceInstance(openInstanceModalStatus.currentData!.id);
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
@@ -226,161 +96,34 @@ const GPUService: React.FC = () => {
|
||||
closeInstanceModal();
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
} catch (error) {
|
||||
message.error(intl.formatMessage({ id: 'common.message.fail' }));
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecreate = useMemoizedFn((row: ListItem) => {
|
||||
modalRef.current?.show({
|
||||
title: intl.formatMessage({
|
||||
id: 'gpuservice.instance.recreate.confirm.title'
|
||||
}),
|
||||
content: 'gpuservice.instance.recreate.confirm.content',
|
||||
okText: 'common.button.recreate',
|
||||
operation: 'gpuservice.instance.recreate.confirm.content',
|
||||
name: row.metadata?.name,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteInstance(row.metadata?.name as any);
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
await createInstance({
|
||||
data: {
|
||||
metadata: { name: row.metadata.name, namespace },
|
||||
spec: row.spec
|
||||
} as FormData
|
||||
});
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
handleSearch();
|
||||
} catch (error) {
|
||||
message.error(intl.formatMessage({ id: 'common.message.fail' }));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleStart = useMemoizedFn(async (row: ListItem) => {
|
||||
modalRef.current?.show({
|
||||
title: intl.formatMessage({ id: 'common.title.start.confirm' }),
|
||||
content: 'gpuservice.instance',
|
||||
okText: 'common.button.start',
|
||||
operation: 'common.start.single.confirm',
|
||||
name: row.metadata?.name,
|
||||
async onOk() {
|
||||
try {
|
||||
await startInstance(row);
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleStop = useMemoizedFn(async (row: ListItem) => {
|
||||
modalRef.current?.show({
|
||||
title: intl.formatMessage({ id: 'common.title.stop.confirm' }),
|
||||
content: 'gpuservice.instance',
|
||||
okText: 'common.button.stop',
|
||||
operation: 'common.stop.single.confirm',
|
||||
name: row.metadata?.name,
|
||||
async onOk() {
|
||||
try {
|
||||
await stopInstance(row);
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleStartBatch = useMemoizedFn(() => {
|
||||
modalRef.current?.show({
|
||||
title: intl.formatMessage({ id: 'common.title.start.confirm' }),
|
||||
content: 'gpuservice.instance',
|
||||
okText: 'common.button.start',
|
||||
operation: 'common.start.confirm',
|
||||
selection: true,
|
||||
async onOk() {
|
||||
const res = await handleBatchRequest(
|
||||
rowSelection.selectedRows as ListItem[],
|
||||
startInstance
|
||||
);
|
||||
fetchData();
|
||||
return res;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleStopBatch = useMemoizedFn(() => {
|
||||
modalRef.current?.show({
|
||||
title: intl.formatMessage({ id: 'common.title.stop.confirm' }),
|
||||
content: 'gpuservice.instance',
|
||||
okText: 'common.button.stop',
|
||||
operation: 'common.stop.confirm',
|
||||
selection: true,
|
||||
async onOk() {
|
||||
const res = await handleBatchRequest(
|
||||
rowSelection.selectedRows as ListItem[],
|
||||
stopInstance
|
||||
);
|
||||
fetchData();
|
||||
return res;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleSelect = useMemoizedFn((val: string, row: ListItem) => {
|
||||
if (val === 'view') {
|
||||
openViewInstanceModal(row);
|
||||
} else if (val === 'edit') {
|
||||
openEditInstanceModal(row);
|
||||
} else if (val === 'delete') {
|
||||
handleDelete({
|
||||
...row,
|
||||
name: row.metadata?.name,
|
||||
id: row.metadata?.name as any
|
||||
});
|
||||
handleDelete({ ...row, name: row.name });
|
||||
} else if (val === 'recreate') {
|
||||
// Keep handleRecreate above for the old confirm-modal flow. The current
|
||||
// UX opens the editable form and recreates after submit.
|
||||
openRecreateInstanceModal(row);
|
||||
} else if (val === 'viewlog') {
|
||||
openViewLogsModal(row);
|
||||
} else if (val === 'viewevent') {
|
||||
openViewEventsModal(row);
|
||||
} else if (val === 'start') {
|
||||
handleStart(row);
|
||||
} else if (val === 'stop') {
|
||||
handleStop(row);
|
||||
}
|
||||
// start / stop are disabled until backend endpoints exist
|
||||
});
|
||||
|
||||
const handleBatchActionSelect = useMemoizedFn((val: string) => {
|
||||
if (val === 'start') {
|
||||
handleStartBatch();
|
||||
} else if (val === 'stop') {
|
||||
handleStopBatch();
|
||||
} else if (val === 'delete') {
|
||||
if (val === 'delete') {
|
||||
handleDeleteBatch();
|
||||
}
|
||||
// start / stop are disabled until backend endpoints exist
|
||||
});
|
||||
|
||||
const handleClusterChange = (value: number) => {
|
||||
const cluster = k8sClusterList.find((item) => item.value === value);
|
||||
if (cluster) {
|
||||
setCurrentCluster(cluster);
|
||||
}
|
||||
handleQueryChange({
|
||||
cluster_id: value,
|
||||
page: 1
|
||||
});
|
||||
};
|
||||
|
||||
const renderEmpty = (type?: string) => {
|
||||
if (type !== 'Table') return;
|
||||
return (
|
||||
@@ -389,7 +132,7 @@ const GPUService: React.FC = () => {
|
||||
loadend={dataSource.loadend}
|
||||
dataSource={dataSource.dataList}
|
||||
image={<IconFont type="icon-cloud-outlined" />}
|
||||
filters={_.pick(queryParams, ['search', 'manufacturer'])}
|
||||
filters={_.pick(queryParams, ['search'])}
|
||||
noFoundText={intl.formatMessage({
|
||||
id: 'noresult.gpuservice.instance.nofound'
|
||||
})}
|
||||
@@ -411,80 +154,64 @@ const GPUService: React.FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainerInner
|
||||
leftContent={
|
||||
<Flex align="center">
|
||||
<span>{intl.formatMessage({ id: 'menu.gpuService.instances' })}</span>
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
style={{
|
||||
marginLeft: 16
|
||||
<>
|
||||
<PageBox>
|
||||
<FilterBar
|
||||
marginBottom={22}
|
||||
marginTop={30}
|
||||
showSelect={false}
|
||||
handleSearch={handleSearch}
|
||||
handleInputChange={handleNameChange}
|
||||
rowSelection={rowSelection}
|
||||
widths={{ input: 300 }}
|
||||
right={
|
||||
<Space size={16}>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={openCreateInstanceModal}
|
||||
>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.add' })}
|
||||
</Button>
|
||||
<DropdownButtons
|
||||
items={batchActionList}
|
||||
extra={
|
||||
rowSelection.selectedRowKeys.length > 0 && (
|
||||
<span>({rowSelection.selectedRowKeys.length})</span>
|
||||
)
|
||||
}
|
||||
size="large"
|
||||
showText={true}
|
||||
disabled={!rowSelection.selectedRowKeys.length}
|
||||
onSelect={handleBatchActionSelect}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
<ConfigProvider renderEmpty={renderEmpty}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={dataSource.dataList}
|
||||
rowSelection={rowSelection}
|
||||
loading={{
|
||||
spinning: dataSource.loading,
|
||||
size: 'middle'
|
||||
}}
|
||||
sortDirections={TABLE_SORT_DIRECTIONS}
|
||||
showSorterTooltip={false}
|
||||
rowKey={(record) => record.id}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
pageSize: queryParams.perPage,
|
||||
current: queryParams.page,
|
||||
total: dataSource.total,
|
||||
hideOnSinglePage: queryParams.perPage === 10,
|
||||
onChange: handlePageChange
|
||||
}}
|
||||
/>
|
||||
<BaseSelect
|
||||
variant="borderless"
|
||||
value={queryParams.cluster_id}
|
||||
options={k8sClusterList}
|
||||
onChange={handleClusterChange}
|
||||
style={{ minWidth: 120, fontWeight: 500 }}
|
||||
></BaseSelect>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<FilterBar
|
||||
marginBottom={22}
|
||||
marginTop={30}
|
||||
showSelect={false}
|
||||
selectOptions={k8sClusterList}
|
||||
select={{ showSearch: true }}
|
||||
selectHolder={intl.formatMessage({
|
||||
id: 'gpuservice.instance.filter.cluster'
|
||||
})}
|
||||
handleSearch={handleSearch}
|
||||
handleSelectChange={handleClusterChange}
|
||||
handleInputChange={handleNameChange}
|
||||
rowSelection={rowSelection}
|
||||
widths={{ input: 300 }}
|
||||
right={
|
||||
<Space size={16}>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={openCreateInstanceModal}
|
||||
>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.add' })}
|
||||
</Button>
|
||||
<DropdownButtons
|
||||
items={batchActionList}
|
||||
extra={
|
||||
rowSelection.selectedRowKeys.length > 0 && (
|
||||
<span>({rowSelection.selectedRowKeys.length})</span>
|
||||
)
|
||||
}
|
||||
size="large"
|
||||
showText={true}
|
||||
disabled={!rowSelection.selectedRowKeys.length}
|
||||
onSelect={handleBatchActionSelect}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
<ConfigProvider renderEmpty={renderEmpty}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={dataSource.dataList}
|
||||
rowSelection={rowSelection}
|
||||
loading={{
|
||||
spinning: dataSource.loading,
|
||||
size: 'middle'
|
||||
}}
|
||||
sortDirections={TABLE_SORT_DIRECTIONS}
|
||||
showSorterTooltip={false}
|
||||
rowKey={(record) => record.metadata.name}
|
||||
onChange={handleTableChange}
|
||||
pagination={false}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</ConfigProvider>
|
||||
</PageBox>
|
||||
<AddModal
|
||||
open={openInstanceModalStatus.open}
|
||||
action={openInstanceModalStatus.action}
|
||||
@@ -510,7 +237,7 @@ const GPUService: React.FC = () => {
|
||||
/>
|
||||
<DeleteModal ref={modalRef} />
|
||||
{modalContextHolder}
|
||||
</PageContainerInner>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user