feat: benchmark detail actions

This commit is contained in:
jialin
2026-02-01 22:56:36 +08:00
parent 0849a2f114
commit 9796233897
12 changed files with 268 additions and 103 deletions
@@ -10,6 +10,7 @@ type AddModalProps = {
action: PageActionType; action: PageActionType;
open: boolean; open: boolean;
currentData?: ListItem; // Used when action is EDIT currentData?: ListItem; // Used when action is EDIT
clusterList?: Global.BaseOption<number>[];
onOk: (values: FormData) => void; onOk: (values: FormData) => void;
onCancel: () => void; onCancel: () => void;
}; };
@@ -18,6 +19,7 @@ const AddBenchmark: React.FC<AddModalProps> = ({
action, action,
open, open,
currentData, currentData,
clusterList,
onOk, onOk,
onCancel onCancel
}) => { }) => {
@@ -51,6 +53,7 @@ const AddBenchmark: React.FC<AddModalProps> = ({
action={action} action={action}
open={open} open={open}
currentData={currentData} currentData={currentData}
clusterList={clusterList}
onFinish={handleOk} onFinish={handleOk}
/> />
</FormDrawer> </FormDrawer>
@@ -1,27 +1,18 @@
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
import { useIntl, useSearchParams } from '@umijs/max'; import { useIntl, useSearchParams } from '@umijs/max';
import { Tabs, TabsProps } from 'antd'; import { Tabs, TabsProps } from 'antd';
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import DetailContext from '../config/detail-context';
import { BenchmarkListItem } from '../config/types';
import useQueryDetail from '../services/use-query-detail';
import Environment from './environment'; import Environment from './environment';
import Summary from './summary'; import Summary from './summary';
const Details: React.FC<{ currentData?: BenchmarkListItem }> = ({ const Details: React.FC<{
currentData tabBarExtraContent: {
}) => { right: React.ReactNode;
};
}> = ({ tabBarExtraContent }) => {
const intl = useIntl(); const intl = useIntl();
const [searchParams] = useSearchParams();
const id = searchParams.get('id');
const { loading, detailData, cancelRequest, fetchData } = useQueryDetail();
const {
clusterList,
fetchClusterList,
cancelRequest: cancelClusterRequest
} = useQueryClusterList();
const [activeKey, setActiveKey] = useState('summary'); const [activeKey, setActiveKey] = useState('summary');
const [searchParams] = useSearchParams();
const items: TabsProps['items'] = [ const items: TabsProps['items'] = [
{ {
@@ -36,45 +27,21 @@ const Details: React.FC<{ currentData?: BenchmarkListItem }> = ({
children: <Environment />, children: <Environment />,
icon: <IconFont type="icon-server02" /> icon: <IconFont type="icon-server02" />
} }
// {
// key: 'logs',
// label: intl.formatMessage({ id: 'benchmark.detail.logs.title' }),
// children: <Logs />,
// icon: <IconFont type="icon-logs" />
// }
]; ];
const handleChangeTab = (key: string) => { const handleChangeTab = (key: string) => {
setActiveKey(key); setActiveKey(key);
}; };
useEffect(() => {
if (id) {
fetchData(id);
fetchClusterList({ page: -1 });
} else {
cancelRequest();
cancelClusterRequest();
}
}, [id]);
return ( return (
<DetailContext.Provider <Tabs
value={{ size="small"
detailData: detailData || {}, activeKey={activeKey}
clusterList: clusterList, onChange={handleChangeTab}
loading: loading, items={items}
id: Number(id) type="card"
}} tabBarExtraContent={tabBarExtraContent}
> />
<Tabs
size="small"
activeKey={activeKey}
onChange={handleChangeTab}
items={items}
type="card"
/>
</DetailContext.Provider>
); );
}; };
@@ -0,0 +1,14 @@
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation-name: fade-in;
animation-fill-mode: both;
}
@@ -0,0 +1,29 @@
import React, { PropsWithChildren } from 'react';
import './fade-in.less';
type FadeInProps = PropsWithChildren<{
duration?: number;
delay?: number;
className?: string;
}>;
const FadeIn: React.FC<FadeInProps> = ({
children,
duration = 200,
delay = 0,
className
}) => {
return (
<div
className={`fade-in ${className || ''}`}
style={{
animationDuration: `${duration}ms`,
animationDelay: `${delay}ms`
}}
>
{children}
</div>
);
};
export default FadeIn;
@@ -53,15 +53,18 @@ const actionList = [
interface RowActionsProps { interface RowActionsProps {
record: ListItem; record: ListItem;
page?: 'detail' | 'list';
handleSelect: (key: string, record: ListItem) => void; handleSelect: (key: string, record: ListItem) => void;
} }
const RowActions: React.FC<RowActionsProps> = (props) => { const RowActions: React.FC<RowActionsProps> = (props) => {
const { record, handleSelect } = props; const { record, handleSelect, page = 'list' } = props;
const { onDownloadLog, contextHolder } = useDownloadLogs(); const { onDownloadLog, contextHolder } = useDownloadLogs();
const actions = actionList.filter((action) => { const actions = actionList.filter((action) => {
if (action.status && action.status.length > 0) { if (page === 'detail' && action.key === 'edit') {
return false;
} else if (action.status && action.status.length > 0) {
return action.status?.includes(record.state); return action.status?.includes(record.state);
} }
@@ -1,14 +1,9 @@
import AutoTooltip from '@/components/auto-tooltip'; import AutoTooltip from '@/components/auto-tooltip';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Descriptions, Flex, Tag } from 'antd'; import { Descriptions, Flex, Tag } from 'antd';
import _ from 'lodash';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
const calcTotalVram = (vram: Record<string, number>) => {
return _.sum(_.values(vram));
};
const Instance: React.FC = () => { const Instance: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { detailData } = useDetailContext(); const { detailData } = useDetailContext();
+128 -3
View File
@@ -1,14 +1,45 @@
import DeleteModal from '@/components/delete-modal';
import BaseSelect from '@/components/seal-form/base/select';
import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
import React, { useEffect } from 'react'; import { useMemoizedFn } from 'ahooks';
import React, { useEffect, useRef } from 'react';
import { PageContainerInner } from '../_components/page-box'; import { PageContainerInner } from '../_components/page-box';
import PageBreadcrumb from '../_components/page-breadcrumb'; import PageBreadcrumb from '../_components/page-breadcrumb';
import { deleteBenchmark } from './apis';
import DetailContent from './components/detail-content'; import DetailContent from './components/detail-content';
import FadeIn from './components/fade-in';
import RowActions from './components/row-actions';
import ViewLogsModal from './components/view-logs-modal';
import DetailContext from './config/detail-context';
import { BenchmarkListItem } from './config/types';
import useViewLogs from './hooks/use-view-logs';
import useQueryBenchmarkList from './services/use-query-benchmarks';
import useQueryDetail from './services/use-query-detail';
import useStopBenchmark from './services/use-stop-benchmark';
const Details: React.FC = () => { const Details: React.FC = () => {
const modalRef = useRef<any>(null);
const navigate = useNavigate(); const navigate = useNavigate();
const intl = useIntl(); const intl = useIntl();
const {
dataList: benchmarkList,
fetchData: fetchBenchmarkList,
cancelRequest: cancelBenchmarkRequest
} = useQueryBenchmarkList();
const { loading, detailData, cancelRequest, fetchData } = useQueryDetail();
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
useViewLogs();
const { handleStopBenchmark } = useStopBenchmark();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const name = searchParams.get('name'); const name = searchParams.get('name');
const id = searchParams.get('id');
const handleOnChange = (value: number, option: any) => {
navigate(
`/models/benchmark/detail?id=${option.value}&name=${option.label}`,
{ state: 'benchmark-details', replace: true }
);
};
const breadcrumbItems = [ const breadcrumbItems = [
{ {
@@ -16,20 +47,114 @@ const Details: React.FC = () => {
onClick: () => navigate(-1) onClick: () => navigate(-1)
}, },
{ {
title: name title: (
<BaseSelect
size="small"
variant="borderless"
options={benchmarkList}
value={name}
style={{ minWidth: 100 }}
popupMatchSelectWidth={false}
onChange={handleOnChange}
></BaseSelect>
)
} }
]; ];
const handleDelete = (row: BenchmarkListItem) => {
modalRef.current?.show({
content: 'benchmark.title',
operation: 'common.delete.single.confirm',
name: row.name,
async onOk() {
await deleteBenchmark(row.id);
fetchBenchmarkList({ page: -1 }).then((items) => {
if (items.length === 0) {
navigate(-1);
} else {
handleOnChange(items[0].id, {
value: items[0].id,
label: items[0].name
});
}
});
}
});
};
const handleSelect = useMemoizedFn((val: any, row: BenchmarkListItem) => {
if (val === 'delete') {
handleDelete({ ...row, name: row.name });
} else if (val === 'viewlog') {
openViewLogsModal(row);
} else if (val === 'stop') {
handleStopBenchmark(row.id);
}
});
useEffect(() => { useEffect(() => {
document.title = `${intl.formatMessage({ id: 'benchmark.title' })} - ${name}`; document.title = `${intl.formatMessage({ id: 'benchmark.title' })} - ${name}`;
}, [name, intl]); }, [name, intl]);
useEffect(() => {
fetchBenchmarkList({ page: -1 });
return () => {
cancelBenchmarkRequest();
};
}, []);
useEffect(() => {
if (id) {
fetchData(id);
} else {
cancelRequest();
}
}, [id]);
return ( return (
<PageContainerInner <PageContainerInner
header={{ header={{
title: <PageBreadcrumb items={breadcrumbItems} /> title: <PageBreadcrumb items={breadcrumbItems} />
}} }}
> >
<DetailContent></DetailContent> <DetailContext.Provider
value={{
detailData: detailData || {},
clusterList: [],
loading: loading,
id: Number(id)
}}
>
<DetailContent
tabBarExtraContent={{
right: (
<FadeIn>
<div
style={{
opacity: loading ? 0 : 1,
position: 'relative',
bottom: -2,
right: -1
}}
>
<RowActions
page="detail"
record={detailData as any}
handleSelect={handleSelect}
></RowActions>
</div>
</FadeIn>
)
}}
></DetailContent>
</DetailContext.Provider>
<ViewLogsModal
open={openViewLogsModalStatus.open}
url={openViewLogsModalStatus.url}
tail={openViewLogsModalStatus.tail}
onCancel={closeViewLogsModal}
></ViewLogsModal>
<DeleteModal ref={modalRef}></DeleteModal>
</PageContainerInner> </PageContainerInner>
); );
}; };
+8 -20
View File
@@ -3,7 +3,6 @@ import SealSelect from '@/components/seal-form/seal-select';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { ClusterStatusValueMap } from '@/pages/cluster-management/config'; import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
@@ -15,13 +14,7 @@ const BasicForm: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const { action, open } = useFormContext(); const { action, open, clusterList } = useFormContext();
const {
loading: clusterLoading,
fetchClusterList,
cancelRequest: cancelClusterRequest,
clusterList
} = useQueryClusterList();
useEffect(() => { useEffect(() => {
const initClusterId = (list: any[]) => { const initClusterId = (list: any[]) => {
@@ -37,18 +30,14 @@ const BasicForm: React.FC = () => {
return cluster_id; return cluster_id;
}; };
fetchClusterList({ page: -1 }).then((list) => { if (
if (list.length > 0 && action === PageAction.CREATE) { clusterList &&
form.setFieldValue('cluster_id', initClusterId(list)); clusterList?.length > 0 &&
} action === PageAction.CREATE
}); ) {
}, [form, action]); form.setFieldValue('cluster_id', initClusterId(clusterList));
useEffect(() => {
if (!open) {
cancelClusterRequest();
} }
}, [open]); }, [form, action, clusterList]);
return ( return (
<> <>
@@ -78,7 +67,6 @@ const BasicForm: React.FC = () => {
> >
<SealSelect <SealSelect
disabled={action === PageAction.EDIT} disabled={action === PageAction.EDIT}
loading={clusterLoading}
options={clusterList} options={clusterList}
label={intl.formatMessage({ id: 'clusters.title' })} label={intl.formatMessage({ id: 'clusters.title' })}
required required
+4 -2
View File
@@ -23,6 +23,7 @@ interface ProviderFormProps {
action: PageActionType; action: PageActionType;
currentData?: ListItem; // Used when action is EDIT currentData?: ListItem; // Used when action is EDIT
open?: boolean; open?: boolean;
clusterList?: Global.BaseOption<number>[];
onFinish: (values: FormData) => Promise<void>; onFinish: (values: FormData) => Promise<void>;
} }
@@ -33,7 +34,7 @@ const TABKeysMap = {
}; };
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => { const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, currentData, onFinish, open } = props; const { action, currentData, onFinish, open, clusterList } = props;
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
const profile = Form.useWatch('profile', form); const profile = Form.useWatch('profile', form);
@@ -109,7 +110,8 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
<FormContext.Provider <FormContext.Provider
value={{ value={{
action, action,
open open,
clusterList: clusterList
}} }}
> >
<Form <Form
@@ -29,9 +29,12 @@ const defaultColumns: string[] = [
]; ];
const fixedColumns: string[] = []; const fixedColumns: string[] = [];
const useColumnSettings = (options: { contentHeight: number }) => { const useColumnSettings = (options: {
contentHeight: number;
clusterList: Global.BaseOption<number>[];
}) => {
const intl = useIntl(); const intl = useIntl();
const { contentHeight } = options; const { contentHeight, clusterList } = options;
const [selectedColumns, setSelectedColumns] = const [selectedColumns, setSelectedColumns] =
React.useState<string[]>(defaultColumns); React.useState<string[]>(defaultColumns);
@@ -241,22 +244,22 @@ const useColumnSettings = (options: { contentHeight: number }) => {
title: renderTitle(intl.formatMessage({ id: 'clusters.title' })), title: renderTitle(intl.formatMessage({ id: 'clusters.title' })),
pos: 1, pos: 1,
dataIndex: 'cluster_id', dataIndex: 'cluster_id',
render: (text: string) => ( render: (text: number) => (
<AutoTooltip ghost minWidth={20}> <AutoTooltip ghost minWidth={20}>
{text} {clusterList?.find((item) => item.value === text)?.label || text}
</AutoTooltip>
)
},
{
title: renderTitle(intl.formatMessage({ id: 'resources.worker' })),
pos: 2,
dataIndex: 'worker_id',
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip> </AutoTooltip>
) )
}, },
// {
// title: renderTitle(intl.formatMessage({ id: 'resources.worker' })),
// pos: 2,
// dataIndex: 'worker_id',
// render: (text: string) => (
// <AutoTooltip ghost minWidth={20}>
// {text}
// </AutoTooltip>
// )
// },
{ {
title: renderTitle( title: renderTitle(
intl.formatMessage({ id: 'benchmark.detail.modelName' }) intl.formatMessage({ id: 'benchmark.detail.modelName' })
@@ -364,9 +367,8 @@ const useColumnSettings = (options: { contentHeight: number }) => {
); );
// Sort by pos // Sort by pos
selected.sort((a, b) => (a.pos || 0) - (b.pos || 0)); selected.sort((a, b) => (a.pos || 0) - (b.pos || 0));
console.log('columns===', selected);
return selected; return selected;
}, [selectedColumns]); }, [selectedColumns, clusterList]);
const SettingsButton = ( const SettingsButton = (
<ColumnSettings <ColumnSettings
+19 -7
View File
@@ -12,6 +12,7 @@ import _ from 'lodash';
import { useEffect } from 'react'; import { useEffect } from 'react';
import NoResult from '../_components/no-result'; import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box'; import PageBox from '../_components/page-box';
import { useQueryClusterList } from '../cluster-management/services/use-query-cluster-list';
import { import {
BENCHMARKS_API, BENCHMARKS_API,
createBenchmark, createBenchmark,
@@ -63,17 +64,26 @@ const Benchmark: React.FC = () => {
}); });
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } = const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
useViewLogs(); useViewLogs();
const { SettingsButton, columns: selectedColumns } = useColumnSettings({
contentHeight: 320
});
const { handleStopBenchmark } = useStopBenchmark(); const { handleStopBenchmark } = useStopBenchmark();
const { datasetList, fetchDatasetData } = useQueryDataset(); const { datasetList, fetchDatasetData } = useQueryDataset();
const { exportData } = useExportBenchmark(); const { exportData } = useExportBenchmark();
const {
fetchClusterList,
cancelRequest: cancelClusterRequest,
clusterList
} = useQueryClusterList();
const { SettingsButton, columns: selectedColumns } = useColumnSettings({
contentHeight: 320,
clusterList
});
useEffect(() => { useEffect(() => {
fetchModelList({ page: -1 }); fetchModelList({ page: -1 });
fetchClusterList({ page: -1 });
fetchDatasetData(); fetchDatasetData();
return () => {
cancelClusterRequest();
};
}, []); }, []);
const handleAddBenchmark = () => { const handleAddBenchmark = () => {
@@ -110,7 +120,7 @@ const Benchmark: React.FC = () => {
closeBenchmarkModal(); closeBenchmarkModal();
}; };
const handleEditUser = (row: ListItem) => { const handleEdit = (row: ListItem) => {
openBenchmarkModal( openBenchmarkModal(
PageAction.EDIT, PageAction.EDIT,
intl.formatMessage({ id: 'benchmark.button.edit' }), intl.formatMessage({ id: 'benchmark.button.edit' }),
@@ -120,7 +130,7 @@ const Benchmark: React.FC = () => {
const handleSelect = useMemoizedFn((val: any, row: ListItem) => { const handleSelect = useMemoizedFn((val: any, row: ListItem) => {
if (val === 'edit') { if (val === 'edit') {
handleEditUser(row); handleEdit(row);
} else if (val === 'delete') { } else if (val === 'delete') {
handleDelete({ ...row, name: row.name }); handleDelete({ ...row, name: row.name });
} else if (val === 'viewlog') { } else if (val === 'viewlog') {
@@ -134,7 +144,8 @@ const Benchmark: React.FC = () => {
(record: ListItem, dataIndex: string) => { (record: ListItem, dataIndex: string) => {
if (dataIndex === 'name') { if (dataIndex === 'name') {
navigate( navigate(
`/models/benchmark/detail?id=${record.id}&name=${record.name}` `/models/benchmark/detail?id=${record.id}&name=${record.name}`,
{ state: 'benchmark-details' }
); );
} }
} }
@@ -230,6 +241,7 @@ const Benchmark: React.FC = () => {
</ConfigProvider> </ConfigProvider>
</PageBox> </PageBox>
<AddBenchmarkModal <AddBenchmarkModal
clusterList={clusterList}
open={openBenchmarkModalStatus.open} open={openBenchmarkModalStatus.open}
action={openBenchmarkModalStatus.action} action={openBenchmarkModalStatus.action}
title={openBenchmarkModalStatus.title} title={openBenchmarkModalStatus.title}
@@ -0,0 +1,25 @@
import { useQueryDataList } from '@/hooks/use-query-data-list';
import { queryBenchmarkList } from '../apis';
import { BenchmarkListItem as ListItem } from '../config/types';
export const useQueryBenchmarkList = (optons?: {
getLabel?: (item: ListItem) => string;
getValue?: (item: ListItem) => any;
}) => {
const { dataList, loading, fetchData, cancelRequest } = useQueryDataList<
ListItem,
Global.SearchParams
>({
key: 'benchmarkList',
fetchList: queryBenchmarkList
});
return {
dataList,
loading,
fetchData,
cancelRequest
};
};
export default useQueryBenchmarkList;