Browse Source

Merge pull request '合并41' (#9) from dev into master

master-fzznrj423
fanshuai 1 year ago
parent
commit
57ed882836
65 changed files with 3303 additions and 2543 deletions
  1. +3
    -0
      react-ui/.eslintrc.js
  2. +4
    -4
      react-ui/config/proxy.ts
  3. +25
    -10
      react-ui/config/routes.ts
  4. +21
    -70
      react-ui/src/app.tsx
  5. +3
    -0
      react-ui/src/dayjsConfig.ts
  6. +1
    -0
      react-ui/src/enums/status.d.ts
  7. +6
    -0
      react-ui/src/hooks/dict.ts
  8. +12
    -0
      react-ui/src/hooks/index.ts
  9. +0
    -7
      react-ui/src/hooks/net/dict.ts
  10. +10
    -0
      react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.less
  11. +11
    -0
      react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx
  12. +0
    -14
      react-ui/src/pages/DevelopmentEnvironment/index.jsx
  13. +22
    -0
      react-ui/src/pages/DevelopmentEnvironment/index.tsx
  14. +18
    -0
      react-ui/src/pages/Experiment/experimentText/LogList.tsx
  15. +51
    -0
      react-ui/src/pages/Experiment/experimentText/addExperimentModal.less
  16. +127
    -0
      react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx
  17. +373
    -343
      react-ui/src/pages/Experiment/experimentText/index.jsx
  18. +34
    -0
      react-ui/src/pages/Experiment/experimentText/logGroup.less
  19. +127
    -0
      react-ui/src/pages/Experiment/experimentText/logGroup.tsx
  20. +401
    -313
      react-ui/src/pages/Experiment/experimentText/props.jsx
  21. +425
    -464
      react-ui/src/pages/Experiment/index.jsx
  22. +50
    -93
      react-ui/src/pages/Experiment/index.less
  23. +59
    -0
      react-ui/src/pages/Experiment/status.ts
  24. +0
    -0
      react-ui/src/pages/Experiment/types.ts
  25. +219
    -180
      react-ui/src/pages/Model/modelIntro.jsx
  26. +299
    -227
      react-ui/src/pages/Model/personalData.jsx
  27. +266
    -198
      react-ui/src/pages/Model/publicData.jsx
  28. +20
    -28
      react-ui/src/pages/User/Center/index.tsx
  29. +57
    -0
      react-ui/src/requestConfig.ts
  30. +0
    -109
      react-ui/src/requestErrorConfig.ts
  31. +72
    -57
      react-ui/src/services/experiment/index.js
  32. +13
    -13
      react-ui/src/services/session.ts
  33. +38
    -0
      react-ui/src/utils/date.ts
  34. +20
    -0
      react-ui/src/utils/promise.ts
  35. +24
    -16
      react-ui/tsconfig.json
  36. +6
    -0
      ruoyi-modules/management-platform/pom.xml
  37. +1
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/RuoYiManagementPlatformApplication.java
  38. +17
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/config/WebSocketConfig.java
  39. +2
    -2
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/DatasetController.java
  40. +3
    -3
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentController.java
  41. +10
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentInsController.java
  42. +20
    -25
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AssetIcon.java
  43. +20
    -20
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Component.java
  44. +18
    -13
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Dataset.java
  45. +25
    -45
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/DatasetVersion.java
  46. +25
    -26
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Experiment.java
  47. +39
    -45
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ExperimentIns.java
  48. +16
    -30
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Image.java
  49. +22
    -45
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ImageVersion.java
  50. +17
    -31
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Models.java
  51. +24
    -43
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ModelsVersion.java
  52. +30
    -34
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Workflow.java
  53. +2
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/ExperimentInsDao.java
  54. +4
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentInsService.java
  55. +1
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/WorkflowService.java
  56. +11
    -6
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentInsServiceImpl.java
  57. +14
    -4
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java
  58. +3
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkflowServiceImpl.java
  59. +6
    -3
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java
  60. +37
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/PodLogVo.java
  61. +69
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/webSocket/WebSocket.java
  62. +3
    -3
      ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentDaoMapper.xml
  63. +27
    -8
      ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentInsDaoMapper.xml
  64. +19
    -9
      ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/WorkflowDaoMapper.xml
  65. +1
    -1
      ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/WorkflowParamDaoMapper.xml

+ 3
- 0
react-ui/.eslintrc.js View File

@@ -4,4 +4,7 @@ module.exports = {
page: true,
REACT_APP_ENV: true,
},
rules: {
"@typescript-eslint/no-use-before-define": "off"
}
};

+ 4
- 4
react-ui/config/proxy.ts View File

@@ -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,
}
},
},

