diff --git a/react-ui/.eslintrc.js b/react-ui/.eslintrc.js index 3ac39ef6..ad3e7590 100644 --- a/react-ui/.eslintrc.js +++ b/react-ui/.eslintrc.js @@ -4,4 +4,7 @@ module.exports = { page: true, REACT_APP_ENV: true, }, + rules: { + "@typescript-eslint/no-use-before-define": "off" + } }; diff --git a/react-ui/config/proxy.ts b/react-ui/config/proxy.ts index fc984955..527bff75 100644 --- a/react-ui/config/proxy.ts +++ b/react-ui/config/proxy.ts @@ -15,9 +15,9 @@ export default { // localhost:8000/api/** -> https://preview.pro.ant.design/api/** '/api/': { // 要代理的地址 - // target: 'ci4s-gateway-service.ci4s-test.svc:8082', - // target: 'http://172.20.32.98:8082', - target: 'http://172.20.32.150:8082', + // target: 'http://172.20.32.181:31205', + target: 'http://172.20.32.98:8082', + // target: 'http://172.20.32.150:8082', // 配置了这个可以从 http 代理到 https // 依赖 origin 的功能可能需要这个,比如 cookie changeOrigin: true, @@ -26,7 +26,7 @@ export default { '/profile/avatar/': { target: 'http://172.20.32.181:31205', changeOrigin: true, - } + }, }, /** diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 9dd26c3e..9c30003d 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -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', }, - ] + ], }, ]; diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index de4c7db2..07fa0094 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -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 - // }, - ], -}; diff --git a/react-ui/src/dayjsConfig.ts b/react-ui/src/dayjsConfig.ts new file mode 100644 index 00000000..199db3db --- /dev/null +++ b/react-ui/src/dayjsConfig.ts @@ -0,0 +1,3 @@ +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; +dayjs.extend(duration); diff --git a/react-ui/src/enums/status.d.ts b/react-ui/src/enums/status.d.ts new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/react-ui/src/enums/status.d.ts @@ -0,0 +1 @@ + diff --git a/react-ui/src/hooks/dict.ts b/react-ui/src/hooks/dict.ts new file mode 100644 index 00000000..e474b9f2 --- /dev/null +++ b/react-ui/src/hooks/dict.ts @@ -0,0 +1,6 @@ +import { getDictValueEnum } from '@/services/system/dict'; + +export function useDictEnum(name: string) { + const data = getDictValueEnum(name); + return data; +} diff --git a/react-ui/src/hooks/index.ts b/react-ui/src/hooks/index.ts new file mode 100644 index 00000000..d0c7705a --- /dev/null +++ b/react-ui/src/hooks/index.ts @@ -0,0 +1,12 @@ +import { useEffect, useRef, useState } from 'react'; +export function useStateRef(initialValue: T) { + const [value, setValue] = useState(initialValue); + + const ref = useRef(value); + + useEffect(() => { + ref.current = value; + }, [value]); + + return [value, setValue, ref] as const; +} diff --git a/react-ui/src/hooks/net/dict.ts b/react-ui/src/hooks/net/dict.ts deleted file mode 100644 index 9c2c41c5..00000000 --- a/react-ui/src/hooks/net/dict.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getDictValueEnum } from "@/services/system/dict"; - -export function useDictEnum(name: string) -{ - const data = getDictValueEnum(name); - return data; -} \ No newline at end of file diff --git a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.less b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.less new file mode 100644 index 00000000..d0d8b09b --- /dev/null +++ b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.less @@ -0,0 +1,10 @@ +.container { + width: 100%; + height: 100%; + + .frame { + width: 100%; + height: 100%; + border: none; + } +} diff --git a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx new file mode 100644 index 00000000..e5c03dbc --- /dev/null +++ b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx @@ -0,0 +1,11 @@ +import styles from './index.less'; + +function DatasetAnnotation() { + return ( +
+ +
+ ); +} + +export default DatasetAnnotation; diff --git a/react-ui/src/pages/DevelopmentEnvironment/index.jsx b/react-ui/src/pages/DevelopmentEnvironment/index.jsx deleted file mode 100644 index 378d12f8..00000000 --- a/react-ui/src/pages/DevelopmentEnvironment/index.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React ,{ useState,useEffect,useRef }from 'react'; -import {getJupyterUrl} from '@/services/developmentEnvironment/index.js' -const developmentEnvironment = React.FC = () => { - const [iframeUrl,setIframeUrl]=useState('') - useEffect(()=>{ - getJupyterUrl().then(ret=>{ - console.log(ret); - setIframeUrl(ret.msg) - }) - },[]) - return ( - - )}; -export default developmentEnvironment; \ No newline at end of file diff --git a/react-ui/src/pages/DevelopmentEnvironment/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/index.tsx new file mode 100644 index 00000000..329c0695 --- /dev/null +++ b/react-ui/src/pages/DevelopmentEnvironment/index.tsx @@ -0,0 +1,22 @@ +import { getJupyterUrl } from '@/services/developmentEnvironment'; +import { to } from '@/utils/promise'; +import { useEffect, useState } from 'react'; + +const DevelopmentEnvironment = () => { + const [iframeUrl, setIframeUrl] = useState(''); + useEffect(() => { + requestJupyterUrl(); + }, []); + + const requestJupyterUrl = async () => { + const [res, error] = await to(getJupyterUrl()); + if (res) { + setIframeUrl(res.data as string); + } else { + console.log(error); + } + }; + + return ; +}; +export default DevelopmentEnvironment; diff --git a/react-ui/src/pages/Experiment/experimentText/LogList.tsx b/react-ui/src/pages/Experiment/experimentText/LogList.tsx new file mode 100644 index 00000000..57e7a7e1 --- /dev/null +++ b/react-ui/src/pages/Experiment/experimentText/LogList.tsx @@ -0,0 +1,18 @@ +import LogGroup from './logGroup'; + +type LogListProps = { + list: any[]; + status: string; +}; + +function LogList({ list = [], status }: LogListProps) { + return ( +
+ {list.map((v) => ( + + ))} +
+ ); +} + +export default LogList; diff --git a/react-ui/src/pages/Experiment/experimentText/addExperimentModal.less b/react-ui/src/pages/Experiment/experimentText/addExperimentModal.less new file mode 100644 index 00000000..a7bb2af8 --- /dev/null +++ b/react-ui/src/pages/Experiment/experimentText/addExperimentModal.less @@ -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; + } + } +} diff --git a/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx b/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx new file mode 100644 index 00000000..89148473 --- /dev/null +++ b/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx @@ -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([]); + 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); + form.setFieldsValue(fields); + } else { + setGlobalParam([]); + } + }; + return ( + + + {dialogTitle} + + } + open={open} + okButtonProps={{ + htmlType: 'submit', + form: 'form', + }} + onCancel={onCancel} + destroyOnClose={true} + > +
+ + + + + + + + + + {globalParam.map((item) => ( + + + + ))} +
+
+ ); +} + +export default AddExperimentModal; diff --git a/react-ui/src/pages/Experiment/experimentText/index.jsx b/react-ui/src/pages/Experiment/experimentText/index.jsx index 51b0f21f..37dbb4b3 100644 --- a/react-ui/src/pages/Experiment/experimentText/index.jsx +++ b/react-ui/src/pages/Experiment/experimentText/index.jsx @@ -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 (
-
+ }, + 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 ( +
+
-
启动时间:{momnet(experimentAllMessage.create_time).format('YYYY-MM-DD HH:mm:ss')}
-
执行时长:{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())}
-
状态: -
- {statusObj[experimentAllMessage.status]}
+
+ 启动时间:{momnet(message.create_time).format('YYYY-MM-DD HH:mm:ss')} +
+
+ 执行时长: + {message.finish_time + ? elapsedTime(new Date(message.create_time), new Date(message.finish_time)) + : elapsedTime(new Date(message.create_time), new Date())} +
+
+ 状态: +
+ + {experimentStatusInfo[message.status]?.label} + +
-
- -
)}; -export default ExperimentText; \ No newline at end of file +
+ +
+ ); +} +export default ExperimentText; diff --git a/react-ui/src/pages/Experiment/experimentText/logGroup.less b/react-ui/src/pages/Experiment/experimentText/logGroup.less new file mode 100644 index 00000000..4eee30d3 --- /dev/null +++ b/react-ui/src/pages/Experiment/experimentText/logGroup.less @@ -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; +} diff --git a/react-ui/src/pages/Experiment/experimentText/logGroup.tsx b/react-ui/src/pages/Experiment/experimentText/logGroup.tsx new file mode 100644 index 00000000..f50114b3 --- /dev/null +++ b/react-ui/src/pages/Experiment/experimentText/logGroup.tsx @@ -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([]); + 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 ( +
+ {log_type === 'resource' && ( +
+
{pod_name}
+ {collapse ? : } +
+ )} + {showLog &&
{logText}
} +
+ {showMoreBtn && ( + + )} +
+
+ ); +} + +export default LogGroup; diff --git a/react-ui/src/pages/Experiment/experimentText/props.jsx b/react-ui/src/pages/Experiment/experimentText/props.jsx index f322cba9..1ab54930 100644 --- a/react-ui/src/pages/Experiment/experimentText/props.jsx +++ b/react-ui/src/pages/Experiment/experimentText/props.jsx @@ -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:
, - icon: - }, - { - key: '2', - label: '配置参数', - icon:, - children:
-
- - 基本信息 -
- - - - - - -
- - 任务信息 -
- - - - - - - - - - - - - - - - - { + 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: , + icon: , + }, + { + key: '2', + label: '配置参数', + icon: , + children: ( + -