| @@ -4,4 +4,7 @@ module.exports = { | |||
| page: true, | |||
| REACT_APP_ENV: true, | |||
| }, | |||
| rules: { | |||
| "@typescript-eslint/no-use-before-define": "off" | |||
| } | |||
| }; | |||
| @@ -15,9 +15,9 @@ export default { | |||
| // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | |||
| '/api/': { | |||
| // 要代理的地址 | |||
| // target: 'ci4s-gateway-service.ci4s-test.svc:8082', | |||
| // target: 'http://172.20.32.98:8082', | |||
| target: 'http://172.20.32.150:8082', | |||
| // target: 'http://172.20.32.181:31205', | |||
| target: 'http://172.20.32.98:8082', | |||
| // target: 'http://172.20.32.150:8082', | |||
| // 配置了这个可以从 http 代理到 https | |||
| // 依赖 origin 的功能可能需要这个,比如 cookie | |||
| changeOrigin: true, | |||
| @@ -26,7 +26,7 @@ export default { | |||
| '/profile/avatar/': { | |||
| target: 'http://172.20.32.181:31205', | |||
| changeOrigin: true, | |||
| } | |||
| }, | |||
| }, | |||
| /** | |||
| @@ -46,18 +46,34 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'datasetPreparation', | |||
| path: '/datasetPreparation', | |||
| routes: [ | |||
| { | |||
| name: 'datasetAnnotation', | |||
| path: 'datasetAnnotation', | |||
| component: './DatasetPreparation/DatasetAnnotation/index', | |||
| }, | |||
| { | |||
| name: '训练', | |||
| path: 'pytorchtext/:id/:name', | |||
| component: './Pipeline/editPipeline/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'pipeline', | |||
| path: '/pipeline', | |||
| routes: [ | |||
| { | |||
| name: '流水线', | |||
| path: '/pipeline', | |||
| path: '', | |||
| component: './Pipeline/index', | |||
| }, | |||
| { | |||
| name: '训练', | |||
| path: '/pipeline/pytorchtext/:id/:name', | |||
| path: 'pytorchtext/:id/:name', | |||
| component: './Pipeline/editPipeline/index', | |||
| }, | |||
| ], | |||
| @@ -68,12 +84,12 @@ export default [ | |||
| routes: [ | |||
| { | |||
| name: '实验', | |||
| path: '/experiment', | |||
| path: '', | |||
| component: './Experiment/index', | |||
| }, | |||
| { | |||
| name: '实验训练', | |||
| path: '/experiment/pytorchtext/:workflowId/:id', | |||
| path: 'pytorchtext/:workflowId/:id', | |||
| component: './Experiment/experimentText/index', | |||
| }, | |||
| ], | |||
| @@ -84,7 +100,7 @@ export default [ | |||
| routes: [ | |||
| { | |||
| name: '开发环境', | |||
| path: '/developmentEnvironment', | |||
| path: '', | |||
| component: './DevelopmentEnvironment/index', | |||
| }, | |||
| ], | |||
| @@ -103,7 +119,7 @@ export default [ | |||
| path: '/system/role-auth/user/:id', | |||
| component: './System/Role/authUser', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| { | |||
| name: 'dataset', | |||
| @@ -123,14 +139,13 @@ export default [ | |||
| name: '模型管理', | |||
| path: '/dataset/modelIndex', | |||
| component: './Model/index', | |||
| }, | |||
| { | |||
| name: '模型简介', | |||
| path: '/dataset/modelIntro/:id', | |||
| component: './Model/modelIntro', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| { | |||
| name: 'monitor', | |||
| @@ -141,7 +156,7 @@ export default [ | |||
| path: '/monitor/job-log/index/:id', | |||
| component: './Monitor/JobLog', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| { | |||
| name: 'tool', | |||
| @@ -157,6 +172,6 @@ export default [ | |||
| path: '/tool/gen/edit', | |||
| component: './Tool/Gen/edit', | |||
| }, | |||
| ] | |||
| ], | |||
| }, | |||
| ]; | |||
| @@ -1,23 +1,25 @@ | |||
| import RightContent from '@/components/RightContent'; | |||
| import { LinkOutlined } from '@ant-design/icons'; | |||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | |||
| import { SettingDrawer } from '@ant-design/pro-components'; | |||
| import type { RunTimeLayoutConfig } from '@umijs/max'; | |||
| import { history, Link } from '@umijs/max'; | |||
| import { history } from '@umijs/max'; | |||
| import axios from 'axios'; | |||
| import defaultSettings from '../config/defaultSettings'; | |||
| import { errorConfig } from './requestErrorConfig'; | |||
| import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; | |||
| import { getRemoteMenu, getRoutersInfo, getUserInfo, patchRouteWithRemoteMenus, setRemoteMenu } from './services/session'; | |||
| import '../public/fonts/font.css'; | |||
| import { getAccessToken } from './access'; | |||
| import './dayjsConfig'; | |||
| import { PageEnum } from './enums/pagesEnums'; | |||
| import '../public/fonts/font.css' | |||
| import './global.less' | |||
| import axios from 'axios' | |||
| axios.defaults.baseUrl='http://172.20.32.150:8082' | |||
| import './global.less'; | |||
| import { | |||
| getRemoteMenu, | |||
| getRoutersInfo, | |||
| getUserInfo, | |||
| patchRouteWithRemoteMenus, | |||
| setRemoteMenu, | |||
| } from './services/session'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| axios.defaults.baseUrl = 'http://172.20.32.150:8082'; | |||
| const isDev = process.env.NODE_ENV === 'development'; | |||
| /** | |||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | |||
| * */ | |||
| @@ -155,9 +157,9 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) = | |||
| export async function onRouteChange({ clientRoutes, location }) { | |||
| const menus = getRemoteMenu(); | |||
| // console.log('onRouteChange', clientRoutes, location, menus); | |||
| if(menus === null && location.pathname !== PageEnum.LOGIN) { | |||
| console.log('refresh') | |||
| // console.log('onRouteChange', clientRoutes, location, menus); | |||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | |||
| console.log('refresh'); | |||
| history.go(0); | |||
| } | |||
| } | |||
| @@ -166,7 +168,6 @@ export async function onRouteChange({ clientRoutes, location }) { | |||
| // console.log('patchRoutes', routes, routeComponents); | |||
| // } | |||
| export async function patchClientRoutes({ routes }) { | |||
| // console.log('patchClientRoutes', routes); | |||
| patchRouteWithRemoteMenus(routes); | |||
| @@ -175,62 +176,12 @@ export async function patchClientRoutes({ routes }) { | |||
| export function render(oldRender: () => void) { | |||
| // console.log('render get routers', oldRender) | |||
| const token = getAccessToken(); | |||
| if(!token || token?.length === 0) { | |||
| if (!token || token?.length === 0) { | |||
| oldRender(); | |||
| return; | |||
| } | |||
| getRoutersInfo().then(res => { | |||
| getRoutersInfo().then((res) => { | |||
| setRemoteMenu(res); | |||
| oldRender() | |||
| oldRender(); | |||
| }); | |||
| } | |||
| /** | |||
| * @name request 配置,可以配置错误处理 | |||
| * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 | |||
| * @doc https://umijs.org/docs/max/request#配置 | |||
| */ | |||
| const checkRegion = 5 * 60 * 1000; | |||
| export const request = { | |||
| ...errorConfig, | |||
| requestInterceptors: [ | |||
| (url: any, options: { headers: any }) => { | |||
| const headers = options.headers ? options.headers : []; | |||
| console.log('request ====>:', url); | |||
| const authHeader = headers['Authorization']; | |||
| const isToken = headers['isToken']; | |||
| if (!authHeader && isToken !== false) { | |||
| const expireTime = getTokenExpireTime(); | |||
| if (expireTime) { | |||
| const left = Number(expireTime) - new Date().getTime(); | |||
| const refreshToken = getRefreshToken(); | |||
| if (left < checkRegion && refreshToken) { | |||
| if (left < 0) { | |||
| clearSessionToken(); | |||
| } | |||
| } else { | |||
| const accessToken = getAccessToken(); | |||
| if (accessToken) { | |||
| headers['Authorization'] = `Bearer ${accessToken}`; | |||
| } | |||
| } | |||
| } else { | |||
| clearSessionToken(); | |||
| } | |||
| } | |||
| return { url, options }; | |||
| }, | |||
| ], | |||
| responseInterceptors: [ | |||
| // (response) => | |||
| // { | |||
| // // // 不再需要异步处理读取返回体内容,可直接在data中读出,部分字段可在 config 中找到 | |||
| // // const { data = {} as any, config } = response; | |||
| // // // do something | |||
| // // console.log('data: ', data) | |||
| // // console.log('config: ', config) | |||
| // return response | |||
| // }, | |||
| ], | |||
| }; | |||
| @@ -0,0 +1,3 @@ | |||
| import dayjs from 'dayjs'; | |||
| import duration from 'dayjs/plugin/duration'; | |||
| dayjs.extend(duration); | |||
| @@ -0,0 +1 @@ | |||
| @@ -0,0 +1,6 @@ | |||
| import { getDictValueEnum } from '@/services/system/dict'; | |||
| export function useDictEnum(name: string) { | |||
| const data = getDictValueEnum(name); | |||
| return data; | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| export function useStateRef<T>(initialValue: T) { | |||
| const [value, setValue] = useState(initialValue); | |||
| const ref = useRef(value); | |||
| useEffect(() => { | |||
| ref.current = value; | |||
| }, [value]); | |||
| return [value, setValue, ref] as const; | |||
| } | |||
| @@ -1,7 +0,0 @@ | |||
| import { getDictValueEnum } from "@/services/system/dict"; | |||
| export function useDictEnum(name: string) | |||
| { | |||
| const data = getDictValueEnum(name); | |||
| return data; | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| .container { | |||
| width: 100%; | |||
| height: 100%; | |||
| .frame { | |||
| width: 100%; | |||
| height: 100%; | |||
| border: none; | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| import styles from './index.less'; | |||
| function DatasetAnnotation() { | |||
| return ( | |||
| <div className={styles.container}> | |||
| <iframe src="http://172.20.32.181:32102" className={styles.frame}></iframe> | |||
| </div> | |||
| ); | |||
| } | |||
| export default DatasetAnnotation; | |||
| @@ -1,14 +0,0 @@ | |||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||
| import {getJupyterUrl} from '@/services/developmentEnvironment/index.js' | |||
| const developmentEnvironment = React.FC = () => { | |||
| const [iframeUrl,setIframeUrl]=useState('') | |||
| useEffect(()=>{ | |||
| getJupyterUrl().then(ret=>{ | |||
| console.log(ret); | |||
| setIframeUrl(ret.msg) | |||
| }) | |||
| },[]) | |||
| return ( | |||
| <iframe style={{width:'100%',height:'81vh'}} src={iframeUrl} frameborder="0"></iframe> | |||
| )}; | |||
| export default developmentEnvironment; | |||
| @@ -0,0 +1,22 @@ | |||
| import { getJupyterUrl } from '@/services/developmentEnvironment'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useEffect, useState } from 'react'; | |||
| const DevelopmentEnvironment = () => { | |||
| const [iframeUrl, setIframeUrl] = useState(''); | |||
| useEffect(() => { | |||
| requestJupyterUrl(); | |||
| }, []); | |||
| const requestJupyterUrl = async () => { | |||
| const [res, error] = await to(getJupyterUrl()); | |||
| if (res) { | |||
| setIframeUrl(res.data as string); | |||
| } else { | |||
| console.log(error); | |||
| } | |||
| }; | |||
| return <iframe style={{ width: '100%', height: '81vh', border: 0 }} src={iframeUrl}></iframe>; | |||
| }; | |||
| export default DevelopmentEnvironment; | |||
| @@ -0,0 +1,18 @@ | |||
| import LogGroup from './logGroup'; | |||
| type LogListProps = { | |||
| list: any[]; | |||
| status: string; | |||
| }; | |||
| function LogList({ list = [], status }: LogListProps) { | |||
| return ( | |||
| <div> | |||
| {list.map((v) => ( | |||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } | |||
| export default LogList; | |||
| @@ -0,0 +1,51 @@ | |||
| .modal { | |||
| :global { | |||
| .ant-modal-content { | |||
| width: 825px; | |||
| padding: 20px 67px; | |||
| background: linear-gradient(180deg, #cfdfff 0%, #d4e2ff 9.77%, #ffffff 40%, #ffffff 100%); | |||
| border-radius: 21px; | |||
| } | |||
| .ant-modal-header { | |||
| margin: 20px 0; | |||
| background-color: transparent; | |||
| } | |||
| .ant-input { | |||
| height: 40px; | |||
| border-color: #e6e6e6; | |||
| } | |||
| .ant-select-single { | |||
| height: 40px; | |||
| } | |||
| .ant-form-item .ant-form-item-label > label { | |||
| color: rgba(29, 29, 32, 0.8); | |||
| } | |||
| .ant-modal-footer { | |||
| display: flex; | |||
| justify-content: center; | |||
| margin: 40px 0 30px 0; | |||
| } | |||
| .ant-btn { | |||
| width: 110px; | |||
| height: 40px; | |||
| font-size: 18px; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| border-radius: 10px; | |||
| } | |||
| .ant-btn-primary { | |||
| background: #1664ff; | |||
| } | |||
| } | |||
| .title { | |||
| display: flex; | |||
| align-items: center; | |||
| font-weight: 500; | |||
| .image { | |||
| width: 20px; | |||
| margin-right: 10px; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| import { Form, Input, Modal, Select } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import styles from './addExperimentModal.less'; | |||
| type FormData = { | |||
| name?: string; | |||
| description?: string; | |||
| workflow_id?: string | number; | |||
| }; | |||
| type AddExperimentModalProps = { | |||
| isAdd: boolean; | |||
| open: boolean; | |||
| onCancel: () => void; | |||
| onFinish: () => void; | |||
| workflowList: Workflow[]; | |||
| initialValues: FormData; | |||
| }; | |||
| interface GlobalParam { | |||
| param_name: string; | |||
| param_value: string; | |||
| } | |||
| interface Workflow { | |||
| id: string | number; | |||
| name: string; | |||
| global_param?: GlobalParam[] | null; | |||
| } | |||
| function AddExperimentModal({ | |||
| isAdd, | |||
| open, | |||
| onCancel, | |||
| onFinish, | |||
| workflowList = [], | |||
| initialValues = {}, | |||
| }: AddExperimentModalProps) { | |||
| const dialogTitle = isAdd ? '新建实验' : '编辑实验'; | |||
| const workflowDisabled = isAdd ? false : true; | |||
| const [globalParam, setGlobalParam] = useState<GlobalParam[]>([]); | |||
| const [form] = Form.useForm(); | |||
| // 除了流水线选择发生变化 | |||
| const handleWorkflowChange = (id: string) => { | |||
| const pipeline: Workflow | undefined = workflowList.find((v) => v.id === id); | |||
| if (pipeline && pipeline.global_param) { | |||
| setGlobalParam(pipeline.global_param); | |||
| const fields = pipeline.global_param.reduce((acc, item) => { | |||
| acc[item.param_name] = item.param_value; | |||
| return acc; | |||
| }, {} as Record<string, string>); | |||
| form.setFieldsValue(fields); | |||
| } else { | |||
| setGlobalParam([]); | |||
| } | |||
| }; | |||
| return ( | |||
| <Modal | |||
| className={styles.modal} | |||
| title={ | |||
| <div className={styles.title}> | |||
| <img className={styles.image} src={`/assets/images/pipeline-edit-icon.png`} alt="" /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={open} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={onCancel} | |||
| destroyOnClose={true} | |||
| > | |||
| <Form | |||
| name="form" | |||
| layout="vertical" | |||
| initialValues={initialValues} | |||
| onFinish={onFinish} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.Item | |||
| label="实验名称" | |||
| name="name" | |||
| rules={[{ required: true, message: '请输入实验名称' }]} | |||
| > | |||
| <Input placeholder="请输入实验名称" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="实验描述" | |||
| name="description" | |||
| rules={[{ required: true, message: '请输入实验描述' }]} | |||
| > | |||
| <Input placeholder="请输入实验描述" maxLength={128} showCount allowClear /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="选择流水线" | |||
| name="workflow_id" | |||
| rules={[{ required: true, message: '请选择流水线' }]} | |||
| > | |||
| <Select | |||
| disabled={workflowDisabled} | |||
| placeholder="请选择流水线" | |||
| onChange={handleWorkflowChange} | |||
| > | |||
| {Array.isArray(workflowList) | |||
| ? workflowList.map((item) => { | |||
| return ( | |||
| <Select.Option key={item.id} value={item.id}> | |||
| {item.name} | |||
| </Select.Option> | |||
| ); | |||
| }) | |||
| : null} | |||
| </Select> | |||
| </Form.Item> | |||
| {globalParam.map((item) => ( | |||
| <Form.Item label={item.param_name} name={item.param_name} key={item.param_name}> | |||
| <Input /> | |||
| </Form.Item> | |||
| ))} | |||
| </Form> | |||
| </Modal> | |||
| ); | |||
| } | |||
| export default AddExperimentModal; | |||
| @@ -1,375 +1,405 @@ | |||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||
| import { useParams } from 'react-router-dom' | |||
| import Props from './props'; | |||
| import { getExperimentIns } from '@/services/experiment/index.js'; | |||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||
| import G6 from '@antv/g6'; | |||
| import Styles from './editPipeline.less' | |||
| import momnet from 'moment'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useNavigate, useParams } from 'react-router-dom'; | |||
| import { s8 } from '../../../utils'; | |||
| import { Button, message} from 'antd'; | |||
| import {SaveOutlined} from '@ant-design/icons'; | |||
| import {saveWorkflow,getWorkflowById,} from '@/services/pipeline/index.js' | |||
| import {getExperimentIns,} from '@/services/experiment/index.js' | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import momnet from 'moment' | |||
| const ExperimentText = React.FC = () => { | |||
| const propsRef=useRef() | |||
| const navgite=useNavigate(); | |||
| const locationParams =useParams () //新版本获取路由参数接口 | |||
| let graph=null | |||
| const [experimentStatusObj,setExperimentStatusObj]=useState({}) | |||
| const [experimentAllMessage,setExperimentAllMessage]=useState({}) | |||
| const statusObj={ | |||
| "Running":'运行中', | |||
| "Succeeded":'成功', | |||
| "Pending":'等待中', | |||
| "Failed":'失败', | |||
| "Error":'错误', | |||
| "Terminated":'终止', | |||
| "Skipped":'未执行', | |||
| "Omitted":'未执行', | |||
| } | |||
| const statusColorObj={ | |||
| "Running":'#165bff', | |||
| "Succeeded":'#63a728', | |||
| "Pending":'#f981eb', | |||
| "Failed":'#c73131', | |||
| "Error":'#c73131', | |||
| "Terminated":'#8a8a8a', | |||
| "Skipped":'#8a8a8a', | |||
| "Omitted":'#8a8a8ae', | |||
| import { experimentStatusInfo } from '../status'; | |||
| import Styles from './editPipeline.less'; | |||
| import Props from './props'; | |||
| function ExperimentText() { | |||
| const [message, setMessage] = useState({}); | |||
| const messageRef = useRef(message); | |||
| const propsRef = useRef(); | |||
| const navgite = useNavigate(); | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| let graph = null; | |||
| const timers = (time) => { | |||
| let timer = new Date(time); | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| }; | |||
| const pipelineContainer = useEmotionCss(() => { | |||
| return { | |||
| display: 'flex', | |||
| backgroundColor: '#fff', | |||
| height: '98vh', | |||
| }; | |||
| }); | |||
| const graphStyle = useEmotionCss(() => { | |||
| return { | |||
| width: '100%', | |||
| backgroundColor: '#f9fafb', | |||
| flex: 1, | |||
| }; | |||
| }); | |||
| const graphRef = useRef(); | |||
| const onDragEnd = (val) => { | |||
| console.log(val, 'eee'); | |||
| const _x = val.x; | |||
| const _y = val.y; | |||
| const point = graph.getPointByClient(_x, _y); | |||
| let model = {}; | |||
| // 元模型 | |||
| model = { | |||
| ...val, | |||
| x: point.x, | |||
| y: point.y, | |||
| id: val.component_name + '-' + s8(), | |||
| isCluster: false, | |||
| }; | |||
| console.log(graph, model); | |||
| graph.addItem('node', model, true); | |||
| console.log(graph); | |||
| }; | |||
| const formChange = (val) => {}; | |||
| const handlerClick = (e) => { | |||
| console.log(propsRef, graph, messageRef.current); | |||
| // let cache = []; | |||
| // let json_str = JSON.stringify(graph, function(key, value) { | |||
| // if (typeof value === 'object' && value !== null) { | |||
| // if (cache.indexOf(value) !== -1) { | |||
| // return; | |||
| // } | |||
| // cache.push(value); | |||
| // } | |||
| // return value; | |||
| // }); | |||
| // console.log(json_str); | |||
| propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | |||
| }; | |||
| const getGraphData = (data) => { | |||
| if (graph) { | |||
| console.log(graph); | |||
| graph.data(data); | |||
| graph.render(); | |||
| } else { | |||
| setTimeout(() => { | |||
| getGraphData(data); | |||
| }, 500); | |||
| } | |||
| const timers=(time)=>{ | |||
| let timer=new Date(time) | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| } | |||
| const pipelineContainer = useEmotionCss(() => { | |||
| return { | |||
| display: 'flex', | |||
| backgroundColor:'#fff', | |||
| height:'98vh' | |||
| }; | |||
| }); | |||
| const graphStyle = useEmotionCss(() => { | |||
| return { | |||
| width:'100%', | |||
| backgroundColor:'#f9fafb', | |||
| flex:1 | |||
| }; | |||
| }); | |||
| const graphRef=useRef() | |||
| const onDragEnd=(val)=>{ | |||
| console.log(val,'eee'); | |||
| const _x = val.x | |||
| const _y = val.y | |||
| const point = graph.getPointByClient(_x, _y); | |||
| let model = {}; | |||
| // 元模型 | |||
| model = { | |||
| ...val, | |||
| x: point.x, | |||
| y: point.y, | |||
| id: val.component_name+'-'+s8(), | |||
| isCluster: false, | |||
| }; | |||
| console.log(graph, model); | |||
| }; | |||
| const getFirstWorkflow = (val) => { | |||
| getWorkflowById(val).then((ret) => { | |||
| console.log(ret, 'retttttttttt'); | |||
| if (ret.code == 200) { | |||
| if (graph && ret.data && ret.data.dag) { | |||
| console.log(JSON.parse(ret.data.dag)); | |||
| getExperimentIns(locationParams.id).then((res) => { | |||
| if (res.code == 200) { | |||
| console.log(ret.data, 'data'); | |||
| setMessage(res.data); | |||
| const experimentStatusObjs = JSON.parse(res.data.nodes_status); | |||
| const newNodeList = JSON.parse(ret.data.dag).nodes.map((item) => { | |||
| console.log(experimentStatusObjs); | |||
| return { | |||
| ...item, | |||
| experimentEndTime: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].finishedAt, | |||
| experimentStartTime: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].startedAt, | |||
| experimentStatus: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].phase, | |||
| component_id: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].id, | |||
| img: | |||
| experimentStatusObjs && | |||
| experimentStatusObjs[item.id] && | |||
| experimentStatusObjs[item.id].phase | |||
| ? item.img.slice(0, item.img.length - 4) + | |||
| '-' + | |||
| experimentStatusObjs[item.id].phase + | |||
| '.png' | |||
| : item.img, | |||
| }; | |||
| }); | |||
| const newData = { ...JSON.parse(ret.data.dag), nodes: newNodeList }; | |||
| graph.addItem('node', model, true); | |||
| console.log(graph); | |||
| } | |||
| const formChange=(val)=>{ | |||
| } | |||
| const handlerClick=(e)=>{ | |||
| console.log(propsRef,graph); | |||
| // let cache = []; | |||
| // let json_str = JSON.stringify(graph, function(key, value) { | |||
| // if (typeof value === 'object' && value !== null) { | |||
| // if (cache.indexOf(value) !== -1) { | |||
| // return; | |||
| // } | |||
| // cache.push(value); | |||
| // } | |||
| // return value; | |||
| // }); | |||
| // console.log(json_str); | |||
| propsRef.current.showDrawer(e,locationParams.id) | |||
| } | |||
| const getGraphData=(data)=>{ | |||
| if(graph){ | |||
| console.log(graph); | |||
| graph.data(data) | |||
| graph.render() | |||
| } | |||
| else{ | |||
| setTimeout(()=>{ | |||
| getGraphData(data) | |||
| },500) | |||
| getGraphData(newData); | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| const getFirstWorkflow=(val)=>{ | |||
| getWorkflowById(val).then(ret=>{ | |||
| console.log(ret,'retttttttttt'); | |||
| if(ret.code==200){ | |||
| if(graph&&ret.data&&ret.data.dag){ | |||
| console.log(JSON.parse(ret.data.dag)); | |||
| getExperimentIns(locationParams.id).then(res=>{ | |||
| if(res.code==200){ | |||
| console.log(ret.data,'data'); | |||
| const experimentStatusObjs=JSON.parse(res.data.nodes_status) | |||
| const newNodeList= JSON.parse(ret.data.dag).nodes.map(item=>{console.log(experimentStatusObjs); return {...item,experimentEndTime:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].finishedAt,experimentStartTime:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].startedAt,experimentStatus:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].phase,component_id:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].id,img:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].phase?item.img.slice(0,item.img.length-4)+'-'+experimentStatusObjs[item.id].phase+'.png':item.img}}) | |||
| const newData={...JSON.parse(ret.data.dag),nodes:newNodeList} | |||
| console.log(newData); | |||
| setExperimentAllMessage(res.data) | |||
| getGraphData(newData) | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| } | |||
| }) | |||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||
| // graph.render() | |||
| }); | |||
| }; | |||
| // const getExperimentIn=(val)=>{ | |||
| // getExperimentIns(val).then(ret=>{ | |||
| // if(ret.code==200){ | |||
| // console.log(JSON.parse(ret.data.nodes_status)); | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| // setTimeout(() => { | |||
| // console.log(experimentStatusObj); | |||
| } | |||
| } | |||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||
| // graph.render() | |||
| }) | |||
| } | |||
| // const getExperimentIn=(val)=>{ | |||
| // getExperimentIns(val).then(ret=>{ | |||
| // if(ret.code==200){ | |||
| // console.log(JSON.parse(ret.data.nodes_status)); | |||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||
| // setTimeout(() => { | |||
| // console.log(experimentStatusObj); | |||
| // }, 1000); | |||
| // } | |||
| // }) | |||
| // } | |||
| useEffect(()=>{ | |||
| initGraph() | |||
| getFirstWorkflow(locationParams.workflowId) | |||
| },[]) | |||
| const initGraph=()=>{ | |||
| G6.registerNode( | |||
| 'rect-node', | |||
| { | |||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||
| getAnchorPoints(cfg) { | |||
| return ( | |||
| cfg.anchorPoints || [ | |||
| // 上下各3,左右各1 | |||
| [0.5, 0], | |||
| [0.5, 1], | |||
| // }, 1000); | |||
| // } | |||
| ] | |||
| ); | |||
| }, | |||
| afterDraw(cfg, group) { | |||
| // console.log(group, cfg, 12312); | |||
| const image = group.addShape('image', { | |||
| attrs: { | |||
| x: -45, | |||
| y: -10, | |||
| width: 20, | |||
| height: 20, | |||
| img: cfg.img, | |||
| cursor: 'pointer', | |||
| }, | |||
| draggable: true, | |||
| }); | |||
| // if (cfg.label) { | |||
| // group.addShape('text', { | |||
| // attrs: { | |||
| // x: 0, | |||
| // y: cfg.height / 2 - 5, | |||
| // textAlign: 'center', | |||
| // textBaseline: 'middle', | |||
| // text: cfg.label, | |||
| // fill: '#fff', | |||
| // }, | |||
| // draggable: true, | |||
| // }); | |||
| // } | |||
| const bbox = group.getBBox(); | |||
| const anchorPoints = this.getAnchorPoints(cfg); | |||
| // console.log(anchorPoints); | |||
| anchorPoints.forEach((anchorPos, i) => { | |||
| group.addShape('circle', { | |||
| attrs: { | |||
| r: 3, | |||
| x: bbox.x + bbox.width * anchorPos[0], | |||
| y: bbox.y + bbox.height * anchorPos[1], | |||
| fill: '#fff', | |||
| stroke: '#a4a4a5', | |||
| }, | |||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | |||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | |||
| links: 0, // cache the number of edges connected to this shape | |||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | |||
| }); | |||
| }); | |||
| return image; | |||
| }, | |||
| // response the state changes and show/hide the link-point circles | |||
| setState(name, value, item) { | |||
| const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | |||
| anchorPoints.forEach(point => { | |||
| if (value || point.get('links') > 0) point.show() | |||
| else point.hide() | |||
| }) | |||
| // } | |||
| // }) | |||
| // } | |||
| useEffect(() => { | |||
| initGraph(); | |||
| getFirstWorkflow(locationParams.workflowId); | |||
| }, []); | |||
| useEffect(() => { | |||
| // Update the refs whenever the state changes | |||
| messageRef.current = message; | |||
| }, [message]); | |||
| const initGraph = () => { | |||
| G6.registerNode( | |||
| 'rect-node', | |||
| { | |||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||
| getAnchorPoints(cfg) { | |||
| return ( | |||
| cfg.anchorPoints || [ | |||
| // 上下各3,左右各1 | |||
| [0.5, 0], | |||
| [0.5, 1], | |||
| ] | |||
| ); | |||
| }, | |||
| afterDraw(cfg, group) { | |||
| // console.log(group, cfg, 12312); | |||
| const image = group.addShape('image', { | |||
| attrs: { | |||
| x: -45, | |||
| y: -10, | |||
| width: 20, | |||
| height: 20, | |||
| img: cfg.img, | |||
| cursor: 'pointer', | |||
| }, | |||
| }, | |||
| 'rect' | |||
| ); | |||
| console.log(graphRef,'graphRef'); | |||
| graph = new G6.Graph({ | |||
| container: graphRef.current, | |||
| grid: true, | |||
| width: graphRef.current.clientWidth ||500, | |||
| height: graphRef.current.clientHeight||760, | |||
| animate: false, | |||
| groupByTypes: false, | |||
| enabledStack: true, | |||
| modes: { | |||
| default: [ | |||
| // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | |||
| { | |||
| type: 'drag-node', | |||
| shouldBegin: e => { | |||
| if (e.target.get('name') === 'anchor-point') return false; | |||
| return true; | |||
| draggable: true, | |||
| }); | |||
| // if (cfg.label) { | |||
| // group.addShape('text', { | |||
| // attrs: { | |||
| // x: 0, | |||
| // y: cfg.height / 2 - 5, | |||
| // textAlign: 'center', | |||
| // textBaseline: 'middle', | |||
| // text: cfg.label, | |||
| // fill: '#fff', | |||
| // }, | |||
| // draggable: true, | |||
| // }); | |||
| // } | |||
| const bbox = group.getBBox(); | |||
| const anchorPoints = this.getAnchorPoints(cfg); | |||
| // console.log(anchorPoints); | |||
| anchorPoints.forEach((anchorPos, i) => { | |||
| group.addShape('circle', { | |||
| attrs: { | |||
| r: 3, | |||
| x: bbox.x + bbox.width * anchorPos[0], | |||
| y: bbox.y + bbox.height * anchorPos[1], | |||
| fill: '#fff', | |||
| stroke: '#a4a4a5', | |||
| }, | |||
| // shouldEnd: e => { | |||
| // console.log(e); | |||
| // return false; | |||
| // }, | |||
| }, | |||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | |||
| 'drag-canvas', | |||
| 'zoom-canvas', | |||
| // 'brush-select', | |||
| 'drag-combo', | |||
| ], | |||
| altSelect: [ | |||
| { | |||
| type: 'brush-select', | |||
| trigger: 'drag', | |||
| }, | |||
| 'drag-node', | |||
| ], | |||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | |||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | |||
| links: 0, // cache the number of edges connected to this shape | |||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | |||
| }); | |||
| }); | |||
| return image; | |||
| }, | |||
| defaultNode: { | |||
| type: 'rect-node', | |||
| size: [110,36], | |||
| labelCfg: { | |||
| style: { | |||
| fill: '#000', | |||
| fontSize: 10, | |||
| cursor: 'pointer', | |||
| x: -20, | |||
| y: 0, | |||
| textAlign: 'left', | |||
| textBaseline: 'middle', | |||
| // response the state changes and show/hide the link-point circles | |||
| setState(name, value, item) { | |||
| const anchorPoints = item | |||
| .getContainer() | |||
| .findAll((ele) => ele.get('name') === 'anchor-point'); | |||
| anchorPoints.forEach((point) => { | |||
| if (value || point.get('links') > 0) point.show(); | |||
| else point.hide(); | |||
| }); | |||
| // } | |||
| }, | |||
| }, | |||
| 'rect', | |||
| ); | |||
| console.log(graphRef, 'graphRef'); | |||
| graph = new G6.Graph({ | |||
| container: graphRef.current, | |||
| grid: true, | |||
| width: graphRef.current.clientWidth || 500, | |||
| height: graphRef.current.clientHeight || 760, | |||
| animate: false, | |||
| groupByTypes: false, | |||
| enabledStack: true, | |||
| modes: { | |||
| default: [ | |||
| // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | |||
| { | |||
| type: 'drag-node', | |||
| shouldBegin: (e) => { | |||
| if (e.target.get('name') === 'anchor-point') return false; | |||
| return true; | |||
| }, | |||
| // shouldEnd: e => { | |||
| // console.log(e); | |||
| // return false; | |||
| // }, | |||
| }, | |||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | |||
| 'drag-canvas', | |||
| 'zoom-canvas', | |||
| // 'brush-select', | |||
| 'drag-combo', | |||
| ], | |||
| altSelect: [ | |||
| { | |||
| type: 'brush-select', | |||
| trigger: 'drag', | |||
| }, | |||
| 'drag-node', | |||
| ], | |||
| }, | |||
| defaultNode: { | |||
| type: 'rect-node', | |||
| size: [110, 36], | |||
| labelCfg: { | |||
| style: { | |||
| fill: '#fff', | |||
| stroke: '#fff', | |||
| radius:10, | |||
| lineWidth:0.5 | |||
| fill: '#000', | |||
| fontSize: 10, | |||
| cursor: 'pointer', | |||
| x: -20, | |||
| y: 0, | |||
| textAlign: 'left', | |||
| textBaseline: 'middle', | |||
| }, | |||
| }, | |||
| nodeStateStyles: { | |||
| nodeSelected: { | |||
| style: { | |||
| fill: '#fff', | |||
| stroke: '#fff', | |||
| radius: 10, | |||
| lineWidth: 0.5, | |||
| }, | |||
| }, | |||
| nodeStateStyles: { | |||
| nodeSelected: { | |||
| fill: 'red', | |||
| shadowColor: 'red', | |||
| stroke: 'red', | |||
| 'text-shape': { | |||
| fill: 'red', | |||
| shadowColor: 'red', | |||
| stroke: 'red', | |||
| 'text-shape': { | |||
| fill: 'red', | |||
| stroke: 'red', | |||
| }, | |||
| }, | |||
| }, | |||
| defaultEdge: { | |||
| // type: 'quadratic', | |||
| type: 'cubic-vertical', | |||
| }, | |||
| defaultEdge: { | |||
| // type: 'quadratic', | |||
| type: 'cubic-vertical', | |||
| style: { | |||
| endArrow: { // 设置终点箭头 | |||
| path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) | |||
| d: 4.5, | |||
| fill:'#a2a6b5' | |||
| }, | |||
| cursor: 'pointer', | |||
| lineWidth: 1, | |||
| opacity: 1, | |||
| stroke: '#a2a6b5', | |||
| radius: 1, | |||
| }, | |||
| nodeStateStyle: { | |||
| hover: { | |||
| opacity: 1, | |||
| stroke: '#8fe8ff', | |||
| }, | |||
| style: { | |||
| endArrow: { | |||
| // 设置终点箭头 | |||
| path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) | |||
| d: 4.5, | |||
| fill: '#a2a6b5', | |||
| }, | |||
| labelCfg: { | |||
| autoRotate: true, | |||
| // refY: 10, | |||
| style: { | |||
| fontSize: 10, | |||
| fill: '#FFF', | |||
| }, | |||
| cursor: 'pointer', | |||
| lineWidth: 1, | |||
| opacity: 1, | |||
| stroke: '#a2a6b5', | |||
| radius: 1, | |||
| }, | |||
| nodeStateStyle: { | |||
| hover: { | |||
| opacity: 1, | |||
| stroke: '#8fe8ff', | |||
| }, | |||
| }, | |||
| defaultCombo: { | |||
| type: 'rect', | |||
| fixCollapseSize: 70, | |||
| labelCfg: { | |||
| autoRotate: true, | |||
| // refY: 10, | |||
| style: { | |||
| fill: '#00e0ff0d', | |||
| stroke: '#00e0ff', | |||
| lineDash: [5, 10], | |||
| cursor: 'pointer', | |||
| fontSize: 10, | |||
| fill: '#FFF', | |||
| }, | |||
| }, | |||
| // linkCenter: true, | |||
| fitView: false, | |||
| fitViewPadding: [60, 60, 60, 80], | |||
| }); | |||
| graph.on('dblclick', handlerClick); | |||
| window.onresize = () => { | |||
| if (!graph || graph.get('destroyed')) return; | |||
| if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) return; | |||
| graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); | |||
| }; | |||
| } | |||
| return (<div className={pipelineContainer}> | |||
| <div className={Styles.centerContainer}> | |||
| }, | |||
| defaultCombo: { | |||
| type: 'rect', | |||
| fixCollapseSize: 70, | |||
| style: { | |||
| fill: '#00e0ff0d', | |||
| stroke: '#00e0ff', | |||
| lineDash: [5, 10], | |||
| cursor: 'pointer', | |||
| }, | |||
| }, | |||
| // linkCenter: true, | |||
| fitView: false, | |||
| fitViewPadding: [60, 60, 60, 80], | |||
| }); | |||
| graph.on('dblclick', handlerClick); | |||
| window.onresize = () => { | |||
| if (!graph || graph.get('destroyed')) return; | |||
| if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) | |||
| return; | |||
| graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); | |||
| }; | |||
| }; | |||
| return ( | |||
| <div className={pipelineContainer}> | |||
| <div className={Styles.centerContainer}> | |||
| <div className={Styles.buttonList}> | |||
| <div className={Styles.allMessageItem}>启动时间:{momnet(experimentAllMessage.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||
| <div className={Styles.allMessageItem}>执行时长:{experimentAllMessage.finish_time?timers(new Date(experimentAllMessage.finish_time).getTime()-new Date(experimentAllMessage.create_time).getTime()):timers(new Date().getTime()-new Date(experimentAllMessage.create_time).getTime())}</div> | |||
| <div className={Styles.allMessageItem}>状态: | |||
| <div style={{width:'8px', | |||
| height:'8px', | |||
| borderRadius:'50%', | |||
| marginRight:'6px', | |||
| backgroundColor:statusColorObj[experimentAllMessage.status] | |||
| }}></div> | |||
| <span style={{color:statusColorObj[experimentAllMessage.status]}}>{statusObj[experimentAllMessage.status]}</span></div> | |||
| <div className={Styles.allMessageItem}> | |||
| 启动时间:{momnet(message.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||
| </div> | |||
| <div className={Styles.allMessageItem}> | |||
| 执行时长: | |||
| {message.finish_time | |||
| ? elapsedTime(new Date(message.create_time), new Date(message.finish_time)) | |||
| : elapsedTime(new Date(message.create_time), new Date())} | |||
| </div> | |||
| <div className={Styles.allMessageItem}> | |||
| 状态: | |||
| <div | |||
| style={{ | |||
| width: '8px', | |||
| height: '8px', | |||
| borderRadius: '50%', | |||
| marginRight: '6px', | |||
| backgroundColor: experimentStatusInfo[message.status]?.color, | |||
| }} | |||
| ></div> | |||
| <span style={{ color: experimentStatusInfo[message.status]?.color }}> | |||
| {experimentStatusInfo[message.status]?.label} | |||
| </span> | |||
| </div> | |||
| </div> | |||
| <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | |||
| </div> | |||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||
| </div>)}; | |||
| export default ExperimentText; | |||
| </div> | |||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentText; | |||
| @@ -0,0 +1,34 @@ | |||
| .log_group { | |||
| padding-bottom: 10px; | |||
| } | |||
| .log_group_pod { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| padding: 15px; | |||
| background: rgba(234, 234, 234, 0.5); | |||
| cursor: pointer; | |||
| &_name { | |||
| margin-right: 10px; | |||
| color: #1d1d20; | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| .log_group_detail { | |||
| padding: 15px; | |||
| color: white; | |||
| font-size: 14px; | |||
| white-space: pre-line; | |||
| word-break: break-all; | |||
| background: #19253b; | |||
| } | |||
| .log_group_more_button { | |||
| display: flex; | |||
| justify-content: center; | |||
| color: white; | |||
| background: #19253b; | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| import { useStateRef } from '@/hooks'; | |||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | |||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | |||
| import { Button } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './logGroup.less'; | |||
| type LogGroupProps = { | |||
| log_type?: string; | |||
| pod_name?: string; | |||
| log_content?: string; | |||
| start_time?: string; | |||
| status: string; | |||
| }; | |||
| type Log = { | |||
| start_time: string; | |||
| log_content: string; | |||
| }; | |||
| function LogGroup({ | |||
| log_type = '', | |||
| pod_name = '', | |||
| log_content = '', | |||
| start_time = '', | |||
| status = '', | |||
| }: LogGroupProps) { | |||
| const [collapse, setCollapse] = useState(true); | |||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | |||
| const [completed, setCompleted] = useState(false); | |||
| useEffect(() => { | |||
| if (status === 'Running') { | |||
| const timerId = setInterval(() => { | |||
| requestExperimentPodsLog(); | |||
| }, 5000); | |||
| return () => { | |||
| clearInterval(timerId); | |||
| }; | |||
| } | |||
| }, []); | |||
| // 请求日志 | |||
| const requestExperimentPodsLog = async () => { | |||
| const list = logListRef.current; | |||
| const startTime = list.length > 0 ? list[list.length - 1].start_time : start_time; | |||
| const params = { | |||
| pod_name, | |||
| start_time: startTime, | |||
| }; | |||
| const res = await getExperimentPodsLog(params); | |||
| const { log_detail } = res.data; | |||
| if (log_detail && log_detail.log_content) { | |||
| setLogList((oldList) => oldList.concat(log_detail)); | |||
| } else { | |||
| setCompleted(true); | |||
| } | |||
| }; | |||
| // 请求实时日志 | |||
| // const requestExperimentPodsRealtimeLog = async () => { | |||
| // const params = { | |||
| // pod_name, | |||
| // namespace: namespace, | |||
| // container_name: log_type === 'resource' ? '' : 'main', | |||
| // }; | |||
| // const res = await getExperimentPodsRealtimeLog(params); | |||
| // const { log_detail } = res.data; | |||
| // if (log_detail && log_detail.log_content) { | |||
| // setLogList((list) => list.concat(log_detail)); | |||
| // } else { | |||
| // setCompleted(true); | |||
| // } | |||
| // }; | |||
| // 处理折叠 | |||
| const handleCollapse = async () => { | |||
| if (!collapse) { | |||
| setCollapse(true); | |||
| return; | |||
| } | |||
| if (logList.length === 0) { | |||
| try { | |||
| await requestExperimentPodsLog(); | |||
| setCollapse(false); | |||
| } catch (error) { | |||
| return Promise.reject(error); | |||
| } | |||
| } else { | |||
| setCollapse(false); | |||
| } | |||
| }; | |||
| // 加载更多 | |||
| const loadMore = () => { | |||
| requestExperimentPodsLog(); | |||
| }; | |||
| const showLog = (log_type === 'resource' && !collapse) || log_type === 'normal'; | |||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | |||
| const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; | |||
| return ( | |||
| <div className={styles.log_group}> | |||
| {log_type === 'resource' && ( | |||
| <div className={styles.log_group_pod} onClick={handleCollapse}> | |||
| <div className={styles.log_group_pod_name}>{pod_name}</div> | |||
| {collapse ? <DownOutlined /> : <UpOutlined />} | |||
| </div> | |||
| )} | |||
| {showLog && <div className={styles.log_group_detail}>{logText}</div>} | |||
| <div className={styles.log_group_more_button}> | |||
| {showMoreBtn && ( | |||
| <Button | |||
| type="text" | |||
| style={{ color: 'white' }} | |||
| icon={<DoubleRightOutlined rotate={90} />} | |||
| onClick={loadMore} | |||
| > | |||
| 更多 | |||
| </Button> | |||
| )} | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default LogGroup; | |||
| @@ -1,336 +1,424 @@ | |||
| import React, { useState,useImperativeHandle ,forwardRef } from 'react'; | |||
| import { Button, Drawer,Form, Input ,Tabs,message } from 'antd'; | |||
| import Styles from './editPipeline.less' | |||
| import{getQueryByExperimentLog,getNodeResult}from '@/services/experiment/index.js' | |||
| import { ProfileOutlined, DatabaseOutlined} from '@ant-design/icons'; | |||
| import {downLoadZip} from '@/utils/downloadfile' | |||
| import momnet from 'moment' | |||
| import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { downLoadZip } from '@/utils/downloadfile'; | |||
| import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
| import { Drawer, Form, Input, Tabs, message } from 'antd'; | |||
| import moment from 'moment'; | |||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||
| import LogList from './LogList'; | |||
| import Styles from './editPipeline.less'; | |||
| const { TextArea } = Input; | |||
| const Props = forwardRef(({onParentChange}, ref) =>{ | |||
| const [form] = Form.useForm(); | |||
| const [stagingItem,setStagingItem]=useState({}) | |||
| const [resultObj,setResultObj]=useState([]) | |||
| const [messageItem,setMessageItem]=useState('') | |||
| const statusObj={ | |||
| "Running":'运行中', | |||
| "Succeeded":'成功', | |||
| "Pending":'等待中', | |||
| "Failed":'失败', | |||
| "Error":'错误', | |||
| "Terminated":'终止', | |||
| "Skipped":'未执行', | |||
| "Omitted":'未执行', | |||
| } | |||
| const statusColorObj={ | |||
| "Running":'#165bff', | |||
| "Succeeded":'#63a728', | |||
| "Pending":'#f981eb', | |||
| "Failed":'#c73131', | |||
| "Error":'#c73131', | |||
| "Terminated":'#8a8a8a', | |||
| "Skipped":'#8a8a8a', | |||
| "Omitted":'#8a8a8ae', | |||
| } | |||
| const exportResult=(e,val)=>{ | |||
| const Props = forwardRef(({ onParentChange }, ref) => { | |||
| const [form] = Form.useForm(); | |||
| const [stagingItem, setStagingItem] = useState({}); | |||
| const [resultObj, setResultObj] = useState([]); | |||
| const [logList, setLogList] = useState([]); | |||
| const statusObj = { | |||
| Running: '运行中', | |||
| Succeeded: '成功', | |||
| Pending: '等待中', | |||
| Failed: '失败', | |||
| Error: '错误', | |||
| Terminated: '终止', | |||
| Skipped: '未执行', | |||
| Omitted: '未执行', | |||
| }; | |||
| const statusColorObj = { | |||
| Running: '#165bff', | |||
| Succeeded: '#63a728', | |||
| Pending: '#f981eb', | |||
| Failed: '#c73131', | |||
| Error: '#c73131', | |||
| Terminated: '#8a8a8a', | |||
| Skipped: '#8a8a8a', | |||
| Omitted: '#8a8a8ae', | |||
| }; | |||
| const exportResult = (e, val) => { | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/minioStorage/download`,{path:val}) | |||
| } | |||
| const timers=(time)=>{ | |||
| let timer=new Date(time) | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| } | |||
| const items = [ | |||
| { | |||
| key: '1', | |||
| label: '日志详情', | |||
| children: <div style={{height:'740px', | |||
| background:'#19253b', | |||
| color:'#fff', | |||
| fontSize:'14px' | |||
| }} dangerouslySetInnerHTML={{ __html: messageItem }}></div>, | |||
| icon:<ProfileOutlined/> | |||
| }, | |||
| { | |||
| key: '2', | |||
| label: '配置参数', | |||
| icon:<DatabaseOutlined />, | |||
| children: <Form | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| labelCol={{ | |||
| span: 16, | |||
| }} | |||
| wrapperCol={{ | |||
| span: 16, | |||
| }} | |||
| style={{ | |||
| maxWidth: 600, | |||
| }} | |||
| initialValues={{ | |||
| remember: true, | |||
| }} | |||
| onFinish={onFinish} | |||
| onFinishFailed={onFinishFailed} | |||
| autoComplete="off" | |||
| > | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/static-message.png'} alt="" /> | |||
| 基本信息 | |||
| </div> | |||
| <Form.Item | |||
| label="任务名称" | |||
| name="label" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="任务ID" | |||
| name="id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务id', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/duty-message.png'} alt="" /> | |||
| 任务信息 | |||
| </div> | |||
| <Form.Item | |||
| label="镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="工作目录" | |||
| name="working_directory" | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="启动命令" | |||
| name="command" | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="resources_standard" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="挂载路径" | |||
| name="mount_path" | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="环境变量" | |||
| name="env_variables" | |||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: val }); | |||
| }; | |||
| const timers = (time) => { | |||
| let timer = new Date(time); | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| }; | |||
| const items = [ | |||
| { | |||
| key: '1', | |||
| label: '日志详情', | |||
| children: <LogList list={logList} status={stagingItem.experimentStatus}></LogList>, | |||
| icon: <ProfileOutlined />, | |||
| }, | |||
| { | |||
| key: '2', | |||
| label: '配置参数', | |||
| icon: <DatabaseOutlined />, | |||
| children: ( | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| labelCol={{ | |||
| span: 16, | |||
| }} | |||
| wrapperCol={{ | |||
| span: 16, | |||
| }} | |||
| style={{ | |||
| maxWidth: 600, | |||
| }} | |||
| initialValues={{ | |||
| remember: true, | |||
| }} | |||
| onFinish={onFinish} | |||
| onFinishFailed={onFinishFailed} | |||
| autoComplete="off" | |||
| > | |||
| <TextArea disabled/> | |||
| </Form.Item> | |||
| {stagingItem.control_strategy&&Object.keys(stagingItem.control_strategy)&&Object.keys(stagingItem.control_strategy).length>0?Object.keys(stagingItem.control_strategy).map(item=> | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/static-message.png'} | |||
| alt="" | |||
| /> | |||
| 基本信息 | |||
| </div> | |||
| <Form.Item | |||
| label={stagingItem.control_strategy[item].label} | |||
| disabled | |||
| name={item} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| ):''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/duty-message.png'} alt="" /> | |||
| 输入参数 | |||
| </div> | |||
| {stagingItem.in_parameters&&Object.keys(stagingItem.in_parameters)&&Object.keys(stagingItem.in_parameters).length>0?Object.keys(stagingItem.in_parameters).map(item=> | |||
| label="任务名称" | |||
| name="label" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={stagingItem.in_parameters[item].label+'('+item+')'} | |||
| name={item} | |||
| disabled | |||
| rules={[{ required: stagingItem.in_parameters[item].require?true:false}]} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| ):''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img style={{width:'13px',marginRight:'10px'}} src={'/assets/images/duty-message.png'} alt="" /> | |||
| 输出参数 | |||
| </div> | |||
| {stagingItem.out_parameters&&Object.keys(stagingItem.out_parameters)&&Object.keys(stagingItem.out_parameters).length>0?Object.keys(stagingItem.out_parameters).map(item=> | |||
| label="任务ID" | |||
| name="id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务id', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/duty-message.png'} | |||
| alt="" | |||
| /> | |||
| 任务信息 | |||
| </div> | |||
| <Form.Item | |||
| label={stagingItem.out_parameters[item].label+'('+item+')'} | |||
| disabled | |||
| rules={[{ required: stagingItem.out_parameters[item].require?true:false}]} | |||
| name={item} | |||
| > | |||
| <Input disabled/> | |||
| </Form.Item> | |||
| ):''} | |||
| </Form>, | |||
| }, | |||
| { | |||
| key: '3', | |||
| label: '输出结果', | |||
| children: <div style={{minHeight:'740px', | |||
| background:'#f4f4f4', | |||
| color:'#000', | |||
| fontSize:'14px', | |||
| padding:'0 10px 20px 20px', | |||
| }} > | |||
| {resultObj&&resultObj.length>0?resultObj.map(item=><div> | |||
| <div className={Styles.resultTop}> | |||
| <span>{item.name}</span> | |||
| <div style={{display:'flex'}}> | |||
| <a onClick={(e)=>{exportResult(e,item.path)}} style={{marginRight:'10px'}}>下载</a> | |||
| <a style={{marginRight:'10px'}}>导出到模型库</a> | |||
| <a style={{marginRight:'10px'}}>导出到数据集</a> | |||
| </div> | |||
| label="镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="工作目录" name="working_directory"> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="启动命令" name="command"> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="resources_standard" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="挂载路径" name="mount_path"> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <Form.Item label="环境变量" name="env_variables"> | |||
| <TextArea disabled /> | |||
| </Form.Item> | |||
| {stagingItem.control_strategy && | |||
| Object.keys(stagingItem.control_strategy) && | |||
| Object.keys(stagingItem.control_strategy).length > 0 | |||
| ? Object.keys(stagingItem.control_strategy).map((item) => ( | |||
| <Form.Item label={stagingItem.control_strategy[item].label} disabled name={item}> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| )) | |||
| : ''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/duty-message.png'} | |||
| alt="" | |||
| /> | |||
| 输入参数 | |||
| </div> | |||
| <div style={{margin:'15px 0'}} className={Styles.resultContent}> | |||
| <span>文件名称</span> | |||
| <span>文件大小</span> | |||
| {stagingItem.in_parameters && | |||
| Object.keys(stagingItem.in_parameters) && | |||
| Object.keys(stagingItem.in_parameters).length > 0 | |||
| ? Object.keys(stagingItem.in_parameters).map((item) => ( | |||
| <Form.Item | |||
| label={stagingItem.in_parameters[item].label + '(' + item + ')'} | |||
| name={item} | |||
| disabled | |||
| rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| )) | |||
| : ''} | |||
| <div className={Styles.editPipelinePropsContent}> | |||
| <img | |||
| style={{ width: '13px', marginRight: '10px' }} | |||
| src={'/assets/images/duty-message.png'} | |||
| alt="" | |||
| /> | |||
| 输出参数 | |||
| </div> | |||
| {item.value&&item.value.length>0?item.value.map(ele=> | |||
| <div className={Styles.resultContent}> | |||
| <span>{ele.name}</span> | |||
| <span>{ele.size}</span> | |||
| </div>):null} | |||
| </div>):null} | |||
| </div>, | |||
| icon:<ProfileOutlined/> | |||
| }, | |||
| ]; | |||
| const [open, setOpen] = useState(false); | |||
| const afterOpenChange=()=>{ | |||
| if(!open){ | |||
| console.log(111,open); | |||
| console.log(stagingItem,form.getFieldsValue()); | |||
| for(let i in form.getFieldsValue()){ | |||
| for(let j in stagingItem.in_parameters){ | |||
| if(i==j){ | |||
| console.log(j,i); | |||
| stagingItem.in_parameters[j].value=form.getFieldsValue()[i] | |||
| } | |||
| {stagingItem.out_parameters && | |||
| Object.keys(stagingItem.out_parameters) && | |||
| Object.keys(stagingItem.out_parameters).length > 0 | |||
| ? Object.keys(stagingItem.out_parameters).map((item) => ( | |||
| <Form.Item | |||
| label={stagingItem.out_parameters[item].label + '(' + item + ')'} | |||
| disabled | |||
| rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]} | |||
| name={item} | |||
| > | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| )) | |||
| : ''} | |||
| </Form> | |||
| ), | |||
| }, | |||
| { | |||
| key: '3', | |||
| label: '输出结果', | |||
| children: ( | |||
| <div | |||
| style={{ | |||
| minHeight: '740px', | |||
| background: '#f4f4f4', | |||
| color: '#000', | |||
| fontSize: '14px', | |||
| padding: '0 10px 20px 20px', | |||
| }} | |||
| > | |||
| {resultObj && resultObj.length > 0 | |||
| ? resultObj.map((item) => ( | |||
| <div> | |||
| <div className={Styles.resultTop}> | |||
| <span>{item.name}</span> | |||
| <div style={{ display: 'flex' }}> | |||
| <a | |||
| onClick={(e) => { | |||
| exportResult(e, item.path); | |||
| }} | |||
| style={{ marginRight: '10px' }} | |||
| > | |||
| 下载 | |||
| </a> | |||
| <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> | |||
| </div> | |||
| </div> | |||
| <div style={{ margin: '15px 0' }} className={Styles.resultContent}> | |||
| <span>文件名称</span> | |||
| <span>文件大小</span> | |||
| </div> | |||
| {item.value && item.value.length > 0 | |||
| ? item.value.map((ele) => ( | |||
| <div className={Styles.resultContent}> | |||
| <span>{ele.name}</span> | |||
| <span>{ele.size}</span> | |||
| </div> | |||
| )) | |||
| : null} | |||
| </div> | |||
| )) | |||
| : null} | |||
| </div> | |||
| ), | |||
| icon: <ProfileOutlined />, | |||
| }, | |||
| ]; | |||
| const [open, setOpen] = useState(false); | |||
| const afterOpenChange = () => { | |||
| if (!open) { | |||
| console.log(111, open); | |||
| console.log(stagingItem, form.getFieldsValue()); | |||
| for (let i in form.getFieldsValue()) { | |||
| for (let j in stagingItem.in_parameters) { | |||
| if (i == j) { | |||
| console.log(j, i); | |||
| stagingItem.in_parameters[j].value = form.getFieldsValue()[i]; | |||
| } | |||
| for(let p in stagingItem.out_parameters){ | |||
| if(i==p){ | |||
| stagingItem.out_parameters[p].value=form.getFieldsValue()[i] | |||
| } | |||
| } | |||
| for (let p in stagingItem.out_parameters) { | |||
| if (i == p) { | |||
| stagingItem.out_parameters[p].value = form.getFieldsValue()[i]; | |||
| } | |||
| for(let k in stagingItem.control_strategy){ | |||
| if(i==k){ | |||
| stagingItem.control_strategy[k].value=form.getFieldsValue()[i] | |||
| } | |||
| } | |||
| for (let k in stagingItem.control_strategy) { | |||
| if (i == k) { | |||
| stagingItem.control_strategy[k].value = form.getFieldsValue()[i]; | |||
| } | |||
| } | |||
| // setStagingItem({...stagingItem,}) | |||
| console.log((stagingItem.control_strategy)); | |||
| onParentChange({...stagingItem,control_strategy:JSON.stringify(stagingItem.control_strategy),in_parameters:JSON.stringify(stagingItem.in_parameters),out_parameters:JSON.stringify(stagingItem.out_parameters),...form.getFieldsValue()}) | |||
| // onParentChange({...stagingItem,...form.getFieldsValue()}) | |||
| } | |||
| // setStagingItem({...stagingItem,}) | |||
| console.log(stagingItem.control_strategy); | |||
| onParentChange({ | |||
| ...stagingItem, | |||
| control_strategy: JSON.stringify(stagingItem.control_strategy), | |||
| in_parameters: JSON.stringify(stagingItem.in_parameters), | |||
| out_parameters: JSON.stringify(stagingItem.out_parameters), | |||
| ...form.getFieldsValue(), | |||
| }); | |||
| // onParentChange({...stagingItem,...form.getFieldsValue()}) | |||
| } | |||
| const onClose=()=> { | |||
| setOpen(false); | |||
| }; | |||
| const onFinish = (values) => { | |||
| console.log('Success:', values); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useImperativeHandle(ref, () => ({ | |||
| showDrawer (e,id) { | |||
| setMessageItem('') | |||
| if(e.item&&e.item.getModel().component_id){ | |||
| let params={ | |||
| id:Number(id), | |||
| component_id:e.item&&e.item.getModel().component_id | |||
| } | |||
| getQueryByExperimentLog(params).then(ret=>{ | |||
| console.log(ret); | |||
| getNodeResult({id,node_id:e.item.getModel().id}).then(res=>{ | |||
| setResultObj(res.data) | |||
| let msg=ret.msg.replace(/\n/g,"</br>") | |||
| let newMsg=msg.replace(/\r/g,"</br>") | |||
| setMessageItem(newMsg) | |||
| form.resetFields(); | |||
| form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| setOpen(true); | |||
| }) | |||
| }) | |||
| }; | |||
| const onClose = () => { | |||
| setOpen(false); | |||
| }; | |||
| const onFinish = (values) => { | |||
| console.log('Success:', values); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useImperativeHandle(ref, () => ({ | |||
| showDrawer(e, id, message) { | |||
| setLogList([]); | |||
| if (e.item && e.item.getModel().component_id) { | |||
| const model = e.item.getModel() || {}; | |||
| const start_time = moment(model.experimentStartTime).valueOf() * 1.0e6; | |||
| const params = { | |||
| task_id: model.id, | |||
| component_id: model.component_id, | |||
| name: message.argo_ins_name, | |||
| namespace: message.argo_ins_ns, | |||
| start_time: start_time, | |||
| }; | |||
| getQueryByExperimentLog(params).then((ret) => { | |||
| const { log_type, pods, log_detail } = ret.data; | |||
| if (log_type === 'normal') { | |||
| const list = [ | |||
| { | |||
| ...log_detail, | |||
| log_type, | |||
| }, | |||
| ]; | |||
| setLogList(list); | |||
| } else if (log_type === 'resource') { | |||
| const list = pods.map((v) => ({ | |||
| log_type, | |||
| pod_name: v, | |||
| log_content: '', | |||
| start_time, | |||
| })); | |||
| setLogList(list); | |||
| } | |||
| else{ | |||
| getNodeResult({ id, node_id: e.item.getModel().id }).then((res) => { | |||
| setResultObj(res.data); | |||
| form.resetFields(); | |||
| form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | |||
| form.setFieldsValue({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setStagingItem({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setOpen(true); | |||
| } | |||
| // console.log(e.item.getModel().in_parameters); | |||
| }, | |||
| })); | |||
| }); | |||
| }); | |||
| } else { | |||
| form.resetFields(); | |||
| form.setFieldsValue({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setStagingItem({ | |||
| ...e.item.getModel(), | |||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||
| }); | |||
| setOpen(true); | |||
| } | |||
| // console.log(e.item.getModel().in_parameters); | |||
| }, | |||
| })); | |||
| return ( | |||
| <> | |||
| <Drawer title="任务执行详情" placement="right" closeIcon={false} onClose={onClose} afterOpenChange={afterOpenChange} open={open}> | |||
| <div className={Styles.detailBox}>任务名称:{stagingItem.label}</div> | |||
| <div className={Styles.detailBox} >执行状态: | |||
| <div style={{width:'8px', | |||
| height:'8px', | |||
| borderRadius:'50%', | |||
| marginRight:'6px', | |||
| backgroundColor:statusColorObj[stagingItem.experimentStatus] | |||
| }}></div> | |||
| <span style={{color:statusColorObj[stagingItem.experimentStatus]}}>{statusObj[stagingItem.experimentStatus]}</span></div> | |||
| <div className={Styles.detailBox}>启动时间:{momnet(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')}</div> | |||
| <div className={Styles.detailBox}>耗时:{stagingItem.experimentEndTime?timers(new Date(stagingItem.experimentEndTime).getTime()-new Date(stagingItem.experimentStartTime).getTime()):timers(new Date().getTime()-new Date(stagingItem.experimentStartTime).getTime())}</div> | |||
| <Tabs | |||
| defaultActiveKey="1" | |||
| items={items}/> | |||
| <Drawer | |||
| title="任务执行详情" | |||
| placement="right" | |||
| closeIcon={false} | |||
| onClose={onClose} | |||
| afterOpenChange={afterOpenChange} | |||
| open={open} | |||
| width={600} | |||
| destroyOnClose={true} | |||
| > | |||
| <div className={Styles.detailBox}>任务名称:{stagingItem.label}</div> | |||
| <div className={Styles.detailBox}> | |||
| 执行状态: | |||
| <div | |||
| style={{ | |||
| width: '8px', | |||
| height: '8px', | |||
| borderRadius: '50%', | |||
| marginRight: '6px', | |||
| backgroundColor: statusColorObj[stagingItem.experimentStatus], | |||
| }} | |||
| ></div> | |||
| <span style={{ color: statusColorObj[stagingItem.experimentStatus] }}> | |||
| {statusObj[stagingItem.experimentStatus]} | |||
| </span> | |||
| </div> | |||
| <div className={Styles.detailBox}> | |||
| 启动时间:{moment(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')} | |||
| </div> | |||
| <div className={Styles.detailBox}> | |||
| 耗时: | |||
| {stagingItem.experimentEndTime | |||
| ? elapsedTime( | |||
| new Date(stagingItem.experimentStartTime), | |||
| new Date(stagingItem.experimentEndTime), | |||
| ) | |||
| : elapsedTime(new Date(stagingItem.experimentStartTime), new Date())} | |||
| </div> | |||
| <Tabs defaultActiveKey="1" items={items} /> | |||
| </Drawer> | |||
| </> | |||
| ); | |||
| @@ -1,484 +1,445 @@ | |||
| import React ,{ useState,useEffect,useRef }from 'react'; | |||
| import { Space, Table, Tag,Button,Modal, Form, Input ,message, Select,} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, EditOutlined ,PlayCircleOutlined,DeleteOutlined,FieldTimeOutlined} from '@ant-design/icons'; | |||
| import {getWorkflow,} from '@/services/pipeline/index.js' | |||
| import {getExperiment,runExperiments,getExperimentById,postExperiment,putExperiment,getQueryByExperimentId,deleteExperimentById,deleteQueryByExperimentInsId,putQueryByExperimentInsId} from '@/services/experiment/index.js' | |||
| import Styles from './index.less' | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import momnet from 'moment' | |||
| const { TextArea } = Input; | |||
| import { | |||
| deleteExperimentById, | |||
| deleteQueryByExperimentInsId, | |||
| getExperiment, | |||
| getExperimentById, | |||
| getQueryByExperimentId, | |||
| postExperiment, | |||
| putExperiment, | |||
| putQueryByExperimentInsId, | |||
| runExperiments, | |||
| } from '@/services/experiment/index.js'; | |||
| import { getWorkflow } from '@/services/pipeline/index.js'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { | |||
| DeleteOutlined, | |||
| EditOutlined, | |||
| FieldTimeOutlined, | |||
| PlayCircleOutlined, | |||
| PlusCircleOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { Button, Modal, Space, Table, message } from 'antd'; | |||
| import momnet from 'moment'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import AddExperimentModal from './experimentText/addExperimentModal'; | |||
| import Styles from './index.less'; | |||
| import { experimentStatusInfo } from './status'; | |||
| const Experiment = React.FC = () => { | |||
| const [form] = Form.useForm(); | |||
| const navgite=useNavigate(); | |||
| const statusObj={ | |||
| "Running":'运行中', | |||
| "Succeeded":'成功', | |||
| "Pending":'等待中', | |||
| "Failed":'失败', | |||
| "Error":'错误', | |||
| "Terminated":'终止', | |||
| "Skipped":'未执行', | |||
| "Omitted":'未执行', | |||
| } | |||
| const statusColorObj={ | |||
| "Running":'#165bff', | |||
| "Succeeded":'#63a728', | |||
| "Pending":'#f981eb', | |||
| "Failed":'#c73131', | |||
| "Error":'#c73131', | |||
| "Terminated":'#8a8a8a', | |||
| "Skipped":'#8a8a8a', | |||
| "Omitted":'#8a8a8ae', | |||
| } | |||
| const statusImgObj={ | |||
| 'Running':'/assets/images/running-icon.png', | |||
| 'Succeeded':'/assets/images/success-icon.png', | |||
| 'Pending':'/assets/images/pending-icon.png', | |||
| 'Failed':'/assets/images/fail-icon.png', | |||
| 'Terminated':'/assets/images/omitted-icon.png', | |||
| 'Skipped':'/assets/images/omitted-icon.png', | |||
| 'Omitted':'/assets/images/omitted-icon.png', | |||
| } | |||
| const [experimentList, setExperimentList] = useState([]); | |||
| const [workflowList, setWorkflowList] = useState([]); | |||
| const [queryFlow,setQueryFlow]=useState({ | |||
| offset:1, | |||
| page:0, | |||
| size:10000, | |||
| name:null | |||
| }); | |||
| const [disableFlag,setDisableFlag]=useState(false) | |||
| const timers=(time)=>{ | |||
| let timer=new Date(time) | |||
| let hours = timer.getHours(); //转换成时 | |||
| let minutes = timer.getMinutes(); //转换成分 | |||
| let secend = timer.getSeconds(); //转换成秒 | |||
| let str = `${minutes}分${secend}秒`; | |||
| return str; | |||
| } | |||
| const [formId,setFormId]=useState(null) | |||
| const [experimentInList,setExperimentInList]=useState([]) | |||
| const [expandedRowKeys,setExpandedRowKeys]=useState(null) | |||
| const [total, setTotal] = useState(0); | |||
| const [dialogTitle, setDialogTitle] = useState('新建实验'); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const getWorkflowList=()=>{ | |||
| getWorkflow(queryFlow).then(ret=>{ | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setWorkflowList(ret.data.content) | |||
| } | |||
| }) | |||
| function Experiment() { | |||
| const navgite = useNavigate(); | |||
| const [experimentList, setExperimentList] = useState([]); | |||
| const [workflowList, setWorkflowList] = useState([]); | |||
| const [queryFlow, setQueryFlow] = useState({ | |||
| offset: 1, | |||
| page: 0, | |||
| size: 10000, | |||
| name: null, | |||
| }); | |||
| const [experimentId, setExperimentId] = useState(null); | |||
| const [experimentInList, setExperimentInList] = useState([]); | |||
| const [expandedRowKeys, setExpandedRowKeys] = useState(null); | |||
| const [total, setTotal] = useState(0); | |||
| const [isAdd, setIsAdd] = useState(true); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [addFormData, setAddFormData] = useState({}); | |||
| useEffect(() => { | |||
| getList(); | |||
| getWorkflowList(); | |||
| }, []); | |||
| // 获取实验列表 | |||
| const getList = async () => { | |||
| const params = { | |||
| offset: 0, | |||
| page: pageOption.current.page - 1, | |||
| size: pageOption.current.size, | |||
| }; | |||
| const [res, _] = await to(getExperiment(params)); | |||
| if (res && res.data && Array.isArray(res.data.content)) { | |||
| setExperimentList( | |||
| res.data.content.map((item) => { | |||
| return { ...item, key: item.id }; | |||
| }), | |||
| ); | |||
| setTotal(res.data.totalElements); | |||
| } | |||
| const getQueryByExperiment=(val)=>{ | |||
| getQueryByExperimentId(val).then(ret=>{ | |||
| setExpandedRowKeys(val) | |||
| if(ret.code==200&&ret.data&&ret.data.length>0){ | |||
| setExperimentInList(ret.data) | |||
| getList() | |||
| } | |||
| else{ | |||
| setExperimentInList([]) | |||
| getList() | |||
| } | |||
| }) | |||
| }; | |||
| // 获取流水线列表 | |||
| const getWorkflowList = async () => { | |||
| const [res, _] = await to(getWorkflow(queryFlow)); | |||
| if (res && res.data && res.data.content) { | |||
| setWorkflowList(res.data.content); | |||
| } | |||
| const expandChange=(e,record)=>{ | |||
| if(record.id==expandedRowKeys){ | |||
| setExpandedRowKeys(null) | |||
| } | |||
| else{ | |||
| getQueryByExperiment(record.id) | |||
| }; | |||
| const getQueryByExperiment = (val) => { | |||
| getQueryByExperimentId(val).then((ret) => { | |||
| setExpandedRowKeys(val); | |||
| if (ret.code === 200 && ret.data && ret.data.length > 0) { | |||
| setExperimentInList(ret.data); | |||
| getList(); | |||
| } else { | |||
| setExperimentInList([]); | |||
| getList(); | |||
| } | |||
| }); | |||
| }; | |||
| const expandChange = (e, record) => { | |||
| if (record.id === expandedRowKeys) { | |||
| setExpandedRowKeys(null); | |||
| } else { | |||
| getQueryByExperiment(record.id); | |||
| } | |||
| const showModal = () => { | |||
| setDialogTitle('新建实验') | |||
| setDisableFlag(false) | |||
| console.log(workflowList); | |||
| }; | |||
| // 创建实验 | |||
| const createExperiment = () => { | |||
| setIsAdd(true); | |||
| setAddFormData({}); | |||
| setExperimentId(null); | |||
| setIsModalOpen(true); | |||
| }; | |||
| // 编辑实验 | |||
| const editExperiment = (id) => { | |||
| getExperimentById(id).then((res) => { | |||
| setAddFormData({ | |||
| ...res.data, | |||
| }); | |||
| setExperimentId(res.data.id); | |||
| setIsAdd(false); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const editTable=(id)=>{ | |||
| getExperimentById(id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| form.setFieldsValue({...ret.data}) | |||
| setDisableFlag(true) | |||
| setFormId(ret.data.id) | |||
| setDialogTitle('编辑实验') | |||
| getWorkflowList() | |||
| setIsModalOpen(true) | |||
| } | |||
| }) | |||
| // navgite({pathname:`/pipeline/pytorchtext/${record.id}/${record.name}` }); | |||
| }); | |||
| }; | |||
| // 创建或编辑实验取消 | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const routeToEdit = (e, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: `/pipeline/pytorchtext/${record.workflow_id}/${record.workflow_name}` }); | |||
| }; | |||
| // 创建或者编辑实验接口请求 | |||
| const handleAddExperiment = async (values) => { | |||
| const workflow_id = values['workflow_id']; | |||
| let global_param = undefined; | |||
| const pipeline = workflowList.find((v) => v.id === workflow_id); | |||
| if (pipeline && pipeline.global_param) { | |||
| const globalParamList = [...pipeline.global_param]; | |||
| for (const item of globalParamList) { | |||
| item.param_value = values[item.param_name]; | |||
| values[item.param_name] = undefined; | |||
| } | |||
| global_param = JSON.stringify(globalParamList); | |||
| } | |||
| const handleOk = () => { | |||
| console.log(1111); | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| const params = { | |||
| ...values, | |||
| global_param, | |||
| }; | |||
| const routeToEdit=(e,record)=>{ | |||
| e.stopPropagation() | |||
| navgite({pathname:`/pipeline/pytorchtext/${record.workflow_id}/${record.workflow_name}` }); | |||
| } | |||
| const onFinish = (values) => { | |||
| console.log(values,formId); | |||
| if(!formId){ | |||
| postExperiment(values).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('新建实验成功') | |||
| setIsModalOpen(false) | |||
| getList() | |||
| } | |||
| else{ | |||
| message.error('新建实验失败') | |||
| } | |||
| }) | |||
| if (!experimentId) { | |||
| const [res, _] = await to(postExperiment(params)); | |||
| if (res) { | |||
| message.success('新建实验成功'); | |||
| setIsModalOpen(false); | |||
| getList(); | |||
| } | |||
| else{ | |||
| putExperiment({...values,id:formId}).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('编辑实验成功') | |||
| setIsModalOpen(false) | |||
| getList() | |||
| } | |||
| else{ | |||
| message.error('编辑实验失败') | |||
| } | |||
| }) | |||
| } else { | |||
| const params = { ...values, id: experimentId }; | |||
| const [res, _] = await to(putExperiment(params)); | |||
| if (res) { | |||
| message.success('编辑实验成功'); | |||
| setIsModalOpen(false); | |||
| getList(); | |||
| } | |||
| setFormId(null) | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| const pageOption = useRef({page: 1,size: 10}) | |||
| const paginationProps = { | |||
| showQuickJumper: true, | |||
| showTotal: () => `共${total}条`, | |||
| total: total, | |||
| page: pageOption.current.page, | |||
| size: pageOption.current.size, | |||
| onChange: (current, size) => paginationChange(current, size) | |||
| } | |||
| // 当前页面切换 | |||
| const paginationChange = async (current, size) => { | |||
| console.log('page', current, size) | |||
| pageOption.current={ | |||
| page:current, | |||
| size:size | |||
| }; | |||
| const pageOption = useRef({ page: 1, size: 10 }); | |||
| const paginationProps = { | |||
| showQuickJumper: true, | |||
| showTotal: () => `共${total}条`, | |||
| total: total, | |||
| page: pageOption.current.page, | |||
| size: pageOption.current.size, | |||
| onChange: (current, size) => paginationChange(current, size), | |||
| }; | |||
| // 当前页面切换 | |||
| const paginationChange = async (current, size) => { | |||
| console.log('page', current, size); | |||
| pageOption.current = { | |||
| page: current, | |||
| size: size, | |||
| }; | |||
| getList(); | |||
| }; | |||
| const runExperiment = (id) => { | |||
| runExperiments(id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('运行成功'); | |||
| getQueryByExperiment(id); | |||
| } else { | |||
| message.error('运行失败'); | |||
| } | |||
| getList() | |||
| } | |||
| const runExperiment=(id)=>{ | |||
| runExperiments(id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('运行成功') | |||
| getQueryByExperiment(id) | |||
| } | |||
| else{ | |||
| message.error('运行失败') | |||
| } | |||
| }) | |||
| } | |||
| const routerToText=(e,item,record)=>{ | |||
| e.stopPropagation() | |||
| navgite({pathname:`/experiment/pytorchtext/${record.workflow_id}/${item.id}` }); | |||
| } | |||
| const getList=()=>{ | |||
| let params={ | |||
| offset:0, | |||
| page:pageOption.current.page-1, | |||
| size:pageOption.current.size | |||
| } | |||
| console.log(params,pageOption); | |||
| getExperiment(params).then(ret=>{ | |||
| if(ret.code==200){ | |||
| setExperimentList(ret.data.content.map(item=>{return{...item,key:item.id}})) | |||
| setTotal(ret.data.totalElements) | |||
| } | |||
| console.log(experimentList,total); | |||
| }) | |||
| } | |||
| useEffect(()=>{ | |||
| getList() | |||
| getWorkflowList() | |||
| // const timeOut= setInterval(() => { | |||
| // getList() | |||
| // }, 2000); | |||
| return () => { | |||
| // timeOut&&clearInterval(timeOut) | |||
| // console.log('will unmount'); | |||
| } | |||
| },[]) | |||
| const columns = [ | |||
| // { | |||
| // title: '序号', | |||
| // dataIndex: 'index', | |||
| // key: 'index', | |||
| // width: 60, | |||
| // render(text, record, index) { | |||
| // return ( | |||
| // <span>{(pageOption.current.page - 1) * 10 + index + 1}</span> | |||
| // ) | |||
| // } | |||
| // // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | |||
| // }, | |||
| { | |||
| title: '实验名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: (text) => <div >{text}</div>, | |||
| }, | |||
| { | |||
| title: '关联流水线名称', | |||
| dataIndex: 'workflow_name', | |||
| key: 'workflow_name', | |||
| render: (text,record) => <a onClick={(e)=>routeToEdit(e,record)}>{text}</a>, | |||
| }, | |||
| { | |||
| title: '实验描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| }, | |||
| { | |||
| title: '最近五次运行状态', | |||
| dataIndex: 'status_list', | |||
| key: 'status_list', | |||
| render: (text) => { | |||
| }); | |||
| }; | |||
| const routerToText = (e, item, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: `/experiment/pytorchtext/${record.workflow_id}/${item.id}` }); | |||
| }; | |||
| let newText=text&&text.replace(/\s+/g,'').split(',') | |||
| console.log(newText); | |||
| return <>{ newText&&newText.length>0?newText.map((item,index)=>{console.log(item,statusImgObj[item]); return <img style={{width:'17px',marginRight:'6px'}} key={index} src={statusImgObj[item]} />}):null}</> | |||
| } | |||
| }, | |||
| { | |||
| title: '操作', | |||
| key: 'action', | |||
| width:300, | |||
| render: (_, record) => ( | |||
| <Space size="small"> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon = {<PlayCircleOutlined />} | |||
| onClick={() => { | |||
| runExperiment(record.id) | |||
| }} | |||
| > | |||
| 运行 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon = {< EditOutlined />} | |||
| onClick={() => { | |||
| editTable(record.id) | |||
| }} | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{color:'#f98e1b'}} | |||
| icon = {< DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实验吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| const columns = [ | |||
| { | |||
| title: '实验名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: (text) => <div>{text}</div>, | |||
| }, | |||
| { | |||
| title: '关联流水线名称', | |||
| dataIndex: 'workflow_name', | |||
| key: 'workflow_name', | |||
| render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | |||
| }, | |||
| { | |||
| title: '实验描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| }, | |||
| { | |||
| title: '最近五次运行状态', | |||
| dataIndex: 'status_list', | |||
| key: 'status_list', | |||
| render: (text) => { | |||
| let newText = text && text.replace(/\s+/g, '').split(','); | |||
| console.log(newText); | |||
| return ( | |||
| <> | |||
| {newText && newText.length > 0 | |||
| ? newText.map((item, index) => { | |||
| return ( | |||
| <img | |||
| style={{ width: '17px', marginRight: '6px' }} | |||
| key={index} | |||
| src={experimentStatusInfo[item].icon} | |||
| /> | |||
| ); | |||
| }) | |||
| : null} | |||
| </> | |||
| ); | |||
| }, | |||
| }, | |||
| onOk: () => { | |||
| console.log(record); | |||
| deleteExperimentById(record.id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('删除成功') | |||
| getList() | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| }); | |||
| // if (success) { | |||
| // if (actionRef.current) { | |||
| // actionRef.current.reload(); | |||
| // } | |||
| // } | |||
| }, | |||
| }); | |||
| }} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| return (<div> | |||
| {/* <div > | |||
| <Button type="primary" onClick={showModal} icon = {< PlusOutlined />}> | |||
| { | |||
| title: '操作', | |||
| key: 'action', | |||
| width: 300, | |||
| render: (_, record) => ( | |||
| <Space size="small"> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="run" | |||
| icon={<PlayCircleOutlined />} | |||
| onClick={() => { | |||
| runExperiment(record.id); | |||
| }} | |||
| > | |||
| 运行 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon={<EditOutlined />} | |||
| onClick={() => { | |||
| editExperiment(record.id); | |||
| }} | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{ color: '#f98e1b' }} | |||
| icon={<DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实验吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| console.log(record); | |||
| deleteExperimentById(record.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('删除成功'); | |||
| getList(); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| // if (success) { | |||
| // if (actionRef.current) { | |||
| // actionRef.current.reload(); | |||
| // } | |||
| // } | |||
| }, | |||
| }); | |||
| }} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div> | |||
| {/* <div > | |||
| <Button type="primary" onClick={createExperiment} icon = {< PlusOutlined />}> | |||
| 新建实验 | |||
| </Button> | |||
| </div> */} | |||
| <div className={Styles.pipelineTopBox}> | |||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||
| 新建实验 | |||
| </Button> | |||
| </div> | |||
| <Table columns={columns} dataSource={experimentList} pagination={paginationProps} expandable={{ | |||
| expandedRowRender: (record) => ( | |||
| <div> | |||
| {experimentInList&&experimentInList.length>0?<div className={Styles.tableExpandBox} style={{paddingBottom:'16px'}}> | |||
| <div style={{width:'50px'}}>序号</div> | |||
| <div style={{width:'200px'}}>状态</div> | |||
| <div style={{width:'300px'}}>运行时长</div> | |||
| <div style={{width:'300px'}}>开始时间</div> | |||
| <div style={{width:'200px'}}>操作</div> | |||
| </div>:''} | |||
| {experimentInList&&experimentInList.length>0?experimentInList.map((item,index)=>( | |||
| <div className={Styles.tableExpandBox} style={{border:'1px solid #eaeaea',backgroundColor:'#fff',height:'45px'}}> | |||
| <a style={{width:'50px'}} onClick={(e)=>routerToText(e,item,record)}>{index+1}</a> | |||
| <div className={Styles.statusBox} style={{width:'200px'}}><img style={{width:'17px',marginRight:'7px'}} src={statusImgObj[item.status]}/> <span style={{color:statusColorObj[item.status]}} className={Styles.statusIcon}>{statusObj[item.status]}</span></div> | |||
| <div style={{width:'300px'}}>{item.finish_time?timers(new Date(item.finish_time).getTime()-new Date(item.create_time).getTime()):timers(new Date().getTime()-new Date(item.create_time).getTime())}</div> | |||
| <div style={{width:'300px'}}>{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||
| <div style={{width:'200px'}}> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="batchRemove" | |||
| disabled={item.status=='Succeeded'||item.status=='Failed'||item.status=='Terminated'} | |||
| icon = {<FieldTimeOutlined />} | |||
| onClick={async () => { | |||
| putQueryByExperimentInsId(item.id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('终止成功') | |||
| getQueryByExperiment(record.id) | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| }) | |||
| }} | |||
| > | |||
| 终止 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{color:'#f98e1b'}} | |||
| disabled={item.status=='Running'||item.status=='Pending'} | |||
| icon = {< DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实例吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| deleteQueryByExperimentInsId(item.id).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('删除成功') | |||
| getQueryByExperiment(record.id) | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| }} | |||
| <div className={Styles.pipelineTopBox}> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| onClick={createExperiment} | |||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 删除 | |||
| 新建实验 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| )):''} | |||
| </div> | |||
| </div> | |||
| <Table | |||
| columns={columns} | |||
| dataSource={experimentList} | |||
| pagination={paginationProps} | |||
| expandable={{ | |||
| expandedRowRender: (record) => ( | |||
| <div> | |||
| {experimentInList && experimentInList.length > 0 ? ( | |||
| <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | |||
| <div style={{ width: '50px' }}>序号</div> | |||
| <div style={{ width: '200px' }}>状态</div> | |||
| <div style={{ width: '300px' }}>运行时长</div> | |||
| <div style={{ width: '300px' }}>开始时间</div> | |||
| <div style={{ width: '200px' }}>操作</div> | |||
| </div> | |||
| ) : ( | |||
| '' | |||
| )} | |||
| ), | |||
| onExpand:(e,a)=>{expandChange(e,a)}, | |||
| expandedRowKeys:[expandedRowKeys], | |||
| rowExpandable: (record) =>true, | |||
| }}/> | |||
| <Modal className={Styles.modal} title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| initialValues={{ | |||
| remember: true, | |||
| }} | |||
| onFinish={onFinish} | |||
| onFinishFailed={onFinishFailed} | |||
| autoComplete="off" | |||
| > | |||
| <Form.Item | |||
| label="实验名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| > | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="实验描述" | |||
| name="description" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| > | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="选择流水线" | |||
| name="workflow_id" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| > | |||
| <Select disabled={disableFlag}> | |||
| {workflowList&&workflowList.length>0?workflowList.map(item=>{return <Select.Option value={item.id}>{item.name}</Select.Option>}):''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </Select> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </div>)}; | |||
| export default Experiment; | |||
| {experimentInList && experimentInList.length > 0 | |||
| ? experimentInList.map((item, index) => ( | |||
| <div | |||
| key={item.id} | |||
| className={Styles.tableExpandBox} | |||
| style={{ | |||
| border: '1px solid #eaeaea', | |||
| backgroundColor: '#fff', | |||
| height: '45px', | |||
| }} | |||
| > | |||
| <a style={{ width: '50px' }} onClick={(e) => routerToText(e, item, record)}> | |||
| {index + 1} | |||
| </a> | |||
| <div className={Styles.statusBox} style={{ width: '200px' }}> | |||
| <img | |||
| style={{ width: '17px', marginRight: '7px' }} | |||
| src={experimentStatusInfo[item.status]?.icon} | |||
| />{' '} | |||
| <span | |||
| style={{ color: experimentStatusInfo[item.status]?.color }} | |||
| className={Styles.statusIcon} | |||
| > | |||
| {experimentStatusInfo[item.status]?.label} | |||
| </span> | |||
| </div> | |||
| <div style={{ width: '300px' }}> | |||
| {item.finish_time | |||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||
| : elapsedTime(new Date(item.create_time), new Date())} | |||
| </div> | |||
| <div style={{ width: '300px' }}> | |||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||
| </div> | |||
| <div style={{ width: '200px' }}> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="stop" | |||
| disabled={ | |||
| item.status === 'Succeeded' || | |||
| item.status === 'Failed' || | |||
| item.status === 'Terminated' | |||
| } | |||
| icon={<FieldTimeOutlined />} | |||
| onClick={async () => { | |||
| putQueryByExperimentInsId(item.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('终止成功'); | |||
| getQueryByExperiment(record.id); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }} | |||
| > | |||
| 终止 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| style={{ color: '#f98e1b' }} | |||
| disabled={item.status === 'Running' || item.status === 'Pending'} | |||
| icon={<DeleteOutlined />} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除该条实例吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| deleteQueryByExperimentInsId(item.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('删除成功'); | |||
| getQueryByExperiment(record.id); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| }} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| )) | |||
| : ''} | |||
| </div> | |||
| ), | |||
| onExpand: (e, a) => { | |||
| expandChange(e, a); | |||
| }, | |||
| expandedRowKeys: [expandedRowKeys], | |||
| rowExpandable: (record) => true, | |||
| }} | |||
| /> | |||
| <AddExperimentModal | |||
| isAdd={isAdd} | |||
| open={isModalOpen} | |||
| initialValues={addFormData} | |||
| onCancel={handleCancel} | |||
| onFinish={handleAddExperiment} | |||
| workflowList={workflowList} | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default Experiment; | |||
| @@ -1,102 +1,59 @@ | |||
| .experimentTopBox{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| padding-right: 30px; | |||
| width: 100%; | |||
| height: 49px; | |||
| background-size: 100% 100%; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| .experimentTopBox { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| width: 100%; | |||
| height: 49px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-size: 100% 100%; | |||
| } | |||
| .pipelineTopBox{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| padding-right: 30px; | |||
| width: 100%; | |||
| height: 49px; | |||
| background-size: 100% 100%; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| margin-bottom: 10px; | |||
| .pipelineTopBox { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| width: 100%; | |||
| height: 49px; | |||
| margin-bottom: 10px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-size: 100% 100%; | |||
| } | |||
| .plusButton{ | |||
| background:rgba(22, 100, 255, 0.06); | |||
| border:1px solid; | |||
| border-color:rgba(22, 100, 255, 0.11); | |||
| border-radius:4px; | |||
| color:#1d1d20; | |||
| font-size:14px; | |||
| font-family: 'Alibaba'; | |||
| .plusButton { | |||
| color: #1d1d20; | |||
| font-size: 14px; | |||
| font-family: 'Alibaba'; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border: 1px solid; | |||
| border-color: rgba(22, 100, 255, 0.11); | |||
| border-radius: 4px; | |||
| } | |||
| .plusButton:hover{ | |||
| background:rgba(22, 100, 255, 0.06)!important; | |||
| border:1px solid!important; | |||
| border-color:rgba(22, 100, 255, 0.11)!important; | |||
| color:#1d1d20!important; | |||
| .plusButton:hover { | |||
| color: #1d1d20 !important; | |||
| background: rgba(22, 100, 255, 0.06) !important; | |||
| border: 1px solid !important; | |||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||
| } | |||
| .tableExpandBox{ | |||
| width: 100%; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| color:#1d1d20; | |||
| font-size:15px; | |||
| padding: 0 65px 0 40px; | |||
| .tableExpandBox { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| width: 100%; | |||
| padding: 0 65px 0 40px; | |||
| color: #1d1d20; | |||
| font-size: 15px; | |||
| } | |||
| .statusBox{ | |||
| display: flex; | |||
| align-items: center; | |||
| .statusBox { | |||
| display: flex; | |||
| align-items: center; | |||
| .statusIcon{ | |||
| visibility: hidden; | |||
| transition: all 0.2s; | |||
| } | |||
| .statusIcon { | |||
| visibility: hidden; | |||
| transition: all 0.2s; | |||
| } | |||
| } | |||
| .statusBox:hover .statusIcon{ | |||
| visibility: visible; | |||
| .statusBox:hover .statusIcon { | |||
| visibility: visible; | |||
| } | |||
| .modal { | |||
| :global { | |||
| .ant-modal-content { | |||
| background:linear-gradient(180deg,#cfdfff 0%,#d4e2ff 9.77%,#ffffff 40%,#ffffff 100%); | |||
| border-radius:21px; | |||
| padding: 20px 67px; | |||
| width: 825px; | |||
| } | |||
| .ant-modal-header{ | |||
| background-color: transparent; | |||
| margin: 20px 0; | |||
| } | |||
| .ant-input{ | |||
| border-color:#e6e6e6; | |||
| height: 40px; | |||
| } | |||
| .ant-select-single{ | |||
| height: 40px; | |||
| } | |||
| .ant-form-item .ant-form-item-label >label{ | |||
| color:rgba(29, 29, 32, 0.8); | |||
| } | |||
| .ant-modal-footer{ | |||
| margin: 40px 0 30px 0; | |||
| display: flex; | |||
| justify-content: center; | |||
| } | |||
| .ant-btn{ | |||
| width:110px; | |||
| height:40px; | |||
| font-size:18px; | |||
| background:rgba(22, 100, 255, 0.06); | |||
| border-radius:10px; | |||
| border-color: transparent; | |||
| } | |||
| .ant-btn-primary{ | |||
| background:#1664ff; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| export interface StatusInfo { | |||
| label: string; | |||
| color: string; | |||
| icon: string; | |||
| } | |||
| export enum ExperimentStatus { | |||
| Running = 'Running', | |||
| Succeeded = 'Succeeded', | |||
| Pending = 'Pending', | |||
| Failed = 'Failed', | |||
| Error = 'Error', | |||
| Terminated = 'Terminated', | |||
| Skipped = 'Skipped', | |||
| Omitted = 'Omitted', | |||
| } | |||
| export const experimentStatusInfo: Record<string, StatusInfo | undefined> = { | |||
| Running: { | |||
| label: '运行中', | |||
| color: '#165bff', | |||
| icon: '/assets/images/running-icon.png', | |||
| }, | |||
| Succeeded: { | |||
| label: '成功', | |||
| color: '#63a728', | |||
| icon: '/assets/images/success-icon.png', | |||
| }, | |||
| Pending: { | |||
| label: '等待中', | |||
| color: '#f981eb', | |||
| icon: '/assets/images/pending-icon.png', | |||
| }, | |||
| Failed: { | |||
| label: '失败', | |||
| color: '#c73131', | |||
| icon: '/assets/images/fail-icon.png', | |||
| }, | |||
| Error: { | |||
| label: '错误', | |||
| color: '#c73131', | |||
| icon: '/assets/images/fail-icon.png', | |||
| }, | |||
| Terminated: { | |||
| label: '终止', | |||
| color: '#8a8a8a', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| Skipped: { | |||
| label: '未执行', | |||
| color: '#8a8a8a', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| Omitted: { | |||
| label: '未执行', | |||
| color: '#8a8a8ae', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| }; | |||
| @@ -1,149 +1,151 @@ | |||
| import React ,{useEffect,useState,useRef}from 'react'; | |||
| import Styles from './index.less' | |||
| import { Input, Space ,Button,Tabs,Pagination,Modal, Form,message, Radio,Select,Table,Upload} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, DeleteOutlined,UploadOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||
| import {getDatasetList,getModelById,getModelVersionsById,getModelVersionIdList,deleteModelVersion,addModelsVersionDetail} from '@/services/dataset/index.js' | |||
| import { useParams } from 'react-router-dom' | |||
| import {downLoadZip} from '@/utils/downloadfile' | |||
| const { Search } = Input; | |||
| import { getAccessToken } from '@/access'; | |||
| import { | |||
| addModelsVersionDetail, | |||
| deleteModelVersion, | |||
| getModelById, | |||
| getModelVersionIdList, | |||
| getModelVersionsById, | |||
| } from '@/services/dataset/index.js'; | |||
| import { downLoadZip } from '@/utils/downloadfile'; | |||
| import { DeleteOutlined, PlusCircleOutlined, UploadOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | |||
| import moment from 'moment'; | |||
| import { getAccessToken } from '@/access'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useParams } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| const { Search } = Input; | |||
| const { TabPane } = Tabs; | |||
| const Dataset= React.FC = () => { | |||
| const Dataset = () => { | |||
| const props = { | |||
| action: '/api/mmp/dataset/upload', | |||
| // headers: { | |||
| // 'X-Requested-With': null | |||
| // }, | |||
| headers: { | |||
| Authorization:getAccessToken(), | |||
| 'X-Requested-With': null | |||
| Authorization: getAccessToken(), | |||
| 'X-Requested-With': null, | |||
| }, | |||
| onChange({ file, fileList }) { | |||
| if (file.status !== 'uploading') { | |||
| console.log(file, fileList); | |||
| setFormList(fileList.map(item=>{ | |||
| return { | |||
| ...form.getFieldsValue(), | |||
| models_id:locationParams.id, | |||
| file_name:item.response.data[0].fileName, | |||
| file_size:item.response.data[0].fileSize, | |||
| url:item.response.data[0].url, | |||
| } | |||
| })) | |||
| setFormList( | |||
| fileList.map((item) => { | |||
| return { | |||
| ...form.getFieldsValue(), | |||
| models_id: locationParams.id, | |||
| file_name: item.response.data[0].fileName, | |||
| file_size: item.response.data[0].fileSize, | |||
| url: item.response.data[0].url, | |||
| }; | |||
| }), | |||
| ); | |||
| } | |||
| }, | |||
| defaultFileList: [ | |||
| ], | |||
| defaultFileList: [], | |||
| }; | |||
| const [form] = Form.useForm(); | |||
| const [formList,setFormList]=useState([]) | |||
| const [formList, setFormList] = useState([]); | |||
| const [dialogTitle, setDialogTitle] = useState('新建版本'); | |||
| const [isModalOpen,setIsModalOpen]=useState(false) | |||
| const [datasetDetailObj,setDatasetDetailObj]=useState({ | |||
| }); | |||
| const [version,setVersion]=useState('') | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||
| const [version, setVersion] = useState(''); | |||
| const [versionList, setVersionList] = useState([]); | |||
| const locationParams =useParams () //新版本获取路由参数接口 | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| console.log(locationParams); | |||
| const [wordList, setWordList] = useState([]); | |||
| const getModelByDetail=()=>{ | |||
| getModelById(locationParams.id).then(ret=>{ | |||
| const getModelByDetail = () => { | |||
| getModelById(locationParams.id).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setDatasetDetailObj(ret.data) | |||
| if (ret.code == 200) { | |||
| setDatasetDetailObj(ret.data); | |||
| } | |||
| }) | |||
| } | |||
| const getModelVersionsList=()=>{ | |||
| getModelVersionsById(locationParams.id).then(ret=>{ | |||
| }); | |||
| }; | |||
| const getModelVersionsList = () => { | |||
| getModelVersionsById(locationParams.id).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200&&ret.data&&ret.data.length>0){ | |||
| setVersionList(ret.data.map(item=>{ | |||
| return { | |||
| 'label':item, | |||
| 'value':item | |||
| } | |||
| })) | |||
| if (ret.code == 200 && ret.data && ret.data.length > 0) { | |||
| setVersionList( | |||
| ret.data.map((item) => { | |||
| return { | |||
| label: item, | |||
| value: item, | |||
| }; | |||
| }), | |||
| ); | |||
| } | |||
| }) | |||
| } | |||
| useEffect(()=>{ | |||
| getModelByDetail() | |||
| getModelVersionsList() | |||
| return ()=>{ | |||
| } | |||
| },[]) | |||
| }); | |||
| }; | |||
| useEffect(() => { | |||
| getModelByDetail(); | |||
| getModelVersionsList(); | |||
| return () => {}; | |||
| }, []); | |||
| const showModal = () => { | |||
| form.resetFields() | |||
| form.setFieldsValue({name:datasetDetailObj.name}) | |||
| setDialogTitle('创建新版本') | |||
| form.resetFields(); | |||
| form.setFieldsValue({ name: datasetDetailObj.name }); | |||
| setDialogTitle('创建新版本'); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const deleteDataset=()=>{ | |||
| const deleteDataset = () => { | |||
| Modal.confirm({ | |||
| title: '删除', | |||
| content: '确定删除模型版本?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| deleteModelVersion({models_id:locationParams.id,version}).then(ret=>{ | |||
| if(ret.code==200){ | |||
| message.success('删除成功') | |||
| getModelVersions({version,models_id:locationParams.id}) | |||
| } | |||
| else{ | |||
| message.error(ret.msg) | |||
| } | |||
| onOk: () => { | |||
| deleteModelVersion({ models_id: locationParams.id, version }).then((ret) => { | |||
| if (ret.code == 200) { | |||
| message.success('删除成功'); | |||
| getModelVersions({ version, models_id: locationParams.id }); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| } | |||
| }; | |||
| const onFinish = () => { | |||
| addModelsVersionDetail(formList).then(ret=>{ | |||
| console.log(ret); | |||
| getModelVersionsList() | |||
| setIsModalOpen(false); | |||
| }) | |||
| }; | |||
| const getModelVersions=(params)=>{ | |||
| getModelVersionIdList(params).then(ret=>{ | |||
| addModelsVersionDetail(formList).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setWordList(ret.data) | |||
| getModelVersionsList(); | |||
| setIsModalOpen(false); | |||
| }); | |||
| }; | |||
| const getModelVersions = (params) => { | |||
| getModelVersionIdList(params).then((ret) => { | |||
| console.log(ret); | |||
| if (ret.code == 200) { | |||
| setWordList(ret.data); | |||
| } | |||
| }) | |||
| } | |||
| }); | |||
| }; | |||
| const handleExport = async () => { | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/models/downloadAllFiles`,{models_id:locationParams.id,version}) | |||
| downLoadZip(`/api/mmp/models/downloadAllFiles`, { models_id: locationParams.id, version }); | |||
| }; | |||
| const downloadAlone=(e,record)=>{ | |||
| const downloadAlone = (e, record) => { | |||
| console.log(record); | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/models/download_model/${record.id}`) | |||
| } | |||
| const handleChange=(value)=>{ | |||
| downLoadZip(`/api/mmp/models/download_model/${record.id}`); | |||
| }; | |||
| const handleChange = (value) => { | |||
| console.log(value); | |||
| if(value){ | |||
| getModelVersions({version:value,models_id:locationParams.id}) | |||
| setVersion(value) | |||
| } | |||
| else{ | |||
| setVersion(null) | |||
| if (value) { | |||
| getModelVersions({ version: value, models_id: locationParams.id }); | |||
| setVersion(value); | |||
| } else { | |||
| setVersion(null); | |||
| } | |||
| } | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| @@ -164,7 +166,7 @@ const Dataset= React.FC = () => { | |||
| title: '文件名称', | |||
| dataIndex: 'file_name', | |||
| key: 'file_name', | |||
| render: (text,record) => <a onClick={(e)=>downloadAlone(e,record)}>{text}</a>, | |||
| render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>, | |||
| }, | |||
| { | |||
| title: '版本号', | |||
| @@ -183,79 +185,107 @@ const Dataset= React.FC = () => { | |||
| render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||
| }, | |||
| ]; | |||
| const pageOption = useRef({page: 1,size: 10}) | |||
| const pageOption = useRef({ page: 1, size: 10 }); | |||
| // 当前页面切换 | |||
| const paginationChange = async (current, size) => { | |||
| console.log('page', current, size) | |||
| pageOption.current={ | |||
| page:current, | |||
| size:size | |||
| } | |||
| console.log('page', current, size); | |||
| pageOption.current = { | |||
| page: current, | |||
| size: size, | |||
| }; | |||
| // getList() | |||
| } | |||
| return (<div className={Styles.datasetBox}> | |||
| <div className={Styles.datasetIntroTopBox}> | |||
| <span style={{color:'#1d1d20',fontSize:'20px'}}>{datasetDetailObj.name}</span> | |||
| <div className={Styles.smallTagBox}> | |||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag||'...'}</div> | |||
| }; | |||
| return ( | |||
| <div className={Styles.datasetBox}> | |||
| <div className={Styles.datasetIntroTopBox}> | |||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | |||
| <div className={Styles.smallTagBox}> | |||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | |||
| <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | |||
| {/* <div className={Styles.tagItem}>English</div> */} | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetIntroCneterBox}> | |||
| <Tabs | |||
| defaultActiveKey="1" | |||
| > | |||
| <TabPane tab="模型简介" key="1"> | |||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||
| </TabPane> | |||
| <TabPane tab="模型文件/版本" key="2"> | |||
| <div className={Styles.dataListBox}> | |||
| <div>模型列表</div> | |||
| <div className={Styles.dataButtonList}> | |||
| <div style={{display: 'flex', | |||
| justifyContent: 'space-between', | |||
| alignItems: 'center'}}> | |||
| <span style={{marginRight:'10px'}}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ | |||
| width: 160, | |||
| }} | |||
| allowClear | |||
| onChange={handleChange} | |||
| options={versionList} | |||
| /> | |||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||
| 创建新版本 | |||
| </Button> | |||
| </div> | |||
| <div style={{display: 'flex', | |||
| justifyContent: 'space-between', | |||
| alignItems: 'center'}}> | |||
| <Button type="primary" className={Styles.plusButton} style={{margin:'0 20px 0 0' }} onClick={deleteDataset} icon = {<DeleteOutlined style={{color:'#1664ff', }} />}> | |||
| 删除 | |||
| </Button> | |||
| <Button type="primary" className={Styles.plusButton} disabled={!version} style={{margin:'0 20px 0 0' }} onClick={handleExport} icon = {<UploadOutlined style={{color:'#1664ff'}} />}> | |||
| 下载 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| <Table columns={columns} dataSource={wordList} pagination={false} /> | |||
| </div> | |||
| </TabPane> | |||
| </Tabs> | |||
| </div> | |||
| <Modal title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} className={Styles.modal} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| </div> | |||
| <div className={Styles.datasetIntroCneterBox}> | |||
| <Tabs defaultActiveKey="1"> | |||
| <TabPane tab="模型简介" key="1"> | |||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||
| </TabPane> | |||
| <TabPane tab="模型文件/版本" key="2"> | |||
| <div className={Styles.dataListBox}> | |||
| <div>模型列表</div> | |||
| <div className={Styles.dataButtonList}> | |||
| <div | |||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||
| > | |||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ | |||
| width: 160, | |||
| }} | |||
| allowClear | |||
| onChange={handleChange} | |||
| options={versionList} | |||
| /> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| onClick={showModal} | |||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 创建新版本 | |||
| </Button> | |||
| </div> | |||
| <div | |||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||
| > | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| style={{ margin: '0 20px 0 0' }} | |||
| onClick={deleteDataset} | |||
| icon={<DeleteOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| disabled={!version} | |||
| style={{ margin: '0 20px 0 0' }} | |||
| onClick={handleExport} | |||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 下载 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| <Table columns={columns} dataSource={wordList} pagination={false} /> | |||
| </div> | |||
| </TabPane> | |||
| </Tabs> | |||
| </div> | |||
| <Modal | |||
| title={ | |||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||
| <img | |||
| style={{ width: '20px', marginRight: '10px' }} | |||
| src={`/assets/images/pipeline-edit-icon.png`} | |||
| alt="" | |||
| /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={isModalOpen} | |||
| className={Styles.modal} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={handleCancel} | |||
| > | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| @@ -270,29 +300,38 @@ const Dataset= React.FC = () => { | |||
| <Form.Item | |||
| label="模型名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input disabled placeholder="请输入数据集名称"/> | |||
| <Input disabled placeholder="请输入数据集名称" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型版本" | |||
| name="version" | |||
| > | |||
| <Input placeholder="请输入数据集版本"/> | |||
| <Form.Item label="模型版本" name="version"> | |||
| <Input placeholder="请输入数据集版本" /> | |||
| </Form.Item> | |||
| <Form.Item label="模型文件" name="dataset_version_vos"> | |||
| <Upload {...props}> | |||
| <Button style={{fontSize:'14px',border:'1px solid',borderColor:'#1664ff',background:'#fff'}} icon={<UploadOutlined style={{color:'#1664ff'}} />}>上传文件</Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| <Upload {...props}> | |||
| <Button | |||
| style={{ | |||
| fontSize: '14px', | |||
| border: '1px solid', | |||
| borderColor: '#1664ff', | |||
| background: '#fff', | |||
| }} | |||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 上传文件 | |||
| </Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </div>) | |||
| </div> | |||
| ); | |||
| }; | |||
| export default Dataset; | |||
| export default Dataset; | |||
| @@ -1,234 +1,305 @@ | |||
| import React ,{useEffect,useState}from 'react'; | |||
| import Styles from './index.less' | |||
| import { Input, Space ,Button,Tabs,Pagination,Modal, Form,message, Radio,Upload,Select} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, UploadOutlined,DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||
| import {getModelList,addModel,getAssetIcon} from '@/services/dataset/index.js' | |||
| const { Search } = Input; | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import { getAccessToken } from '@/access'; | |||
| import { addModel, getAssetIcon, getModelList } from '@/services/dataset/index.js'; | |||
| import { PlusCircleOutlined, UploadOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; | |||
| import moment from 'moment'; | |||
| import { getAccessToken } from '@/access'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| const { Search } = Input; | |||
| const leftdataList=[1,2,3] | |||
| const leftdataList = [1, 2, 3]; | |||
| const PublicData= React.FC = () => { | |||
| const PublicData = () => { | |||
| const props = { | |||
| action: '/api/mmp/dataset/upload', | |||
| // headers: { | |||
| // 'X-Requested-With': null | |||
| // }, | |||
| headers: { | |||
| Authorization:getAccessToken(), | |||
| 'X-Requested-With': null | |||
| Authorization: getAccessToken(), | |||
| 'X-Requested-With': null, | |||
| }, | |||
| onChange({ file, fileList }) { | |||
| if (file.status !== 'uploading') { | |||
| console.log(file, fileList); | |||
| form.setFieldsValue({dataset_version_vos:fileList.map(item=>item.response.data[0])}) | |||
| form.setFieldsValue({ dataset_version_vos: fileList.map((item) => item.response.data[0]) }); | |||
| } | |||
| }, | |||
| defaultFileList: [ | |||
| ], | |||
| defaultFileList: [], | |||
| }; | |||
| const [queryFlow,setQueryFlow]=useState({ | |||
| page:0, | |||
| size:10, | |||
| name:null, | |||
| available_range:0 | |||
| const [queryFlow, setQueryFlow] = useState({ | |||
| page: 0, | |||
| size: 10, | |||
| name: null, | |||
| available_range: 0, | |||
| }); | |||
| const navgite = useNavigate(); | |||
| const [iconParams, setIconParams] = useState({ | |||
| name: null, | |||
| page: 0, | |||
| size: 10000, | |||
| }); | |||
| const navgite=useNavigate(); | |||
| const [iconParams,setIconParams]=useState({ | |||
| name:null, | |||
| page:0, | |||
| size:10000 | |||
| }) | |||
| const [activeType,setActiveType]=useState(null) | |||
| const [activeTag,setActiveTag]=useState(null) | |||
| const [datasetTypeList,setDatasetTypeList]=useState([]) | |||
| const [datasetDirectionList,setDatasetDirectionList]=useState([]) | |||
| const [isModalOpen,setIsModalOpen]=useState(false) | |||
| const [datasetList,setDatasetList]=useState([]); | |||
| const [total,setTotal]=useState(0); | |||
| const [activeType, setActiveType] = useState(null); | |||
| const [activeTag, setActiveTag] = useState(null); | |||
| const [datasetTypeList, setDatasetTypeList] = useState([]); | |||
| const [datasetDirectionList, setDatasetDirectionList] = useState([]); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetList, setDatasetList] = useState([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [form] = Form.useForm(); | |||
| const [dialogTitle, setDialogTitle] = useState('新建模型'); | |||
| const getModelLists=(queryFlow)=>{ | |||
| getModelList(queryFlow).then(ret=>{ | |||
| const getModelLists = (queryFlow) => { | |||
| getModelList(queryFlow).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setDatasetList(ret.data.content) | |||
| setTotal(ret.data.totalElements) | |||
| if (ret.code == 200) { | |||
| setDatasetList(ret.data.content); | |||
| setTotal(ret.data.totalElements); | |||
| } | |||
| }) | |||
| } | |||
| }); | |||
| }; | |||
| const showModal = () => { | |||
| form.resetFields() | |||
| setDialogTitle('新建模型') | |||
| form.resetFields(); | |||
| setDialogTitle('新建模型'); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const getAssetIconList=(params)=>{ | |||
| getAssetIcon(params).then(ret=>{ | |||
| const getAssetIconList = (params) => { | |||
| getAssetIcon(params).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200&&ret.data.content&&ret.data.content.length>0){ | |||
| setDatasetTypeList(ret.data.content.filter(item=>item.category_id==3)) | |||
| setDatasetDirectionList(ret.data.content.filter(item=>item.category_id==4)) | |||
| } | |||
| else{ | |||
| setDatasetTypeList([]) | |||
| setDatasetDirectionList([]) | |||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||
| setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3)); | |||
| setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4)); | |||
| } else { | |||
| setDatasetTypeList([]); | |||
| setDatasetDirectionList([]); | |||
| } | |||
| }) | |||
| } | |||
| const onSearch=(values)=>{ | |||
| }); | |||
| }; | |||
| const onSearch = (values) => { | |||
| console.log(values); | |||
| getAssetIconList({...iconParams,name:values}) | |||
| } | |||
| const nameSearch=(values)=>{ | |||
| getAssetIconList({ ...iconParams, name: values }); | |||
| }; | |||
| const nameSearch = (values) => { | |||
| console.log(values); | |||
| getModelLists({...queryFlow,name:values}) | |||
| } | |||
| getModelLists({ ...queryFlow, name: values }); | |||
| }; | |||
| const handleOk = () => { | |||
| console.log(1111); | |||
| console.log(1111); | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const onFinish = (values) => { | |||
| addModel(values).then(ret=>{ | |||
| console.log(ret); | |||
| getModelLists(queryFlow) | |||
| setIsModalOpen(false); | |||
| }) | |||
| }; | |||
| const chooseModelType=(val,item)=>{ | |||
| console.log(val,item); | |||
| if(item.path==queryFlow.model_type){ | |||
| setActiveType('') | |||
| setQueryFlow({...queryFlow,model_type:null}) | |||
| getModelLists({...queryFlow,model_type:null}) | |||
| } | |||
| else{ | |||
| setActiveType(item.path) | |||
| setQueryFlow({...queryFlow,model_type:item.path}) | |||
| getModelLists({...queryFlow,model_type:item.path}) | |||
| addModel(values).then((ret) => { | |||
| console.log(ret); | |||
| getModelLists(queryFlow); | |||
| setIsModalOpen(false); | |||
| }); | |||
| }; | |||
| const chooseModelType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.model_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||
| getModelLists({ ...queryFlow, model_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, model_type: item.path }); | |||
| getModelLists({ ...queryFlow, model_type: item.path }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const chooseModelTag=(val,item)=>{ | |||
| if(item.path==queryFlow.model_tag){ | |||
| setActiveTag('') | |||
| setQueryFlow({...queryFlow,model_tag:null}) | |||
| getModelLists({...queryFlow,model_tag:null}) | |||
| } | |||
| else{ | |||
| setActiveTag(item.path) | |||
| setQueryFlow({...queryFlow,model_tag:item.path}) | |||
| getModelLists({...queryFlow,model_tag:item.path}) | |||
| const chooseModelTag = (val, item) => { | |||
| if (item.path == queryFlow.model_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||
| getModelLists({ ...queryFlow, model_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.path }); | |||
| getModelLists({ ...queryFlow, model_tag: item.path }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const routeToIntro=(e,record)=>{ | |||
| e.stopPropagation() | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({pathname:`/dataset/modelIntro/${record.id}` }); | |||
| } | |||
| navgite({ pathname: `/dataset/modelIntro/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useEffect(()=>{ | |||
| getAssetIconList(iconParams) | |||
| getModelLists(queryFlow) | |||
| return ()=>{ | |||
| } | |||
| },[]) | |||
| return (<> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom:'15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList&&datasetTypeList.length>0?datasetTypeList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeType?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelType(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} onClick={(e)=>{chooseModelTag(e, item)}}>{item.name}</span> | |||
| useEffect(() => { | |||
| getAssetIconList(iconParams); | |||
| getModelLists(queryFlow); | |||
| return () => {}; | |||
| }, []); | |||
| return ( | |||
| <> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom: '15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList && datasetTypeList.length > 0 | |||
| ? datasetTypeList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelType(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span | |||
| className={Styles.messageText} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| }} | |||
| > | |||
| {item.name} | |||
| </span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList && datasetDirectionList.length > 0 | |||
| ? datasetDirectionList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span className={Styles.messageText}>{item.name}</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按模型名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| <Button | |||
| type="primary" | |||
| className={Styles.plusButton} | |||
| onClick={showModal} | |||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 模型注册 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList&&datasetDirectionList.length>0?datasetDirectionList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeTag?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelTag(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} >{item.name}</span> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList && datasetList.length > 0 | |||
| ? datasetList.map((item) => { | |||
| return ( | |||
| <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}> | |||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||
| </div> | |||
| <div className={Styles.itemIcon}> | |||
| <img | |||
| style={{ width: '17px', marginRight: '3px' }} | |||
| src={`/assets/images/upload-icon.png`} | |||
| alt="" | |||
| /> | |||
| <span>1582</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按模型名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||
| 模型注册 | |||
| </Button> | |||
| <Pagination size="small" total={total} showSizeChanger showQuickJumper /> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList&&datasetList.length>0?datasetList.map(item=>{return <div className={Styles.dataItem} onClick={(e)=>routeToIntro(e,item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}><span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span></div> | |||
| <div className={Styles.itemIcon}><img style={{width:'17px',marginRight:'3px'}} src={`/assets/images/upload-icon.png`} alt="" /><span>1582</span></div> | |||
| </div>}):''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| <Pagination | |||
| size="small" | |||
| total={total} | |||
| showSizeChanger | |||
| showQuickJumper | |||
| /> | |||
| </div> | |||
| </div> | |||
| <Modal title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} className={Styles.modal} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| <Modal | |||
| title={ | |||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||
| <img | |||
| style={{ width: '20px', marginRight: '10px' }} | |||
| src={`/assets/images/pipeline-edit-icon.png`} | |||
| alt="" | |||
| /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={isModalOpen} | |||
| className={Styles.modal} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={handleCancel} | |||
| > | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| @@ -243,73 +314,74 @@ const PublicData= React.FC = () => { | |||
| <Form.Item | |||
| label="模型名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入模型名称"/> | |||
| <Input placeholder="请输入模型名称" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型描述" | |||
| name="description" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入模型描述"/> | |||
| <Input placeholder="请输入模型描述" /> | |||
| </Form.Item> | |||
| <Form.Item label="可见范围" name="available_range"> | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型框架" | |||
| name="model_type" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择模型类型" | |||
| options={[]} | |||
| /> | |||
| <Select allowClear placeholder="请选择模型类型" options={[]} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| <Form.Item | |||
| label="模型能力" | |||
| name="model_tag" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择模型标签" | |||
| options={[]} | |||
| /> | |||
| <Select allowClear placeholder="请选择模型标签" options={[]} /> | |||
| </Form.Item> | |||
| <Form.Item label="模型文件" name="dataset_version_vos"> | |||
| <Upload {...props}> | |||
| <Button icon={<UploadOutlined style={{color:'#1664ff'}} />}>上传文件</Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| <Upload {...props}> | |||
| <Button icon={<UploadOutlined style={{ color: '#1664ff' }} />}>上传文件</Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </>) | |||
| </> | |||
| ); | |||
| }; | |||
| export default PublicData; | |||
| export default PublicData; | |||
| @@ -1,204 +1,263 @@ | |||
| import React ,{useEffect,useState}from 'react'; | |||
| import Styles from './index.less' | |||
| import { Input, Space ,Button,Tabs,Pagination,Modal, Form,message, Radio,} from 'antd'; | |||
| import { PlusOutlined,PlusCircleOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||
| import {getModelList,getAssetIcon} from '@/services/dataset/index.js' | |||
| const { Search } = Input; | |||
| import { useNavigate} from 'react-router-dom'; | |||
| import { getAssetIcon, getModelList } from '@/services/dataset/index.js'; | |||
| import { Form, Input, Modal, Pagination, Radio } from 'antd'; | |||
| import moment from 'moment'; | |||
| const leftdataList=[1,2,3] | |||
| import { useEffect, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| const { Search } = Input; | |||
| const leftdataList = [1, 2, 3]; | |||
| const PublicData= React.FC = () => { | |||
| const [queryFlow,setQueryFlow]=useState({ | |||
| page:0, | |||
| size:10, | |||
| name:null, | |||
| available_range:1 | |||
| const PublicData = () => { | |||
| const [queryFlow, setQueryFlow] = useState({ | |||
| page: 0, | |||
| size: 10, | |||
| name: null, | |||
| available_range: 1, | |||
| }); | |||
| const [iconParams,setIconParams]=useState({ | |||
| name:null, | |||
| page:0, | |||
| size:10000 | |||
| }) | |||
| const [activeType,setActiveType]=useState(null) | |||
| const [activeTag,setActiveTag]=useState(null) | |||
| const [datasetTypeList,setDatasetTypeList]=useState([]) | |||
| const [datasetDirectionList,setDatasetDirectionList]=useState([]) | |||
| const navgite=useNavigate(); | |||
| const [isModalOpen,setIsModalOpen]=useState(false) | |||
| const [datasetList,setDatasetList]=useState([]); | |||
| const [total,setTotal]=useState(0); | |||
| const [iconParams, setIconParams] = useState({ | |||
| name: null, | |||
| page: 0, | |||
| size: 10000, | |||
| }); | |||
| const [activeType, setActiveType] = useState(null); | |||
| const [activeTag, setActiveTag] = useState(null); | |||
| const [datasetTypeList, setDatasetTypeList] = useState([]); | |||
| const [datasetDirectionList, setDatasetDirectionList] = useState([]); | |||
| const navgite = useNavigate(); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetList, setDatasetList] = useState([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [form] = Form.useForm(); | |||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | |||
| const getModelLists=(queryFlow)=>{ | |||
| getModelList(queryFlow).then(ret=>{ | |||
| const getModelLists = (queryFlow) => { | |||
| getModelList(queryFlow).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200){ | |||
| setDatasetList(ret.data.content) | |||
| setTotal(ret.data.totalElements) | |||
| if (ret.code == 200) { | |||
| setDatasetList(ret.data.content); | |||
| setTotal(ret.data.totalElements); | |||
| } | |||
| }) | |||
| } | |||
| }); | |||
| }; | |||
| const showModal = () => { | |||
| form.resetFields() | |||
| setDialogTitle('新建数据集') | |||
| form.resetFields(); | |||
| setDialogTitle('新建数据集'); | |||
| setIsModalOpen(true); | |||
| }; | |||
| const nameSearch=(values)=>{ | |||
| const nameSearch = (values) => { | |||
| console.log(values); | |||
| getModelLists({...queryFlow,name:values}) | |||
| } | |||
| const getAssetIconList=(params)=>{ | |||
| getAssetIcon(params).then(ret=>{ | |||
| getModelLists({ ...queryFlow, name: values }); | |||
| }; | |||
| const getAssetIconList = (params) => { | |||
| getAssetIcon(params).then((ret) => { | |||
| console.log(ret); | |||
| if(ret.code==200&&ret.data.content&&ret.data.content.length>0){ | |||
| setDatasetTypeList(ret.data.content.filter(item=>item.category_id==3)) | |||
| setDatasetDirectionList(ret.data.content.filter(item=>item.category_id==4)) | |||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||
| setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3)); | |||
| setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4)); | |||
| } else { | |||
| setDatasetTypeList([]); | |||
| setDatasetDirectionList([]); | |||
| } | |||
| else{ | |||
| setDatasetTypeList([]) | |||
| setDatasetDirectionList([]) | |||
| } | |||
| }) | |||
| } | |||
| const onSearch=(values)=>{ | |||
| }); | |||
| }; | |||
| const onSearch = (values) => { | |||
| console.log(values); | |||
| getAssetIconList({...iconParams,name:values}) | |||
| } | |||
| getAssetIconList({ ...iconParams, name: values }); | |||
| }; | |||
| const handleOk = () => { | |||
| console.log(1111); | |||
| console.log(1111); | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const chooseModelType=(val,item)=>{ | |||
| console.log(val,item); | |||
| if(item.path==queryFlow.model_type){ | |||
| setActiveType('') | |||
| setQueryFlow({...queryFlow,model_type:null}) | |||
| getModelLists({...queryFlow,model_type:null}) | |||
| const chooseModelType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.model_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||
| getModelLists({ ...queryFlow, model_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, model_type: item.path }); | |||
| getModelLists({ ...queryFlow, model_type: item.path }); | |||
| } | |||
| else{ | |||
| setActiveType(item.path) | |||
| setQueryFlow({...queryFlow,model_type:item.path}) | |||
| getModelLists({...queryFlow,model_type:item.path}) | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const chooseModelTag=(val,item)=>{ | |||
| if(item.path==queryFlow.model_tag){ | |||
| setActiveTag('') | |||
| setQueryFlow({...queryFlow,model_tag:null}) | |||
| getModelLists({...queryFlow,model_tag:null}) | |||
| } | |||
| else{ | |||
| setActiveTag(item.path) | |||
| setQueryFlow({...queryFlow,model_tag:item.path}) | |||
| getModelLists({...queryFlow,model_tag:item.path}) | |||
| const chooseModelTag = (val, item) => { | |||
| if (item.path == queryFlow.model_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||
| getModelLists({ ...queryFlow, model_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.path }); | |||
| getModelLists({ ...queryFlow, model_tag: item.path }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| // }) | |||
| }; | |||
| const onFinish = (values) => { | |||
| }; | |||
| const routeToIntro=(e,record)=>{ | |||
| e.stopPropagation() | |||
| console.log(record); | |||
| navgite({pathname:`/dataset/modelIntro/${record.id}` }); | |||
| } | |||
| const onFinish = (values) => {}; | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({ pathname: `/dataset/modelIntro/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| useEffect(()=>{ | |||
| getAssetIconList(iconParams) | |||
| getModelLists(queryFlow) | |||
| return ()=>{ | |||
| } | |||
| },[]) | |||
| return (<> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom:'15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList&&datasetTypeList.length>0?datasetTypeList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeType?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelType(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} >{item.name}</span> | |||
| useEffect(() => { | |||
| getAssetIconList(iconParams); | |||
| getModelLists(queryFlow); | |||
| return () => {}; | |||
| }, []); | |||
| return ( | |||
| <> | |||
| <div className={Styles.datasetCneterBox}> | |||
| <div className={Styles.datasetCneterLeftBox}> | |||
| <div className={Styles.leftContentBox}> | |||
| <Search | |||
| placeholder="搜索" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| style={{ | |||
| width: 300, | |||
| marginBottom: '15px', | |||
| }} | |||
| /> | |||
| <div className={Styles.itemTitle}>模型框架</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetTypeList && datasetTypeList.length > 0 | |||
| ? datasetTypeList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelType(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span className={Styles.messageText}>{item.name}</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList && datasetDirectionList.length > 0 | |||
| ? datasetDirectionList.map((item) => { | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| }} | |||
| > | |||
| <img | |||
| className={Styles.ptIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={Styles.hoverIcon} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/model/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span className={Styles.messageText}>{item.name}</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| </div> | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| <div className={Styles.itemTitle}>模型能力</div> | |||
| <div className={Styles.itemBox}> | |||
| {datasetDirectionList&&datasetDirectionList.length>0?datasetDirectionList.map(item=>{return <div > | |||
| <div className={[Styles.messageBox, item.path===activeTag?Styles.active:null].join(' ')} onClick={(e)=>{chooseModelTag(e, item)}}> | |||
| <img className={Styles.ptIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}.png`} alt="" /> | |||
| <img className={Styles.hoverIcon} style={{width:'22px'}} src={`/assets/images/model/${item.path}-hover.png`} alt="" /> | |||
| <span className={Styles.messageText} >{item.name}</span> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按数据名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div>}):''} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.datasetCneterRightBox}> | |||
| <div className={Styles.dataSource}> | |||
| <span>数据总数:{total}个</span> | |||
| <div> | |||
| <Search | |||
| placeholder="按数据名称筛选" | |||
| allowClear | |||
| onSearch={nameSearch} | |||
| style={{ | |||
| width: 300, | |||
| }} | |||
| /> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList && datasetList.length > 0 | |||
| ? datasetList.map((item) => { | |||
| return ( | |||
| <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}> | |||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||
| </div> | |||
| <div className={Styles.itemIcon}> | |||
| <img | |||
| style={{ width: '17px', marginRight: '3px' }} | |||
| src={`/assets/images/upload-icon.png`} | |||
| alt="" | |||
| /> | |||
| <span>1582</span> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }) | |||
| : ''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| <Pagination size="small" total={total} showSizeChanger showQuickJumper /> | |||
| </div> | |||
| </div> | |||
| <div className={Styles.dataContent}> | |||
| {datasetList&&datasetList.length>0?datasetList.map(item=>{return <div className={Styles.dataItem} onClick={(e)=>routeToIntro(e,item)}> | |||
| <div className={Styles.itemText}>{item.name}</div> | |||
| <div className={Styles.itemTime}><span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span></div> | |||
| <div className={Styles.itemIcon}><img style={{width:'17px',marginRight:'3px'}} src={`/assets/images/upload-icon.png`} alt="" /><span>1582</span></div> | |||
| </div>}):''} | |||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||
| </div> | |||
| <Pagination | |||
| size="small" | |||
| total={total} | |||
| showSizeChanger | |||
| showQuickJumper | |||
| /> | |||
| </div> | |||
| </div> | |||
| <Modal title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||
| </div>} open={isModalOpen} className={Styles.modal} okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} onCancel={handleCancel}> | |||
| <Modal | |||
| title={ | |||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||
| <img | |||
| style={{ width: '20px', marginRight: '10px' }} | |||
| src={`/assets/images/pipeline-edit-icon.png`} | |||
| alt="" | |||
| /> | |||
| {dialogTitle} | |||
| </div> | |||
| } | |||
| open={isModalOpen} | |||
| className={Styles.modal} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| onCancel={handleCancel} | |||
| > | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| @@ -213,59 +272,68 @@ const PublicData= React.FC = () => { | |||
| <Form.Item | |||
| label="数据名称" | |||
| name="name" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据名称"/> | |||
| <Input placeholder="请输入数据名称" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据集版本" | |||
| name="data_type" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据集版本"/> | |||
| <Input placeholder="请输入数据集版本" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据描述" | |||
| name="description" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据描述"/> | |||
| <Input placeholder="请输入数据描述" /> | |||
| </Form.Item> | |||
| <Form.Item label="选择流水线" name="description1"> | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据标签" | |||
| name="description3" | |||
| rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ]} | |||
| rules={ | |||
| [ | |||
| // { | |||
| // required: true, | |||
| // message: 'Please input your username!', | |||
| // }, | |||
| ] | |||
| } | |||
| > | |||
| <Input placeholder="请输入数据标签" /> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| </>) | |||
| </> | |||
| ); | |||
| }; | |||
| export default PublicData; | |||
| export default PublicData; | |||
| @@ -1,51 +1,42 @@ | |||
| import { getUserInfo } from '@/services/session'; | |||
| import { | |||
| ClusterOutlined, | |||
| MailOutlined, | |||
| ManOutlined, | |||
| MobileOutlined, | |||
| TeamOutlined, | |||
| UserOutlined, | |||
| MobileOutlined, | |||
| ManOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { PageLoading } from '@ant-design/pro-components'; | |||
| import { useRequest } from '@umijs/max'; | |||
| import { Card, Col, Divider, List, Row } from 'antd'; | |||
| import React, { useState } from 'react'; | |||
| import styles from './Center.less'; | |||
| import AvatarCropper from './components/AvatarCropper'; | |||
| import BaseInfo from './components/BaseInfo'; | |||
| import ResetPassword from './components/ResetPassword'; | |||
| import AvatarCropper from './components/AvatarCropper'; | |||
| import { useRequest } from '@umijs/max'; | |||
| import { getUserInfo } from '@/services/session'; | |||
| import { PageLoading } from '@ant-design/pro-components'; | |||
| const operationTabList = [ | |||
| { | |||
| key: 'base', | |||
| tab: ( | |||
| <span> | |||
| 基本资料 | |||
| </span> | |||
| ), | |||
| tab: <span>基本资料</span>, | |||
| }, | |||
| { | |||
| key: 'password', | |||
| tab: ( | |||
| <span> | |||
| 重置密码 | |||
| </span> | |||
| ), | |||
| tab: <span>重置密码</span>, | |||
| }, | |||
| ]; | |||
| export type tabKeyType = 'base' | 'password'; | |||
| const Center: React.FC = () => { | |||
| const [tabKey, setTabKey] = useState<tabKeyType>('base'); | |||
| const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false); | |||
| // 获取用户信息 | |||
| const { data: userInfo, loading } = useRequest(async () => { | |||
| return { data: await getUserInfo()}; | |||
| return { data: await getUserInfo() }; | |||
| }); | |||
| if (loading) { | |||
| return <div>loading...</div>; | |||
| @@ -141,14 +132,15 @@ const Center: React.FC = () => { | |||
| <div> | |||
| <Row gutter={[16, 24]}> | |||
| <Col lg={8} md={24}> | |||
| <Card | |||
| title="个人信息" | |||
| bordered={false} | |||
| loading={loading} | |||
| > | |||
| <Card title="个人信息" bordered={false} loading={loading}> | |||
| {!loading && ( | |||
| <div style={{ textAlign: "center"}}> | |||
| <div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}> | |||
| <div style={{ textAlign: 'center' }}> | |||
| <div | |||
| className={styles.avatarHolder} | |||
| onClick={() => { | |||
| setCropperModalOpen(true); | |||
| }} | |||
| > | |||
| <img alt="" src={currentUser.avatar} /> | |||
| </div> | |||
| {renderUserInfo(currentUser)} | |||
| @@ -188,7 +180,7 @@ const Center: React.FC = () => { | |||
| </Row> | |||
| <AvatarCropper | |||
| onFinished={() => { | |||
| setCropperModalOpen(false); | |||
| setCropperModalOpen(false); | |||
| }} | |||
| open={cropperModalOpen} | |||
| data={currentUser.avatar} | |||
| @@ -0,0 +1,57 @@ | |||
| import type { RequestConfig } from '@umijs/max'; | |||
| import { message } from 'antd'; | |||
| import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; | |||
| const checkRegion = 5 * 60 * 1000; | |||
| /** | |||
| * Umi Max 网络请求配置 | |||
| * @doc https://umijs.org/docs/max/request#配置 | |||
| */ | |||
| export const requestConfig: RequestConfig = { | |||
| errorConfig: {}, | |||
| requestInterceptors: [ | |||
| (url: any, options: { headers: any }) => { | |||
| const headers = options.headers ? options.headers : []; | |||
| console.log('request ====>:', url); | |||
| const authHeader = headers['Authorization']; | |||
| const isToken = headers['isToken']; | |||
| if (!authHeader && isToken !== false) { | |||
| const expireTime = getTokenExpireTime(); | |||
| if (expireTime) { | |||
| const left = Number(expireTime) - new Date().getTime(); | |||
| const refreshToken = getRefreshToken(); | |||
| if (left < checkRegion && refreshToken) { | |||
| if (left < 0) { | |||
| clearSessionToken(); | |||
| } | |||
| } else { | |||
| const accessToken = getAccessToken(); | |||
| if (accessToken) { | |||
| headers['Authorization'] = `Bearer ${accessToken}`; | |||
| } | |||
| } | |||
| } else { | |||
| clearSessionToken(); | |||
| } | |||
| } | |||
| return { url, options }; | |||
| }, | |||
| ], | |||
| responseInterceptors: [ | |||
| (response: any) => | |||
| { | |||
| const { status, data } = response; | |||
| if (status && status >= 200 && status < 300 && data && data.code === 200) { | |||
| return response | |||
| } else { | |||
| if (data && data.msg) { | |||
| message.error(data.msg); | |||
| } else { | |||
| message.error("请求失败"); | |||
| } | |||
| return Promise.reject(response) | |||
| } | |||
| }, | |||
| ], | |||
| }; | |||
| @@ -1,109 +0,0 @@ | |||
| import type { RequestOptions } from '@@/plugin-request/request'; | |||
| import type { RequestConfig } from '@umijs/max'; | |||
| import { message, notification } from 'antd'; | |||
| // 错误处理方案: 错误类型 | |||
| enum ErrorShowType { | |||
| SILENT = 0, | |||
| WARN_MESSAGE = 1, | |||
| ERROR_MESSAGE = 2, | |||
| NOTIFICATION = 3, | |||
| REDIRECT = 9, | |||
| } | |||
| // 与后端约定的响应数据格式 | |||
| interface ResponseStructure { | |||
| success: boolean; | |||
| data: any; | |||
| errorCode?: number; | |||
| errorMessage?: string; | |||
| showType?: ErrorShowType; | |||
| } | |||
| /** | |||
| * @name 错误处理 | |||
| * pro 自带的错误处理, 可以在这里做自己的改动 | |||
| * @doc https://umijs.org/docs/max/request#配置 | |||
| */ | |||
| export const errorConfig: RequestConfig = { | |||
| // 错误处理: umi@3 的错误处理方案。 | |||
| errorConfig: { | |||
| // 错误抛出 | |||
| errorThrower: (res) => { | |||
| const { success, data, errorCode, errorMessage, showType } = | |||
| res as unknown as ResponseStructure; | |||
| if (!success) { | |||
| const error: any = new Error(errorMessage); | |||
| error.name = 'BizError'; | |||
| error.info = { errorCode, errorMessage, showType, data }; | |||
| throw error; // 抛出自制的错误 | |||
| } | |||
| }, | |||
| // 错误接收及处理 | |||
| errorHandler: (error: any, opts: any) => { | |||
| if (opts?.skipErrorHandler) throw error; | |||
| // 我们的 errorThrower 抛出的错误。 | |||
| if (error.name === 'BizError') { | |||
| const errorInfo: ResponseStructure | undefined = error.info; | |||
| if (errorInfo) { | |||
| const { errorMessage, errorCode } = errorInfo; | |||
| switch (errorInfo.showType) { | |||
| case ErrorShowType.SILENT: | |||
| // do nothing | |||
| break; | |||
| case ErrorShowType.WARN_MESSAGE: | |||
| message.warning(errorMessage); | |||
| break; | |||
| case ErrorShowType.ERROR_MESSAGE: | |||
| message.error(errorMessage); | |||
| break; | |||
| case ErrorShowType.NOTIFICATION: | |||
| notification.open({ | |||
| description: errorMessage, | |||
| message: errorCode, | |||
| }); | |||
| break; | |||
| case ErrorShowType.REDIRECT: | |||
| // TODO: redirect | |||
| break; | |||
| default: | |||
| message.error(errorMessage); | |||
| } | |||
| } | |||
| } else if (error.response) { | |||
| // Axios 的错误 | |||
| // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围 | |||
| message.error(`Response status:${error.response.status}`); | |||
| } else if (error.request) { | |||
| // 请求已经成功发起,但没有收到响应 | |||
| // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例, | |||
| // 而在node.js中是 http.ClientRequest 的实例 | |||
| message.error('None response! Please retry.'); | |||
| } else { | |||
| // 发送请求时出了点问题 | |||
| message.error('Request error, please retry.'); | |||
| } | |||
| }, | |||
| }, | |||
| // 请求拦截器 | |||
| requestInterceptors: [ | |||
| (config: RequestOptions) => { | |||
| // 拦截请求配置,进行个性化处理。 | |||
| const url = config?.url?.concat('?token = 123'); | |||
| return { ...config, url }; | |||
| }, | |||
| ], | |||
| // 响应拦截器 | |||
| responseInterceptors: [ | |||
| (response) => { | |||
| // 拦截响应数据,进行个性化处理 | |||
| const { data } = response as unknown as ResponseStructure; | |||
| if (data?.success === false) { | |||
| message.error('请求失败!'); | |||
| } | |||
| return response; | |||
| }, | |||
| ], | |||
| }; | |||
| @@ -1,87 +1,102 @@ | |||
| import { request } from '@umijs/max'; | |||
| import { request } from '@umijs/max'; | |||
| // 查询实验列表 | |||
| export function getExperiment(params) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'GET', | |||
| params | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 运行实验 | |||
| export function runExperiments(id) { | |||
| return request('/api/mmp/experiment/experiments/'+id, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| }); | |||
| } | |||
| export function runExperiments(id) { | |||
| return request('/api/mmp/experiment/experiments/' + id, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| }); | |||
| } | |||
| // 根据id查询实验 | |||
| export function getExperimentById(id) { | |||
| return request(`/api/mmp/experiment/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experiment/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 根据id删除实验 | |||
| export function deleteExperimentById(id) { | |||
| return request(`/api/mmp/experiment/${id}`, { | |||
| method: 'DELETE', | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 根据id查询实验实例 | |||
| export function getQueryByExperimentId(id) { | |||
| return request(`/api/mmp/experimentIns/queryByExperimentId/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experimentIns/queryByExperimentId/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 根据id删除实验实例 | |||
| export function deleteQueryByExperimentInsId(id) { | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'DELETE', | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 根据id终止实验实例 | |||
| export function putQueryByExperimentInsId(id) { | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'PUT', | |||
| method: 'PUT', | |||
| }); | |||
| } | |||
| // 查询实验实例实时日志 | |||
| export function getQueryByExperimentLog(data) { | |||
| return request("/api/mmp/experimentIns/realTimeLog/", { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 根据id查询查询日志 | |||
| export function getQueryByExperimentLog(params) { | |||
| return request(`/api/mmp/experimentIns/log/`, { | |||
| method: 'GET', | |||
| params | |||
| }); | |||
| } | |||
| // 根据id查询输出结果 | |||
| // 查询实例节点结果 | |||
| export function getNodeResult(params) { | |||
| return request(`/api/mmp/experimentIns/nodeResult/`, { | |||
| method: 'GET', | |||
| params | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 获取pod实时日志请求,运行完成的 | |||
| export function getExperimentPodsLog(params) { | |||
| return request("/api/mmp/experimentIns/pods/log", { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 获取pod实时日志请求,运行中的 | |||
| export function getExperimentPodsRealtimeLog(data) { | |||
| return request("/api/mmp/experimentIns/pods/realTimeLog", { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 根据实例查询详情 | |||
| export function getExperimentIns(id) { | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| return request(`/api/mmp/experimentIns/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 新增实验 | |||
| export function postExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data | |||
| }); | |||
| } | |||
| export function postExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data, | |||
| }); | |||
| } | |||
| // 编辑实验 | |||
| export function putExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data | |||
| }); | |||
| } | |||
| export function putExperiment(data) { | |||
| return request(`/api/mmp/experiment`, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data, | |||
| }); | |||
| } | |||
| @@ -3,7 +3,6 @@ import { MenuDataItem } from '@ant-design/pro-components'; | |||
| import { request } from '@umijs/max'; | |||
| import React, { lazy } from 'react'; | |||
| let remoteMenu: any = null; | |||
| export function getRemoteMenu() { | |||
| @@ -14,7 +13,6 @@ export function setRemoteMenu(data: any) { | |||
| remoteMenu = data; | |||
| } | |||
| function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| for (const menuItem of menu) { | |||
| if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') { | |||
| @@ -28,19 +26,19 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| } | |||
| } | |||
| if (!hasItem) { | |||
| newItem = { | |||
| newItem = { | |||
| path: menuItem.path, | |||
| routes: [], | |||
| children: [] | |||
| } | |||
| route.routes.push(newItem) | |||
| children: [], | |||
| }; | |||
| route.routes.push(newItem); | |||
| } | |||
| patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/'); | |||
| } | |||
| } else { | |||
| const names: string[] = menuItem.component.split('/'); | |||
| let path = ''; | |||
| names.forEach(name => { | |||
| names.forEach((name) => { | |||
| if (path.length > 0) { | |||
| path += '/'; | |||
| } | |||
| @@ -49,9 +47,9 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| } else { | |||
| path += name; | |||
| } | |||
| }) | |||
| }); | |||
| if (!path.endsWith('.tsx')) { | |||
| path += '.tsx' | |||
| path += '.tsx'; | |||
| } | |||
| if (route.routes === undefined) { | |||
| route.routes = []; | |||
| @@ -62,7 +60,7 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| const newRoute = { | |||
| element: React.createElement(lazy(() => import('@/pages/' + path))), | |||
| path: parentPath + menuItem.path, | |||
| } | |||
| }; | |||
| route.children.push(newRoute); | |||
| route.routes.push(newRoute); | |||
| } | |||
| @@ -70,7 +68,9 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| } | |||
| export function patchRouteWithRemoteMenus(routes: any) { | |||
| if (remoteMenu === null) { return; } | |||
| if (remoteMenu === null) { | |||
| return; | |||
| } | |||
| let proLayout = null; | |||
| for (const routeItem of routes) { | |||
| if (routeItem.id === 'ant-design-pro-layout') { | |||
| @@ -92,8 +92,8 @@ export async function getUserInfo(options?: Record<string, any>) { | |||
| // 刷新方法 | |||
| export async function refreshToken() { | |||
| return request('/api/auth/refresh', { | |||
| method: 'post' | |||
| }) | |||
| method: 'post', | |||
| }); | |||
| } | |||
| export async function getRouters(): Promise<any> { | |||
| @@ -0,0 +1,38 @@ | |||
| import dayjs from 'dayjs'; | |||
| export const elapsedTime = (beginDate: Date, endDate: Date): string => { | |||
| if (!isValidDate(beginDate) || !isValidDate(endDate)) { | |||
| return '--'; | |||
| } | |||
| const timestamp = endDate.getTime() - beginDate.getTime(); | |||
| if (timestamp < 0) { | |||
| return '时间有误'; | |||
| } | |||
| const duration = dayjs.duration(timestamp); | |||
| const years = duration.years(); | |||
| const months = duration.months(); | |||
| const days = duration.days(); | |||
| const hours = duration.hours(); | |||
| const minutes = duration.minutes(); | |||
| const seconds = duration.seconds(); | |||
| if (years !== 0) { | |||
| return `${years}年${months}个月`; | |||
| } | |||
| if (months !== 0) { | |||
| return `${months}个月${days}天`; | |||
| } | |||
| if (days !== 0) { | |||
| return `${days}天${hours}小时`; | |||
| } | |||
| if (hours !== 0) { | |||
| return `${hours}小时${minutes}分`; | |||
| } | |||
| return `${minutes}分${seconds}秒`; | |||
| }; | |||
| // 是否是有效的日期 | |||
| export const isValidDate = (date: Date): boolean => { | |||
| if (date instanceof Date) { | |||
| return !Number.isNaN(date.getTime()); // valueOf() 也可以 | |||
| } | |||
| return false; | |||
| }; | |||
| @@ -0,0 +1,20 @@ | |||
| /** | |||
| * @param { Promise } promise | |||
| * @return { Promise } | |||
| */ | |||
| export async function to<T>(promise: Promise<T>): Promise<[T, null] | [null, Error]> { | |||
| try { | |||
| const data = await promise; | |||
| return [data, null]; | |||
| } catch (error) { | |||
| if (error instanceof Error) { | |||
| return [null, error]; | |||
| } else if (typeof error === 'string') { | |||
| return [null, new Error(error)]; | |||
| } else { | |||
| return [null, new Error('Error')]; | |||
| } | |||
| } | |||
| } | |||
| export default to; | |||
| @@ -1,23 +1,31 @@ | |||
| { | |||
| "compilerOptions": { | |||
| "target": "esnext", | |||
| "module": "esnext", | |||
| "moduleResolution": "node", | |||
| "importHelpers": true, | |||
| "jsx": "preserve", | |||
| "esModuleInterop": true, | |||
| "sourceMap": true, | |||
| "target": "esnext", // 指定ECMAScript目标版本 | |||
| "lib": ["dom", "dom.iterable", "esnext"], // 要包含在编译中的库文件列表 | |||
| "allowJs": true, // 允许编译JavaScript文件 | |||
| "skipLibCheck": true, // 跳过所有声明文件的类型检查 | |||
| "esModuleInterop": true, // 禁用命名空间导入(import * as fs from "fs"),并启用CJS/AMD/UMD样式的导入(import fs from "fs") | |||
| "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入 | |||
| "strict": true, // 启用所有严格类型检查选项 | |||
| "forceConsistentCasingInFileNames": true, // 不允许对同一文件的引用使用不一致的大小写 | |||
| "module": "esnext", // 指定模块代码生成 | |||
| "moduleResolution": "node", // 使用Node.js样式解析模块 | |||
| "isolatedModules": true, // 无条件地为未解析的文件发出导入 | |||
| "resolveJsonModule": true, // 包含.json扩展名的模块 | |||
| "noEmit": true, // 不发出输出(即不编译代码,只进行类型检查) | |||
| "jsx": "react-jsx", // 在.tsx文件中支持JSX | |||
| "sourceMap": true, // 生成相应的.map文件 | |||
| "declaration": true, // 生成相应的.d.ts文件 | |||
| "noUnusedLocals": true, // 报告未使用的局部变量错误 | |||
| "noUnusedParameters": true, // 报告未使用的参数错误 | |||
| "incremental": true, // 通过读写磁盘上的文件来启用增量编译 | |||
| "noFallthroughCasesInSwitch": true, // 报告switch语句中的fallthrough案例错误 | |||
| "baseUrl": "./", | |||
| "skipLibCheck": true, | |||
| "experimentalDecorators": true, | |||
| "strict": true, | |||
| "resolveJsonModule": true, | |||
| "allowSyntheticDefaultImports": true, | |||
| "paths": { | |||
| "@/*": ["./src/*"], | |||
| "@@/*": ["./src/.umi/*"], | |||
| "@@test/*": ["./src/.umi-test/*"] | |||
| "@/*": ["src/*"] | |||
| } | |||
| }, | |||
| "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "src/pages/Pipeline/index.jsx", "src/pages/Dataset/index.jsx"] | |||
| "include": [ | |||
| "src/**/*" // *** TypeScript应该检查的文件 *** | |||
| ] | |||
| } | |||
| @@ -201,6 +201,12 @@ | |||
| <version>0.1.55</version> <!-- 检查最新版本 --> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.springframework.boot</groupId> | |||
| <artifactId>spring-boot-starter-websocket</artifactId> | |||
| </dependency> | |||
| </dependencies> | |||
| <build> | |||
| @@ -32,3 +32,4 @@ public class RuoYiManagementPlatformApplication { | |||
| " ''-' `'-' `-..-' "); | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package com.ruoyi.platform.config; | |||
| import org.springframework.context.annotation.Bean; | |||
| import org.springframework.context.annotation.Configuration; | |||
| import org.springframework.web.socket.server.standard.ServerEndpointExporter; | |||
| @Configuration | |||
| public class WebSocketConfig { | |||
| /** | |||
| * 注入 ServerEndpointExporter, | |||
| * 这个 bean 会自动注册使用了 @ServerEndpoint 注解声明的 WebSocket endpoint | |||
| */ | |||
| @Bean | |||
| public ServerEndpointExporter serverEndpointExporter() { | |||
| return new ServerEndpointExporter(); | |||
| } | |||
| } | |||
| @@ -91,7 +91,7 @@ public class DatasetController { | |||
| * @return 单条数据 | |||
| */ | |||
| @GetMapping("{id}") | |||
| @ApiOperation("根据id查询数据集") | |||
| @ApiOperation("根据数据集id查询数据集") | |||
| public AjaxResult queryById(@PathVariable("id") Integer id) { | |||
| return AjaxResult.success(this.datasetService.queryById(id)); | |||
| } | |||
| @@ -148,7 +148,7 @@ public class DatasetController { | |||
| * @return 删除是否成功 | |||
| */ | |||
| @DeleteMapping({"{id}"}) | |||
| @ApiOperation("删除数据集") | |||
| @ApiOperation("根据id删除数据集") | |||
| public AjaxResult deleteById(@PathVariable("id") Integer id) { | |||
| return AjaxResult.success(this.datasetService.removeById(id)); | |||
| } | |||
| @@ -45,7 +45,7 @@ public class ExperimentController extends BaseController { | |||
| return genericsSuccess(this.experimentService.queryByPage(experiment, pageRequest)); | |||
| } | |||
| @GetMapping(("/status")) | |||
| @GetMapping("/status") | |||
| @ApiOperation("查询实验状态") | |||
| public GenericsAjaxResult<Page<Experiment>> selectStatus(@RequestBody Experiment experiment, PageRequest pageRequest) throws IOException { | |||
| return genericsSuccess(this.experimentService.selectStatus(experiment, pageRequest)); | |||
| @@ -53,8 +53,8 @@ public class ExperimentController extends BaseController { | |||
| @GetMapping(("/configuration")) | |||
| @ApiOperation("查询实验配置") | |||
| @GetMapping("/configuration") | |||
| @ApiOperation("查询实验配置参数") | |||
| public GenericsAjaxResult<ResponseEntity<Experiment>> showExperimentConfig(@RequestBody Experiment experiment){ | |||
| return genericsSuccess(this.experimentService.showExperimentConfig(experiment)); | |||
| } | |||
| @@ -5,6 +5,7 @@ import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | |||
| import com.ruoyi.platform.domain.ExperimentIns; | |||
| import com.ruoyi.platform.service.ExperimentInsService; | |||
| import com.ruoyi.platform.vo.LogRequestVo; | |||
| import com.ruoyi.platform.vo.PodLogVo; | |||
| import io.swagger.annotations.Api; | |||
| import io.swagger.annotations.ApiOperation; | |||
| import org.springframework.data.domain.Page; | |||
| @@ -138,11 +139,19 @@ public class ExperimentInsController extends BaseController { | |||
| @GetMapping("/pods/log") | |||
| @ApiOperation("获取pod实时日志请求") | |||
| public GenericsAjaxResult<Map> getRealtimePodLog(@RequestParam("pod_name") String podName, | |||
| public GenericsAjaxResult<Map<String, Object>> getRealtimePodLog(@RequestParam("pod_name") String podName, | |||
| @RequestParam("start_time") String startTime){ | |||
| return genericsSuccess(this.experimentInsService.getRealtimePodLog(podName,startTime)); | |||
| } | |||
| @PostMapping("/pods/realTimeLog") | |||
| @ApiOperation("获取pod实时日志请求") | |||
| public GenericsAjaxResult<String> getRealtimePodLogFromPod(@RequestBody PodLogVo podLogVo){ | |||
| return genericsSuccess(this.experimentInsService.getRealtimePodLogFromPod(podLogVo)); | |||
| } | |||
| /** | |||
| * 查询实验实例实时日志 | |||
| * | |||
| @@ -2,6 +2,7 @@ package com.ruoyi.platform.domain; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import io.swagger.annotations.ApiModelProperty; | |||
| import java.util.Date; | |||
| import java.io.Serializable; | |||
| @@ -18,40 +19,34 @@ public class AssetIcon implements Serializable { | |||
| /** | |||
| * 主键 | |||
| */ | |||
| private Integer id; | |||
| /** | |||
| * 资产图标名称 | |||
| */ | |||
| @ApiModelProperty(value = "资产ID") | |||
| private Integer id; | |||
| @ApiModelProperty(value = "资产图标名称") | |||
| private String name; | |||
| /** | |||
| * 分类id | |||
| */ | |||
| @ApiModelProperty(value = "分类ID") | |||
| private Integer categoryId; | |||
| /** | |||
| * 路径 | |||
| */ | |||
| @ApiModelProperty(value = "路径") | |||
| private String path; | |||
| /** | |||
| * 描述 | |||
| */ | |||
| @ApiModelProperty(value = "描述") | |||
| private String description; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0失效,1生效 | |||
| */ | |||
| @ApiModelProperty(value = "状态,0失效,1生效") | |||
| private Integer state; | |||
| @@ -20,108 +20,108 @@ public class Component implements Serializable { | |||
| /** | |||
| * 主键 | |||
| */ | |||
| @ApiModelProperty(name = "id") | |||
| @ApiModelProperty(name = "id", value = "主键") | |||
| private Integer id; | |||
| /** | |||
| * 类别ID,数据字典配置 | |||
| */ | |||
| @ApiModelProperty(name = "category_id") | |||
| @ApiModelProperty(name = "category_id" ,value = "类别id") | |||
| private Integer categoryId; | |||
| /** | |||
| * 组件name | |||
| */ | |||
| @ApiModelProperty(name = "category_name") | |||
| @ApiModelProperty(name = "category_name", value = "组件名称") | |||
| private String componentName; | |||
| /** | |||
| * 组件面板名 | |||
| */ | |||
| @ApiModelProperty(name = "component_label") | |||
| @ApiModelProperty(name = "component_label" , value = "组件面板名" ) | |||
| private String componentLabel; | |||
| /** | |||
| * 镜像 | |||
| */ | |||
| @JsonProperty("image") | |||
| @ApiModelProperty(name = "image") | |||
| @ApiModelProperty(name = "image" ,value = "镜像") | |||
| private String images; | |||
| /** | |||
| * 工作目录 | |||
| */ | |||
| @ApiModelProperty(name = "working_directory") | |||
| @ApiModelProperty(name = "working_directory" , value = "工作目录") | |||
| private String workingDirectory; | |||
| /** | |||
| * 启动命令 | |||
| */ | |||
| @ApiModelProperty(name = "command") | |||
| @ApiModelProperty(name = "command" , value = "启动命令") | |||
| private String command; | |||
| /** | |||
| * 环境变量 | |||
| */ | |||
| @JsonProperty("env_variables") | |||
| @ApiModelProperty(name = "env_variables") | |||
| @ApiModelProperty(name = "env_variables", value = "环境变量") | |||
| private String envVirables; | |||
| /** | |||
| * 资源规格 | |||
| */ | |||
| @ApiModelProperty(name = "resources_standard") | |||
| @ApiModelProperty(name = "resources_standard" , value = "资源规格") | |||
| private String resourcesStandard; | |||
| /** | |||
| * 控制策略 | |||
| */ | |||
| @ApiModelProperty(name = "control_strategy") | |||
| @ApiModelProperty(name = "control_strategy" ,value = "控制策略") | |||
| private String controlStrategy; | |||
| /** | |||
| * 挂载路径 | |||
| */ | |||
| @ApiModelProperty(name = "mount_path") | |||
| @ApiModelProperty(name = "mount_path" , value = "挂载路径") | |||
| private String mountPath; | |||
| /** | |||
| * 输入参数 | |||
| */ | |||
| @ApiModelProperty(name = "in_parameters") | |||
| @ApiModelProperty(name = "in_parameters" ,value = "输入参数") | |||
| private String inParameters; | |||
| /** | |||
| * 输出参数 | |||
| */ | |||
| @ApiModelProperty(name = "out_parameters") | |||
| @ApiModelProperty(name = "out_parameters" ,value = "输出参数") | |||
| private String outParameters; | |||
| /** | |||
| * 描述 | |||
| */ | |||
| @ApiModelProperty(name = "description") | |||
| @ApiModelProperty(name = "description" , value = "描述") | |||
| private String description; | |||
| /** | |||
| * 图标路径 | |||
| */ | |||
| @ApiModelProperty(name = "icon_path") | |||
| @ApiModelProperty(name = "icon_path" ,value = "图标路径") | |||
| private String iconPath; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| //@JsonProperty("creater") | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(name = "create_by" ,value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(name = "create_time" , value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| //@JsonProperty("modify_by") | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(name = "update_by" , value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(name = "update_time" , value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0,失效 1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(name = "state" , value = "状态") | |||
| private Integer state; | |||
| public Integer getId() { | |||
| @@ -22,53 +22,58 @@ public class Dataset implements Serializable { | |||
| private Integer id; | |||
| @CheckDuplicate | |||
| @ApiModelProperty(name = "name") | |||
| @ApiModelProperty(value = "名称") | |||
| private String name; | |||
| @ApiModelProperty(name = "description") | |||
| @ApiModelProperty(value = "描述") | |||
| private String description; | |||
| /** | |||
| * 是否公开: 1公开,0私有 | |||
| */ | |||
| @ApiModelProperty(name = "available_range") | |||
| @ApiModelProperty(value = "是否公开:1公开,0私有") | |||
| private int availableRange; | |||
| /** | |||
| * 数据集类型 | |||
| */ | |||
| @ApiModelProperty(name = "data_type") | |||
| @ApiModelProperty(value = "数据集类型") | |||
| private String dataType; | |||
| /** | |||
| * 数据集tag | |||
| * 数据集标签 | |||
| */ | |||
| @ApiModelProperty(name = "data_tag") | |||
| @ApiModelProperty(value = "数据集标签") | |||
| private String dataTag; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0,失效 1生效 | |||
| /** | |||
| * 状态,0失效,1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态:0失效,1生效") | |||
| private Integer state; | |||
| @@ -22,66 +22,46 @@ public class DatasetVersion implements Serializable { | |||
| */ | |||
| @ApiModelProperty(name = "id") | |||
| private Integer id; | |||
| @ApiModelProperty(name = "dataset_id") | |||
| @ApiModelProperty(value = "数据集ID") | |||
| private Integer datasetId; | |||
| /** | |||
| * 版本 | |||
| */ | |||
| @ApiModelProperty(name = "version") | |||
| @ApiModelProperty(value = "版本") | |||
| private String version; | |||
| /** | |||
| * 数据集存储地址 | |||
| */ | |||
| @ApiModelProperty(name = "url") | |||
| @ApiModelProperty(value = "数据集存储地址") | |||
| @CheckDuplicate | |||
| private String url; | |||
| /** | |||
| * 文件名 | |||
| */ | |||
| @ApiModelProperty(name = "file_name") | |||
| @ApiModelProperty(value = "文件名") | |||
| private String fileName; | |||
| /** | |||
| * 文件大小 | |||
| */ | |||
| @ApiModelProperty(name = "file_size") | |||
| @ApiModelProperty(value = "文件大小") | |||
| private String fileSize; | |||
| /** | |||
| * 可用集群 | |||
| */ | |||
| @ApiModelProperty(name = "available_cluster") | |||
| @ApiModelProperty(value = "可用集群") | |||
| private String availableCluster; | |||
| /** | |||
| * 状态 | |||
| */ | |||
| @ApiModelProperty(name = "status") | |||
| @ApiModelProperty(value = "状态") | |||
| private Integer status; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0失效,1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态:0失效,1生效") | |||
| private Integer state; | |||
| public Integer getId() { | |||
| return id; | |||
| } | |||
| @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.FieldFill; | |||
| import com.baomidou.mybatisplus.annotation.TableField; | |||
| import com.baomidou.mybatisplus.annotation.TableId; | |||
| import com.baomidou.mybatisplus.annotation.TableLogic; | |||
| import com.fasterxml.jackson.annotation.JsonRawValue; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import com.ruoyi.common.core.web.domain.BaseEntity; | |||
| @@ -24,45 +25,43 @@ import java.util.List; | |||
| @ApiModel("实验对象") | |||
| public class Experiment implements Serializable { | |||
| private static final long serialVersionUID = 409135817108439880L; | |||
| // @ApiModelProperty(name = "id") | |||
| @ApiModelProperty(value = "主键") | |||
| private Integer id; | |||
| // @ApiModelProperty(name = "name") | |||
| @ApiModelProperty(value = "名称") | |||
| private String name; | |||
| // @ApiModelProperty(name = "workflow_id") | |||
| @ApiModelProperty(value = "工作流ID") | |||
| private Long workflowId; | |||
| /** | |||
| * 全局参数 | |||
| */ | |||
| @ApiModelProperty(name = "global_param") | |||
| @ApiModelProperty(value = "全局参数,以JSON字符串格式存储") | |||
| @JsonRawValue | |||
| private String globalParam; | |||
| @ApiModelProperty(value = "状态列表") | |||
| private String statusList; | |||
| /** | |||
| * 简介 | |||
| */ | |||
| @ApiModelProperty(name = "description") | |||
| @ApiModelProperty(value = "简介") | |||
| private String description; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0,失效 1生效 | |||
| */ | |||
| @ApiModelProperty(value = "状态:0失效,1生效") | |||
| private Integer state; | |||
| @ApiModelProperty(value = "对应的实例列表") | |||
| private List<ExperimentIns> experimentInsList; | |||
| @ApiModelProperty(value = "对应的流水线名称") | |||
| private String workflowName; | |||
| public String getName() { | |||
| return name; | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ruoyi.platform.domain; | |||
| import com.fasterxml.jackson.annotation.JsonRawValue; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import io.swagger.annotations.ApiModel; | |||
| @@ -15,6 +16,7 @@ import java.util.Date; | |||
| * @since 2023-11-09 09:48:36 | |||
| */ | |||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||
| @ApiModel("实验实例对象") | |||
| public class ExperimentIns implements Serializable { | |||
| private static final long serialVersionUID = 623464560240790680L; | |||
| @ApiModelProperty(name = "id") | |||
| @@ -22,69 +24,53 @@ public class ExperimentIns implements Serializable { | |||
| /** | |||
| * 实验ID | |||
| */ | |||
| @ApiModelProperty(name = "experiment_id") | |||
| @ApiModelProperty(value = "实验ID") | |||
| private Integer experimentId; | |||
| /** | |||
| * argo返回name | |||
| */ | |||
| @ApiModelProperty(name = "argo_ins_name") | |||
| @ApiModelProperty(value = "Argo实例名称") | |||
| private String argoInsName; | |||
| /** | |||
| * argo返回命名空间 | |||
| */ | |||
| @ApiModelProperty(name = "argo_ins_ns") | |||
| @ApiModelProperty(value = "Argo命名空间") | |||
| private String argoInsNs; | |||
| /** | |||
| * 实例运行状态 | |||
| */ | |||
| @ApiModelProperty(name = "status") | |||
| @ApiModelProperty(value = "实例运行状态") | |||
| private String status; | |||
| @ApiModelProperty(name = "nodes_status") | |||
| @ApiModelProperty(value = "节点状态") | |||
| private String nodesStatus; | |||
| @ApiModelProperty(name = "nodes_result") | |||
| @ApiModelProperty(value = "节点结果") | |||
| private String nodesResult; | |||
| @ApiModelProperty(name = "nodes_logs") | |||
| @ApiModelProperty(value = "节点日志") | |||
| private String nodesLogs; | |||
| /** | |||
| * 开始时间 | |||
| */ | |||
| @ApiModelProperty(name = "start_time") | |||
| @ApiModelProperty(value = "实验实例全局参数", notes = "以JSON字符串格式存储") | |||
| @JsonRawValue | |||
| private String globalParam; | |||
| @ApiModelProperty(value = "开始时间") | |||
| private Date startTime; | |||
| /** | |||
| * 结束时间 | |||
| */ | |||
| @ApiModelProperty(name = "finish_time") | |||
| @ApiModelProperty(value = "结束时间") | |||
| private Date finishTime; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0,失效 1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态:0失效,1生效") | |||
| private Integer state; | |||
| public ExperimentIns() { | |||
| } | |||
| @@ -154,6 +140,14 @@ public class ExperimentIns implements Serializable { | |||
| this.nodesLogs = nodesLogs; | |||
| } | |||
| public String getGlobalParam() { | |||
| return globalParam; | |||
| } | |||
| public void setGlobalParam(String globalParam) { | |||
| this.globalParam = globalParam; | |||
| } | |||
| public void setStartTime(Date startTime) { | |||
| this.startTime = startTime; | |||
| } | |||
| @@ -19,51 +19,37 @@ public class Image implements Serializable { | |||
| /** | |||
| * 主键 | |||
| */ | |||
| @ApiModelProperty(value = "主键") | |||
| private Integer id; | |||
| /** | |||
| * 镜像名称 | |||
| */ | |||
| @ApiModelProperty(name = "name") | |||
| @ApiModelProperty(value = "名称") | |||
| private String name; | |||
| /** | |||
| * 镜像描述 | |||
| */ | |||
| @ApiModelProperty(name = "description") | |||
| @ApiModelProperty(value = "镜像描述") | |||
| private String description; | |||
| /** | |||
| * 镜像类型 | |||
| */ | |||
| @ApiModelProperty(name = "image_type") | |||
| @ApiModelProperty(value = "镜像类型") | |||
| private Integer imageType; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 状态,0失效,1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态,0失效,1生效") | |||
| private Integer state; | |||
| private Integer versionCount; // 版本数量统计 | |||
| @ApiModelProperty(value = "版本数量统计") | |||
| private Integer versionCount; | |||
| @@ -20,63 +20,40 @@ public class ImageVersion implements Serializable { | |||
| * 主键 | |||
| */ | |||
| @ApiModelProperty(name = "id") | |||
| @ApiModelProperty(value = "ID") | |||
| private Integer id; | |||
| /** | |||
| * 对应的镜像id | |||
| */ | |||
| @ApiModelProperty(name = "image_id") | |||
| @ApiModelProperty(value = "对应的镜像ID") | |||
| private Integer imageId; | |||
| /** | |||
| * 镜像版本 | |||
| */ | |||
| @ApiModelProperty(name = "version") | |||
| @ApiModelProperty(value = "镜像版本") | |||
| private String version; | |||
| /** | |||
| * 镜像推送地址 | |||
| */ | |||
| @ApiModelProperty(name = "url") | |||
| @ApiModelProperty(value = "镜像推送地址") | |||
| private String url; | |||
| /** | |||
| * 镜像tag名称 | |||
| */ | |||
| @ApiModelProperty(name = "tag_name") | |||
| @ApiModelProperty(value = "镜像tag名称") | |||
| private String tagName; | |||
| /** | |||
| * 镜像文件大小 | |||
| */ | |||
| @ApiModelProperty(name = "file_size") | |||
| @ApiModelProperty(value = "镜像文件大小") | |||
| private String fileSize; | |||
| /** | |||
| * 镜像构建状态 | |||
| */ | |||
| @ApiModelProperty(name = "status") | |||
| @ApiModelProperty(value = "镜像构建状态") | |||
| private String status; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 状态,0失效,1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态,0失效,1生效") | |||
| private Integer state; | |||
| @@ -18,51 +18,37 @@ import java.io.Serializable; | |||
| @ApiModel("模型对象") | |||
| public class Models implements Serializable { | |||
| private static final long serialVersionUID = -59896385986032571L; | |||
| @ApiModelProperty(name = "id") | |||
| @ApiModelProperty(value = "ID") | |||
| private Integer id; | |||
| @ApiModelProperty(name = "name") | |||
| @ApiModelProperty(value = "模型名称") | |||
| private String name; | |||
| // private String version; | |||
| @ApiModelProperty(name = "description") | |||
| @ApiModelProperty(value = "模型描述") | |||
| private String description; | |||
| /** | |||
| * 模型可见范围 | |||
| */ | |||
| @ApiModelProperty(name = "available_range") | |||
| @ApiModelProperty(value = "模型可见范围,1表示公开,0表示私有") | |||
| private int availableRange; | |||
| @ApiModelProperty(name = "model_type") | |||
| @ApiModelProperty(value = "模型类型") | |||
| private String modelType; | |||
| @ApiModelProperty(name = "model_tag") | |||
| @ApiModelProperty(value = "模型标签") | |||
| private String modelTag; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0,失效 1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态,0失效,1生效") | |||
| private Integer state; | |||
| @@ -21,59 +21,40 @@ public class ModelsVersion implements Serializable { | |||
| /** | |||
| * 主键 | |||
| */ | |||
| @ApiModelProperty(name = "id") | |||
| private Integer id; | |||
| @ApiModelProperty(name = "models_id") | |||
| @ApiModelProperty(value = "ID") | |||
| private Integer id; | |||
| @ApiModelProperty(value = "模型ID") | |||
| private Integer modelsId; | |||
| /** | |||
| * 版本 | |||
| */ | |||
| @ApiModelProperty(name = "version") | |||
| @ApiModelProperty(value = "版本") | |||
| private String version; | |||
| /** | |||
| * 模型存储地址 | |||
| */ | |||
| @ApiModelProperty(name = "url") | |||
| @ApiModelProperty(value = "模型存储地址") | |||
| private String url; | |||
| /** | |||
| * 文件名 | |||
| */ | |||
| @ApiModelProperty(name = "file_name") | |||
| @ApiModelProperty(value = "文件名") | |||
| private String fileName; | |||
| /** | |||
| * 文件大小 | |||
| */ | |||
| @ApiModelProperty(name = "file_size") | |||
| @ApiModelProperty(value = "文件大小") | |||
| private String fileSize; | |||
| /** | |||
| * 状态 | |||
| */ | |||
| @ApiModelProperty(name = "status") | |||
| @ApiModelProperty(value = "状态") | |||
| private Integer status; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0失效,1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态,0失效,1生效") | |||
| private Integer state; | |||
| @@ -2,6 +2,7 @@ package com.ruoyi.platform.domain; | |||
| import com.baomidou.mybatisplus.annotation.TableId; | |||
| import com.fasterxml.jackson.annotation.JsonFormat; | |||
| import com.fasterxml.jackson.annotation.JsonRawValue; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import com.ruoyi.platform.handler.BaseMetaObjectHandler; | |||
| @@ -28,48 +29,35 @@ public class Workflow extends BaseMetaObjectHandler implements Serializable { | |||
| /** | |||
| * id | |||
| */ | |||
| @TableId | |||
| @ApiModelProperty(name = "id") | |||
| @ApiModelProperty(value = "ID") | |||
| private Long id; | |||
| /** | |||
| * 工作流名称 | |||
| */ | |||
| @ApiModelProperty(name = "name") | |||
| @ApiModelProperty(value = "工作流名称") | |||
| private String name; | |||
| /** | |||
| * DAG工作流描述 | |||
| */ | |||
| @ApiModelProperty(name = "description") | |||
| @ApiModelProperty(value = "流水线描述") | |||
| private String description; | |||
| /** | |||
| * DAG图 | |||
| */ | |||
| @ApiModelProperty(name = "dag") | |||
| @ApiModelProperty(value = "DAG图") | |||
| private String dag; | |||
| /** | |||
| * 创建者 | |||
| */ | |||
| @ApiModelProperty(name = "create_by") | |||
| @JsonRawValue | |||
| @ApiModelProperty(value = "全局参数") | |||
| private String globalParam; | |||
| @ApiModelProperty(value = "创建者") | |||
| private String createBy; | |||
| /** | |||
| * 创建时间 | |||
| */ | |||
| @ApiModelProperty(name = "create_time") | |||
| @ApiModelProperty(value = "创建时间") | |||
| private Date createTime; | |||
| /** | |||
| * 更新者 | |||
| */ | |||
| @ApiModelProperty(name = "update_by") | |||
| @ApiModelProperty(value = "更新者") | |||
| private String updateBy; | |||
| /** | |||
| * 更新时间 | |||
| */ | |||
| @ApiModelProperty(name = "update_time") | |||
| @ApiModelProperty(value = "更新时间") | |||
| private Date updateTime; | |||
| /** | |||
| * 0,失效 1生效 | |||
| */ | |||
| @ApiModelProperty(name = "state") | |||
| @ApiModelProperty(value = "状态,0失效,1生效") | |||
| private Integer state; | |||
| @@ -105,6 +93,14 @@ public class Workflow extends BaseMetaObjectHandler implements Serializable { | |||
| this.dag = dag; | |||
| } | |||
| public String getGlobalParam() { | |||
| return globalParam; | |||
| } | |||
| public void setGlobalParam(String globalParam) { | |||
| this.globalParam = globalParam; | |||
| } | |||
| public String getCreateBy() { | |||
| return createBy; | |||
| } | |||
| @@ -90,5 +90,7 @@ public interface ExperimentInsDao { | |||
| List<ExperimentIns> getByExperimentId(Integer experimentId); | |||
| List<ExperimentIns> queryByExperiment(@Param("experimentIns") ExperimentIns experimentIns); | |||
| List<ExperimentIns> queryByExperimentId(Integer id); | |||
| } | |||
| @@ -2,6 +2,7 @@ package com.ruoyi.platform.service; | |||
| import com.ruoyi.platform.domain.ExperimentIns; | |||
| import com.ruoyi.platform.vo.LogRequestVo; | |||
| import com.ruoyi.platform.vo.PodLogVo; | |||
| import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageRequest; | |||
| @@ -94,4 +95,7 @@ public interface ExperimentInsService { | |||
| Map<String, Object> getRealtimeWorkflowLog(LogRequestVo logRequest); | |||
| Map<String, Object> getRealtimePodLog(String podName, String startTime); | |||
| String getRealtimePodLogFromPod(PodLogVo podLogVo); | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ruoyi.platform.service; | |||
| import com.fasterxml.jackson.core.JsonProcessingException; | |||
| import com.ruoyi.platform.domain.Workflow; | |||
| import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageRequest; | |||
| @@ -9,6 +9,7 @@ import com.ruoyi.platform.service.ExperimentInsService; | |||
| import com.ruoyi.platform.service.WorkflowService; | |||
| import com.ruoyi.platform.utils.*; | |||
| import com.ruoyi.platform.vo.LogRequestVo; | |||
| import com.ruoyi.platform.vo.PodLogVo; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| @@ -51,6 +52,9 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| private String argoWorkflowRealTimeLog; | |||
| @Value("${argo.workflowPodLog}") | |||
| private String argoWorkflowPodLog; | |||
| @Value("${argo.ins.logsLines}") | |||
| private int logsLines; | |||
| private final MinioUtil minioUtil; | |||
| public ExperimentInsServiceImpl(MinioUtil minioUtil) { | |||
| @@ -91,7 +95,7 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| //搞个标记,当状态改变才去改表 | |||
| boolean flag = false; | |||
| List<ExperimentIns> result = new ArrayList<ExperimentIns>(); | |||
| if (experimentInsList!=null&&experimentInsList.size()>0) { | |||
| if (experimentInsList!=null && experimentInsList.size()>0) { | |||
| for (ExperimentIns experimentIns : experimentInsList) { | |||
| //当原本状态为null或非终止态时才调用argo接口 | |||
| if (experimentIns != null && (StringUtils.isEmpty(experimentIns.getStatus())) || !isTerminatedState(experimentIns)) { | |||
| @@ -107,11 +111,9 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| } | |||
| //新增查询tensorBoard容器状态 | |||
| result.add(experimentIns); | |||
| } | |||
| } | |||
| if (flag) { | |||
| List<String> statusList = new ArrayList<String>(); | |||
| // 更新实验状态列表 | |||
| @@ -227,9 +229,7 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| @Override | |||
| public List<ExperimentIns> queryByExperimentId(Integer id) { | |||
| ExperimentIns experimentIns = new ExperimentIns(); | |||
| experimentIns.setExperimentId(id); | |||
| return experimentInsDao.queryByExperiment(experimentIns); | |||
| return experimentInsDao.queryByExperimentId(id); | |||
| } | |||
| @Override | |||
| @@ -518,6 +518,11 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| } | |||
| @Override | |||
| public String getRealtimePodLogFromPod(PodLogVo podLogVo) { | |||
| return K8sClientUtil.getPodLogs(podLogVo.getPodName(), podLogVo.getNamespace(),podLogVo.getContainerName(), logsLines); | |||
| } | |||
| private boolean isTerminatedState(ExperimentIns ins) throws IOException { | |||
| // 定义终止态的列表,例如 "Succeeded", "Failed" 等 | |||
| String status = ins.getStatus(); | |||
| @@ -10,6 +10,7 @@ import com.ruoyi.platform.service.ExperimentInsService; | |||
| import com.ruoyi.platform.service.ExperimentService; | |||
| import com.ruoyi.platform.service.WorkflowService; | |||
| import com.ruoyi.platform.utils.HttpUtils; | |||
| import com.ruoyi.platform.utils.JacksonUtil; | |||
| import com.ruoyi.platform.utils.JsonUtils; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| import org.apache.commons.collections4.MapUtils; | |||
| @@ -225,7 +226,12 @@ public class ExperimentServiceImpl implements ExperimentService { | |||
| // 组装运行接口json | |||
| Map<String, Object> runReqMap = new HashMap<>(); | |||
| runReqMap.put("data", converMap.get("data")); | |||
| runReqMap.put("params", JsonUtils.jsonToMap(StringUtils.isEmpty(experiment.getGlobalParam())?"{}":experiment.getGlobalParam())); | |||
| //这里全局参数是一个json数组,需要转换成一个list<Map> | |||
| List<Map<String, Object>> params = JacksonUtil.parseJSONStr2MapList(StringUtils.isEmpty(experiment.getGlobalParam()) ? "[]" : experiment.getGlobalParam()); | |||
| runReqMap.put("params", params); | |||
| runReqMap.put("experiment", new HashMap<String, Object>().put("name", "experiment-"+experiment.getId())); | |||
| Map<String ,Object> output = (Map<String, Object>) converMap.get("output"); | |||
| // 调argo运行接口 | |||
| String runRes = HttpUtils.sendPost(argoUrl + argoWorkflowRun, JsonUtils.mapToJson(runReqMap)); | |||
| @@ -243,10 +249,15 @@ public class ExperimentServiceImpl implements ExperimentService { | |||
| Map<String, Object> metadata = (Map<String, Object>) data.get("metadata"); | |||
| // 插入记录到实验实例表 | |||
| ExperimentIns experimentIns = new ExperimentIns(); | |||
| experimentIns.setExperimentId(experiment.getId()); | |||
| experimentIns.setArgoInsNs((String) metadata.get("namespace")); | |||
| experimentIns.setArgoInsName((String) metadata.get("name")); | |||
| //传入实验全局参数 | |||
| experimentIns.setGlobalParam(experiment.getGlobalParam()); | |||
| //替换argoInsName | |||
| String outputString = JsonUtils.mapToJson(output); | |||
| @@ -257,10 +268,9 @@ public class ExperimentServiceImpl implements ExperimentService { | |||
| }catch (Exception e){ | |||
| throw new RuntimeException(e); | |||
| } | |||
| List<ExperimentIns> experimentIns = experimentInsService.queryByExperimentId(id); | |||
| List<ExperimentIns> updatedExperimentInsList = experimentInsService.getByExperimentId(id); | |||
| experiment.setExperimentInsList(experimentIns); | |||
| experiment.setExperimentInsList(updatedExperimentInsList); | |||
| return experiment; | |||
| } | |||
| @@ -153,6 +153,8 @@ public class WorkflowServiceImpl implements WorkflowService { | |||
| return new PageImpl<>(this.workflowDao.queryByName(name)); | |||
| } | |||
| @Override | |||
| public Workflow duplicateWorkflow(Long id) { | |||
| //先去查找数据库中存在的数据 | |||
| @@ -180,6 +182,7 @@ public class WorkflowServiceImpl implements WorkflowService { | |||
| } | |||
| duplicateWorkflow.setDag(newDag); | |||
| duplicateWorkflow.setDescription(workflow.getDescription()); | |||
| duplicateWorkflow.setGlobalParam(workflow.getGlobalParam()); | |||
| return this.insert(duplicateWorkflow); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException("复制流水线失败: " + e.getMessage(), e); | |||
| @@ -1,13 +1,16 @@ | |||
| package com.ruoyi.platform.utils; | |||
| import com.alibaba.nacos.shaded.com.google.gson.reflect.TypeToken; | |||
| import io.kubernetes.client.Exec; | |||
| import io.kubernetes.client.custom.IntOrString; | |||
| import io.kubernetes.client.custom.Quantity; | |||
| import io.kubernetes.client.openapi.ApiClient; | |||
| import io.kubernetes.client.openapi.ApiException; | |||
| import io.kubernetes.client.openapi.ApiResponse; | |||
| import io.kubernetes.client.openapi.apis.CoreV1Api; | |||
| import io.kubernetes.client.openapi.models.*; | |||
| import io.kubernetes.client.util.ClientBuilder; | |||
| import io.kubernetes.client.util.Watch; | |||
| import io.kubernetes.client.util.credentials.AccessTokenAuthentication; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.apache.commons.lang.StringUtils; | |||
| @@ -15,7 +18,6 @@ import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.stereotype.Component; | |||
| import java.io.BufferedReader; | |||
| import java.io.IOException; | |||
| import java.io.InputStreamReader; | |||
| import java.util.HashMap; | |||
| import java.util.LinkedHashMap; | |||
| @@ -402,13 +404,14 @@ public class K8sClientUtil { | |||
| return pod.getStatus().getPhase(); | |||
| } | |||
| public static String getPodLogs(String podName,String namespace,int line) { | |||
| public static String getPodLogs(String podName,String namespace,String container,int line) { | |||
| CoreV1Api api = new CoreV1Api(apiClient); | |||
| try { | |||
| String log = api.readNamespacedPodLog(podName, namespace, null, null, null, null, null,null, null, line, null); | |||
| String log = api.readNamespacedPodLog(podName, namespace, StringUtils.isEmpty(container)?null:container, null, null, null, null,null, null, line, null); | |||
| return log; | |||
| } catch (ApiException e) { | |||
| throw new RuntimeException("获取Pod日志异常", e); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| package com.ruoyi.platform.vo; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import java.io.Serializable; | |||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||
| public class PodLogVo implements Serializable { | |||
| private String podName; | |||
| private String namespace; | |||
| private String containerName; | |||
| public String getPodName() { | |||
| return podName; | |||
| } | |||
| public void setPodName(String podName) { | |||
| this.podName = podName; | |||
| } | |||
| public String getNamespace() { | |||
| return namespace; | |||
| } | |||
| public void setNamespace(String namespace) { | |||
| this.namespace = namespace; | |||
| } | |||
| public String getContainerName() { | |||
| return containerName; | |||
| } | |||
| public void setContainerName(String containerName) { | |||
| this.containerName = containerName; | |||
| } | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| package com.ruoyi.platform.webSocket; | |||
| import javax.annotation.Resource; | |||
| import javax.websocket.*; | |||
| import javax.websocket.server.PathParam; | |||
| import javax.websocket.server.ServerEndpoint; | |||
| import com.ruoyi.platform.service.ExperimentInsService; | |||
| import com.ruoyi.platform.utils.K8sClientUtil; | |||
| import org.springframework.stereotype.Component; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| import java.io.IOException; | |||
| import java.util.concurrent.CopyOnWriteArraySet; | |||
| import java.util.concurrent.ConcurrentHashMap; | |||
| @Component | |||
| @Slf4j | |||
| @ServerEndpoint("/websocket/workflowLogs") | |||
| @RestController | |||
| public class WebSocket { | |||
| private Session session; | |||
| private String userId; | |||
| private static CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>(); | |||
| private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>(); | |||
| @OnOpen | |||
| public void onOpen(Session session, @PathParam("userId") String userId) { | |||
| try { | |||
| this.session = session; | |||
| this.userId = "workflowLogs"; | |||
| webSockets.add(this); | |||
| sessionPool.put(userId, session); | |||
| log.info("【WebSocket 消息】有新的连接,总数为:" + webSockets.size()); | |||
| } catch (Exception e) { | |||
| // 异常处理 | |||
| } | |||
| } | |||
| @OnClose | |||
| public void onClose() { | |||
| try { | |||
| webSockets.remove(this); | |||
| sessionPool.remove(this.userId); | |||
| log.info("【WebSocket 消息】连接断开,总数为:" + webSockets.size()); | |||
| } catch (Exception e) { | |||
| // 异常处理 | |||
| } | |||
| } | |||
| @OnMessage | |||
| public void onMessage(String message, Session session) { | |||
| log.info("【WebSocket 消息】收到客户端消息:" + message); | |||
| // 处理收到的消息,例如获取实时日志数据 | |||
| // 推送日志数据给客户端 | |||
| try { | |||
| session.getBasicRemote().sendText("nihao"); | |||
| } catch (Exception e) { | |||
| log.error("【WebSocket 消息】推送日志数据失败:" + e.getMessage()); | |||
| } | |||
| } | |||
| @OnError | |||
| public void onError(Session session, Throwable error) { | |||
| log.error("【WebSocket 消息】发生错误:" + error.getMessage()); | |||
| } | |||
| } | |||
| @@ -84,7 +84,7 @@ | |||
| and global_param = #{experiment.globalParam} | |||
| </if> | |||
| <if test="experiment.statusList != null and experiment.statusList != ''"> | |||
| status_list = #{experiment.statusList}, | |||
| status_list = #{experiment.statusList} | |||
| </if> | |||
| <if test="experiment.description != null and experiment.description != ''"> | |||
| and description = #{experiment.description} | |||
| @@ -125,7 +125,7 @@ | |||
| and global_param = #{experiment.globalParam} | |||
| </if> | |||
| <if test="experiment.statusList != null and experiment.statusList != ''"> | |||
| status_list = #{experiment.statusList}, | |||
| status_list = #{experiment.statusList} | |||
| </if> | |||
| <if test="experiment.description != null and experiment.description != ''"> | |||
| and description = #{experiment.description} | |||
| @@ -148,7 +148,7 @@ | |||
| <!--新增所有列--> | |||
| <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into experiment(name,workflow_id, global_param, status_list, description, create_by, create_time, update_by, update_time, state) | |||
| values (#{experiment.name},#{experiment.workflowId}, #{experiment.globalParam},#{experiment.statusList}, #{experiment.description}, #{experiment.createBy}, #{experiment.createTime}, #{experiment.updateBy}, #{experiment.updateTime}, #{experiment.state}) | |||
| values (#{experiment.name},#{experiment.workflowId}, #{experiment.globalParam},#{experiment.statusList}, #{experiment.description}, #{experiment.createBy}, #{experiment.createTime}, #{experiment.updateBy}, #{experiment.updateTime}, #{experiment.state}) | |||
| </insert> | |||
| <insert id="insertBatch" keyProperty="id" useGeneratedKeys="true"> | |||
| @@ -11,6 +11,7 @@ | |||
| <result property="nodesStatus" column="nodes_status" jdbcType="VARCHAR"/> | |||
| <result property="nodesResult" column="nodes_result" jdbcType="VARCHAR"/> | |||
| <result property="nodesLogs" column="nodes_logs" jdbcType="VARCHAR"/> | |||
| <result property="globalParam" column="global_param" jdbcType="VARCHAR"/> | |||
| <result property="startTime" column="start_time" jdbcType="TIMESTAMP"/> | |||
| <result property="finishTime" column="finish_time" jdbcType="TIMESTAMP"/> | |||
| <result property="createBy" column="create_by" jdbcType="VARCHAR"/> | |||
| @@ -24,14 +25,14 @@ | |||
| <!--查询单个--> | |||
| <select id="queryById" resultMap="ExperimentInsMap"> | |||
| select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| from experiment_ins | |||
| where id = #{id} and state = 1 | |||
| </select> | |||
| <!--查询列表--> | |||
| <select id="getByExperimentId" resultMap="ExperimentInsMap"> | |||
| select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| from experiment_ins | |||
| where experiment_id = #{experiment_id} and state = 1 | |||
| order by create_time DESC | |||
| @@ -41,7 +42,7 @@ | |||
| <select id="queryByExperiment" resultMap="ExperimentInsMap"> | |||
| select | |||
| id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| from experiment_ins | |||
| <where> | |||
| state = 1 | |||
| @@ -66,6 +67,9 @@ | |||
| <if test="experimentIns.nodesLogs != null and experimentIns.nodesLogs != ''"> | |||
| and nodes_logs = #{experimentIns.nodesLogs} | |||
| </if> | |||
| <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> | |||
| and global_param = #{experimentIns.globalParam} | |||
| </if> | |||
| <if test="experimentIns.startTime != null"> | |||
| and start_time = #{experimentIns.startTime} | |||
| </if> | |||
| @@ -90,7 +94,7 @@ | |||
| <!--查询指定行数据--> | |||
| <select id="queryAllByLimit" resultMap="ExperimentInsMap"> | |||
| select | |||
| id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| from experiment_ins | |||
| <where> | |||
| state = 1 | |||
| @@ -115,6 +119,9 @@ | |||
| <if test="experimentIns.nodesLogs != null and experimentIns.nodesLogs != ''"> | |||
| and nodes_logs = #{experimentIns.nodesLogs} | |||
| </if> | |||
| <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> | |||
| and global_param = #{experimentIns.globalParam} | |||
| </if> | |||
| <if test="experimentIns.startTime != null"> | |||
| and start_time = #{experimentIns.startTime} | |||
| </if> | |||
| @@ -165,6 +172,9 @@ | |||
| <if test="experimentIns.nodesLogs != null and experimentIns.nodesLogs != ''"> | |||
| and nodes_logs = #{experimentIns.nodesLogs} | |||
| </if> | |||
| <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> | |||
| and global_param = #{experimentIns.globalParam} | |||
| </if> | |||
| <if test="experimentIns.startTime != null"> | |||
| and start_time = #{experimentIns.startTime} | |||
| </if> | |||
| @@ -186,18 +196,24 @@ | |||
| </where> | |||
| </select> | |||
| <select id="queryByExperimentId" resultMap="ExperimentInsMap"> | |||
| select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state | |||
| from experiment_ins | |||
| where experiment_id = #{id} and state = 1 | |||
| </select> | |||
| <!--新增所有列--> | |||
| <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,start_time,finish_time,create_by,create_time,update_by,update_time,state) | |||
| values (#{experimentIns.experimentId},#{experimentIns.argoInsName},#{experimentIns.argoInsNs},#{experimentIns.status},#{experimentIns.nodesStatus},#{experimentIns.nodesResult},#{experimentIns.nodesLogs},#{experimentIns.startTime},#{experimentIns.finishTime},#{experimentIns.createBy},#{experimentIns.createTime},#{experimentIns.updateBy},#{experimentIns.updateTime},#{experimentIns.state}) | |||
| insert into experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,start_time,finish_time,create_by,create_time,update_by,update_time,state) | |||
| values (#{experimentIns.experimentId},#{experimentIns.argoInsName},#{experimentIns.argoInsNs},#{experimentIns.status},#{experimentIns.nodesStatus},#{experimentIns.nodesResult},#{experimentIns.nodesLogs},#{experimentIns.globalParam},#{experimentIns.startTime},#{experimentIns.finishTime},#{experimentIns.createBy},#{experimentIns.createTime},#{experimentIns.updateBy},#{experimentIns.updateTime},#{experimentIns.state}) | |||
| </insert> | |||
| <insert id="insertBatch" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into | |||
| experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,start_time,finish_time,create_by,create_time,update_by,update_time,state) | |||
| experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,start_time,finish_time,create_by,create_time,update_by,update_time,state) | |||
| values | |||
| <foreach collection="entities" item="entity" separator=","> | |||
| (#{entity.experimentId},#{entity.argoInsName},#{entity.argoInsNs},#{entity.status},#{entity.nodesStatus},#{entity.nodesResult},#{entity.nodesLogs},#{entity.startTime},#{entity.finishTime},#{entity.createBy},#{entity.createTime},#{entity.updateBy},#{entity.updateTime},#{entity.state}) | |||
| (#{entity.experimentId},#{entity.argoInsName},#{entity.argoInsNs},#{entity.status},#{entity.nodesStatus},#{entity.nodesResult},#{entity.nodesLogs},#{entity.globalParam},#{entity.startTime},#{entity.finishTime},#{entity.createBy},#{entity.createTime},#{entity.updateBy},#{entity.updateTime},#{entity.state}) | |||
| </foreach> | |||
| </insert> | |||
| @@ -238,6 +254,9 @@ | |||
| <if test="experimentIns.nodesLogs != null and experimentIns.nodesLogs != ''"> | |||
| nodes_logs = #{experimentIns.nodesLogs}, | |||
| </if> | |||
| <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> | |||
| global_param = #{experimentIns.globalParam}, | |||
| </if> | |||
| <if test="experimentIns.startTime != null"> | |||
| start_time = #{experimentIns.startTime}, | |||
| </if> | |||
| @@ -7,6 +7,7 @@ | |||
| <result property="name" column="name" jdbcType="VARCHAR"/> | |||
| <result property="description" column="description" jdbcType="VARCHAR"/> | |||
| <result property="dag" column="dag" jdbcType="VARCHAR"/> | |||
| <result property="globalParam" column="global_param" jdbcType="VARCHAR"/> | |||
| <result property="createBy" column="create_by" jdbcType="VARCHAR"/> | |||
| <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/> | |||
| <result property="updateBy" column="update_by" jdbcType="VARCHAR"/> | |||
| @@ -17,7 +18,7 @@ | |||
| <!--查询单个--> | |||
| <select id="queryById" resultMap="WorkflowMap"> | |||
| select | |||
| id, name, description, dag, create_by, create_time, update_by, update_time, state | |||
| id, name, description, dag, global_param, create_by, create_time, update_by, update_time, state | |||
| from workflow | |||
| where id = #{id} and state = 1 | |||
| </select> | |||
| @@ -25,7 +26,7 @@ | |||
| <!--查询指定行数据--> | |||
| <select id="queryAllByLimit" resultMap="WorkflowMap" > | |||
| select | |||
| id, name, description, dag, create_by, create_time, update_by, update_time, state | |||
| id, name, description, dag, global_param, create_by, create_time, update_by, update_time, state | |||
| from workflow | |||
| <where> | |||
| state = 1 | |||
| @@ -41,6 +42,9 @@ | |||
| <if test="workflow.dag != null and workflow.dag != ''"> | |||
| and dag = #{workflow.dag} | |||
| </if> | |||
| <if test="workflow.globalParam != null and workflow.globalParam != ''"> | |||
| and global_param = #{workflow.globalParam} | |||
| </if> | |||
| <if test="workflow.createBy != null and workflow.createBy != ''"> | |||
| and create_by = #{workflow.createBy} | |||
| </if> | |||
| @@ -76,6 +80,9 @@ | |||
| <if test="workflow.dag != null and workflow.dag != ''"> | |||
| and dag = #{workflow.dag} | |||
| </if> | |||
| <if test="workflow.globalParam != null and workflow.globalParam != ''"> | |||
| and global_param = #{workflow.globalParam} | |||
| </if> | |||
| <if test="workflow.createBy != null and workflow.createBy != ''"> | |||
| and create_by = #{workflow.createBy} | |||
| </if> | |||
| @@ -93,26 +100,26 @@ | |||
| <!--新增所有列--> | |||
| <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into workflow(name, description, dag, create_by, create_time, update_by, update_time,state) | |||
| values (#{workflow.name}, #{workflow.description}, #{workflow.dag}, #{workflow.createBy}, #{workflow.createTime}, #{workflow.updateBy}, #{workflow.updateTime}, #{workflow.state}) | |||
| insert into workflow(name, description, dag, global_param, create_by, create_time, update_by, update_time,state) | |||
| values (#{workflow.name}, #{workflow.description}, #{workflow.dag},#{workflow.globalParam}, #{workflow.createBy}, #{workflow.createTime}, #{workflow.updateBy}, #{workflow.updateTime}, #{workflow.state}) | |||
| </insert> | |||
| <insert id="insertBatch" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into workflow(name, description, dag, create_by, create_time, update_by, update_time, state) | |||
| insert into workflow(name, description, dag, global_param, create_by, create_time, update_by, update_time, state) | |||
| values | |||
| <foreach collection="entities" item="entity" separator=","> | |||
| (#{entity.name}, #{entity.description}, #{entity.dag}, #{entity.createBy}, #{entity.createTime}, | |||
| (#{entity.name}, #{entity.description}, #{entity.dag}, #{entity.globalParam}, #{entity.createBy}, #{entity.createTime}, | |||
| #{entity.updateBy}, #{entity.updateTime}, #{entity.state}) | |||
| </foreach> | |||
| </insert> | |||
| <insert id="insertOrUpdateBatch" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into workflow(name, description, dag, create_by, create_time, update_by, update_time, state) | |||
| insert into workflow(name, description, dag, global_param, create_by, create_time, update_by, update_time, state) | |||
| values | |||
| <foreach collection="entities" item="entity" separator=","> | |||
| (#{entity.name}, #{entity.description}, #{entity.dag}, #{entity.createBy}, #{entity.createTime}, | |||
| (#{entity.name}, #{entity.description}, #{entity.dag}, #{entity.globalParam},#{entity.createBy}, #{entity.createTime}, | |||
| #{entity.updateBy}, #{entity.updateTime}, #{entity.state}) | |||
| </foreach> | |||
| on duplicate key update | |||
| @@ -139,6 +146,9 @@ | |||
| <if test="workflow.dag != null and workflow.dag != ''"> | |||
| dag = #{workflow.dag}, | |||
| </if> | |||
| <if test="workflow.globalParam != null and workflow.globalParam != ''"> | |||
| global_param = #{workflow.globalParam}, | |||
| </if> | |||
| <if test="workflow.createBy != null and workflow.createBy != ''"> | |||
| create_by = #{workflow.createBy}, | |||
| </if> | |||
| @@ -166,7 +176,7 @@ | |||
| <!--通过流水线名字进行模糊查询--> | |||
| <select id="queryByName" resultMap="WorkflowMap"> | |||
| select | |||
| id, name, description, dag, create_by, create_time, update_by, update_time, state | |||
| id, name, description, dag, global_param, create_by, create_time, update_by, update_time, state | |||
| from workflow | |||
| <where> | |||
| state = 1 | |||
| @@ -7,7 +7,7 @@ | |||
| <result property="workflowId" column="workflow_id" jdbcType="INTEGER"/> | |||
| <result property="paramName" column="param_name" jdbcType="VARCHAR"/> | |||
| <result property="description" column="description" jdbcType="VARCHAR"/> | |||
| <result property="paramType" column="param_type" jdbcType="INTEGER"/> | |||
| <result property="paramType" column="param_type" jdbcType="VARCHAR"/> | |||
| <result property="paramValue" column="param_value" jdbcType="VARCHAR"/> | |||
| <result property="isSensitive" column="is_sensitive" jdbcType="INTEGER"/> | |||
| <result property="createBy" column="create_by" jdbcType="VARCHAR"/> | |||