| @@ -0,0 +1,16 @@ | |||||
| .experiment-parameter { | |||||
| padding-top: 8px; | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 43px; | |||||
| margin-right: 8px; | |||||
| margin-bottom: 20px; | |||||
| margin-left: 8px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8fbff; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,162 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { Form, Input, type FormProps } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| const { TextArea } = Input; | |||||
| type ExperimentParameterProps = { | |||||
| form: FormProps['form']; | |||||
| nodeData: PipelineNodeModelSerialize; | |||||
| }; | |||||
| function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| // 控制策略 | |||||
| const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| // 输入参数 | |||||
| const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| // 输出参数 | |||||
| const outParametersList = Object.entries(nodeData.out_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| return ( | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| form={form} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| autoComplete="off" | |||||
| className={styles['experiment-parameter']} | |||||
| > | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle> | |||||
| </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['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle> | |||||
| </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"> | |||||
| <TextArea 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> | |||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={item.value.label} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle> | |||||
| </div> | |||||
| {inParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['in_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle> | |||||
| </div> | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </Form> | |||||
| ); | |||||
| } | |||||
| export default ExperimentParameter; | |||||
| @@ -0,0 +1,38 @@ | |||||
| .experiment-result { | |||||
| padding: 8px; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| &__content { | |||||
| padding: 10px 20px 20px 20px; | |||||
| background-color: rgba(234, 234, 234, 0.5); | |||||
| } | |||||
| &__item { | |||||
| margin-bottom: 20px; | |||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| &__name { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 10px 0; | |||||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | |||||
| } | |||||
| &__file { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| margin-bottom: 10px; | |||||
| padding: 0 20px 0 0; | |||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,59 @@ | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { Button } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| type ExperimentResultProps = { | |||||
| results?: ExperimentResultData[] | null; | |||||
| }; | |||||
| type ExperimentResultData = { | |||||
| name: string; | |||||
| path: string; | |||||
| type: string; | |||||
| value: { | |||||
| name: string; | |||||
| size: string; | |||||
| }[]; | |||||
| }; | |||||
| function ExperimentResult({ results }: ExperimentResultProps) { | |||||
| const exportResult = (val: string) => { | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: val }); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['experiment-result']}> | |||||
| <div className={styles['experiment-result__content']}> | |||||
| {results?.map((item) => ( | |||||
| <div key={item.name} className={styles['experiment-result__item']}> | |||||
| <div className={styles['experiment-result__item__name']}> | |||||
| <span>{item.name}</span> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportResult(item.path); | |||||
| }} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | |||||
| </div> | |||||
| <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value?.map((ele) => ( | |||||
| <div className={styles['experiment-result__item__file']} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ExperimentResult; | |||||
| @@ -0,0 +1,34 @@ | |||||
| .log-group { | |||||
| padding-bottom: 10px; | |||||
| &__pod { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 15px; | |||||
| background: rgba(234, 234, 234, 0.5); | |||||
| cursor: pointer; | |||||
| &__name { | |||||
| margin-right: 10px; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| &__detail { | |||||
| padding: 15px; | |||||
| color: white; | |||||
| font-size: 14px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| background: #19253b; | |||||
| } | |||||
| &__more-button { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| color: white; | |||||
| background: #19253b; | |||||
| } | |||||
| } | |||||
| @@ -1,16 +1,19 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-05-16 08:47:46 | |||||
| * @Description: 日志组件 | |||||
| */ | |||||
| import { useStateRef } from '@/hooks'; | import { useStateRef } from '@/hooks'; | ||||
| import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; | |||||
| import { ExperimentStatus } from '@/pages/Experiment/status'; | |||||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | import { getExperimentPodsLog } from '@/services/experiment/index.js'; | ||||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { ExperimentStatus } from '../status'; | |||||
| import styles from './logGroup.less'; | |||||
| import styles from './index.less'; | |||||
| export type LogGroupProps = { | |||||
| log_type: 'normal' | 'resource'; // 日志类型 | |||||
| pod_name?: string; // 分布式名称 | |||||
| log_content?: string; // 日志内容 | |||||
| start_time?: string; // 日志开始时间 | |||||
| export type LogGroupProps = ExperimentLog & { | |||||
| status: ExperimentStatus; // 实验状态 | status: ExperimentStatus; // 实验状态 | ||||
| }; | }; | ||||
| @@ -23,7 +26,7 @@ function LogGroup({ | |||||
| log_type = 'normal', | log_type = 'normal', | ||||
| pod_name = '', | pod_name = '', | ||||
| log_content = '', | log_content = '', | ||||
| start_time = '', | |||||
| start_time, | |||||
| status = ExperimentStatus.Pending, | status = ExperimentStatus.Pending, | ||||
| }: LogGroupProps) { | }: LogGroupProps) { | ||||
| const [collapse, setCollapse] = useState(true); | const [collapse, setCollapse] = useState(true); | ||||
| @@ -57,21 +60,6 @@ function LogGroup({ | |||||
| setCompleted(true); | setCompleted(true); | ||||
| } | } | ||||
| }; | }; | ||||
| // 请求实时日志 | |||||
| // const requestExperimentPodsRealtimeLog = async () => { | |||||
| // const params = { | |||||
| // pod_name, | |||||
| // namespace: namespace, | |||||
| // container_name: log_type === 'resource' ? '' : 'main', | |||||
| // }; | |||||
| // const res = await getExperimentPodsRealtimeLog(params); | |||||
| // const { log_detail } = res.data; | |||||
| // if (log_detail && log_detail.log_content) { | |||||
| // setLogList((list) => list.concat(log_detail)); | |||||
| // } else { | |||||
| // setCompleted(true); | |||||
| // } | |||||
| // }; | |||||
| // 处理折叠 | // 处理折叠 | ||||
| const handleCollapse = async () => { | const handleCollapse = async () => { | ||||
| @@ -101,15 +89,15 @@ function LogGroup({ | |||||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | const logText = log_content + logList.map((v) => v.log_content).join(''); | ||||
| const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; | const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; | ||||
| return ( | return ( | ||||
| <div className={styles.log_group}> | |||||
| <div className={styles['log-group']}> | |||||
| {log_type === 'resource' && ( | {log_type === 'resource' && ( | ||||
| <div className={styles.log_group_pod} onClick={handleCollapse}> | |||||
| <div className={styles.log_group_pod_name}>{pod_name}</div> | |||||
| <div className={styles['log-group__pod']} onClick={handleCollapse}> | |||||
| <div className={styles['log-group__pod__name']}>{pod_name}</div> | |||||
| {collapse ? <DownOutlined /> : <UpOutlined />} | {collapse ? <DownOutlined /> : <UpOutlined />} | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {showLog && <div className={styles.log_group_detail}>{logText}</div>} | |||||
| <div className={styles.log_group_more_button}> | |||||
| {showLog && <div className={styles['log-group__detail']}>{logText}</div>} | |||||
| <div className={styles['log-group__more-button']}> | |||||
| {showMoreBtn && ( | {showMoreBtn && ( | ||||
| <Button | <Button | ||||
| type="text" | type="text" | ||||
| @@ -0,0 +1,3 @@ | |||||
| .log-list { | |||||
| padding: 8px; | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; | |||||
| import { ExperimentStatus } from '@/pages/Experiment/status'; | |||||
| import LogGroup from '../LogGroup'; | |||||
| import styles from './index.less'; | |||||
| type LogListProps = { | |||||
| list: ExperimentLog[]; | |||||
| status: ExperimentStatus; | |||||
| }; | |||||
| function LogList({ list = [], status }: LogListProps) { | |||||
| return ( | |||||
| <div className={styles['log-list']}> | |||||
| {list.map((v) => ( | |||||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default LogList; | |||||
| @@ -1,19 +0,0 @@ | |||||
| import { ExperimentStatus } from '../status'; | |||||
| import LogGroup, { type LogGroupProps } from './logGroup'; | |||||
| type LogListProps = { | |||||
| list: Omit<LogGroupProps, 'status'>[]; | |||||
| status: ExperimentStatus; | |||||
| }; | |||||
| function LogList({ list = [], status }: LogListProps) { | |||||
| return ( | |||||
| <div> | |||||
| {list.map((v) => ( | |||||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default LogList; | |||||
| @@ -1,11 +1,10 @@ | |||||
| import { useVisible } from '@/hooks'; | |||||
| import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { getExperimentIns } from '@/services/experiment/index.js'; | import { getExperimentIns } from '@/services/experiment/index.js'; | ||||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | import { getWorkflowById } from '@/services/pipeline/index.js'; | ||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | import { s8 } from '../../../utils'; | ||||
| import ParamsModal from '../components/ViewParamsModal'; | import ParamsModal from '../components/ViewParamsModal'; | ||||
| @@ -13,38 +12,15 @@ import { experimentStatusInfo } from '../status'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import Props from './props'; | import Props from './props'; | ||||
| let graph = null; | |||||
| function ExperimentText() { | function ExperimentText() { | ||||
| const [message, setMessage] = useState({}); | |||||
| const messageRef = useRef(message); | |||||
| const [message, setMessage, messageRef] = useStateRef({}); | |||||
| const propsRef = useRef(); | const propsRef = useRef(); | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| let graph = null; | |||||
| const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | ||||
| const timers = (time) => { | |||||
| let timer = new Date(time); | |||||
| let hours = timer.getHours(); //转换成时 | |||||
| let minutes = timer.getMinutes(); //转换成分 | |||||
| let secend = timer.getSeconds(); //转换成秒 | |||||
| let str = `${minutes}分${secend}秒`; | |||||
| return str; | |||||
| }; | |||||
| const pipelineContainer = useEmotionCss(() => { | |||||
| return { | |||||
| display: 'flex', | |||||
| backgroundColor: '#fff', | |||||
| height: '98vh', | |||||
| }; | |||||
| }); | |||||
| const graphStyle = useEmotionCss(() => { | |||||
| return { | |||||
| width: '100%', | |||||
| backgroundColor: '#f9fafb', | |||||
| flex: 1, | |||||
| }; | |||||
| }); | |||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val, 'eee'); | console.log(val, 'eee'); | ||||
| @@ -60,12 +36,8 @@ function ExperimentText() { | |||||
| id: val.component_name + '-' + s8(), | id: val.component_name + '-' + s8(), | ||||
| isCluster: false, | isCluster: false, | ||||
| }; | }; | ||||
| console.log(graph, model); | |||||
| graph.addItem('node', model, true); | graph.addItem('node', model, true); | ||||
| console.log(graph); | |||||
| }; | }; | ||||
| const formChange = (val) => {}; | |||||
| const handlerClick = (e) => { | const handlerClick = (e) => { | ||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | if (e.target.get('name') !== 'anchor-point' && e.item) { | ||||
| propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | ||||
| @@ -73,7 +45,6 @@ function ExperimentText() { | |||||
| }; | }; | ||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| if (graph) { | if (graph) { | ||||
| console.log(graph); | |||||
| graph.data(data); | graph.data(data); | ||||
| graph.render(); | graph.render(); | ||||
| } else { | } else { | ||||
| @@ -83,77 +54,39 @@ function ExperimentText() { | |||||
| } | } | ||||
| }; | }; | ||||
| const getFirstWorkflow = (val) => { | const getFirstWorkflow = (val) => { | ||||
| getWorkflowById(val).then((ret) => { | |||||
| if (graph && ret.data && ret.data.dag) { | |||||
| console.log(JSON.parse(ret.data.dag)); | |||||
| getExperimentIns(locationParams.id).then((res) => { | |||||
| if (res.code == 200) { | |||||
| console.log(ret.data, 'data'); | |||||
| setMessage(res.data); | |||||
| const experimentStatusObjs = JSON.parse(res.data.nodes_status); | |||||
| const newNodeList = JSON.parse(ret.data.dag).nodes.map((item) => { | |||||
| console.log(experimentStatusObjs); | |||||
| getWorkflowById(val).then((pipelineRes) => { | |||||
| if (graph && pipelineRes.data && pipelineRes.data.dag) { | |||||
| getExperimentIns(locationParams.id).then((experimentRes) => { | |||||
| if (experimentRes.code === 200) { | |||||
| setMessage(experimentRes.data); | |||||
| const experimentStatusObjs = JSON.parse(experimentRes.data.nodes_status); | |||||
| const newNodeList = JSON.parse(pipelineRes.data.dag).nodes.map((item) => { | |||||
| return { | return { | ||||
| ...item, | ...item, | ||||
| experimentEndTime: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].finishedAt, | |||||
| experimentStartTime: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].startedAt, | |||||
| experimentStatus: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].phase, | |||||
| component_id: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].id, | |||||
| img: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].phase | |||||
| ? item.img.slice(0, item.img.length - 4) + | |||||
| '-' + | |||||
| experimentStatusObjs[item.id].phase + | |||||
| '.png' | |||||
| : item.img, | |||||
| experimentEndTime: experimentStatusObjs?.[item.id]?.finishedAt, | |||||
| experimentStartTime: experimentStatusObjs?.[item.id]?.startedAt, | |||||
| experimentStatus: experimentStatusObjs?.[item.id]?.phase, | |||||
| component_id: experimentStatusObjs?.[item.id]?.id, | |||||
| img: 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 }; | |||||
| const newData = { ...JSON.parse(pipelineRes.data.dag), nodes: newNodeList }; | |||||
| getGraphData(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(() => { | useEffect(() => { | ||||
| initGraph(); | initGraph(); | ||||
| getFirstWorkflow(locationParams.workflowId); | getFirstWorkflow(locationParams.workflowId); | ||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| // Update the refs whenever the state changes | |||||
| messageRef.current = message; | |||||
| }, [message]); | |||||
| const initGraph = () => { | const initGraph = () => { | ||||
| const fittingString = (str, maxWidth, fontSize) => { | const fittingString = (str, maxWidth, fontSize) => { | ||||
| @@ -374,6 +307,8 @@ function ExperimentText() { | |||||
| }, | }, | ||||
| // linkCenter: true, | // linkCenter: true, | ||||
| fitView: true, | fitView: true, | ||||
| minZoom: 0.5, | |||||
| maxZoom: 3, | |||||
| fitViewPadding: [320, 320, 220, 320], | fitViewPadding: [320, 320, 220, 320], | ||||
| }); | }); | ||||
| graph.on('node:click', handlerClick); | graph.on('node:click', handlerClick); | ||||
| @@ -385,38 +320,39 @@ function ExperimentText() { | |||||
| }; | }; | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={pipelineContainer}> | |||||
| <div className={styles.centerContainer}> | |||||
| <div className={styles.buttonList}> | |||||
| <div className={styles.allMessageItem}>启动时间:{formatDate(message.create_time)}</div> | |||||
| <div className={styles.allMessageItem}> | |||||
| 执行时长: | |||||
| {message.finish_time | |||||
| ? elapsedTime(new Date(message.create_time), new Date(message.finish_time)) | |||||
| : elapsedTime(new Date(message.create_time), new Date())} | |||||
| </div> | |||||
| <div className={styles.allMessageItem}> | |||||
| 状态: | |||||
| <div | |||||
| style={{ | |||||
| width: '8px', | |||||
| height: '8px', | |||||
| borderRadius: '50%', | |||||
| marginRight: '6px', | |||||
| backgroundColor: experimentStatusInfo[message.status]?.color, | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: experimentStatusInfo[message.status]?.color }}> | |||||
| {experimentStatusInfo[message.status]?.label} | |||||
| </span> | |||||
| </div> | |||||
| <Button className={styles.param_button} onClick={openParamsModal}> | |||||
| 执行参数 | |||||
| </Button> | |||||
| <div className={styles['pipeline-container']}> | |||||
| <div className={styles['pipeline-container__top']}> | |||||
| <div className={styles['pipeline-container__top__info']}> | |||||
| 启动时间:{formatDate(message.create_time)} | |||||
| </div> | |||||
| <div className={styles['pipeline-container__top__info']}> | |||||
| 执行时长: | |||||
| {elapsedTime(message.create_time, message.finish_time)} | |||||
| </div> | |||||
| <div className={styles['pipeline-container__top__info']}> | |||||
| 状态: | |||||
| <div | |||||
| style={{ | |||||
| width: '8px', | |||||
| height: '8px', | |||||
| borderRadius: '50%', | |||||
| marginRight: '6px', | |||||
| backgroundColor: experimentStatusInfo[message.status]?.color, | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: experimentStatusInfo[message.status]?.color }}> | |||||
| {experimentStatusInfo[message.status]?.label} | |||||
| </span> | |||||
| </div> | </div> | ||||
| <div className={graphStyle} ref={graphRef} id={styles.graphStyle}></div> | |||||
| <Button | |||||
| className={styles['pipeline-container__top__param-button']} | |||||
| onClick={openParamsModal} | |||||
| > | |||||
| 执行参数 | |||||
| </Button> | |||||
| </div> | </div> | ||||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||||
| <div className={styles['pipeline-container__graph']} ref={graphRef}></div> | |||||
| <Props ref={propsRef}></Props> | |||||
| <ParamsModal | <ParamsModal | ||||
| open={paramsModalOpen} | open={paramsModalOpen} | ||||
| onCancel={closeParamsModal} | onCancel={closeParamsModal} | ||||
| @@ -1,90 +1,31 @@ | |||||
| #graph { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| .pipeline-container { | |||||
| height: 100%; | height: 100%; | ||||
| } | |||||
| .editPipelinePropsContent { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 43px; | |||||
| margin-bottom: 20px; | |||||
| padding: 0 24px; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| font-family: 'Alibaba'; | |||||
| background: #f8fbff; | |||||
| } | |||||
| .centerContainer { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| } | |||||
| .buttonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 56px; | |||||
| padding: 0 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| } | |||||
| .drawBox{ | |||||
| :global{ | |||||
| .ant-drawer .ant-drawer-body{ | |||||
| overflow: hidden; | |||||
| } | |||||
| } | |||||
| } | |||||
| .experimentDrawer{ | |||||
| :global{ | |||||
| .ant-tabs >.ant-tabs-nav .ant-tabs-nav-list{ | |||||
| margin-left: 24px; | |||||
| } | |||||
| .ant-drawer .ant-drawer-body{ | |||||
| overflow-y: hidden; | |||||
| background-color: #fff; | |||||
| &__top { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 56px; | |||||
| padding: 0 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__info { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-right: 30px; | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| font-size: 15px; | |||||
| } | } | ||||
| .ant-tabs { | |||||
| height: calc(100% - 160px); | |||||
| overflow-y: auto; | |||||
| &__param-button { | |||||
| margin-right: 0; | |||||
| margin-left: auto; | |||||
| } | } | ||||
| } | } | ||||
| } | |||||
| .detailBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 15px; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| padding-left: 24px; | |||||
| } | |||||
| .allMessageItem { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-right: 30px; | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| font-size: 15px; | |||||
| } | |||||
| .param_button { | |||||
| margin-right: 0; | |||||
| margin-left: auto; | |||||
| } | |||||
| .resultTop { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 10px 0; | |||||
| border-bottom: 1px solid #eee; | |||||
| } | |||||
| .resultContent { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| margin-bottom: 10px; | |||||
| padding: 0 20px 0 0; | |||||
| &__graph { | |||||
| width: 100%; | |||||
| height: calc(100% - 56px); | |||||
| background-color: @background-color; | |||||
| } | |||||
| } | } | ||||
| @@ -1,34 +0,0 @@ | |||||
| .log_group { | |||||
| padding-bottom: 10px; | |||||
| } | |||||
| .log_group_pod { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 15px; | |||||
| background: rgba(234, 234, 234, 0.5); | |||||
| cursor: pointer; | |||||
| &_name { | |||||
| margin-right: 10px; | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| .log_group_detail { | |||||
| padding: 15px; | |||||
| color: white; | |||||
| font-size: 14px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| background: #19253b; | |||||
| } | |||||
| .log_group_more_button { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| color: white; | |||||
| background: #19253b; | |||||
| } | |||||
| @@ -1,439 +0,0 @@ | |||||
| import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||||
| import { Drawer, Form, Input, Tabs, message } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||||
| import LogList from './LogList'; | |||||
| import Styles from './index.less'; | |||||
| const { TextArea } = Input; | |||||
| const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const [form] = Form.useForm(); | |||||
| const [stagingItem, setStagingItem] = useState({}); | |||||
| const [resultObj, setResultObj] = useState([]); | |||||
| const [logList, setLogList] = useState([]); | |||||
| const statusObj = { | |||||
| Running: '运行中', | |||||
| Succeeded: '成功', | |||||
| Pending: '等待中', | |||||
| Failed: '失败', | |||||
| Error: '错误', | |||||
| Terminated: '终止', | |||||
| Skipped: '未执行', | |||||
| Omitted: '未执行', | |||||
| }; | |||||
| const statusColorObj = { | |||||
| Running: '#165bff', | |||||
| Succeeded: '#63a728', | |||||
| Pending: '#f981eb', | |||||
| Failed: '#c73131', | |||||
| Error: '#c73131', | |||||
| Terminated: '#8a8a8a', | |||||
| Skipped: '#8a8a8a', | |||||
| Omitted: '#8a8a8ae', | |||||
| }; | |||||
| const exportResult = (e, val) => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: val }); | |||||
| }; | |||||
| const timers = (time) => { | |||||
| let timer = new Date(time); | |||||
| let hours = timer.getHours(); //转换成时 | |||||
| let minutes = timer.getMinutes(); //转换成分 | |||||
| let secend = timer.getSeconds(); //转换成秒 | |||||
| let str = `${minutes}分${secend}秒`; | |||||
| return str; | |||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: '日志详情', | |||||
| children: <LogList list={logList} status={stagingItem.experimentStatus}></LogList>, | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: '配置参数', | |||||
| icon: <DatabaseOutlined />, | |||||
| children: ( | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 16, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| > | |||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', 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: '15px', 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"> | |||||
| <TextArea 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 | |||||
| key={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 | |||||
| key={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: '15px', 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 | |||||
| key={item} | |||||
| label={stagingItem.out_parameters[item].label + '(' + item + ')'} | |||||
| disabled | |||||
| rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]} | |||||
| name={item} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| </Form> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: '输出结果', | |||||
| children: ( | |||||
| <div | |||||
| style={{ | |||||
| minHeight: '740px', | |||||
| background: '#f4f4f4', | |||||
| color: '#000', | |||||
| fontSize: '14px', | |||||
| padding: '0 10px 20px 20px', | |||||
| }} | |||||
| > | |||||
| {resultObj && resultObj.length > 0 | |||||
| ? resultObj.map((item) => ( | |||||
| <div key={item.name}> | |||||
| <div className={Styles.resultTop}> | |||||
| <span>{item.name}</span> | |||||
| <div style={{ display: 'flex' }}> | |||||
| <a | |||||
| onClick={(e) => { | |||||
| exportResult(e, item.path); | |||||
| }} | |||||
| style={{ marginRight: '10px' }} | |||||
| > | |||||
| 下载 | |||||
| </a> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | |||||
| </div> | |||||
| </div> | |||||
| <div style={{ margin: '15px 0' }} className={Styles.resultContent}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value && item.value.length > 0 | |||||
| ? item.value.map((ele) => ( | |||||
| <div className={Styles.resultContent} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | |||||
| )) | |||||
| : null} | |||||
| </div> | |||||
| )) | |||||
| : null} | |||||
| </div> | |||||
| ), | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| ]; | |||||
| const [open, setOpen] = useState(false); | |||||
| const afterOpenChange = () => { | |||||
| if (!open) { | |||||
| console.log(111, open); | |||||
| console.log(stagingItem, form.getFieldsValue()); | |||||
| for (let i in form.getFieldsValue()) { | |||||
| for (let j in stagingItem.in_parameters) { | |||||
| if (i == j) { | |||||
| console.log(j, i); | |||||
| stagingItem.in_parameters[j].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let p in stagingItem.out_parameters) { | |||||
| if (i == p) { | |||||
| stagingItem.out_parameters[p].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let 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, message) { | |||||
| setLogList([]); | |||||
| if (e.item && e.item.getModel().component_id) { | |||||
| const model = e.item.getModel() || {}; | |||||
| const start_time = moment(model.experimentStartTime).valueOf() * 1.0e6; | |||||
| const params = { | |||||
| task_id: model.id, | |||||
| component_id: model.component_id, | |||||
| name: message.argo_ins_name, | |||||
| namespace: message.argo_ins_ns, | |||||
| start_time: start_time, | |||||
| }; | |||||
| getQueryByExperimentLog(params).then((ret) => { | |||||
| const { log_type, pods, log_detail } = ret.data; | |||||
| if (log_type === 'normal') { | |||||
| const list = [ | |||||
| { | |||||
| ...log_detail, | |||||
| log_type, | |||||
| }, | |||||
| ]; | |||||
| setLogList(list); | |||||
| } else if (log_type === 'resource') { | |||||
| const list = pods.map((v) => ({ | |||||
| log_type, | |||||
| pod_name: v, | |||||
| log_content: '', | |||||
| start_time, | |||||
| })); | |||||
| setLogList(list); | |||||
| } | |||||
| getNodeResult({ id, node_id: e.item.getModel().id }).then((res) => { | |||||
| setResultObj(res.data); | |||||
| form.resetFields(); | |||||
| form.setFieldsValue({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| setStagingItem({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| 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), | |||||
| }); | |||||
| setOpen(true); | |||||
| } | |||||
| // console.log(e.item.getModel().in_parameters); | |||||
| }, | |||||
| })); | |||||
| return ( | |||||
| <div className={Styles.drawBox}> | |||||
| <Drawer | |||||
| title="任务执行详情" | |||||
| placement="right" | |||||
| rootStyle={{ marginTop: '68px' }} | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| afterOpenChange={afterOpenChange} | |||||
| open={open} | |||||
| width={420} | |||||
| className={Styles.experimentDrawer} | |||||
| destroyOnClose={true} | |||||
| > | |||||
| <div className={Styles.detailBox} style={{ marginTop: '15px' }}> | |||||
| 任务名称:{stagingItem.label} | |||||
| </div> | |||||
| <div className={Styles.detailBox}> | |||||
| 执行状态: | |||||
| <div | |||||
| style={{ | |||||
| width: '8px', | |||||
| height: '8px', | |||||
| borderRadius: '50%', | |||||
| marginRight: '6px', | |||||
| backgroundColor: statusColorObj[stagingItem.experimentStatus], | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: statusColorObj[stagingItem.experimentStatus] }}> | |||||
| {statusObj[stagingItem.experimentStatus]} | |||||
| </span> | |||||
| </div> | |||||
| <div className={Styles.detailBox}> | |||||
| 启动时间:{formatDate(stagingItem.experimentStartTime)} | |||||
| </div> | |||||
| <div className={Styles.detailBox}> | |||||
| 耗时: | |||||
| {stagingItem.experimentEndTime | |||||
| ? elapsedTime( | |||||
| new Date(stagingItem.experimentStartTime), | |||||
| new Date(stagingItem.experimentEndTime), | |||||
| ) | |||||
| : elapsedTime(new Date(stagingItem.experimentStartTime), new Date())} | |||||
| </div> | |||||
| <Tabs defaultActiveKey="1" items={items} /> | |||||
| </Drawer> | |||||
| </div> | |||||
| ); | |||||
| }); | |||||
| export default Props; | |||||
| @@ -0,0 +1,37 @@ | |||||
| .experiment-drawer { | |||||
| :global { | |||||
| .ant-drawer-body { | |||||
| overflow-y: hidden; | |||||
| } | |||||
| } | |||||
| &__tabs { | |||||
| height: calc(100% - 170px); | |||||
| :global { | |||||
| .ant-tabs-nav { | |||||
| padding-left: 24px; | |||||
| background-color: #f8fbff; | |||||
| border: 1px solid #e0eaff; | |||||
| } | |||||
| .ant-tabs-content-holder { | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__info { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 15px; | |||||
| padding-left: 24px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &__status-dot { | |||||
| width: 8px; | |||||
| height: 8px; | |||||
| margin-right: 6px; | |||||
| border-radius: 50%; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,171 @@ | |||||
| import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||||
| import { Drawer, Form, Tabs } from 'antd'; | |||||
| import dayjs from 'dayjs'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||||
| import ExperimentParameter from '../components/ExperimentParameter'; | |||||
| import ExperimentResult from '../components/ExperimentResult'; | |||||
| import LogList from '../components/LogList'; | |||||
| import { experimentStatusInfo } from '../status'; | |||||
| import styles from './props.less'; | |||||
| export type ExperimentLog = { | |||||
| log_type: 'normal' | 'resource'; // 日志类型 | |||||
| pod_name?: string; // 分布式名称 | |||||
| log_content?: string; // 日志内容 | |||||
| start_time?: string; // 日志开始时间 | |||||
| }; | |||||
| const Props = forwardRef((_, ref) => { | |||||
| const [form] = Form.useForm(); | |||||
| const [experimentNodeData, setExperimentNodeData] = useState<PipelineNodeModelSerialize>( | |||||
| {} as PipelineNodeModelSerialize, | |||||
| ); | |||||
| const [experimentResults, setExperimentResults] = useState([]); | |||||
| const [experimentLogList, setExperimentLogList] = useState<ExperimentLog[]>([]); | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: '日志详情', | |||||
| children: ( | |||||
| <LogList list={experimentLogList} status={experimentNodeData.experimentStatus}></LogList> | |||||
| ), | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: '配置参数', | |||||
| icon: <DatabaseOutlined />, | |||||
| children: <ExperimentParameter form={form} nodeData={experimentNodeData} />, | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: '输出结果', | |||||
| children: <ExperimentResult results={experimentResults}></ExperimentResult>, | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| ]; | |||||
| const [open, setOpen] = useState(false); | |||||
| const onClose = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| // 获取实验日志 | |||||
| const getExperimentLog = async (params: any, start_time: number) => { | |||||
| const [res] = await to(getQueryByExperimentLog(params)); | |||||
| if (res && res.data) { | |||||
| const { log_type, pods, log_detail } = res.data; | |||||
| if (log_type === 'normal') { | |||||
| const list = [ | |||||
| { | |||||
| ...log_detail, | |||||
| log_type, | |||||
| }, | |||||
| ]; | |||||
| setExperimentLogList(list); | |||||
| } else if (log_type === 'resource') { | |||||
| const list = pods.map((v: string) => ({ | |||||
| log_type, | |||||
| pod_name: v, | |||||
| log_content: '', | |||||
| start_time, | |||||
| })); | |||||
| setExperimentLogList(list); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 获取实验结果 | |||||
| const getExperimentResult = async (params: any) => { | |||||
| const [res] = await to(getNodeResult(params)); | |||||
| if (res && res.data) { | |||||
| setExperimentResults(res.data); | |||||
| } | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | |||||
| showDrawer(e: any, id: string, message: any) { | |||||
| setOpen(true); | |||||
| // 获取实验参数 | |||||
| const model = e.item.getModel(); | |||||
| try { | |||||
| const nodeData = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| setExperimentNodeData(nodeData); | |||||
| form.setFieldsValue(nodeData); | |||||
| } catch (error) { | |||||
| console.log(error); | |||||
| } | |||||
| // 获取实验日志和实验结果 | |||||
| setExperimentLogList([]); | |||||
| setExperimentResults([]); | |||||
| if (e.item && e.item.getModel()) { | |||||
| const model = e.item.getModel(); | |||||
| const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; | |||||
| const params = { | |||||
| task_id: model.id, | |||||
| component_id: model.component_id, | |||||
| name: message.argo_ins_name, | |||||
| namespace: message.argo_ins_ns, | |||||
| start_time: start_time, | |||||
| }; | |||||
| getExperimentLog(params, start_time); | |||||
| getExperimentResult({ id, node_id: model.id }); | |||||
| } | |||||
| }, | |||||
| })); | |||||
| return ( | |||||
| <Drawer | |||||
| title="任务执行详情" | |||||
| placement="right" | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| open={open} | |||||
| width={520} | |||||
| className={styles['experiment-drawer']} | |||||
| destroyOnClose={true} | |||||
| > | |||||
| <div style={{ paddingTop: '15px' }}> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 任务名称:{experimentNodeData.label} | |||||
| </div> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 执行状态: | |||||
| <div | |||||
| className={styles['experiment-drawer__status-dot']} | |||||
| style={{ | |||||
| backgroundColor: experimentStatusInfo[experimentNodeData.experimentStatus]?.color, | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: experimentStatusInfo[experimentNodeData.experimentStatus]?.color }}> | |||||
| {experimentStatusInfo[experimentNodeData.experimentStatus]?.label} | |||||
| </span> | |||||
| </div> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 启动时间:{formatDate(experimentNodeData.experimentStartTime)} | |||||
| </div> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 耗时: | |||||
| {elapsedTime( | |||||
| experimentNodeData.experimentStartTime, | |||||
| experimentNodeData.experimentEndTime, | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| <Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} /> | |||||
| </Drawer> | |||||
| ); | |||||
| }); | |||||
| export default Props; | |||||
| @@ -437,9 +437,7 @@ function Experiment() { | |||||
| </div> | </div> | ||||
| <div className={Styles.description}> | <div className={Styles.description}> | ||||
| <div style={{ width: '50%' }}> | <div style={{ width: '50%' }}> | ||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| {elapsedTime(item.create_time, item.finish_time)} | |||||
| </div> | </div> | ||||
| <div style={{ width: '50%' }}>{formatDate(item.create_time)}</div> | <div style={{ width: '50%' }}>{formatDate(item.create_time)}</div> | ||||
| </div> | </div> | ||||
| @@ -606,6 +606,8 @@ const EditPipeline = () => { | |||||
| }, | }, | ||||
| // linkCenter: true, | // linkCenter: true, | ||||
| fitView: true, | fitView: true, | ||||
| minZoom: 0.5, | |||||
| maxZoom: 3, | |||||
| fitViewPadding: [320, 320, 220, 320], | fitViewPadding: [320, 320, 220, 320], | ||||
| }); | }); | ||||
| // graph.on('dblclick', (e) => { | // graph.on('dblclick', (e) => { | ||||
| @@ -19,7 +19,7 @@ const ModelMenus = ({ onParDragEnd }) => { | |||||
| x: e.clientX, | x: e.clientX, | ||||
| y: e.clientY, | y: e.clientY, | ||||
| label: data.component_label, | label: data.component_label, | ||||
| img: `/assets/images/pipeline/${data.icon_path}.png`, | |||||
| img: `/assets/images/${data.icon_path}.png`, | |||||
| }); | }); | ||||
| }; | }; | ||||
| const { Panel } = Collapse; | const { Panel } = Collapse; | ||||
| @@ -45,11 +45,13 @@ const ModelMenus = ({ onParDragEnd }) => { | |||||
| }} | }} | ||||
| className={Styles.collapseItem} | className={Styles.collapseItem} | ||||
| > | > | ||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/pipeline/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| {ele.icon_path && ( | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| )} | |||||
| {ele.component_label} | {ele.component_label} | ||||
| </div> | </div> | ||||
| )) | )) | ||||
| @@ -1,4 +1,5 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { getComputingResourceReq } from '@/services/pipeline'; | import { getComputingResourceReq } from '@/services/pipeline'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| @@ -7,7 +8,7 @@ import { pick } from 'lodash'; | |||||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../components/PropsLabel'; | import PropsLabel from '../components/PropsLabel'; | ||||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | ||||
| import Styles from './editPipeline.less'; | |||||
| import styles from './editPipeline.less'; | |||||
| import { createMenuItems } from './utils'; | import { createMenuItems } from './utils'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| @@ -18,7 +19,8 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | ||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | ||||
| const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 | const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 | ||||
| const [items, setItems] = useState([]); | |||||
| const [menuItems, setMenuItems] = useState([]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getComputingResource(); | getComputingResource(); | ||||
| }, []); | }, []); | ||||
| @@ -38,7 +40,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| // console.log('zzzz', stagingItem, form.getFieldsValue()); | |||||
| console.log('zzzz', form.getFieldsValue()); | |||||
| const control_strategy = form.getFieldValue('control_strategy'); | const control_strategy = form.getFieldValue('control_strategy'); | ||||
| const in_parameters = form.getFieldValue('in_parameters'); | const in_parameters = form.getFieldValue('in_parameters'); | ||||
| const out_parameters = form.getFieldValue('out_parameters'); | const out_parameters = form.getFieldValue('out_parameters'); | ||||
| @@ -68,29 +70,32 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| if (e.item && e.item.getModel()) { | if (e.item && e.item.getModel()) { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| const model = e.item.getModel(); | const model = e.item.getModel(); | ||||
| const nodeData = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| setStagingItem({ | |||||
| ...nodeData, | |||||
| }); | |||||
| form.setFieldsValue({ | |||||
| ...nodeData, | |||||
| }); | |||||
| try { | |||||
| const nodeData = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| setStagingItem({ | |||||
| ...nodeData, | |||||
| }); | |||||
| form.setFieldsValue({ | |||||
| ...nodeData, | |||||
| }); | |||||
| } catch (error) { | |||||
| console.log(error); | |||||
| } | |||||
| setSelectedModel(undefined); | setSelectedModel(undefined); | ||||
| setSelectedDataset(undefined); | setSelectedDataset(undefined); | ||||
| setOpen(true); | setOpen(true); | ||||
| setItems(createMenuItems(params, parentNodes)); | |||||
| // 参数下拉菜单 | |||||
| setMenuItems(createMenuItems(params, parentNodes)); | |||||
| } | } | ||||
| }, | }, | ||||
| propClose: async () => { | |||||
| setOpen(false); | |||||
| const [openRes, propsError] = await to(setOpen(false)); | |||||
| console.log(setOpen(false)); | |||||
| propClose: () => { | |||||
| close(); | |||||
| }, | }, | ||||
| })); | })); | ||||
| @@ -161,6 +166,11 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | ||||
| }; | }; | ||||
| // 参数回填 | |||||
| const handleParameterClick = (name, value) => { | |||||
| form.setFieldValue(name, value); | |||||
| }; | |||||
| // 控制策略 | // 控制策略 | ||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | ||||
| ([key, value]) => ({ key, value }), | ([key, value]) => ({ key, value }), | ||||
| @@ -189,7 +199,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| afterOpenChange={afterOpenChange} | afterOpenChange={afterOpenChange} | ||||
| open={open} | open={open} | ||||
| width={520} | width={520} | ||||
| className={Styles.editPipelineProps} | |||||
| className={styles.editPipelineProps} | |||||
| > | > | ||||
| <Form | <Form | ||||
| name="form" | name="form" | ||||
| @@ -206,13 +216,8 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }} | }} | ||||
| autoComplete="off" | autoComplete="off" | ||||
| > | > | ||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '13px', marginRight: '10px' }} | |||||
| src={'/assets/images/static-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 基本信息 | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| <Form.Item | <Form.Item | ||||
| label="任务名称" | label="任务名称" | ||||
| @@ -238,16 +243,11 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| > | > | ||||
| <Input disabled /> | <Input disabled /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 任务信息 | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| <Form.Item label="镜像" required> | <Form.Item label="镜像" required> | ||||
| <div className={Styles['ref-row']}> | |||||
| <div className={styles['ref-row']}> | |||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | ||||
| <Input placeholder="请输入或选择镜像" allowClear /> | <Input placeholder="请输入或选择镜像" allowClear /> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -256,7 +256,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon({ item_type: 'image' })} | icon={getSelectBtnIcon({ item_type: 'image' })} | ||||
| onClick={() => selectResource('image', { item_type: 'image' })} | onClick={() => selectResource('image', { item_type: 'image' })} | ||||
| className={Styles['select-button']} | |||||
| className={styles['select-button']} | |||||
| > | > | ||||
| 选择镜像 | 选择镜像 | ||||
| </Button> | </Button> | ||||
| @@ -267,10 +267,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| name="working_directory" | name="working_directory" | ||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={items} | |||||
| menuItems={menuItems} | |||||
| title="工作目录" | title="工作目录" | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| form.setFieldValue('working_directory', value); | |||||
| handleParameterClick('working_directory', value); | |||||
| }} | }} | ||||
| /> | /> | ||||
| } | } | ||||
| @@ -281,10 +281,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| name="command" | name="command" | ||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={items} | |||||
| title="工作目录" | |||||
| menuItems={menuItems} | |||||
| title="启动命令" | |||||
| onClick={(value) => { | onClick={(value) => { | ||||
| form.setFieldValue('command', value); | |||||
| handleParameterClick('command', value); | |||||
| }} | }} | ||||
| /> | /> | ||||
| } | } | ||||
| @@ -316,10 +316,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| name="mount_path" | name="mount_path" | ||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={items} | |||||
| menuItems={menuItems} | |||||
| title="挂载路径" | title="挂载路径" | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| form.setFieldValue('mount_path', value); | |||||
| handleParameterClick('mount_path', value); | |||||
| }} | }} | ||||
| /> | /> | ||||
| } | } | ||||
| @@ -330,10 +330,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| name="env_variables" | name="env_variables" | ||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={items} | |||||
| menuItems={menuItems} | |||||
| title="环境变量" | title="环境变量" | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| form.setFieldValue('env_variables', value); | |||||
| handleParameterClick('env_variables', value); | |||||
| }} | }} | ||||
| /> | /> | ||||
| } | } | ||||
| @@ -346,10 +346,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| name={['control_strategy', item.key]} | name={['control_strategy', item.key]} | ||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={items} | |||||
| menuItems={menuItems} | |||||
| title={item.value.label} | title={item.value.label} | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| form.setFieldValue(['control_strategy', item.key], { | |||||
| handleParameterClick(['control_strategy', item.key], { | |||||
| ...item.value, | ...item.value, | ||||
| value, | value, | ||||
| }); | }); | ||||
| @@ -369,23 +369,18 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Input placeholder={item.value.label} allowClear /> | <Input placeholder={item.value.label} allowClear /> | ||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 输入参数 | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| {inParametersList.map((item) => ( | {inParametersList.map((item) => ( | ||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={items} | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | title={item.value.label + '(' + item.key + ')'} | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| form.setFieldValue(['in_parameters', item.key], { | |||||
| handleParameterClick(['in_parameters', item.key], { | |||||
| ...item.value, | ...item.value, | ||||
| value, | value, | ||||
| }); | }); | ||||
| @@ -394,7 +389,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } | } | ||||
| required={item.value.require ? true : false} | required={item.value.require ? true : false} | ||||
| > | > | ||||
| <div className={Styles['ref-row']}> | |||||
| <div className={styles['ref-row']}> | |||||
| <Form.Item | <Form.Item | ||||
| name={['in_parameters', item.key]} | name={['in_parameters', item.key]} | ||||
| noStyle | noStyle | ||||
| @@ -417,7 +412,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon(item.value)} | icon={getSelectBtnIcon(item.value)} | ||||
| onClick={() => selectResource(['in_parameters', item.key], item.value)} | onClick={() => selectResource(['in_parameters', item.key], item.value)} | ||||
| className={Styles['select-button']} | |||||
| className={styles['select-button']} | |||||
| > | > | ||||
| {item.value.label} | {item.value.label} | ||||
| </Button> | </Button> | ||||
| @@ -426,13 +421,8 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| </div> | </div> | ||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 输出参数 | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| {outParametersList.map((item) => ( | {outParametersList.map((item) => ( | ||||
| <Form.Item | <Form.Item | ||||
| @@ -440,10 +430,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| name={['out_parameters', item.key]} | name={['out_parameters', item.key]} | ||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={items} | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | title={item.value.label + '(' + item.key + ')'} | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| form.setFieldValue(['out_parameters', item.key], { | |||||
| handleParameterClick(['out_parameters', item.key], { | |||||
| ...item.value, | ...item.value, | ||||
| value, | value, | ||||
| }); | }); | ||||
| @@ -34,10 +34,7 @@ function ExperimentTable({ tableData = [], style }: ExperimentTableProps) { | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div className={styles['experiment-table__duration']}> | <div className={styles['experiment-table__duration']}> | ||||
| {elapsedTime( | |||||
| new Date(item.create_time), | |||||
| item.finish_time ? new Date(item.finish_time) : new Date(), | |||||
| )} | |||||
| {elapsedTime(item.create_time, item.finish_time)} | |||||
| </div> | </div> | ||||
| <div className={styles['experiment-table__date']}>{formatDate(item.create_time)}</div> | <div className={styles['experiment-table__date']}>{formatDate(item.create_time)}</div> | ||||
| <div className={styles['experiment-table__operation']}> | <div className={styles['experiment-table__operation']}> | ||||
| @@ -4,6 +4,8 @@ | |||||
| * @Description: 定义全局类型,比如无关联的页面都需要要的类型 | * @Description: 定义全局类型,比如无关联的页面都需要要的类型 | ||||
| */ | */ | ||||
| import { ExperimentStatus } from '@/pages/Experiment/status'; | |||||
| // 流水线全局参数 | // 流水线全局参数 | ||||
| export type PipelineGlobalParam = { | export type PipelineGlobalParam = { | ||||
| param_name: string; | param_name: string; | ||||
| @@ -28,3 +30,34 @@ export type ExperimentInstance = { | |||||
| nodes_status: string; | nodes_status: string; | ||||
| global_param: PipelineGlobalParam[]; | global_param: PipelineGlobalParam[]; | ||||
| }; | }; | ||||
| // 流水线节点 | |||||
| export type PipelineNodeModel = { | |||||
| id: string; | |||||
| experimentStatus: ExperimentStatus; | |||||
| label: string; | |||||
| experimentStartTime: string; | |||||
| experimentEndTime?: string | null; | |||||
| control_strategy: string; | |||||
| in_parameters: string; | |||||
| out_parameters: string; | |||||
| }; | |||||
| // 流水线 | |||||
| export type PipelineNodeModelParameter = { | |||||
| label: string; | |||||
| value: any; | |||||
| require: number; | |||||
| type: string; | |||||
| placeholder?: string; | |||||
| describe?: string; | |||||
| }; | |||||
| export type PipelineNodeModelSerialize = Omit< | |||||
| PipelineNodeModel, | |||||
| 'control_strategy' | 'in_parameters' | 'out_parameters' | |||||
| > & { | |||||
| control_strategy: Record<string, PipelineNodeModelParameter>; | |||||
| in_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| out_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| }; | |||||
| @@ -3,15 +3,22 @@ import dayjs from 'dayjs'; | |||||
| /** | /** | ||||
| * Calculates the elapsed time between two dates and returns a formatted string representing the duration. | * Calculates the elapsed time between two dates and returns a formatted string representing the duration. | ||||
| * | * | ||||
| * @param {Date} beginDate - The starting date. | |||||
| * @param {Date} endDate - The ending date. | |||||
| * @param {string | null | undefined} begin - The starting date. | |||||
| * @param {string | null | undefined} end - The ending date. | |||||
| * @return {string} The formatted elapsed time string. | * @return {string} The formatted elapsed time string. | ||||
| */ | */ | ||||
| export const elapsedTime = (beginDate: Date, endDate: Date): string => { | |||||
| if (!isValidDate(beginDate) || !isValidDate(endDate)) { | |||||
| export const elapsedTime = (begin?: string | null, end?: string | null): string => { | |||||
| if (begin === undefined || begin === null) { | |||||
| return '--'; | return '--'; | ||||
| } | } | ||||
| const timestamp = endDate.getTime() - beginDate.getTime(); | |||||
| const beginDate = dayjs(begin); | |||||
| const endDate = end === undefined || end === null ? dayjs() : dayjs(end); | |||||
| if (!beginDate.isValid() || !endDate.isValid()) { | |||||
| return '--'; | |||||
| } | |||||
| const timestamp = endDate.valueOf() - beginDate.valueOf(); | |||||
| if (timestamp < 0) { | if (timestamp < 0) { | ||||
| return '时间有误'; | return '时间有误'; | ||||
| } | } | ||||