Browse Source

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

dev-lhz
cp3hnu 1 year ago
parent
commit
142273a2d1
47 changed files with 1468 additions and 329 deletions
  1. +6
    -1
      react-ui/config/routes.ts
  2. BIN
      react-ui/src/assets/img/editor-parameter.png
  3. +1
    -1
      react-ui/src/components/ParameterInput/index.tsx
  4. +10
    -0
      react-ui/src/enums/index.ts
  5. +6
    -5
      react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
  6. +3
    -2
      react-ui/src/pages/Dataset/components/CategoryItem/index.tsx
  7. +3
    -4
      react-ui/src/pages/Dataset/components/CategoryList/index.tsx
  8. +7
    -8
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  9. +8
    -7
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  10. +4
    -13
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  11. +7
    -6
      react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx
  12. +17
    -0
      react-ui/src/pages/DevelopmentEnvironment/Create/index.less
  13. +344
    -0
      react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx
  14. +22
    -0
      react-ui/src/pages/DevelopmentEnvironment/List/index.less
  15. +263
    -0
      react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
  16. +19
    -0
      react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.less
  17. +44
    -0
      react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.tsx
  18. +1
    -1
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.less
  19. +1
    -1
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
  20. +0
    -1
      react-ui/src/pages/Experiment/components/ExperimentResult/index.less
  21. +31
    -4
      react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx
  22. +7
    -0
      react-ui/src/pages/Experiment/components/ExportModelModal/index.less
  23. +207
    -0
      react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx
  24. +1
    -14
      react-ui/src/pages/Experiment/training/index.jsx
  25. +0
    -18
      react-ui/src/pages/Mirror/Info/index.less
  26. +1
    -2
      react-ui/src/pages/Mirror/Info/index.tsx
  27. +6
    -39
      react-ui/src/pages/Model/components/ModelEvolution/index.tsx
  28. +3
    -2
      react-ui/src/pages/Model/components/ModelEvolution/utils.tsx
  29. +101
    -22
      react-ui/src/pages/Model/components/NodeTooltips/index.tsx
  30. +22
    -14
      react-ui/src/pages/ModelDeployment/Create/index.tsx
  31. +1
    -1
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less
  32. +20
    -2
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
  33. +12
    -13
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx
  34. +95
    -97
      react-ui/src/pages/Pipeline/editPipeline/index.jsx
  35. +8
    -1
      react-ui/src/services/dataset/index.js
  36. +0
    -16
      react-ui/src/services/developmentEnvironment/index.js
  37. +59
    -0
      react-ui/src/services/developmentEnvironment/index.ts
  38. +8
    -7
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java
  39. +10
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ExperimentIns.java
  40. +2
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java
  41. +16
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java
  42. +3
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java
  43. +10
    -3
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java
  44. +3
    -4
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkflowServiceImpl.java
  45. +5
    -5
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java
  46. +44
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/DevEnvironmentVo.java
  47. +27
    -13
      ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentInsDaoMapper.xml

+ 6
- 1
react-ui/config/routes.ts View File

@@ -107,7 +107,12 @@ export default [
{ {
name: '开发环境', name: '开发环境',
path: '', path: '',
component: './DevelopmentEnvironment/index',
component: './DevelopmentEnvironment/List',
},
{
name: '创建编辑器',
path: 'create',
component: './DevelopmentEnvironment/Create',
}, },
], ],
}, },


BIN
react-ui/src/assets/img/editor-parameter.png View File

Before After
Width: 34  |  Height: 31  |  Size: 1.3 kB

+ 1
- 1
react-ui/src/components/ParameterInput/index.tsx View File

@@ -43,7 +43,7 @@ function ParameterInput({
} }
const isSelect = valueObj?.fromSelect; const isSelect = valueObj?.fromSelect;
const InputComponent = textArea ? Input.TextArea : Input; const InputComponent = textArea ? Input.TextArea : Input;
const placeholder = valueObj?.placeholder;
const placeholder = valueObj?.placeholder || rest?.placeholder;


