diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx
index 8006675a..13e3dcbe 100644
--- a/react-ui/src/pages/AutoML/List/index.tsx
+++ b/react-ui/src/pages/AutoML/List/index.tsx
@@ -26,6 +26,7 @@ import {
ConfigProvider,
Input,
Table,
+ Tooltip,
type TablePaginationConfig,
type TableProps,
} from 'antd';
@@ -276,13 +277,18 @@ function AutoMLList() {
{newText && newText.length > 0
? newText.map((item, index) => {
return (
-
+ placement="top"
+ title={experimentStatusInfo[item as ExperimentStatus].label}
+ >
+
+
);
})
: null}
diff --git a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.less b/react-ui/src/pages/AutoML/components/ExperimentInstance/index.less
index 499b8424..3e7d2eec 100644
--- a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.less
+++ b/react-ui/src/pages/AutoML/components/ExperimentInstance/index.less
@@ -54,13 +54,9 @@
width: 200px;
.statusIcon {
- visibility: hidden;
- transition: all 0.2s;
+ visibility: visible;
}
}
- .statusBox:hover .statusIcon {
- visibility: visible;
- }
}
.loadMoreBox {
diff --git a/react-ui/src/pages/Experiment/components/ExperimentInstance/index.less b/react-ui/src/pages/Experiment/components/ExperimentInstance/index.less
index 31df6572..a5bc8eb3 100644
--- a/react-ui/src/pages/Experiment/components/ExperimentInstance/index.less
+++ b/react-ui/src/pages/Experiment/components/ExperimentInstance/index.less
@@ -57,13 +57,9 @@
width: 200px;
.statusIcon {
- visibility: hidden;
- transition: all 0.2s;
+ visibility: visible;
}
}
- .statusBox:hover .statusIcon {
- visibility: visible;
- }
}
.loadMoreBox {
diff --git a/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
index 754034aa..1171df18 100644
--- a/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
+++ b/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
@@ -98,6 +98,17 @@ function ExperimentInstanceComponent({
}
};
+ // 终止实验实例
+ const handleTerminate = (instance: ExperimentInstance) => {
+ modalConfirm({
+ title: '确定要终止这次实验运行吗?',
+ isDelete: false,
+ onOk: () => {
+ terminateExperimentInstance(instance);
+ },
+ });
+ };
+
// 终止实验实例
const terminateExperimentInstance = async (instance: ExperimentInstance) => {
const [res] = await to(putQueryByExperimentInsId(instance.id));
@@ -202,7 +213,7 @@ function ExperimentInstanceComponent({
item.status === ExperimentStatus.Terminated
}
icon={}
- onClick={() => terminateExperimentInstance(item)}
+ onClick={() => handleTerminate(item)}
>
终止
diff --git a/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.less b/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.less
index a5df71aa..67d76fc6 100644
--- a/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.less
+++ b/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.less
@@ -1,12 +1,3 @@
.experiment-status-cell {
height: 100%;
- &__label {
- display: none;
- }
-}
-
-.experiment-status-cell:hover {
- .experiment-status-cell__label {
- display: inline;
- }
}
diff --git a/react-ui/src/pages/Experiment/index.jsx b/react-ui/src/pages/Experiment/index.jsx
index 998fb436..373f8b54 100644
--- a/react-ui/src/pages/Experiment/index.jsx
+++ b/react-ui/src/pages/Experiment/index.jsx
@@ -1,5 +1,7 @@
import KFIcon from '@/components/KFIcon';
+import PageTitle from '@/components/PageTitle';
import { ExperimentStatus, TensorBoardStatus } from '@/enums';
+import { useCacheState } from '@/hooks/pageCacheState';
import {
deleteExperimentById,
getExperiment,
@@ -16,14 +18,14 @@ import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
-import { App, Button, ConfigProvider, Dropdown, Space, Table } from 'antd';
+import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd';
import classNames from 'classnames';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ComparisonType } from './Comparison/config';
import AddExperimentModal from './components/AddExperimentModal';
import ExperimentInstance from './components/ExperimentInstance';
-import Styles from './index.less';
+import styles from './index.less';
import { experimentStatusInfo } from './status';
// 定时器
@@ -47,32 +49,34 @@ function Experiment() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [addFormData, setAddFormData] = useState({});
const [experimentInsTotal, setExperimentInsTotal] = useState(0);
+ const [cacheState, setCacheState] = useCacheState();
+ const [searchText, setSearchText] = useState(cacheState?.searchText);
+ const [inputText, setInputText] = useState(cacheState?.searchText);
+ const [pagination, setPagination] = useState(
+ cacheState?.pagination ?? {
+ current: 1,
+ pageSize: 10,
+ },
+ );
const { message } = App.useApp();
- const pageOption = useRef({ page: 1, size: 10 });
- const paginationProps = {
- showSizeChanger: true,
- showQuickJumper: true,
- showTotal: () => `共${total}条`,
- total: total,
- page: pageOption.current.page,
- size: pageOption.current.size,
- onChange: (current, size) => paginationChange(current, size),
- };
useEffect(() => {
- getExperimentList();
getWorkflowList();
return () => {
clearExperimentInTimers();
};
}, []);
+ useEffect(() => {
+ getExperimentList();
+ }, [pagination, searchText]);
+
// 获取实验列表
const getExperimentList = async () => {
const params = {
- offset: 0,
- page: pageOption.current.page - 1,
- size: pageOption.current.size,
+ page: pagination.current - 1,
+ size: pagination.pageSize,
+ name: searchText || undefined,
};
const [res] = await to(getExperiment(params));
if (res && res.data && Array.isArray(res.data.content)) {
@@ -94,6 +98,15 @@ function Experiment() {
}
};
+ // 搜索
+ const onSearch = (value) => {
+ setSearchText(value);
+ setPagination((prev) => ({
+ ...prev,
+ current: 1,
+ }));
+ };
+
// 获取实验实例列表
const getQueryByExperiment = async (experimentId, page) => {
const params = {
@@ -259,11 +272,11 @@ function Experiment() {
};
// 当前页面切换
- const paginationChange = async (current, size) => {
- pageOption.current = {
- page: current,
- size: size,
- };
+ const paginationChange = async (current, pageSize) => {
+ setPagination({
+ current,
+ pageSize,
+ });
getExperimentList();
};
// 运行实验
@@ -278,14 +291,23 @@ function Experiment() {
}
};
+ // 跳转, 缓存当前状态
+ const navigateToUrl = (url) => {
+ setCacheState({
+ pagination,
+ searchText,
+ });
+ navigate(url);
+ };
+
// 跳转到流水线
const gotoPipeline = (record) => {
- navigate({ pathname: `/pipeline/template/info/${record.workflow_id}` });
+ navigateToUrl(`/pipeline/template/info/${record.workflow_id}`);
};
// 跳转到实验实例详情
const gotoInstanceInfo = (item, record) => {
- navigate({ pathname: `/pipeline/experiment/instance/${record.workflow_id}/${item.id}` });
+ navigateToUrl(`/pipeline/experiment/instance/${record.workflow_id}/${item.id}`);
};
// 处理 TensorBoard 操作
@@ -340,7 +362,7 @@ function Experiment() {
},
],
onClick: ({ key }) => {
- navigate(`/pipeline/experiment/compare?type=${key}&id=${experimentId}`);
+ navigateToUrl(`/pipeline/experiment/compare?type=${key}&id=${experimentId}`);
},
};
};
@@ -392,13 +414,14 @@ function Experiment() {
{newText && newText.length > 0
? newText.map((item, index) => {
return (
-
+
+
+
);
})
: null}
@@ -479,41 +502,61 @@ function Experiment() {
},
];
return (
-
-
- }>
- 新建实验
-
-
-
-
(
- gotoInstanceInfo(item, record)}
- onClickTensorBoard={handleTensorboard}
- onRemove={() => {
- refreshExperimentIns(record.id);
- refreshExperimentList();
- }}
- onTerminate={handleInstanceTerminate}
- onLoadMore={() => loadMoreExperimentIns()}
- >
- ),
- onExpand: (e, a) => {
- expandChange(e, a);
- },
- expandedRowKeys: [expandedRowKeys],
- rowExpandable: (record) => true,
- }}
- />
+
+
+
+
+ setInputText(e.target.value)}
+ style={{ width: 300 }}
+ value={inputText}
+ allowClear
+ />
+ }>
+ 新建实验
+
+
+
+
`共${total}条`,
+ onChange: paginationChange,
+ }}
+ rowKey="id"
+ scroll={{ y: 'calc(100% - 55px)' }}
+ expandable={{
+ expandedRowRender: (record) => (
+ gotoInstanceInfo(item, record)}
+ onClickTensorBoard={handleTensorboard}
+ onRemove={() => {
+ refreshExperimentIns(record.id);
+ refreshExperimentList();
+ }}
+ onTerminate={handleInstanceTerminate}
+ onLoadMore={() => loadMoreExperimentIns()}
+ >
+ ),
+ onExpand: (e, a) => {
+ expandChange(e, a);
+ },
+ expandedRowKeys: [expandedRowKeys],
+ rowExpandable: (record) => true,
+ }}
+ />
+
{isModalOpen && (
diff --git a/react-ui/src/pages/Experiment/index.less b/react-ui/src/pages/Experiment/index.less
index b13e1145..0791397a 100644
--- a/react-ui/src/pages/Experiment/index.less
+++ b/react-ui/src/pages/Experiment/index.less
@@ -1,28 +1,21 @@
-.experimentBox {
+.experiment-list {
height: 100%;
- .experimentTopBox {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- width: 100%;
- height: 49px;
- margin-bottom: 10px;
- padding-right: 30px;
- background-image: url(@/assets/img/page-title-bg.png);
- background-repeat: no-repeat;
- background-position: top center;
- background-size: 100% 100%;
- }
- .experimentTable {
+ &__content {
height: calc(100% - 60px);
- :global {
- .ant-table-wrapper .ant-table {
- // overflow-y: auto;
- height: calc(100% - 48px);
- }
- .ant-table-row-expand-icon-cell {
- padding: 0 30px;
- }
+ margin-top: 10px;
+ padding: 20px 30px 0;
+ background-color: white;
+ border-radius: 10px;
+
+ &__filter {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ &__table {
+ height: calc(100% - 32px - 28px);
+ margin-top: 28px;
}
}
}
diff --git a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
index 9b9780bd..a7740e7b 100644
--- a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
+++ b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
@@ -163,6 +163,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
showValue: res.code_repo_name,
fromSelect: true,
});
+ form.validateFields([formItemName]);
}
close();
},
@@ -278,6 +279,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
// 参数回填
const handleParameterClick = (name: NamePath, value: any) => {
form.setFieldValue(name, value);
+ form.validateFields([name]);
};
// form item label
diff --git a/react-ui/src/pages/Pipeline/index.jsx b/react-ui/src/pages/Pipeline/index.jsx
index 48f58e6d..2ba1b963 100644
--- a/react-ui/src/pages/Pipeline/index.jsx
+++ b/react-ui/src/pages/Pipeline/index.jsx
@@ -1,5 +1,7 @@
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
+import PageTitle from '@/components/PageTitle';
+import { useCacheState } from '@/hooks/pageCacheState';
import {
addWorkflow,
cloneWorkflow,
@@ -13,9 +15,9 @@ import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd';
import classNames from 'classnames';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
-import Styles from './index.less';
+import styles from './index.less';
const { TextArea } = Input;
const Pipeline = () => {
@@ -26,7 +28,46 @@ const Pipeline = () => {
const [pipeList, setPipeList] = useState([]);
const [total, setTotal] = useState(0);
const [isModalOpen, setIsModalOpen] = useState(false);
+ const [cacheState, setCacheState] = useCacheState();
+ const [searchText, setSearchText] = useState(cacheState?.searchText);
+ const [inputText, setInputText] = useState(cacheState?.searchText);
+ const [pagination, setPagination] = useState(
+ cacheState?.pagination ?? {
+ current: 1,
+ pageSize: 10,
+ },
+ );
const { message } = App.useApp();
+
+ useEffect(() => {
+ getList();
+ }, [pagination, searchText]);
+
+ // 获取流水线模板列表
+ const getList = () => {
+ const params = {
+ page: pagination.current - 1,
+ size: pagination.pageSize,
+ name: searchText || undefined,
+ };
+ getWorkflow(params).then((res) => {
+ if (res && res.data && Array.isArray(res.data.content)) {
+ setPipeList(res.data.content);
+ setTotal(res.data.totalElements);
+ }
+ });
+ };
+
+ // 搜索
+ const onSearch = (value) => {
+ setSearchText(value);
+ setPagination((prev) => ({
+ ...prev,
+ current: 1,
+ }));
+ };
+
+ // 编辑
const editTable = (e, record) => {
e.stopPropagation();
getWorkflowById(record.id).then((ret) => {
@@ -39,18 +80,35 @@ const Pipeline = () => {
}
});
};
- const routeToEdit = (record) => {
- navigate({ pathname: `/pipeline/template/info/${record.id}` });
+
+ // 跳转, 缓存当前状态
+ const navigateToUrl = (url) => {
+ setCacheState({
+ pagination,
+ searchText,
+ });
+ navigate(url);
+ };
+
+ // 查看详情
+ const gotoDetail = (record) => {
+ navigateToUrl(`/pipeline/template/info/${record.id}`);
};
+
+ // 显示 modal
const showModal = () => {
form.resetFields();
setFormId(null);
setDialogTitle('新建流水线');
setIsModalOpen(true);
};
+
+ // modal 取消
const handleCancel = () => {
setIsModalOpen(false);
};
+
+ // 表单提交
const onFinish = (values) => {
if (formId) {
editWorkflow({ ...values, id: formId }).then((ret) => {
@@ -63,47 +121,21 @@ const Pipeline = () => {
setIsModalOpen(false);
message.success('新建成功');
if (ret.code === 200) {
- navigate({ pathname: `/pipeline/template/info/${ret.data.id}` });
+ navigateToUrl(`/pipeline/template/info/${ret.data.id}`);
}
});
}
};
- const pageOption = useRef({ page: 1, size: 10 });
- const paginationProps = {
- showSizeChanger: true,
- showQuickJumper: true,
- showTotal: () => `共${total}条`,
- total: total,
- page: pageOption.current.page,
- size: pageOption.current.size,
- onChange: (current, size) => paginationChange(current, size),
- };
+
// 当前页面切换
- const paginationChange = async (current, size) => {
- // console.log('page', current, size);
- pageOption.current = {
- page: current,
- size: size,
- };
+ const paginationChange = async (current, pageSize) => {
+ setPagination({
+ current,
+ pageSize,
+ });
getList();
};
- const getList = () => {
- let params = {
- offset: 1,
- page: pageOption.current.page - 1,
- size: pageOption.current.size,
- };
- getWorkflow(params).then((ret) => {
- if (ret.code === 200) {
- setPipeList(ret.data.content);
- setTotal(ret.data.totalElements);
- }
- });
- };
- useEffect(() => {
- getList();
- }, []);
const columns = [
{
title: '序号',
@@ -112,8 +144,8 @@ const Pipeline = () => {
width: 120,
align: 'center',
render: tableCellRender(false, TableCellValueType.Index, {
- page: pageOption.current.page - 1,
- pageSize: pageOption.current.size,
+ page: pagination.current - 1,
+ pageSize: pagination.pageSize,
}),
},
{
@@ -121,7 +153,7 @@ const Pipeline = () => {
dataIndex: 'name',
key: 'name',
render: tableCellRender(false, TableCellValueType.Link, {
- onClick: routeToEdit,
+ onClick: gotoDetail,
}),
},
{
@@ -167,10 +199,10 @@ const Pipeline = () => {
icon={}
onClick={async () => {
modalConfirm({
- title: '复制',
- content: '确定复制该条流水线吗?',
+ title: '确定复制该条流水线吗?',
okText: '确认',
cancelText: '取消',
+ isDelete: false,
onOk: () => {
cloneWorkflow(record.id).then((ret) => {
if (ret.code === 200) {
@@ -235,27 +267,47 @@ const Pipeline = () => {
},
];
return (
-
-
- }>
- 新建流水线
-
-
-
-
+
+
+
+
+ setInputText(e.target.value)}
+ style={{ width: 300 }}
+ value={inputText}
+ allowClear
+ />
+ }>
+ 新建流水线
+
+
+
+
`共${total}条`,
+ onChange: paginationChange,
+ }}
+ rowKey="id"
+ scroll={{ y: 'calc(100% - 55px)' }}
+ />
+