| @@ -5,7 +5,7 @@ | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| margin: 20px 0; | |||||
| margin: 20px 0 30px; | |||||
| background-color: transparent; | background-color: transparent; | ||||
| } | } | ||||
| .ant-modal-footer { | .ant-modal-footer { | ||||
| @@ -3,6 +3,7 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| color: @kf-primary-color; | color: @kf-primary-color; | ||||
| font-weight: 400; | |||||
| font-size: 20px; | font-size: 20px; | ||||
| &_image { | &_image { | ||||
| @@ -31,6 +31,12 @@ body { | |||||
| -webkit-font-smoothing: antialiased; | -webkit-font-smoothing: antialiased; | ||||
| -moz-osx-font-smoothing: grayscale; | -moz-osx-font-smoothing: grayscale; | ||||
| } | } | ||||
| a{ | |||||
| color:#1664ff; | |||||
| } | |||||
| .ant-btn-link{ | |||||
| color: #1664ff; | |||||
| } | |||||
| .ant-pro-layout .ant-pro-layout-content { | .ant-pro-layout .ant-pro-layout-content { | ||||
| padding: 10px; | padding: 10px; | ||||
| } | } | ||||
| @@ -46,6 +52,10 @@ body { | |||||
| .ant-menu-light .ant-menu-item-selected { | .ant-menu-light .ant-menu-item-selected { | ||||
| background: rgba(197, 232, 255, 0.8) !important; | background: rgba(197, 232, 255, 0.8) !important; | ||||
| } | } | ||||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children{ | |||||
| background:#f2f5f7; | |||||
| } | |||||
| .ant-pro-base-menu-inline { | .ant-pro-base-menu-inline { | ||||
| // height: 87vh; | // height: 87vh; | ||||
| background: #f2f5f7; | background: #f2f5f7; | ||||
| @@ -54,7 +64,14 @@ body { | |||||
| .ant-pro-layout .ant-pro-layout-content { | .ant-pro-layout .ant-pro-layout-content { | ||||
| background-color: transparent; | background-color: transparent; | ||||
| } | } | ||||
| .ant-table-wrapper .ant-table-pagination.ant-pagination{ | |||||
| background-color: #fff; | |||||
| margin: 0; | |||||
| padding: 21px 16px; | |||||
| } | |||||
| .ant-table-wrapper .ant-table{ | |||||
| height: 75vh; | |||||
| } | |||||
| .ant-pro-global-header-logo img { | .ant-pro-global-header-logo img { | ||||
| height: 21px; | height: 21px; | ||||
| } | } | ||||
| @@ -64,15 +81,38 @@ body { | |||||
| .ant-pro-layout .ant-pro-layout-container { | .ant-pro-layout .ant-pro-layout-container { | ||||
| height: 98vh; | height: 98vh; | ||||
| } | } | ||||
| .ant-modal .ant-modal-close-x{ | |||||
| border: 2px solid #272536; | |||||
| border-radius: 50%; | |||||
| width: 26px; | |||||
| height: 26px; | |||||
| color: #272536; | |||||
| } | |||||
| .ant-modal-content{ | |||||
| margin-left: -130px; | |||||
| margin-top: 50px; | |||||
| } | |||||
| .ant-modal .ant-modal-close:hover { | |||||
| background-color: transparent; | |||||
| } | |||||
| .ant-modal .ant-modal-footer >.ant-btn+.ant-btn{ | |||||
| margin-left: 20px; | |||||
| } | |||||
| .ant-pagination .ant-pagination-item-active a { | .ant-pagination .ant-pagination-item-active a { | ||||
| color: #fff; | color: #fff; | ||||
| background: #1664ff; | background: #1664ff; | ||||
| border-color: #1664ff; | border-color: #1664ff; | ||||
| border-radius:6px; | |||||
| } | } | ||||
| .ant-pagination .ant-pagination-item-active a:hover { | |||||
| .ant-pagination .ant-pagination-item-active:hover { | |||||
| color: #fff; | color: #fff; | ||||
| background: rgba(22, 100, 255, 0.8); | background: rgba(22, 100, 255, 0.8); | ||||
| border-color: rgba(22, 100, 255, 0.8); | border-color: rgba(22, 100, 255, 0.8); | ||||
| border-radius:6px; | |||||
| } | |||||
| .ant-pagination .ant-pagination-item { | |||||
| border: 1px solid #e6e6e6; | |||||
| border-radius:6px; | |||||
| } | } | ||||
| // ::-webkit-scrollbar-button { | // ::-webkit-scrollbar-button { | ||||
| // background: #97a1bd; | // background: #97a1bd; | ||||
| @@ -37,3 +37,27 @@ export function useAntdModal(initialValue: boolean) { | |||||
| return [visible, open, close]; | return [visible, open, close]; | ||||
| } | } | ||||
| type Callback<T> = (state: T) => void; | |||||
| /** | |||||
| * Generates a stateful value and a function to update it that triggers callbacks. | |||||
| * | |||||
| * @param initialValue - The initial value of the state. | |||||
| * @return A tuple containing the current state value and a function to update the state. | |||||
| */ | |||||
| export function useCallbackState<T>(initialValue: T) { | |||||
| const [state, _setState] = useState(initialValue); | |||||
| const callbackQueue = useRef<Callback<T>[]>([]); | |||||
| useEffect(() => { | |||||
| callbackQueue.current.forEach((cb) => cb(state)); | |||||
| callbackQueue.current = []; | |||||
| }, [state]); | |||||
| const setState = (newValue: T, callback: Callback<T>) => { | |||||
| _setState(newValue); | |||||
| if (callback && typeof callback === 'function') { | |||||
| callbackQueue.current.push(callback); | |||||
| } | |||||
| }; | |||||
| return [state, setState]; | |||||
| } | |||||
| @@ -39,9 +39,9 @@ const Dataset = () => { | |||||
| return { | return { | ||||
| ...form.getFieldsValue(), | ...form.getFieldsValue(), | ||||
| dataset_id: locationParams.id, | dataset_id: locationParams.id, | ||||
| file_name: item.response.data[0].fileName, | |||||
| file_size: item.response.data[0].fileSize, | |||||
| url: item.response.data[0].url, | |||||
| file_name: item.response.code === 200 ? item.response.data[0].fileName : null, | |||||
| file_size: item.response.code === 200 ? item.response.data[0].fileSize : null, | |||||
| url: item.response.code === 200 ? item.response.data[0].url : null, | |||||
| }; | }; | ||||
| }), | }), | ||||
| ); | ); | ||||
| @@ -1,6 +1,6 @@ | |||||
| import { getDatasetList } from '@/services/dataset/index.js'; | import { getDatasetList } from '@/services/dataset/index.js'; | ||||
| import { Form, Input, Tabs } from 'antd'; | import { Form, Input, Tabs } from 'antd'; | ||||
| import React, { useEffect, useState } from 'react'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| import PersonalData from './personalData'; | import PersonalData from './personalData'; | ||||
| @@ -9,7 +9,7 @@ const { Search } = Input; | |||||
| const { TabPane } = Tabs; | const { TabPane } = Tabs; | ||||
| const leftdataList = [1, 2, 3]; | const leftdataList = [1, 2, 3]; | ||||
| const Dataset = (React.FC = () => { | |||||
| const Dataset = () => { | |||||
| const [queryFlow, setQueryFlow] = useState({ | const [queryFlow, setQueryFlow] = useState({ | ||||
| page: 0, | page: 0, | ||||
| size: 10, | size: 10, | ||||
| @@ -52,7 +52,7 @@ const Dataset = (React.FC = () => { | |||||
| console.log('Failed:', errorInfo); | console.log('Failed:', errorInfo); | ||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getDatasetlist(); | |||||
| //getDatasetlist(); | |||||
| return () => {}; | return () => {}; | ||||
| }, []); | }, []); | ||||
| return ( | return ( | ||||
| @@ -70,5 +70,5 @@ const Dataset = (React.FC = () => { | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| }); | |||||
| }; | |||||
| export default Dataset; | export default Dataset; | ||||
| @@ -263,7 +263,10 @@ | |||||
| .ant-modal-content { | .ant-modal-content { | ||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background: linear-gradient(180deg, #cfdfff 0%, #d4e2ff 9.77%, #ffffff 40%, #ffffff 100%); | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-position: top center; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -51,6 +51,7 @@ const PublicData = (React.FC = () => { | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | const [dialogTitle, setDialogTitle] = useState('新建数据'); | ||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const getDatasetlist = (queryFlow) => { | const getDatasetlist = (queryFlow) => { | ||||
| getDatasetList(queryFlow).then((ret) => { | getDatasetList(queryFlow).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| @@ -64,6 +65,7 @@ const PublicData = (React.FC = () => { | |||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| setDialogTitle('新建数据集'); | setDialogTitle('新建数据集'); | ||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| const getAssetIconList = (params) => { | const getAssetIconList = (params) => { | ||||
| @@ -311,30 +313,27 @@ const PublicData = (React.FC = () => { | |||||
| <Form.Item | <Form.Item | ||||
| label="数据名称" | label="数据名称" | ||||
| name="name" | name="name" | ||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据名称e!', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input placeholder="请输入数据名称" /> | |||||
| <Input placeholder="请输入数据名称" showCount maxLength={64} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="数据集版本" | label="数据集版本" | ||||
| name="version" | name="version" | ||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据集版本!', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input placeholder="请输入数据集版本" /> | |||||
| <Input placeholder="请输入数据集版本" showCount maxLength={64} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="数据集分类" | label="数据集分类" | ||||
| @@ -391,7 +390,7 @@ const PublicData = (React.FC = () => { | |||||
| ] | ] | ||||
| } | } | ||||
| > | > | ||||
| <Input placeholder="请输入数据简介" /> | |||||
| <Input placeholder="请输入数据简介" showCount maxLength={256} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="选择流水线" name="range"> | <Form.Item label="选择流水线" name="range"> | ||||
| <Radio.Group> | <Radio.Group> | ||||
| @@ -400,7 +399,7 @@ const PublicData = (React.FC = () => { | |||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="数据文件" name="dataset_version_vos"> | <Form.Item label="数据文件" name="dataset_version_vos"> | ||||
| <Upload {...props}> | |||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button | <Button | ||||
| style={{ | style={{ | ||||
| fontSize: '14px', | fontSize: '14px', | ||||
| @@ -0,0 +1,26 @@ | |||||
| .tensorBoard-status { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: rgba(29, 29, 32, 0.75); | |||||
| &__label { | |||||
| color: rgba(29, 29, 32, 0.75); | |||||
| font-size: 15px; | |||||
| &--running { | |||||
| color: #6ac21d; | |||||
| } | |||||
| &--failed { | |||||
| color: #df6d6d; | |||||
| } | |||||
| } | |||||
| &__icon { | |||||
| width: 14px; | |||||
| color: #6ac21d; | |||||
| cursor: pointer; | |||||
| & + & { | |||||
| margin-left: 6px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,82 @@ | |||||
| import exportImg from '@/assets/img/tensor-board-export.png'; | |||||
| import pendingImg from '@/assets/img/tensor-board-pending.png'; | |||||
| import { LoadingOutlined } from '@ant-design/icons'; | |||||
| import classNames from 'classnames'; | |||||
| import styles from './index.less'; | |||||
| // import stopImg from '@/assets/img/tensor-board-stop.png'; | |||||
| import terminatedImg from '@/assets/img/tensor-board-terminated.png'; | |||||
| export enum TensorBoardStatusEnum { | |||||
| Unknown = 'Unknown', // 未知 | |||||
| Pending = 'Pending', // 启动中 | |||||
| Running = 'Running', // 运行中 | |||||
| Terminated = 'Terminated', // 未启动或者已终止 | |||||
| Failed = 'Failed', // 失败 | |||||
| } | |||||
| const statusConfig = { | |||||
| Unknown: { | |||||
| label: '未知', | |||||
| icon: terminatedImg, | |||||
| classname: '', | |||||
| }, | |||||
| Terminated: { | |||||
| label: '未启动', | |||||
| icon: terminatedImg, | |||||
| classname: '', | |||||
| }, | |||||
| Failed: { | |||||
| label: '失败', | |||||
| icon: terminatedImg, | |||||
| classname: 'tensorBoard-status__label--failed', | |||||
| }, | |||||
| Pending: { | |||||
| label: '启动中', | |||||
| icon: pendingImg, | |||||
| classname: '', | |||||
| }, | |||||
| Running: { | |||||
| label: '运行中', | |||||
| icon: exportImg, | |||||
| classname: 'tensorBoard-status__label--running', | |||||
| }, | |||||
| }; | |||||
| type TensorBoardStatusProps = { | |||||
| status: TensorBoardStatusEnum; | |||||
| onClick: () => void; | |||||
| }; | |||||
| function TensorBoardStatus({ | |||||
| status = TensorBoardStatusEnum.Unknown, | |||||
| onClick, | |||||
| }: TensorBoardStatusProps) { | |||||
| return ( | |||||
| <div className={styles['tensorBoard-status']}> | |||||
| <div | |||||
| className={classNames( | |||||
| styles['tensorBoard-status__label'], | |||||
| styles[statusConfig[status].classname], | |||||
| )} | |||||
| > | |||||
| {statusConfig[status].label} | |||||
| </div> | |||||
| {statusConfig[status].icon ? ( | |||||
| <> | |||||
| <div style={{ margin: '0 6px' }}>|</div> | |||||
| {status === TensorBoardStatusEnum.Pending ? ( | |||||
| <LoadingOutlined className={styles['tensorBoard-status__icon']} /> | |||||
| ) : ( | |||||
| <img | |||||
| className={styles['tensorBoard-status__icon']} | |||||
| src={statusConfig[status].icon} | |||||
| onClick={onClick} | |||||
| /> | |||||
| )} | |||||
| </> | |||||
| ) : null} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default TensorBoardStatus; | |||||
| @@ -1,6 +1,6 @@ | |||||
| .params_container { | .params_container { | ||||
| max-height: 230px; | max-height: 230px; | ||||
| padding: 15px; | |||||
| padding: 15px 15px 0; | |||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| @@ -4,10 +4,12 @@ import { | |||||
| getExperiment, | getExperiment, | ||||
| getExperimentById, | getExperimentById, | ||||
| getQueryByExperimentId, | getQueryByExperimentId, | ||||
| getTensorBoardStatusReq, | |||||
| postExperiment, | postExperiment, | ||||
| putExperiment, | putExperiment, | ||||
| putQueryByExperimentInsId, | putQueryByExperimentInsId, | ||||
| runExperiments, | runExperiments, | ||||
| runTensorBoardReq, | |||||
| } from '@/services/experiment/index.js'; | } from '@/services/experiment/index.js'; | ||||
| import { getWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflow } from '@/services/pipeline/index.js'; | ||||
| import { elapsedTime } from '@/utils/date'; | import { elapsedTime } from '@/utils/date'; | ||||
| @@ -23,10 +25,14 @@ import { Button, Modal, Space, Table, message } from 'antd'; | |||||
| import momnet from 'moment'; | import momnet from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus'; | |||||
| import AddExperimentModal from './experimentText/addExperimentModal'; | import AddExperimentModal from './experimentText/addExperimentModal'; | ||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| import { experimentStatusInfo } from './status'; | import { experimentStatusInfo } from './status'; | ||||
| // 定时器 | |||||
| const timerIds = new Map(); | |||||
| function Experiment() { | function Experiment() { | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| const [experimentList, setExperimentList] = useState([]); | const [experimentList, setExperimentList] = useState([]); | ||||
| @@ -44,9 +50,13 @@ function Experiment() { | |||||
| const [isAdd, setIsAdd] = useState(true); | const [isAdd, setIsAdd] = useState(true); | ||||
| const [isModalOpen, setIsModalOpen] = useState(false); | const [isModalOpen, setIsModalOpen] = useState(false); | ||||
| const [addFormData, setAddFormData] = useState({}); | const [addFormData, setAddFormData] = useState({}); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getList(); | getList(); | ||||
| getWorkflowList(); | getWorkflowList(); | ||||
| return () => { | |||||
| clearExperimentInTimers(); | |||||
| }; | |||||
| }, []); | }, []); | ||||
| // 获取实验列表 | // 获取实验列表 | ||||
| const getList = async () => { | const getList = async () => { | ||||
| @@ -72,11 +82,32 @@ function Experiment() { | |||||
| setWorkflowList(res.data.content); | setWorkflowList(res.data.content); | ||||
| } | } | ||||
| }; | }; | ||||
| // 获取实验实例 | |||||
| const getQueryByExperiment = (val) => { | const getQueryByExperiment = (val) => { | ||||
| getQueryByExperimentId(val).then((ret) => { | getQueryByExperimentId(val).then((ret) => { | ||||
| setExpandedRowKeys(val); | setExpandedRowKeys(val); | ||||
| if (ret.code === 200 && ret.data && ret.data.length > 0) { | |||||
| setExperimentInList(ret.data); | |||||
| if (ret && ret.data && ret.data.length > 0) { | |||||
| try { | |||||
| const list = ret.data.map((v) => { | |||||
| const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {}; | |||||
| return { | |||||
| ...v, | |||||
| nodes_result, | |||||
| }; | |||||
| }); | |||||
| setExperimentInList(list); | |||||
| // 获取 TensorBoard 状态 | |||||
| list.forEach((item) => { | |||||
| if (item.nodes_result.tensorboard_log) { | |||||
| const timerId = setTimeout(() => { | |||||
| getTensorBoardStatus(item); | |||||
| }, 0); | |||||
| timerIds.set(item.id, timerId); | |||||
| } | |||||
| }); | |||||
| } catch (error) { | |||||
| setExperimentInList([]); | |||||
| } | |||||
| getList(); | getList(); | ||||
| } else { | } else { | ||||
| setExperimentInList([]); | setExperimentInList([]); | ||||
| @@ -84,13 +115,66 @@ function Experiment() { | |||||
| } | } | ||||
| }); | }); | ||||
| }; | }; | ||||
| // 运行 TensorBoard | |||||
| const runTensorBoard = async (experimentIn) => { | |||||
| const params = { | |||||
| namespace: experimentIn.nodes_result.tensorboard_log.namespace, | |||||
| path: experimentIn.nodes_result.tensorboard_log.path, | |||||
| pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name, | |||||
| }; | |||||
| const [res] = await to(runTensorBoardReq(params)); | |||||
| if (res) { | |||||
| experimentIn.tensorboardUrl = res.data; | |||||
| const timerId = timerIds.get(experimentIn.id); | |||||
| if (timerId) { | |||||
| clearTimeout(timerId); | |||||
| timerIds.delete(experimentIn.id); | |||||
| getTensorBoardStatus(experimentIn); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 获取 TensorBoard 状态 | |||||
| const getTensorBoardStatus = async (experimentIn) => { | |||||
| const params = { | |||||
| namespace: experimentIn.nodes_result.tensorboard_log.namespace, | |||||
| path: experimentIn.nodes_result.tensorboard_log.path, | |||||
| pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name, | |||||
| }; | |||||
| const [res] = await to(getTensorBoardStatusReq(params)); | |||||
| if (res && res.data) { | |||||
| setExperimentInList((prevList) => { | |||||
| const newList = [...prevList]; | |||||
| const index = prevList.findIndex((item) => item.id === experimentIn.id); | |||||
| const preObj = prevList[index]; | |||||
| const newObj = { | |||||
| ...preObj, | |||||
| tensorBoardStatus: res.data.status, | |||||
| tensorboardUrl: res.data.url, | |||||
| }; | |||||
| newList.splice(index, 1, newObj); | |||||
| return newList; | |||||
| }); | |||||
| const timerId = setTimeout(() => { | |||||
| getTensorBoardStatus(experimentIn); | |||||
| }, 10000); | |||||
| timerIds.set(experimentIn.id, timerId); | |||||
| } | |||||
| }; | |||||
| const expandChange = (e, record) => { | const expandChange = (e, record) => { | ||||
| if (record.id === expandedRowKeys) { | if (record.id === expandedRowKeys) { | ||||
| clearExperimentInTimers(); | |||||
| setExpandedRowKeys(null); | setExpandedRowKeys(null); | ||||
| } else { | } else { | ||||
| getQueryByExperiment(record.id); | getQueryByExperiment(record.id); | ||||
| } | } | ||||
| }; | }; | ||||
| // 终止实验实例获取 TensorBoard 状态的定时器 | |||||
| const clearExperimentInTimers = () => { | |||||
| timerIds.values().forEach((timerId) => { | |||||
| clearTimeout(timerId); | |||||
| }); | |||||
| timerIds.clear(); | |||||
| }; | |||||
| // 创建实验 | // 创建实验 | ||||
| const createExperiment = () => { | const createExperiment = () => { | ||||
| setIsAdd(true); | setIsAdd(true); | ||||
| @@ -174,6 +258,19 @@ function Experiment() { | |||||
| navgite({ pathname: `/experiment/pytorchtext/${record.workflow_id}/${item.id}` }); | navgite({ pathname: `/experiment/pytorchtext/${record.workflow_id}/${item.id}` }); | ||||
| }; | }; | ||||
| const handleTensorboard = async (experimentIn) => { | |||||
| if ( | |||||
| experimentIn.tensorBoardStatus === TensorBoardStatusEnum.Terminated || | |||||
| experimentIn.tensorBoardStatus === TensorBoardStatusEnum.Failed | |||||
| ) { | |||||
| await runTensorBoard(experimentIn); | |||||
| } else if ( | |||||
| experimentIn.tensorBoardStatus === TensorBoardStatusEnum.Running && | |||||
| experimentIn.tensorboardUrl | |||||
| ) { | |||||
| window.open(experimentIn.tensorboardUrl, '_blank'); | |||||
| } | |||||
| }; | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| title: '实验名称', | title: '实验名称', | ||||
| @@ -198,7 +295,6 @@ function Experiment() { | |||||
| key: 'status_list', | key: 'status_list', | ||||
| render: (text) => { | render: (text) => { | ||||
| let newText = text && text.replace(/\s+/g, '').split(','); | let newText = text && text.replace(/\s+/g, '').split(','); | ||||
| console.log(newText); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| {newText && newText.length > 0 | {newText && newText.length > 0 | ||||
| @@ -306,15 +402,17 @@ function Experiment() { | |||||
| columns={columns} | columns={columns} | ||||
| dataSource={experimentList} | dataSource={experimentList} | ||||
| pagination={paginationProps} | pagination={paginationProps} | ||||
| rowKey="id" | |||||
| expandable={{ | expandable={{ | ||||
| expandedRowRender: (record) => ( | expandedRowRender: (record) => ( | ||||
| <div> | <div> | ||||
| {experimentInList && experimentInList.length > 0 ? ( | {experimentInList && experimentInList.length > 0 ? ( | ||||
| <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | ||||
| <div style={{ width: '50px' }}>序号</div> | |||||
| <div style={{ width: '200px' }}>状态</div> | |||||
| <div style={{ width: '150px' }}>序号</div> | |||||
| <div style={{ width: '300px' }}>TensorBoard</div> | |||||
| <div style={{ width: '300px' }}>运行时长</div> | <div style={{ width: '300px' }}>运行时长</div> | ||||
| <div style={{ width: '300px' }}>开始时间</div> | <div style={{ width: '300px' }}>开始时间</div> | ||||
| <div style={{ width: '200px' }}>状态</div> | |||||
| <div style={{ width: '200px' }}>操作</div> | <div style={{ width: '200px' }}>操作</div> | ||||
| </div> | </div> | ||||
| ) : ( | ) : ( | ||||
| @@ -332,9 +430,27 @@ function Experiment() { | |||||
| height: '45px', | height: '45px', | ||||
| }} | }} | ||||
| > | > | ||||
| <a style={{ width: '50px' }} onClick={(e) => routerToText(e, item, record)}> | |||||
| <a style={{ width: '150px' }} onClick={(e) => routerToText(e, item, record)}> | |||||
| {index + 1} | {index + 1} | ||||
| </a> | </a> | ||||
| <div style={{ width: '300px' }}> | |||||
| {item.nodes_result.tensorboard_log ? ( | |||||
| <TensorBoardStatus | |||||
| status={item.tensorBoardStatus} | |||||
| onClick={() => handleTensorboard(item)} | |||||
| ></TensorBoardStatus> | |||||
| ) : ( | |||||
| '-' | |||||
| )} | |||||
| </div> | |||||
| <div style={{ width: '300px' }}> | |||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| </div> | |||||
| <div style={{ width: '300px' }}> | |||||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| </div> | |||||
| <div className={Styles.statusBox} style={{ width: '200px' }}> | <div className={Styles.statusBox} style={{ width: '200px' }}> | ||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '7px' }} | style={{ width: '17px', marginRight: '7px' }} | ||||
| @@ -347,14 +463,6 @@ function Experiment() { | |||||
| {experimentStatusInfo[item.status]?.label} | {experimentStatusInfo[item.status]?.label} | ||||
| </span> | </span> | ||||
| </div> | </div> | ||||
| <div style={{ width: '300px' }}> | |||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| </div> | |||||
| <div style={{ width: '300px' }}> | |||||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| </div> | |||||
| <div style={{ width: '200px' }}> | <div style={{ width: '200px' }}> | ||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| @@ -259,7 +259,10 @@ | |||||
| .ant-modal-content { | .ant-modal-content { | ||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background: linear-gradient(180deg, #cfdfff 0%, #d4e2ff 9.77%, #ffffff 40%, #ffffff 100%); | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-position: top center; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -39,9 +39,9 @@ const Dataset = () => { | |||||
| return { | return { | ||||
| ...form.getFieldsValue(), | ...form.getFieldsValue(), | ||||
| models_id: locationParams.id, | models_id: locationParams.id, | ||||
| file_name: item.response.data[0].fileName, | |||||
| file_size: item.response.data[0].fileSize, | |||||
| url: item.response.data[0].url, | |||||
| file_name: item.response.code === 200 ? item.response.data[0].fileName : null, | |||||
| file_size: item.response.code === 200 ? item.response.data[0].fileSize : null, | |||||
| url: item.response.code === 200 ? item.response.data[0].url : null, | |||||
| }; | }; | ||||
| }), | }), | ||||
| ); | ); | ||||
| @@ -59,6 +59,7 @@ const Dataset = () => { | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| console.log(locationParams); | console.log(locationParams); | ||||
| const [wordList, setWordList] = useState([]); | const [wordList, setWordList] = useState([]); | ||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const getModelByDetail = () => { | const getModelByDetail = () => { | ||||
| getModelById(locationParams.id).then((ret) => { | getModelById(locationParams.id).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| @@ -68,7 +69,7 @@ const Dataset = () => { | |||||
| const getModelVersionsList = () => { | const getModelVersionsList = () => { | ||||
| getModelVersionsById(locationParams.id).then((ret) => { | getModelVersionsById(locationParams.id).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| if (ret.data && ret.data.length > 0) { | |||||
| if (ret && ret.data && ret.data.length > 0) { | |||||
| setVersionList( | setVersionList( | ||||
| ret.data.map((item) => { | ret.data.map((item) => { | ||||
| return { | return { | ||||
| @@ -77,6 +78,8 @@ const Dataset = () => { | |||||
| }; | }; | ||||
| }), | }), | ||||
| ); | ); | ||||
| setVersion(ret.data[0]); | |||||
| getModelVersions({ version: ret.data[0], models_id: locationParams.id }); | |||||
| } | } | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -90,6 +93,7 @@ const Dataset = () => { | |||||
| form.setFieldsValue({ name: datasetDetailObj.name }); | form.setFieldsValue({ name: datasetDetailObj.name }); | ||||
| setDialogTitle('创建新版本'); | setDialogTitle('创建新版本'); | ||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| @@ -104,9 +108,7 @@ const Dataset = () => { | |||||
| onOk: () => { | onOk: () => { | ||||
| deleteModelVersion({ models_id: locationParams.id, version }).then((ret) => { | deleteModelVersion({ models_id: locationParams.id, version }).then((ret) => { | ||||
| setVersion(null); | |||||
| getModelVersionsList(); | getModelVersionsList(); | ||||
| getModelVersions({ version, models_id: locationParams.id }); | |||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| }); | }); | ||||
| }, | }, | ||||
| @@ -368,7 +370,7 @@ const Dataset = () => { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Upload {...props}> | |||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button | <Button | ||||
| style={{ | style={{ | ||||
| fontSize: '14px', | fontSize: '14px', | ||||
| @@ -49,6 +49,7 @@ const PublicData = () => { | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [dialogTitle, setDialogTitle] = useState('新建模型'); | const [dialogTitle, setDialogTitle] = useState('新建模型'); | ||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const getModelLists = (queryFlow) => { | const getModelLists = (queryFlow) => { | ||||
| getModelList(queryFlow).then((ret) => { | getModelList(queryFlow).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| @@ -62,6 +63,7 @@ const PublicData = () => { | |||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| setDialogTitle('新建模型'); | setDialogTitle('新建模型'); | ||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| const getAssetIconList = (params) => { | const getAssetIconList = (params) => { | ||||
| @@ -314,31 +316,27 @@ const PublicData = () => { | |||||
| <Form.Item | <Form.Item | ||||
| label="模型名称" | label="模型名称" | ||||
| name="name" | name="name" | ||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型名称!', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input placeholder="请输入模型名称" /> | |||||
| <Input placeholder="请输入模型名称" showCount maxLength={64} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型描述" | label="模型描述" | ||||
| name="description" | name="description" | ||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型描述!', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input placeholder="请输入模型描述" /> | |||||
| <Input placeholder="请输入模型描述" showCount maxLength={256} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="可见范围" name="available_range"> | <Form.Item label="可见范围" name="available_range"> | ||||
| <Radio.Group> | <Radio.Group> | ||||
| @@ -375,7 +373,7 @@ const PublicData = () => { | |||||
| <Select allowClear placeholder="请选择模型标签" options={[]} /> | <Select allowClear placeholder="请选择模型标签" options={[]} /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="模型文件" name="dataset_version_vos"> | <Form.Item label="模型文件" name="dataset_version_vos"> | ||||
| <Upload {...props}> | |||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button icon={<UploadOutlined style={{ color: '#1664ff' }} />}>上传文件</Button> | <Button icon={<UploadOutlined style={{ color: '#1664ff' }} />}>上传文件</Button> | ||||
| </Upload> | </Upload> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -42,6 +42,7 @@ const modelMenus = ({ onParDragEnd }) => { | |||||
| const { Panel } = Collapse; | const { Panel } = Collapse; | ||||
| return ( | return ( | ||||
| <div style={{ width: '250px', height: '99%' }} className={Styles.collapse}> | <div style={{ width: '250px', height: '99%' }} className={Styles.collapse}> | ||||
| <div className={Styles.modelMenusTitle}>组件库</div> | |||||
| <Collapse collapsible="header" defaultActiveKey={['1']} expandIconPosition="end"> | <Collapse collapsible="header" defaultActiveKey={['1']} expandIconPosition="end"> | ||||
| {modelMenusList && modelMenusList.length > 0 | {modelMenusList && modelMenusList.length > 0 | ||||
| ? modelMenusList.map((item) => ( | ? modelMenusList.map((item) => ( | ||||
| @@ -12,6 +12,7 @@ | |||||
| } | } | ||||
| .collapseItem:hover { | .collapseItem:hover { | ||||
| background: rgba(22, 100, 255, 0.08); | background: rgba(22, 100, 255, 0.08); | ||||
| color:#1664ff; | |||||
| } | } | ||||
| .collapse { | .collapse { | ||||
| :global { | :global { | ||||
| @@ -23,10 +24,12 @@ | |||||
| margin-bottom: 5px; | margin-bottom: 5px; | ||||
| background-color: #fff; | background-color: #fff; | ||||
| border-color: transparent; | border-color: transparent; | ||||
| padding: 20px 16px 15px 16px; | |||||
| } | } | ||||
| .ant-collapse > .ant-collapse-item { | .ant-collapse > .ant-collapse-item { | ||||
| margin: 0 10px; | margin: 0 10px; | ||||
| border-color: rgba(20, 49, 179, 0.12); | |||||
| border-bottom:0.5px dashed rgba(20, 49, 179, 0.12); | |||||
| border-radius: 0px; | border-radius: 0px; | ||||
| } | } | ||||
| .ant-collapse .ant-collapse-content { | .ant-collapse .ant-collapse-content { | ||||
| @@ -38,3 +41,10 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .modelMenusTitle{ | |||||
| padding: 12px 25px; | |||||
| margin-bottom: 10px; | |||||
| color:#111111; | |||||
| font-size:16px; | |||||
| font-family: 'Alibaba'; | |||||
| } | |||||
| @@ -111,7 +111,8 @@ const Pipeline = () => { | |||||
| title: '序号', | title: '序号', | ||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: 80, | |||||
| width: 140, | |||||
| align: 'center', | |||||
| render(text, record, index) { | render(text, record, index) { | ||||
| return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | ||||
| }, | }, | ||||
| @@ -143,6 +144,7 @@ const Pipeline = () => { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| key: 'action', | key: 'action', | ||||
| width: 320, | |||||
| render: (_, record) => ( | render: (_, record) => ( | ||||
| <Space size="small"> | <Space size="small"> | ||||
| @@ -241,7 +243,7 @@ const Pipeline = () => { | |||||
| 新建流水线 | 新建流水线 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <Table columns={columns} dataSource={pipeList} pagination={paginationProps} /> | |||||
| <Table columns={columns} dataSource={pipeList} pagination={paginationProps} rowKey="id" /> | |||||
| <Modal | <Modal | ||||
| title={ | title={ | ||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | ||||
| @@ -275,30 +277,26 @@ const Pipeline = () => { | |||||
| <Form.Item | <Form.Item | ||||
| label="流水线名称" | label="流水线名称" | ||||
| name="name" | name="name" | ||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入流水线名称', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input /> | |||||
| <Input showCount maxLength={64} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="流水线描述" | label="流水线描述" | ||||
| name="description" | name="description" | ||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入流水线描述', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input /> | |||||
| <Input showCount maxLength={128} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Form> | </Form> | ||||
| </Modal> | </Modal> | ||||
| @@ -29,7 +29,10 @@ | |||||
| .ant-modal-content { | .ant-modal-content { | ||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background: linear-gradient(180deg, #cfdfff 0%, #d4e2ff 9.77%, #ffffff 40%, #ffffff 100%); | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-position: top center; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -100,3 +100,19 @@ export function putExperiment(data) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 启动tensorBoard | |||||
| export function runTensorBoardReq(data) { | |||||
| return request(`/api/mmp/tensorBoard/run`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 启动tensorBoard | |||||
| export function getTensorBoardStatusReq(data) { | |||||
| return request(`/api/mmp/tensorBoard/getStatus`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||