/**


+ 25
- 10
react-ui/config/routes.ts View File

@@ -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',
},
]
],
},
];

+ 21
- 70
react-ui/src/app.tsx View File

@@ -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
// },
],
};

+ 3
- 0
react-ui/src/dayjsConfig.ts View File

@@ -0,0 +1,3 @@
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
dayjs.extend(duration);

+ 1
- 0
react-ui/src/enums/status.d.ts View File

@@ -0,0 +1 @@


+ 6
- 0
react-ui/src/hooks/dict.ts View File

@@ -0,0 +1,6 @@
import { getDictValueEnum } from '@/services/system/dict';

export function useDictEnum(name: string) {
const data = getDictValueEnum(name);
return data;
}

+ 12
- 0
react-ui/src/hooks/index.ts View File

@@ -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;
}

+ 0
- 7
react-ui/src/hooks/net/dict.ts View File

@@ -1,7 +0,0 @@
import { getDictValueEnum } from "@/services/system/dict";

export function useDictEnum(name: string)
{
const data = getDictValueEnum(name);
return data;
}

+ 10
- 0
react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.less View File

@@ -0,0 +1,10 @@
.container {
width: 100%;
height: 100%;

.frame {
width: 100%;
height: 100%;
border: none;
}
}

+ 11
- 0
react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx View File

@@ -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;

+ 0
- 14
react-ui/src/pages/DevelopmentEnvironment/index.jsx View File

@@ -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;

+ 22
- 0
react-ui/src/pages/DevelopmentEnvironment/index.tsx View File

@@ -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;

+ 18
- 0
react-ui/src/pages/Experiment/experimentText/LogList.tsx View File

@@ -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;

+ 51
- 0
react-ui/src/pages/Experiment/experimentText/addExperimentModal.less View File

@@ -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;
}
}
}

+ 127
- 0
react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx View File

@@ -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;

+ 373
- 343
react-ui/src/pages/Experiment/experimentText/index.jsx View File

@@ -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;

+ 34
- 0
react-ui/src/pages/Experiment/experimentText/logGroup.less View File

@@ -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;
}

+ 127
- 0
react-ui/src/pages/Experiment/experimentText/logGroup.tsx View File

@@ -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;

+ 401
- 313
react-ui/src/pages/Experiment/experimentText/props.jsx View File

@@ -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>
</>
);


+ 425
- 464
react-ui/src/pages/Experiment/index.jsx View File

@@ -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;

+ 50
- 93
react-ui/src/pages/Experiment/index.less View File

@@ -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;
}
}
}

+ 59
- 0
react-ui/src/pages/Experiment/status.ts View File

@@ -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',
},
};

+ 0
- 0
react-ui/src/pages/Experiment/types.ts View File


+ 219
- 180
react-ui/src/pages/Model/modelIntro.jsx View File

@@ -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;

+ 299
- 227
react-ui/src/pages/Model/personalData.jsx View File

@@ -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;

+ 266
- 198
react-ui/src/pages/Model/publicData.jsx View File

@@ -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;

+ 20
- 28
react-ui/src/pages/User/Center/index.tsx View File

@@ -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}


+ 57
- 0
react-ui/src/requestConfig.ts View File

@@ -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)
}
},
],
};

+ 0
- 109
react-ui/src/requestErrorConfig.ts View File

@@ -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;
},
],
};

+ 72
- 57
react-ui/src/services/experiment/index.js View File

@@ -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,
});
}

+ 13
- 13
react-ui/src/services/session.ts View File

@@ -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> {


+ 38
- 0
react-ui/src/utils/date.ts View File

@@ -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;
};

+ 20
- 0
react-ui/src/utils/promise.ts View File

@@ -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;

+ 24
- 16
react-ui/tsconfig.json View File

@@ -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应该检查的文件 ***
]
}

+ 6
- 0
ruoyi-modules/management-platform/pom.xml View File

@@ -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>


+ 1
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/RuoYiManagementPlatformApplication.java View File

@@ -32,3 +32,4 @@ public class RuoYiManagementPlatformApplication {
" ''-' `'-' `-..-' ");
}
}


+ 17
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/config/WebSocketConfig.java View File

@@ -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();
}
}

+ 2
- 2
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/DatasetController.java View File

@@ -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));
}


+ 3
- 3
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentController.java View File

@@ -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));
}


+ 10
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentInsController.java View File

@@ -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));
}


/**
* 查询实验实例实时日志
*


+ 20
- 25
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AssetIcon.java View File

@@ -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
- 20
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Component.java View File

@@ -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() {


+ 18
- 13
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Dataset.java View File

@@ -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;




+ 25
- 45
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/DatasetVersion.java View File

@@ -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;
}


+ 25
- 26
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Experiment.java View File

@@ -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;


+ 39
- 45
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ExperimentIns.java View File

@@ -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;
}


+ 16
- 30
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Image.java View File

@@ -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;





+ 22
- 45
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ImageVersion.java View File

@@ -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;




+ 17
- 31
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Models.java View File

@@ -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;




+ 24
- 43
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ModelsVersion.java View File

@@ -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;




+ 30
- 34
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Workflow.java View File

@@ -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;
}


+ 2
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/ExperimentInsDao.java View File

@@ -90,5 +90,7 @@ public interface ExperimentInsDao {
List<ExperimentIns> getByExperimentId(Integer experimentId);

List<ExperimentIns> queryByExperiment(@Param("experimentIns") ExperimentIns experimentIns);

List<ExperimentIns> queryByExperimentId(Integer id);
}


+ 4
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentInsService.java View File

@@ -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
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/WorkflowService.java View File

@@ -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;


+ 11
- 6
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentInsServiceImpl.java View File

@@ -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();


+ 14
- 4
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java View File

@@ -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;
}


+ 3
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkflowServiceImpl.java View File

@@ -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);


+ 6
- 3
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java View File

@@ -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);
}

}
}

+ 37
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/PodLogVo.java View File

@@ -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;
}
}

+ 69
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/webSocket/WebSocket.java View File

@@ -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());
}
}

+ 3
- 3
ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentDaoMapper.xml View File

@@ -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">


+ 27
- 8
ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentInsDaoMapper.xml View File

@@ -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>


+ 19
- 9
ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/WorkflowDaoMapper.xml View File

@@ -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


+ 1
- 1
ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/WorkflowParamDaoMapper.xml View File

@@ -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"/>


Loading…
Cancel
Save