| @@ -76,7 +76,7 @@ export default defineConfig({ | |||||
| * @name layout 插件 | * @name layout 插件 | ||||
| * @doc https://umijs.org/docs/max/layout-menu | * @doc https://umijs.org/docs/max/layout-menu | ||||
| */ | */ | ||||
| title: 'Ant Design Pro', | |||||
| title: '复杂智能软件', | |||||
| layout: { | layout: { | ||||
| locale: true, | locale: true, | ||||
| ...defaultSettings, | ...defaultSettings, | ||||
| @@ -14,9 +14,9 @@ const Settings: ProLayoutProps & { | |||||
| contentWidth: 'Fluid', | contentWidth: 'Fluid', | ||||
| fixedHeader: false, | fixedHeader: false, | ||||
| fixSiderbar: false, | fixSiderbar: false, | ||||
| splitMenus: true, | |||||
| splitMenus: false, | |||||
| colorWeak: false, | colorWeak: false, | ||||
| title: 'Ant Design Pro', | |||||
| title: '复杂智能软件', | |||||
| pwa: true, | pwa: true, | ||||
| logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', | logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', | ||||
| iconfontUrl: '', | iconfontUrl: '', | ||||
| @@ -15,14 +15,15 @@ export default { | |||||
| // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | ||||
| '/api/': { | '/api/': { | ||||
| // 要代理的地址 | // 要代理的地址 | ||||
| target: 'http://localhost:8082', | |||||
| // target: 'http://172.20.32.181:31205', | |||||
| target: 'http://172.20.32.150:8082', | |||||
| // 配置了这个可以从 http 代理到 https | // 配置了这个可以从 http 代理到 https | ||||
| // 依赖 origin 的功能可能需要这个,比如 cookie | // 依赖 origin 的功能可能需要这个,比如 cookie | ||||
| changeOrigin: true, | changeOrigin: true, | ||||
| pathRewrite: { '^/api': '' }, | pathRewrite: { '^/api': '' }, | ||||
| }, | }, | ||||
| '/profile/avatar/': { | '/profile/avatar/': { | ||||
| target: 'http://localhost:8082', | |||||
| target: 'http://172.20.32.181:31205', | |||||
| changeOrigin: true, | changeOrigin: true, | ||||
| } | } | ||||
| }, | }, | ||||
| @@ -46,6 +46,49 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: 'pipeline', | |||||
| path: '/pipeline', | |||||
| routes: [ | |||||
| { | |||||
| name: '流水线', | |||||
| path: '/pipeline', | |||||
| component: './Pipeline/index', | |||||
| }, | |||||
| { | |||||
| name: '训练', | |||||
| path: '/pipeline/pytorchtext/:id/:name', | |||||
| component: './Pipeline/editPipeline/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| name: 'experiment', | |||||
| path: '/experiment', | |||||
| routes: [ | |||||
| { | |||||
| name: '实验', | |||||
| path: '/experiment', | |||||
| component: './Experiment/index', | |||||
| }, | |||||
| { | |||||
| name: '实验训练', | |||||
| path: '/experiment/pytorchtext/:workflowId/:id', | |||||
| component: './Experiment/experimentText/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| name: 'developmentEnvironment', | |||||
| path: '/developmentEnvironment', | |||||
| routes: [ | |||||
| { | |||||
| name: '开发环境', | |||||
| path: '/developmentEnvironment', | |||||
| component: './DevelopmentEnvironment/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | { | ||||
| name: 'system', | name: 'system', | ||||
| path: '/system', | path: '/system', | ||||
| @@ -12,7 +12,7 @@ export default async () => { | |||||
| ...config, | ...config, | ||||
| testEnvironmentOptions: { | testEnvironmentOptions: { | ||||
| ...(config?.testEnvironmentOptions || {}), | ...(config?.testEnvironmentOptions || {}), | ||||
| url: 'http://localhost:8081', | |||||
| url: 'http://localhost:8000', | |||||
| }, | }, | ||||
| setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'], | setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'], | ||||
| globals: { | globals: { | ||||
| @@ -56,13 +56,14 @@ | |||||
| "@ant-design/icons": "^5.0.0", | "@ant-design/icons": "^5.0.0", | ||||
| "@ant-design/pro-components": "^2.4.4", | "@ant-design/pro-components": "^2.4.4", | ||||
| "@ant-design/use-emotion-css": "1.0.4", | "@ant-design/use-emotion-css": "1.0.4", | ||||
| "@antv/g6": "^4.8.24", | |||||
| "@umijs/route-utils": "^4.0.1", | "@umijs/route-utils": "^4.0.1", | ||||
| "antd": "^5.4.4", | "antd": "^5.4.4", | ||||
| "classnames": "^2.3.2", | "classnames": "^2.3.2", | ||||
| "fabric": "^5.3.0", | "fabric": "^5.3.0", | ||||
| "highlight.js": "^11.7.0", | "highlight.js": "^11.7.0", | ||||
| "lodash": "^4.17.21", | "lodash": "^4.17.21", | ||||
| "moment": "^2.29.4", | |||||
| "moment": "^2.30.1", | |||||
| "omit.js": "^2.0.2", | "omit.js": "^2.0.2", | ||||
| "pnpm": "^8.9.0", | "pnpm": "^8.9.0", | ||||
| "query-string": "^8.1.0", | "query-string": "^8.1.0", | ||||
| @@ -1,4 +1,4 @@ | |||||
| @font-face { | |||||
| font-family: Alibaba; | |||||
| @font-face { | |||||
| font-family: Alibaba; | |||||
| src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); | src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); | ||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| 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 ( | |||||
| <iframe style={{width:'100%',height:'81vh'}} src={iframeUrl} frameborder="0"></iframe> | |||||
| )}; | |||||
| export default developmentEnvironment; | |||||
| @@ -0,0 +1,30 @@ | |||||
| #graph { | |||||
| height: 100%; | |||||
| width: 100%; | |||||
| position: relative; | |||||
| } | |||||
| .editPipelinePropsContent{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height:43px; | |||||
| background:#f8fbff; | |||||
| color:#1d1d20; | |||||
| font-size:15px; | |||||
| font-family: 'Alibaba'; | |||||
| padding: 0 20px; | |||||
| } | |||||
| .centerContainer{ | |||||
| flex: 1; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | |||||
| .buttonList{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding: 0 30px; | |||||
| width: 100%; | |||||
| height:45px; | |||||
| background:#ffffff; | |||||
| box-shadow:0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| } | |||||
| @@ -0,0 +1,335 @@ | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||||
| import { useParams } from 'react-router-dom' | |||||
| import Props from './props'; | |||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||||
| import G6 from '@antv/g6'; | |||||
| import Styles from './editPipeline.less' | |||||
| 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'; | |||||
| const ExperimentText = React.FC = () => { | |||||
| const propsRef=useRef() | |||||
| const navgite=useNavigate(); | |||||
| const locationParams =useParams () //新版本获取路由参数接口 | |||||
| let graph=null | |||||
| const [experimentStatusObj,setExperimentStatusObj]=useState({}) | |||||
| const pipelineContainer = useEmotionCss(() => { | |||||
| return { | |||||
| display: 'flex', | |||||
| backgroundColor:'#fff', | |||||
| height:'81vh' | |||||
| }; | |||||
| }); | |||||
| 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); | |||||
| // 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) | |||||
| } | |||||
| } | |||||
| 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){ | |||||
| const experimentStatusObjs=JSON.parse(res.data.nodes_status) | |||||
| const newNodeList= JSON.parse(ret.data.dag).nodes.map(item=>{console.log(experimentStatusObjs); return {...item,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); | |||||
| 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); | |||||
| // }, 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.1, 0.05], | |||||
| [0.5, 0.05], | |||||
| [0.9, 0.05], | |||||
| [0, 0.5], | |||||
| [1, 0.5], | |||||
| [0.1, 1], | |||||
| [0.5, 1], | |||||
| [0.9, 1], | |||||
| // 四边中间 | |||||
| // [0.5, 0.05], | |||||
| // [0, 0.5], | |||||
| // [1, 0.5], | |||||
| // [0.5, 1], | |||||
| // 四个角落 | |||||
| // [0.05, 0.05], | |||||
| // [0.9, 0.05], | |||||
| // [0.05, 1], | |||||
| // [0.9, 1], | |||||
| ] | |||||
| ); | |||||
| }, | |||||
| afterDraw(cfg, group) { | |||||
| // console.log(group, cfg, 12312); | |||||
| const image = group.addShape('image', { | |||||
| attrs: { | |||||
| x: -25, | |||||
| y: -13, | |||||
| width: 23, | |||||
| height: 21, | |||||
| 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: 5, | |||||
| x: bbox.x + bbox.width * anchorPos[0], | |||||
| y: bbox.y + bbox.height * anchorPos[1], | |||||
| fill: '#000', | |||||
| stroke: '#000', | |||||
| }, | |||||
| 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 | |||||
| draggable: true, // allow to catch the drag events on this shape | |||||
| }); | |||||
| }); | |||||
| return image; | |||||
| }, | |||||
| // response the state changes and show/hide the link-point circles | |||||
| setState(name, value, item) { | |||||
| // 默认显示全部锚点,防止过宽导致锚点无法被选中 | |||||
| // if (name === 'showAnchors') { | |||||
| const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | |||||
| anchorPoints.forEach(point => { | |||||
| // if (value) point.show(); | |||||
| // else point.hide(); | |||||
| point.show(); | |||||
| }); | |||||
| // } | |||||
| }, | |||||
| }, | |||||
| '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, | |||||
| plugins: [], | |||||
| enabledStack: true, | |||||
| modes: { | |||||
| default: [ | |||||
| 'drag-canvas', | |||||
| 'zoom-canvas', | |||||
| ], | |||||
| altSelect: [ | |||||
| { | |||||
| type: 'brush-select', | |||||
| trigger: 'drag', | |||||
| }, | |||||
| 'drag-node', | |||||
| ], | |||||
| }, | |||||
| defaultNode: { | |||||
| type: 'rect-node', | |||||
| size: 70, | |||||
| labelCfg: { | |||||
| style: { | |||||
| fill: '#000', | |||||
| fontSize: 12, | |||||
| cursor: 'pointer', | |||||
| x: 0, | |||||
| y: 0, | |||||
| textAlign: 'left', | |||||
| textBaseline: 'middle', | |||||
| }, | |||||
| }, | |||||
| style: { | |||||
| fill: 'transparent', | |||||
| stroke: 'transparent', | |||||
| }, | |||||
| }, | |||||
| // nodeStateStyles: { | |||||
| // nodeSelected: { | |||||
| // fill: 'red', | |||||
| // shadowColor: 'red', | |||||
| // stroke: 'red', | |||||
| // 'text-shape': { | |||||
| // fill: 'red', | |||||
| // stroke: 'red', | |||||
| // }, | |||||
| // }, | |||||
| // }, | |||||
| defaultEdge: { | |||||
| // type: 'quadratic', | |||||
| type: 'polyline', | |||||
| style: { | |||||
| endArrow: { | |||||
| path: G6.Arrow.triangle(), | |||||
| }, | |||||
| cursor: 'pointer', | |||||
| endArrow: true, | |||||
| lineWidth: 1, | |||||
| opacity: 1, | |||||
| stroke: '#a2a6b5', | |||||
| radius: 10, | |||||
| }, | |||||
| nodeStateStyle: { | |||||
| hover: { | |||||
| opacity: 1, | |||||
| stroke: '#8fe8ff', | |||||
| }, | |||||
| }, | |||||
| labelCfg: { | |||||
| autoRotate: true, | |||||
| // refY: 10, | |||||
| style: { | |||||
| fontSize: 10, | |||||
| fill: '#FFF', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| defaultCombo: { | |||||
| type: 'rect', | |||||
| fixCollapseSize: 70, | |||||
| style: { | |||||
| fill: '#00e0ff0d', | |||||
| stroke: '#00e0ff', | |||||
| lineDash: [5, 10], | |||||
| cursor: 'pointer', | |||||
| }, | |||||
| }, | |||||
| linkCenter: true, | |||||
| fitView: true, | |||||
| 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> | |||||
| <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | |||||
| </div> | |||||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||||
| </div>)}; | |||||
| export default ExperimentText; | |||||
| @@ -0,0 +1,265 @@ | |||||
| import React, { useState,useImperativeHandle ,forwardRef } from 'react'; | |||||
| import { Button, Drawer,Form, Input ,Tabs } from 'antd'; | |||||
| import Styles from './editPipeline.less' | |||||
| import{getQueryByExperimentLog}from '@/services/experiment/index.js' | |||||
| import { ProfileOutlined, DatabaseOutlined} from '@ant-design/icons'; | |||||
| const { TextArea } = Input; | |||||
| const Props = forwardRef(({onParentChange}, ref) =>{ | |||||
| const [form] = Form.useForm(); | |||||
| const [stagingItem,setStagingItem]=useState({}) | |||||
| const [messageItem,setMessageItem]=useState('') | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: '日志详情', | |||||
| children: <div style={{height:'740px', | |||||
| background:'rgba(234, 234, 234, 0.5)', | |||||
| color:'rgba(29, 29, 32, 0.8)', | |||||
| 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" | |||||
| > | |||||
| <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> | |||||
| {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> | |||||
| {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>, | |||||
| }, | |||||
| ]; | |||||
| 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 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()}) | |||||
| } | |||||
| } | |||||
| 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); | |||||
| 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)}) | |||||
| // form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setTimeout(() => { | |||||
| // console.log(stagingItem); | |||||
| // }, (500)); | |||||
| setOpen(true); | |||||
| }) | |||||
| } | |||||
| 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)}) | |||||
| // form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setTimeout(() => { | |||||
| // console.log(stagingItem); | |||||
| // }, (500)); | |||||
| setOpen(true); | |||||
| } | |||||
| // console.log(e.item.getModel().in_parameters); | |||||
| }, | |||||
| })); | |||||
| return ( | |||||
| <> | |||||
| <Drawer title="编辑任务" placement="right" closeIcon={false} onClose={onClose} afterOpenChange={afterOpenChange} open={open}> | |||||
| <Tabs | |||||
| defaultActiveKey="1" | |||||
| items={items}/> | |||||
| </Drawer> | |||||
| </> | |||||
| ); | |||||
| }); | |||||
| export default Props; | |||||
| @@ -0,0 +1,364 @@ | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||||
| import { Space, Table, Tag,Button,Modal, Form, Input ,message, Select,} from 'antd'; | |||||
| import { PlusOutlined, EditOutlined ,PlayCircleOutlined} from '@ant-design/icons'; | |||||
| import {getWorkflow,addWorkflow,removeWorkflow,cloneWorkflow} from '@/services/pipeline/index.js' | |||||
| import {getExperiment,runExperiments,getExperimentById,postExperiment,putExperiment,getQueryByExperimentId} from '@/services/experiment/index.js' | |||||
| import Styles from './index.less' | |||||
| import { useNavigate} from 'react-router-dom'; | |||||
| import momnet from 'moment' | |||||
| const { TextArea } = Input; | |||||
| const Experiment = React.FC = () => { | |||||
| const [form] = Form.useForm(); | |||||
| const navgite=useNavigate(); | |||||
| const statusObj={ | |||||
| "Running":'运行中', | |||||
| "Succeeded":'成功', | |||||
| "Failed":'失败', | |||||
| "Error":'错误', | |||||
| "Teminated":'终止' | |||||
| } | |||||
| const [experimentList, setExperimentList] = useState([]); | |||||
| const [workflowList, setWorkflowList] = useState([]); | |||||
| const [queryFlow,setQueryFlow]=useState({ | |||||
| offset:1, | |||||
| page:0, | |||||
| size:10000, | |||||
| name: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 [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) | |||||
| } | |||||
| }) | |||||
| } | |||||
| const getQueryByExperiment=(val)=>{ | |||||
| getQueryByExperimentId(val).then(ret=>{ | |||||
| setExpandedRowKeys(val) | |||||
| if(ret.code==200&&ret.data&&ret.data.length>0){ | |||||
| setExperimentInList(ret.data) | |||||
| } | |||||
| else{ | |||||
| setExperimentInList([]) | |||||
| } | |||||
| }) | |||||
| } | |||||
| const expandChange=(e,record)=>{ | |||||
| if(record.id==expandedRowKeys){ | |||||
| setExpandedRowKeys(null) | |||||
| } | |||||
| else{ | |||||
| getQueryByExperiment(record.id) | |||||
| } | |||||
| } | |||||
| const showModal = () => { | |||||
| setDialogTitle('新建实验') | |||||
| console.log(workflowList); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const editTable=(id)=>{ | |||||
| getExperimentById(id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| form.setFieldsValue({...ret.data}) | |||||
| setFormId(ret.data.id) | |||||
| setDialogTitle('编辑实验') | |||||
| getWorkflowList() | |||||
| setIsModalOpen(true) | |||||
| } | |||||
| }) | |||||
| // navgite({pathname:`/pipeline/pytorchtext/${record.id}/${record.name}` }); | |||||
| } | |||||
| const handleOk = () => { | |||||
| console.log(1111); | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| 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('新建实验失败') | |||||
| } | |||||
| }) | |||||
| } | |||||
| else{ | |||||
| putExperiment({...values,id:formId}).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| message.success('编辑实验成功') | |||||
| setIsModalOpen(false) | |||||
| getList() | |||||
| } | |||||
| else{ | |||||
| message.error('编辑实验失败') | |||||
| } | |||||
| }) | |||||
| } | |||||
| 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 | |||||
| } | |||||
| getList() | |||||
| } | |||||
| const runExperiment=(id)=>{ | |||||
| runExperiments(id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| message.success('运行成功') | |||||
| getQueryByExperiment(id) | |||||
| getList() | |||||
| } | |||||
| 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: 'state', | |||||
| key: 'state', | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| key: 'action', | |||||
| 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> | |||||
| </Space> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return (<div> | |||||
| <div > | |||||
| <Button type="primary" onClick={showModal} icon = {< PlusOutlined />}> | |||||
| 新建实验 | |||||
| </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>:''} | |||||
| {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 style={{width:'200px'}}>{statusObj[item.status]}</div> | |||||
| <div style={{width:'300px'}}>{item.finish_time?timers(new Date(item.finish_time).getTime()-new Date(item.start_time).getTime()):timers(new Date().getTime()-new Date(item.start_time).getTime())}</div> | |||||
| <div style={{width:'300px'}}>{momnet(item.start_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||||
| </div> | |||||
| )):''} | |||||
| </div> | |||||
| ), | |||||
| onExpand:(e,a)=>{expandChange(e,a)}, | |||||
| expandedRowKeys:[expandedRowKeys], | |||||
| rowExpandable: (record) =>true, | |||||
| }}/> | |||||
| <Modal title={dialogTitle} open={isModalOpen} okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} onCancel={handleCancel}> | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 8, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 16, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| 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> | |||||
| {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; | |||||
| @@ -0,0 +1,35 @@ | |||||
| .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); | |||||
| } | |||||
| // .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:15px; | |||||
| // font-family: 'Alibaba'; | |||||
| // } | |||||
| // .plusButton:hover{ | |||||
| // background:rgba(22, 100, 255, 0.06); | |||||
| // border:1px solid; | |||||
| // border-color:rgba(22, 100, 255, 0.11); | |||||
| // border-radius:4px; | |||||
| // } | |||||
| .tableExpandBox{ | |||||
| width: 100%; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| color:#1d1d20; | |||||
| font-size:15px; | |||||
| padding: 0 65px 0 40px; | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| #graph { | |||||
| height: 100%; | |||||
| width: 100%; | |||||
| position: relative; | |||||
| } | |||||
| .editPipelinePropsContent{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height:43px; | |||||
| background:#f8fbff; | |||||
| color:#1d1d20; | |||||
| font-size:15px; | |||||
| font-family: 'Alibaba'; | |||||
| padding: 0 20px; | |||||
| } | |||||
| .centerContainer{ | |||||
| flex: 1; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | |||||
| .buttonList{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding: 0 30px; | |||||
| width: 100%; | |||||
| height:45px; | |||||
| background:#ffffff; | |||||
| box-shadow:0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| } | |||||
| @@ -0,0 +1,366 @@ | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||||
| import { useParams } from 'react-router-dom' | |||||
| import ModelMenus from './modelMenus'; | |||||
| import Props from './props'; | |||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||||
| import G6 from '@antv/g6'; | |||||
| import Styles from './editPipeline.less' | |||||
| import { s8 } from '../../../utils'; | |||||
| import { Button, message} from 'antd'; | |||||
| import {SaveOutlined} from '@ant-design/icons'; | |||||
| import {saveWorkflow,getWorkflowById} from '@/services/pipeline/index.js' | |||||
| import { useNavigate} from 'react-router-dom'; | |||||
| const editPipeline = React.FC = () => { | |||||
| const propsRef=useRef() | |||||
| const navgite=useNavigate(); | |||||
| const locationParams =useParams () //新版本获取路由参数接口 | |||||
| let graph=null | |||||
| const pipelineContainer = useEmotionCss(() => { | |||||
| return { | |||||
| display: 'flex', | |||||
| backgroundColor:'#fff', | |||||
| height:'81vh' | |||||
| }; | |||||
| }); | |||||
| 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 data = graph.save(); | |||||
| const index = data.nodes.findIndex(item => { | |||||
| return item.id ===val.id; | |||||
| }); | |||||
| data.nodes[index] = val; | |||||
| graph.changeData(data) | |||||
| } | |||||
| const savePipeline=()=>{ | |||||
| const data = graph.save(); | |||||
| console.log(data); | |||||
| let params={ | |||||
| ...locationParams, | |||||
| dag:JSON.stringify(data) | |||||
| } | |||||
| saveWorkflow(params).then(ret=>{ | |||||
| console.log(ret); | |||||
| message.success('保存成功') | |||||
| setTimeout(()=>{ | |||||
| navgite({pathname:`/pipeline`,}); | |||||
| },500) | |||||
| }) | |||||
| console.log(params); | |||||
| } | |||||
| 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) | |||||
| } | |||||
| const getGraphData=(data)=>{ | |||||
| if(graph){ | |||||
| console.log(graph); | |||||
| graph.data(data) | |||||
| graph.render() | |||||
| } | |||||
| else{ | |||||
| setTimeout(()=>{ | |||||
| getGraphData(data) | |||||
| },500) | |||||
| } | |||||
| } | |||||
| const getFirstWorkflow=(val)=>{ | |||||
| getWorkflowById(val).then(ret=>{ | |||||
| console.log(ret); | |||||
| if(ret.code==200){ | |||||
| if(graph&&ret.data&&ret.data.dag){ | |||||
| getGraphData(JSON.parse(ret.data.dag)) | |||||
| } | |||||
| } | |||||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||||
| // graph.render() | |||||
| }) | |||||
| } | |||||
| useEffect(()=>{ | |||||
| getFirstWorkflow(locationParams.id) | |||||
| initGraph() | |||||
| },[]) | |||||
| const initGraph=()=>{ | |||||
| G6.registerNode( | |||||
| 'rect-node', | |||||
| { | |||||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||||
| getAnchorPoints(cfg) { | |||||
| return ( | |||||
| cfg.anchorPoints || [ | |||||
| // 上下各3,左右各1 | |||||
| [0.1, 0.05], | |||||
| [0.5, 0.05], | |||||
| [0.9, 0.05], | |||||
| [0, 0.5], | |||||
| [1, 0.5], | |||||
| [0.1, 1], | |||||
| [0.5, 1], | |||||
| [0.9, 1], | |||||
| // 四边中间 | |||||
| // [0.5, 0.05], | |||||
| // [0, 0.5], | |||||
| // [1, 0.5], | |||||
| // [0.5, 1], | |||||
| // 四个角落 | |||||
| // [0.05, 0.05], | |||||
| // [0.9, 0.05], | |||||
| // [0.05, 1], | |||||
| // [0.9, 1], | |||||
| ] | |||||
| ); | |||||
| }, | |||||
| afterDraw(cfg, group) { | |||||
| // console.log(group, cfg, 12312); | |||||
| const image = group.addShape('image', { | |||||
| attrs: { | |||||
| x: -25, | |||||
| y: -13, | |||||
| width: 21, | |||||
| height: 21, | |||||
| 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: 5, | |||||
| x: bbox.x + bbox.width * anchorPos[0], | |||||
| y: bbox.y + bbox.height * anchorPos[1], | |||||
| fill: '#000', | |||||
| stroke: '#000', | |||||
| }, | |||||
| 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 | |||||
| draggable: true, // allow to catch the drag events on this shape | |||||
| }); | |||||
| }); | |||||
| return image; | |||||
| }, | |||||
| // response the state changes and show/hide the link-point circles | |||||
| setState(name, value, item) { | |||||
| // 默认显示全部锚点,防止过宽导致锚点无法被选中 | |||||
| // if (name === 'showAnchors') { | |||||
| const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | |||||
| anchorPoints.forEach(point => { | |||||
| // if (value) point.show(); | |||||
| // else point.hide(); | |||||
| point.show(); | |||||
| }); | |||||
| // } | |||||
| }, | |||||
| }, | |||||
| '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, | |||||
| fitView:true, | |||||
| plugins: [], | |||||
| 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 | |||||
| { | |||||
| type: 'create-edge', | |||||
| key: 'shift', // undefined by default, options: 'shift', 'control', 'ctrl', 'meta', 'alt' | |||||
| }, | |||||
| 'drag-canvas', | |||||
| 'zoom-canvas', | |||||
| // 'brush-select', | |||||
| 'drag-combo', | |||||
| ], | |||||
| altSelect: [ | |||||
| { | |||||
| type: 'brush-select', | |||||
| trigger: 'drag', | |||||
| }, | |||||
| 'drag-node', | |||||
| ], | |||||
| }, | |||||
| defaultNode: { | |||||
| type: 'rect-node', | |||||
| size: 70, | |||||
| labelCfg: { | |||||
| style: { | |||||
| fill: '#000', | |||||
| fontSize: 12, | |||||
| cursor: 'pointer', | |||||
| x: 0, | |||||
| y: 0, | |||||
| textAlign: 'left', | |||||
| textBaseline: 'middle', | |||||
| }, | |||||
| }, | |||||
| style: { | |||||
| fill: 'transparent', | |||||
| stroke: 'transparent', | |||||
| }, | |||||
| }, | |||||
| // nodeStateStyles: { | |||||
| // nodeSelected: { | |||||
| // fill: 'red', | |||||
| // shadowColor: 'red', | |||||
| // stroke: 'red', | |||||
| // 'text-shape': { | |||||
| // fill: 'red', | |||||
| // stroke: 'red', | |||||
| // }, | |||||
| // }, | |||||
| // }, | |||||
| defaultEdge: { | |||||
| // type: 'quadratic', | |||||
| type: 'polyline', | |||||
| style: { | |||||
| endArrow: { | |||||
| path: G6.Arrow.triangle(), | |||||
| }, | |||||
| cursor: 'pointer', | |||||
| endArrow: true, | |||||
| lineWidth: 1, | |||||
| opacity: 1, | |||||
| stroke: '#a2a6b5', | |||||
| radius: 10, | |||||
| }, | |||||
| nodeStateStyle: { | |||||
| hover: { | |||||
| opacity: 1, | |||||
| stroke: '#8fe8ff', | |||||
| }, | |||||
| }, | |||||
| labelCfg: { | |||||
| autoRotate: true, | |||||
| // refY: 10, | |||||
| style: { | |||||
| fontSize: 10, | |||||
| fill: '#FFF', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| defaultCombo: { | |||||
| type: 'rect', | |||||
| fixCollapseSize: 70, | |||||
| style: { | |||||
| fill: '#00e0ff0d', | |||||
| stroke: '#00e0ff', | |||||
| lineDash: [5, 10], | |||||
| cursor: 'pointer', | |||||
| }, | |||||
| }, | |||||
| linkCenter: true, | |||||
| fitView: true, | |||||
| fitViewPadding: [60, 60, 60, 80], | |||||
| }); | |||||
| graph.on('dblclick', handlerClick); | |||||
| graph.on('aftercreateedge', (e) => { | |||||
| const edges = graph.save().edges; | |||||
| G6.Util.processParallelEdges(edges); | |||||
| graph.getEdges().forEach((edge, i) => { | |||||
| graph.updateItem(edge, { | |||||
| curveOffset: edges[i].curveOffset, | |||||
| curvePosition: edges[i].curvePosition, | |||||
| }); | |||||
| }); | |||||
| }); | |||||
| 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}> | |||||
| <ModelMenus onParDragEnd={onDragEnd}></ModelMenus> | |||||
| <div className={Styles.centerContainer}> | |||||
| <div className={Styles.buttonList}> | |||||
| <Button type="primary" shape="round" icon={<SaveOutlined />} onClick={savePipeline}>撤回</Button> | |||||
| <Button type="primary" shape="round" icon={<SaveOutlined />} onClick={savePipeline}>恢复</Button> | |||||
| <Button type="primary" shape="round" icon={<SaveOutlined />} onClick={savePipeline}>保存</Button> | |||||
| </div> | |||||
| <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | |||||
| </div> | |||||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||||
| </div>)}; | |||||
| export default editPipeline; | |||||
| @@ -0,0 +1,60 @@ | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||||
| import { Collapse, Divider } from 'antd'; | |||||
| import Styles from './modelMenus.less' | |||||
| import {getComponentAll} from '@/services/pipeline/index.js' | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: 'This is panel header 1', | |||||
| children: [1,2,3,4,5], | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: 'This is panel header 2', | |||||
| children: [1,2,3,4,5], | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: 'This is panel header 3', | |||||
| children: [1,2,3,4,5], | |||||
| }, | |||||
| ]; | |||||
| const modelMenus = ({onParDragEnd}) => { | |||||
| const [modelMenusList,setModelMenusList]=useState([]) | |||||
| useEffect(()=>{ | |||||
| getComponentAll().then(ret=>{ | |||||
| console.log(ret); | |||||
| if(ret.code==200){ | |||||
| setModelMenusList(ret.data) | |||||
| } | |||||
| }) | |||||
| },[]) | |||||
| const dragEnd=(e,data)=>{ | |||||
| console.log(e,data); | |||||
| onParDragEnd({...data,x:e.clientX,y:e.clientY,label:data.component_label,img:`/assets/images/${data.icon_path}.png`}) | |||||
| } | |||||
| const { Panel } = Collapse; | |||||
| return (<div style={{width:'300px',height:'100%'}}> | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| defaultActiveKey={['1']} | |||||
| > | |||||
| {modelMenusList && modelMenusList.length > 0 | |||||
| ? modelMenusList.map(item => ( | |||||
| <Panel | |||||
| header={<div>{item.name}</div>} | |||||
| key={item.key} | |||||
| > | |||||
| {item.value&&item.value.length>0?item.value.map(ele=>(<div draggable="true" onDragEnd={(e)=>{dragEnd(e,ele)}} className={Styles.collapseItem}> | |||||
| <img style={{height:'16px',marginRight:'15px'}} src={`/assets/images/${ele.icon_path}.png`} alt="" /> | |||||
| {ele.component_label} | |||||
| </div>) | |||||
| ):''} | |||||
| </Panel> | |||||
| )) | |||||
| : ""} | |||||
| </Collapse> | |||||
| </div>)}; | |||||
| export default modelMenus; | |||||
| @@ -0,0 +1,12 @@ | |||||
| .collapseList{ | |||||
| } | |||||
| .collapseItem{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color:#575757; | |||||
| font-size:14px; | |||||
| height:40px; | |||||
| cursor: pointer; | |||||
| } | |||||
| @@ -0,0 +1,214 @@ | |||||
| import React, { useState,useImperativeHandle ,forwardRef } from 'react'; | |||||
| import { Button, Drawer,Form, Input , } from 'antd'; | |||||
| import Styles from './editPipeline.less' | |||||
| const { TextArea } = Input; | |||||
| const Props = forwardRef(({onParentChange}, ref) =>{ | |||||
| const [form] = Form.useForm(); | |||||
| const [stagingItem,setStagingItem]=useState({}) | |||||
| 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 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()}) | |||||
| } | |||||
| } | |||||
| const onClose=()=> { | |||||
| setOpen(false); | |||||
| }; | |||||
| const onFinish = (values) => { | |||||
| console.log('Success:', values); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | |||||
| showDrawer (e) { | |||||
| console.log(e.item.getModel()); | |||||
| // console.log(e.item.getModel().in_parameters); | |||||
| 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)}) | |||||
| // setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setTimeout(() => { | |||||
| // console.log(stagingItem); | |||||
| // }, (500)); | |||||
| setOpen(true); | |||||
| }, | |||||
| })); | |||||
| return ( | |||||
| <> | |||||
| <Drawer title="编辑任务" placement="right" closeIcon={false} onClose={onClose} afterOpenChange={afterOpenChange} open={open}> | |||||
| <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 /> | |||||
| </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 /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="工作目录" | |||||
| name="working_directory" | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="启动命令" | |||||
| name="command" | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="挂载路径" | |||||
| name="mount_path" | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="环境变量" | |||||
| name="env_variables" | |||||
| > | |||||
| <TextArea /> | |||||
| </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} | |||||
| name={item} | |||||
| > | |||||
| <Input /> | |||||
| </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=> | |||||
| <Form.Item | |||||
| label={stagingItem.in_parameters[item].label+'('+item+')'} | |||||
| name={item} | |||||
| rules={[{ required: stagingItem.in_parameters[item].require?true:false}]} | |||||
| > | |||||
| <Input /> | |||||
| </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=> | |||||
| <Form.Item | |||||
| label={stagingItem.out_parameters[item].label+'('+item+')'} | |||||
| rules={[{ required: stagingItem.out_parameters[item].require?true:false}]} | |||||
| name={item} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| ):''} | |||||
| </Form> | |||||
| </Drawer> | |||||
| </> | |||||
| ); | |||||
| }); | |||||
| export default Props; | |||||
| @@ -1,58 +1,142 @@ | |||||
| import React ,{ useState }from 'react'; | |||||
| import { Space, Table, Tag,Button,Modal, Form, Input } from 'antd'; | |||||
| import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons'; | |||||
| import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'; | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||||
| import { Space, Table, Tag,Button,Modal, Form, Input ,message} from 'antd'; | |||||
| import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||||
| import {getWorkflow,addWorkflow,removeWorkflow,cloneWorkflow,getWorkflowById,editWorkflow} from '@/services/pipeline/index.js' | |||||
| import Styles from './index.less' | import Styles from './index.less' | ||||
| import momnet from 'moment' | |||||
| import { useNavigate} from 'react-router-dom'; | |||||
| const { TextArea } = Input; | |||||
| const Pipeline = React.FC = () => { | const Pipeline = React.FC = () => { | ||||
| const editTable=()=>{ | |||||
| console.log(111) | |||||
| } | |||||
| const [form] = Form.useForm(); | |||||
| const navgite=useNavigate(); | |||||
| const [formId,setFormId]=useState(null) | |||||
| const [dialogTitle,setDialogTitle]=useState('新建流水线') | |||||
| const [pipeList, setPipeList] = useState([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | const [isModalOpen, setIsModalOpen] = useState(false); | ||||
| const editTable=(e,record)=>{ | |||||
| e.stopPropagation() | |||||
| getWorkflowById(record.id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| form.resetFields() | |||||
| form.setFieldsValue({...ret.data}) | |||||
| setFormId(ret.data.id) | |||||
| setDialogTitle('编辑流水线') | |||||
| setIsModalOpen(true) | |||||
| } | |||||
| }) | |||||
| } | |||||
| const routeToEdit=(e,record)=>{ | |||||
| e.stopPropagation() | |||||
| navgite({pathname:`/pipeline/pytorchtext/${record.id}/${record.name}` }); | |||||
| } | |||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields() | |||||
| setDialogTitle('编辑流水线') | |||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| console.log(1111); | |||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| }; | }; | ||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| }; | }; | ||||
| const onFinish = (values) => { | const onFinish = (values) => { | ||||
| console.log('Success:', values); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| if(formId){ | |||||
| editWorkflow({...values,id:formId}).then(ret=>{ | |||||
| message.success('编辑成功') | |||||
| getList() | |||||
| setIsModalOpen(false) | |||||
| }) | |||||
| } | |||||
| else{ | |||||
| addWorkflow(values).then(ret=>{ | |||||
| navgite({pathname:`/pipeline/pytorchtext/${ret.id}/${ret.name}`,}); | |||||
| } | |||||
| ) | |||||
| } | |||||
| }; | }; | ||||
| 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 | |||||
| } | |||||
| getList() | |||||
| } | |||||
| const getList=()=>{ | |||||
| let params={ | |||||
| offset:1, | |||||
| page:pageOption.current.page-1, | |||||
| size:pageOption.current.size | |||||
| } | |||||
| console.log(params,pageOption); | |||||
| getWorkflow(params).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| setPipeList(ret.data.content) | |||||
| setTotal(ret.data.totalElements) | |||||
| } | |||||
| }) | |||||
| } | |||||
| useEffect(()=>{ | |||||
| getList() | |||||
| },[]) | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: 60, | width: 60, | ||||
| render:(text,record,index)=> index + 1, | |||||
| render(text, record, index) { | |||||
| return ( | |||||
| <span>{(pageOption.current.page - 1) * 10 + index + 1}</span> | |||||
| ) | |||||
| } | |||||
| // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '流水线名称', | title: '流水线名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: (text) => <a>{text}</a>, | |||||
| render: (text,record) => <a onClick={(e)=>routeToEdit(e,record)}>{text}</a>, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '流水线描述', | title: '流水线描述', | ||||
| dataIndex: 'age', | |||||
| key: 'age', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'address', | |||||
| key: 'address', | |||||
| dataIndex: 'create_time', | |||||
| key: 'create_time', | |||||
| render: (text) => <span>{momnet(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '修改时间', | title: '修改时间', | ||||
| dataIndex: 'address', | |||||
| key: 'address', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| render: (text) => <span>{momnet(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -64,8 +148,8 @@ const Pipeline = React.FC = () => { | |||||
| size="small" | size="small" | ||||
| key="edit" | key="edit" | ||||
| icon = {< EditOutlined />} | icon = {< EditOutlined />} | ||||
| onClick={() => { | |||||
| editTable() | |||||
| onClick={(e) => { | |||||
| editTable(e,record) | |||||
| }} | }} | ||||
| > | > | ||||
| 编辑 | 编辑 | ||||
| @@ -73,10 +157,33 @@ const Pipeline = React.FC = () => { | |||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="edit" | |||||
| icon = {< EditOutlined />} | |||||
| onClick={() => { | |||||
| editTable() | |||||
| key="clone" | |||||
| icon = {< CopyOutlined />} | |||||
| onClick={async () => { | |||||
| Modal.confirm({ | |||||
| title: '复制', | |||||
| content: '确定复制该条流水线吗?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| console.log(record); | |||||
| cloneWorkflow(record.id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| message.success('复制成功') | |||||
| getList() | |||||
| } | |||||
| else{ | |||||
| message.error('复制失败') | |||||
| } | |||||
| }); | |||||
| // if (success) { | |||||
| // if (actionRef.current) { | |||||
| // actionRef.current.reload(); | |||||
| // } | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| }} | }} | ||||
| > | > | ||||
| 复制 | 复制 | ||||
| @@ -90,11 +197,21 @@ const Pipeline = React.FC = () => { | |||||
| onClick={async () => { | onClick={async () => { | ||||
| Modal.confirm({ | Modal.confirm({ | ||||
| title: '删除', | title: '删除', | ||||
| content: '确定删除该项吗?', | |||||
| content: '确定删除该条流水线吗?', | |||||
| okText: '确认', | okText: '确认', | ||||
| cancelText: '取消', | cancelText: '取消', | ||||
| onOk: async () => { | |||||
| // const success = await handleRemoveOne(record); | |||||
| onOk: () => { | |||||
| console.log(record); | |||||
| removeWorkflow(record.id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| message.success('删除成功') | |||||
| getList() | |||||
| } | |||||
| else{ | |||||
| message.error(ret.msg) | |||||
| } | |||||
| }); | |||||
| // if (success) { | // if (success) { | ||||
| // if (actionRef.current) { | // if (actionRef.current) { | ||||
| // actionRef.current.reload(); | // actionRef.current.reload(); | ||||
| @@ -110,102 +227,21 @@ const Pipeline = React.FC = () => { | |||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| const data = [ | |||||
| { | |||||
| key: '1', | |||||
| name: 'John Brown', | |||||
| age: 32, | |||||
| address: 'New York No. 1 Lake Park', | |||||
| tags: ['nice', 'developer'], | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| name: 'Jim Green', | |||||
| age: 42, | |||||
| address: 'London No. 1 Lake Park', | |||||
| tags: ['loser'], | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '4', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '5', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '6', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '7', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '8', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '9', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '10', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '11', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| { | |||||
| key: '12', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sydney No. 1 Lake Park', | |||||
| tags: ['cool', 'teacher'], | |||||
| }, | |||||
| ]; | |||||
| return (<div> | return (<div> | ||||
| <div className={Styles.pipelineTopBox}> | <div className={Styles.pipelineTopBox}> | ||||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {< PlusOutlined />}> | <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {< PlusOutlined />}> | ||||
| 新建流水线 | 新建流水线 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <Table columns={columns} dataSource={data} /> | |||||
| <Modal title="新建流水线" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}> | |||||
| <Table columns={columns} dataSource={pipeList} pagination={paginationProps}/> | |||||
| <Modal title={dialogTitle} open={isModalOpen} okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} onCancel={handleCancel}> | |||||
| <Form | <Form | ||||
| name="basic" | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| labelCol={{ | labelCol={{ | ||||
| span: 8, | span: 8, | ||||
| }} | }} | ||||
| @@ -223,18 +259,42 @@ const Pipeline = React.FC = () => { | |||||
| autoComplete="off" | autoComplete="off" | ||||
| > | > | ||||
| <Form.Item | <Form.Item | ||||
| label="Username" | |||||
| name="username" | |||||
| label="流水线名称" | |||||
| name="name" | |||||
| rules={[ | rules={[ | ||||
| { | |||||
| required: true, | |||||
| message: 'Please input your username!', | |||||
| }, | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ]} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="流水线描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input /> | <Input /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="备注" | |||||
| name="remake" | |||||
| rules={[ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ]} | |||||
| > | |||||
| <TextArea /> | |||||
| </Form.Item> | |||||
| </Form> | </Form> | ||||
| </Modal> | </Modal> | ||||
| </div>)}; | </div>)}; | ||||
| export default Pipeline; | |||||
| export default Pipeline; | |||||
| @@ -22,4 +22,4 @@ | |||||
| // border:1px solid; | // border:1px solid; | ||||
| // border-color:rgba(22, 100, 255, 0.11); | // border-color:rgba(22, 100, 255, 0.11); | ||||
| // border-radius:4px; | // border-radius:4px; | ||||
| // } | |||||
| // } | |||||
| @@ -118,7 +118,7 @@ const Login: React.FC = () => { | |||||
| color:'#1d1d20', | color:'#1d1d20', | ||||
| fontSize:'36px', | fontSize:'36px', | ||||
| fontFamily: 'Alibaba', | fontFamily: 'Alibaba', | ||||
| }; | }; | ||||
| }); | }); | ||||
| const centerTitleBoX= useEmotionCss(() => { | const centerTitleBoX= useEmotionCss(() => { | ||||
| @@ -249,12 +249,12 @@ const Login: React.FC = () => { | |||||
| </div> | </div> | ||||
| <div className={centerTitleBoX}> | <div className={centerTitleBoX}> | ||||
| <span style={{whiteSpace:'nowrap'}}>复杂智能软件</span> | <span style={{whiteSpace:'nowrap'}}>复杂智能软件</span> | ||||
| <img src="/assets/images/ai-logo.png" style={{height:'47px',marginTop:'-10px'}} alt="" /> | <img src="/assets/images/ai-logo.png" style={{height:'47px',marginTop:'-10px'}} alt="" /> | ||||
| </div> | </div> | ||||
| <div className={centerMessage}> | <div className={centerMessage}> | ||||
| <span style={{whiteSpace:'nowrap'}}>大语言模型运维 统一管理平台</span> | <span style={{whiteSpace:'nowrap'}}>大语言模型运维 统一管理平台</span> | ||||
| </div> | </div> | ||||
| <img src="/assets/images/left-back-logo.png" style={{width:'90%',position:'absolute',top:'326px',left: '50%', | <img src="/assets/images/left-back-logo.png" style={{width:'90%',position:'absolute',top:'326px',left: '50%', | ||||
| transform:'translateX(-50%)',}} alt="" /> | transform:'translateX(-50%)',}} alt="" /> | ||||
| @@ -480,7 +480,7 @@ const Login: React.FC = () => { | |||||
| </div> | </div> | ||||
| </LoginForm> | </LoginForm> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {/* <Helmet> | {/* <Helmet> | ||||
| <title> | <title> | ||||
| @@ -1,6 +1,6 @@ | |||||
| .loginForm{ | .loginForm{ | ||||
| width: 520px; | width: 520px; | ||||
| } | } | ||||
| :global .ant-pro-form-login-main{ | :global .ant-pro-form-login-main{ | ||||
| margin: unset; | margin: unset; | ||||
| @@ -26,4 +26,4 @@ | |||||
| color:#ffffff; | color:#ffffff; | ||||
| font-size:20px; | font-size:20px; | ||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| } | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| // 查询流水线列表 | |||||
| export function getJupyterUrl(params) { | |||||
| return request(`/api/mmp/jupyter/getURL`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,62 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| // 查询实验列表 | |||||
| export function getExperiment(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', | |||||
| }, | |||||
| }); | |||||
| } | |||||
| // 根据id查询实验 | |||||
| export function getExperimentById(id) { | |||||
| return request(`/api/mmp/experiment/${id}`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 根据id查询实验实例 | |||||
| export function getQueryByExperimentId(id) { | |||||
| return request(`/api/mmp/experimentIns/queryByExperimentId/${id}`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 根据id查询查询日志 | |||||
| export function getQueryByExperimentLog(params) { | |||||
| return request(`/api/mmp/experimentIns/log/`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | |||||
| } | |||||
| // 根据实例查询详情 | |||||
| export function getExperimentIns(id) { | |||||
| 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 putExperiment(data) { | |||||
| return request(`/api/mmp/experiment`, { | |||||
| method: 'PUT', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,66 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| import { ContentType } from '@/enums/httpEnum'; | |||||
| // 查询流水线列表 | |||||
| export function getWorkflow(params) { | |||||
| return request(`/api/mmp/workflow`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | |||||
| } | |||||
| // 新建流水线 | |||||
| export function addWorkflow(params) { | |||||
| return request('/api/mmp/workflow', { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data: params | |||||
| }); | |||||
| } | |||||
| // 编辑流水线 | |||||
| export function editWorkflow(params) { | |||||
| return request('/api/mmp/workflow', { | |||||
| method: 'PUT', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data: params | |||||
| }); | |||||
| } | |||||
| // 删除流水线 | |||||
| export async function removeWorkflow(id) { | |||||
| return request(`/api/mmp/workflow/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 复制流水线 | |||||
| export function cloneWorkflow(id) { | |||||
| return request(`/api/mmp/workflow/duplicate/${id}`, { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| }); | |||||
| } | |||||
| // 查询组件模型列表 | |||||
| export function getComponentAll() { | |||||
| return request(`/api/mmp/component/components/all`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 保存流水线 | |||||
| export function saveWorkflow(params) { | |||||
| return request('/api/mmp/workflow', { | |||||
| method: 'PUT', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data: params | |||||
| }); | |||||
| } | |||||
| // 根据id查询流水线 | |||||
| export function getWorkflowById(id) { | |||||
| return request(`/api/mmp/workflow/${id}`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| export function s8() { | |||||
| return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | |||||
| } | |||||
| export function getNameByCode(list,code){ | |||||
| let name = '' | |||||
| list.forEach(item=>{ | |||||
| if(item.dictValue === code) name = item.dictLabel | |||||
| }) | |||||
| return name | |||||
| } | |||||
| @@ -19,5 +19,5 @@ | |||||
| "@@test/*": ["./src/.umi-test/*"] | "@@test/*": ["./src/.umi-test/*"] | ||||
| } | } | ||||
| }, | }, | ||||
| "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"] | |||||
| "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "src/pages/Pipeline/index.jsx"] | |||||
| } | } | ||||