Browse Source

merge

pull/31/head
liu2592603532 1 year ago
parent
commit
e27478072a
38 changed files with 764 additions and 358 deletions
  1. +1
    -4
      react-ui/config/config.ts
  2. +5
    -7
      react-ui/src/app.tsx
  3. +5
    -0
      react-ui/src/components/CommonTableCell/index.tsx
  4. +13
    -0
      react-ui/src/components/DateTableCell/index.tsx
  5. +5
    -0
      react-ui/src/components/KFRadio/index.less
  6. +1
    -1
      react-ui/src/components/PageTitle/index.less
  7. +5
    -3
      react-ui/src/global.less
  8. +10
    -14
      react-ui/src/pages/Dataset/datasetIntro.jsx
  9. +33
    -42
      react-ui/src/pages/Dataset/index.less
  10. +3
    -3
      react-ui/src/pages/Dataset/publicData.jsx
  11. +31
    -34
      react-ui/src/pages/Experiment/index.jsx
  12. +37
    -18
      react-ui/src/pages/Experiment/index.less
  13. +4
    -5
      react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx
  14. +1
    -1
      react-ui/src/pages/Mirror/create.less
  15. +107
    -32
      react-ui/src/pages/Mirror/create.tsx
  16. +1
    -1
      react-ui/src/pages/Mirror/info.less
  17. +84
    -20
      react-ui/src/pages/Mirror/info.tsx
  18. +4
    -5
      react-ui/src/pages/Mirror/list.less
  19. +85
    -23
      react-ui/src/pages/Mirror/list.tsx
  20. +34
    -34
      react-ui/src/pages/Model/index.less
  21. +10
    -14
      react-ui/src/pages/Model/modelIntro.jsx
  22. +1
    -1
      react-ui/src/pages/Pipeline/editPipeline/props.jsx
  23. +1
    -6
      react-ui/src/pages/Pipeline/index.jsx
  24. +3
    -18
      react-ui/src/pages/Pipeline/index.less
  25. +14
    -8
      react-ui/src/services/mirror/index.ts
  26. +0
    -34
      react-ui/src/utils/index.ts
  27. +2
    -0
      react-ui/src/utils/sessionKeys.ts
  28. +18
    -1
      react-ui/src/utils/ui.tsx
  29. +6
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/DatasetVersionController.java
  30. +2
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/ImageDao.java
  31. +3
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DatasetVersionService.java
  32. +20
    -2
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ImageService.java
  33. +10
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DatasetVersionServiceImpl.java
  34. +58
    -19
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java
  35. +23
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/HttpUtils.java
  36. +46
    -5
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java
  37. +63
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/LabelDatasetVersion.java
  38. +15
    -2
      ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ImageDaoMapper.xml

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

@@ -1,14 +1,12 @@
// https://umijs.org/config/
import { defineConfig } from '@umijs/max';
import { join, resolve } from 'path';
import { join } from 'path';
import defaultSettings from './defaultSettings';
import proxy from './proxy';
import routes from './routes';

const { REACT_APP_ENV = 'dev' } = process.env;

console.log('zzz', resolve(__dirname, '../src/styles/theme.less'));

