| @@ -4,4 +4,7 @@ module.exports = { | |||
| page: true, | |||
| REACT_APP_ENV: true, | |||
| }, | |||
| rules: { | |||
| "@typescript-eslint/no-use-before-define": "off" | |||
| } | |||
| }; | |||
| @@ -16,8 +16,8 @@ export default { | |||
| '/api/': { | |||
| // 要代理的地址 | |||
| // target: 'http://172.20.32.181:31205', | |||
| // target: 'http://172.20.32.98:8082', | |||
| target: 'http://172.20.32.150:8082', | |||
| target: 'http://172.20.32.98:8082', | |||
| // target: 'http://172.20.32.150:8082', | |||
| // 配置了这个可以从 http 代理到 https | |||
| // 依赖 origin 的功能可能需要这个,比如 cookie | |||
| changeOrigin: true, | |||
| @@ -46,18 +46,34 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'datasetPreparation', | |||
| path: '/datasetPreparation', | |||
| routes: [ | |||
| { | |||
| name: 'datasetAnnotation', | |||
| path: 'datasetAnnotation', | |||
| component: './DatasetPreparation/DatasetAnnotation/index', | |||
| }, | |||
| { | |||
| name: '训练', | |||
| path: 'pytorchtext/:id/:name', | |||
| component: './Pipeline/editPipeline/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'pipeline', | |||
| path: '/pipeline', | |||
| routes: [ | |||
| { | |||
| name: '流水线', | |||
| path: '/pipeline', | |||
| path: '', | |||
| component: './Pipeline/index', | |||
| }, | |||
| { | |||
| name: '训练', | |||
| path: '/pipeline/pytorchtext/:id/:name', | |||
| path: 'pytorchtext/:id/:name', | |||
| component: './Pipeline/editPipeline/index', | |||
| }, | |||
| ], | |||
| @@ -68,12 +84,12 @@ export default [ | |||
| routes: [ | |||
| { | |||
| name: '实验', | |||
| path: '/experiment', | |||
| path: '', | |||
| component: './Experiment/index', | |||
| }, | |||
| { | |||
| name: '实验训练', | |||
| path: '/experiment/pytorchtext/:workflowId/:id', | |||
| path: 'pytorchtext/:workflowId/:id', | |||
| component: './Experiment/experimentText/index', | |||
| }, | |||
| ], | |||
| @@ -84,7 +100,7 @@ export default [ | |||
| routes: [ | |||
| { | |||
| name: '开发环境', | |||
| path: '/developmentEnvironment', | |||
| path: '', | |||
| component: './DevelopmentEnvironment/index', | |||
| }, | |||
| ], | |||
| @@ -103,7 +119,7 @@ export default [ | |||
| path: '/system/role-auth/user/:id', | |||
| component: './System/Role/authUser', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| { | |||
| name: 'dataset', | |||
| @@ -123,14 +139,13 @@ export default [ | |||
| name: '模型管理', | |||
| path: '/dataset/modelIndex', | |||
| component: './Model/index', | |||
| }, | |||
| { | |||
| name: '模型简介', | |||
| path: '/dataset/modelIntro/:id', | |||
| component: './Model/modelIntro', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| { | |||
| name: 'monitor', | |||
| @@ -141,7 +156,7 @@ export default [ | |||
| path: '/monitor/job-log/index/:id', | |||
| component: './Monitor/JobLog', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| { | |||
| name: 'tool', | |||
| @@ -157,6 +172,6 @@ export default [ | |||
| path: '/tool/gen/edit', | |||
| component: './Tool/Gen/edit', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| ]; | |||
| @@ -1,23 +1,25 @@ | |||
| import RightContent from '@/components/RightContent'; | |||
| import { LinkOutlined } from '@ant-design/icons'; | |||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | |||
| import { SettingDrawer } from '@ant-design/pro-components'; | |||
| import type { RunTimeLayoutConfig } from '@umijs/max'; | |||
| import { history, Link } from '@umijs/max'; | |||
| import { history } from '@umijs/max'; | |||
| import axios from 'axios'; | |||
| import defaultSettings from '../config/defaultSettings'; | |||
| import { errorConfig } from './requestErrorConfig'; | |||
| import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; | |||
| import { getRemoteMenu, getRoutersInfo, getUserInfo, patchRouteWithRemoteMenus, setRemoteMenu } from './services/session'; | |||
| import '../public/fonts/font.css'; | |||
| import { getAccessToken } from './access'; | |||
| import './dayjsConfig'; | |||
| import { PageEnum } from './enums/pagesEnums'; | |||
| import '../public/fonts/font.css' | |||
| import './global.less' | |||
| import axios from 'axios' | |||
| axios.defaults.baseUrl='http://172.20.32.150:8082' | |||
| import './global.less'; | |||
| import { | |||
| getRemoteMenu, | |||
| getRoutersInfo, | |||
| getUserInfo, | |||
| patchRouteWithRemoteMenus, | |||
| setRemoteMenu, | |||
| } from './services/session'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| axios.defaults.baseUrl = 'http://172.20.32.150:8082'; | |||
| const isDev = process.env.NODE_ENV === 'development'; | |||
| /** | |||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | |||
| * */ | |||
| @@ -155,9 +157,9 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) = | |||
| export async function onRouteChange({ clientRoutes, location }) { | |||
| const menus = getRemoteMenu(); | |||
| // console.log('onRouteChange', clientRoutes, location, menus); | |||
| if(menus === null && location.pathname !== PageEnum.LOGIN) { | |||
| console.log('refresh') | |||
| // console.log('onRouteChange', clientRoutes, location, menus); | |||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | |||
| console.log('refresh'); | |||
| history.go(0); | |||
| } | |||
| } | |||
| @@ -166,7 +168,6 @@ export async function onRouteChange({ clientRoutes, location }) { | |||
| // console.log('patchRoutes', routes, routeComponents); | |||
| // } | |||
| export async function patchClientRoutes({ routes }) { | |||
| // console.log('patchClientRoutes', routes); | |||
| patchRouteWithRemoteMenus(routes); | |||
| @@ -175,62 +176,12 @@ export async function patchClientRoutes({ routes }) { | |||
| export function render(oldRender: () => void) { | |||
| // console.log('render get routers', oldRender) | |||
| const token = getAccessToken(); | |||
| if(!token || token?.length === 0) { | |||
| if (!token || token?.length === 0) { | |||
| oldRender(); | |||
| return; | |||
| } | |||
| getRoutersInfo().then(res => { | |||
| getRoutersInfo().then((res) => { | |||
| setRemoteMenu(res); | |||
| oldRender() | |||
| oldRender(); | |||
| }); | |||
| } | |||
| /** | |||
| * @name request 配置,可以配置错误处理 | |||
| * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 | |||
| * @doc https://umijs.org/docs/max/request#配置 | |||
| */ | |||
| const checkRegion = 5 * 60 * 1000; | |||
| export const request = { | |||
| ...errorConfig, | |||
| requestInterceptors: [ | |||
| (url: any, options: { headers: any }) => { | |||
| const headers = options.headers ? options.headers : []; | |||
| console.log('request ====>:', url); | |||
| const authHeader = headers['Authorization']; | |||
| const isToken = headers['isToken']; | |||
| if (!authHeader && isToken !== false) { | |||
| const expireTime = getTokenExpireTime(); | |||
| if (expireTime) { | |||
| const left = Number(expireTime) - new Date().getTime(); | |||
| const refreshToken = getRefreshToken(); | |||
| if (left < checkRegion && refreshToken) { | |||
| if (left < 0) { | |||
| clearSessionToken(); | |||
| } | |||
| } else { | |||
| const accessToken = getAccessToken(); | |||
| if (accessToken) { | |||
| headers['Authorization'] = `Bearer ${accessToken}`; | |||
| } | |||
| } | |||
| } else { | |||
| clearSessionToken(); | |||
| } | |||
| } | |||
| return { url, options }; | |||
| }, | |||
| ], | |||
| responseInterceptors: [ | |||
| // (response) => | |||
| // { | |||
| // // // 不再需要异步处理读取返回体内容,可直接在data中读出,部分字段可在 config 中找到 | |||
| // // const { data = {} as any, config } = response; | |||
| // // // do something | |||
| // // console.log('data: ', data) | |||
| // // console.log('config: ', config) | |||
| // return response | |||
| // }, | |||
| ], | |||
| }; | |||
| @@ -0,0 +1,3 @@ | |||
| import dayjs from 'dayjs'; | |||
| import duration from 'dayjs/plugin/duration'; | |||
| dayjs.extend(duration); | |||
| @@ -0,0 +1 @@ | |||
| @@ -0,0 +1,6 @@ | |||
| import { getDictValueEnum } from '@/services/system/dict'; | |||
| export function useDictEnum(name: string) { | |||
| const data = getDictValueEnum(name); | |||
| return data; | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| export function useStateRef<T>(initialValue: T) { | |||
| const [value, setValue] = useState(initialValue); | |||
| const ref = useRef(value); | |||
| useEffect(() => { | |||
| ref.current = value; | |||
| }, [value]); | |||
| return [value, setValue, ref] as const; | |||
| } | |||
| @@ -1,7 +0,0 @@ | |||
| import { getDictValueEnum } from "@/services/system/dict"; | |||
| export function useDictEnum(name: string) | |||
| { | |||
| const data = getDictValueEnum(name); | |||
| return data; | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| .container { | |||
| width: 100%; | |||
| height: 100%; | |||
| .frame { | |||
| width: 100%; | |||
| height: 100%; | |||
| border: none; | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| import styles from './index.less'; | |||
| function DatasetAnnotation() { | |||
| return ( | |||
| <div className={styles.container}> | |||
| <iframe src="http://172.20.32.181:32102" className={styles.frame}></iframe> | |||
| </div> | |||
| ); | |||
| } | |||
| export default DatasetAnnotation; | |||
| @@ -0,0 +1,18 @@ | |||
| import LogGroup from './logGroup'; | |||
| type LogListProps = { | |||
| list: any[]; | |||
| status: string; | |||
| }; | |||
| function LogList({ list = [], status }: LogListProps) { | |||
| return ( | |||
| <div> | |||
| {list.map((v) => ( | |||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } | |||
| export default LogList; | |||
| @@ -0,0 +1,51 @@ | |||
| .modal { | |||
| :global { | |||
| .ant-modal-content { | |||
| width: 825px; | |||
| padding: 20px 67px; | |||
| background: linear-gradient(180deg, #cfdfff 0%, #d4e2ff 9.77%, #ffffff 40%, #ffffff 100%); | |||
| border-radius: 21px; | |||
| } | |||
| .ant-modal-header { | |||
| margin: 20px 0; | |||
| background-color: transparent; | |||
| } | |||
| .ant-input { | |||
| height: 40px; | |||
| border-color: #e6e6e6; | |||
| } | |||
| .ant-select-single { | |||
| height: 40px; | |||
| } | |||
| .ant-form-item .ant-form-item-label > label { | |||
| color: rgba(29, 29, 32, 0.8); | |||
| } | |||
| .ant-modal-footer { | |||
| display: flex; | |||
| justify-content: center; | |||
| margin: 40px 0 30px 0; | |||
| } | |||
| .ant-btn { | |||
| width: 110px; | |||
| height: 40px; | |||
| font-size: 18px; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| border-radius: 10px; | |||
| } | |||
| .ant-btn-primary { | |||
| background: #1664ff; | |||
| } | |||
| } | |||
| .title { | |||
| display: flex; | |||
| align-items: center; | |||
| font-weight: 500; | |||
| .image { | |||
| width: 20px; | |||
| margin-right: 10px; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| import { Form, Input, Modal, Select } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import styles from './addExperimentModal.less'; | |||
| type FormData = { | |||
| name?: string; | |||
| description?: string; | |||
| workflow_id?: string | number; | |||
| }; | |||
| type AddExperimentModalProps = { | |||
| isAdd: boolean; | |||
| open: boolean; | |||
| onCancel: () => void; | |||
| onFinish: () => void; | |||
| workflowList: Workflow[]; | |||
| initialValues: FormData; | |||
| }; | |||
| interface GlobalParam { | |||
| param_name: string; | |||
| param_value: string; | |||
| } | |||
| interface Workflow { | |||
| id: string | number; | |||
| name: string; | |||
| global_param?: GlobalParam[] | null; | |||
| } | |||
| function AddExperimentModal({ | |||
| isAdd, | |||
| open, | |||
| onCancel, | |||
| onFinish, | |||
| workflowList = [], | |||
| initialValues = {}, | |||
| }: AddExperimentModalProps) { | |||
| const dialogTitle = isAdd ? '新建实验' : '编辑实验'; | |||
| const workflowDisabled = isAdd ? false : true; | |||
| const [globalParam, setGlobalParam] = useState<GlobalParam[]>([]); | |||
| const [form] = Form.useForm(); | |||
| // 除了流水线选择发生变化 | |||
| const handleWorkflowChange = (id: string) => { | |||
| const pipeline: Workflow | undefined = workflowList.find((v) => v.id === id); | |||
| if (pipeline && pipeline.global_param) { | |||
| setGlobalParam(pipeline.global_param); | |||
| const fields = pipeline.global_param.reduce((acc, item) => { | |||
| acc[item.param_name] = item.param_value; | |||
| return acc; | |||
| }, {} as Record<string, string>); | |||
| form.setFieldsValue(fields); | |||
| } else { | |||
| setGlobalParam([]); | |||
| } | |||
| }; | |||
| return ( | |||
| <Modal | |||
| className={styles.modal} | |||
| title={ | |||
| <div className={styles.title}> | |||
| <img className={styles.image} src={`/assets/images/pipeline-edit-icon.png`} alt="" /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={open} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={onCancel} | |||
| destroyOnClose={true} | |||
| > | |||
| <Form | |||
| name="form" | |||
| layout="vertical" | |||
| initialValues={initialValues} | |||
| onFinish={onFinish} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.Item | |||
| label="实验名称" | |||
| name="name" | |||
| rules={[{ required: true, message: '请输入实验名称' }]} | |||
| > | |||
| <Input placeholder="请输入实验名称" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="实验描述" | |||
| name="description" | |||
| rules={[{ required: true, message: '请输入实验描述' }]} | |||
| > | |||
| <Input placeholder="请输入实验描述" maxLength={128} showCount allowClear /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="选择流水线" | |||
| name="workflow_id" | |||
| rules={[{ required: true, message: '请选择流水线' }]} | |||
| > | |||
| <Select | |||
| disabled={workflowDisabled} | |||
| placeholder="请选择流水线" | |||
| onChange={handleWorkflowChange} | |||
| > | |||
| {Array.isArray(workflowList) | |||
| ? workflowList.map((item) => { | |||
| return ( | |||
| <Select.Option key={item.id} value={item.id}> | |||
| {item.name} | |||
| </Select.Option> | |||
| ); | |||
| }) | |||
| : null} | |||
| </Select> | |||
| </Form.Item> | |||
| {globalParam.map((item) => ( | |||
| <Form.Item label={item.param_name} name={item.param_name} key={item.param_name}> | |||
| <Input /> | |||
| </Form.Item> | |||
| ))} | |||
| </Form> | |||
| </Modal> | |||
| ); | |||
| } | |||
| export default AddExperimentModal; | |||
| @@ -1,375 +1,405 @@ | |||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||
| import { useParams } from 'react-router-dom' | |||
| import Props from './props'; | |||
| import { getExperimentIns } from '@/services/experiment/index.js'; | |||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||
| import G6 from '@antv/g6'; | |||
| import Styles from './editPipeline.less' | |||
| import momnet from 'moment'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useNavigate, useParams } from 'react-router-dom'; | |||
| import { s8 } from '../../../utils'; | |||
| import { Button, message} from 'antd'; | |||
| import {SaveOutlined} from '@ant-design/icons'; | |||
| import {saveWorkflow,getWorkflowById,} from '@/services/pipeline/index.js' | |||
| import {getExperimentIns,} from '@/services/experiment/index.js' | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import momnet from 'moment' | |||
| const ExperimentText = React.FC = () => { | |||
| const propsRef=useRef() | |||
| const navgite=useNavigate(); | |||
| const locationParams =useParams () //新版本获取路由参数接口 | |||
| let graph=null | |||
| const [experimentStatusObj,setExperimentStatusObj]=useState({}) | |||
| const [experimentAllMessage,setExperimentAllMessage]=useState({}) | |||
| const statusObj={ | |||
| "Running":'运行中', | |||
| "Succeeded":'成功', | |||
| "Pending":'等待中', | |||
| "Failed":'失败', | |||
| "Error":'错误', | |||
| "Terminated":'终止', | |||
| "Skipped":'未执行', | |||
| "Omitted":'未执行', | |||
| } | |||
| const statusColorObj={ | |||
| "Running":'#165bff', | |||
| "Succeeded":'#63a728', | |||
| "Pending":'#f981eb', | |||
| "Failed":'#c73131', | |||
| "Error":'#c73131', | |||
| "Terminated":'#8a8a8a', | |||
| "Skipped":'#8a8a8a', | |||
| "Omitted":'#8a8a8ae', | |||
| import { experimentStatusInfo } from '../status'; | |||
| import Styles from './editPipeline.less'; | |||
| import Props from './props'; | |||
| function ExperimentText() { | |||
| const [message, setMessage] = useState({}); | |||
| const messageRef = useRef(message); | |||
| const propsRef = useRef(); | |||
| const navgite = useNavigate(); | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| let graph = null; | |||
| const timers = (time) => { | |||
| let timer = new Date(time); | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| }; | |||
| const pipelineContainer = useEmotionCss(() => { | |||
| return { | |||
| display: 'flex', | |||
| backgroundColor: '#fff', | |||
| height: '98vh', | |||
| }; | |||
| }); | |||
| const graphStyle = useEmotionCss(() => { | |||
| return { | |||
| width: '100%', | |||
| backgroundColor: '#f9fafb', | |||
| flex: 1, | |||
| }; | |||
| }); | |||
| const graphRef = useRef(); | |||
| const onDragEnd = (val) => { | |||
| console.log(val, 'eee'); | |||
| const _x = val.x; | |||
| const _y = val.y; | |||
| const point = graph.getPointByClient(_x, _y); | |||
| let model = {}; | |||
| // 元模型 | |||
| model = { | |||
| ...val, | |||
| x: point.x, | |||
| y: point.y, | |||
| id: val.component_name + '-' + s8(), | |||
| isCluster: false, | |||
| }; | |||
| console.log(graph, model); | |||
| graph.addItem('node', model, true); | |||
| console.log(graph); | |||
| }; | |||
| const formChange = (val) => {}; | |||
| const handlerClick = (e) => { | |||
| console.log(propsRef, graph, messageRef.current); | |||
| // let cache = []; | |||
| // let json_str = JSON.stringify(graph, function(key, value) { | |||
| // if (typeof value === 'object' && value !== null) { | |||
| // if (cache.indexOf(value) !== -1) { | |||
| // return; | |||
| // } | |||
| // cache.push(value); | |||
| // } | |||
| // return value; | |||
| // }); | |||
| // console.log(json_str); | |||
| propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | |||
| }; | |||
| const getGraphData = (data) => { | |||
| if (graph) { | |||
| console.log(graph); | |||
| graph.data(data); | |||
| graph.render(); | |||
| } else { | |||
| setTimeout(() => { | |||
| getGraphData(data); | |||
| }, 500); | |||
| } | |||
| const timers=(time)=>{ | |||
| let timer=new Date(time) | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| } | |||
| const pipelineContainer = useEmotionCss(() => { | |||
| return { | |||
| display: 'flex', | |||
| backgroundColor:'#fff', | |||
| height:'98vh' | |||
| }; | |||
| }); | |||
| const graphStyle = useEmotionCss(() => { | |||
| return { | |||
| width:'100%', | |||
| backgroundColor:'#f9fafb', | |||
| flex:1 | |||
| }; | |||
| }); | |||
| const graphRef=useRef() | |||
| const onDragEnd=(val)=>{ | |||
| console.log(val,'eee'); | |||
| const _x = val.x | |||
| const _y = val.y | |||
| const point = graph.getPointByClient(_x, _y); | |||
| let model = {}; | |||
| // 元模型 | |||
| model = { | |||
| ...val, | |||
| x: point.x, | |||
| y: point.y, | |||
| id: val.component_name+'-'+s8(), | |||
| isCluster: false, | |||
| }; | |||
| console.log(graph, model); | |||
| }; | |||
| const getFirstWorkflow = (val) => { | |||
| getWorkflowById(val).then((ret) => { | |||
| console.log(ret, 'retttttttttt'); | |||
| if (ret.code == 200) { | |||
| if (graph && ret.data && ret.data.dag) { | |||
| console.log(JSON.parse(ret.data.dag)); | |||
| getExperimentIns(locationParams.id).then((res) => { | |||
| if (res.code == 200) { | |||
| console.log(ret.data, 'data'); | |||
| setMessage(res.data); | |||
| const experimentStatusObjs = JSON.parse(res.data.nodes_status); | |||
| const newNodeList = JSON.parse(ret.data.dag).nodes.map((item) => { | |||
| console.log(experimentStatusObjs); | |||
| return { | |||
| ...item, | |||
| experimentEndTime: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].finishedAt, | |||
| experimentStartTime: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].startedAt, | |||
| experimentStatus: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].phase, | |||
| component_id: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].id, | |||
| img: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].phase | |||
| ? item.img.slice(0, item.img.length - 4) + | |||
| '-' + | |||
| experimentStatusObjs[item.id].phase + | |||
| '.png' | |||
| : item.img, | |||
| }; | |||
| }); | |||
| const newData = { ...JSON.parse(ret.data.dag), nodes: newNodeList }; | |||
| graph.addItem('node', model, true); | |||
| console.log(graph); | |||
| } | |||
| const formChange=(val)=>{ | |||
| } | |||
| const handlerClick=(e)=>{ | |||
| console.log(propsRef,graph); | |||
| // let cache = []; | |||
| // let json_str = JSON.stringify(graph, function(key, value) { | |||
| // if (typeof value === 'object' && value !== null) { | |||
| // if (cache.indexOf(value) !== -1) { | |||
| // return; | |||
| // } | |||
| // cache.push(value); | |||
| // } | |||
| // return value; | |||
| // }); | |||
| // console.log(json_str); | |||
| propsRef.current.showDrawer(e,locationParams.id) | |||
| } | |||
| const getGraphData=(data)=>{ | |||
| if(graph){ | |||
| console.log(graph); | |||
| graph.data(data) | |||
| graph.render() | |||
| } | |||
| else{ | |||
| setTimeout(()=>{ | |||
| getGraphData(data) | |||
| },500) | |||
| getGraphData(newData); | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| const getFirstWorkflow=(val)=>{ | |||
| getWorkflowById(val).then(ret=>{ | |||
| console.log(ret,'retttttttttt'); | |||
| if(ret.code==200){ | |||
| if(graph&&ret.data&&ret.data.dag){ | |||
| console.log(JSON.parse(ret.data.dag)); | |||
| getExperimentIns(locationParams.id).then(res=>{ | |||
| if(res.code==200){ | |||
| console.log(ret.data,'data'); | |||
| const experimentStatusObjs=JSON.parse(res.data.nodes_status) | |||
| const newNodeList= JSON.parse(ret.data.dag).nodes.map(item=>{console.log(experimentStatusObjs); return {...item,experimentEndTime:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].finishedAt,experimentStartTime:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].startedAt,experimentStatus:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].phase,component_id:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].id,img:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].phase?item.img.slice(0,item.img.length-4)+'-'+experimentStatusObjs[item.id].phase+'.png':item.img}}) | |||
| const newData={...JSON.parse(ret.data.dag),nodes:newNodeList} | |||
| console.log(newData); | |||
| setExperimentAllMessage(res.data) | |||
| getGraphData(newData) | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| } | |||
| }) | |||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||
| // graph.render() | |||
| }); | |||
| }; | |||
| // const getExperimentIn=(val)=>{ | |||
| // getExperimentIns(val).then(ret=>{ | |||
| // if(ret.code==200){ | |||
| // console.log(JSON.parse(ret.data.nodes_status)); | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| // setTimeout(() => { | |||
| // console.log(experimentStatusObj); | |||
| } | |||
| } | |||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||
| // graph.render() | |||
| }) | |||
| } | |||
| // const getExperimentIn=(val)=>{ | |||
| // getExperimentIns(val).then(ret=>{ | |||
| // if(ret.code==200){ | |||
| // console.log(JSON.parse(ret.data.nodes_status)); | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| // setTimeout(() => { | |||
| // console.log(experimentStatusObj); | |||
| // }, 1000); | |||
| // } | |||
| // }) | |||
| // } | |||
| useEffect(()=>{ | |||
| initGraph() | |||
| getFirstWorkflow(locationParams.workflowId) | |||
| },[]) | |||
| const initGraph=()=>{ | |||
| G6.registerNode( | |||
| 'rect-node', | |||
| { | |||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||
| getAnchorPoints(cfg) { | |||
| return ( | |||
| cfg.anchorPoints || [ | |||
| // 上下各3,左右各1 | |||
| [0.5, 0], | |||
| [0.5, 1], | |||
| // }, 1000); | |||
| // } | |||
| ] | |||
| ); | |||
| }, | |||
| afterDraw(cfg, group) { | |||
| // console.log(group, cfg, 12312); | |||
| const image = group.addShape('image', { | |||
| attrs: { | |||
| x: -45, | |||
| y: -10, | |||
| width: 20, | |||
| height: 20, | |||
| img: cfg.img, | |||
| cursor: 'pointer', | |||
| }, | |||
| draggable: true, | |||
| }); | |||
| // if (cfg.label) { | |||
| // group.addShape('text', { | |||
| // attrs: { | |||
| // x: 0, | |||
| // y: cfg.height / 2 - 5, | |||
| // textAlign: 'center', | |||
| // textBaseline: 'middle', | |||
| // text: cfg.label, | |||
| // fill: '#fff', | |||
| // }, | |||
| // draggable: true, | |||
| // }); | |||
| // } | |||
| const bbox = group.getBBox(); | |||
| const anchorPoints = this.getAnchorPoints(cfg); | |||
| // console.log(anchorPoints); | |||
| anchorPoints.forEach((anchorPos, i) => { | |||
| group.addShape('circle', { | |||
| attrs: { | |||
| r: 3, | |||
| x: bbox.x + bbox.width * anchorPos[0], | |||
| y: bbox.y + bbox.height * anchorPos[1], | |||
| fill: '#fff', | |||
| stroke: '#a4a4a5', | |||
| }, | |||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | |||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | |||
| links: 0, // cache the number of edges connected to this shape | |||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | |||
| }); | |||
| }); | |||
| return image; | |||
| }, | |||
| // response the state changes and show/hide the link-point circles | |||
| setState(name, value, item) { | |||
| const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | |||
| anchorPoints.forEach(point => { | |||
| if (value || point.get('links') > 0) point.show() | |||
| else point.hide() | |||
| }) | |||
| // } | |||
| // }) | |||
| // } | |||
| useEffect(() => { | |||
| initGraph(); | |||
| getFirstWorkflow(locationParams.workflowId); | |||
| }, []); | |||
| useEffect(() => { | |||
| // Update the refs whenever the state changes | |||
| messageRef.current = message; | |||
| }, [message]); | |||
| const initGraph = () => { | |||
| G6.registerNode( | |||
| 'rect-node', | |||
| { | |||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||
| getAnchorPoints(cfg) { | |||
| return ( | |||
| cfg.anchorPoints || [ | |||
| // 上下各3,左右各1 | |||
| [0.5, 0], | |||
| [0.5, 1], | |||
| ] | |||
| ); | |||
| }, | |||
| afterDraw(cfg, group) { | |||
| // console.log(group, cfg, 12312); | |||
| const image = group.addShape('image', { | |||
| attrs: { | |||
| x: -45, | |||
| y: -10, | |||
| width: 20, | |||
| height: 20, | |||
| img: cfg.img, | |||
| cursor: 'pointer', | |||
| }, | |||
| }, | |||
| 'rect' | |||
| ); | |||
| console.log(graphRef,'graphRef'); | |||
| graph = new G6.Graph({ | |||
| container: graphRef.current, | |||
| grid: true, | |||
| width: graphRef.current.clientWidth ||500, | |||
| height: graphRef.current.clientHeight||760, | |||
| animate: false, | |||
| groupByTypes: false, | |||
| enabledStack: true, | |||
| modes: { | |||
| default: [ | |||
| // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | |||
| { | |||
| type: 'drag-node', | |||
| shouldBegin: e => { | |||
| if (e.target.get('name') === 'anchor-point') return false; | |||
| return true; | |||
| draggable: true, | |||
| }); | |||
| // if (cfg.label) { | |||
| // group.addShape('text', { | |||
| // attrs: { | |||
| // x: 0, | |||
| // y: cfg.height / 2 - 5, | |||
| // textAlign: 'center', | |||
| // textBaseline: 'middle', | |||
| // text: cfg.label, | |||
| // fill: '#fff', | |||
| // }, | |||
| // draggable: true, | |||
| // }); | |||
| // } | |||
| const bbox = group.getBBox(); | |||
| const anchorPoints = this.getAnchorPoints(cfg); | |||
| // console.log(anchorPoints); | |||
| anchorPoints.forEach((anchorPos, i) => { | |||
| group.addShape('circle', { | |||
| attrs: { | |||
| r: 3, | |||
| x: bbox.x + bbox.width * anchorPos[0], | |||
| y: bbox.y + bbox.height * anchorPos[1], | |||
| fill: '#fff', | |||
| stroke: '#a4a4a5', | |||
| }, | |||
| // shouldEnd: e => { | |||
| // console.log(e); | |||
| // return false; | |||
| // }, | |||
| }, | |||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | |||
| 'drag-canvas', | |||
| 'zoom-canvas', | |||
| // 'brush-select', | |||
| 'drag-combo', | |||
| ], | |||
| altSelect: [ | |||
| { | |||
| type: 'brush-select', | |||
| trigger: 'drag', | |||
| }, | |||
| 'drag-node', | |||
| ], | |||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | |||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | |||
| links: 0, // cache the number of edges connected to this shape | |||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | |||
| }); | |||
| }); | |||
| return image; | |||
| }, | |||
| defaultNode: { | |||
| type: 'rect-node', | |||
| size: [110,36], | |||
| labelCfg: { | |||
| style: { | |||
| fill: '#000', | |||
| fontSize: 10, | |||
| cursor: 'pointer', | |||
| x: -20, | |||
| y: 0, | |||
| textAlign: 'left', | |||
| textBaseline: 'middle', | |||
| // response the state changes and show/hide the link-point circles | |||
| setState(name, value, item) { | |||
| const anchorPoints = item | |||
| .getContainer() | |||
| .findAll((ele) => ele.get('name') === 'anchor-point'); | |||
| anchorPoints.forEach((point) => { | |||
| if (value || point.get('links') > 0) point.show(); | |||
| else point.hide(); | |||
| }); | |||
| // } | |||
| }, | |||
| }, | |||
| 'rect', | |||
| ); | |||
| console.log(graphRef, 'graphRef'); | |||
| graph = new G6.Graph({ | |||
| container: graphRef.current, | |||
| grid: true, | |||
| width: graphRef.current.clientWidth || 500, | |||
| height: graphRef.current.clientHeight || 760, | |||
| animate: false, | |||
| groupByTypes: false, | |||
| enabledStack: true, | |||
| modes: { | |||
| default: [ | |||
| // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | |||
| { | |||
| type: 'drag-node', | |||
| shouldBegin: (e) => { | |||
| if (e.target.get('name') === 'anchor-point') return false; | |||
| return true; | |||
| }, | |||
| // shouldEnd: e => { | |||
| // console.log(e); | |||
| // return false; | |||
| // }, | |||
| }, | |||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | |||
| 'drag-canvas', | |||
| 'zoom-canvas', | |||
| // 'brush-select', | |||
| 'drag-combo', | |||
| ], | |||
| altSelect: [ | |||
| { | |||
| type: 'brush-select', | |||
| trigger: 'drag', | |||
| }, | |||
| 'drag-node', | |||
| ], | |||
| }, | |||
| defaultNode: { | |||
| type: 'rect-node', | |||
| size: [110, 36], | |||
| labelCfg: { | |||
| style: { | |||
| fill: '#fff', | |||
| stroke: '#fff', | |||
| radius:10, | |||
| lineWidth:0.5 | |||
| fill: '#000', | |||
| fontSize: 10, | |||
| cursor: 'pointer', | |||
| x: -20, | |||
| y: 0, | |||
| textAlign: 'left', | |||
| textBaseline: 'middle', | |||
| }, | |||
| }, | |||
| nodeStateStyles: { | |||
| nodeSelected: { | |||
| style: { | |||
| fill: '#fff', | |||
| stroke: '#fff', | |||
| radius: 10, | |||
| lineWidth: 0.5, | |||
| }, | |||
| }, | |||
| nodeStateStyles: { | |||
| nodeSelected: { | |||
| fill: 'red', | |||
| shadowColor: 'red', | |||
| stroke: 'red', | |||
| 'text-shape': { | |||
| fill: 'red', | |||
| shadowColor: 'red', | |||
| stroke: 'red', | |||
| 'text-shape': { | |||
| fill: 'red', | |||
| stroke: 'red', | |||
| }, | |||
| }, | |||
| }, | |||
| defaultEdge: { | |||
| // type: 'quadratic', | |||
| type: 'cubic-vertical', | |||
| }, | |||
| defaultEdge: { | |||
| // type: 'quadratic', | |||
| type: 'cubic-vertical', | |||
| style: { | |||
| endArrow: { // 设置终点箭头 | |||
| path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) | |||
| d: 4.5, | |||
| fill:'#a2a6b5' | |||
| }, | |||
| cursor: 'pointer', | |||
| lineWidth: 1, | |||
| opacity: 1, | |||
| stroke: '#a2a6b5', | |||
| radius: 1, | |||
| }, | |||
| nodeStateStyle: { | |||
| hover: { | |||
| opacity: 1, | |||
| stroke: '#8fe8ff', | |||
| }, | |||
| style: { | |||
| endArrow: { | |||
| // 设置终点箭头 | |||
| path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) | |||
| d: 4.5, | |||
| fill: '#a2a6b5', | |||
| }, | |||
| labelCfg: { | |||
| autoRotate: true, | |||
| // refY: 10, | |||
| style: { | |||
| fontSize: 10, | |||
| fill: '#FFF', | |||
| }, | |||
| cursor: 'pointer', | |||
| lineWidth: 1, | |||
| opacity: 1, | |||
| stroke: '#a2a6b5', | |||
| radius: 1, | |||
| }, | |||
| nodeStateStyle: { | |||
| hover: { | |||
| opacity: 1, | |||
| stroke: '#8fe8ff', | |||
| }, | |||
| }, | |||
| defaultCombo: { | |||
| type: 'rect', | |||
| fixCollapseSize: 70, | |||
| labelCfg: { | |||
| autoRotate: true, | |||
| // refY: 10, | |||
| style: { | |||
| fill: '#00e0ff0d', | |||
| stroke: '#00e0ff', | |||
| lineDash: [5, 10], | |||
| cursor: 'pointer', | |||
| fontSize: 10, | |||
| fill: '#FFF', | |||
| }, | |||
| }, | |||
| // linkCenter: true, | |||
| fitView: false, | |||
| fitViewPadding: [60, 60, 60, 80], | |||
| }); | |||
| graph.on('dblclick', handlerClick); | |||
| window.onresize = () => { | |||
| if (!graph || graph.get('destroyed')) return; | |||
| if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) return; | |||
| graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); | |||
| }; | |||
| } | |||
| return (<div className={pipelineContainer}> | |||
| <div className={Styles.centerContainer}> | |||
| }, | |||
| defaultCombo: { | |||
| type: 'rect', | |||
| fixCollapseSize: 70, | |||
| style: { | |||
| fill: '#00e0ff0d', | |||
| stroke: '#00e0ff', | |||
| lineDash: [5, 10], | |||
| cursor: 'pointer', | |||
| }, | |||
| }, | |||
| // linkCenter: true, | |||
| fitView: false, | |||
| fitViewPadding: [60, 60, 60, 80], | |||
| }); | |||
| graph.on('dblclick', handlerClick); | |||
| window.onresize = () => { | |||
| if (!graph || graph.get('destroyed')) return; | |||
| if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) | |||
| return; | |||
| graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); | |||
| }; | |||
| }; | |||
| return ( | |||
| <div className={pipelineContainer}> | |||
| <div className={Styles.centerContainer}> | |||
| <div className={Styles.buttonList}> | |||
| <div className={Styles.allMessageItem}>启动时间:{momnet(experimentAllMessage.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||
| <div className={Styles.allMessageItem}>执行时长:{experimentAllMessage.finish_time?timers(new Date(experimentAllMessage.finish_time).getTime()-new Date(experimentAllMessage.create_time).getTime()):timers(new Date().getTime()-new Date(experimentAllMessage.create_time).getTime())}</div> | |||
| <div className={Styles.allMessageItem}>状态: | |||
| <div style={{width:'8px', | |||
| height:'8px', | |||
| borderRadius:'50%', | |||
| marginRight:'6px', | |||
| backgroundColor:statusColorObj[experimentAllMessage.status] | |||
| }}></div> | |||
| <span style={{color:statusColorObj[experimentAllMessage.status]}}>{statusObj[experimentAllMessage.status]}</span></div> | |||
| <div className={Styles.allMessageItem}> | |||
| 启动时间:{momnet(message.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||
| </div> | |||
| <div className={Styles.allMessageItem}> | |||
| 执行时长: | |||
| {message.finish_time | |||
| ? elapsedTime(new Date(message.create_time), new Date(message.finish_time)) | |||
| : elapsedTime(new Date(message.create_time), new Date())} | |||
| </div> | |||
| <div className={Styles.allMessageItem}> | |||
| 状态: | |||
| <div | |||
| style={{ | |||
| width: '8px', | |||
| height: '8px', | |||
| borderRadius: '50%', | |||
| marginRight: '6px', | |||
| backgroundColor: experimentStatusInfo[message.status]?.color, | |||
| }} | |||
| ></div> | |||
| <span style={{ color: experimentStatusInfo[message.status]?.color }}> | |||
| {experimentStatusInfo[message.status]?.label} | |||
| </span> | |||
| </div> | |||
| </div> | |||
| <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | |||
| </div> | |||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||
| </div>)}; | |||
| export default ExperimentText; | |||
| </div> | |||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentText; | |||
| @@ -0,0 +1,34 @@ | |||
| .log_group { | |||
| padding-bottom: 10px; | |||
| } | |||
| .log_group_pod { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| padding: 15px; | |||
| background: rgba(234, 234, 234, 0.5); | |||
| cursor: pointer; | |||
| &_name { | |||
| margin-right: 10px; | |||
| color: #1d1d20; | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| .log_group_detail { | |||
| padding: 15px; | |||
| color: white; | |||
| font-size: 14px; | |||
| white-space: pre-line; | |||
| word-break: break-all; | |||
| background: #19253b; | |||
| } | |||
| .log_group_more_button { | |||
| display: flex; | |||
| justify-content: center; | |||
| color: white; | |||
| background: #19253b; | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| import { useStateRef } from '@/hooks'; | |||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | |||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | |||
| import { Button } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './logGroup.less'; | |||
| type LogGroupProps = { | |||
| log_type?: string; | |||
| pod_name?: string; | |||
| log_content?: string; | |||
| start_time?: string; | |||
| status: string; | |||
| }; | |||
| type Log = { | |||
| start_time: string; | |||
| log_content: string; | |||
| }; | |||
| function LogGroup({ | |||
| log_type = '', | |||
| pod_name = '', | |||
| log_content = '', | |||
| start_time = '', | |||
| status = '', | |||
| }: LogGroupProps) { | |||
| const [collapse, setCollapse] = useState(true); | |||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | |||
| const [completed, setCompleted] = useState(false); | |||
| useEffect(() => { | |||
| if (status === 'Running') { | |||
| const timerId = setInterval(() => { | |||
| requestExperimentPodsLog(); | |||
| }, 5000); | |||
| return () => { | |||
| clearInterval(timerId); | |||
| }; | |||
| } | |||
| }, []); | |||
| // 请求日志 | |||
| const requestExperimentPodsLog = async () => { | |||
| const list = logListRef.current; | |||
| const startTime = list.length > 0 ? list[list.length - 1].start_time : start_time; | |||
| const params = { | |||
| pod_name, | |||
| start_time: startTime, | |||
| }; | |||
| const res = await getExperimentPodsLog(params); | |||
| const { log_detail } = res.data; | |||
| if (log_detail && log_detail.log_content) { | |||
| setLogList((oldList) => oldList.concat(log_detail)); | |||
| } else { | |||
| setCompleted(true); | |||
| } | |||
| }; | |||
| // 请求实时日志 | |||
| // const requestExperimentPodsRealtimeLog = async () => { | |||
| // const params = { | |||
| // pod_name, | |||
| // namespace: namespace, | |||
| // container_name: log_type === 'resource' ? '' : 'main', | |||
| // }; | |||
| // const res = await getExperimentPodsRealtimeLog(params); | |||
| // const { log_detail } = res.data; | |||
| // if (log_detail && log_detail.log_content) { | |||
| // setLogList((list) => list.concat(log_detail)); | |||
| // } else { | |||
| // setCompleted(true); | |||
| // } | |||
| // }; | |||
| // 处理折叠 | |||
| const handleCollapse = async () => { | |||
| if (!collapse) { | |||
| setCollapse(true); | |||
| return; | |||
| } | |||
| if (logList.length === 0) { | |||
| try { | |||
| await requestExperimentPodsLog(); | |||
| setCollapse(false); | |||
| } catch (error) { | |||
| return Promise.reject(error); | |||
| } | |||
| } else { | |||
| setCollapse(false); | |||
| } | |||
| }; | |||
| // 加载更多 | |||
| const loadMore = () => { | |||
| requestExperimentPodsLog(); | |||
| }; | |||
| const showLog = (log_type === 'resource' && !collapse) || log_type === 'normal'; | |||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | |||
| const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; | |||
| return ( | |||
| <div className={styles.log_group}> | |||
| {log_type === 'resource' && ( | |||
| <div className={styles.log_group_pod} onClick={handleCollapse}> | |||
| <div className={styles.log_group_pod_name}>{pod_name}</div> | |||
| {collapse ? <DownOutlined /> : <UpOutlined />} | |||
| </div> | |||
| )} | |||
| {showLog && <div className={styles.log_group_detail}>{logText}</div>} | |||
| <div className={styles.log_group_more_button}> | |||
| {showMoreBtn && ( | |||
| <Button | |||
| type="text" | |||
| style={{ color: 'white' }} | |||
| icon={<DoubleRightOutlined rotate={90} />} | |||
| onClick={loadMore} | |||
| > | |||
| 更多 | |||
| </Button> | |||
| )} | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default LogGroup; | |||
| @@ -1,336 +1,424 @@ | |||
| import React, { useState,useImperativeHandle ,forwardRef } from 'react'; | |||
| import { Button, Drawer,Form, Input ,Tabs,message } from 'antd'; | |||
| import Styles from './editPipeline.less' | |||
| import{getQueryByExperimentLog,getNodeResult}from '@/services/experiment/index.js' | |||
| import { ProfileOutlined, DatabaseOutlined} from '@ant-design/icons'; | |||
| import {downLoadZip} from '@/utils/downloadfile' | |||
| import momnet from 'moment' | |||
| import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { downLoadZip } from '@/utils/downloadfile'; | |||
| import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
| import { Drawer, Form, Input, Tabs, message } from 'antd'; | |||
| import moment from 'moment'; | |||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||
| import LogList from './LogList'; | |||
| import Styles from './editPipeline.less'; | |||
| const { TextArea } = Input; | |||
| const Props = forwardRef(({onParentChange}, ref) =>{ | |||
| const [form] = Form.useForm(); | |||
| const [stagingItem,setStagingItem]=useState({}) | |||
| const [resultObj,setResultObj]=useState([]) | |||
| const [messageItem,setMessageItem]=useState('') | |||
| const statusObj={ | |||
| "Running":'运行中', | |||
| "Succeeded":'成功', | |||
| "Pending":'等待中', | |||
| "Failed":'失败', | |||
| "Error":'错误', | |||
| "Terminated":'终止', | |||
| "Skipped":'未执行', | |||
| "Omitted":'未执行', | |||
| } | |||
| const statusColorObj={ | |||
| "Running":'#165bff', | |||
| "Succeeded":'#63a728', | |||
| "Pending":'#f981eb', | |||
| "Failed":'#c73131', | |||
| "Error":'#c73131', | |||
| "Terminated":'#8a8a8a', | |||
| "Skipped":'#8a8a8a', | |||
| "Omitted":'#8a8a8ae', | |||
| } | |||
| const exportResult=(e,val)=>{ | |||
| const Props = forwardRef(({ onParentChange }, ref) => { | |||
| const [form] = Form.useForm(); | |||
| const [stagingItem, setStagingItem] = useState({}); | |||
| const [resultObj, setResultObj] = useState([]); | |||
| const [logList, setLogList] = useState([]); | |||
| const statusObj = { | |||
| Running: '运行中', | |||
| Succeeded: '成功', | |||
| Pending: '等待中', | |||
| Failed: '失败', | |||
| Error: '错误', | |||
| Terminated: '终止', | |||
| Skipped: '未执行', | |||
| Omitted: '未执行', | |||
| }; | |||
| const statusColorObj = { | |||
| Running: '#165bff', | |||
| Succeeded: '#63a728', | |||
| Pending: '#f981eb', | |||
| Failed: '#c73131', | |||
| Error: '#c73131', | |||
| Terminated: '#8a8a8a', | |||
| Skipped: '#8a8a8a', | |||
| Omitted: '#8a8a8ae', | |||
| }; | |||
| const exportResult = (e, val) => { | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/minioStorage/download`,{path:val}) | |||
| } | |||
| const timers=(time)=>{ | |||
| let timer=new Date(time) | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| } | |||
| const items = [ | |||
| { | |||
| key: '1', | |||
| label: '日志详情', | |||
| children: <div style={{height:'740px', | |||
| background:'#19253b', | |||
| color:'#fff', | |||
| fontSize:'14px' | |||
| }} dangerouslySetInnerHTML={{ __html: messageItem }}></div>, | |||
| icon:<ProfileOutlined/> | |||
| }, | |||
| { | |||
| key: '2', | |||
| label: '配置参数', | |||
| icon:<DatabaseOutlined />, | |||
| children: <Form | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| labelCol={{ | |||
| span: 16, | |||
| }} | |||
| wrapperCol={{ | |||
| span: 16, | |||
| }} | |||
| style={{ | |||
| maxWidth: 600, | |||
| }} | |||
| initialValues={{ | |||
| remember: true, | |||
| }} | |||
| onFinish={onFinish} | |||
| onFinishFailed={onFinishFailed} | |||
| autoComplete="off" | |||
| > | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/static-message.png'} alt="" /> | |||
| 基本信息 | |||
| </div> | |||
| <Form.Item | |||
| label="任务名称" | |||
| name="label" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="任务ID" | |||
| name="id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务id', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/duty-message.png'} alt="" /> | |||
| 任务信息 | |||
| </div> | |||
| <Form.Item | |||
| label="镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="工作目录" | |||
| name="working_directory" | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="启动命令" | |||
| name="command" | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="resources_standard" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="挂载路径" | |||
| name="mount_path" | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="环境变量" | |||
| name="env_variables" | |||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: val }); | |||
| }; | |||
| const timers = (time) => { | |||
| let timer = new Date(time); | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| }; | |||
| const items = [ | |||
| { | |||
| key: '1', | |||
| label: '日志详情', | |||
| children: <LogList list={logList} status={stagingItem.experimentStatus}></LogList>, | |||
| icon: <ProfileOutlined />, | |||
| }, | |||
| { | |||
| key: '2', | |||
| label: '配置参数', | |||
| icon: <DatabaseOutlined />, | |||
| children: ( | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| labelCol={{ | |||
| span: 16, | |||
| }} | |||
| wrapperCol={{ | |||
| span: 16, | |||
| }} | |||
| style={{ | |||
| maxWidth: 600, | |||
| }} | |||
| initialValues={{ | |||
| remember: true, | |||
| }} | |||
| onFinish={onFinish} | |||
| onFinishFailed={onFinishFailed} | |||
| autoComplete="off" | |||
| > | |||
| <TextArea disabled/> | |||
| </Form.Item> | |||
| {stagingItem.control_strategy&&Object.keys(stagingItem.control_strategy)&&Object.keys(stagingItem.control_strategy).length>0?Object.keys(stagingItem.control_strategy).map(item=> | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/static-message.png'} | |||
| alt="" | |||
| /> | |||
| 基本信息 | |||
| </div> | |||
| <Form.Item | |||
| label={stagingItem.control_strategy[item].label} | |||
| disabled | |||
| name={item} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| ):''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/duty-message.png'} alt="" /> | |||
| 输入参数 | |||
| </div> | |||
| {stagingItem.in_parameters&&Object.keys(stagingItem.in_parameters)&&Object.keys(stagingItem.in_parameters).length>0?Object.keys(stagingItem.in_parameters).map(item=> | |||
| label="任务名称" | |||
| name="label" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={stagingItem.in_parameters[item].label+'('+item+')'} | |||
| name={item} | |||
| disabled | |||
| rules={[{ required: stagingItem.in_parameters[item].require?true:false}]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| ):''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/duty-message.png'} alt="" /> | |||
| 输出参数 | |||
| </div> | |||
| {stagingItem.out_parameters&&Object.keys(stagingItem.out_parameters)&&Object.keys(stagingItem.out_parameters).length>0?Object.keys(stagingItem.out_parameters).map(item=> | |||
| label="任务ID" | |||
| name="id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务id', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/duty-message.png'} | |||
| alt="" | |||
| /> | |||
| 任务信息 | |||
| </div> | |||
| <Form.Item | |||
| label={stagingItem.out_parameters[item].label+'('+item+')'} | |||
| disabled | |||
| rules={[{ required: stagingItem.out_parameters[item].require?true:false}]} | |||
| name={item} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| ):''} | |||
| </Form>, | |||
| }, | |||
| { | |||
| key: '3', | |||
| label: '输出结果', | |||
| children: <div style={{minHeight:'740px', | |||
| background:'#f4f4f4', | |||
| color:'#000', | |||
| fontSize:'14px', | |||
| padding:'0 10px 20px 20px', | |||
| }} > | |||
| {resultObj&&resultObj.length>0?resultObj.map(item=><div> | |||
| <div className={Styles.resultTop}> | |||
| <span>{item.name}</span> | |||
| <div style={{display:'flex'}}> | |||
| <a onClick={(e)=>{exportResult(e,item.path)}} style={{marginRight:'10px'}}>下载</a> | |||
| <a style={{marginRight:'10px'}}>导出到模型库</a> | |||
| <a style={{marginRight:'10px'}}>导出到数据集</a> | |||
| </div> | |||
| label="镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="工作目录" name="working_directory"> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="启动命令" name="command"> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="resources_standard" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="挂载路径" name="mount_path"> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="环境变量" name="env_variables"> | |||
| <TextArea disabled /> | |||
| </Form.Item> | |||
| {stagingItem.control_strategy && | |||
| Object.keys(stagingItem.control_strategy) && | |||
| Object.keys(stagingItem.control_strategy).length > 0 | |||
| ? Object.keys(stagingItem.control_strategy).map((item) => ( | |||
| <Form.Item label={stagingItem.control_strategy[item].label} disabled name={item}> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| )) | |||
| : ''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/duty-message.png'} | |||
| alt="" | |||
| /> | |||
| 输入参数 | |||
| </div> | |||
| <div style={{margin:'15px 0'}} className={Styles.resultContent}> | |||
| <span>文件名称</span> | |||
| <span>文件大小</span> | |||
| {stagingItem.in_parameters && | |||
| Object.keys(stagingItem.in_parameters) && | |||
| Object.keys(stagingItem.in_parameters).length > 0 | |||
| ? Object.keys(stagingItem.in_parameters).map((item) => ( | |||
| <Form.Item | |||
| label={stagingItem.in_parameters[item].label + '(' + item + ')'} | |||
| name={item} | |||
| disabled | |||
| rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| )) | |||
| : ''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/duty-message.png'} | |||
| alt="" | |||
| /> | |||
| 输出参数 | |||
| </div> | |||
| {item.value&&item.value.length>0?item.value.map(ele=> | |||
| <div className={Styles.resultContent}> | |||
| <span>{ele.name}</span> | |||
| <span>{ele.size}</span> | |||
| </div>):null} | |||
| </div>):null} | |||
| </div>, | |||
| icon:<ProfileOutlined/> | |||
| }, | |||
| ]; | |||
| const [open, setOpen] = useState(false); | |||
| const afterOpenChange=()=>{ | |||
| if(!open){ | |||
| console.log(111,open); | |||
| console.log(stagingItem,form.getFieldsValue()); | |||
| for(let i in form.getFieldsValue()){ | |||
| for(let j in stagingItem.in_parameters){ | |||
| if(i==j){ | |||
| console.log(j,i); | |||
| stagingItem.in_parameters[j].value=form.getFieldsValue()[i] | |||
| } | |||
| {stagingItem.out_parameters && | |||
| Object.keys(stagingItem.out_parameters) && | |||
| Object.keys(stagingItem.out_parameters).length > 0 | |||
| ? Object.keys(stagingItem.out_parameters).map((item) => ( | |||
| <Form.Item | |||
| label={stagingItem.out_parameters[item].label + '(' + item + ')'} | |||
| disabled | |||
| rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]} | |||
| name={item} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| )) | |||
| : ''} | |||
| </Form> | |||
| ), | |||
| }, | |||
| { | |||
| key: '3', | |||
| label: '输出结果', | |||
| children: ( | |||
| <div | |||
| style={{ | |||
| minHeight: '740px', | |||
| background: '#f4f4f4', | |||
| color: '#000', | |||
| fontSize: '14px', | |||
| padding: '0 10px 20px 20px', | |||
| }} | |||
| > | |||
| {resultObj && resultObj.length > 0 | |||
| ? resultObj.map((item) => ( | |||
| <div> | |||
| <div className={Styles.resultTop}> | |||
| <span>{item.name}</span> | |||
| <div style={{ display: 'flex' }}> | |||
| <a | |||
| onClick={(e) => { | |||
| exportResult(e, item.path); | |||
| }} | |||
| style={{ marginRight: '10px' }} | |||
| > | |||
| 下载 | |||
| </a> | |||
| <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> | |||
| </div> | |||
| </div> | |||
| <div style={{ margin: '15px 0' }} className={Styles.resultContent}> | |||
| <span>文件名称</span> | |||
| <span>文件大小</span> | |||
| </div> | |||
| {item.value && item.value.length > 0 | |||
| ? item.value.map((ele) => ( | |||
| <div className={Styles.resultContent}> | |||
| <span>{ele.name}</span> | |||
| <span>{ele.size}</span> | |||
| </div> | |||
| )) | |||
| : null} | |||
| </div> | |||
| )) | |||
| : null} | |||
| </div> | |||
| ), | |||
| icon: <ProfileOutlined />, | |||
| }, | |||
| ]; | |||
| const [open, setOpen] = useState(false); | |||
| const afterOpenChange = () => { | |||
| if (!open) { | |||
| console.log(111, open); | |||
| console.log(stagingItem, form.getFieldsValue()); | |||
| for (let i in form.getFieldsValue()) { | |||
| for (let j in stagingItem.in_parameters) { | |||
| if (i == j) { | |||
| console.log(j, i); | |||
| stagingItem.in_parameters[j].value = form.getFieldsValue()[i]; | |||
| } | |||
| for(let p in stagingItem.out_parameters){ | |||
| if(i==p){ | |||
| stagingItem.out_parameters[p].value=form.getFieldsValue()[i] | |||
| } | |||
| } | |||
| for (let p in stagingItem.out_parameters) { | |||
| if (i == p) { | |||
| stagingItem.out_parameters[p].value = form.getFieldsValue()[i]; | |||
| } | |||
| for(let k in stagingItem.control_strategy){ | |||
| if(i==k){ | |||
| stagingItem.control_strategy[k].value=form.getFieldsValue()[i] | |||
| } | |||
| } | |||
| for (let k in stagingItem.control_strategy) { | |||
| if (i == k) { | |||
| stagingItem.control_strategy[k].value = form.getFieldsValue()[i]; | |||
| } | |||
| } | |||
| // setStagingItem({...stagingItem,}) | |||
| console.log((stagingItem.control_strategy)); | |||
| onParentChange({...stagingItem,control_strategy:JSON.stringify(stagingItem.control_strategy),in_parameters:JSON.stringify(stagingItem.in_parameters),out_parameters:JSON.stringify(stagingItem.out_parameters),...form.getFieldsValue()}) | |||
| // onParentChange({...stagingItem,...form.getFieldsValue()}) | |||
| } | |||
| // setStagingItem({...stagingItem,}) | |||
| console.log(stagingItem.control_strategy); | |||
| onParentChange({ | |||
| ...stagingItem, | |||
| control_strategy: JSON.stringify(stagingItem.control_strategy), | |||
| in_parameters: JSON.stringify(stagingItem.in_parameters), | |||
| out_parameters: JSON.stringify(stagingItem.out_parameters), | |||
| ...form.getFieldsValue(), | |||
| }); | |||
| // onParentChange({...stagingItem,...form.getFieldsValue()}) | |||
| } | |||
| const onClose=()=> { | |||
| setOpen(false); | |||
| }; | |||
| const onFinish = (values) => { | |||
| console.log('Success:', values); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useImperativeHandle(ref, () => ({ | |||
| showDrawer (e,id) { | |||
| setMessageItem('') | |||
| if(e.item&&e.item.getModel().component_id){ | |||
| let params={ | |||
| id:Number(id), | |||
| component_id:e.item&&e.item.getModel().component_id | |||
| } | |||
| getQueryByExperimentLog(params).then(ret=>{ | |||
| console.log(ret); | |||
| getNodeResult({id,node_id:e.item.getModel().id}).then(res=>{ | |||
| setResultObj(res.data) | |||
| let msg=ret.msg.replace(/\n/g,"</br>") | |||
| let newMsg=msg.replace(/\r/g,"</br>") | |||
| setMessageItem(newMsg) | |||
| form.resetFields(); | |||
| form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| setOpen(true); | |||
| }) | |||
| }) | |||
| }; | |||
| const onClose = () => { | |||
| setOpen(false); | |||
| }; | |||
| const onFinish = (values) => { | |||
| console.log('Success:', values); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useImperativeHandle(ref, () => ({ | |||
| showDrawer(e, id, message) { | |||
| setLogList([]); | |||
| if (e.item && e.item.getModel().component_id) { | |||
| const model = e.item.getModel() || {}; | |||
| const start_time = moment(model.experimentStartTime).valueOf() * 1.0e6; | |||
| const params = { | |||
| task_id: model.id, | |||
| component_id: model.component_id, | |||
| name: message.argo_ins_name, | |||
| namespace: message.argo_ins_ns, | |||
| start_time: start_time, | |||
| }; | |||
| getQueryByExperimentLog(params).then((ret) => { | |||
| const { log_type, pods, log_detail } = ret.data; | |||
| if (log_type === 'normal') { | |||
| const list = [ | |||
| { | |||
| ...log_detail, | |||
| log_type, | |||
| }, | |||
| ]; | |||
| setLogList(list); | |||
| } else if (log_type === 'resource') { | |||
| const list = pods.map((v) => ({ | |||
| log_type, | |||
| pod_name: v, | |||
| log_content: '', | |||
| start_time, | |||
| })); | |||
| setLogList(list); | |||
| } | |||
| else{ | |||
| getNodeResult({ id, node_id: e.item.getModel().id }).then((res) => { | |||
| setResultObj(res.data); | |||
| form.resetFields(); | |||
| form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| form.setFieldsValue({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setStagingItem({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setOpen(true); | |||
| } | |||
| // console.log(e.item.getModel().in_parameters); | |||
| }, | |||
| })); | |||
| }); | |||
| }); | |||
| } else { | |||
| form.resetFields(); | |||
| form.setFieldsValue({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setStagingItem({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setOpen(true); | |||
| } | |||
| // console.log(e.item.getModel().in_parameters); | |||
| }, | |||
| })); | |||
| return ( | |||
| <> | |||
| <Drawer title="任务执行详情" placement="right" closeIcon={false} onClose={onClose} afterOpenChange={afterOpenChange} open={open}> | |||
| <div className={Styles.detailBox}>任务名称:{stagingItem.label}</div> | |||
| <div className={Styles.detailBox} >执行状态: | |||
| <div style={{width:'8px', | |||
| height:'8px', | |||
| borderRadius:'50%', | |||
| marginRight:'6px', | |||
| backgroundColor:statusColorObj[stagingItem.experimentStatus] | |||
| }}></div> | |||
| <span style={{color:statusColorObj[stagingItem.experimentStatus]}}>{statusObj[stagingItem.experimentStatus]}</span></div> | |||
| <div className={Styles.detailBox}>启动时间:{momnet(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')}</div> | |||
| <div className={Styles.detailBox}>耗时:{stagingItem.experimentEndTime?timers(new Date(stagingItem.experimentEndTime).getTime()-new Date(stagingItem.experimentStartTime).getTime()):timers(new Date().getTime()-new Date(stagingItem.experimentStartTime).getTime())}</div> | |||
| <Tabs | |||
| defaultActiveKey="1" | |||
| items={items}/> | |||
| <Drawer | |||
| title="任务执行详情" | |||
| placement="right" | |||
| closeIcon={false} | |||
| onClose={onClose} | |||
| afterOpenChange={afterOpenChange} | |||
| open={open} | |||
| width={600} | |||
| destroyOnClose={true} | |||
| > | |||
| <div className={Styles.detailBox}>任务名称:{stagingItem.label}</div> | |||
| <div className={Styles.detailBox}> | |||
| 执行状态: | |||
| <div | |||
| style={{ | |||
| width: '8px', | |||
| height: '8px', | |||
| borderRadius: '50%', | |||
| marginRight: '6px', | |||
| backgroundColor: statusColorObj[stagingItem.experimentStatus], | |||
| }} | |||
| ></div> | |||
| <span style={{ color: statusColorObj[stagingItem.experimentStatus] }}> | |||
| {statusObj[stagingItem.experimentStatus]} | |||
| </span> | |||
| </div> | |||
| <div className={Styles.detailBox}> | |||
| 启动时间:{moment(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')} | |||
| </div> | |||
| <div className={Styles.detailBox}> | |||
| 耗时: | |||
| {stagingItem.experimentEndTime | |||
| ? elapsedTime( | |||
| new Date(stagingItem.experimentStartTime), | |||
| new Date(stagingItem.experimentEndTime), | |||
| ) | |||
| : elapsedTime(new Date(stagingItem.experimentStartTime), new Date())} | |||
| </div> | |||
| <Tabs defaultActiveKey="1" items={items} /> | |||
| </Drawer> | |||
| </> | |||
| ); | |||
| @@ -1,484 +1,445 @@ | |||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||
| import { Space, Table, Tag,Button,Modal, Form, Input ,message, Select,} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, EditOutlined ,PlayCircleOutlined,DeleteOutlined,FieldTimeOutlined} from '@ant-design/icons'; | |||
| import {getWorkflow,} from '@/services/pipeline/index.js' | |||
| import {getExperiment,runExperiments,getExperimentById,postExperiment,putExperiment,getQueryByExperimentId,deleteExperimentById,deleteQueryByExperimentInsId,putQueryByExperimentInsId} from '@/services/experiment/index.js' | |||
| import Styles from './index.less' | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import momnet from 'moment' | |||
| const { TextArea } = Input; | |||
| import { | |||
| deleteExperimentById, | |||
| deleteQueryByExperimentInsId, | |||
| getExperiment, | |||
| getExperimentById, | |||
| getQueryByExperimentId, | |||
| postExperiment, | |||
| putExperiment, | |||
| putQueryByExperimentInsId, | |||
| runExperiments, | |||
| } from '@/services/experiment/index.js'; | |||
| import { getWorkflow } from '@/services/pipeline/index.js'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { | |||
| DeleteOutlined, | |||
| EditOutlined, | |||
| FieldTimeOutlined, | |||
| PlayCircleOutlined, | |||
| PlusCircleOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { Button, Modal, Space, Table, message } from 'antd'; | |||
| import momnet from 'moment'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import AddExperimentModal from './experimentText/addExperimentModal'; | |||
| import Styles from './index.less'; | |||
| import { experimentStatusInfo } from './status'; | |||
| const Experiment = React.FC = () => { | |||
| const [form] = Form.useForm(); | |||
| const navgite=useNavigate(); | |||
| const statusObj={ | |||
| "Running":'运行中', | |||
| "Succeeded":'成功', | |||
| "Pending":'等待中', | |||
| "Failed":'失败', | |||
| "Error":'错误', | |||
| "Terminated":'终止', | |||
| "Skipped":'未执行', | |||
| "Omitted":'未执行', | |||
| } | |||
| const statusColorObj={ | |||
| "Running":'#165bff', | |||
| "Succeeded":'#63a728', | |||
| "Pending":'#f981eb', | |||
| "Failed":'#c73131', | |||
| "Error":'#c73131', | |||
| "Terminated":'#8a8a8a', | |||
| "Skipped":'#8a8a8a', | |||
| "Omitted":'#8a8a8ae', | |||
| } | |||
| const statusImgObj={ | |||
| 'Running':'/assets/images/running-icon.png', | |||
| 'Succeeded':'/assets/images/success-icon.png', | |||
| 'Pending':'/assets/images/pending-icon.png', | |||
| 'Failed':'/assets/images/fail-icon.png', | |||
| 'Terminated':'/assets/images/omitted-icon.png', | |||
| 'Skipped':'/assets/images/omitted-icon.png', | |||
| 'Omitted':'/assets/images/omitted-icon.png', | |||
| } | |||
| const [experimentList, setExperimentList] = useState([]); | |||
| const [workflowList, setWorkflowList] = useState([]); | |||
| const [queryFlow,setQueryFlow]=useState({ | |||
| offset:1, | |||
| page:0, | |||
| size:10000, | |||
| name:null | |||
| }); | |||
| const [disableFlag,setDisableFlag]=useState(false) | |||
| const timers=(time)=>{ | |||
| let timer=new Date(time) | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| } | |||
| const [formId,setFormId]=useState(null) | |||
| const [experimentInList,setExperimentInList]=useState([]) | |||
| const [expandedRowKeys,setExpandedRowKeys]=useState(null) | |||
| const [total, setTotal] = useState(0); | |||
| const [dialogTitle, setDialogTitle] = useState('新建实验'); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const getWorkflowList=()=>{ | |||
| getWorkflow(queryFlow).then(ret=>{ | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setWorkflowList(ret.data.content) | |||
| } | |||
| }) | |||
| function Experiment() { | |||
| const navgite = useNavigate(); | |||
| const [experimentList, setExperimentList] = useState([]); | |||
| const [workflowList, setWorkflowList] = useState([]); | |||
| const [queryFlow, setQueryFlow] = useState({ | |||
| offset: 1, | |||
| page: 0, | |||
| size: 10000, | |||
| name: null, | |||
| }); | |||
| const [experimentId, setExperimentId] = useState(null); | |||
| const [experimentInList, setExperimentInList] = useState([]); | |||
| const [expandedRowKeys, setExpandedRowKeys] = useState(null); | |||
| const [total, setTotal] = useState(0); | |||
| const [isAdd, setIsAdd] = useState(true); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [addFormData, setAddFormData] = useState({}); | |||
| useEffect(() => { | |||
| getList(); | |||
| getWorkflowList(); | |||
| }, []); | |||
| // 获取实验列表 | |||
| const getList = async () => { | |||
| const params = { | |||
| offset: 0, | |||
| page: pageOption.current.page - 1, | |||
| size: pageOption.current.size, | |||
| }; | |||
| const [res, _] = await to(getExperiment(params)); | |||
| if (res && res.data && Array.isArray(res.data.content)) { | |||
| setExperimentList( | |||
| res.data.content.map((item) => { | |||
| return { ...item, key: item.id }; | |||
| }), | |||
| ); | |||
| setTotal(res.data.totalElements); | |||
| } | |||
| const getQueryByExperiment=(val)=>{ | |||
| getQueryByExperimentId(val).then(ret=>{ | |||
| setExpandedRowKeys(val) | |||
| if(ret.code==200&&ret.data&&ret.data.length>0){ | |||
| setExperimentInList(ret.data) | |||
| getList() | |||
| } | |||
| else{ | |||
| setExperimentInList([]) | |||
| getList() | |||
| } | |||
| }) | |||
| }; | |||
| // 获取流水线列表 | |||
| const getWorkflowList = async () => { | |||
| const [res, _] = await to(getWorkflow(queryFlow)); | |||
| if (res && res.data && res.data.content) { | |||
| setWorkflowList(res.data.content); | |||
| } | |||
| const expandChange=(e,record)=>{ | |||
| if(record.id==expandedRowKeys){ | |||
| setExpandedRowKeys(null) | |||
| } | |||
| else{ | |||
| getQueryByExperiment(record.id) | |||
| }; | |||
| const getQueryByExperiment = (val) => { | |||
| getQueryByExperimentId(val).then((ret) => { | |||
| setExpandedRowKeys(val); | |||
| if (ret.code === 200 && ret.data && ret.data.length > 0) { | |||
| setExperimentInList(ret.data); | |||
| getList(); | |||
| } else { | |||
| setExperimentInList([]); | |||
| getList(); | |||
| } | |||
| }); | |||
| }; | |||
| const expandChange = (e, record) => { | |||
| if (record.id === expandedRowKeys) { | |||
| setExpandedRowKeys(null); | |||
| } else { | |||
| getQueryByExperiment(record.id); | |||
| } | |||
| const showModal = () => { | |||
| setDialogTitle('新建实验') | |||
| setDisableFlag(false) | |||
| console.log(workflowList); | |||
| }; | |||
| // 创建实验 | |||
| const createExperiment = () => { | |||
| setIsAdd(true); | |||
| setAddFormData({}); | |||
| setExperimentId(null); | |||
| setIsModalOpen(true); | |||
| }; | |||
| // 编辑实验 | |||
| const editExperiment = (id) => { | |||
| getExperimentById(id).then((res) => { | |||
| setAddFormData({ | |||
| ...res.data, | |||
| }); | |||
| setExperimentId(res.data.id); | |||
| setIsAdd(false); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const editTable=(id)=>{ | |||
| getExperimentById(id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| form.setFieldsValue({...ret.data}) | |||
| setDisableFlag(true) | |||
| setFormId(ret.data.id) | |||
| setDialogTitle('编辑实验') | |||
| getWorkflowList() | |||
| setIsModalOpen(true) | |||
| } | |||
| }) | |||
| // navgite({pathname:`/pipeline/pytorchtext/${record.id}/${record.name}` }); | |||
| }); | |||
| }; | |||
| // 创建或编辑实验取消 | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const routeToEdit = (e, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: `/pipeline/pytorchtext/${record.workflow_id}/${record.workflow_name}` }); | |||
| }; | |||
| // 创建或者编辑实验接口请求 | |||
| const handleAddExperiment = async (values) => { | |||
| const workflow_id = values['workflow_id']; | |||
| let global_param = undefined; | |||
| const pipeline = workflowList.find((v) => v.id === workflow_id); | |||
| if (pipeline && pipeline.global_param) { | |||
| const globalParamList = [...pipeline.global_param]; | |||
| for (const item of globalParamList) { | |||
| item.param_value = values[item.param_name]; | |||
| values[item.param_name] = undefined; | |||
| } | |||
| global_param = JSON.stringify(globalParamList); | |||
| } | |||
| const handleOk = () => { | |||
| console.log(1111); | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| const params = { | |||
| ...values, | |||
| global_param, | |||
| }; | |||
| const routeToEdit=(e,record)=>{ | |||
| e.stopPropagation() | |||
| navgite({pathname:`/pipeline/pytorchtext/${record.workflow_id}/${record.workflow_name}` }); | |||
| } | |||
| const onFinish = (values) => { | |||
| console.log(values,formId); | |||
| if(!formId){ | |||
| postExperiment(values).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('新建实验成功') | |||
| setIsModalOpen(false) | |||
| getList() | |||
| } | |||
| else{ | |||
| message.error('新建实验失败') | |||
| } | |||
| }) | |||
| if (!experimentId) { | |||
| const [res, _] = await to(postExperiment(params)); | |||
| if (res) { | |||
| message.success('新建实验成功'); | |||
| setIsModalOpen(false); | |||
| getList(); | |||
| } | |||
| else{ | |||
| putExperiment({...values,id:formId}).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('编辑实验成功') | |||
| setIsModalOpen(false) | |||
| getList() | |||
| } | |||
| else{ | |||
| message.error('编辑实验失败') | |||
| } | |||
| }) | |||
| } else { | |||
| const params = { ...values, id: experimentId }; | |||
| const [res, _] = await to(putExperiment(params)); | |||
| if (res) { | |||
| message.success('编辑实验成功'); | |||
| setIsModalOpen(false); | |||
| getList(); | |||
| } | |||
| setFormId(null) | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| const pageOption = useRef({page: 1,size: 10}) | |||
| const paginationProps = { | |||
| 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 pageOption = useRef({ page: 1, size: 10 }); | |||
| const paginationProps = { | |||
| 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, | |||
| }; | |||
| getList(); | |||
| }; | |||
| const runExperiment = (id) => { | |||
| runExperiments(id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('运行成功'); | |||
| getQueryByExperiment(id); | |||
| } else { | |||
| message.error('运行失败'); | |||
| } | |||
| getList() | |||
| } | |||
| const runExperiment=(id)=>{ | |||
| runExperiments(id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('运行成功') | |||
| getQueryByExperiment(id) | |||
| } | |||
| else{ | |||
| message.error('运行失败') | |||
| } | |||
| }) | |||
| } | |||
| const routerToText=(e,item,record)=>{ | |||
| e.stopPropagation() | |||
| navgite({pathname:`/experiment/pytorchtext/${record.workflow_id}/${item.id}` }); | |||
| } | |||
| const getList=()=>{ | |||
| let params={ | |||
| offset:0, | |||
| page:pageOption.current.page-1, | |||
| size:pageOption.current.size | |||
| } | |||
| console.log(params,pageOption); | |||
| getExperiment(params).then(ret=>{ | |||
| if(ret.code==200){ | |||
| setExperimentList(ret.data.content.map(item=>{return{...item,key:item.id}})) | |||
| setTotal(ret.data.totalElements) | |||
| } | |||
| console.log(experimentList,total); | |||
| }) | |||
| } | |||
| useEffect(()=>{ | |||
| getList() | |||
| getWorkflowList() | |||
| // const timeOut= setInterval(() => { | |||
| // getList() | |||
| // }, 2000); | |||
| return () => { | |||
| // timeOut&&clearInterval(timeOut) | |||
| // console.log('will unmount'); | |||
| } | |||
| },[]) | |||
| const columns = [ | |||
| // { | |||
| // title: '序号', | |||
| // dataIndex: 'index', | |||
| // key: 'index', | |||
| // width: 60, | |||
| // render(text, record, index) { | |||
| // return ( | |||
| // <span>{(pageOption.current.page - 1) * 10 + index + 1}</span> | |||
| // ) | |||
| // } | |||
| // // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | |||
| // }, | |||
| { | |||
| title: '实验名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: (text) => <div >{text}</div>, | |||
| }, | |||
| { | |||
| title: '关联流水线名称', | |||
| dataIndex: 'workflow_name', | |||
| key: 'workflow_name', | |||
| render: (text,record) => <a onClick={(e)=>routeToEdit(e,record)}>{text}</a>, | |||
| }, | |||
| { | |||
| title: '实验描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| }, | |||
| { | |||
| title: '最近五次运行状态', | |||
| dataIndex: 'status_list', | |||
| key: 'status_list', | |||
| render: (text) => { | |||
| }); | |||
| }; | |||
| const routerToText = (e, item, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: `/experiment/pytorchtext/${record.workflow_id}/${item.id}` }); | |||
| }; | |||
| let newText=text&&text.replace(/\s+/g,'').split(',') | |||
| console.log(newText); | |||
| return <>{ newText&&newText.length>0?newText.map((item,index)=>{console.log(item,statusImgObj[item]); return <img style={{width:'17px',marginRight:'6px'}} key={index} src={statusImgObj[item]} />}):null}</> | |||
| } | |||
| }, | |||
| { | |||
| title: '操作', | |||
| key: 'action', | |||
| width:300, | |||
| render: (_, record) => ( | |||
| <Space size="small"> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon = {<PlayCircleOutlined />} | |||
| onClick={() => { | |||
| runExperiment(record.id) | |||
| }} | |||
| > | |||
| 运行 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon = {< EditOutlined />} | |||
| onClick={() => { | |||
| editTable(record.id) | |||
| }} | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{color:'#f98e1b'}} | |||
| icon = {< DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实验吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| const columns = [ | |||
| { | |||
| title: '实验名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: (text) => <div>{text}</div>, | |||
| }, | |||
| { | |||
| title: '关联流水线名称', | |||
| dataIndex: 'workflow_name', | |||
| key: 'workflow_name', | |||
| render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | |||
| }, | |||
| { | |||
| title: '实验描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| }, | |||
| { | |||
| title: '最近五次运行状态', | |||
| dataIndex: 'status_list', | |||
| key: 'status_list', | |||
| render: (text) => { | |||
| let newText = text && text.replace(/\s+/g, '').split(','); | |||
| console.log(newText); | |||
| return ( | |||
| <> | |||
| {newText && newText.length > 0 | |||
| ? newText.map((item, index) => { | |||
| return ( | |||
| <img | |||
| style={{ width: '17px', marginRight: '6px' }} | |||
| key={index} | |||
| src={experimentStatusInfo[item].icon} | |||
| /> | |||
| ); | |||
| }) | |||
| : null} | |||
| </> | |||
| ); | |||
| }, | |||
| }, | |||
| onOk: () => { | |||
| console.log(record); | |||
| deleteExperimentById(record.id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('删除成功') | |||
| getList() | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| }); | |||
| // if (success) { | |||
| // if (actionRef.current) { | |||
| // actionRef.current.reload(); | |||
| // } | |||
| // } | |||
| }, | |||
| }); | |||
| }} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| return (<div> | |||
| {/* <div > | |||
| <Button type="primary" onClick={showModal} icon = {< PlusOutlined />}> | |||
| { | |||
| title: '操作', | |||
| key: 'action', | |||
| width: 300, | |||
| render: (_, record) => ( | |||
| <Space size="small"> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="run" | |||
| icon={<PlayCircleOutlined />} | |||
| onClick={() => { | |||
| runExperiment(record.id); | |||
| }} | |||
| > | |||
| 运行 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon={<EditOutlined />} | |||
| onClick={() => { | |||
| editExperiment(record.id); | |||
| }} | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{ color: '#f98e1b' }} | |||
| icon={<DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实验吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| console.log(record); | |||
| deleteExperimentById(record.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('删除成功'); | |||
| getList(); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| // if (success) { | |||
| // if (actionRef.current) { | |||
| // actionRef.current.reload(); | |||
| // } | |||
| // } | |||
| }, | |||
| }); | |||
| }} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div> | |||
| {/* <div > | |||
| <Button type="primary" onClick={createExperiment} icon = {< PlusOutlined />}> | |||
| 新建实验 | |||
| </Button> | |||
| </div> */} | |||
| <div className={Styles.pipelineTopBox}> | |||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||
| 新建实验 | |||
| </Button> | |||
| </div> | |||
| <Table columns={columns} dataSource={experimentList} pagination={paginationProps} expandable={{ | |||
| expandedRowRender: (record) => ( | |||
| <div> | |||
| {experimentInList&&experimentInList.length>0?<div className={Styles.tableExpandBox} style={{paddingBottom:'16px'}}> | |||
| <div style={{width:'50px'}}>序号</div> | |||
| <div style={{width:'200px'}}>状态</div> | |||
| <div style={{width:'300px'}}>运行时长</div> | |||
| <div style={{width:'300px'}}>开始时间</div> | |||
| <div style={{width:'200px'}}>操作</div> | |||
| </div>:''} | |||
| {experimentInList&&experimentInList.length>0?experimentInList.map((item,index)=>( | |||
| <div className={Styles.tableExpandBox} style={{border:'1px solid #eaeaea',backgroundColor:'#fff',height:'45px'}}> | |||
| <a style={{width:'50px'}} onClick={(e)=>routerToText(e,item,record)}>{index+1}</a> | |||
| <div className={Styles.statusBox} style={{width:'200px'}}><img style={{width:'17px',marginRight:'7px'}} src={statusImgObj[item.status]}/> <span style={{color:statusColorObj[item.status]}} className={Styles.statusIcon}>{statusObj[item.status]}</span></div> | |||
| <div style={{width:'300px'}}>{item.finish_time?timers(new Date(item.finish_time).getTime()-new Date(item.create_time).getTime()):timers(new Date().getTime()-new Date(item.create_time).getTime())}</div> | |||
| <div style={{width:'300px'}}>{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||
| <div style={{width:'200px'}}> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="batchRemove" | |||
| disabled={item.status=='Succeeded'||item.status=='Failed'||item.status=='Terminated'} | |||
| icon = {<FieldTimeOutlined />} | |||
| onClick={async () => { | |||
| putQueryByExperimentInsId(item.id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('终止成功') | |||
| getQueryByExperiment(record.id) | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| }) | |||
| }} | |||
| > | |||
| 终止 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{color:'#f98e1b'}} | |||
| disabled={item.status=='Running'||item.status=='Pending'} | |||
| icon = {< DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实例吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| deleteQueryByExperimentInsId(item.id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('删除成功') | |||
| getQueryByExperiment(record.id) | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| }} | |||
| <div className={Styles.pipelineTopBox}> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| onClick={createExperiment} | |||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 删除 | |||
| 新建实验 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| )):''} | |||
| </div> | |||
| </div> | |||
| <Table | |||
| columns={columns} | |||
| dataSource={experimentList} | |||
| pagination={paginationProps} | |||
| expandable={{ | |||
| expandedRowRender: (record) => ( | |||
| <div> | |||
| {experimentInList && experimentInList.length > 0 ? ( | |||
| <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | |||
| <div style={{ width: '50px' }}>序号</div> | |||
| <div style={{ width: '200px' }}>状态</div> | |||
| <div style={{ width: '300px' }}>运行时长</div> | |||
| <div style={{ width: '300px' }}>开始时间</div> | |||
| <div style={{ width: '200px' }}>操作</div> | |||
| </div> | |||
| ) : ( | |||
| '' | |||
| )} | |||
| ), | |||
| onExpand:(e,a)=>{expandChange(e,a)}, | |||
| expandedRowKeys:[expandedRowKeys], | |||
| rowExpandable: (record) =>true, | |||
| }}/> | |||
| <Modal className={Styles.modal} title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| initialValues={{ | |||
| remember: true, | |||
| }} | |||
| onFinish={onFinish} | |||
| onFinishFailed={onFinishFailed} | |||
| autoComplete="off" | |||
| > | |||
| <Form.Item | |||
| label="实验名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| > | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="实验描述" | |||
| name="description" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| > | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="选择流水线" | |||
| name="workflow_id" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| > | |||
| <Select disabled={disableFlag}> | |||
| {workflowList&&workflowList.length>0?workflowList.map(item=>{return <Select.Option value={item.id}>{item.name}</Select.Option>}):''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </Select> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </div>)}; | |||
| export default Experiment; | |||
| {experimentInList && experimentInList.length > 0 | |||
| ? experimentInList.map((item, index) => ( | |||
| <div | |||
| key={item.id} | |||
| className={Styles.tableExpandBox} | |||
| style={{ | |||
| border: '1px solid #eaeaea', | |||
| backgroundColor: '#fff', | |||
| height: '45px', | |||
| }} | |||
| > | |||
| <a style={{ width: '50px' }} onClick={(e) => routerToText(e, item, record)}> | |||
| {index + 1} | |||
| </a> | |||
| <div className={Styles.statusBox} style={{ width: '200px' }}> | |||
| <img | |||
| style={{ width: '17px', marginRight: '7px' }} | |||
| src={experimentStatusInfo[item.status]?.icon} | |||
| />{' '} | |||
| <span | |||
| style={{ color: experimentStatusInfo[item.status]?.color }} | |||
| className={Styles.statusIcon} | |||
| > | |||
| {experimentStatusInfo[item.status]?.label} | |||
| </span> | |||
| </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' }}> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="stop" | |||
| disabled={ | |||
| item.status === 'Succeeded' || | |||
| item.status === 'Failed' || | |||
| item.status === 'Terminated' | |||
| } | |||
| icon={<FieldTimeOutlined />} | |||
| onClick={async () => { | |||
| putQueryByExperimentInsId(item.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('终止成功'); | |||
| getQueryByExperiment(record.id); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }} | |||
| > | |||
| 终止 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{ color: '#f98e1b' }} | |||
| disabled={item.status === 'Running' || item.status === 'Pending'} | |||
| icon={<DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实例吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| deleteQueryByExperimentInsId(item.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('删除成功'); | |||
| getQueryByExperiment(record.id); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| }} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| )) | |||
| : ''} | |||
| </div> | |||
| ), | |||
| onExpand: (e, a) => { | |||
| expandChange(e, a); | |||
| }, | |||
| expandedRowKeys: [expandedRowKeys], | |||
| rowExpandable: (record) => true, | |||
| }} | |||
| /> | |||
| <AddExperimentModal | |||
| isAdd={isAdd} | |||
| open={isModalOpen} | |||
| initialValues={addFormData} | |||
| onCancel={handleCancel} | |||
| onFinish={handleAddExperiment} | |||
| workflowList={workflowList} | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default Experiment; | |||
| @@ -1,102 +1,59 @@ | |||
| .experimentTopBox{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| padding-right: 30px; | |||
| width: 100%; | |||
| height: 49px; | |||
| background-size: 100% 100%; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| .experimentTopBox { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| width: 100%; | |||
| height: 49px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-size: 100% 100%; | |||
| } | |||
| .pipelineTopBox{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| padding-right: 30px; | |||
| width: 100%; | |||
| height: 49px; | |||
| background-size: 100% 100%; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| margin-bottom: 10px; | |||
| .pipelineTopBox { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| width: 100%; | |||
| height: 49px; | |||
| margin-bottom: 10px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-size: 100% 100%; | |||
| } | |||
| .plusButton{ | |||
| background:rgba(22, 100, 255, 0.06); | |||
| border:1px solid; | |||
| border-color:rgba(22, 100, 255, 0.11); | |||
| border-radius:4px; | |||
| color:#1d1d20; | |||
| font-size:14px; | |||
| font-family: 'Alibaba'; | |||
| .plusButton { | |||
| color: #1d1d20; | |||
| font-size: 14px; | |||
| font-family: 'Alibaba'; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border: 1px solid; | |||
| border-color: rgba(22, 100, 255, 0.11); | |||
| border-radius: 4px; | |||
| } | |||
| .plusButton:hover{ | |||
| background:rgba(22, 100, 255, 0.06)!important; | |||
| border:1px solid!important; | |||
| border-color:rgba(22, 100, 255, 0.11)!important; | |||
| color:#1d1d20!important; | |||
| .plusButton:hover { | |||
| color: #1d1d20 !important; | |||
| background: rgba(22, 100, 255, 0.06) !important; | |||
| border: 1px solid !important; | |||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||
| } | |||
| .tableExpandBox{ | |||
| width: 100%; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| color:#1d1d20; | |||
| font-size:15px; | |||
| padding: 0 65px 0 40px; | |||
| .tableExpandBox { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| width: 100%; | |||
| padding: 0 65px 0 40px; | |||
| color: #1d1d20; | |||
| font-size: 15px; | |||
| } | |||
| .statusBox{ | |||
| display: flex; | |||
| align-items: center; | |||
| .statusBox { | |||
| display: flex; | |||
| align-items: center; | |||
| .statusIcon{ | |||
| visibility: hidden; | |||
| transition: all 0.2s; | |||
| } | |||
| .statusIcon { | |||
| visibility: hidden; | |||
| transition: all 0.2s; | |||
| } | |||
| } | |||
| .statusBox:hover .statusIcon{ | |||
| visibility: visible; | |||
| .statusBox:hover .statusIcon { | |||
| visibility: visible; | |||
| } | |||
| .modal { | |||
| :global { | |||
| .ant-modal-content { | |||
| background:linear-gradient(180deg,#cfdfff 0%,#d4e2ff 9.77%,#ffffff 40%,#ffffff 100%); | |||
| border-radius:21px; | |||
| padding: 20px 67px; | |||
| width: 825px; | |||
| } | |||
| .ant-modal-header{ | |||
| background-color: transparent; | |||
| margin: 20px 0; | |||
| } | |||
| .ant-input{ | |||
| border-color:#e6e6e6; | |||
| height: 40px; | |||
| } | |||
| .ant-select-single{ | |||
| height: 40px; | |||
| } | |||
| .ant-form-item .ant-form-item-label >label{ | |||
| color:rgba(29, 29, 32, 0.8); | |||
| } | |||
| .ant-modal-footer{ | |||
| margin: 40px 0 30px 0; | |||
| display: flex; | |||
| justify-content: center; | |||
| } | |||
| .ant-btn{ | |||
| width:110px; | |||
| height:40px; | |||
| font-size:18px; | |||
| background:rgba(22, 100, 255, 0.06); | |||
| border-radius:10px; | |||
| border-color: transparent; | |||
| } | |||
| .ant-btn-primary{ | |||
| background:#1664ff; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| export interface StatusInfo { | |||
| label: string; | |||
| color: string; | |||
| icon: string; | |||
| } | |||
| export enum ExperimentStatus { | |||
| Running = 'Running', | |||
| Succeeded = 'Succeeded', | |||
| Pending = 'Pending', | |||
| Failed = 'Failed', | |||
| Error = 'Error', | |||
| Terminated = 'Terminated', | |||
| Skipped = 'Skipped', | |||
| Omitted = 'Omitted', | |||
| } | |||
| export const experimentStatusInfo: Record<string, StatusInfo | undefined> = { | |||
| Running: { | |||
| label: '运行中', | |||
| color: '#165bff', | |||
| icon: '/assets/images/running-icon.png', | |||
| }, | |||
| Succeeded: { | |||
| label: '成功', | |||
| color: '#63a728', | |||
| icon: '/assets/images/success-icon.png', | |||
| }, | |||
| Pending: { | |||
| label: '等待中', | |||
| color: '#f981eb', | |||
| icon: '/assets/images/pending-icon.png', | |||
| }, | |||
| Failed: { | |||
| label: '失败', | |||
| color: '#c73131', | |||
| icon: '/assets/images/fail-icon.png', | |||
| }, | |||
| Error: { | |||
| label: '错误', | |||
| color: '#c73131', | |||
| icon: '/assets/images/fail-icon.png', | |||
| }, | |||
| Terminated: { | |||
| label: '终止', | |||
| color: '#8a8a8a', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| Skipped: { | |||
| label: '未执行', | |||
| color: '#8a8a8a', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| Omitted: { | |||
| label: '未执行', | |||
| color: '#8a8a8ae', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| }; | |||
| @@ -1,149 +1,151 @@ | |||
| import React ,{useEffect,useState,useRef}from 'react'; | |||
| import Styles from './index.less' | |||
| import { Input, Space ,Button,Tabs,Pagination,Modal, Form,message, Radio,Select,Table,Upload} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, DeleteOutlined,UploadOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||
| import {getDatasetList,getModelById,getModelVersionsById,getModelVersionIdList,deleteModelVersion,addModelsVersionDetail} from '@/services/dataset/index.js' | |||
| import { useParams } from 'react-router-dom' | |||
| import {downLoadZip} from '@/utils/downloadfile' | |||
| const { Search } = Input; | |||
| import { getAccessToken } from '@/access'; | |||
| import { | |||
| addModelsVersionDetail, | |||
| deleteModelVersion, | |||
| getModelById, | |||
| getModelVersionIdList, | |||
| getModelVersionsById, | |||
| } from '@/services/dataset/index.js'; | |||
| import { downLoadZip } from '@/utils/downloadfile'; | |||
| import { DeleteOutlined, PlusCircleOutlined, UploadOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | |||
| import moment from 'moment'; | |||
| import { getAccessToken } from '@/access'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useParams } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| const { Search } = Input; | |||
| const { TabPane } = Tabs; | |||
| const Dataset= React.FC = () => { | |||
| const Dataset = () => { | |||
| const props = { | |||
| action: '/api/mmp/dataset/upload', | |||
| // headers: { | |||
| // 'X-Requested-With': null | |||
| // }, | |||
| headers: { | |||
| Authorization:getAccessToken(), | |||
| 'X-Requested-With': null | |||
| Authorization: getAccessToken(), | |||
| 'X-Requested-With': null, | |||
| }, | |||
| onChange({ file, fileList }) { | |||
| if (file.status !== 'uploading') { | |||
| console.log(file, fileList); | |||
| setFormList(fileList.map(item=>{ | |||
| return { | |||
| ...form.getFieldsValue(), | |||
| models_id:locationParams.id, | |||
| file_name:item.response.data[0].fileName, | |||
| file_size:item.response.data[0].fileSize, | |||
| url:item.response.data[0].url, | |||
| } | |||
| })) | |||
| setFormList( | |||
| fileList.map((item) => { | |||
| return { | |||
| ...form.getFieldsValue(), | |||
| models_id: locationParams.id, | |||
| file_name: item.response.data[0].fileName, | |||
| file_size: item.response.data[0].fileSize, | |||
| url: item.response.data[0].url, | |||
| }; | |||
| }), | |||
| ); | |||
| } | |||
| }, | |||
| defaultFileList: [ | |||
| ], | |||
| defaultFileList: [], | |||
| }; | |||
| const [form] = Form.useForm(); | |||
| const [formList,setFormList]=useState([]) | |||
| const [formList, setFormList] = useState([]); | |||
| const [dialogTitle, setDialogTitle] = useState('新建版本'); | |||
| const [isModalOpen,setIsModalOpen]=useState(false) | |||
| const [datasetDetailObj,setDatasetDetailObj]=useState({ | |||
| }); | |||
| const [version,setVersion]=useState('') | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||
| const [version, setVersion] = useState(''); | |||
| const [versionList, setVersionList] = useState([]); | |||
| const locationParams =useParams () //新版本获取路由参数接口 | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| console.log(locationParams); | |||
| const [wordList, setWordList] = useState([]); | |||
| const getModelByDetail=()=>{ | |||
| getModelById(locationParams.id).then(ret=>{ | |||
| const getModelByDetail = () => { | |||
| getModelById(locationParams.id).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setDatasetDetailObj(ret.data) | |||
| if (ret.code == 200) { | |||
| setDatasetDetailObj(ret.data); | |||
| } | |||
| }) | |||
| } | |||
| const getModelVersionsList=()=>{ | |||
| getModelVersionsById(locationParams.id).then(ret=>{ | |||
| }); | |||
| }; | |||
| const getModelVersionsList = () => { | |||
| getModelVersionsById(locationParams.id).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200&&ret.data&&ret.data.length>0){ | |||
| setVersionList(ret.data.map(item=>{ | |||
| return { | |||
| 'label':item, | |||
| 'value':item | |||
| } | |||
| })) | |||
| if (ret.code == 200 && ret.data && ret.data.length > 0) { | |||
| setVersionList( | |||
| ret.data.map((item) => { | |||
| return { | |||
| label: item, | |||
| value: item, | |||
| }; | |||
| }), | |||
| ); | |||
| } | |||
| }) | |||
| } | |||
| useEffect(()=>{ | |||
| getModelByDetail() | |||
| getModelVersionsList() | |||
| return ()=>{ | |||
| } | |||
| },[]) | |||
| }); | |||
| }; | |||
| useEffect(() => { | |||
| getModelByDetail(); | |||
| getModelVersionsList(); | |||
| return () => {}; | |||
| }, []); | |||
| const showModal = () => { | |||
| form.resetFields() | |||
| form.setFieldsValue({name:datasetDetailObj.name}) | |||
| setDialogTitle('创建新版本') | |||
| form.resetFields(); | |||
| form.setFieldsValue({ name: datasetDetailObj.name }); | |||
| setDialogTitle('创建新版本'); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const deleteDataset=()=>{ | |||
| const deleteDataset = () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除模型版本?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| deleteModelVersion({models_id:locationParams.id,version}).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('删除成功') | |||
| getModelVersions({version,models_id:locationParams.id}) | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| onOk: () => { | |||
| deleteModelVersion({ models_id: locationParams.id, version }).then((ret) => { | |||
| if (ret.code == 200) { | |||
| message.success('删除成功'); | |||
| getModelVersions({ version, models_id: locationParams.id }); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| } | |||
| }; | |||
| const onFinish = () => { | |||
| addModelsVersionDetail(formList).then(ret=>{ | |||
| console.log(ret); | |||
| getModelVersionsList() | |||
| setIsModalOpen(false); | |||
| }) | |||
| }; | |||
| const getModelVersions=(params)=>{ | |||
| getModelVersionIdList(params).then(ret=>{ | |||
| addModelsVersionDetail(formList).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setWordList(ret.data) | |||
| getModelVersionsList(); | |||
| setIsModalOpen(false); | |||
| }); | |||
| }; | |||
| const getModelVersions = (params) => { | |||
| getModelVersionIdList(params).then((ret) => { | |||
| console.log(ret); | |||
| if (ret.code == 200) { | |||
| setWordList(ret.data); | |||
| } | |||
| }) | |||
| } | |||
| }); | |||
| }; | |||
| const handleExport = async () => { | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/models/downloadAllFiles`,{models_id:locationParams.id,version}) | |||
| downLoadZip(`/api/mmp/models/downloadAllFiles`, { models_id: locationParams.id, version }); | |||
| }; | |||
| const downloadAlone=(e,record)=>{ | |||
| const downloadAlone = (e, record) => { | |||
| console.log(record); | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/models/download_model/${record.id}`) | |||
| } | |||
| const handleChange=(value)=>{ | |||
| downLoadZip(`/api/mmp/models/download_model/${record.id}`); | |||
| }; | |||
| const handleChange = (value) => { | |||
| console.log(value); | |||
| if(value){ | |||
| getModelVersions({version:value,models_id:locationParams.id}) | |||
| setVersion(value) | |||
| } | |||
| else{ | |||
| setVersion(null) | |||
| if (value) { | |||
| getModelVersions({ version: value, models_id: locationParams.id }); | |||
| setVersion(value); | |||
| } else { | |||
| setVersion(null); | |||
| } | |||
| } | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| @@ -164,7 +166,7 @@ const Dataset= React.FC = () => { | |||
| title: '文件名称', | |||
| dataIndex: 'file_name', | |||
| key: 'file_name', | |||
| render: (text,record) => <a onClick={(e)=>downloadAlone(e,record)}>{text}</a>, | |||
| render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>, | |||
| }, | |||
| { | |||
| title: '版本号', | |||
| @@ -183,79 +185,107 @@ const Dataset= React.FC = () => { | |||
| render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||
| }, | |||
| ]; | |||
| const pageOption = useRef({page: 1,size: 10}) | |||
| const pageOption = useRef({ page: 1, size: 10 }); | |||
| // 当前页面切换 | |||
| const paginationChange = async (current, size) => { | |||
| console.log('page', current, size) | |||
| pageOption.current={ | |||
| page:current, | |||
| size:size | |||
| } | |||
| console.log('page', current, size); | |||
| pageOption.current = { | |||
| page: current, | |||
| size: size, | |||
| }; | |||
| // getList() | |||
| } | |||
| return (<div className={Styles.datasetBox}> | |||
| <div className={Styles.datasetIntroTopBox}> | |||
| <span style={{color:'#1d1d20',fontSize:'20px'}}>{datasetDetailObj.name}</span> | |||
| <div className={Styles.smallTagBox}> | |||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag||'...'}</div> | |||
| }; | |||
| return ( | |||
| <div className={Styles.datasetBox}> | |||
| <div className={Styles.datasetIntroTopBox}> | |||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | |||
| <div className={Styles.smallTagBox}> | |||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | |||
| <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | |||
| {/* <div className={Styles.tagItem}>English</div> */} | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetIntroCneterBox}> | |||
| <Tabs | |||
| defaultActiveKey="1" | |||
| > | |||
| <TabPane tab="模型简介" key="1"> | |||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||
| </TabPane> | |||
| <TabPane tab="模型文件/版本" key="2"> | |||
| <div className={Styles.dataListBox}> | |||
| <div>模型列表</div> | |||
| <div className={Styles.dataButtonList}> | |||
| <div style={{display: 'flex', | |||
| justifyContent: 'space-between', | |||
| alignItems: 'center'}}> | |||
| <span style={{marginRight:'10px'}}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ | |||
| width: 160, | |||
| }} | |||
| allowClear | |||
| onChange={handleChange} | |||
| options={versionList} | |||
| /> | |||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||
| 创建新版本 | |||
| </Button> | |||
| </div> | |||
| <div style={{display: 'flex', | |||
| justifyContent: 'space-between', | |||
| alignItems: 'center'}}> | |||
| <Button type="primary" className={Styles.plusButton} style={{margin:'0 20px 0 0' }} onClick={deleteDataset} icon = {<DeleteOutlined style={{color:'#1664ff', }} />}> | |||
| 删除 | |||
| </Button> | |||
| <Button type="primary" className={Styles.plusButton} disabled={!version} style={{margin:'0 20px 0 0' }} onClick={handleExport} icon = {<UploadOutlined style={{color:'#1664ff'}} />}> | |||
| 下载 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| <Table columns={columns} dataSource={wordList} pagination={false} /> | |||
| </div> | |||
| </TabPane> | |||
| </Tabs> | |||
| </div> | |||
| <Modal title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} className={Styles.modal} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| </div> | |||
| <div className={Styles.datasetIntroCneterBox}> | |||
| <Tabs defaultActiveKey="1"> | |||
| <TabPane tab="模型简介" key="1"> | |||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||
| </TabPane> | |||
| <TabPane tab="模型文件/版本" key="2"> | |||
| <div className={Styles.dataListBox}> | |||
| <div>模型列表</div> | |||
| <div className={Styles.dataButtonList}> | |||
| <div | |||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||
| > | |||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ | |||
| width: 160, | |||
| }} | |||
| allowClear | |||
| onChange={handleChange} | |||
| options={versionList} | |||
| /> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| onClick={showModal} | |||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 创建新版本 | |||
| </Button> | |||
| </div> | |||
| <div | |||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||
| > | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| style={{ margin: '0 20px 0 0' }} | |||
| onClick={deleteDataset} | |||
| icon={<DeleteOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| disabled={!version} | |||
| style={{ margin: '0 20px 0 0' }} | |||
| onClick={handleExport} | |||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 下载 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| <Table columns={columns} dataSource={wordList} pagination={false} /> | |||
| </div> | |||
| </TabPane> | |||
| </Tabs> | |||
| </div> | |||
| <Modal | |||
| title={ | |||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||
| <img | |||
| style={{ width: '20px', marginRight: '10px' }} | |||
| src={`/assets/images/pipeline-edit-icon.png`} | |||
| alt="" | |||
| /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={isModalOpen} | |||
| className={Styles.modal} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={handleCancel} | |||
| > | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| @@ -270,29 +300,38 @@ const Dataset= React.FC = () => { | |||
| <Form.Item | |||
| label="模型名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input disabled placeholder="请输入数据集名称"/> | |||
| <Input disabled placeholder="请输入数据集名称" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型版本" | |||
| name="version" | |||
| > | |||
| <Input placeholder="请输入数据集版本"/> | |||
| <Form.Item label="模型版本" name="version"> | |||
| <Input placeholder="请输入数据集版本" /> | |||
| </Form.Item> | |||
| <Form.Item label="模型文件" name="dataset_version_vos"> | |||
| <Upload {...props}> | |||
| <Button style={{fontSize:'14px',border:'1px solid',borderColor:'#1664ff',background:'#fff'}} icon={<UploadOutlined style={{color:'#1664ff'}} />}>上传文件</Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| <Upload {...props}> | |||
| <Button | |||
| style={{ | |||
| fontSize: '14px', | |||
| border: '1px solid', | |||
| borderColor: '#1664ff', | |||
| background: '#fff', | |||
| }} | |||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 上传文件 | |||
| </Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </div>) | |||
| </div> | |||
| ); | |||
| }; | |||
| export default Dataset; | |||
| export default Dataset; | |||
| @@ -1,234 +1,305 @@ | |||
| import React ,{useEffect,useState}from 'react'; | |||
| import Styles from './index.less' | |||
| import { Input, Space ,Button,Tabs,Pagination,Modal, Form,message, Radio,Upload,Select} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, UploadOutlined,DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||
| import {getModelList,addModel,getAssetIcon} from '@/services/dataset/index.js' | |||
| const { Search } = Input; | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import { getAccessToken } from '@/access'; | |||
| import { addModel, getAssetIcon, getModelList } from '@/services/dataset/index.js'; | |||
| import { PlusCircleOutlined, UploadOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; | |||
| import moment from 'moment'; | |||
| import { getAccessToken } from '@/access'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| const { Search } = Input; | |||
| const leftdataList=[1,2,3] | |||
| const leftdataList = [1, 2, 3]; | |||
| const PublicData= React.FC = () => { | |||
| const PublicData = () => { | |||
| const props = { | |||
| action: '/api/mmp/dataset/upload', | |||
| // headers: { | |||
| // 'X-Requested-With': null | |||
| // }, | |||
| headers: { | |||
| Authorization:getAccessToken(), | |||
| 'X-Requested-With': null | |||
| Authorization: getAccessToken(), | |||
| 'X-Requested-With': null, | |||
| }, | |||
| onChange({ file, fileList }) { | |||
| if (file.status !== 'uploading') { | |||
| console.log(file, fileList); | |||
| form.setFieldsValue({dataset_version_vos:fileList.map(item=>item.response.data[0])}) | |||
| form.setFieldsValue({ dataset_version_vos: fileList.map((item) => item.response.data[0]) }); | |||
| } | |||
| }, | |||
| defaultFileList: [ | |||
| ], | |||
| defaultFileList: [], | |||
| }; | |||
| const [queryFlow,setQueryFlow]=useState({ | |||
| page:0, | |||
| size:10, | |||
| name:null, | |||
| available_range:0 | |||
| const [queryFlow, setQueryFlow] = useState({ | |||
| page: 0, | |||
| size: 10, | |||
| name: null, | |||
| available_range: 0, | |||
| }); | |||
| const navgite = useNavigate(); | |||
| const [iconParams, setIconParams] = useState({ | |||
| name: null, | |||
| page: 0, | |||
| size: 10000, | |||
| }); | |||
| const navgite=useNavigate(); | |||
| const [iconParams,setIconParams]=useState({ | |||
| name:null, | |||
| page:0, | |||
| size:10000 | |||
| }) | |||
| const [activeType,setActiveType]=useState(null) | |||
| const [activeTag,setActiveTag]=useState(null) | |||
| const [datasetTypeList,setDatasetTypeList]=useState([]) | |||
| const [datasetDirectionList,setDatasetDirectionList]=useState([]) | |||
| const [isModalOpen,setIsModalOpen]=useState(false) | |||
| const [datasetList,setDatasetList]=useState([]); | |||
| const [total,setTotal]=useState(0); | |||
| const [activeType, setActiveType] = useState(null); | |||
| const [activeTag, setActiveTag] = useState(null); | |||
| const [datasetTypeList, setDatasetTypeList] = useState([]); | |||
| const [datasetDirectionList, setDatasetDirectionList] = useState([]); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetList, setDatasetList] = useState([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [form] = Form.useForm(); | |||
| const [dialogTitle, setDialogTitle] = useState('新建模型'); | |||
| const getModelLists=(queryFlow)=>{ | |||
| getModelList(queryFlow).then(ret=>{ | |||
| const getModelLists = (queryFlow) => { | |||
| getModelList(queryFlow).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setDatasetList(ret.data.content) | |||
| setTotal(ret.data.totalElements) | |||
| if (ret.code == 200) { | |||
| setDatasetList(ret.data.content); | |||
| setTotal(ret.data.totalElements); | |||
| } | |||
| }) | |||
| } | |||
| }); | |||
| }; | |||
| const showModal = () => { | |||
| form.resetFields() | |||
| setDialogTitle('新建模型') | |||
| form.resetFields(); | |||
| setDialogTitle('新建模型'); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const getAssetIconList=(params)=>{ | |||
| getAssetIcon(params).then(ret=>{ | |||
| const getAssetIconList = (params) => { | |||
| getAssetIcon(params).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200&&ret.data.content&&ret.data.content.length>0){ | |||
| setDatasetTypeList(ret.data.content.filter(item=>item.category_id==3)) | |||
| setDatasetDirectionList(ret.data.content.filter(item=>item.category_id==4)) | |||
| } | |||
| else{ | |||
| setDatasetTypeList([]) | |||
| setDatasetDirectionList([]) | |||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||
| setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3)); | |||
| setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4)); | |||
| } else { | |||
| setDatasetTypeList([]); | |||
| setDatasetDirectionList([]); | |||
| } | |||
| }) | |||
| } | |||
| const onSearch=(values)=>{ | |||
| }); | |||
| }; | |||
| const onSearch = (values) => { | |||
| console.log(values); | |||
| getAssetIconList({...iconParams,name:values}) | |||
| } | |||
| const nameSearch=(values)=>{ | |||
| getAssetIconList({ ...iconParams, name: values }); | |||
| }; | |||
| const nameSearch = (values) => { | |||
| console.log(values); | |||
| getModelLists({...queryFlow,name:values}) | |||
| } | |||
| getModelLists({ ...queryFlow, name: values }); | |||
| }; | |||
| const handleOk = () => { | |||
| console.log(1111); | |||
| console.log(1111); | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const onFinish = (values) => { | |||
| addModel(values).then(ret=>{ | |||
| console.log(ret); | |||
| getModelLists(queryFlow) | |||
| setIsModalOpen(false); | |||
| }) | |||
| }; | |||
| const chooseModelType=(val,item)=>{ | |||
| console.log(val,item); | |||
| if(item.path==queryFlow.model_type){ | |||
| setActiveType('') | |||
| setQueryFlow({...queryFlow,model_type:null}) | |||
| getModelLists({...queryFlow,model_type:null}) | |||
| } | |||
| else{ | |||
| setActiveType(item.path) | |||
| setQueryFlow({...queryFlow,model_type:item.path}) | |||
| getModelLists({...queryFlow,model_type:item.path}) | |||
| addModel(values).then((ret) => { | |||
| console.log(ret); | |||
| getModelLists(queryFlow); | |||
| setIsModalOpen(false); | |||
| }); | |||
| }; | |||
| const chooseModelType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.model_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||
| getModelLists({ ...queryFlow, model_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, model_type: item.path }); | |||
| getModelLists({ ...queryFlow, model_type: item.path }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const chooseModelTag=(val,item)=>{ | |||
| if(item.path==queryFlow.model_tag){ | |||
| setActiveTag('') | |||
| setQueryFlow({...queryFlow,model_tag:null}) | |||
| getModelLists({...queryFlow,model_tag:null}) | |||
| } | |||
| else{ | |||
| setActiveTag(item.path) | |||
| setQueryFlow({...queryFlow,model_tag:item.path}) | |||
| getModelLists({...queryFlow,model_tag:item.path}) | |||
| const chooseModelTag = (val, item) => { | |||
| if (item.path == queryFlow.model_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||
| getModelLists({ ...queryFlow, model_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.path }); | |||
| getModelLists({ ...queryFlow, model_tag: item.path }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const routeToIntro=(e,record)=>{ | |||
| e.stopPropagation() | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({pathname:`/dataset/modelIntro/${record.id}` }); | |||
| } | |||
| navgite({ pathname: `/dataset/modelIntro/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useEffect(()=>{ | |||
| getAssetIconList(iconParams) | |||
| getModelLists(queryFlow) | |||
| return ()=>{ | |||
| } | |||
| },[]) | |||
| return (<> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom:'15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList&&datasetTypeList.length>0?datasetTypeList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeType?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelType(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} onClick={(e)=>{chooseModelTag(e, item)}}>{item.name}</span> | |||
| useEffect(() => { | |||
| getAssetIconList(iconParams); | |||
| getModelLists(queryFlow); | |||
| return () => {}; | |||
| }, []); | |||
| return ( | |||
| <> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom: '15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList && datasetTypeList.length > 0 | |||
| ? datasetTypeList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelType(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span | |||
| className={Styles.messageText} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| }} | |||
| > | |||
| {item.name} | |||
| </span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList && datasetDirectionList.length > 0 | |||
| ? datasetDirectionList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span className={Styles.messageText}>{item.name}</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按模型名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| onClick={showModal} | |||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 模型注册 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList&&datasetDirectionList.length>0?datasetDirectionList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeTag?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelTag(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} >{item.name}</span> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList && datasetList.length > 0 | |||
| ? datasetList.map((item) => { | |||
| return ( | |||
| <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}> | |||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||
| </div> | |||
| <div className={Styles.itemIcon}> | |||
| <img | |||
| style={{ width: '17px', marginRight: '3px' }} | |||
| src={`/assets/images/upload-icon.png`} | |||
| alt="" | |||
| /> | |||
| <span>1582</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按模型名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||
| 模型注册 | |||
| </Button> | |||
| <Pagination size="small" total={total} showSizeChanger showQuickJumper /> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList&&datasetList.length>0?datasetList.map(item=>{return <div className={Styles.dataItem} onClick={(e)=>routeToIntro(e,item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}><span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span></div> | |||
| <div className={Styles.itemIcon}><img style={{width:'17px',marginRight:'3px'}} src={`/assets/images/upload-icon.png`} alt="" /><span>1582</span></div> | |||
| </div>}):''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| <Pagination | |||
| size="small" | |||
| total={total} | |||
| showSizeChanger | |||
| showQuickJumper | |||
| /> | |||
| </div> | |||
| </div> | |||
| <Modal title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} className={Styles.modal} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| <Modal | |||
| title={ | |||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||
| <img | |||
| style={{ width: '20px', marginRight: '10px' }} | |||
| src={`/assets/images/pipeline-edit-icon.png`} | |||
| alt="" | |||
| /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={isModalOpen} | |||
| className={Styles.modal} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={handleCancel} | |||
| > | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| @@ -243,73 +314,74 @@ const PublicData= React.FC = () => { | |||
| <Form.Item | |||
| label="模型名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入模型名称"/> | |||
| <Input placeholder="请输入模型名称" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型描述" | |||
| name="description" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入模型描述"/> | |||
| <Input placeholder="请输入模型描述" /> | |||
| </Form.Item> | |||
| <Form.Item label="可见范围" name="available_range"> | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型框架" | |||
| name="model_type" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择模型类型" | |||
| options={[]} | |||
| /> | |||
| <Select allowClear placeholder="请选择模型类型" options={[]} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| <Form.Item | |||
| label="模型能力" | |||
| name="model_tag" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择模型标签" | |||
| options={[]} | |||
| /> | |||
| <Select allowClear placeholder="请选择模型标签" options={[]} /> | |||
| </Form.Item> | |||
| <Form.Item label="模型文件" name="dataset_version_vos"> | |||
| <Upload {...props}> | |||
| <Button icon={<UploadOutlined style={{color:'#1664ff'}} />}>上传文件</Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| <Upload {...props}> | |||
| <Button icon={<UploadOutlined style={{ color: '#1664ff' }} />}>上传文件</Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </>) | |||
| </> | |||
| ); | |||
| }; | |||
| export default PublicData; | |||
| export default PublicData; | |||
| @@ -1,204 +1,263 @@ | |||
| import React ,{useEffect,useState}from 'react'; | |||
| import Styles from './index.less' | |||
| import { Input, Space ,Button,Tabs,Pagination,Modal, Form,message, Radio,} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||
| import {getModelList,getAssetIcon} from '@/services/dataset/index.js' | |||
| const { Search } = Input; | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import { getAssetIcon, getModelList } from '@/services/dataset/index.js'; | |||
| import { Form, Input, Modal, Pagination, Radio } from 'antd'; | |||
| import moment from 'moment'; | |||
| const leftdataList=[1,2,3] | |||
| import { useEffect, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| const { Search } = Input; | |||
| const leftdataList = [1, 2, 3]; | |||
| const PublicData= React.FC = () => { | |||
| const [queryFlow,setQueryFlow]=useState({ | |||
| page:0, | |||
| size:10, | |||
| name:null, | |||
| available_range:1 | |||
| const PublicData = () => { | |||
| const [queryFlow, setQueryFlow] = useState({ | |||
| page: 0, | |||
| size: 10, | |||
| name: null, | |||
| available_range: 1, | |||
| }); | |||
| const [iconParams,setIconParams]=useState({ | |||
| name:null, | |||
| page:0, | |||
| size:10000 | |||
| }) | |||
| const [activeType,setActiveType]=useState(null) | |||
| const [activeTag,setActiveTag]=useState(null) | |||
| const [datasetTypeList,setDatasetTypeList]=useState([]) | |||
| const [datasetDirectionList,setDatasetDirectionList]=useState([]) | |||
| const navgite=useNavigate(); | |||
| const [isModalOpen,setIsModalOpen]=useState(false) | |||
| const [datasetList,setDatasetList]=useState([]); | |||
| const [total,setTotal]=useState(0); | |||
| const [iconParams, setIconParams] = useState({ | |||
| name: null, | |||
| page: 0, | |||
| size: 10000, | |||
| }); | |||
| const [activeType, setActiveType] = useState(null); | |||
| const [activeTag, setActiveTag] = useState(null); | |||
| const [datasetTypeList, setDatasetTypeList] = useState([]); | |||
| const [datasetDirectionList, setDatasetDirectionList] = useState([]); | |||
| const navgite = useNavigate(); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetList, setDatasetList] = useState([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [form] = Form.useForm(); | |||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | |||
| const getModelLists=(queryFlow)=>{ | |||
| getModelList(queryFlow).then(ret=>{ | |||
| const getModelLists = (queryFlow) => { | |||
| getModelList(queryFlow).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setDatasetList(ret.data.content) | |||
| setTotal(ret.data.totalElements) | |||
| if (ret.code == 200) { | |||
| setDatasetList(ret.data.content); | |||
| setTotal(ret.data.totalElements); | |||
| } | |||
| }) | |||
| } | |||
| }); | |||
| }; | |||
| const showModal = () => { | |||
| form.resetFields() | |||
| setDialogTitle('新建数据集') | |||
| form.resetFields(); | |||
| setDialogTitle('新建数据集'); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const nameSearch=(values)=>{ | |||
| const nameSearch = (values) => { | |||
| console.log(values); | |||
| getModelLists({...queryFlow,name:values}) | |||
| } | |||
| const getAssetIconList=(params)=>{ | |||
| getAssetIcon(params).then(ret=>{ | |||
| getModelLists({ ...queryFlow, name: values }); | |||
| }; | |||
| const getAssetIconList = (params) => { | |||
| getAssetIcon(params).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200&&ret.data.content&&ret.data.content.length>0){ | |||
| setDatasetTypeList(ret.data.content.filter(item=>item.category_id==3)) | |||
| setDatasetDirectionList(ret.data.content.filter(item=>item.category_id==4)) | |||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||
| setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3)); | |||
| setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4)); | |||
| } else { | |||
| setDatasetTypeList([]); | |||
| setDatasetDirectionList([]); | |||
| } | |||
| else{ | |||
| setDatasetTypeList([]) | |||
| setDatasetDirectionList([]) | |||
| } | |||
| }) | |||
| } | |||
| const onSearch=(values)=>{ | |||
| }); | |||
| }; | |||
| const onSearch = (values) => { | |||
| console.log(values); | |||
| getAssetIconList({...iconParams,name:values}) | |||
| } | |||
| getAssetIconList({ ...iconParams, name: values }); | |||
| }; | |||
| const handleOk = () => { | |||
| console.log(1111); | |||
| console.log(1111); | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const chooseModelType=(val,item)=>{ | |||
| console.log(val,item); | |||
| if(item.path==queryFlow.model_type){ | |||
| setActiveType('') | |||
| setQueryFlow({...queryFlow,model_type:null}) | |||
| getModelLists({...queryFlow,model_type:null}) | |||
| const chooseModelType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.model_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||
| getModelLists({ ...queryFlow, model_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, model_type: item.path }); | |||
| getModelLists({ ...queryFlow, model_type: item.path }); | |||
| } | |||
| else{ | |||
| setActiveType(item.path) | |||
| setQueryFlow({...queryFlow,model_type:item.path}) | |||
| getModelLists({...queryFlow,model_type:item.path}) | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const chooseModelTag=(val,item)=>{ | |||
| if(item.path==queryFlow.model_tag){ | |||
| setActiveTag('') | |||
| setQueryFlow({...queryFlow,model_tag:null}) | |||
| getModelLists({...queryFlow,model_tag:null}) | |||
| } | |||
| else{ | |||
| setActiveTag(item.path) | |||
| setQueryFlow({...queryFlow,model_tag:item.path}) | |||
| getModelLists({...queryFlow,model_tag:item.path}) | |||
| const chooseModelTag = (val, item) => { | |||
| if (item.path == queryFlow.model_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||
| getModelLists({ ...queryFlow, model_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.path }); | |||
| getModelLists({ ...queryFlow, model_tag: item.path }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const onFinish = (values) => { | |||
| }; | |||
| const routeToIntro=(e,record)=>{ | |||
| e.stopPropagation() | |||
| console.log(record); | |||
| navgite({pathname:`/dataset/modelIntro/${record.id}` }); | |||
| } | |||
| const onFinish = (values) => {}; | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({ pathname: `/dataset/modelIntro/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useEffect(()=>{ | |||
| getAssetIconList(iconParams) | |||
| getModelLists(queryFlow) | |||
| return ()=>{ | |||
| } | |||
| },[]) | |||
| return (<> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom:'15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList&&datasetTypeList.length>0?datasetTypeList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeType?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelType(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} >{item.name}</span> | |||
| useEffect(() => { | |||
| getAssetIconList(iconParams); | |||
| getModelLists(queryFlow); | |||
| return () => {}; | |||
| }, []); | |||
| return ( | |||
| <> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom: '15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList && datasetTypeList.length > 0 | |||
| ? datasetTypeList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelType(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span className={Styles.messageText}>{item.name}</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList && datasetDirectionList.length > 0 | |||
| ? datasetDirectionList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span className={Styles.messageText}>{item.name}</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList&&datasetDirectionList.length>0?datasetDirectionList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeTag?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelTag(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} >{item.name}</span> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按数据名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按数据名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList && datasetList.length > 0 | |||
| ? datasetList.map((item) => { | |||
| return ( | |||
| <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}> | |||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||
| </div> | |||
| <div className={Styles.itemIcon}> | |||
| <img | |||
| style={{ width: '17px', marginRight: '3px' }} | |||
| src={`/assets/images/upload-icon.png`} | |||
| alt="" | |||
| /> | |||
| <span>1582</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| <Pagination size="small" total={total} showSizeChanger showQuickJumper /> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList&&datasetList.length>0?datasetList.map(item=>{return <div className={Styles.dataItem} onClick={(e)=>routeToIntro(e,item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}><span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span></div> | |||
| <div className={Styles.itemIcon}><img style={{width:'17px',marginRight:'3px'}} src={`/assets/images/upload-icon.png`} alt="" /><span>1582</span></div> | |||
| </div>}):''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| <Pagination | |||
| size="small" | |||
| total={total} | |||
| showSizeChanger | |||
| showQuickJumper | |||
| /> | |||
| </div> | |||
| </div> | |||
| <Modal title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} className={Styles.modal} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| <Modal | |||
| title={ | |||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||
| <img | |||
| style={{ width: '20px', marginRight: '10px' }} | |||
| src={`/assets/images/pipeline-edit-icon.png`} | |||
| alt="" | |||
| /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={isModalOpen} | |||
| className={Styles.modal} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={handleCancel} | |||
| > | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| @@ -213,59 +272,68 @@ const PublicData= React.FC = () => { | |||
| <Form.Item | |||
| label="数据名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据名称"/> | |||
| <Input placeholder="请输入数据名称" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据集版本" | |||
| name="data_type" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据集版本"/> | |||
| <Input placeholder="请输入数据集版本" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据描述" | |||
| name="description" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据描述"/> | |||
| <Input placeholder="请输入数据描述" /> | |||
| </Form.Item> | |||
| <Form.Item label="选择流水线" name="description1"> | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据标签" | |||
| name="description3" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据标签" /> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </>) | |||
| </> | |||
| ); | |||
| }; | |||
| export default PublicData; | |||
| export default PublicData; | |||
| @@ -1,51 +1,42 @@ | |||
| import { getUserInfo } from '@/services/session'; | |||
| import { | |||
| ClusterOutlined, | |||
| MailOutlined, | |||
| ManOutlined, | |||
| MobileOutlined, | |||
| TeamOutlined, | |||
| UserOutlined, | |||
| MobileOutlined, | |||
| ManOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { PageLoading } from '@ant-design/pro-components'; | |||
| import { useRequest } from '@umijs/max'; | |||
| import { Card, Col, Divider, List, Row } from 'antd'; | |||
| import React, { useState } from 'react'; | |||
| import styles from './Center.less'; | |||
| import AvatarCropper from './components/AvatarCropper'; | |||
| import BaseInfo from './components/BaseInfo'; | |||
| import ResetPassword from './components/ResetPassword'; | |||
| import AvatarCropper from './components/AvatarCropper'; | |||
| import { useRequest } from '@umijs/max'; | |||
| import { getUserInfo } from '@/services/session'; | |||
| import { PageLoading } from '@ant-design/pro-components'; | |||
| const operationTabList = [ | |||
| { | |||
| key: 'base', | |||
| tab: ( | |||
| <span> | |||
| 基本资料 | |||
| </span> | |||
| ), | |||
| tab: <span>基本资料</span>, | |||
| }, | |||
| { | |||
| key: 'password', | |||
| tab: ( | |||
| <span> | |||
| 重置密码 | |||
| </span> | |||
| ), | |||
| tab: <span>重置密码</span>, | |||
| }, | |||
| ]; | |||
| export type tabKeyType = 'base' | 'password'; | |||
| const Center: React.FC = () => { | |||
| const [tabKey, setTabKey] = useState<tabKeyType>('base'); | |||
| const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false); | |||
| // 获取用户信息 | |||
| const { data: userInfo, loading } = useRequest(async () => { | |||
| return { data: await getUserInfo()}; | |||
| return { data: await getUserInfo() }; | |||
| }); | |||
| if (loading) { | |||
| return <div>loading...</div>; | |||
| @@ -141,14 +132,15 @@ const Center: React.FC = () => { | |||
| <div> | |||
| <Row gutter={[16, 24]}> | |||
| <Col lg={8} md={24}> | |||
| <Card | |||
| title="个人信息" | |||
| bordered={false} | |||
| loading={loading} | |||
| > | |||
| <Card title="个人信息" bordered={false} loading={loading}> | |||
| {!loading && ( | |||
| <div style={{ textAlign: "center"}}> | |||
| <div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}> | |||
| <div style={{ textAlign: 'center' }}> | |||
| <div | |||
| className={styles.avatarHolder} | |||
| onClick={() => { | |||
| setCropperModalOpen(true); | |||
| }} | |||
| > | |||
| <img alt="" src={currentUser.avatar} /> | |||
| </div> | |||
| {renderUserInfo(currentUser)} | |||
| @@ -188,7 +180,7 @@ const Center: React.FC = () => { | |||
| </Row> | |||
| <AvatarCropper | |||
| onFinished={() => { | |||
| setCropperModalOpen(false); | |||
| setCropperModalOpen(false); | |||
| }} | |||
| open={cropperModalOpen} | |||
| data={currentUser.avatar} | |||
| @@ -0,0 +1,57 @@ | |||
| import type { RequestConfig } from '@umijs/max'; | |||
| import { message } from 'antd'; | |||
| import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; | |||
| const checkRegion = 5 * 60 * 1000; | |||
| /** | |||
| * Umi Max 网络请求配置 | |||
| * @doc https://umijs.org/docs/max/request#配置 | |||
| */ | |||
| export const requestConfig: RequestConfig = { | |||
| errorConfig: {}, | |||
| requestInterceptors: [ | |||
| (url: any, options: { headers: any }) => { | |||
| const headers = options.headers ? options.headers : []; | |||
| console.log('request ====>:', url); | |||
| const authHeader = headers['Authorization']; | |||
| const isToken = headers['isToken']; | |||
| if (!authHeader && isToken !== false) { | |||
| const expireTime = getTokenExpireTime(); | |||
| if (expireTime) { | |||
| const left = Number(expireTime) - new Date().getTime(); | |||
| const refreshToken = getRefreshToken(); | |||
| if (left < checkRegion && refreshToken) { | |||
| if (left < 0) { | |||
| clearSessionToken(); | |||
| } | |||
| } else { | |||
| const accessToken = getAccessToken(); | |||
| if (accessToken) { | |||
| headers['Authorization'] = `Bearer ${accessToken}`; | |||
| } | |||
| } | |||
| } else { | |||
| clearSessionToken(); | |||
| } | |||
| } | |||
| return { url, options }; | |||
| }, | |||
| ], | |||
| responseInterceptors: [ | |||
| (response: any) => | |||
| { | |||
| const { status, data } = response; | |||
| if (status && status >= 200 && status < 300 && data && data.code === 200) { | |||
| return response | |||
| } else { | |||
| if (data && data.msg) { | |||
| message.error(data.msg); | |||
| } else { | |||
| message.error("请求失败"); | |||
| } | |||
| return Promise.reject(response) | |||
| } | |||
| }, | |||
| ], | |||
| }; | |||
| @@ -1,109 +0,0 @@ | |||
| import type { RequestOptions } from '@@/plugin-request/request'; | |||
| import type { RequestConfig } from '@umijs/max'; | |||
| import { message, notification } from 'antd'; | |||
| // 错误处理方案: 错误类型 | |||
| enum ErrorShowType { | |||
| SILENT = 0, | |||
| WARN_MESSAGE = 1, | |||
| ERROR_MESSAGE = 2, | |||
| NOTIFICATION = 3, | |||
| REDIRECT = 9, | |||
| } | |||
| // 与后端约定的响应数据格式 | |||
| interface ResponseStructure { | |||
| success: boolean; | |||
| data: any; | |||
| errorCode?: number; | |||
| errorMessage?: string; | |||
| showType?: ErrorShowType; | |||
| } | |||
| /** | |||
| * @name 错误处理 | |||
| * pro 自带的错误处理, 可以在这里做自己的改动 | |||
| * @doc https://umijs.org/docs/max/request#配置 | |||
| */ | |||
| export const errorConfig: RequestConfig = { | |||
| // 错误处理: umi@3 的错误处理方案。 | |||
| errorConfig: { | |||
| // 错误抛出 | |||
| errorThrower: (res) => { | |||
| const { success, data, errorCode, errorMessage, showType } = | |||
| res as unknown as ResponseStructure; | |||
| if (!success) { | |||
| const error: any = new Error(errorMessage); | |||
| error.name = 'BizError'; | |||
| error.info = { errorCode, errorMessage, showType, data }; | |||
| throw error; // 抛出自制的错误 | |||
| } | |||
| }, | |||
| // 错误接收及处理 | |||
| errorHandler: (error: any, opts: any) => { | |||
| if (opts?.skipErrorHandler) throw error; | |||
| // 我们的 errorThrower 抛出的错误。 | |||
| if (error.name === 'BizError') { | |||
| const errorInfo: ResponseStructure | undefined = error.info; | |||
| if (errorInfo) { | |||
| const { errorMessage, errorCode } = errorInfo; | |||
| switch (errorInfo.showType) { | |||
| case ErrorShowType.SILENT: | |||
| // do nothing | |||
| break; | |||
| case ErrorShowType.WARN_MESSAGE: | |||
| message.warning(errorMessage); | |||
| break; | |||
| case ErrorShowType.ERROR_MESSAGE: | |||
| message.error(errorMessage); | |||
| break; | |||
| case ErrorShowType.NOTIFICATION: | |||
| notification.open({ | |||
| description: errorMessage, | |||
| message: errorCode, | |||
| }); | |||
| break; | |||
| case ErrorShowType.REDIRECT: | |||
| // TODO: redirect | |||
| break; | |||
| default: | |||
| message.error(errorMessage); | |||
| } | |||
| } | |||
| } else if (error.response) { | |||
| // Axios 的错误 | |||
| // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围 | |||
| message.error(`Response status:${error.response.status}`); | |||
| } else if (error.request) { | |||
| // 请求已经成功发起,但没有收到响应 | |||
| // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例, | |||
| // 而在node.js中是 http.ClientRequest 的实例 | |||
| message.error('None response! Please retry.'); | |||
| } else { | |||
| // 发送请求时出了点问题 | |||
| message.error('Request error, please retry.'); | |||
| } | |||
| }, | |||
| }, | |||
| // 请求拦截器 | |||
| requestInterceptors: [ | |||
| (config: RequestOptions) => { | |||
| // 拦截请求配置,进行个性化处理。 | |||
| const url = config?.url?.concat('?token = 123'); | |||
| return { ...config, url }; | |||
| }, | |||
| ], | |||
| // 响应拦截器 | |||
| responseInterceptors: [ | |||
| (response) => { | |||
| // 拦截响应数据,进行个性化处理 | |||
| const { data } = response as unknown as ResponseStructure; | |||
| if (data?.success === false) { | |||
| message.error('请求失败!'); | |||
| } | |||
| return response; | |||
| }, | |||
| ], | |||
| }; | |||
| @@ -1,87 +1,102 @@ | |||
| import { request } from '@umijs/max'; | |||
| import { request } from '@umijs/max'; | |||
| // 查询实验列表 | |||
| export function getExperiment(params) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'GET', | |||
| params | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 运行实验 | |||
| export function runExperiments(id) { | |||
| return request('/api/mmp/experiment/experiments/'+id, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| }); | |||
| } | |||
| export function runExperiments(id) { | |||
| return request('/api/mmp/experiment/experiments/' + id, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| }); | |||
| } | |||
| // 根据id查询实验 | |||
| export function getExperimentById(id) { | |||
| return request(`/api/mmp/experiment/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experiment/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 根据id删除实验 | |||
| export function deleteExperimentById(id) { | |||
| return request(`/api/mmp/experiment/${id}`, { | |||
| method: 'DELETE', | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 根据id查询实验实例 | |||
| export function getQueryByExperimentId(id) { | |||
| return request(`/api/mmp/experimentIns/queryByExperimentId/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experimentIns/queryByExperimentId/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 根据id删除实验实例 | |||
| export function deleteQueryByExperimentInsId(id) { | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'DELETE', | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 根据id终止实验实例 | |||
| export function putQueryByExperimentInsId(id) { | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'PUT', | |||
| method: 'PUT', | |||
| }); | |||
| } | |||
| // 查询实验实例实时日志 | |||
| export function getQueryByExperimentLog(data) { | |||
| return request("/api/mmp/experimentIns/realTimeLog/", { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 根据id查询查询日志 | |||
| export function getQueryByExperimentLog(params) { | |||
| return request(`/api/mmp/experimentIns/log/`, { | |||
| method: 'GET', | |||
| params | |||
| }); | |||
| } | |||
| // 根据id查询输出结果 | |||
| // 查询实例节点结果 | |||
| export function getNodeResult(params) { | |||
| return request(`/api/mmp/experimentIns/nodeResult/`, { | |||
| method: 'GET', | |||
| params | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 获取pod实时日志请求,运行完成的 | |||
| export function getExperimentPodsLog(params) { | |||
| return request("/api/mmp/experimentIns/pods/log", { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 获取pod实时日志请求,运行中的 | |||
| export function getExperimentPodsRealtimeLog(data) { | |||
| return request("/api/mmp/experimentIns/pods/realTimeLog", { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 根据实例查询详情 | |||
| export function getExperimentIns(id) { | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 新增实验 | |||
| export function postExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data | |||
| }); | |||
| } | |||
| export function postExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data, | |||
| }); | |||
| } | |||
| // 编辑实验 | |||
| export function putExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data | |||
| }); | |||
| } | |||
| export function putExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data, | |||
| }); | |||
| } | |||
| @@ -3,7 +3,6 @@ import { MenuDataItem } from '@ant-design/pro-components'; | |||
| import { request } from '@umijs/max'; | |||
| import React, { lazy } from 'react'; | |||
| let remoteMenu: any = null; | |||
| export function getRemoteMenu() { | |||
| @@ -14,7 +13,6 @@ export function setRemoteMenu(data: any) { | |||
| remoteMenu = data; | |||
| } | |||
| function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| for (const menuItem of menu) { | |||
| if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') { | |||
| @@ -28,19 +26,19 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| } | |||
| } | |||
| if (!hasItem) { | |||
| newItem = { | |||
| newItem = { | |||
| path: menuItem.path, | |||
| routes: [], | |||
| children: [] | |||
| } | |||
| route.routes.push(newItem) | |||
| children: [], | |||
| }; | |||
| route.routes.push(newItem); | |||
| } | |||
| patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/'); | |||
| } | |||
| } else { | |||
| const names: string[] = menuItem.component.split('/'); | |||
| let path = ''; | |||
| names.forEach(name => { | |||
| names.forEach((name) => { | |||
| if (path.length > 0) { | |||
| path += '/'; | |||
| } | |||
| @@ -49,9 +47,9 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| } else { | |||
| path += name; | |||
| } | |||
| }) | |||
| }); | |||
| if (!path.endsWith('.tsx')) { | |||
| path += '.tsx' | |||
| path += '.tsx'; | |||
| } | |||
| if (route.routes === undefined) { | |||
| route.routes = []; | |||
| @@ -62,7 +60,7 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| const newRoute = { | |||
| element: React.createElement(lazy(() => import('@/pages/' + path))), | |||
| path: parentPath + menuItem.path, | |||
| } | |||
| }; | |||
| route.children.push(newRoute); | |||
| route.routes.push(newRoute); | |||
| } | |||
| @@ -70,7 +68,9 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| } | |||
| export function patchRouteWithRemoteMenus(routes: any) { | |||
| if (remoteMenu === null) { return; } | |||
| if (remoteMenu === null) { | |||
| return; | |||
| } | |||
| let proLayout = null; | |||
| for (const routeItem of routes) { | |||
| if (routeItem.id === 'ant-design-pro-layout') { | |||
| @@ -92,8 +92,8 @@ export async function getUserInfo(options?: Record<string, any>) { | |||
| // 刷新方法 | |||
| export async function refreshToken() { | |||
| return request('/api/auth/refresh', { | |||
| method: 'post' | |||
| }) | |||
| method: 'post', | |||
| }); | |||
| } | |||
| export async function getRouters(): Promise<any> { | |||
| @@ -0,0 +1,38 @@ | |||
| import dayjs from 'dayjs'; | |||
| export const elapsedTime = (beginDate: Date, endDate: Date): string => { | |||
| if (!isValidDate(beginDate) || !isValidDate(endDate)) { | |||
| return '--'; | |||
| } | |||
| const timestamp = endDate.getTime() - beginDate.getTime(); | |||
| if (timestamp < 0) { | |||
| return '时间有误'; | |||
| } | |||
| const duration = dayjs.duration(timestamp); | |||
| const years = duration.years(); | |||
| const months = duration.months(); | |||
| const days = duration.days(); | |||
| const hours = duration.hours(); | |||
| const minutes = duration.minutes(); | |||
| const seconds = duration.seconds(); | |||
| if (years !== 0) { | |||
| return `${years}年${months}个月`; | |||
| } | |||
| if (months !== 0) { | |||
| return `${months}个月${days}天`; | |||
| } | |||
| if (days !== 0) { | |||
| return `${days}天${hours}小时`; | |||
| } | |||
| if (hours !== 0) { | |||
| return `${hours}小时${minutes}分`; | |||
| } | |||
| return `${minutes}分${seconds}秒`; | |||
| }; | |||
| // 是否是有效的日期 | |||
| export const isValidDate = (date: Date): boolean => { | |||
| if (date instanceof Date) { | |||
| return !Number.isNaN(date.getTime()); // valueOf() 也可以 | |||
| } | |||
| return false; | |||
| }; | |||
| @@ -0,0 +1,20 @@ | |||
| /** | |||
| * @param { Promise } promise | |||
| * @return { Promise } | |||
| */ | |||
| export async function to<T>(promise: Promise<T>): Promise<[T, null] | [null, Error]> { | |||
| try { | |||
| const data = await promise; | |||
| return [data, null]; | |||
| } catch (error) { | |||
| if (error instanceof Error) { | |||
| return [null, error]; | |||
| } else if (typeof error === 'string') { | |||
| return [null, new Error(error)]; | |||
| } else { | |||
| return [null, new Error('Error')]; | |||
| } | |||
| } | |||
| } | |||
| export default to; | |||
| @@ -1,23 +1,31 @@ | |||
| { | |||
| "compilerOptions": { | |||
| "target": "esnext", | |||
| "module": "esnext", | |||
| "moduleResolution": "node", | |||
| "importHelpers": true, | |||
| "jsx": "preserve", | |||
| "esModuleInterop": true, | |||
| "sourceMap": true, | |||
| "target": "esnext", // 指定ECMAScript目标版本 | |||
| "lib": ["dom", "dom.iterable", "esnext"], // 要包含在编译中的库文件列表 | |||
| "allowJs": true, // 允许编译JavaScript文件 | |||
| "skipLibCheck": true, // 跳过所有声明文件的类型检查 | |||
| "esModuleInterop": true, // 禁用命名空间导入(import * as fs from "fs"),并启用CJS/AMD/UMD样式的导入(import fs from "fs") | |||
| "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入 | |||
| "strict": true, // 启用所有严格类型检查选项 | |||
| "forceConsistentCasingInFileNames": true, // 不允许对同一文件的引用使用不一致的大小写 | |||
| "module": "esnext", // 指定模块代码生成 | |||
| "moduleResolution": "node", // 使用Node.js样式解析模块 | |||
| "isolatedModules": true, // 无条件地为未解析的文件发出导入 | |||
| "resolveJsonModule": true, // 包含.json扩展名的模块 | |||
| "noEmit": true, // 不发出输出(即不编译代码,只进行类型检查) | |||
| "jsx": "react-jsx", // 在.tsx文件中支持JSX | |||
| "sourceMap": true, // 生成相应的.map文件 | |||
| "declaration": true, // 生成相应的.d.ts文件 | |||
| "noUnusedLocals": true, // 报告未使用的局部变量错误 | |||
| "noUnusedParameters": true, // 报告未使用的参数错误 | |||
| "incremental": true, // 通过读写磁盘上的文件来启用增量编译 | |||
| "noFallthroughCasesInSwitch": true, // 报告switch语句中的fallthrough案例错误 | |||
| "baseUrl": "./", | |||
| "skipLibCheck": true, | |||
| "experimentalDecorators": true, | |||
| "strict": true, | |||
| "resolveJsonModule": true, | |||
| "allowSyntheticDefaultImports": true, | |||
| "paths": { | |||
| "@/*": ["./src/*"], | |||
| "@@/*": ["./src/.umi/*"], | |||
| "@@test/*": ["./src/.umi-test/*"] | |||
| "@/*": ["src/*"] | |||
| } | |||
| }, | |||
| "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "src/pages/Pipeline/index.jsx", "src/pages/Dataset/index.jsx"] | |||
| "include": [ | |||
| "src/**/*" // *** TypeScript应该检查的文件 *** | |||
| ] | |||
| } | |||