| @@ -76,7 +76,7 @@ export default defineConfig({ | |||
| * @name layout 插件 | |||
| * @doc https://umijs.org/docs/max/layout-menu | |||
| */ | |||
| title: 'Ant Design Pro', | |||
| title: '复杂智能软件', | |||
| layout: { | |||
| locale: true, | |||
| ...defaultSettings, | |||
| @@ -14,9 +14,9 @@ const Settings: ProLayoutProps & { | |||
| contentWidth: 'Fluid', | |||
| fixedHeader: false, | |||
| fixSiderbar: false, | |||
| splitMenus: true, | |||
| splitMenus: false, | |||
| colorWeak: false, | |||
| title: 'Ant Design Pro', | |||
| title: '复杂智能软件', | |||
| pwa: true, | |||
| logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', | |||
| iconfontUrl: '', | |||
| @@ -15,14 +15,15 @@ export default { | |||
| // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | |||
| '/api/': { | |||
| // 要代理的地址 | |||
| target: 'http://localhost:8082', | |||
| // target: 'http://172.20.32.181:31205', | |||
| target: 'http://172.20.32.150:8082', | |||
| // 配置了这个可以从 http 代理到 https | |||
| // 依赖 origin 的功能可能需要这个,比如 cookie | |||
| changeOrigin: true, | |||
| pathRewrite: { '^/api': '' }, | |||
| }, | |||
| '/profile/avatar/': { | |||
| target: 'http://localhost:8082', | |||
| target: 'http://172.20.32.181:31205', | |||
| 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', | |||
| path: '/system', | |||
| @@ -12,7 +12,7 @@ export default async () => { | |||
| ...config, | |||
| testEnvironmentOptions: { | |||
| ...(config?.testEnvironmentOptions || {}), | |||
| url: 'http://localhost:8081', | |||
| url: 'http://localhost:8000', | |||
| }, | |||
| setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'], | |||
| globals: { | |||
| @@ -56,13 +56,14 @@ | |||
| "@ant-design/icons": "^5.0.0", | |||
| "@ant-design/pro-components": "^2.4.4", | |||
| "@ant-design/use-emotion-css": "1.0.4", | |||
| "@antv/g6": "^4.8.24", | |||
| "@umijs/route-utils": "^4.0.1", | |||
| "antd": "^5.4.4", | |||
| "classnames": "^2.3.2", | |||
| "fabric": "^5.3.0", | |||
| "highlight.js": "^11.7.0", | |||
| "lodash": "^4.17.21", | |||
| "moment": "^2.29.4", | |||
| "moment": "^2.30.1", | |||
| "omit.js": "^2.0.2", | |||
| "pnpm": "^8.9.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'); | |||
| } | |||
| } | |||
| @@ -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 momnet from 'moment' | |||
| import { useNavigate} from 'react-router-dom'; | |||
| const { TextArea } = Input; | |||
| 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 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 = () => { | |||
| form.resetFields() | |||
| setDialogTitle('编辑流水线') | |||
| setIsModalOpen(true); | |||
| }; | |||
| const handleOk = () => { | |||
| console.log(1111); | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| 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 = [ | |||
| { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| 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)}`, | |||
| }, | |||
| { | |||
| title: '流水线名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: (text) => <a>{text}</a>, | |||
| render: (text,record) => <a onClick={(e)=>routeToEdit(e,record)}>{text}</a>, | |||
| }, | |||
| { | |||
| title: '流水线描述', | |||
| dataIndex: 'age', | |||
| key: 'age', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| }, | |||
| { | |||
| 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: '修改时间', | |||
| dataIndex: 'address', | |||
| key: 'address', | |||
| dataIndex: 'update_time', | |||
| key: 'update_time', | |||
| render: (text) => <span>{momnet(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| @@ -64,8 +148,8 @@ const Pipeline = React.FC = () => { | |||
| size="small" | |||
| key="edit" | |||
| icon = {< EditOutlined />} | |||
| onClick={() => { | |||
| editTable() | |||
| onClick={(e) => { | |||
| editTable(e,record) | |||
| }} | |||
| > | |||
| 编辑 | |||
| @@ -73,10 +157,33 @@ const Pipeline = React.FC = () => { | |||
| <Button | |||
| type="link" | |||
| 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 () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该项吗?', | |||
| content: '确定删除该条流水线吗?', | |||
| okText: '确认', | |||
| 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 (actionRef.current) { | |||
| // 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> | |||
| <div className={Styles.pipelineTopBox}> | |||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {< PlusOutlined />}> | |||
| 新建流水线 | |||
| </Button> | |||
| </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 | |||
| name="basic" | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| labelCol={{ | |||
| span: 8, | |||
| }} | |||
| @@ -223,18 +259,42 @@ const Pipeline = React.FC = () => { | |||
| autoComplete="off" | |||
| > | |||
| <Form.Item | |||
| label="Username" | |||
| name="username" | |||
| label="流水线名称" | |||
| name="name" | |||
| 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 /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="备注" | |||
| name="remake" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| > | |||
| <TextArea /> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </div>)}; | |||
| export default Pipeline; | |||
| export default Pipeline; | |||
| @@ -22,4 +22,4 @@ | |||
| // border:1px solid; | |||
| // border-color:rgba(22, 100, 255, 0.11); | |||
| // border-radius:4px; | |||
| // } | |||
| // } | |||
| @@ -118,7 +118,7 @@ const Login: React.FC = () => { | |||
| color:'#1d1d20', | |||
| fontSize:'36px', | |||
| fontFamily: 'Alibaba', | |||
| }; | |||
| }); | |||
| const centerTitleBoX= useEmotionCss(() => { | |||
| @@ -249,12 +249,12 @@ const Login: React.FC = () => { | |||
| </div> | |||
| <div className={centerTitleBoX}> | |||
| <span style={{whiteSpace:'nowrap'}}>复杂智能软件</span> | |||
| <img src="/assets/images/ai-logo.png" style={{height:'47px',marginTop:'-10px'}} alt="" /> | |||
| </div> | |||
| <div className={centerMessage}> | |||
| <span style={{whiteSpace:'nowrap'}}>大语言模型运维 统一管理平台</span> | |||
| </div> | |||
| <img src="/assets/images/left-back-logo.png" style={{width:'90%',position:'absolute',top:'326px',left: '50%', | |||
| transform:'translateX(-50%)',}} alt="" /> | |||
| @@ -480,7 +480,7 @@ const Login: React.FC = () => { | |||
| </div> | |||
| </LoginForm> | |||
| </div> | |||
| </div> | |||
| {/* <Helmet> | |||
| <title> | |||
| @@ -1,6 +1,6 @@ | |||
| .loginForm{ | |||
| width: 520px; | |||
| } | |||
| :global .ant-pro-form-login-main{ | |||
| margin: unset; | |||
| @@ -26,4 +26,4 @@ | |||
| color:#ffffff; | |||
| font-size:20px; | |||
| 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/*"] | |||
| } | |||
| }, | |||
| "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"] | |||
| "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "src/pages/Pipeline/index.jsx"] | |||
| } | |||