export default defineConfig({
/**
* @name 开启 hash 模式
@@ -42,7 +40,6 @@ export default defineConfig({
// 如果不想要 configProvide 动态设置主题需要把这个设置为 default
// 只有设置为 variable, 才能使用 configProvide 动态设置主色调
// 'root-entry-name': 'variable',
'kf-success-color': '#ff0000',
},
/**
* @name moment 的国际化配置


+ 5
- 7
react-ui/src/app.tsx View File

@@ -193,7 +193,6 @@ export const antd: RuntimeAntdConfig = (memo) => {
colorSuccess: themes['successColor'],
colorError: themes['errorColor'],
colorWarning: themes['warningColor'],
// fontSize: themes['fontSize'],
};
memo.theme.components ??= {};
memo.theme.components.Tabs = {};
@@ -207,20 +206,19 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: themes['fontSize'],
paddingBlock: 4,
paddingInline: 15,
contentFontSize: parseInt(themes['fontSize']),
controlHeight: 34,
};
memo.theme.components.Input = {
inputFontSize: themes['fontSize'],
paddingBlock: 4,
paddingInline: 15,
inputFontSize: parseInt(themes['fontSize']),
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
};
memo.theme.components.Tabs = {
titleFontSize: 16,
};
memo.theme.cssVar = true;
// memo.theme.hashed = false;



+ 5
- 0
react-ui/src/components/CommonTableCell/index.tsx View File

@@ -0,0 +1,5 @@
function CommonTableCell(text?: string | null) {
return <span>{text ?? '--'}</span>;
}

export default CommonTableCell;

+ 13
- 0
react-ui/src/components/DateTableCell/index.tsx View File

@@ -0,0 +1,13 @@
import dayjs from 'dayjs';

function DateTableCell(text?: string | null) {
if (text === undefined || text === null || text === '') {
return <span>--</span>;
}
if (!dayjs(text).isValid()) {
return <span>日期无效</span>;
}
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
}

export default DateTableCell;

+ 5
- 0
react-ui/src/components/KFRadio/index.less View File

@@ -17,6 +17,11 @@
border: 1px solid @primary-color-hover;
}

&:active {
color: @primary-color;
border: 1px solid @primary-color;
}

&--active {
color: @primary-color;
border: 1px solid @primary-color;


+ 1
- 1
react-ui/src/components/PageTitle/index.less View File

@@ -1,7 +1,7 @@
.kf-page-title {
display: flex;
align-items: center;
height: 49px;
height: 50px;
padding-left: 30px;
background-image: url('../../assets/img/page-title-bg.png');
}

+ 5
- 3
react-ui/src/global.less View File

@@ -4,6 +4,7 @@ body,
height: 100%;
margin: 0;
padding: 0;
overflow-y: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
@@ -190,12 +191,13 @@ body {
.ant-pagination .ant-pagination-item-active a {
color: #fff;
background: rgba(22, 100, 255, 0.8);
// color: #fff;
border-radius: 6px;
}
.ant-pagination .ant-pagination-item-active:hover {
color: #fff;
background: rgba(22, 100, 255, 0.8);
border-color: rgba(22, 100, 255, 0.8);
// color: #fff;
// background: rgba(22, 100, 255, 0.8);
// border-color: rgba(22, 100, 255, 0.8);
border-radius: 6px;
}
.ant-pagination .ant-pagination-item {


+ 10
- 14
react-ui/src/pages/Dataset/datasetIntro.jsx View File

@@ -1,4 +1,5 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import {
addDatasetVersionDetail,
deleteDatasetVersion,
@@ -7,12 +8,7 @@ import {
getDatasetVersionsById,
} from '@/services/dataset/index.js';
import { downLoadZip } from '@/utils/downloadfile';
import {
DeleteOutlined,
DownloadOutlined,
PlusCircleOutlined,
UploadOutlined,
} from '@ant-design/icons';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
import { useEffect, useRef, useState } from 'react';
@@ -203,7 +199,7 @@ const Dataset = () => {
type="link"
size="small"
key="download"
icon={<DownloadOutlined />}
icon={<KFIcon type="icon-xiazai" />}
onClick={(e) => downloadAlone(e, record)}
>
下载
@@ -227,9 +223,9 @@ const Dataset = () => {
<div className={Styles.datasetIntroTopBox}>
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<div className={Styles.smallTagBox}>
<div className={Styles.tagItem}>数据集 id:{datasetDetailObj.id}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_type}</div>
{/* <div className={Styles.tagItem}>English</div> */}
</div>
</div>
<div className={Styles.datasetIntroCneterBox}>
@@ -257,10 +253,10 @@ const Dataset = () => {
options={versionList}
/>
<Button
type="primary"
type="default"
className={Styles.plusButton}
onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
>
创建新版本
</Button>
@@ -269,21 +265,21 @@ const Dataset = () => {
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="primary"
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<DeleteOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
<Button
type="primary"
type="default"
disabled={!version}
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={handleExport}
icon={<DownloadOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xiazai" />}
>
下载
</Button>


+ 33
- 42
react-ui/src/pages/Dataset/index.less View File

@@ -5,9 +5,9 @@
height: 49px;
padding: 0 30px;
padding-right: 30px;
font-family: 'Alibaba';
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
font-family: 'Alibaba';
}
.datasetIntroTopBox {
display: flex;
@@ -36,10 +36,10 @@
padding: 20px 30px;
color: #1d1d20;
font-size: 16px;
font-family: alibaba;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
font-family: alibaba;
.dataButtonList {
display: flex;
align-items: center;
@@ -70,7 +70,7 @@
.datasetBox {
font-family: 'Alibaba';
background: #f9fafb;
:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
@@ -89,20 +89,8 @@
}
.plusButton {
margin: 0 18px 0 20px;
color: #1d1d20;
font-size: 14px;
font-family: 'Alibaba';
background: rgba(22, 100, 255, 0.06);
border: 1px solid;
border-color: rgba(22, 100, 255, 0.11);
border-radius: 4px;
}
.plusButton:hover {
color: #1d1d20 !important;
background: rgba(22, 100, 255, 0.06) !important;
border: 1px solid !important;
border-color: rgba(22, 100, 255, 0.11) !important;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
@@ -119,8 +107,8 @@
height: 100%;
margin-right: 10px;
padding-top: 15px;
background: #ffffff;
font-family: 'Alibaba';
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
@@ -235,62 +223,65 @@
position: relative;
width: 23.8%;
height:164px;
background:#ffffff;
border:1px solid;
border-color:#eaeaea;
border-radius:4px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.itemText {
position: absolute;
top: 20px;
left: 20px;
background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%);
height: 6px;
line-height: 0px;
color:#1d1d20;
font-size:16px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.6) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition{
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
color:#575757;
font-size:14px;
word-break: break-all;
overflow: hidden;
display: -webkit-box;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
display: flex;
align-items: center;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
display: flex;
align-items: center;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover{
border-color: #1664FF;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1)
.dataItem:hover {
border-color: #1664ff;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);
}
.dataItem:hover .itemText{
color: #1664FF;
.dataItem:hover .itemText {
color: #1664ff;
}
}
}
@@ -301,9 +292,9 @@
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat:no-repeat;
background-size:100%;
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {


+ 3
- 3
react-ui/src/pages/Dataset/publicData.jsx View File

@@ -3,13 +3,13 @@ import creatByImg from '@/assets/img/creatBy.png';
import { getAssetIcon, getDatasetList } from '@/services/dataset/index.js';
import { Form, Input, Pagination } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = (React.FC = () => {
const PublicData = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
@@ -256,5 +256,5 @@ const PublicData = (React.FC = () => {
</div>
</>
);
});
};
export default PublicData;

+ 31
- 34
react-ui/src/pages/Experiment/index.jsx View File

@@ -18,6 +18,7 @@ import { elapsedTime } from '@/utils/date';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { Button, ConfigProvider, Space, Table, message } from 'antd';
import classNames from 'classnames';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -274,12 +275,14 @@ function Experiment() {
dataIndex: 'name',
key: 'name',
render: (text) => <div>{text}</div>,
width: '20%',
},
{
title: '关联流水线名称',
dataIndex: 'workflow_name',
key: 'workflow_name',
render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>,
width: '20%',
},
{
title: '实验描述',
@@ -290,6 +293,7 @@ function Experiment() {
title: '最近五次运行状态',
dataIndex: 'status_list',
key: 'status_list',
width: 200,
render: (text) => {
let newText = text && text.replace(/\s+/g, '').split(',');
return (
@@ -376,18 +380,8 @@ function Experiment() {
];
return (
<div>
{/* <div >
<Button type="primary" onClick={createExperiment} icon = {< PlusOutlined />}>
新建实验
</Button>
</div> */}
<div className={Styles.pipelineTopBox}>
<Button
type="primary"
className={Styles.plusButton}
onClick={createExperiment}
icon={<KFIcon type="icon-xinjian2" />}
>
<Button type="default" onClick={createExperiment} icon={<KFIcon type="icon-xinjian2" />}>
新建实验
</Button>
</div>
@@ -401,12 +395,14 @@ function Experiment() {
<div>
{experimentInList && experimentInList.length > 0 ? (
<div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div style={{ width: '150px' }}>序号</div>
<div style={{ width: '300px' }}>TensorBoard</div>
<div style={{ width: '300px' }}>运行时长</div>
<div style={{ width: '300px' }}>开始时间</div>
<div style={{ width: '200px' }}>状态</div>
<div style={{ width: '200px' }}>操作</div>
<div className={Styles.index}>序号</div>
<div className={Styles.tensorBoard}>可视化</div>
<div className={Styles.description}>
<div style={{ width: '50%' }}>运行时长</div>
<div style={{ width: '50%' }}>开始时间</div>
</div>
<div className={Styles.status}>状态</div>
<div className={Styles.operation}>操作</div>
</div>
) : (
''
@@ -416,17 +412,16 @@ function Experiment() {
? experimentInList.map((item, index) => (
<div
key={item.id}
className={Styles.tableExpandBox}
style={{
border: '1px solid #eaeaea',
backgroundColor: '#fff',
height: '45px',
}}
className={classNames(Styles.tableExpandBox, Styles.tableExpandBoxContent)}
>
<a style={{ width: '150px' }} onClick={(e) => routerToText(e, item, record)}>
<a
className={Styles.index}
style={{ padding: '0 16px' }}
onClick={(e) => routerToText(e, item, record)}
>
{index + 1}
</a>
<div style={{ width: '300px' }}>
<div className={Styles.tensorBoard}>
{item.nodes_result?.tensorboard_log ? (
<TensorBoardStatus
status={item.tensorBoardStatus}
@@ -436,15 +431,17 @@ function Experiment() {
'-'
)}
</div>
<div style={{ width: '300px' }}>
{item.finish_time
? elapsedTime(new Date(item.create_time), new Date(item.finish_time))
: elapsedTime(new Date(item.create_time), new Date())}
</div>
<div style={{ width: '300px' }}>
{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}
<div className={Styles.description}>
<div style={{ width: '50%' }}>
{item.finish_time
? elapsedTime(new Date(item.create_time), new Date(item.finish_time))
: elapsedTime(new Date(item.create_time), new Date())}
</div>
<div style={{ width: '50%' }}>
{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}
</div>
</div>
<div className={Styles.statusBox} style={{ width: '200px' }}>
<div className={Styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[item.status]?.icon}
@@ -456,7 +453,7 @@ function Experiment() {
{experimentStatusInfo[item.status]?.label}
</span>
</div>
<div style={{ width: '200px' }}>
<div className={Styles.operation}>
<Button
type="link"
size="small"


+ 37
- 18
react-ui/src/pages/Experiment/index.less View File

@@ -19,35 +19,54 @@
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.plusButton {
color: #1d1d20;
font-size: 14px;
font-family: 'Alibaba';
background: rgba(22, 100, 255, 0.06);
border: 1px solid;
border-color: rgba(22, 100, 255, 0.11);
border-radius: 4px;
}
.plusButton:hover {
color: #1d1d20 !important;
background: rgba(22, 100, 255, 0.06) !important;
border: 1px solid !important;
border-color: rgba(22, 100, 255, 0.11) !important;
}
.tableExpandBox {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;

padding: 0 65px 0 40px;
padding: 0 0 0 33px;
color: #1d1d20;
font-size: 15px;

& > div {
padding: 0 16px;
}

.index {
width: calc((100% + 32px + 33px) / 5);
}

.tensorBoard {
width: calc((100% + 32px + 33px) / 5);
}

.description {
display: flex;
flex: 1;
align-items: center;
}

.status {
width: 200px;
}

.operation {
width: 284px;
}
}
.tableExpandBoxContent {
height: 45px;
background-color: #fff;
border: 1px solid #eaeaea;

& + & {
border-top: none;
}
}

.statusBox {
display: flex;
align-items: center;
width: 200px;

.statusIcon {
visibility: hidden;


+ 4
- 5
react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx View File

@@ -6,10 +6,6 @@
import { MirrorVersionStatus } from '@/enums';
import styles from './index.less';

type MirrorStatusCellProps = {
status: MirrorVersionStatus;
};

type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus;
type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys];

@@ -33,7 +29,10 @@ const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = {
},
};

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



+ 1
- 1
react-ui/src/pages/Mirror/create.less View File

@@ -4,7 +4,7 @@
height: 100%;

&__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 10px;
overflow: auto;


+ 107
- 32
react-ui/src/pages/Mirror/create.tsx View File

@@ -3,15 +3,20 @@
* @Date: 2024-04-16 13:58:08
* @Description: 创建镜像
*/
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFRadio, { type KFRadioItem } from '@/components/KFRadio';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { createPrivateMirrorReq, createPublicMirrorReq } from '@/services/mirror';
import { createMirrorReq } from '@/services/mirror';
import { to } from '@/utils/promise';
import { useNavigate, useSearchParams } from '@umijs/max';
import { Button, Col, Form, Input, Row, message } from 'antd';
import { mirrorNameKey } from '@/utils/sessionKeys';
import { getFileListFromEvent } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import styles from './create.less';

type FormData = {
@@ -19,7 +24,8 @@ type FormData = {
tag: string;
description: string;
path?: string;
type: string;
upload_type: string;
fileList?: UploadFile[];
};

const mirrorRadioItems: KFRadioItem[] = [
@@ -37,15 +43,64 @@ const mirrorRadioItems: KFRadioItem[] = [

function MirrorCreate() {
const navgite = useNavigate();
const [seachParams] = useSearchParams();
const [form] = Form.useForm();
const isPublic = seachParams.get('isPublic') === 'true';
const [nameDisabled, setNameDisabled] = useState(false);

const uploadProps: UploadProps = {
action: '/api/mmp/image/upload',
headers: {
Authorization: getAccessToken() || '',
},
maxCount: 1,
defaultFileList: [],
};

useEffect(() => {
const name = sessionStorage.getItem(mirrorNameKey);
if (name) {
form.setFieldValue('name', name);
setNameDisabled(true);
}
return () => {
sessionStorage.removeItem(mirrorNameKey);
};
}, []);

// 创建公网、本地镜像
const createPublicMirror = async (params: FormData) => {
// createPrivateMirrorReq
const req = isPublic ? createPublicMirrorReq : createPrivateMirrorReq;
const [res] = await to(req(params));
const createPublicMirror = async (formData: FormData) => {
const upload_type = formData['upload_type'];
let params;
if (upload_type === CommonTabKeys.Public) {
params = {
...omit(formData, ['upload_type']),
upload_type: 0,
image_type: 0,
};
} else {
const fileList = formData['fileList'] ?? [];
if (fileList.length === 0) {
message.error('请上传文件');
return;
}
const file = fileList[0];
if (file.status === 'uploading') {
message.error('请等待文件上传完成');
return;
} else if (file.status === 'error') {
message.error('文件上传失败,请重新上传文件');
return;
}

params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
upload_type: 1,
image_type: 0,
};
}

const [res] = await to(createMirrorReq(params));
if (res) {
message.success('创建成功');
navgite(-1);
@@ -54,13 +109,26 @@ function MirrorCreate() {

// 提交
const handleSubmit = (values: FormData) => {
console.log(values);
createPublicMirror(values);
};

// 取消
const cancel = () => {
navgite(-1);
};

const beforeUpload: UploadProps['beforeUpload'] = () => {
const fileList = form.getFieldValue('fileList');
if (fileList.length >= 1) {
message.error('只允许上传一个文件');
return Upload.LIST_IGNORE;
}
return true;
};

return (
<div className={styles['mirror-create']}>
<PageTitle title="基本信息"></PageTitle>
<PageTitle title="创建镜像"></PageTitle>
<div className={styles['mirror-create__content']}>
<div>
<Form
@@ -69,7 +137,7 @@ function MirrorCreate() {
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
initialValues={{ type: CommonTabKeys.Public }}
initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit}
>
<SubAreaTitle
@@ -89,13 +157,19 @@ function MirrorCreate() {
},
]}
>
<Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear />
<Input
placeholder="请输入镜像名称"
maxLength={64}
disabled={nameDisabled}
showCount
allowClear
/>
</Form.Item>
</Col>
<Col span={10}>
<Form.Item
label=" "
name="tag"
name="tag_name"
labelCol={{ flex: '20px' }}
wrapperCol={{ flex: 1 }}
required={false}
@@ -141,7 +215,7 @@ function MirrorCreate() {
<Col span={10}>
<Form.Item
label="构建方式"
name="type"
name="upload_type"
rules={[
{
required: true,
@@ -155,10 +229,12 @@ function MirrorCreate() {
</Row>
<Form.Item
noStyle
shouldUpdate={(prevValues, curValues) => prevValues.type !== curValues.type}
shouldUpdate={(prevValues, curValues) =>
prevValues.upload_type !== curValues.upload_type
}
>
{({ getFieldValue }) => {
const type = getFieldValue('type');
const type = getFieldValue('upload_type');
if (type === CommonTabKeys.Public) {
return (
<>
@@ -199,7 +275,9 @@ function MirrorCreate() {
<Col span={10}>
<Form.Item
label="镜像文件"
name="path"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
@@ -207,19 +285,11 @@ function MirrorCreate() {
},
]}
>
{/* <Upload {...props} data={{ uuid: uuid }}>
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff' }} />}
>
上传文件
<Upload {...uploadProps} beforeUpload={beforeUpload}>
<Button type="link" style={{ paddingLeft: 0, paddingRight: 0 }}>
选择镜像文件
</Button>
</Upload> */}
</Upload>
</Form.Item>
</Col>
</Row>
@@ -233,7 +303,12 @@ function MirrorCreate() {
<Button type="primary" htmlType="submit">
创建镜像
</Button>
<Button type="default" htmlType="reset" style={{ marginLeft: '20px' }}>
<Button
type="default"
htmlType="button"
onClick={cancel}
style={{ marginLeft: '20px' }}
>
取消
</Button>
</Form.Item>


+ 1
- 1
react-ui/src/pages/Mirror/info.less View File

@@ -23,7 +23,7 @@
}

&__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 0;
background-color: white;


+ 84
- 20
react-ui/src/pages/Mirror/info.tsx View File

@@ -3,16 +3,33 @@
* @Date: 2024-04-16 13:58:08
* @Description: 镜像详情
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { MirrorVersionStatus } from '@/enums';
import { useDomSize } from '@/hooks';
import { getMirrorInfoReq, getMirrorVersionListReq } from '@/services/mirror';
import {
deleteMirrorVersionReq,
getMirrorInfoReq,
getMirrorVersionListReq,
} from '@/services/mirror';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import { useParams, useSearchParams } from '@umijs/max';
import { Button, Col, ConfigProvider, Row, Table, TablePaginationConfig, TableProps } from 'antd';
import { mirrorNameKey } from '@/utils/sessionKeys';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useParams, useSearchParams } from '@umijs/max';
import {
Button,
Col,
ConfigProvider,
Flex,
Row,
Table,
message,
type TablePaginationConfig,
type TableProps,
} from 'antd';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
@@ -27,6 +44,7 @@ type MirrorInfoData = {
};

type MirrorVersionData = {
id: number;
version: string;
url: string;
status: string;
@@ -35,8 +53,9 @@ type MirrorVersionData = {
};

function MirrorInfo() {
const navigate = useNavigate();
const urlParams = useParams();
const [seachParams] = useSearchParams();
const [searchParams] = useSearchParams();
const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({});
const [tableData, setTableData] = useState<MirrorVersionData[]>([]);
const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]);
@@ -47,7 +66,7 @@ function MirrorInfo() {
current: 1,
pageSize: 10,
});
const isPublic = seachParams.get('isPublic') === 'true';
const isPublic = searchParams.get('isPublic') === 'true';
useEffect(() => {
getMirrorInfo();
}, []);
@@ -61,7 +80,8 @@ function MirrorInfo() {
const [res] = await to(getMirrorInfoReq(id));
if (res && res.data) {
const { name = '', description = '', version_count = '', create_time: time } = res.data;
const create_time = time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '';
let create_time =
time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--';
setMirrorInfo({
name,
description,
@@ -87,6 +107,25 @@ function MirrorInfo() {
}
};

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

// 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
if (action === 'paginate') {
@@ -94,8 +133,21 @@ function MirrorInfo() {
}
};

const downloadVersion = (record: MirrorVersionData) => {};
const removeVersion = (record: MirrorVersionData) => {};
// 处理删除
const handleVersionDelete = (record: MirrorVersionData) => {
modalConfirm({
title: '删除后,该镜像版本将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteMirrorVersion(record.id);
},
});
};

const createMirrorVersion = () => {
navigate(`/dataset/mirror/create`);
sessionStorage.setItem(mirrorNameKey, mirrorInfo.name || '');
};

const columns: TableProps<MirrorVersionData>['columns'] = [
{
@@ -103,31 +155,34 @@ function MirrorInfo() {
dataIndex: 'tag_name',
key: 'tag_name',
width: '25%',
render: CommonTableCell,
},
{
title: '镜像地址',
dataIndex: 'url',
key: 'url',
render: CommonTableCell,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 150,
render: (text: string) => <MirrorStatusCell status={text as MirrorVersionStatus} />,
render: MirrorStatusCell,
},
{
title: '镜像大小',
dataIndex: 'file_size',
key: 'file_size',
width: 150,
render: CommonTableCell,
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: 200,
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: DateTableCell,
},
{
title: '操作',
@@ -135,7 +190,7 @@ function MirrorInfo() {
width: 150,
key: 'operation',
hidden: isPublic,
render: (_: any, record: any) => (
render: (_: any, record: MirrorVersionData) => (
<div>
{!isPublic && (
<ConfigProvider
@@ -150,7 +205,7 @@ function MirrorInfo() {
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => removeVersion(record)}
onClick={() => handleVersionDelete(record)}
>
删除
</Button>
@@ -182,7 +237,7 @@ function MirrorInfo() {
<Col span={10}>
<div className={styles['mirror-info__basic__item']}>
<div className={styles['label']}>版本数:</div>
<div className={styles['value']}>{mirrorInfo.version_count || '--'}</div>
<div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div>
</div>
</Col>
</Row>
@@ -201,12 +256,21 @@ function MirrorInfo() {
</Col>
</Row>
</div>

<SubAreaTitle
title="镜像版本"
image={require('@/assets/img/mirror-version.png')}
style={{ marginTop: '40px' }}
></SubAreaTitle>
<Flex justify="space-between" align="center" style={{ marginTop: '40px' }}>
<SubAreaTitle
title="镜像版本"
image={require('@/assets/img/mirror-version.png')}
></SubAreaTitle>
{!isPublic && (
<Button
type="default"
onClick={createMirrorVersion}
icon={<KFIcon type="icon-xinjian2" />}
>
新增镜像版本
</Button>
)}
</Flex>
</div>
<div
className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])}


+ 4
- 5
react-ui/src/pages/Mirror/list.less View File

@@ -1,13 +1,13 @@
.mirror-list {
height: 100%;
&__tabs-container {
height: 49px;
height: 50px;
padding-left: 27px;
background-image: url('../../assets/img/page-title-bg.png');
}

&__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px;
padding: 20px 30px 0;
background-color: white;
@@ -17,12 +17,11 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
}

&__table {
height: calc(100% - 44px);
margin-top: 12px;
height: calc(100% - 34px - 28px);
margin-top: 28px;
}
}
}

+ 85
- 23
react-ui/src/pages/Mirror/list.tsx View File

@@ -3,23 +3,28 @@
* @Date: 2024-04-16 13:58:08
* @Description: 镜像列表
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import { getMirrorListReq } from '@/services/mirror';
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import { useNavigate } from '@umijs/max';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useSearchParams } from '@umijs/max';
import {
Button,
ConfigProvider,
Input,
Table,
TablePaginationConfig,
TableProps,
Tabs,
message,
type TablePaginationConfig,
type TableProps,
type TabsProps,
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import styles from './list.less';

@@ -44,8 +49,12 @@ export type MirrorData = {
};

function MirrorList() {
const navgite = useNavigate();
const [activeTab, setActiveTab] = useState('Public');
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const isPrivate = searchParams.get('isPublic') === 'false';
const [activeTab, setActiveTab] = useState<string>(
isPrivate ? CommonTabKeys.Private : CommonTabKeys.Public,
);
const [searchText, setSearchText] = useState('');
const [tableData, setTableData] = useState<MirrorData[]>([]);
const [total, setTotal] = useState(0);
@@ -55,19 +64,37 @@ function MirrorList() {
current: 1,
pageSize: 10,
});

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

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
setSearchText('');
setPagination({
showSizeChanger: true,
showQuickJumper: true,
current: 1,
pageSize: 10,
});
setTotal(0);
setTableData([]);
setActiveTab(value);
setSearchParams([['isPublic', value === CommonTabKeys.Public ? 'true' : 'false']], {
replace: true,
});
};
// 获取镜像列表
const getMirrorList = async () => {
const params = {
const getMirrorList = async (params?: Record<string, any>) => {
const reqParams = {
page: pagination.current! - 1,
size: pagination.pageSize,
name: searchText,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
...params,
};
const [res] = await to(getMirrorListReq(params));
const [res] = await to(getMirrorListReq(reqParams));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
@@ -75,25 +102,57 @@ function MirrorList() {
}
};

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

// 搜索
const onSearch = () => {
getMirrorList();
const onSearch: SearchProps['onSearch'] = (value) => {
// 带参数是为了点清除时,searchText 更新不及时的问题
getMirrorList({
name: value,
});
};

// 查看详情
const toDetail = (record: MirrorData) => {
console.log('record', record);
navgite({
pathname: `/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`,
navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, {
state: {
isPublic: activeTab === CommonTabKeys.Public,
},
});
};

// 删除镜像
const deleteMirror = (record: MirrorData) => {};
// 处理删除
const handleMirrorDelete = (record: MirrorData) => {
modalConfirm({
title: '删除后,该镜像将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteMirror(record.id);
},
});
};

// 创建镜像
const createMirror = () => {
navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` });
navigate(`/dataset/mirror/create`);
};

// 分页切换
@@ -101,7 +160,7 @@ function MirrorList() {
if (action === 'paginate') {
setPagination(pagination);
}
console.log(pagination, filters, sorter, action);
// console.log(pagination, filters, sorter, action);
};

const columns: TableProps<MirrorData>['columns'] = [
@@ -110,32 +169,35 @@ function MirrorList() {
dataIndex: 'name',
key: 'name',
width: '30%',
render: CommonTableCell,
},
{
title: '版本数据',
dataIndex: 'version_count',
key: 'version_count',
width: 100,
render: CommonTableCell,
},
{
title: '镜像描述',
dataIndex: 'description',
key: 'description',
//ellipsis: true,
render: CommonTableCell,
ellipsis: true,
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: 200,
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: DateTableCell,
},
{
title: '操作',
dataIndex: 'operation',
width: activeTab === CommonTabKeys.Private ? 200 : 150,
key: 'operation',
render: (_: any, record: any) => (
render: (_: any, record: MirrorData) => (
<div>
<Button
type="link"
@@ -159,7 +221,7 @@ function MirrorList() {
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => deleteMirror(record)}
onClick={() => handleMirrorDelete(record)}
>
删除
</Button>
@@ -176,7 +238,7 @@ function MirrorList() {
<Tabs
activeKey={activeTab}
items={mirrorTabItems}
onChange={setActiveTab}
onChange={hanleTabChange}
className={styles['model-tabs']}
/>
</div>


+ 34
- 34
react-ui/src/pages/Model/index.less View File

@@ -87,20 +87,8 @@
}
.plusButton {
margin: 0 18px 0 20px;
color: #1d1d20;
font-size: 14px;
font-family: 'Alibaba';
background: rgba(22, 100, 255, 0.06);
border: 1px solid;
border-color: rgba(22, 100, 255, 0.11);
border-radius: 4px;
}
.plusButton:hover {
color: #1d1d20 !important;
background: rgba(22, 100, 255, 0.06) !important;
border: 1px solid !important;
border-color: rgba(22, 100, 255, 0.11) !important;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
@@ -223,64 +211,76 @@
width: 100%;
.dataItem {
position: relative;
<<<<<<< HEAD
width: 23.8%;
height:164px;
background:#ffffff;
border:1px solid;
border-color:#eaeaea;
border-radius:4px;
=======
width: 23.5%;
height: 164px;
>>>>>>> 708a5facb090bd542e06a0356466fa159720a223
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.itemText {
position: absolute;
top: 20px;
left: 20px;
background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%);
height: 6px;
line-height: 0px;
color:#1d1d20;
font-size:16px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.6) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition{
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
color:#575757;
font-size:14px;
word-break: break-all;
overflow: hidden;
display: -webkit-box;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
display: flex;
align-items: center;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
display: flex;
align-items: center;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover{
border-color: #1664FF;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1)
.dataItem:hover {
border-color: #1664ff;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);
}
.dataItem:hover .itemText{
color: #1664FF;
.dataItem:hover .itemText {
color: #1664ff;
}
}
}
@@ -291,9 +291,9 @@
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat:no-repeat;
background-size:100%;
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {


+ 10
- 14
react-ui/src/pages/Model/modelIntro.jsx View File

@@ -1,4 +1,5 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import {
addModelsVersionDetail,
deleteModelVersion,
@@ -7,12 +8,7 @@ import {
getModelVersionsById,
} from '@/services/dataset/index.js';
import { downLoadZip } from '@/utils/downloadfile';
import {
DeleteOutlined,
DownloadOutlined,
PlusCircleOutlined,
UploadOutlined,
} from '@ant-design/icons';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
import { useEffect, useRef, useState } from 'react';
@@ -201,7 +197,7 @@ const Dataset = () => {
type="link"
size="small"
key="download"
icon={<DownloadOutlined />}
icon={<KFIcon type="icon-xiazai" />}
onClick={(e) => downloadAlone(e, record)}
>
下载
@@ -225,9 +221,9 @@ const Dataset = () => {
<div className={Styles.datasetIntroTopBox}>
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<div className={Styles.smallTagBox}>
<div className={Styles.tagItem}>模型 id:{datasetDetailObj.id}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_type}</div>
{/* <div className={Styles.tagItem}>English</div> */}
</div>
</div>
<div className={Styles.datasetIntroCneterBox}>
@@ -255,10 +251,10 @@ const Dataset = () => {
options={versionList}
/>
<Button
type="primary"
type="default"
className={Styles.plusButton}
onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
>
创建新版本
</Button>
@@ -267,21 +263,21 @@ const Dataset = () => {
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="primary"
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<DeleteOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
<Button
type="primary"
type="default"
className={Styles.plusButton}
disabled={!version}
style={{ margin: '0 20px 0 0' }}
onClick={handleExport}
icon={<DownloadOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xiazai" />}
>
下载
</Button>


+ 1
- 1
react-ui/src/pages/Pipeline/editPipeline/props.jsx View File

@@ -1,9 +1,9 @@
import KFIcon from '@/components/KFIcon';
import { getComputingResourceReq } from '@/services/pipeline';
import { pick } from '@/utils/index';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { Button, Drawer, Form, Input, Select } from 'antd';
import { pick } from 'lodash';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal';
import Styles from './editPipeline.less';


+ 1
- 6
react-ui/src/pages/Pipeline/index.jsx View File

@@ -241,12 +241,7 @@ const Pipeline = () => {
return (
<div>
<div className={Styles.pipelineTopBox}>
<Button
type="primary"
className={Styles.plusButton}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
新建流水线
</Button>
</div>


+ 3
- 18
react-ui/src/pages/Pipeline/index.less View File

@@ -9,30 +9,16 @@
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.plusButton {
color: #1d1d20;
font-size: 14px;
font-family: 'Alibaba';
background: rgba(22, 100, 255, 0.06);
border: 1px solid;
border-color: rgba(22, 100, 255, 0.11);
border-radius: 4px;
}
.plusButton:hover {
color: #1d1d20 !important;
background: rgba(22, 100, 255, 0.06) !important;
border: 1px solid !important;
border-color: rgba(22, 100, 255, 0.11) !important;
}

.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat:no-repeat;
background-size:100%;
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
@@ -62,6 +48,5 @@
.ant-btn-primary {
background: #1664ff;
}
}
}

+ 14
- 8
react-ui/src/services/mirror/index.ts View File

@@ -28,18 +28,24 @@ export function getMirrorVersionListReq(params: any) {
});
}

// 创建公网镜像
export function createPublicMirrorReq(data: any) {
return request(`/api/mmp/image/net`, {
// 创建镜像
export function createMirrorReq(data: any) {
return request(`/api/mmp/image/addImageAndVersion`, {
method: 'POST',
data,
});
}

// 创建本地镜像
export function createPrivateMirrorReq(data: any) {
return request(`/api/mmp/image/local`, {
method: 'POST',
data,
// 删除镜像
export function deleteMirrorReq(id: number) {
return request(`/api/mmp/image/${id}`, {
method: 'DELETE',
});
}

// 删除镜像
export function deleteMirrorVersionReq(id: number) {
return request(`/api/mmp/imageVersion/${id}`, {
method: 'DELETE',
});
}

+ 0
- 34
react-ui/src/utils/index.ts View File

@@ -14,37 +14,3 @@ export function getNameByCode(list: any[], code: any) {
});
return name;
}

/**
* Picks specified properties from an object and returns a new object with only those properties.
*
* @param obj - The object to pick properties from.
* @param properties - An array of property names to pick from the object.
* @return A new object with only the picked properties.
*/
export function pick<T extends object, K extends keyof T>(obj: T, properties: K[]): Pick<T, K> {
const picked: Partial<T> = {};
for (const key of properties) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
picked[key] = obj[key];
}
}
return picked as Pick<T, K>;
}

/**
* Omit properties from an object and return a new object without those properties.
*
* @param obj - The object to omit properties from.
* @param properties - An array of property names to omit from the object.
* @return A new object without the omitted properties.
*/
export function omit<T extends object, K extends keyof T>(obj: T, properties: K[]): Omit<T, K> {
const omitted: Partial<T> = { ...obj };
for (const key of properties) {
if (Object.prototype.hasOwnProperty.call(omitted, key)) {
delete omitted[key];
}
}
return omitted as Omit<T, K>;
}

+ 2
- 0
react-ui/src/utils/sessionKeys.ts View File

@@ -0,0 +1,2 @@
// 用于新建镜像
export const mirrorNameKey = 'mirror-name';

+ 18
- 1
react-ui/src/utils/ui.tsx View File

@@ -4,7 +4,7 @@
* @Description: UI 公共方法
*/
import themes from '@/styles/theme.less';
import { Modal, type ModalFuncProps } from 'antd';
import { Modal, type ModalFuncProps, type UploadFile } from 'antd';

// 自定义 Confirm 弹框
export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) {
@@ -26,3 +26,20 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps)
onOk: onOk,
});
}

// 从事件中获取上传文件列表,用于 Upload + Form 中
export const getFileListFromEvent = (e: any) => {
const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || [];
return fileList.map((item) => {
if (item.status === 'done') {
const { response } = item;
if (response?.code !== 200) {
return {
...item,
status: 'error',
};
}
}
return item;
});
};

+ 6
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/DatasetVersionController.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.platform.domain.DatasetVersion;
import com.ruoyi.platform.service.DatasetVersionService;
import com.ruoyi.platform.vo.LabelDatasetVersion;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -135,5 +136,10 @@ public class DatasetVersionController extends BaseController {
return genericsSuccess(this.datasetVersionService.deleteDatasetVersion(datasetId, version));
}

@PostMapping("/addDatasetVersionsFromLabel")
@ApiOperation("从数据标注添加数据集版本")
public GenericsAjaxResult<Boolean> addDatasetVersionsFromLabel(@RequestBody LabelDatasetVersion labelDatasetVersion) throws Exception {
return genericsSuccess(true);
}
}


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

@@ -80,5 +80,7 @@ public interface ImageDao {
int deleteById(Integer id);

List<Image> queryByName(String name);

Image getByName(String name);
}


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

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

import com.ruoyi.platform.domain.Dataset;
import com.ruoyi.platform.domain.DatasetVersion;
import com.ruoyi.platform.vo.LabelDatasetVersion;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;

@@ -73,4 +74,6 @@ public interface DatasetVersionService {
void checkDeclaredVersion(DatasetVersion insert) throws Exception;

String addDatasetVersions(List<DatasetVersion> datasetVersions) throws Exception;

void addDatasetVersionsFromLabel(LabelDatasetVersion labelDatasetVersion) throws Exception;
}

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

@@ -26,6 +26,20 @@ public interface ImageService {
*/
Image queryById(Integer id);


Page<Image> queryByName(String name);

/**
* 通过名字精确查询单条数据
*
* @param name 名字
* @return 实例对象
*/

Image getByName(String name);



/**
* 分页查询
*
@@ -59,9 +73,11 @@ public interface ImageService {
*/
boolean deleteById(Integer id);

String removeById(Integer id);
String removeById(Integer id) throws Exception;




Page<Image> queryByName(String name);

String insertImageAndVersion(ImageVo imageVo) throws Exception;

@@ -77,4 +93,6 @@ public interface ImageService {
Map<String, String> createImageFromNet(String imageName, String imageTag, String NetPath) throws Exception;
Map<String, String> uploadImageFiles(MultipartFile file) throws Exception;



}

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

@@ -8,6 +8,8 @@ import com.ruoyi.platform.domain.ModelsVersion;
import com.ruoyi.platform.domain.Workflow;
import com.ruoyi.platform.mapper.DatasetVersionDao;
import com.ruoyi.platform.service.DatasetVersionService;
import com.ruoyi.platform.utils.HttpUtils;
import com.ruoyi.platform.vo.LabelDatasetVersion;
import com.ruoyi.system.api.model.LoginUser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
@@ -16,6 +18,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
@@ -32,7 +35,6 @@ import java.util.Map;
public class DatasetVersionServiceImpl implements DatasetVersionService {
@Resource
private DatasetVersionDao datasetVersionDao;

// 固定存储桶名
private final String bucketName = "platform-data";

@@ -193,8 +195,15 @@ public class DatasetVersionServiceImpl implements DatasetVersionService {
throw new Exception("新增数据集版本失败: " + e.getMessage());

}
}

@Override
public void addDatasetVersionsFromLabel(LabelDatasetVersion labelDatasetVersion) throws Exception{
// 获取label-studio数据流
InputStream inputStream = HttpUtils.getInputStream("http://127.0.0.1:8080/api/projects/"+labelDatasetVersion.getProject_id()+"/export?exportType="+labelDatasetVersion.getExportType(), labelDatasetVersion.getToken());
// 上传镜像至minio

//保存DatasetVersion
}

private void insertPrepare(DatasetVersion datasetVersion) throws Exception {


+ 58
- 19
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java View File

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

import com.alibaba.fastjson2.util.DateUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.Image;
import com.ruoyi.platform.domain.ImageVersion;
@@ -12,6 +13,7 @@ import com.ruoyi.platform.utils.FileUtil;
import com.ruoyi.platform.utils.K8sClientUtil;
import com.ruoyi.platform.vo.ImageVo;
import com.ruoyi.system.api.model.LoginUser;
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
import io.kubernetes.client.openapi.models.V1Pod;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@@ -64,6 +66,15 @@ public class ImageServiceImpl implements ImageService {
private String deploymentName;
@Value("${harbor.serviceNS}")
private String serviceNS;

@Value("${dockerpush.image}")
private String image;
@Value("${dockerpush.mountPath}")
private String mountPath;
@Value("${dockerpush.proxyUrl}")
private String proxyUrl;
@Value("${minio.pvcName}")
private String pvcName;
/**
* 通过ID查询单条数据
*
@@ -88,6 +99,8 @@ public class ImageServiceImpl implements ImageService {
return new PageImpl<>(this.imageDao.queryAllByLimit(image, pageRequest), pageRequest, total);
}



/**
* 新增数据
*
@@ -138,10 +151,10 @@ public class ImageServiceImpl implements ImageService {
}

@Override
public String removeById(Integer id) {
public String removeById(Integer id) throws Exception {
Image image = this.imageDao.queryById(id);
if (image == null){
return "镜像不存在";
throw new Exception("镜像不存在");
}

//判断权限,只有admin和创建者本身可以删除该数据集
@@ -150,11 +163,11 @@ public class ImageServiceImpl implements ImageService {


String createdBy = image.getCreateBy();
if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){
return "无权限删除该镜像";
if (!(StringUtils.equals(username,"admin") || !StringUtils.equals(username,createdBy))){
throw new Exception("无权限删除该镜像");
}
if (!imageVersionService.queryByImageId(id).isEmpty()){
return "请先删除该镜像下的版本文件";
throw new Exception("请先删除该镜像下的版本文件");
}
image.setState(0);
return this.imageDao.update(image)>0?"删除成功":"删除失败";
@@ -167,26 +180,46 @@ public class ImageServiceImpl implements ImageService {
return new PageImpl<>(this.imageDao.queryByName(name));
}


/**
* 通过名字精确查询镜像
*
* @param name 名字
* @return 镜像
*/
@Override
public Image getByName(String name) {
return this.imageDao.getByName(name);
}

@Override
@Transactional
public String insertImageAndVersion(ImageVo imageVo) throws Exception {
Image image = new Image();
image.setName(imageVo.getName());
image.setDescription(imageVo.getDescription());
image.setImageType(imageVo.getImageType());
Image imageInsert = this.insert(image);
if (imageInsert == null){
throw new Exception("新增镜像失败");
Image existingImage = getByName(imageVo.getName());
Image imageToUse;
if(existingImage == null) {
// 如果不存在相同名称的镜像,则创建新的镜像记录
Image newImage = new Image();
newImage.setName(imageVo.getName());
newImage.setDescription(imageVo.getDescription());
newImage.setImageType(imageVo.getImageType());
imageToUse = this.insert(newImage);
if (imageToUse == null) {
throw new Exception("新增镜像失败");
}
}else{
// 如果已存在相同名称的镜像,使用已存在的镜像
imageToUse = existingImage;
}
ImageVersion imageVersion = new ImageVersion();
imageVersion.setImageId(imageInsert.getId());
imageVersion.setImageId(imageToUse.getId());
imageVersion.setVersion(imageVo.getVersion());
imageVersion.setTagName(imageVo.getTagName());
imageVersion.setFileSize(imageVo.getFileSize());
imageVersion.setStatus("building");
ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion);
if (imageVersionInsert == null) {
throw new Exception("新增镜像失败");
throw new Exception("新增镜像版本失败");
}
// 使用CompletableFuture异步执行不同的镜像构建逻辑
CompletableFuture.supplyAsync(() -> {
@@ -206,7 +239,7 @@ public class ImageServiceImpl implements ImageService {
}).thenAccept(resultMap ->{
try {
String imageUrl = resultMap.get("url");
String fileSize = resultMap.get("filesize");
String fileSize = resultMap.get("fileSize");
imageVersion.setUrl(imageUrl);
imageVersion.setFileSize(fileSize);
imageVersion.setStatus("available");
@@ -229,7 +262,8 @@ public class ImageServiceImpl implements ImageService {
// 得到容器
V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName);
if (pod == null) {
throw new Exception("镜像推送服务不存在");
String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date());
pod = createPod(serviceNS, podName);
}
String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl;
// 执行命令 docker login -u admin -p Harbor12345 172.20.32.187
@@ -240,7 +274,7 @@ public class ImageServiceImpl implements ImageService {
String logs2 = k8sClientUtil.executeCommand(pod,"docker pull "+ netPath);
// 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag
if (StringUtils.isNoneBlank(logs2)){
String substring = logs2.substring(logs2.lastIndexOf(harborUrl), logs2.length());
String substring = logs2.substring(logs2.lastIndexOf(harborUrl));
String cleanedString = substring.replaceAll("(\\r|\\n)", "");
String tagCmd = "docker tag " + cleanedString + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
@@ -270,7 +304,8 @@ public class ImageServiceImpl implements ImageService {
// 得到容器
V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName);
if (pod == null) {
throw new Exception("镜像推送服务不存在");
String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date());
pod = createPod(serviceNS, podName);
}
String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl;
// 执行命令 docker login -u admin -p Harbor12345 172.20.32.187
@@ -282,7 +317,7 @@ public class ImageServiceImpl implements ImageService {
String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath);
// 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag
if (StringUtils.isNoneBlank(logs2)){
String substring = logs2.substring(logs2.lastIndexOf(harborUrl), logs2.length());
String substring = logs2.substring(logs2.lastIndexOf(harborUrl));
String cleanedString = substring.replaceAll("(\\r|\\n)", "");
String tagCmd = "docker tag " + cleanedString + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
@@ -311,4 +346,8 @@ public class ImageServiceImpl implements ImageService {
String path = loginUser.getUsername()+"/"+file.getOriginalFilename();
return minioService.uploadFile(bucketName, path, file);
}

private V1Pod createPod(String namespace, String podName){
return k8sClientUtil.createPodWithEnv(podName,namespace,proxyUrl,mountPath,pvcName,image);
}
}

+ 23
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/HttpUtils.java View File

@@ -374,6 +374,29 @@ public class HttpUtils {
return httpClient;
}


/**
* 发送 HTTP 请求并返回二进制数据流(InputStream)。
*
* @param url 请求的 URL 地址。
* @param token 要携带的 Token。
* @return 服务器响应的二进制数据流(InputStream)。
* @throws IOException 如果请求失败或发生其他 I/O 错误。
*/
public static InputStream getInputStream(String url, String token) throws IOException {
URL requestUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer " + token); // 添加 Authorization 头部,携带 Token

int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
return connection.getInputStream(); // 获取响应的输入流
} else {
throw new IOException("HTTP 请求失败,状态码:" + responseCode);
}
}

private static class TrustAnyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {


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

@@ -382,7 +382,7 @@ public class K8sClientUtil {
// invokes the CoreV1Api client

for (V1Pod item : v1PodList.getItems()) {
String generateName = item.getMetadata().getGenerateName();
String generateName = item.getMetadata().getName();
if (StringUtils.isNotEmpty(generateName) && generateName.startsWith(deploymentName)) {
// 找到匹配的Pod,获取其名称
return item;
@@ -410,13 +410,12 @@ public class K8sClientUtil {
int exitValue = proc.waitFor();
if (exitValue != 0) {
// 如果进程的退出值不为0,表示命令执行失败
throw new RuntimeException("命令执行失败,退出值:" + exitValue);
throw new RuntimeException("容器中命令执行失败,退出值:" + exitValue);
}

return builder.toString();
} catch (Exception e) {
log.error("执行命令异常", e);
throw new RuntimeException("执行命令异常");
log.error("容器执行命令异常", e);
throw new RuntimeException("容器执行命令异常");
}
}

@@ -441,4 +440,46 @@ public class K8sClientUtil {
}

}


public V1Pod createPodWithEnv(String podName,String namespace,String proxyUrl ,String mountPath,String pvcName, String image){
CoreV1Api api = new CoreV1Api(apiClient);
V1PodList v1PodList = null;
V1Pod pod = new V1PodBuilder()
.withNewMetadata()
.withName(podName)
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName(podName)
.withImage(image) // 替换为您实际要使用的镜像名称
.withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath))
.withNewSecurityContext().withNewPrivileged(true).endSecurityContext()
.addNewEnv()
.withName("HTTP_PROXY")
.withValue(proxyUrl)
.endEnv()
.addNewEnv()
.withName("HTTPS_PROXY")
.withValue(proxyUrl)
.endEnv()
.addNewEnv()
.withName("NO_PROXY")
.withValue("localhost,kubernetes.default.svc")
.endEnv()
.endContainer()
.addNewVolume()
.withName("workspace").withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName))
.endVolume()
.endSpec()
.build();
try {
pod = api.createNamespacedPod(namespace, pod, null, null, null);
} catch (ApiException e) {
log.error("创建pod异常:" + e.getResponseBody(), e);
} catch (Exception e) {
log.error("创建pod系统异常:", e);
}
return pod;
}
}

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

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

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

import java.io.Serializable;

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class LabelDatasetVersion implements Serializable {
private String token;
private String project_id;
private String dataset_id;
private String version;
private String desc;
private String exportType;
public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}

public String getProject_id() {
return project_id;
}

public void setProject_id(String project_id) {
this.project_id = project_id;
}

public String getDataset_id() {
return dataset_id;
}

public void setDataset_id(String dataset_id) {
this.dataset_id = dataset_id;
}

public String getVersion() {
return version;
}

public void setVersion(String version) {
this.version = version;
}

public String getDesc() {
return desc;
}

public void setDesc(String desc) {
this.desc = desc;
}

public String getExportType() {
return exportType;
}

public void setExportType(String exportType) {
this.exportType = exportType;
}
}

+ 15
- 2
ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ImageDaoMapper.xml View File

@@ -26,7 +26,7 @@
<select id="queryAllByLimit" resultMap="ImageMap">
select
img.id, img.name, img.description, img.image_type, img.create_by, img.create_time, img.update_by, img.update_time, img.state,
(SELECT COUNT(*) FROM image_version WHERE image_version.image_id = img.id) as versionCount
(SELECT COUNT(*) FROM image_version WHERE image_version.image_id = img.id and image_version.state = 1) as versionCount
from image img
<where>
img.state = 1
@@ -75,6 +75,18 @@
</where>
</select>

<select id="getByName" resultMap="ImageMap">
select
id, name, description, image_type, create_by, create_time, update_by, update_time, state
from image
<where>
state = 1
<if test="name != null and name != ''">
and name = #{name}
</if>
</where>
</select>


<!--统计总行数-->
<select id="count" resultType="java.lang.Long">
@@ -111,6 +123,7 @@
</if>
</where>
</select>

<!--新增所有列-->
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into image(name,description,image_type,create_by,create_time,update_by,update_time,state)
@@ -161,7 +174,7 @@ name = values(name)description = values(description)image_type = values(image_ty
<if test="image.updateTime != null">
update_time = #{image.updateTime},
</if>
<if test="image.state != null and image.state != ''">
<if test="image.state != null">
state = #{image.state},
</if>
</set>


Loading…
Cancel
Save