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)' }} + /> +