return ( return (
<> <>


+ 10
- 0
react-ui/src/enums/index.ts View File

@@ -20,6 +20,7 @@ export enum ModelDeploymentStatus {
Pending = 'Pending', // 挂起中 Pending = 'Pending', // 挂起中
} }


// 模型部署状态选项列表
export const modelDeploymentStatusOptions = [ export const modelDeploymentStatusOptions = [
{ label: '全部', value: '' }, { label: '全部', value: '' },
{ label: '启动中', value: ModelDeploymentStatus.Init }, { label: '启动中', value: ModelDeploymentStatus.Init },
@@ -28,3 +29,12 @@ export const modelDeploymentStatusOptions = [
{ label: '失败', value: ModelDeploymentStatus.Failed }, { label: '失败', value: ModelDeploymentStatus.Failed },
{ label: '挂起中', value: ModelDeploymentStatus.Pending }, { label: '挂起中', value: ModelDeploymentStatus.Pending },
]; ];

// 开发环境编辑器状态
export enum DevEditorStatus {
Pending = 'Pending', // 启动中
Running = 'Running', // 运行中
Terminated = 'Terminated', // 已终止
Failed = 'Failed', // 失败
Unknown = 'Unknown', // 未启动
}

+ 6
- 5
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

@@ -33,10 +33,11 @@ function AddVersionModal({
...rest ...rest
}: AddVersionModalProps) { }: AddVersionModalProps) {
const [uuid] = useState(Date.now()); const [uuid] = useState(Date.now());
const config = resourceConfig[resourceType];


// 上传组件参数 // 上传组件参数
const uploadProps: UploadProps = { const uploadProps: UploadProps = {
action: resourceConfig[resourceType].uploadAction,
action: config.uploadAction,
headers: { headers: {
Authorization: getAccessToken() || '', Authorization: getAccessToken() || '',
}, },
@@ -45,7 +46,7 @@ function AddVersionModal({


// 上传请求 // 上传请求
const createDatasetVersion = async (params: any) => { const createDatasetVersion = async (params: any) => {
const request = resourceConfig[resourceType].addVersion;
const request = config.addVersion;
const [res] = await to(request(params)); const [res] = await to(request(params));
if (res) { if (res) {
message.success('创建成功'); message.success('创建成功');
@@ -62,7 +63,7 @@ function AddVersionModal({
const data = item.response?.data?.[0] ?? {}; const data = item.response?.data?.[0] ?? {};
return { return {
...otherParams, ...otherParams,
[resourceConfig[resourceType].idParamKey]: resourceId,
[config.idParamKey]: resourceId,
file_name: data.fileName, file_name: data.fileName,
file_size: data.fileSize, file_size: data.fileSize,
url: data.url, url: data.url,
@@ -72,8 +73,8 @@ function AddVersionModal({
} }
}; };


const name = resourceConfig[resourceType].name;
const accept = resourceConfig[resourceType].uploadAccept;
const name = config.name;
const accept = config.uploadAccept;
return ( return (
<KFModal <KFModal
{...rest} {...rest}


+ 3
- 2
react-ui/src/pages/Dataset/components/CategoryItem/index.tsx View File

@@ -10,6 +10,7 @@ type CategoryItemProps = {
}; };


function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) { function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) {
const config = resourceConfig[resourceType];
return ( return (
<div <div
className={classNames(styles['category-item'], { className={classNames(styles['category-item'], {
@@ -20,13 +21,13 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP
<img <img
className={styles['category-item__icon']} className={styles['category-item__icon']}
style={{ width: '22px' }} style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}.png`}
src={`/assets/images/${config.prefix}/${item.path}.png`}
alt="" alt=""
/> />
<img <img
className={styles['category-item__active-icon']} className={styles['category-item__active-icon']}
style={{ width: '22px' }} style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}-hover.png`}
src={`/assets/images/${config.prefix}/${item.path}-hover.png`}
alt="" alt=""
/> />
<span className={styles['category-item__name']}>{item.name}</span> <span className={styles['category-item__name']}>{item.name}</span>


+ 3
- 4
react-ui/src/pages/Dataset/components/CategoryList/index.tsx View File

@@ -29,15 +29,14 @@ function CategoryList({
onTagSelect, onTagSelect,
onSearch, onSearch,
}: CategoryProps) { }: CategoryProps) {
const config = resourceConfig[resourceType];
return ( return (
<div className={styles['category-list']}> <div className={styles['category-list']}>
<div style={{ padding: '0 20px' }}> <div style={{ padding: '0 20px' }}>
<Input.Search placeholder="搜索" allowClear onSearch={onSearch} /> <Input.Search placeholder="搜索" allowClear onSearch={onSearch} />
</div> </div>
<div className={styles['category-list__content']}> <div className={styles['category-list__content']}>
<div className={styles['category-list__content__title']}>
{resourceConfig[resourceType].typeTitle}
</div>
<div className={styles['category-list__content__title']}>{config.typeTitle}</div>
<Flex wrap="wrap" gap="20px 12px"> <Flex wrap="wrap" gap="20px 12px">
{typeList?.map((item) => ( {typeList?.map((item) => (
<CategoryItem <CategoryItem
@@ -50,7 +49,7 @@ function CategoryList({
))} ))}
</Flex> </Flex>
<div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}> <div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}>
{resourceConfig[resourceType].tagTitle}
{config.tagTitle}
</div> </div>
<Flex wrap="wrap" gap="20px 12px"> <Flex wrap="wrap" gap="20px 12px">
{tagList?.map((item) => ( {tagList?.map((item) => (


+ 7
- 8
react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx View File

@@ -28,16 +28,17 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
const [version, setVersion] = useState<string | undefined>(undefined); const [version, setVersion] = useState<string | undefined>(undefined);
const [activeTab, setActiveTab] = useState<string>(defaultTab); const [activeTab, setActiveTab] = useState<string>(defaultTab);
const resourceId = Number(locationParams.id); const resourceId = Number(locationParams.id);
const typeName = resourceConfig[resourceType].name; // 数据集/模型
const config = resourceConfig[resourceType];
const typeName = config.name; // 数据集/模型


useEffect(() => { useEffect(() => {
getModelByDetail(); getModelByDetail();
getVersionList(); getVersionList();
}, []);
}, [resourceId]);


// 获取详情 // 获取详情
const getModelByDetail = async () => { const getModelByDetail = async () => {
const request = resourceConfig[resourceType].getInfo;
const request = config.getInfo;
const [res] = await to(request(resourceId)); const [res] = await to(request(resourceId));
if (res) { if (res) {
setInfo(res.data); setInfo(res.data);
@@ -46,7 +47,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {


// 获取版本列表 // 获取版本列表
const getVersionList = async () => { const getVersionList = async () => {
const request = resourceConfig[resourceType].getVersions;
const request = config.getVersions;
const [res] = await to(request(resourceId)); const [res] = await to(request(resourceId));
if (res && res.data && res.data.length > 0) { if (res && res.data && res.data.length > 0) {
setVersionList( setVersionList(
@@ -122,10 +123,8 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
}); });
} }


const infoTypePropertyName = resourceConfig[resourceType]
.infoTypePropertyName as keyof ResourceData;
const infoTagPropertyName = resourceConfig[resourceType]
.infoTagPropertyName as keyof ResourceData;
const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData;
const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData;


return ( return (
<div className={styles['resource-intro']}> <div className={styles['resource-intro']}>


+ 8
- 7
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -54,6 +54,7 @@ function ResourceList(
const [searchText, setSearchText] = useState(initialSearchText); const [searchText, setSearchText] = useState(initialSearchText);
const [inputText, setInputText] = useState(initialSearchText); const [inputText, setInputText] = useState(initialSearchText);
const { message } = App.useApp(); const { message } = App.useApp();
const config = resourceConfig[resourceType];


useEffect(() => { useEffect(() => {
getDataList(); getDataList();
@@ -82,12 +83,12 @@ function ResourceList(
const params = { const params = {
page: pagination.current! - 1, page: pagination.current! - 1,
size: pagination.pageSize, size: pagination.pageSize,
[resourceConfig[resourceType].typeParamKey]: dataType,
[resourceConfig[resourceType].tagParamKey]: dataTag,
[config.typeParamKey]: dataType,
[config.tagParamKey]: dataTag,
available_range: isPublic ? 1 : 0, available_range: isPublic ? 1 : 0,
name: searchText !== '' ? searchText : undefined, name: searchText !== '' ? searchText : undefined,
}; };
const request = resourceConfig[resourceType].getList;
const request = config.getList;
const [res] = await to(request(params)); const [res] = await to(request(params));
if (res && res.data && res.data.content) { if (res && res.data && res.data.content) {
setDataList(res.data.content); setDataList(res.data.content);
@@ -97,7 +98,7 @@ function ResourceList(


// 删除请求 // 删除请求
const deleteRecord = async (id: number) => { const deleteRecord = async (id: number) => {
const request = resourceConfig[resourceType].deleteRecord;
const request = config.deleteRecord;
const [res] = await to(request(id)); const [res] = await to(request(id));
if (res) { if (res) {
getDataList(); getDataList();
@@ -113,7 +114,7 @@ function ResourceList(
// 删除 // 删除
const handleRemove = (record: ResourceData) => { const handleRemove = (record: ResourceData) => {
modalConfirm({ modalConfirm({
title: resourceConfig[resourceType].deleteModalTitle,
title: config.deleteModalTitle,
onOk: () => { onOk: () => {
deleteRecord(record.id); deleteRecord(record.id);
}, },
@@ -129,7 +130,7 @@ function ResourceList(
activeType: dataType, activeType: dataType,
activeTag: dataTag, activeTag: dataTag,
}); });
const prefix = resourceConfig[resourceType].prefix;
const prefix = config.prefix;
navigate(`/dataset/${prefix}/${record.id}`); navigate(`/dataset/${prefix}/${record.id}`);
}; };


@@ -176,7 +177,7 @@ function ResourceList(
onClick={showModal} onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />} icon={<KFIcon type="icon-xinjian2" />}
> >
{resourceConfig[resourceType].addBtnTitle}
{config.addBtnTitle}
</Button> </Button>
)} )}
</div> </div>


+ 4
- 13
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -21,6 +21,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType); const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType);
const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag); const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag);
const dataListRef = useRef<ResourceListRef>(null); const dataListRef = useRef<ResourceListRef>(null);
const config = resourceConfig[resourceType];


useEffect(() => { useEffect(() => {
getAssetIconList(); getAssetIconList();
@@ -52,16 +53,10 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
if (res && res.data && res.data.content) { if (res && res.data && res.data.content) {
const { content } = res.data; const { content } = res.data;
setTypeList( setTypeList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].typeValue,
),
content.filter((item: CategoryData) => Number(item.category_id) === config.typeValue),
); );
setTagList( setTagList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].tagValue,
),
content.filter((item: CategoryData) => Number(item.category_id) === config.tagValue),
); );
} }
}; };
@@ -76,11 +71,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
return ( return (
<div className={styles['resource-page']}> <div className={styles['resource-page']}>
<div className={styles['resource-page__tabs-container']}> <div className={styles['resource-page__tabs-container']}>
<Tabs
activeKey={activeTab}
items={resourceConfig[resourceType].tabItems}
onChange={hanleTabChange}
/>
<Tabs activeKey={activeTab} items={config.tabItems} onChange={hanleTabChange} />
</div> </div>
<Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}> <Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}>
<CategoryList <CategoryList


+ 7
- 6
react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx View File

@@ -41,6 +41,7 @@ function ResourceVersion({
}: ResourceVersionProps) { }: ResourceVersionProps) {
const [fileList, setFileList] = useState<ResourceFileData[]>([]); const [fileList, setFileList] = useState<ResourceFileData[]>([]);
const { message } = App.useApp(); const { message } = App.useApp();
const config = resourceConfig[resourceType];


// 获取版本文件列表 // 获取版本文件列表
useEffectWhen( useEffectWhen(
@@ -59,9 +60,9 @@ function ResourceVersion({
const getFileList = async (version: string) => { const getFileList = async (version: string) => {
const params = { const params = {
version, version,
[resourceConfig[resourceType].fileReqParamKey]: resourceId,
[config.fileReqParamKey]: resourceId,
}; };
const request = resourceConfig[resourceType].getFiles;
const request = config.getFiles;
const [res] = await to(request(params)); const [res] = await to(request(params));
if (res) { if (res) {
setFileList(res?.data?.content ?? []); setFileList(res?.data?.content ?? []);
@@ -70,9 +71,9 @@ function ResourceVersion({


// 删除版本 // 删除版本
const deleteVersion = async () => { const deleteVersion = async () => {
const request = resourceConfig[resourceType].deleteVersion;
const request = config.deleteVersion;
const params = { const params = {
[resourceConfig[resourceType].idParamKey]: resourceId,
[config.idParamKey]: resourceId,
version, version,
}; };
const [res] = await to(request(params)); const [res] = await to(request(params));
@@ -111,13 +112,13 @@ function ResourceVersion({


// 全部导出 // 全部导出
const handleExport = async () => { const handleExport = async () => {
const url = resourceConfig[resourceType].downloadAllAction;
const url = config.downloadAllAction;
downLoadZip(url, { models_id: resourceId, version }); downLoadZip(url, { models_id: resourceId, version });
}; };


// 单个导出 // 单个导出
const downloadAlone = (record: ResourceFileData) => { const downloadAlone = (record: ResourceFileData) => {
const url = resourceConfig[resourceType].downloadSingleAction;
const url = config.downloadSingleAction;
downLoadZip(`${url}/${record.id}`); downLoadZip(`${url}/${record.id}`);
}; };




+ 17
- 0
react-ui/src/pages/DevelopmentEnvironment/Create/index.less View File

@@ -0,0 +1,17 @@
.editor-create {
height: 100%;

&__content {
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 10px;
overflow: auto;
background-color: white;
border-radius: 10px;

&__type {
color: @text-color;
font-size: @font-size-input-lg;
}
}
}

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

@@ -0,0 +1,344 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 创建镜像
*/
import KFIcon from '@/components/KFIcon';
import KFRadio, { type KFRadioItem } from '@/components/KFRadio';
import PageTitle from '@/components/PageTitle';
import ParameterInput from '@/components/ParameterInput';
import SubAreaTitle from '@/components/SubAreaTitle';
import { useComputingResource } from '@/hooks/resource';
import ResourceSelectorModal, {
ResourceSelectorResponse,
ResourceSelectorType,
selectorTypeConfig,
} from '@/pages/Pipeline/components/ResourceSelectorModal';
import { createEditorReq } from '@/services/developmentEnvironment';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { useNavigate } from '@umijs/max';
import { App, Button, Col, Form, Input, Row, Select } from 'antd';
import { pick } from 'lodash';
import { useState } from 'react';
import styles from './index.less';

type FormData = {
name: string;
computing_resource: string;
standard: string;
image: string;
model: ResourceSelectorResponse;
dataset: ResourceSelectorResponse;
};

enum ComputingResourceType {
GPU = 'GPU',
NPU = 'NPU',
}

const EditorRadioItems: KFRadioItem[] = [
{
key: ComputingResourceType.GPU,
title: '英伟达GPU',
icon: <KFIcon type="icon-jiyugongwangjingxiang" />,
},
{
key: ComputingResourceType.NPU,
title: '昇腾NPU',
icon: <KFIcon type="icon-bendishangchuan" />,
},
];

function EditorCreate() {
const navgite = useNavigate();
const [form] = Form.useForm();
const { message } = App.useApp();
const [resourceStandardList, filterResourceStandard] = useComputingResource();
const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>(
undefined,
); // 选择的模型,为了再次打开时恢复原来的选择
const [selectedDataset, setSelectedDataset] = useState<ResourceSelectorResponse | undefined>(
undefined,
); // 选择的数据集,为了再次打开时恢复原来的选择
const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>(
undefined,
); // 选择的镜像,为了再次打开时恢复原来的选择

// 创建编辑器
const createEditor = async (formData: FormData) => {
// const { model, dataset } = formData;
// const params = {
// ...formData,
// model: JSON.stringify(omit(model, ['showValue'])),
// dataset: JSON.stringify(dataset, ['showValue']),
// };
const [res] = await to(createEditorReq(formData));
if (res) {
message.success('创建成功');
navgite(-1);
}
};

// 提交
const handleSubmit = (values: FormData) => {
createEditor(values);
};

// 取消
const cancel = () => {
navgite(-1);
};
// 获取选择数据集、模型后面按钮 icon
const getSelectBtnIcon = (type: ResourceSelectorType) => {
return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />;
};

// 选择模型、镜像、数据集
const selectResource = (name: string, type: ResourceSelectorType) => {
let resource: ResourceSelectorResponse | undefined;
switch (type) {
case ResourceSelectorType.Model:
resource = selectedModel;
break;
case ResourceSelectorType.Dataset:
resource = selectedDataset;
break;
default:
resource = selectedMirror;
break;
}
const { close } = openAntdModal(ResourceSelectorModal, {
type,
defaultExpandedKeys: resource ? [resource.id] : [],
defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [],
defaultActiveTab: resource?.activeTab,
onOk: (res) => {
if (res) {
if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res.path);
setSelectedMirror(res);
} else {
const showValue = `${res.name}:${res.version}`;
form.setFieldValue(name, {
...pick(res, ['id', 'version', 'path']),
showValue,
});
if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
} else if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
}
}
} else {
if (type === ResourceSelectorType.Model) {
setSelectedModel(undefined);
} else if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(undefined);
} else if (type === ResourceSelectorType.Mirror) {
setSelectedMirror(undefined);
}
form.setFieldValue(name, '');
}
close();
},
});
};

return (
<div className={styles['editor-create']}>
<PageTitle title="创建编辑器"></PageTitle>
<div className={styles['editor-create__content']}>
<div>
<Form
name="editor-create"
labelCol={{ flex: '100px' }}
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
initialValues={{ computing_resource: ComputingResourceType.GPU }}
onFinish={handleSubmit}
size="large"
>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<Row gutter={10}>
<Col span={10}>
<Form.Item
label="任务名称"
name="name"
rules={[
{
required: true,
message: '请输入任务名称',
},
]}
>
<Input placeholder="请输入任务名称" maxLength={64} showCount allowClear />
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Col span={10}>
<Form.Item
label="计算资源"
name="computing_resource"
rules={[
{
required: true,
message: '请选择计算资源',
},
]}
>
<KFRadio items={EditorRadioItems}></KFRadio>
</Form.Item>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="资源规格"
name="standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<Select
showSearch
placeholder="请选择资源规格"
filterOption={filterResourceStandard}
options={resourceStandardList}
fieldNames={{
label: 'description',
value: 'standard',
}}
/>
</Form.Item>
</Col>
</Row>
<SubAreaTitle
title="参数设置"
image={require('@/assets/img/editor-parameter.png')}
style={{ marginTop: '20px', marginBottom: '24px' }}
></SubAreaTitle>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<ParameterInput
placeholder="请选择镜像"
canInput={false}
size="large"
onClick={() => selectResource('image', ResourceSelectorType.Mirror)}
/>
</Form.Item>
</Col>
<Col span={10}>
<Button
size="large"
type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Mirror)}
onClick={() => selectResource('image', ResourceSelectorType.Mirror)}
>
选择镜像
</Button>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="模型"
name="model"
rules={[
{
required: true,
message: '请选择模型',
},
]}
>
<ParameterInput
placeholder="请选择模型"
canInput={false}
size="large"
onClick={() => selectResource('model', ResourceSelectorType.Model)}
/>
</Form.Item>
</Col>
<Col span={10}>
<Button
size="large"
type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Model)}
onClick={() => selectResource('model', ResourceSelectorType.Model)}
>
选择模型
</Button>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="数据集"
name="dataset"
rules={[
{
required: true,
message: '请选择数据集',
},
]}
>
<ParameterInput
placeholder="请选择数据集"
canInput={false}
size="large"
onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)}
/>
</Form.Item>
</Col>
<Col span={10}>
<Button
size="large"
type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Dataset)}
onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)}
>
选择数据集
</Button>
</Col>
</Row>

<Form.Item wrapperCol={{ offset: 0, span: 16 }}>
<Button type="primary" htmlType="submit">
确定
</Button>
<Button
type="default"
htmlType="button"
onClick={cancel}
style={{ marginLeft: '20px' }}
>
取消
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
);
}

export default EditorCreate;

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

@@ -0,0 +1,22 @@
.develop-env {
height: 100%;
&__header {
display: flex;
align-items: center;
justify-content: flex-end;
height: 50px;
margin-bottom: 10px;
padding: 0 30px;
background-image: url(@/assets/img/page-title-bg.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;
}

&__table {
height: calc(100% - 60px);
padding: 20px 30px 0;
background-color: white;
border-radius: 10px;
}
}

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

@@ -0,0 +1,263 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 开发环境
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import { DevEditorStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import {
deleteEditorReq,
getEditorListReq,
startEditorReq,
stopEditorReq,
} from '@/services/developmentEnvironment';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import {
App,
Button,
ConfigProvider,
Table,
type TablePaginationConfig,
type TableProps,
} from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import EditorStatusCell from '../components/EditorStatusCell';
import styles from './index.less';

export type EditorData = {
id: number;
name: string;
status: string;
computing_resource: string;
update_by: string;
create_time: string;
};

function EditorList() {
const navigate = useNavigate();
const [cacheState, setCacheState] = useCacheState();
const { message } = App.useApp();
const [tableData, setTableData] = useState<EditorData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
pageSize: 10,
},
);

useEffect(() => {
getEditorList();
}, [pagination]);

// 获取编辑器列表
const getEditorList = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
};
const [res] = await to(getEditorListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

// 删除编辑器
const deleteEditor = async (id: number) => {
const [res] = await to(deleteEditorReq(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除时,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
...prev,
current: 1,
}));
} else {
getEditorList();
}
}
};

// 启动编辑器
const startEditor = async (id: number) => {
const [res] = await to(startEditorReq(id));
if (res) {
message.success('操作成功');
getEditorList();
}
};

// 停止编辑器
const stopEditor = async (id: number) => {
const [res] = await to(stopEditorReq(id));
if (res) {
message.success('操作成功');
getEditorList();
}
};

// 处理删除
const handleEditorDelete = (record: EditorData) => {
modalConfirm({
title: '删除后,该编辑器将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteEditor(record.id);
},
});
};

// 创建编辑器
const createEditor = () => {
navigate(`/developmentEnvironment/create`);
setCacheState({
pagination,
});
};

// 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
if (action === 'paginate') {
setPagination(pagination);
}
};

const columns: TableProps<EditorData>['columns'] = [
{
title: '编辑器名称',
dataIndex: 'name',
key: 'name',
width: '30%',
render: CommonTableCell(),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: '10%',
render: EditorStatusCell,
},
{
title: '资源',
dataIndex: 'computing_resource',
key: 'computing_resource',
width: '20%',
render: CommonTableCell(),
},
{
title: '创建者',
dataIndex: 'update_by',
key: 'update_by',
width: '20%',
render: CommonTableCell(),
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: '20%',
render: DateTableCell,
},
{
title: '操作',
dataIndex: 'operation',
width: 300,
key: 'operation',
render: (_: any, record: EditorData) => (
<div>
{record.status === DevEditorStatus.Pending ||
record.status === DevEditorStatus.Running ? (
<Button
type="link"
size="small"
key="stop"
icon={<KFIcon type="icon-tingzhi" />}
onClick={() => stopEditor(record.id)}
>
停止
</Button>
) : (
<Button
type="link"
size="small"
key="debug"
icon={<KFIcon type="icon-tiaoshi" />}
onClick={() => startEditor(record.id)}
>
再次调试
</Button>
)}
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleEditorDelete(record)}
>
删除
</Button>
</ConfigProvider>
</div>
),
},
];

return (
<div className={styles['develop-env']}>
<div className={styles['develop-env__header']}>
<Button
style={{ marginLeft: '20px' }}
type="default"
onClick={createEditor}
icon={<KFIcon type="icon-xinjian2" />}
>
创建编辑器
</Button>
<Button
style={{ marginLeft: '20px' }}
type="default"
onClick={getEditorList}
icon={<KFIcon type="icon-shuaxin" />}
>
刷新
</Button>
</div>
<div className={classNames('vertical-scroll-table', styles['develop-env__table'])}>
<Table
dataSource={tableData}
columns={columns}
scroll={{ y: 'calc(100% - 55px)' }}
pagination={{
...pagination,
total: total,
showSizeChanger: true,
showQuickJumper: true,
}}
onChange={handleTableChange}
rowKey="id"
/>
</div>
</div>
);
}

export default EditorList;

+ 19
- 0
react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.less View File

@@ -0,0 +1,19 @@
.model-deployment-status-cell {
color: @text-color;

&--running {
color: @primary-color;
}

&--terminated {
color: @abort-color;
}

&--error {
color: @error-color;
}

&--pending {
color: @warning-color;
}
}

+ 44
- 0
react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.tsx View File

@@ -0,0 +1,44 @@
/*
* @Author: 赵伟
* @Date: 2024-04-18 18:35:41
* @Description: 编辑器状态组件
*/
import { DevEditorStatus } from '@/enums';
import styles from './index.less';

export type DevEditorStatusInfo = {
text: string;
classname: string;
};

export const statusInfo: Record<DevEditorStatus, DevEditorStatusInfo> = {
[DevEditorStatus.Unknown]: {
text: '未启动',
classname: styles['model-deployment-status-cell'],
},
[DevEditorStatus.Running]: {
classname: styles['model-deployment-status-cell--running'],
text: '运行中',
},
[DevEditorStatus.Terminated]: {
classname: styles['model-deployment-status-cell--terminated'],
text: '已停止',
},
[DevEditorStatus.Failed]: {
classname: styles['model-deployment-status-cell--error'],
text: '失败',
},
[DevEditorStatus.Pending]: {
classname: styles['model-deployment-status-cell--pending'],
text: '启动中',
},
};

function EditorStatusCell(status?: DevEditorStatus | null) {
if (status === null || status === undefined || !statusInfo[status]) {
return <span>--</span>;
}
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
}

export default EditorStatusCell;

+ 1
- 1
react-ui/src/pages/Experiment/components/AddExperimentModal/index.less View File

@@ -1,4 +1,4 @@
.modal {
.add-experiment-modal {
.global_param_item { .global_param_item {
max-height: 230px; max-height: 230px;
padding: 24px 12px 0; padding: 24px 12px 0;


+ 1
- 1
react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx View File

@@ -115,7 +115,7 @@ function AddExperimentModal({
}; };
return ( return (
<KFModal <KFModal
className={styles.modal}
className={styles['add-experiment-modal']}
title={modalTitle} title={modalTitle}
image={modalIcon} image={modalIcon}
open={open} open={open}


+ 0
- 1
react-ui/src/pages/Experiment/components/ExperimentResult/index.less View File

@@ -17,7 +17,6 @@
&__name { &__name {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
padding: 10px 0; padding: 10px 0;
border-bottom: 1px solid rgba(234, 234, 234, 0.8); border-bottom: 1px solid rgba(234, 234, 234, 0.8);
} }


+ 31
- 4
react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx View File

@@ -1,6 +1,9 @@
import { downLoadZip } from '@/utils/downloadfile'; import { downLoadZip } from '@/utils/downloadfile';
import { Button } from 'antd';
import { openAntdModal } from '@/utils/modal';
import { App, Button } from 'antd';
import ExportModelModal from '../ExportModelModal';
import styles from './index.less'; import styles from './index.less';

type ExperimentResultProps = { type ExperimentResultProps = {
results?: ExperimentResultData[] | null; results?: ExperimentResultData[] | null;
}; };
@@ -16,8 +19,22 @@ type ExperimentResultData = {
}; };


function ExperimentResult({ results }: ExperimentResultProps) { function ExperimentResult({ results }: ExperimentResultProps) {
const exportResult = (val: string) => {
downLoadZip(`/api/mmp/minioStorage/download`, { path: val });
const { message } = App.useApp();

// 下载
const download = (path: string) => {
downLoadZip(`/api/mmp/minioStorage/download`, { path });
};

// 导出到模型库
const exportToModel = (path: string) => {
const { close } = openAntdModal(ExportModelModal, {
path,
onOk: () => {
message.success('导出成功');
close();
},
});
}; };


return ( return (
@@ -31,12 +48,22 @@ function ExperimentResult({ results }: ExperimentResultProps) {
<Button <Button
size="small" size="small"
type="link" type="link"
style={{ marginLeft: 'auto' }}
onClick={() => { onClick={() => {
exportResult(item.path);
download(item.path);
}} }}
> >
下载 下载
</Button> </Button>
<Button
size="small"
type="link"
onClick={() => {
exportToModel(item.path);
}}
>
导出到模型库
</Button>
{/* <a style={{ marginRight: '10px' }}>导出到模型库</a> {/* <a style={{ marginRight: '10px' }}>导出到模型库</a>
<a style={{ marginRight: '10px' }}>导出到数据集</a> */} <a style={{ marginRight: '10px' }}>导出到数据集</a> */}
</div> </div>


+ 7
- 0
react-ui/src/pages/Experiment/components/ExportModelModal/index.less View File

@@ -0,0 +1,7 @@
.export-model-modal__tooltip {
:global {
.ant-tooltip-inner {
white-space: pre-line;
}
}
}

+ 207
- 0
react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx View File

@@ -0,0 +1,207 @@
import editExperimentIcon from '@/assets/img/edit-experiment.png';
import KFModal from '@/components/KFModal';
import { type ResourceData } from '@/pages/Dataset/config';
import {
addModelsVersionDetail,
exportModelReq,
getModelList,
getModelVersionsById,
} from '@/services/dataset';
import { to } from '@/utils/promise';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Form, Input, ModalProps, Select } from 'antd';
import { useEffect, useState } from 'react';
import styles from './index.less';

type FormData = {
models_id: string;
version: string;
description: string;
};

type ExportModelResponce = {
fileName: string;
fileSize: string;
url: string;
};

type CreateModelVersionParams = FormData & {
file_name: string;
file_size: string;
url: string;
// name: string;
};

interface ExportModelModalProps extends Omit<ModalProps, 'onOk'> {
path: string;
onOk: () => void;
}

function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
const [form] = Form.useForm();
const [models, setModels] = useState<ResourceData[]>([]);
const [versions, setVersions] = useState<string[]>([]);
const [uuid] = useState(Date.now());

const layout = {
labelCol: { span: 24 },
wrapperCol: { span: 24 },
};

useEffect(() => {
requestModelList();
}, []);

// 模型版本tooltip
const getTooltip = () => {
const id = form.getFieldValue('models_id');
const name = models.find((item) => item.id === id)?.name ?? '';
const tooltip =
versions.length > 0 ? `${name}有以下版本:\n${versions.join('、')}\n注意不能重复` : undefined;
return tooltip;
};

// 处理模型名称变化
const handleModelChange = (id: number | undefined) => {
if (id) {
getModelVersions(id);
} else {
setVersions([]);
}
};

// 获取模型列表
const requestModelList = async () => {
const params = {
page: 0,
size: 1000,
available_range: 0, // 个人
};
const [res] = await to(getModelList(params));
if (res && res.data) {
setModels(res.data.content || []);
}
};

// 获取模型版本列表
const getModelVersions = async (id: number) => {
const [res] = await to(getModelVersionsById(id));
if (res && res.data) {
setVersions(res.data);
}
};

// 提交
const hanldeFinish = (formData: FormData) => {
exportToModel(formData);
};

// 导出到模型
const exportToModel = async (formData: FormData) => {
const params = {
uuid: String(uuid),
path,
};
const [res] = await to(exportModelReq(params));
if (res && res.data) {
const files = res.data as ExportModelResponce[];
const params: CreateModelVersionParams[] = files.map((item) => ({
...formData,
file_name: item.fileName,
file_size: item.fileSize,
url: item.url,
}));

createModelVersion(params);
}
};

// 创建模型版本
const createModelVersion = async (params: CreateModelVersionParams[]) => {
const [res] = await to(addModelsVersionDetail(params));
if (res) {
onOk();
}
};

return (
<KFModal
{...rest}
title="导出到模型库"
image={editExperimentIcon}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
width={825}
className={styles['export-model-modal']}
>
<Form
name="form"
layout="horizontal"
onFinish={hanldeFinish}
autoComplete="off"
form={form}
{...layout}
labelAlign="left"
labelWrap
>
<Form.Item
label="模型名称"
name="models_id"
rules={[{ required: true, message: '请选择模型' }]}
>
<Select
placeholder="请选择模型"
onChange={handleModelChange}
options={models}
fieldNames={{ label: 'name', value: 'id' }}
allowClear
></Select>
</Form.Item>
<Form.Item
label="模型版本"
name="version"
tooltip={
getTooltip()
? {
overlayClassName: styles['export-model-modal__tooltip'],
title: getTooltip(),
icon: <InfoCircleOutlined />,
}
: undefined
}
rules={[
{ required: true, message: '请输入模型版本' },
{
validator: (_, value) => {
if (value && versions.includes(value)) {
return Promise.reject('模型版本已存在');
} else {
return Promise.resolve();
}
},
},
]}
>
<Input placeholder="请输入模型版本" maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
rules={[{ required: true, message: '请输入版本描述' }]}
>
<Input.TextArea
placeholder="请输入版本描述"
maxLength={128}
autoSize={{ minRows: 2, maxRows: 5 }}
showCount
allowClear
/>
</Form.Item>
</Form>
</KFModal>
);
}

export default ExportModelModal;

+ 1
- 14
react-ui/src/pages/Experiment/training/index.jsx View File

@@ -21,8 +21,8 @@ function ExperimentText() {
const navgite = useNavigate(); const navgite = useNavigate();
const locationParams = useParams(); //新版本获取路由参数接口 const locationParams = useParams(); //新版本获取路由参数接口
const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);

const graphRef = useRef(); const graphRef = useRef();

const getGraphData = (data) => { const getGraphData = (data) => {
if (graph) { if (graph) {
// 修改历史数据有蓝色边框的问题 // 修改历史数据有蓝色边框的问题
@@ -202,9 +202,6 @@ function ExperimentText() {
}, },
}, },
defaultEdge: { defaultEdge: {
// type: 'quadratic',
// type: 'cubic-vertical',

style: { style: {
endArrow: { endArrow: {
// 设置终点箭头 // 设置终点箭头
@@ -226,16 +223,6 @@ function ExperimentText() {
}, },
}, },
}, },
defaultCombo: {
type: 'rect',
fixCollapseSize: 70,
style: {
fill: '#00e0ff0d',
stroke: '#00e0ff',
lineDash: [5, 10],
cursor: 'pointer',
},
},
}); });
graph.on('node:click', (e) => { graph.on('node:click', (e) => {
if (e.target.get('name') !== 'anchor-point' && e.item) { if (e.target.get('name') !== 'anchor-point' && e.item) {


+ 0
- 18
react-ui/src/pages/Mirror/Info/index.less View File

@@ -31,23 +31,5 @@
display: flex; display: flex;
align-items: center; align-items: center;
} }

&__table {
:global {
.ant-table-wrapper {
height: 100%;
.ant-spin-nested-loading {
height: 100%;
}
.ant-spin-container {
height: 100%;
}
.ant-table {
height: calc(100% - 74px);
overflow: auto;
}
}
}
}
} }
} }

+ 1
- 2
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -32,7 +32,6 @@ import {
type TablePaginationConfig, type TablePaginationConfig,
type TableProps, type TableProps,
} from 'antd'; } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import MirrorStatusCell from '../components/MirrorStatusCell'; import MirrorStatusCell from '../components/MirrorStatusCell';
import styles from './index.less'; import styles from './index.less';
@@ -282,7 +281,7 @@ function MirrorInfo() {
</Flex> </Flex>
</div> </div>
<div <div
className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])}
className="vertical-scroll-table"
style={{ marginTop: '24px', height: `calc(100% - ${topHeight + 24}px)` }} style={{ marginTop: '24px', height: `calc(100% - ${topHeight + 24}px)` }}
> >
<Table <Table


+ 6
- 39
react-ui/src/pages/Model/components/ModelEvolution/index.tsx View File

@@ -5,21 +5,13 @@ import themes from '@/styles/theme.less';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import G6, { G6GraphEvent, Graph } from '@antv/g6'; import G6, { G6GraphEvent, Graph } from '@antv/g6';
// @ts-ignore // @ts-ignore
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro';
import { Flex, Select } from 'antd'; import { Flex, Select } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import GraphLegend from '../GraphLegend'; import GraphLegend from '../GraphLegend';
import NodeTooltips from '../NodeTooltips'; import NodeTooltips from '../NodeTooltips';
import styles from './index.less'; import styles from './index.less';
import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils';
import {
NodeType,
getGraphData,
nodeFontSize,
nodeHeight,
nodeWidth,
normalizeTreeData,
} from './utils';
import { getGraphData, nodeFontSize, nodeHeight, nodeWidth, normalizeTreeData } from './utils';


type modeModelEvolutionProps = { type modeModelEvolutionProps = {
resourceId: number; resourceId: number;
@@ -80,7 +72,7 @@ function ModelEvolution({
width: graphRef.current!.clientWidth, width: graphRef.current!.clientWidth,
height: graphRef.current!.clientHeight, height: graphRef.current!.clientHeight,
fitView: true, fitView: true,
fitViewPadding: [100, 100, 100, 100],
fitViewPadding: 200,
minZoom: 0.5, minZoom: 0.5,
maxZoom: 5, maxZoom: 5,
defaultNode: { defaultNode: {
@@ -172,36 +164,7 @@ function ModelEvolution({
const nodeItem = e.item; const nodeItem = e.item;
const model = nodeItem.getModel() as ModelDepsData | ProjectDependency | TrainDataset; const model = nodeItem.getModel() as ModelDepsData | ProjectDependency | TrainDataset;
const { model_type } = model; const { model_type } = model;
const { origin } = location;
let url: string = '';
switch (model_type) { switch (model_type) {
case NodeType.children:
case NodeType.parent: {
const { current_model_id, version } = model;
url = `${origin}/dataset/model/${current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${version}`;
break;
}
case NodeType.project: {
const { url: projectUrl } = model;
url = projectUrl;
break;
}
case NodeType.trainDataset:
case NodeType.testDataset: {
const { dataset_id, dataset_version } = model;
url = `${origin}/dataset/dataset/${dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${dataset_version}`;
break;
}
case NodeType.current: {
// TODO: 隐藏数据集和项目
break;
}
default:
break;
}

if (url) {
window.open(url, '_blank');
} }
}); });


@@ -234,6 +197,8 @@ function ModelEvolution({
graph.data(graphData); graph.data(graphData);
graph.render(); graph.render();
graph.fitView(); graph.fitView();
setShowNodeTooltip(false);
setEnterTooltip(false);
} else { } else {
clearGraphData(); clearGraphData();
} }
@@ -266,9 +231,11 @@ function ModelEvolution({
<div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div>
{(showNodeTooltip || enterTooltip) && ( {(showNodeTooltip || enterTooltip) && (
<NodeTooltips <NodeTooltips
resourceId={resourceId}
x={nodeTooltipX} x={nodeTooltipX}
y={nodeTooltipY} y={nodeTooltipY}
data={hoverNodeData!} data={hoverNodeData!}
onVersionChange={onVersionChange}
onMouseEnter={handleTooltipsMouseEnter} onMouseEnter={handleTooltipsMouseEnter}
onMouseLeave={handleTooltipsMouseLeave} onMouseLeave={handleTooltipsMouseLeave}
/> />


+ 3
- 2
react-ui/src/pages/Model/components/ModelEvolution/utils.tsx View File

@@ -3,8 +3,8 @@ import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util }
// @ts-ignore // @ts-ignore
import Hierarchy from '@antv/hierarchy'; import Hierarchy from '@antv/hierarchy';


export const nodeWidth = 110;
export const nodeHeight = 50;
export const nodeWidth = 90;
export const nodeHeight = 40;
export const vGap = nodeHeight + 20; export const vGap = nodeHeight + 20;
export const hGap = nodeWidth; export const hGap = nodeWidth;
export const ellipseWidth = nodeWidth; export const ellipseWidth = nodeWidth;
@@ -64,6 +64,7 @@ export type ModalDetail = {
export interface ModelDepsAPIData { export interface ModelDepsAPIData {
current_model_id: number; current_model_id: number;
version: string; version: string;
workflow_id: number;
exp_ins_id: number; exp_ins_id: number;
model_type: NodeType.children | NodeType.current | NodeType.parent; model_type: NodeType.children | NodeType.current | NodeType.parent;
current_model_name: string; current_model_name: string;


+ 101
- 22
react-ui/src/pages/Model/components/NodeTooltips/index.tsx View File

@@ -1,20 +1,35 @@
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max';
import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils';
import styles from './index.less'; import styles from './index.less';


type NodeTooltipsProps = {
data?: ModelDepsData | ProjectDependency | TrainDataset;
x: number;
y: number;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
type ModelInfoProps = {
resourceId: number;
data: ModelDepsData;
onVersionChange: (version: string) => void;
}; };


function ModelInfo({ data }: { data: ModelDepsData }) {
function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
const navigate = useNavigate();

const gotoExperimentPage = () => { const gotoExperimentPage = () => {
if (data.train_task?.ins_id) { if (data.train_task?.ins_id) {
const { origin } = location; const { origin } = location;
window.open(`${origin}/pipeline/experiment/144/${data.train_task.ins_id}`, '_blank');
const url = `${origin}/pipeline/experiment/${data.workflow_id}/${data.train_task.ins_id}`;
window.open(url, '_blank');
}
};

const gotoModelPage = () => {
if (data.model_type === NodeType.current) {
return;
}
if (data.current_model_id === resourceId) {
onVersionChange?.(data.version);
} else {
const path = `/dataset/model/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`;
navigate(path);
} }
}; };


@@ -24,9 +39,18 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
<div> <div>
<div className={styles['node-tooltips__row']}> <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>模型名称:</span> <span className={styles['node-tooltips__row__title']}>模型名称:</span>
<span className={styles['node-tooltips__row__value']}>
{data.model_version_dependcy_vo.name || '--'}
</span>
{data.model_type === NodeType.current ? (
<span className={styles['node-tooltips__row__value']}>
{data.model_version_dependcy_vo?.name || '--'}
</span>
) : (
<ValueLink
value={data.model_version_dependcy_vo?.name}
className={styles['node-tooltips__row__link']}
nullClassName={styles['node-tooltips__row__value']}
onClick={gotoModelPage}
></ValueLink>
)}
</div> </div>
<div className={styles['node-tooltips__row']}> <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>模型版本:</span> <span className={styles['node-tooltips__row__title']}>模型版本:</span>
@@ -61,13 +85,12 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
<div> <div>
<div className={styles['node-tooltips__row']}> <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>训练任务:</span> <span className={styles['node-tooltips__row__title']}>训练任务:</span>
{data.train_task?.name ? (
<a className={styles['node-tooltips__row__link']} onClick={gotoExperimentPage}>
{data.train_task?.name}
</a>
) : (
'--'
)}
<ValueLink
value={data.train_task?.name}
className={styles['node-tooltips__row__link']}
nullClassName={styles['node-tooltips__row__value']}
onClick={gotoExperimentPage}
></ValueLink>
</div> </div>
</div> </div>
</> </>
@@ -75,13 +98,24 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
} }


function DatasetInfo({ data }: { data: TrainDataset }) { function DatasetInfo({ data }: { data: TrainDataset }) {
const gotoDatasetPage = () => {
const { origin } = location;
const url = `${origin}/dataset/dataset/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`;
window.open(url, '_blank');
};

return ( return (
<> <>
<div className={styles['node-tooltips__title']}>数据集信息</div> <div className={styles['node-tooltips__title']}>数据集信息</div>
<div> <div>
<div className={styles['node-tooltips__row']}> <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>数据集名称:</span> <span className={styles['node-tooltips__row__title']}>数据集名称:</span>
<span className={styles['node-tooltips__row__value']}>{data.dataset_name || '--'}</span>
<ValueLink
value={data.dataset_name}
className={styles['node-tooltips__row__link']}
nullClassName={styles['node-tooltips__row__value']}
onClick={gotoDatasetPage}
></ValueLink>
</div> </div>
<div className={styles['node-tooltips__row']}> <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>数据集版本:</span> <span className={styles['node-tooltips__row__title']}>数据集版本:</span>
@@ -95,13 +129,23 @@ function DatasetInfo({ data }: { data: TrainDataset }) {
} }


function ProjectInfo({ data }: { data: ProjectDependency }) { function ProjectInfo({ data }: { data: ProjectDependency }) {
const gotoProjectPage = () => {
const { url } = data;
window.open(url, '_blank');
};

return ( return (
<> <>
<div className={styles['node-tooltips__title']}>项目信息</div> <div className={styles['node-tooltips__title']}>项目信息</div>
<div> <div>
<div className={styles['node-tooltips__row']}> <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>项目名称:</span> <span className={styles['node-tooltips__row__title']}>项目名称:</span>
<span className={styles['node-tooltips__row__value']}>{data.name || '--'}</span>
<ValueLink
value={data.name}
className={styles['node-tooltips__row__link']}
nullClassName={styles['node-tooltips__row__value']}
onClick={gotoProjectPage}
></ValueLink>
</div> </div>
<div className={styles['node-tooltips__row']}> <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>项目分支:</span> <span className={styles['node-tooltips__row__title']}>项目分支:</span>
@@ -116,7 +160,42 @@ function ProjectInfo({ data }: { data: ProjectDependency }) {
); );
} }


function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) {
type ValueLinkProps = {
value: string | undefined;
onClick?: () => void;
className?: string;
nullClassName?: string;
};

const ValueLink = ({ value, onClick, className, nullClassName }: ValueLinkProps) => {
return value ? (
<a className={className} onClick={onClick}>
{value}
</a>
) : (
<span className={nullClassName}>--</span>
);
};

type NodeTooltipsProps = {
resourceId: number;
data: ModelDepsData | ProjectDependency | TrainDataset;
x: number;
y: number;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
onVersionChange: (version: string) => void;
};

function NodeTooltips({
resourceId,
data,
x,
y,
onMouseEnter,
onMouseLeave,
onVersionChange,
}: NodeTooltipsProps) {
if (!data) return null; if (!data) return null;
let Component = null; let Component = null;
const { model_type } = data; const { model_type } = data;
@@ -129,7 +208,7 @@ function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsPr
model_type === NodeType.parent || model_type === NodeType.parent ||
model_type === NodeType.current model_type === NodeType.current
) { ) {
Component = <ModelInfo data={data} />;
Component = <ModelInfo resourceId={resourceId} data={data} onVersionChange={onVersionChange} />;
} }
return ( return (
<div <div


+ 22
- 14
react-ui/src/pages/ModelDeployment/Create/index.tsx View File

@@ -63,6 +63,9 @@ function ModelDeploymentCreate() {
const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>(
undefined, undefined,
); );
const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>(
undefined,
); // 选择的镜像,为了再次打开时恢复原来的选择
const { message } = App.useApp(); const { message } = App.useApp();


useEffect(() => { useEffect(() => {
@@ -84,16 +87,14 @@ function ModelDeploymentCreate() {
}; };


// 选择模型、镜像 // 选择模型、镜像
const selectResource = (name: string, selectType: string) => {
let type;
const selectResource = (name: string, type: ResourceSelectorType) => {
let resource: ResourceSelectorResponse | undefined; let resource: ResourceSelectorResponse | undefined;
switch (selectType) {
case 'model':
type = ResourceSelectorType.Model;
switch (type) {
case ResourceSelectorType.Model:
resource = selectedModel; resource = selectedModel;
break; break;
default: default:
type = ResourceSelectorType.Mirror;
resource = selectedMirror;
break; break;
} }
const { close } = openAntdModal(ResourceSelectorModal, { const { close } = openAntdModal(ResourceSelectorModal, {
@@ -105,18 +106,20 @@ function ModelDeploymentCreate() {
if (res) { if (res) {
if (type === ResourceSelectorType.Mirror) { if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res.path); form.setFieldValue(name, res.path);
setSelectedMirror(res);
} else { } else {
const response = res as ResourceSelectorResponse;
const showValue = `${response.name}:${response.version}`;
const showValue = `${res.name}:${res.version}`;
form.setFieldValue(name, { form.setFieldValue(name, {
...pick(response, ['id', 'version', 'path']),
...pick(res, ['id', 'version', 'path']),
showValue, showValue,
}); });
setSelectedModel(response);
setSelectedModel(res);
} }
} else { } else {
if (type === ResourceSelectorType.Model) { if (type === ResourceSelectorType.Model) {
setSelectedModel(undefined); setSelectedModel(undefined);
} else {
setSelectedMirror(undefined);
} }
form.setFieldValue(name, ''); form.setFieldValue(name, '');
} }
@@ -248,7 +251,6 @@ function ModelDeploymentCreate() {
image={require('@/assets/img/model-deployment.png')} image={require('@/assets/img/model-deployment.png')}
style={{ marginTop: '20px', marginBottom: '24px' }} style={{ marginTop: '20px', marginBottom: '24px' }}
></SubAreaTitle> ></SubAreaTitle>

<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
@@ -266,6 +268,7 @@ function ModelDeploymentCreate() {
disabled={disabled} disabled={disabled}
canInput={false} canInput={false}
size="large" size="large"
onClick={() => selectResource('model', ResourceSelectorType.Model)}
/> />
</Form.Item> </Form.Item>
</Col> </Col>
@@ -275,7 +278,7 @@ function ModelDeploymentCreate() {
size="large" size="large"
type="link" type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Model)} icon={getSelectBtnIcon(ResourceSelectorType.Model)}
onClick={() => selectResource('model', 'model')}
onClick={() => selectResource('model', ResourceSelectorType.Model)}
> >
选择模型 选择模型
</Button> </Button>
@@ -293,7 +296,12 @@ function ModelDeploymentCreate() {
}, },
]} ]}
> >
<ParameterInput placeholder="请选择镜像" canInput={false} size="large" />
<ParameterInput
placeholder="请选择镜像"
canInput={false}
size="large"
onClick={() => selectResource('image', ResourceSelectorType.Mirror)}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={10}> <Col span={10}>
@@ -301,7 +309,7 @@ function ModelDeploymentCreate() {
size="large" size="large"
type="link" type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} icon={getSelectBtnIcon(ResourceSelectorType.Mirror)}
onClick={() => selectResource('image', 'image')}
onClick={() => selectResource('image', ResourceSelectorType.Mirror)}
> >
选择镜像 选择镜像
</Button> </Button>


+ 1
- 1
react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less View File

@@ -11,7 +11,7 @@


:global { :global {
.anticon.anticon-question-circle { .anticon.anticon-question-circle {
margin-top: -14px;
margin-top: -12px;
} }
} }
} }


+ 20
- 2
react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx View File

@@ -76,9 +76,27 @@ const GlobalParamsDrawer = forwardRef(
{...restField} {...restField}
name={[name, 'param_name']} name={[name, 'param_name']}
label="参数名称" label="参数名称"
rules={[{ required: true, message: '请输入参数名称' }]}
validateTrigger={[]}
rules={[
{ required: true, message: '请输入参数名称' },
{
validator: (_, value) => {
const list = form.getFieldValue('global_param') || [];
const names = list.filter((item: any) => item?.param_name === value);
if (value && names.length > 1) {
return Promise.reject('参数名称不能重复');
} else {
return Promise.resolve();
}
},
},
]}
> >
<Input placeholder="请输入参数名称" allowClear />
<Input
placeholder="请输入参数名称"
allowClear
onBlur={() => form.validateFields()}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
{...restField} {...restField}


+ 12
- 13
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx View File

@@ -118,6 +118,7 @@ function ResourceSelectorModal({
const [fisrtLoadList, setFisrtLoadList] = useState(false); const [fisrtLoadList, setFisrtLoadList] = useState(false);
const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false); const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false);
const treeRef = useRef<TreeRef>(null); const treeRef = useRef<TreeRef>(null);
const config = selectorTypeConfig[type];


useEffect(() => { useEffect(() => {
setExpandedKeys([]); setExpandedKeys([]);
@@ -143,9 +144,9 @@ function ResourceSelectorModal({
const params = { const params = {
page: 0, page: 0,
size: 1000, size: 1000,
[selectorTypeConfig[type].litReqParamKey]: available_range,
[config.litReqParamKey]: available_range,
}; };
const getListReq = selectorTypeConfig[type].getList;
const getListReq = config.getList;
const [res] = await to(getListReq(params)); const [res] = await to(getListReq(params));
if (res) { if (res) {
const list = res.data?.content || []; const list = res.data?.content || [];
@@ -161,10 +162,10 @@ function ResourceSelectorModal({


// 获取数据集\模型\镜像版本列表 // 获取数据集\模型\镜像版本列表
const getVersions = async (parentId: number) => { const getVersions = async (parentId: number) => {
const getVersionsReq = selectorTypeConfig[type].getVersions;
const getVersionsReq = config.getVersions;
const [res, error] = await to(getVersionsReq(parentId)); const [res, error] = await to(getVersionsReq(parentId));
if (res) { if (res) {
const list = selectorTypeConfig[type].handleVersionResponse(res);
const list = config.handleVersionResponse(res);
const children = list.map(convertVersionToTreeData(parentId)); const children = list.map(convertVersionToTreeData(parentId));
// 更新 treeData children // 更新 treeData children
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
@@ -183,8 +184,8 @@ function ResourceSelectorModal({


// 获取版本下的文件 // 获取版本下的文件
const getFiles = async (id: number, version: string) => { const getFiles = async (id: number, version: string) => {
const getFilesReq = selectorTypeConfig[type].getFiles;
const paramsKey = selectorTypeConfig[type].fileReqParamKey;
const getFilesReq = config.getFiles;
const paramsKey = config.fileReqParamKey;
const params = { version: version, [paramsKey]: id }; const params = { version: version, [paramsKey]: id };
const [res] = await to(getFilesReq(params)); const [res] = await to(getFilesReq(params));
if (res) { if (res) {
@@ -282,14 +283,12 @@ function ResourceSelectorModal({
} }
}; };


const title = `选择${selectorTypeConfig[type].name}`;
const palceholder = `请输入${selectorTypeConfig[type].name}名称`;
const title = `选择${config.name}`;
const palceholder = `请输入${config.name}名称`;
const fileTitle = const fileTitle =
type === ResourceSelectorType.Mirror
? '已选镜像'
: `已选${selectorTypeConfig[type].name}文件(${files.length})`;
const tabItems = selectorTypeConfig[type].tabItems;
const titleImg = selectorTypeConfig[type].modalIcon;
type === ResourceSelectorType.Mirror ? '已选镜像' : `已选${config.name}文件(${files.length})`;
const tabItems = config.tabItems;
const titleImg = config.modalIcon;


return ( return (
<KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose>


+ 95
- 97
react-ui/src/pages/Pipeline/editPipeline/index.jsx View File

@@ -13,13 +13,12 @@ import GlobalParamsDrawer from '../components/GlobalParamsDrawer';
import ModelMenu from '../components/ModelMenu'; import ModelMenu from '../components/ModelMenu';
import styles from './index.less'; import styles from './index.less';
import Props from './props'; import Props from './props';
import { findAllParentNodes, findFirstDuplicate } from './utils';
import { findAllParentNodes } from './utils';


let graph = null; let graph = null;


const EditPipeline = () => { const EditPipeline = () => {
const navgite = useNavigate(); const navgite = useNavigate();
let contextMenu = {};
const locationParams = useParams(); //新版本获取路由参数接口 const locationParams = useParams(); //新版本获取路由参数接口
const graphRef = useRef(); const graphRef = useRef();
const paramsDrawerRef = useRef(); const paramsDrawerRef = useRef();
@@ -31,10 +30,23 @@ const EditPipeline = () => {
let dragSourceNode; let dragSourceNode;


useEffect(() => { useEffect(() => {
initMenu();
initGraph();
getFirstWorkflow(locationParams.id); getFirstWorkflow(locationParams.id);

const changeSize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graphRef.current) return;
graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight);
graph.fitView();
};

window.addEventListener('resize', changeSize);
return () => {
window.removeEventListener('resize', changeSize);
};
}, []); }, []);


// 拖拽结束,添加新节点
const onDragEnd = (val) => { const onDragEnd = (val) => {
const { x, y } = val; const { x, y } = val;
const point = graph.getPointByClient(x, y); const point = graph.getPointByClient(x, y);
@@ -45,11 +57,14 @@ const EditPipeline = () => {
y: point.y, y: point.y,
id: val.component_name + '-' + s8(), id: val.component_name + '-' + s8(),
isCluster: false, isCluster: false,
formError: true,
}; };
// console.log('model', model); // console.log('model', model);
graph.addItem('node', model, false); graph.addItem('node', model, false);
}; };
const formChange = (val) => {

// 节点数据发生变化
const handleFormChange = (val) => {
if (graph) { if (graph) {
const data = graph.save(); const data = graph.save();
const index = data.nodes.findIndex((item) => { const index = data.nodes.findIndex((item) => {
@@ -68,6 +83,8 @@ const EditPipeline = () => {
graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y);
} }
}; };

// 保存
const savePipeline = async (val) => { const savePipeline = async (val) => {
const [res, error] = await to(paramsDrawerRef.current.validateFields()); const [res, error] = await to(paramsDrawerRef.current.validateFields());
if (error) { if (error) {
@@ -76,13 +93,6 @@ const EditPipeline = () => {
return; return;
} }


const duplicateName = findFirstDuplicate(res.global_param || []);
if (duplicateName) {
message.error('全局参数配置有重复的参数名称:' + duplicateName);
openParamsDrawer();
return;
}

// const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); // const [propsRes, propsError] = await to(propsRef.current.getFieldsValue());
// if (propsError) { // if (propsError) {
// message.error('基本信息必填项需配置'); // message.error('基本信息必填项需配置');
@@ -108,6 +118,8 @@ const EditPipeline = () => {
}); });
}, 500); }, 500);
}; };

// 渲染数据
const getGraphData = (data) => { const getGraphData = (data) => {
if (graph) { if (graph) {
// 修改历史数据有蓝色边框的问题 // 修改历史数据有蓝色边框的问题
@@ -122,6 +134,8 @@ const EditPipeline = () => {
}, 500); }, 500);
} }
}; };

// 处理并行边,暂时没有用
const processParallelEdgesOnAnchorPoint = ( const processParallelEdgesOnAnchorPoint = (
edges, edges,
offsetDiff = 15, offsetDiff = 15,
@@ -242,11 +256,11 @@ const EditPipeline = () => {
} }
return false; return false;
}; };

// 复制节点
const cloneElement = (item) => { const cloneElement = (item) => {
console.log(item);
let data = graph.save(); let data = graph.save();
const nodeId = s8(); const nodeId = s8();
console.log(item.getModel());
data.nodes.push({ data.nodes.push({
...item.getModel(), ...item.getModel(),
label: item.getModel().label + '-copy', label: item.getModel().label + '-copy',
@@ -256,66 +270,22 @@ const EditPipeline = () => {
}); });
graph.changeData(data); graph.changeData(data);
}; };
const getFirstWorkflow = (val) => {
getWorkflowById(val).then((ret) => {
if (ret && ret.data) {
setGlobalParam(ret.data.global_param || []);
}
if (graph && ret.data && ret.data.dag) {
getGraphData(JSON.parse(ret.data.dag));
}
});
};
// 上下文菜单
const initMenu = () => {
// const selectedNodes = this.selectedNodes;
contextMenu = new G6.Menu({
getContent(evt) {
const type = evt.item.getType();
const cloneDisplay = type === 'node' ? 'block' : 'none';
return `
<ul style="position: absolute;
width: 100px;
padding-left:0;
display:flex;
flex-direction: column;
align-items:center;
left: 0px;
top: 0px;
background-color: #ffffff;
font-size: 14px;
color: #333333;
overflow-y: auto;">
<li style="padding: 10px 20px;cursor: pointer; display: ${cloneDisplay}" code="clone">复制</li>
<li style="padding: 10px 20px;cursor: pointer;" code="delete">删除</li>
</ul>`;
},
handleMenuClick: (target, item) => {
switch (target.getAttribute('code')) {
case 'delete':
graph.removeItem(item);
break;
case 'clone':
cloneElement(item);
break;
default:
break;
}
},
// offsetX and offsetY include the padding of the parent container
// 需要加上父级容器的 padding-left 16 与自身偏移量 10
offsetX: 16 + 10,
// 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10
offsetY: 0,
// the types of items that allow the menu show up
// 在哪些类型的元素上响应
itemTypes: ['node', 'edge'],
});


initGraph();
// 获取流水线详情
const getFirstWorkflow = async (val) => {
const [res] = await to(getWorkflowById(val));
if (res && res.data) {
const { global_param, dag } = res.data;
setGlobalParam(global_param || []);
if (dag) {
getGraphData(JSON.parse(dag));
}
}
}; };


// 初始化图
const initGraph = () => { const initGraph = () => {
const contextMenu = initMenu();
G6.registerNode( G6.registerNode(
'rect-node', 'rect-node',
{ {
@@ -515,6 +485,7 @@ const EditPipeline = () => {
}, },
cursor: 'pointer', cursor: 'pointer',
lineWidth: 1, lineWidth: 1,
lineAppendWidth: 4,
opacity: 1, opacity: 1,
stroke: '#CDD0DC', stroke: '#CDD0DC',
radius: 1, radius: 1,
@@ -527,19 +498,13 @@ const EditPipeline = () => {
}, },
}, },
}, },
defaultCombo: {
type: 'rect',
fixCollapseSize: 70,
style: {
fill: '#00e0ff0d',
stroke: '#00e0ff',
lineDash: [5, 10],
cursor: 'pointer',
},
},
}); });

bindEvents();
};

const bindEvents = () => {
graph.on('node:click', (e) => { graph.on('node:click', (e) => {
e.stopPropagation();
if (e.target.get('name') !== 'anchor-point' && e.item) { if (e.target.get('name') !== 'anchor-point' && e.item) {
// 获取所有的上游节点 // 获取所有的上游节点
const parentNodes = findAllParentNodes(graph, e.item); const parentNodes = findAllParentNodes(graph, e.item);
@@ -558,16 +523,6 @@ const EditPipeline = () => {
type: type:
targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal', targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal',
}); });

// update the curveOffset for parallel edges
// const edges = graph.save().edges;
// processParallelEdgesOnAnchorPoint(edges);
// graph.getEdges().forEach((edge, i) => {
// graph.updateItem(edge, {
// curveOffset: edges[i].curveOffset,
// curvePosition: edges[i].curvePosition,
// });
// });
}); });
// 删除边时,修改 anchor-point 的 links 值 // 删除边时,修改 anchor-point 的 links 值
graph.on('afterremoveitem', (e) => { graph.on('afterremoveitem', (e) => {
@@ -639,13 +594,56 @@ const EditPipeline = () => {
graph.setItemState(e.item, 'drop', false); graph.setItemState(e.item, 'drop', false);
dropAnchorIdx = undefined; dropAnchorIdx = undefined;
}); });
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graphRef.current) return;
graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight);
graph.fitView();
};
}; };

// 上下文菜单
const initMenu = () => {
const contextMenu = new G6.Menu({
getContent(evt) {
const type = evt.item.getType();
const cloneDisplay = type === 'node' ? 'block' : 'none';
return `
<ul style="position: absolute;
width: 100px;
padding-left:0;
display:flex;
flex-direction: column;
align-items:center;
left: 0px;
top: 0px;
background-color: #ffffff;
font-size: 14px;
color: #333333;
overflow-y: auto;">
<li style="padding: 10px 20px;cursor: pointer; display: ${cloneDisplay}" code="clone">复制</li>
<li style="padding: 10px 20px;cursor: pointer;" code="delete">删除</li>
</ul>`;
},
handleMenuClick: (target, item) => {
switch (target.getAttribute('code')) {
case 'delete':
graph.removeItem(item);
break;
case 'clone':
cloneElement(item);
break;
default:
break;
}
},
// offsetX and offsetY include the padding of the parent container
// 需要加上父级容器的 padding-left 16 与自身偏移量 10
offsetX: 16 + 10,
// 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10
offsetY: 0,
// the types of items that allow the menu show up
// 在哪些类型的元素上响应
itemTypes: ['node', 'edge'],
});

return contextMenu;
};

return ( return (
<div className={styles['pipeline-container']}> <div className={styles['pipeline-container']}>
<ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu> <ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu>
@@ -687,7 +685,7 @@ const EditPipeline = () => {
</div> </div>
<div className={styles['pipeline-container__workflow__graph']} ref={graphRef}></div> <div className={styles['pipeline-container__workflow__graph']} ref={graphRef}></div>
</div> </div>
<Props ref={propsRef} onParentChange={formChange}></Props>
<Props ref={propsRef} onParentChange={handleFormChange}></Props>
<GlobalParamsDrawer <GlobalParamsDrawer
ref={paramsDrawerRef} ref={paramsDrawerRef}
open={paramsDrawerOpen} open={paramsDrawerOpen}


+ 8
- 1
react-ui/src/services/dataset/index.js View File

@@ -130,7 +130,6 @@ export function deleteDataset(id) {
method: 'DELETE', method: 'DELETE',
}); });
} }

// 获取模型依赖 // 获取模型依赖
export function getModelAtlasReq(data) { export function getModelAtlasReq(data) {
return request(`/api/mmp/modelDependency/queryModelAtlas`, { return request(`/api/mmp/modelDependency/queryModelAtlas`, {
@@ -138,3 +137,11 @@ export function getModelAtlasReq(data) {
data data
}); });
} }

// 实验结果导出到模型
export function exportModelReq(data) {
return request(`/api/mmp/models/exportModel`, {
method: 'POST',
data
});
}

+ 0
- 16
react-ui/src/services/developmentEnvironment/index.js View File

@@ -1,16 +0,0 @@
import { request } from '@umijs/max';
// 查询开发环境url
export function getJupyterUrl(params) {
return request(`/api/mmp/jupyter/getURL`, {
method: 'GET',
params,
});
}

// 查询 labelStudio url
export function getLabelStudioUrl(params) {
return request(`/api/mmp/labelStudio/getURL`, {
method: 'GET',
params,
});
}

+ 59
- 0
react-ui/src/services/developmentEnvironment/index.ts View File

@@ -0,0 +1,59 @@
import { request } from '@umijs/max';
// 查询开发环境url
export function getJupyterUrl(params: any) {
return request(`/api/mmp/jupyter/getURL`, {
method: 'GET',
params,
});
}

// 查询 labelStudio url
export function getLabelStudioUrl(params: any) {
return request(`/api/mmp/labelStudio/getURL`, {
method: 'GET',
params,
});
}

// 创建编辑器
export function createEditorReq(data: any) {
return request(`/api/mmp/devEnvironment`, {
method: 'POST',
data,
});
}

// 获取编辑器列表
export function getEditorListReq(params: any) {
return request(`/api/mmp/devEnvironment`, {
method: 'GET',
params,
});
}

// 获取编辑器详情
export function getEditorInfoReq(id: number) {
return request(`/api/mmp/devEnvironment/${id}`, {
method: 'GET',
});
}

// 创建编辑器
export function deleteEditorReq(id: number) {
return request(`/api/mmp/devEnvironment/${id}`, {
method: 'DELETE',
});
}

// 启动编辑器
export function startEditorReq(id: number) {
return request(`/api/mmp/jupyter/run/${id}`, {
method: 'POST',
});
}
// 停止编辑器
export function stopEditorReq(id: number) {
return request(`/api/mmp/jupyter/stop/${id}`, {
method: 'DELETE',
});
}

+ 8
- 7
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java View File

@@ -4,6 +4,7 @@ import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.GenericsAjaxResult; import com.ruoyi.common.core.web.domain.GenericsAjaxResult;
import com.ruoyi.platform.domain.DevEnvironment; import com.ruoyi.platform.domain.DevEnvironment;
import com.ruoyi.platform.service.DevEnvironmentService; import com.ruoyi.platform.service.DevEnvironmentService;
import com.ruoyi.platform.vo.DevEnvironmentVo;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
@@ -56,12 +57,12 @@ public class DevEnvironmentController extends BaseController {
/** /**
* 新增数据 * 新增数据
* *
* @param devEnvironment 实体
* @param devEnvironmentVo 实体
* @return 新增结果 * @return 新增结果
*/ */
@PostMapping @PostMapping
public ResponseEntity<DevEnvironment> add(@RequestBody DevEnvironment devEnvironment) {
return ResponseEntity.ok(this.devEnvironmentService.insert(devEnvironment));
public GenericsAjaxResult<DevEnvironment> add(@RequestBody DevEnvironmentVo devEnvironmentVo) {
return genericsSuccess(this.devEnvironmentService.insert(devEnvironmentVo));
} }


/** /**
@@ -71,8 +72,8 @@ public class DevEnvironmentController extends BaseController {
* @return 编辑结果 * @return 编辑结果
*/ */
@PutMapping @PutMapping
public ResponseEntity<DevEnvironment> edit(@RequestBody DevEnvironment devEnvironment) {
return ResponseEntity.ok(this.devEnvironmentService.update(devEnvironment));
public GenericsAjaxResult<DevEnvironment> edit(@RequestBody DevEnvironment devEnvironment) {
return genericsSuccess(this.devEnvironmentService.update(devEnvironment));
} }


/** /**
@@ -82,8 +83,8 @@ public class DevEnvironmentController extends BaseController {
* @return 删除是否成功 * @return 删除是否成功
*/ */
@DeleteMapping("{id}") @DeleteMapping("{id}")
public ResponseEntity<String> deleteById(@PathVariable("id") Integer id) {
return ResponseEntity.ok(this.devEnvironmentService.removeById(id));
public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) {
return genericsSuccess(this.devEnvironmentService.removeById(id));
} }


} }


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

@@ -48,6 +48,9 @@ public class ExperimentIns implements Serializable {
@ApiModelProperty(value = "实验实例全局参数", notes = "以JSON字符串格式存储") @ApiModelProperty(value = "实验实例全局参数", notes = "以JSON字符串格式存储")
@JsonRawValue @JsonRawValue
private String globalParam; private String globalParam;
@ApiModelProperty(value = "参数记录", notes = "以JSON字符串格式存储")
@JsonRawValue
private String metricRecord;


@ApiModelProperty(value = "开始时间") @ApiModelProperty(value = "开始时间")
private Date startTime; private Date startTime;
@@ -210,5 +213,12 @@ public class ExperimentIns implements Serializable {


public void setWorkflowId(Long workflowId) {this.workflowId = workflowId;} public void setWorkflowId(Long workflowId) {this.workflowId = workflowId;}


public String getMetricRecord() {
return metricRecord;
}

public void setMetricRecord(String metricRecord) {
this.metricRecord = metricRecord;
}
} }



+ 2
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java View File

@@ -1,6 +1,7 @@
package com.ruoyi.platform.service; package com.ruoyi.platform.service;


import com.ruoyi.platform.domain.DevEnvironment; import com.ruoyi.platform.domain.DevEnvironment;
import com.ruoyi.platform.vo.DevEnvironmentVo;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;


@@ -35,7 +36,7 @@ public interface DevEnvironmentService {
* @param devEnvironment 实例对象 * @param devEnvironment 实例对象
* @return 实例对象 * @return 实例对象
*/ */
DevEnvironment insert(DevEnvironment devEnvironment);
DevEnvironment insert(DevEnvironmentVo devEnvironmentVo);


/** /**
* 修改数据 * 修改数据


+ 16
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java View File

@@ -6,6 +6,7 @@ import com.ruoyi.platform.mapper.DevEnvironmentDao;
import com.ruoyi.platform.service.DevEnvironmentService; import com.ruoyi.platform.service.DevEnvironmentService;
import com.ruoyi.platform.service.JupyterService; import com.ruoyi.platform.service.JupyterService;
import com.ruoyi.platform.utils.JacksonUtil; import com.ruoyi.platform.utils.JacksonUtil;
import com.ruoyi.platform.vo.DevEnvironmentVo;
import com.ruoyi.system.api.model.LoginUser; import com.ruoyi.system.api.model.LoginUser;
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -61,13 +62,27 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService {
* @return 实例对象 * @return 实例对象
*/ */
@Override @Override
public DevEnvironment insert(DevEnvironment devEnvironment) {
public DevEnvironment insert(DevEnvironmentVo devEnvironmentVo) {
//插入预备,此时不需要判断版本重复 //插入预备,此时不需要判断版本重复
DevEnvironment devEnvironment = new DevEnvironment();
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
devEnvironment.setName(devEnvironmentVo.getName());
//状态先设为未知
devEnvironment.setStatus("Unknown");
devEnvironment.setComputingResource(devEnvironmentVo.getComputingResource());
devEnvironment.setStandard(devEnvironmentVo.getStandard());
devEnvironment.setEnvVariable(devEnvironmentVo.getEnvVariable());
devEnvironment.setImage(devEnvironmentVo.getImage());
// 将 dataset 和 model 转换成 JSON 字符串
String datasetJson = JacksonUtil.toJSONString(devEnvironmentVo.getDataset());
String modelJson = JacksonUtil.toJSONString(devEnvironmentVo.getModel());
devEnvironment.setDataset(datasetJson);
devEnvironment.setModel(modelJson);
devEnvironment.setCreateBy(loginUser.getUsername()); devEnvironment.setCreateBy(loginUser.getUsername());
devEnvironment.setUpdateBy(loginUser.getUsername()); devEnvironment.setUpdateBy(loginUser.getUsername());
devEnvironment.setUpdateTime(new Date()); devEnvironment.setUpdateTime(new Date());
devEnvironment.setCreateTime(new Date()); devEnvironment.setCreateTime(new Date());
devEnvironment.setState(1);
this.devEnvironmentDao.insert(devEnvironment); this.devEnvironmentDao.insert(devEnvironment);
return devEnvironment; return devEnvironment;
} }


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

@@ -251,6 +251,9 @@ public class ExperimentServiceImpl implements ExperimentService {
if (data == null || MapUtils.isEmpty(data)) { if (data == null || MapUtils.isEmpty(data)) {
throw new RuntimeException("Failed to run workflow."); throw new RuntimeException("Failed to run workflow.");
} }
//获取训练参数
Map<String, Object> metricRecord = (Map<String, Object>) runResMap.get("metric_record");

Map<String, Object> metadata = (Map<String, Object>) data.get("metadata"); Map<String, Object> metadata = (Map<String, Object>) data.get("metadata");
// 插入记录到实验实例表 // 插入记录到实验实例表
ExperimentIns experimentIns = new ExperimentIns(); ExperimentIns experimentIns = new ExperimentIns();


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

@@ -43,6 +43,8 @@ public class JupyterServiceImpl implements JupyterService {
private String masterIp; private String masterIp;
@Value("${k8s.storageClassName}") @Value("${k8s.storageClassName}")
private String storageClassName; private String storageClassName;
@Value("${minio.pvcName}")
private String minioPvcName;


private final MinioUtil minioUtil; private final MinioUtil minioUtil;


@@ -89,14 +91,16 @@ public class JupyterServiceImpl implements JupyterService {
String modelPath = (String) model.get("path"); String modelPath = (String) model.get("path");


LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
String podName = loginUser.getUsername().toLowerCase() + "-editor-pod";
//手动构造pod名称
String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id;
String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc"; String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc";
//新建编辑器的pvc
V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage, storageClassName); V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage, storageClassName);


//TODO 设置镜像可配置,这里先用默认镜像启动pod //TODO 设置镜像可配置,这里先用默认镜像启动pod


// 调用修改后的 createPod 方法,传入额外的参数 // 调用修改后的 createPod 方法,传入额外的参数
Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, datasetPath, modelPath);
Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, minioPvcName, datasetPath, modelPath);
return masterIp + ":" + podPort; return masterIp + ":" + podPort;




@@ -110,7 +114,8 @@ public class JupyterServiceImpl implements JupyterService {
} }


LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
String podName = loginUser.getUsername().toLowerCase() + "-editor-pod";
//手动构造pod名称
String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id;
//得到pod //得到pod
V1Pod pod = k8sClientUtil.getNSPodList(namespace, podName); V1Pod pod = k8sClientUtil.getNSPodList(namespace, podName);
if(pod == null){ if(pod == null){
@@ -145,6 +150,8 @@ public class JupyterServiceImpl implements JupyterService {


} catch (Exception e) { } catch (Exception e) {
return JupyterStatusVo; return JupyterStatusVo;


} }
String url = redisService.getCacheObject(podName); String url = redisService.getCacheObject(podName);
JupyterStatusVo.setStatus(status); JupyterStatusVo.setStatus(status);


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

@@ -133,7 +133,9 @@ public class WorkflowServiceImpl implements WorkflowService {
public String removeById(Long id) throws Exception { public String removeById(Long id) throws Exception {
//先根据id提取出对应的流水线 //先根据id提取出对应的流水线
Workflow workflow = workflowDao.queryById(id); Workflow workflow = workflowDao.queryById(id);

if (workflow == null){
throw new Exception("流水线不存在");
}
//判断权限,只有admin和创建者本身可以删除该流水线 //判断权限,只有admin和创建者本身可以删除该流水线
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
String username = loginUser.getUsername(); String username = loginUser.getUsername();
@@ -142,9 +144,6 @@ public class WorkflowServiceImpl implements WorkflowService {
throw new Exception("无权限删除该流水线"); throw new Exception("无权限删除该流水线");
} }


if (workflow == null){
throw new Exception("流水线不存在");
}
//判断这个流水线是否有相关实验存在 //判断这个流水线是否有相关实验存在
List<Experiment> experimentList = experimentService.queryByWorkflowId(id); List<Experiment> experimentList = experimentService.queryByWorkflowId(id);
if (experimentList!=null&&experimentList.size()>0){ if (experimentList!=null&&experimentList.size()>0){


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

@@ -372,7 +372,7 @@ public class K8sClientUtil {
} }




public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, String image, String datasetPath, String modelPath) {
public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, String image, String dataPvcName, String datasetPath, String modelPath) {
Map<String, String> selector = new LinkedHashMap<>(); Map<String, String> selector = new LinkedHashMap<>();
selector.put("k8s-jupyter", podName); selector.put("k8s-jupyter", podName);


@@ -398,13 +398,13 @@ public class K8sClientUtil {
// 配置卷和卷挂载 // 配置卷和卷挂载
List<V1VolumeMount> volumeMounts = new ArrayList<>(); List<V1VolumeMount> volumeMounts = new ArrayList<>();
volumeMounts.add(new V1VolumeMount().name("workspace").mountPath(mountPath)); volumeMounts.add(new V1VolumeMount().name("workspace").mountPath(mountPath));
volumeMounts.add(new V1VolumeMount().name("dataset").mountPath("/datasets").subPath(datasetPath).readOnly(true));
volumeMounts.add(new V1VolumeMount().name("model").mountPath("/model").subPath(modelPath).readOnly(true));
volumeMounts.add(new V1VolumeMount().name("minio-pvc").mountPath("/datasets").subPath(datasetPath).readOnly(true));
volumeMounts.add(new V1VolumeMount().name("minio-pvc").mountPath("/model").subPath(modelPath).readOnly(true));


List<V1Volume> volumes = new ArrayList<>(); List<V1Volume> volumes = new ArrayList<>();
volumes.add(new V1Volume().name("workspace").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName()))); volumes.add(new V1Volume().name("workspace").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName())));
volumes.add(new V1Volume().name("dataset").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName())));
volumes.add(new V1Volume().name("model").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName())));
volumes.add(new V1Volume().name("minio-pvc").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(dataPvcName)));


V1Pod pod = new V1PodBuilder() V1Pod pod = new V1PodBuilder()
.withNewMetadata() .withNewMetadata()


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

@@ -0,0 +1,44 @@
package com.ruoyi.platform.vo;

import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;

import java.io.Serializable;
import java.util.Map;

@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class DevEnvironmentVo implements Serializable {

private Integer id;

private String name;

/**
* 计算资源
*/
private String computingResource;
/**
* 资源规格
*/
private String standard;

/**
* 环境变量
*/
private String envVariable;
/**
* 所用镜像
*/
private String image;
/**
* 对应数据集
*/
private Map<String,Object> dataset;
/**
* 对应模型
*/
private Map<String,Object> model;

}

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

@@ -12,6 +12,7 @@
<result property="nodesResult" column="nodes_result" jdbcType="VARCHAR"/> <result property="nodesResult" column="nodes_result" jdbcType="VARCHAR"/>
<result property="nodesLogs" column="nodes_logs" jdbcType="VARCHAR"/> <result property="nodesLogs" column="nodes_logs" jdbcType="VARCHAR"/>
<result property="globalParam" column="global_param" jdbcType="VARCHAR"/> <result property="globalParam" column="global_param" jdbcType="VARCHAR"/>
<result property="metricRecord" column="metric_record" jdbcType="VARCHAR"/>
<result property="startTime" column="start_time" jdbcType="TIMESTAMP"/> <result property="startTime" column="start_time" jdbcType="TIMESTAMP"/>
<result property="finishTime" column="finish_time" jdbcType="TIMESTAMP"/> <result property="finishTime" column="finish_time" jdbcType="TIMESTAMP"/>
<result property="createBy" column="create_by" jdbcType="VARCHAR"/> <result property="createBy" column="create_by" jdbcType="VARCHAR"/>
@@ -23,7 +24,7 @@


<!--查询非终止态的实例--> <!--查询非终止态的实例-->
<select id="queryByExperimentIsNotTerminated" resultMap="ExperimentInsMap"> <select id="queryByExperimentIsNotTerminated" 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
select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state
from experiment_ins from experiment_ins
where (status NOT IN ('Terminated', 'Succeeded', 'Failed') where (status NOT IN ('Terminated', 'Succeeded', 'Failed')
OR status IS NULL) and state = 1 OR status IS NULL) and state = 1
@@ -31,14 +32,14 @@


<!--查询单个--> <!--查询单个-->
<select id="queryById" resultMap="ExperimentInsMap"> <select id="queryById" 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
select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state
from experiment_ins from experiment_ins
where id = #{id} and state = 1 where id = #{id} and state = 1
</select> </select>


<!--查询列表--> <!--查询列表-->
<select id="getByExperimentId" resultMap="ExperimentInsMap"> <select id="getByExperimentId" 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
select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state
from experiment_ins from experiment_ins
where experiment_id = #{experiment_id} and state = 1 where experiment_id = #{experiment_id} and state = 1
order by create_time DESC order by create_time DESC
@@ -47,7 +48,7 @@


<!--查询最近的3个实例列表--> <!--查询最近的3个实例列表-->
<select id="getLatestInsList" resultMap="ExperimentInsMap"> <select id="getLatestInsList" 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
select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state
from experiment_ins from experiment_ins
where state = 1 where state = 1
order by create_time DESC order by create_time DESC
@@ -58,7 +59,7 @@


<select id="queryByExperiment" resultMap="ExperimentInsMap"> <select id="queryByExperiment" resultMap="ExperimentInsMap">
select 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
id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state
from experiment_ins from experiment_ins
<where> <where>
state = 1 state = 1
@@ -86,6 +87,9 @@
<if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''">
and global_param = #{experimentIns.globalParam} and global_param = #{experimentIns.globalParam}
</if> </if>
<if test="experimentIns.metricRecord != null and experimentIns.metricRecord != ''">
and metric_record = #{experimentIns.metricRecord}
</if>
<if test="experimentIns.startTime != null"> <if test="experimentIns.startTime != null">
and start_time = #{experimentIns.startTime} and start_time = #{experimentIns.startTime}
</if> </if>
@@ -110,7 +114,7 @@
<!--查询指定行数据--> <!--查询指定行数据-->
<select id="queryAllByLimit" resultMap="ExperimentInsMap"> <select id="queryAllByLimit" resultMap="ExperimentInsMap">
select 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
id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state
from experiment_ins from experiment_ins
<where> <where>
state = 1 state = 1
@@ -138,6 +142,9 @@
<if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''">
and global_param = #{experimentIns.globalParam} and global_param = #{experimentIns.globalParam}
</if> </if>
<if test="experimentIns.metricRecord != null and experimentIns.metricRecord != ''">
and metric_record = #{experimentIns.metricRecord}
</if>
<if test="experimentIns.startTime != null"> <if test="experimentIns.startTime != null">
and start_time = #{experimentIns.startTime} and start_time = #{experimentIns.startTime}
</if> </if>
@@ -191,6 +198,9 @@
<if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''">
and global_param = #{experimentIns.globalParam} and global_param = #{experimentIns.globalParam}
</if> </if>
<if test="experimentIns.metricRecord != null and experimentIns.metricRecord != ''">
and metric_record = #{experimentIns.metricRecord}
</if>
<if test="experimentIns.startTime != null"> <if test="experimentIns.startTime != null">
and start_time = #{experimentIns.startTime} and start_time = #{experimentIns.startTime}
</if> </if>
@@ -213,23 +223,23 @@
</select> </select>


<select id="queryByExperimentId" resultMap="ExperimentInsMap"> <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
select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state
from experiment_ins from experiment_ins
where experiment_id = #{id} and state = 1 where experiment_id = #{id} and state = 1
</select> </select>


<!--新增所有列--> <!--新增所有列-->
<insert id="insert" keyProperty="id" useGeneratedKeys="true"> <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,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 into experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,metric_record,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.metricRecord},#{experimentIns.startTime},#{experimentIns.finishTime},#{experimentIns.createBy},#{experimentIns.createTime},#{experimentIns.updateBy},#{experimentIns.updateTime},#{experimentIns.state})
</insert> </insert>


<insert id="insertBatch" keyProperty="id" useGeneratedKeys="true"> <insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">
insert into 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)
experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,metric_record,start_time,finish_time,create_by,create_time,update_by,update_time,state)
values values
<foreach collection="entities" item="entity" separator=","> <foreach collection="entities" item="entity" separator=",">
(#{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})
(#{entity.experimentId},#{entity.argoInsName},#{entity.argoInsNs},#{entity.status},#{entity.nodesStatus},#{entity.nodesResult},#{entity.nodesLogs},#{entity.globalParam},#{entity.metricRecord},#{entity.startTime},#{entity.finishTime},#{entity.createBy},#{entity.createTime},#{entity.updateBy},#{entity.updateTime},#{entity.state})
</foreach> </foreach>
</insert> </insert>


@@ -244,10 +254,10 @@
<!-- experiment_id = values(experiment_id)argo_ins_name = values(argo_ins_name)argo_ins_ns = values(argo_ins_ns)status = values(status) nodes_status = values(nodes_status) create_by = values(create_by)create_time = values(create_time)update_by = values(update_by)update_time = values(update_time)state = values(state)--> <!-- experiment_id = values(experiment_id)argo_ins_name = values(argo_ins_name)argo_ins_ns = values(argo_ins_ns)status = values(status) nodes_status = values(nodes_status) create_by = values(create_by)create_time = values(create_time)update_by = values(update_by)update_time = values(update_time)state = values(state)-->
<!-- </insert>--> <!-- </insert>-->
<update id="insertOrUpdateBatch"> <update id="insertOrUpdateBatch">
insert into experiment_ins (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)
insert into experiment_ins (id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status, nodes_result, nodes_logs, global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state)
values values
<foreach collection="list" item="item" index="index" separator=","> <foreach collection="list" item="item" index="index" separator=",">
(#{item.id}, #{item.experimentId}, #{item.argoInsName}, #{item.argoInsNs}, #{item.status}, #{item.nodesStatus}, #{item.nodesResult}, #{item.nodesLogs}, #{item.globalParam}, #{item.startTime}, #{item.finishTime}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.state})
(#{item.id}, #{item.experimentId}, #{item.argoInsName}, #{item.argoInsNs}, #{item.status}, #{item.nodesStatus}, #{item.nodesResult}, #{item.nodesLogs}, #{item.globalParam},#{item.metricRecord}, #{item.startTime}, #{item.finishTime}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.state})
</foreach> </foreach>
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
experiment_id = VALUES(experiment_id), experiment_id = VALUES(experiment_id),
@@ -258,6 +268,7 @@
nodes_result = VALUES(nodes_result), nodes_result = VALUES(nodes_result),
nodes_logs = VALUES(nodes_logs), nodes_logs = VALUES(nodes_logs),
global_param = VALUES(global_param), global_param = VALUES(global_param),
metric_record = VALUES(metric_record),
start_time = VALUES(start_time), start_time = VALUES(start_time),
finish_time = VALUES(finish_time), finish_time = VALUES(finish_time),
create_by = VALUES(create_by), create_by = VALUES(create_by),
@@ -294,6 +305,9 @@
<if test="experimentIns.globalParam != null and experimentIns.globalParam != ''"> <if test="experimentIns.globalParam != null and experimentIns.globalParam != ''">
global_param = #{experimentIns.globalParam}, global_param = #{experimentIns.globalParam},
</if> </if>
<if test="experimentIns.metricRecord != null and experimentIns.metricRecord != ''">
and metric_record = #{experimentIns.metricRecord}
</if>
<if test="experimentIns.startTime != null"> <if test="experimentIns.startTime != null">
start_time = #{experimentIns.startTime}, start_time = #{experimentIns.startTime},
</if> </if>


Loading…
Cancel
Save