Browse Source

Merge remote-tracking branch 'origin/dev' into dev

pull/51/head
西大锐 1 year ago
parent
commit
8afd4359a1
100 changed files with 3963 additions and 4570 deletions
  1. +1
    -1
      react-ui/config/defaultSettings.ts
  2. +16
    -7
      react-ui/config/routes.ts
  3. BIN
      react-ui/public/assets/images/compoent-icon-6.png
  4. BIN
      react-ui/public/assets/images/icon/流水线-1.png
  5. BIN
      react-ui/public/assets/images/mindspore模型转换.png
  6. BIN
      react-ui/public/assets/images/pipelieEditIcon.png
  7. BIN
      react-ui/public/assets/images/pipeline-edit-icon.png
  8. BIN
      react-ui/public/assets/images/pytorch推理.png
  9. BIN
      react-ui/public/assets/images/pytorch训练.png
  10. BIN
      react-ui/public/assets/images/sjj-icon-1.png
  11. BIN
      react-ui/public/assets/images/tensorflow模型转换.png
  12. BIN
      react-ui/public/assets/images/发送通知.png
  13. +27
    -32
      react-ui/src/app.tsx
  14. BIN
      react-ui/src/assets/img/model-deployment.zip
  15. +11
    -1
      react-ui/src/components/CommonTableCell/index.tsx
  16. +2
    -1
      react-ui/src/components/DateTableCell/index.tsx
  17. +0
    -2
      react-ui/src/components/KFModal/index.less
  18. +2
    -1
      react-ui/src/components/KFModal/index.tsx
  19. +5
    -0
      react-ui/src/components/KFRadio/index.less
  20. +1
    -1
      react-ui/src/components/PageTitle/index.less
  21. +1
    -0
      react-ui/src/components/RightContent/index.tsx
  22. +10
    -0
      react-ui/src/hooks/index.ts
  23. +1
    -1
      react-ui/src/iconfont/iconfont.js
  24. +29
    -13
      react-ui/src/overrides.less
  25. +9
    -0
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.less
  26. +198
    -0
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx
  27. +176
    -0
      react-ui/src/pages/Dataset/components/AddModelModal/index.tsx
  28. +169
    -0
      react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
  29. +41
    -0
      react-ui/src/pages/Dataset/components/CategoryItem/index.less
  30. +37
    -0
      react-ui/src/pages/Dataset/components/CategoryItem/index.tsx
  31. +22
    -0
      react-ui/src/pages/Dataset/components/CategoryList/index.less
  32. +71
    -0
      react-ui/src/pages/Dataset/components/CategoryList/index.tsx
  33. +39
    -0
      react-ui/src/pages/Dataset/components/ResourceList/index.less
  34. +210
    -0
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  35. +8
    -0
      react-ui/src/pages/Dataset/components/ResourcePage/index.less
  36. +113
    -0
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  37. +61
    -0
      react-ui/src/pages/Dataset/components/Resourcetem/index.less
  38. +54
    -0
      react-ui/src/pages/Dataset/components/Resourcetem/index.tsx
  39. +5
    -88
      react-ui/src/pages/Dataset/index.jsx
  40. +0
    -337
      react-ui/src/pages/Dataset/index.less
  41. +54
    -192
      react-ui/src/pages/Dataset/intro.jsx
  42. +79
    -0
      react-ui/src/pages/Dataset/intro.less
  43. +0
    -479
      react-ui/src/pages/Dataset/personalData.jsx
  44. +0
    -284
      react-ui/src/pages/Dataset/publicData.jsx
  45. +132
    -0
      react-ui/src/pages/Dataset/type.tsx
  46. +9
    -0
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.less
  47. +1
    -1
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
  48. +16
    -0
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.less
  49. +162
    -0
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx
  50. +38
    -0
      react-ui/src/pages/Experiment/components/ExperimentResult/index.less
  51. +59
    -0
      react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx
  52. +34
    -0
      react-ui/src/pages/Experiment/components/LogGroup/index.less
  53. +16
    -28
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  54. +3
    -0
      react-ui/src/pages/Experiment/components/LogList/index.less
  55. +21
    -0
      react-ui/src/pages/Experiment/components/LogList/index.tsx
  56. +0
    -0
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.less
  57. +3
    -2
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx
  58. +0
    -19
      react-ui/src/pages/Experiment/experimentText/LogList.tsx
  59. +0
    -22
      react-ui/src/pages/Experiment/experimentText/addExperimentModal.less
  60. +58
    -125
      react-ui/src/pages/Experiment/experimentText/index.jsx
  61. +26
    -85
      react-ui/src/pages/Experiment/experimentText/index.less
  62. +0
    -34
      react-ui/src/pages/Experiment/experimentText/logGroup.less
  63. +0
    -439
      react-ui/src/pages/Experiment/experimentText/props.jsx
  64. +37
    -0
      react-ui/src/pages/Experiment/experimentText/props.less
  65. +171
    -0
      react-ui/src/pages/Experiment/experimentText/props.tsx
  66. +4
    -9
      react-ui/src/pages/Experiment/index.jsx
  67. +3
    -3
      react-ui/src/pages/Mirror/create.less
  68. +14
    -26
      react-ui/src/pages/Mirror/create.tsx
  69. +5
    -6
      react-ui/src/pages/Mirror/info.tsx
  70. +2
    -2
      react-ui/src/pages/Mirror/list.less
  71. +15
    -21
      react-ui/src/pages/Mirror/list.tsx
  72. +5
    -87
      react-ui/src/pages/Model/index.jsx
  73. +0
    -327
      react-ui/src/pages/Model/index.less
  74. +42
    -178
      react-ui/src/pages/Model/intro.jsx
  75. +78
    -0
      react-ui/src/pages/Model/intro.less
  76. +0
    -525
      react-ui/src/pages/Model/personalData.jsx
  77. +0
    -384
      react-ui/src/pages/Model/publicData.jsx
  78. +11
    -0
      react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.less
  79. +39
    -0
      react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.tsx
  80. +17
    -0
      react-ui/src/pages/ModelDeployment/create.less
  81. +297
    -0
      react-ui/src/pages/ModelDeployment/create.tsx
  82. +53
    -0
      react-ui/src/pages/ModelDeployment/info.less
  83. +148
    -0
      react-ui/src/pages/ModelDeployment/info.tsx
  84. +21
    -0
      react-ui/src/pages/ModelDeployment/list.less
  85. +283
    -0
      react-ui/src/pages/ModelDeployment/list.tsx
  86. +0
    -0
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less
  87. +6
    -7
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
  88. +6
    -0
      react-ui/src/pages/Pipeline/components/PropsLabel/index.less
  89. +44
    -0
      react-ui/src/pages/Pipeline/components/PropsLabel/index.tsx
  90. +0
    -4
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx
  91. +11
    -0
      react-ui/src/pages/Pipeline/editPipeline/editPipeline.less
  92. +34
    -23
      react-ui/src/pages/Pipeline/editPipeline/index.jsx
  93. +7
    -22
      react-ui/src/pages/Pipeline/editPipeline/modelMenus.jsx
  94. +259
    -198
      react-ui/src/pages/Pipeline/editPipeline/props.jsx
  95. +79
    -0
      react-ui/src/pages/Pipeline/editPipeline/utils.tsx
  96. +12
    -17
      react-ui/src/pages/Pipeline/index.jsx
  97. +4
    -47
      react-ui/src/pages/Pipeline/index.less
  98. +191
    -456
      react-ui/src/pages/User/Login/index.tsx
  99. +29
    -26
      react-ui/src/pages/User/Login/login.less
  100. +6
    -7
      react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx

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

@@ -19,7 +19,7 @@ const Settings: ProLayoutProps & {
title: '智能软件开发平台',
pwa: true,
logo: '/assets/images/left-top-logo.png',
iconfontUrl: '//at.alicdn.com/t/c/font_4511326_1cmi0j3dj1x.js',
iconfontUrl: '//at.alicdn.com/t/c/font_4511326_a182r7rksx5.js',
token: {
// 参见ts声明,demo 见文档,通过token 修改样式
//https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F


+ 16
- 7
react-ui/config/routes.ts View File

@@ -131,7 +131,7 @@ export default [
{
name: '数据集简介',
path: ':id',
component: './Dataset/datasetIntro',
component: './Dataset/intro',
},
],
},
@@ -147,7 +147,7 @@ export default [
{
name: '模型简介',
path: ':id',
component: './Model/modelIntro',
component: './Model/intro',
},
],
},
@@ -188,14 +188,23 @@ export default [
],
},
{
name: 'modelDseployment',
path: '/modelDseployment',
name: 'modelDeployment',
path: '/modelDeployment',
routes: [
{
name: '模型部署',
name: '模型列表',
path: '',
key: 'modelDseployment',
component: './missingPage.jsx',
component: './ModelDeployment/list',
},
{
name: '镜像详情',
path: ':id',
component: './ModelDeployment/info',
},
{
name: '创建镜像',
path: 'create',
component: './ModelDeployment/create',
},
],
},


BIN
react-ui/public/assets/images/compoent-icon-6.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.5 kB

BIN
react-ui/public/assets/images/icon/流水线-1.png View File

Before After
Width: 54  |  Height: 54  |  Size: 1.6 kB

BIN
react-ui/public/assets/images/mindspore模型转换.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.9 kB

BIN
react-ui/public/assets/images/pipelieEditIcon.png View File

Before After
Width: 42  |  Height: 40  |  Size: 1.1 kB

BIN
react-ui/public/assets/images/pipeline-edit-icon.png View File

Before After
Width: 42  |  Height: 40  |  Size: 1.1 kB

BIN
react-ui/public/assets/images/pytorch推理.png View File

Before After
Width: 72  |  Height: 72  |  Size: 2.3 kB

BIN
react-ui/public/assets/images/pytorch训练.png View File

Before After
Width: 72  |  Height: 72  |  Size: 4.8 kB

BIN
react-ui/public/assets/images/sjj-icon-1.png View File

Before After
Width: 44  |  Height: 44  |  Size: 1.1 kB

BIN
react-ui/public/assets/images/tensorflow模型转换.png View File

Before After
Width: 72  |  Height: 72  |  Size: 4.0 kB

BIN
react-ui/public/assets/images/发送通知.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.2 kB

+ 27
- 32
react-ui/src/app.tsx View File

@@ -17,9 +17,10 @@ import {
patchRouteWithRemoteMenus,
setRemoteMenu,
} from './services/session';
import './styles/menu.less';
export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development';
import { menuItemRender } from '@/utils/menuRender';
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
@@ -139,10 +140,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
onClick: () => {
// 点击菜单项,删除所有的页面 state 缓存
removeAllPageCacheState();
// console.log('click menu');
},
// onSelect: (e) => {
// console.log(e);
// },
},
...initialState?.settings,
token: {
@@ -150,60 +149,49 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
colorTextMenu: themes['textColor'],
colorTextMenuSelected: themes['primaryColor'],
colorTextMenuActive: themes['primaryColor'],
colorTextMenuItemHover: themes['primaryColor'],
colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)',
colorMenuBackground: themes['siderBGColor'],
},
},
// menuItemRender: (itemProps, defaultDom, props) => {
// console.log('menuItemProps', itemProps);
// console.log('defaultDom', defaultDom);
// console.log('props', props);

// const { pathname } = window.location;
// const isSelected = pathname === itemProps.path;

// // 根据菜单项的状态动态显示不同的 icon
// const icon = isSelected ? 'icon-developmentEnvironment-icon' : 'icon-kaifahuanjing';
// return (
// <Link to={itemProps.path || ''} target={itemProps.target}>
// <KFIcon type={icon} />
// {itemProps.name}
// </Link>
// );
// },
menuItemRender: menuItemRender(false),
subMenuItemRender: menuItemRender(true),
};
};

export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => {
const { location } = e;
const menus = getRemoteMenu();
console.log('onRouteChange', e);
// console.log('onRouteChange', e);
if (menus === null && location.pathname !== PageEnum.LOGIN) {
console.log('refresh');
history.go(0);
}
};

export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => {
console.log('patchRoutes', e);
//console.log('patchRoutes', e);
};

export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => {
console.log('patchClientRoutes', e);
//console.log('patchClientRoutes', e);
patchRouteWithRemoteMenus(e.routes);
};

export function render(oldRender: () => void) {
console.log('render');
//console.log('render');
const token = getAccessToken();
if (!token || token?.length === 0) {
oldRender();
return;
}
getRoutersInfo().then((res) => {
setRemoteMenu(res);
oldRender();
});
getRoutersInfo()
.then((res) => {
setRemoteMenu(res);
oldRender();
})
.catch(() => {
oldRender();
});
}

// 主题修改
@@ -215,6 +203,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
colorError: themes['errorColor'],
colorWarning: themes['warningColor'],
colorLink: themes['primaryColor'],
colorText: themes['textColor'],
};
memo.theme.components ??= {};
memo.theme.components.Tabs = {};
@@ -229,10 +218,11 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']),
controlHeight: 34,
};
memo.theme.components.Input = {
inputFontSize: parseInt(themes['fontSize']),
inputFontSize: parseInt(themes['fontSizeInput']),
inputFontSizeLG: parseInt(themes['fontSizeInputLg']),
paddingBlockLG: 10,
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
@@ -241,6 +231,11 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme.components.Tabs = {
titleFontSize: 16,
};

memo.theme.components.Form = {
labelColor: 'rgba(29, 29, 32, 0.8);',
};

memo.theme.cssVar = true;
// memo.theme.hashed = false;



BIN
react-ui/src/assets/img/model-deployment.zip View File


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

@@ -4,8 +4,18 @@
* @Description: 自定义 Table 单元格,没有数据时展示 --
*/

function CommonTableCell(text?: string | null) {
import { Tooltip } from 'antd';

function renderCell(text?: string | null) {
return <span>{text ?? '--'}</span>;
}

function CommonTableCell(ellipsis: boolean = false) {
if (ellipsis) {
return (text?: string | null) => <Tooltip title={text}>{renderCell(text)}</Tooltip>;
} else {
return renderCell;
}
}

export default CommonTableCell;

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

@@ -4,6 +4,7 @@
* @Description: 自定义 Table 日期类单元格
*/

import { formatDate } from '@/utils/date';
import dayjs from 'dayjs';

function DateTableCell(text?: string | null) {
@@ -13,7 +14,7 @@ function DateTableCell(text?: string | null) {
if (!dayjs(text).isValid()) {
return <span>无效的日期</span>;
}
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
return <span>{formatDate(text)}</span>;
}

export default DateTableCell;

+ 0
- 2
react-ui/src/components/KFModal/index.less View File

@@ -23,8 +23,6 @@
border-radius: 10px;
}
.ant-btn-default {
color: @text-color;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
}
.ant-btn + .ant-btn {


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

@@ -12,11 +12,12 @@ import './index.less';
export interface KFModalProps extends ModalProps {
image?: string;
}
function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) {
function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) {
return (
<Modal
className={classNames(['kf-modal', className])}
{...rest}
centered={centered ?? true}
title={<ModalTitle title={title} image={image}></ModalTitle>}
>
{children}


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

@@ -23,6 +23,11 @@
&--active {
color: @primary-color;
border: 1px solid @primary-color;

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

& + & {


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

@@ -3,5 +3,5 @@
align-items: center;
height: 50px;
padding-left: 30px;
background-image: url('../../assets/img/page-title-bg.png');
background-image: url(@/assets/img/page-title-bg.png);
}

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

@@ -2,6 +2,7 @@ import { useEmotionCss } from '@ant-design/use-emotion-css';
import { useModel } from '@umijs/max';
import React from 'react';
import Avatar from './AvatarDropdown';
// import { SelectLang } from '@umijs/max';

export type SiderTheme = 'light' | 'dark';



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

@@ -126,3 +126,13 @@ export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => {
}
}, [form, prevOpen, open]);
};

export const useInputModel = <T>(initialValue: T) => {
const [value, setValue] = useState<T>(initialValue);

const updateValue = useCallback((e: any) => {
setValue(e.target?.value);
}, []);

return [value, updateValue];
};

+ 1
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


+ 29
- 13
react-ui/src/overrides.less View File

@@ -57,11 +57,6 @@
overflow-y: auto;
}

// Input
.ant-input-textarea-affix-wrapper.ant-input-affix-wrapper {
padding: 0;
}

// Modal
.ant-modal {
.ant-modal-close {
@@ -81,18 +76,24 @@
}
}

.ant-form-item .ant-form-item-label > label {
font-size: @font-size;
}

// 输入框高度为46px
.ant-input-affix-wrapper {
height: 46px;
padding: 1px 11px;
padding-top: 2px;
padding-bottom: 2px;

.ant-input {
height: 40px;
}
}

// 选择框高度为46px
.ant-select-single {
height: 46px;
}

.ant-select-single .ant-select-selector .ant-select-selection-placeholder {
line-height: 46px;
}
}

// Confirm Modal
@@ -128,8 +129,6 @@
border-radius: 10px;
}
.ant-btn-default {
color: @text-color;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
}
.ant-btn + .ant-btn {
@@ -137,3 +136,20 @@
}
}
}

// 表单类型为large时,font-size为15px
.ant-form-large {
.ant-form-item-label {
label {
font-size: @font-size;
}
}
}

// 取消 hover 颜色变化
.ant-menu .ant-menu-title-content {
transition: color 0s;
a {
transition: color 0s;
}
}

+ 9
- 0
react-ui/src/pages/Dataset/components/AddDatasetModal/index.less View File

@@ -0,0 +1,9 @@
.upload-tip {
margin-top: 5px;
color: @error-color;
}

.upload-button {
height: 46px;
font-size: 15px;
}

+ 198
- 0
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

@@ -0,0 +1,198 @@
import { getAccessToken } from '@/access';
import { DictValueEnumObj } from '@/components/DictTag';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { addDatesetAndVesion } from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
Input,
Radio,
Select,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import { CategoryData } from '../../type';
import styles from './index.less';

interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
typeList: CategoryData[];
tagList: CategoryData[];
onOk: () => void;
}

function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
const [uuid] = useState(Date.now());
const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);

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

// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/dataset/upload',
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

// 获取集群版本数据
const getClusterOptions = async () => {
const [res] = await to(getDictSelectOption('available_cluster'));
if (res) {
setClusterOptions(res);
}
};

// 上传请求
const createDataset = async (params: any) => {
const [res] = await to(addDatesetAndVesion(params));
if (res) {
message.success('创建成功');
onOk?.();
}
};

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const params = {
...omit(formData, ['fileList']),
dataset_version_vos: fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
};
createDataset(params);
}
};

return (
<KFModal
{...rest}
title="新建数据集"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
destroyOnClose
>
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
<Form.Item
label="数据集名称"
name="name"
required
rules={[
{
required: true,
message: '请输入数据集名称',
},
]}
>
<Input placeholder="请输入数据名称" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本',
},
]}
>
<Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item label="数据集分类" name="data_type">
<Select
allowClear
placeholder="请选择数据集分类"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="研究方向/应用领域" name="data_tag">
<Select
allowClear
placeholder="请选择研究方向/应用领域"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="集群版本" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item>
<Form.Item
label="数据集简介"
name="description"
rules={[
{
required: true,
message: '请输入数据集简介',
},
]}
>
<Input.TextArea
placeholder="请输入数据集简介"
showCount
maxLength={256}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
<Form.Item label="选择流水线" name="range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="数据集文件"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: '请上传数据集文件',
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
<div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddDatasetModal;

+ 176
- 0
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

@@ -0,0 +1,176 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { CategoryData } from '@/pages/Dataset/type';
import { addModel } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
Input,
Select,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useState } from 'react';
import styles from '../AddDatasetModal/index.less';

interface AddModelModalProps extends Omit<ModalProps, 'onOk'> {
typeList: CategoryData[];
tagList: CategoryData[];
onOk: () => void;
}

function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) {
const [uuid] = useState(Date.now());
// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/models/upload',
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

// 上传请求
const createModel = async (params: any) => {
const [res] = await to(addModel(params));
if (res) {
message.success('创建成功');
onOk?.();
}
};

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const params = {
...omit(formData, ['fileList']),
models_version_vos: fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
};
createModel(params);
}
};

return (
<KFModal
{...rest}
title="新建模型"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
>
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称!',
},
]}
>
<Input placeholder="请输入模型名称" showCount allowClear maxLength={64} />
</Form.Item>

<Form.Item
label="模型版本"
name="version"
rules={[
{
required: true,
message: '请输入模型版本',
},
]}
>
<Input placeholder="请输入模型版本" allowClear maxLength={64} />
</Form.Item>
<Form.Item
label="模型简介"
name="description"
rules={[
{
required: true,
message: '请输入模型简介',
},
]}
>
<Input.TextArea
placeholder="请输入模型简介"
showCount
maxLength={256}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
{/* <Form.Item label="可见范围" name="available_range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item> */}
<Form.Item label="模型框架" name="model_type">
<Select
allowClear
placeholder="请选择模型类型"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="模型能力" name="model_tag">
<Select
allowClear
placeholder="请选择模型标签"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item
label="模型文件"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: '请上传模型文件',
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }}>
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddModelModal;

+ 169
- 0
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

@@ -0,0 +1,169 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { ResourceType, resourceConfig } from '@/pages/Dataset/type';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
Input,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useState } from 'react';
import styles from '../AddDatasetModal/index.less';

interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> {
resourceType: ResourceType;
resourceId: number;
initialName: string;
onOk: () => void;
}

function AddVersionModal({
resourceType,
resourceId,
initialName,
onOk,
...rest
}: AddVersionModalProps) {
const [uuid] = useState(Date.now());

// 上传组件参数
const uploadProps: UploadProps = {
action: resourceConfig[resourceType].uploadAction,
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

// 上传请求
const createDatasetVersion = async (params: any) => {
const request = resourceConfig[resourceType].addVersionReq;
const [res] = await to(request(params));
if (res) {
message.success('创建成功');
onOk?.();
}
};

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const otherParams = omit(formData, ['fileList']);
const params = fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
...otherParams,
[resourceConfig[resourceType].idParamKey]: resourceId,
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
});
createDatasetVersion(params);
}
};

const name = resourceConfig[resourceType].name;
const accept = resourceConfig[resourceType].uploadAccept;
return (
<KFModal
{...rest}
title="创建新版本"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
>
<Form
name="form"
layout="vertical"
initialValues={{
name: initialName,
}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label={`${name}名称`}
name="name"
rules={[
{
required: true,
message: `请输入${name}名称`,
},
]}
>
<Input disabled placeholder={`请输入${name}名称`} />
</Form.Item>
<Form.Item
label={`${name}版本`}
name="version"
rules={[
{
required: true,
message: `请输入${name}版本`,
},
]}
>
<Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={256}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label={`${name}文件`}
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: `请上传${name}文件`,
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }} accept={accept}>
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
{resourceType === ResourceType.Dataset && (
<div className={styles['upload-tip']}>只允许上传.zip格式文件</div>
)}
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddVersionModal;

+ 41
- 0
react-ui/src/pages/Dataset/components/CategoryItem/index.less View File

@@ -0,0 +1,41 @@
.category-item {
display: flex;
flex-direction: column;
align-items: center;
width: 92px;
height: 62px;
padding: 11px 8px 7px;
color: @text-color;
font-size: 12px;
border: 1px solid rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;

&__icon {
display: block;
}
&__active-icon {
display: none;
}
&__name {
width: 100%;
margin-top: 4px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
}

&:hover,
&--active {
background: rgba(22, 100, 255, 0.03);
border: 1px solid @primary-color;

.category-item__icon {
display: none;
}
.category-item__active-icon {
display: block;
}
}
}

+ 37
- 0
react-ui/src/pages/Dataset/components/CategoryItem/index.tsx View File

@@ -0,0 +1,37 @@
import classNames from 'classnames';
import { CategoryData, ResourceType, resourceConfig } from '../../type';
import styles from './index.less';

type CategoryItemProps = {
resourceType: ResourceType;
item: CategoryData;
isSelected: boolean;
onClick: (item: CategoryData) => void;
};

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

export default CategoryItem;

+ 22
- 0
react-ui/src/pages/Dataset/components/CategoryList/index.less View File

@@ -0,0 +1,22 @@
.category-list {
width: 340px;
height: 100%;
margin-right: 10px;
padding: 15px 0;
background: white;
border-radius: 4px;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__content {
height: calc(100% - 32px - 15px);
margin-top: 15px;
padding-left: 20px;
overflow-y: auto;

&__title {
margin-bottom: 15px;
color: @text-color;
font-size: 14px;
}
}
}

+ 71
- 0
react-ui/src/pages/Dataset/components/CategoryList/index.tsx View File

@@ -0,0 +1,71 @@
import { Flex, Input } from 'antd';
import { CategoryData, ResourceType, resourceConfig } from '../../type';
import CategoryItem from '../CategoryItem';
import styles from './index.less';

export type CategoryValue = {
dataType: number | undefined;
dataTag: number | undefined;
};

type CategoryProps = {
resourceType: ResourceType; // 资源类型,数据集还是模型
typeList: CategoryData[];
tagList: CategoryData[];
activeType?: number;
activeTag?: number;
onTypeSelect: (value: CategoryData) => void;
onTagSelect: (value: CategoryData) => void;
onSearch: (value: string) => void;
};

function CategoryList({
resourceType,
typeList,
tagList,
activeType,
activeTag,
onTypeSelect,
onTagSelect,
onSearch,
}: CategoryProps) {
return (
<div className={styles['category-list']}>
<div style={{ padding: '0 20px' }}>
<Input.Search placeholder="搜索" allowClear onSearch={onSearch} />
</div>
<div className={styles['category-list__content']}>
<div className={styles['category-list__content__title']}>
{resourceConfig[resourceType].typeTitle}
</div>
<Flex wrap="wrap" gap="20px 12px">
{typeList?.map((item) => (
<CategoryItem
key={item.id}
resourceType={resourceType}
item={item}
onClick={onTypeSelect}
isSelected={item.id === activeType}
></CategoryItem>
))}
</Flex>
<div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}>
{resourceConfig[resourceType].tagTitle}
</div>
<Flex wrap="wrap" gap="20px 12px">
{tagList?.map((item) => (
<CategoryItem
key={item.id}
resourceType={resourceType}
item={item}
onClick={onTagSelect}
isSelected={item.id === activeTag}
></CategoryItem>
))}
</Flex>
</div>
</div>
);
}

export default CategoryList;

+ 39
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.less View File

@@ -0,0 +1,39 @@
.resource-list {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
padding: 20px 0;
background: white;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
padding: 0 30px;
color: @text-color;
font-size: 15px;
}

&__content {
display: flex;
flex: 1;
flex-wrap: wrap;
gap: 20px;
align-content: flex-start;
width: 100%;
margin-bottom: 30px;
padding: 0 30px;
overflow-y: auto;
}

:global {
.ant-pagination {
margin-right: 30px;
text-align: right;
}
}
}

+ 210
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -0,0 +1,210 @@
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import AddModelModal from '@/pages/Dataset/components/AddModelModal';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { Button, Input, Pagination, PaginationProps, message } from 'antd';
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../type';
import AddDatasetModal from '../AddDatasetModal';
import ResourceItem from '../Resourcetem';
import styles from './index.less';

export type ResourceListRef = {
reset: () => void;
};

type ResourceListProps = {
resourceType: ResourceType;
dataType?: number;
dataTag?: number;
isPublic: boolean;
typeList: CategoryData[];
tagList: CategoryData[];
initialSearchText?: string;
initialPagination?: PaginationProps;
setCacheState: (state?: any) => void;
};

function ResourceList(
{
resourceType,
dataType,
dataTag,
typeList,
tagList,
isPublic,
initialSearchText,
initialPagination,
setCacheState,
}: ResourceListProps,
ref: Ref<ResourceListRef>,
) {
const navigate = useNavigate();
const [dataList, setDataList] = useState<ResourceData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<PaginationProps>(
initialPagination ?? {
current: 1,
pageSize: 20,
},
);
const [searchText, setSearchText] = useState(initialSearchText);
const [inputText, setInputText] = useState(initialSearchText);

useEffect(() => {
getDataList();
}, [resourceType, dataType, dataTag, pagination, searchText, isPublic]);

useImperativeHandle(
ref,
() => {
return {
reset: () => {
setPagination({
current: 1,
pageSize: 20,
});
setSearchText('');
setInputText('');
setDataList([]);
},
};
},
[],
);

// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
[resourceConfig[resourceType].typeParamKey]: dataType,
[resourceConfig[resourceType].tagParamKey]: dataTag,
available_range: isPublic ? 1 : 0,
name: searchText !== '' ? searchText : undefined,
};
const request = resourceConfig[resourceType].getList;
const [res] = await to(request(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

// 删除请求
const deleteRecord = async (id: number) => {
const request = resourceConfig[resourceType].deleteRecord;
const [res] = await to(request(id));
if (res) {
getDataList();
message.success('删除成功');
}
};

// 搜索
const handleSearch = (value: string) => {
setSearchText(value);
};

// 删除
const handleRemove = (record: ResourceData) => {
modalConfirm({
title: resourceConfig[resourceType].deleteModalTitle,
onOk: () => {
deleteRecord(record.id);
},
});
};

// 跳转
const handleClick = (record: ResourceData) => {
setCacheState({
activeTab: isPublic ? CommonTabKeys.Public : CommonTabKeys.Private,
pagination,
searchText,
activeType: dataType,
activeTag: dataTag,
});
if (resourceType === ResourceType.Dataset) {
navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`);
} else {
navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`);
}
};

// 分页切换
const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => {
setPagination({
current: page,
pageSize: pageSize,
});
};

// 新建弹框
const showModal = () => {
const Modal = resourceType === ResourceType.Dataset ? AddDatasetModal : AddModelModal;
const { close } = openAntdModal(Modal, {
typeList: typeList,
tagList: tagList,
onOk: () => {
getDataList();
close();
},
});
};

return (
<div className={styles['resource-list']}>
<div className={styles['resource-list__header']}>
<span>数据总数:{total}个</span>
<div>
<Input.Search
placeholder="按数据名称筛选"
allowClear
onSearch={handleSearch}
style={{
width: 300,
}}
onChange={(e) => setInputText(e.target.value)}
value={inputText}
/>
{!isPublic && (
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
{resourceConfig[resourceType].addBtnTitle}
</Button>
)}
</div>
</div>
<div className={styles['resource-list__content']}>
{dataList?.map((item) => (
<ResourceItem
item={item}
key={item.id}
isPublic={isPublic}
onRemove={handleRemove}
onClick={handleClick}
></ResourceItem>
))}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={handlePageChange}
{...pagination}
/>
</div>
);
}

export default forwardRef(ResourceList);

+ 8
- 0
react-ui/src/pages/Dataset/components/ResourcePage/index.less View File

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

+ 113
- 0
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -0,0 +1,113 @@
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { getAssetIcon } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { Flex, Tabs, type TabsProps } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { CategoryData, ResourceType, resourceConfig } from '../../type';
import CategoryList from '../CategoryList';
import ResourceList, { ResourceListRef } from '../ResourceList';
import styles from './index.less';

type ResourcePageProps = {
resourceType: ResourceType; // 资源类型,数据集还是模型
};

function ResourcePage({ resourceType }: ResourcePageProps) {
const [cacheState, setCacheState] = useCacheState();
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
const [typeList, setTypeList] = useState<CategoryData[]>([]);
const [tagList, setTagList] = useState<CategoryData[]>([]);
const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType);
const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag);
const dataListRef = useRef<ResourceListRef>(null);

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

// 分类搜索
const handleCategorySearch = (value: string) => {
getAssetIconList(value);
};

// 选择类型
const chooseType = (record: CategoryData) => {
setActiveType((prev) => (prev === record.id ? undefined : record.id));
};

// 选择 Tag
const chooseTag = (record: CategoryData) => {
setActiveTag((prev) => (prev === record.id ? undefined : record.id));
};

// 获取分类
const getAssetIconList = async (name: string = '') => {
const params = {
name: name,
page: 0,
size: 10000,
};
const [res] = await to(getAssetIcon(params));
if (res && res.data && res.data.content) {
const { content } = res.data;
setTypeList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].typeValue,
),
);
setTagList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].tagValue,
),
);
}
};

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
dataListRef.current?.reset();
setActiveTab(value);
};

const isPublic = activeTab === CommonTabKeys.Public;
return (
<div className={styles['resource-page']}>
<div className={styles['resource-page__tabs-container']}>
<Tabs
activeKey={activeTab}
items={resourceConfig[resourceType].tabItems}
onChange={hanleTabChange}
/>
</div>
<Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}>
<CategoryList
resourceType={resourceType}
typeList={typeList}
tagList={tagList}
activeType={activeType}
activeTag={activeTag}
onTypeSelect={chooseType}
onTagSelect={chooseTag}
onSearch={handleCategorySearch}
/>
<ResourceList
ref={dataListRef}
resourceType={resourceType}
typeList={typeList}
tagList={tagList}
isPublic={isPublic}
dataType={activeType}
dataTag={activeTag}
initialSearchText={cacheState?.searchText}
initialPagination={cacheState?.initialPagination}
setCacheState={setCacheState}
></ResourceList>
</Flex>
</div>
);
}

export default ResourcePage;

+ 61
- 0
react-ui/src/pages/Dataset/components/Resourcetem/index.less View File

@@ -0,0 +1,61 @@
.resource-item {
position: relative;
width: calc(25% - 15px);
padding: 20px;
background: white;
border: 1px solid #eaeaea;
border-radius: 4px;
cursor: pointer;

@media screen and (max-width: 1860px) {
& {
width: calc(33.33% - 13.33px);
}
}

&__name {
position: relative;
display: inline-block;
height: 24px;
margin: 0 10px 0 0 !important;
color: @text-color;
font-size: 16px;
}

&__description {
height: 44px;
margin-bottom: 20px;
color: @text-color-secondary;
font-size: 14px;
.multiLine(2);
}
&__time {
display: flex;
flex: 0 1 content;
align-items: center;
width: 100%;
color: #808080;
font-size: 13px;
}

&:hover {
border-color: @primary-color;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);

.resource-item__name {
color: @primary-color;
}
}
}

.resource-item__name {
&::after {
position: absolute;
top: 14px;
left: 0;
width: 100%;
height: 6px;
background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%);
content: '';
}
}

+ 54
- 0
react-ui/src/pages/Dataset/components/Resourcetem/index.tsx View File

@@ -0,0 +1,54 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import KFIcon from '@/components/KFIcon';
import { formatDate } from '@/utils/date';
import { Button, Flex, Typography } from 'antd';
import { ResourceData } from '../../type';
import styles from './index.less';

type ResourceItemProps = {
item: ResourceData;
isPublic: boolean;
onRemove: (item: ResourceData) => void;
onClick: (item: ResourceData) => void;
};

function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) {
return (
<div className={styles['resource-item']} onClick={() => onClick(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}>
<Typography.Paragraph
className={styles['resource-item__name']}
ellipsis={{ tooltip: item.name }}
>
{item.name}
</Typography.Paragraph>
{!isPublic && (
<Button
type="text"
shape="circle"
onClick={(e) => {
e.stopPropagation();
onRemove(item);
}}
>
<KFIcon type="icon-shanchu" font={17} />
</Button>
)}
</Flex>
<div className={styles['resource-item__description']}>{item.description}</div>
<Flex justify="space-between">
<div className={styles['resource-item__time']}>
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
<span>{item.create_by}</span>
</div>
<div className={styles['resource-item__time']}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span>
</div>
</Flex>
</div>
);
}

export default ResourceItem;

+ 5
- 88
react-ui/src/pages/Dataset/index.jsx View File

@@ -1,90 +1,7 @@
import { getDatasetList } from '@/services/dataset/index.js';
import { Form, Input, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
import PersonalData from './personalData';
import PublicData from './publicData';
const { Search } = Input;
const { TabPane } = Tabs;
const leftdataList = [1, 2, 3];
import ResourcePage from './components/ResourcePage';
import { ResourceType } from './type';

const Dataset = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
});
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getDatasetlist = () => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
navgite({ pathname: '/dataset/dataset' });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
//getDatasetlist();
return () => {};
}, []);
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetTopBox}></div>
<div className={Styles.datasetAllBox}>
<Tabs defaultActiveKey="1">
<TabPane
tab="数据广场"
key="1"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-shujujiguangchang"></use>
</svg>
}
>
<PublicData />
</TabPane>
<TabPane
tab="个人数据"
key="2"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-gerenshujuji"></use>
</svg>
}
>
<PersonalData />
</TabPane>
</Tabs>
</div>
</div>
);
const DatasetPage = () => {
return <ResourcePage resourceType={ResourceType.Dataset} />;
};
export default Dataset;
export default DatasetPage;

+ 0
- 337
react-ui/src/pages/Dataset/index.less View File

@@ -1,337 +0,0 @@
.datasetTopBox {
display: flex;
align-items: center;
width: 100%;
height: 49px;
padding: 0 30px;
padding-right: 30px;
font-family: 'Alibaba';
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.datasetIntroTopBox {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 25px 30px;
background-image: url(/assets/images/dataset-back.png);
background-size: 100% 100%;
.smallTagBox {
display: flex;
align-items: center;
color: #1664ff;
font-size: 14px;
.tagItem {
margin-right: 20px;
padding: 4px 10px;
background: rgba(22, 100, 255, 0.1);
border-radius: 4px;
}
}
}
.dataListBox {
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);
.dataButtonList {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin: 24px 0 30px 0;
color: #575757;
font-size: 16px;
}
}
.datasetIntroCneterBox {
height: 77vh;
padding: 20px 30px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}
.datasetIntroTitle {
margin: 37px 0 10px 0;
color: #1d1d20;
font-size: 15px;
}
.datasetIntroText {
margin-bottom: 30px;
color: #575757;
font-size: 14px;
}
.datasetBox {
font-family: 'Alibaba';
background: #f9fafb;

:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
}
.ant-pagination {
text-align: right;
}
}
}
.datasetAllBox {
:global {
.ant-tabs-nav .ant-tabs-nav-wrap {
margin: -48px 0 0 30px;
}
}
}
.plusButton {
margin: 0 18px 0 20px;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
width: 100%;
height: 87.5vh;
:global {
.ant-btn {
color: #1d1d20;
font-size: 14px;
}
}
.datasetCneterLeftBox {
width: 340px;
height: 100%;
margin-right: 10px;
padding-top: 15px;
font-family: 'Alibaba';
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
height: 32px;
border-bottom: 1px solid #e0eaff;
.tabItem {
width: 52px;
height: 100%;
color: #808080;
font-size: 15px;
text-align: center;
cursor: pointer;
}
}
.leftContentBox {
max-height: 80vh;
padding: 15px 20px;
overflow-x: hidden;
overflow-y: auto;
font-family: 'Alibaba';
.itemTitle {
margin-bottom: 15px;
color: #1d1d20;
font-size: 14px;
}
.itemBox {
display: flex;
flex-wrap: wrap;
align-content: start;
width: 110%;
.messageBox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 92px;
height: 62px;
margin: 0 12px 20px 0;
padding: 11px 0px 7px 0px;
color: #1d1d20;
font-size: 12px;
border: 1px solid;
border-color: rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;
.ptIcon {
display: block;
}
.hoverIcon {
display: none;
}
.messageText {
width: 65px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
}
}
.messageBox:hover {
background: rgba(22, 100, 255, 0.03);
border: 1px solid;
border-color: #1664ff;
.ptIcon {
display: none;
}
.hoverIcon {
display: block;
}
}
.active {
background: rgba(22, 100, 255, 0.03) !important;
border: 1px solid !important;
border-color: #1664ff !important;
.ptIcon {
display: none !important;
}
.hoverIcon {
display: block !important;
}
}
}
}
}

.datasetCneterRightBox {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.dataSource {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.dataContent {
display: flex;
flex: 1;
flex-wrap: wrap;
align-content: flex-start;

width: 100%;
.dataItem {
position: relative;
width: 23.8%;
height:164px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText {
position: absolute;
top: 20px;
left: 20px;
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
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 .itemText {
color: #1664ff;
}
}
}
}
.tipContent{
color: #c73131;
margin-top: 5px;
}
.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
}

react-ui/src/pages/Dataset/datasetIntro.jsx → react-ui/src/pages/Dataset/intro.jsx View File

@@ -1,61 +1,34 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import { ResourceType } from '@/pages/Dataset/type';
import {
addDatasetVersionDetail,
deleteDatasetVersion,
getDatasetById,
getDatasetVersionIdList,
getDatasetVersionsById,
} from '@/services/dataset/index.js';
import { formatDate } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
import { openAntdModal } from '@/utils/modal';
import { modalConfirm } from '@/utils/ui';
import { useParams, useSearchParams } from '@umijs/max';
import { Button, Input, Select, Table, Tabs, message } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import Styles from './index.less';
import AddVersionModal from './components/AddVersionModal';
import Styles from './intro.less';
const { Search } = Input;
const { TabPane } = Tabs;

const Dataset = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
setFormList(
fileList.map((item) => {
return {
...form.getFieldsValue(),
dataset_id: locationParams.id,
file_name: item.response.code === 200 ? item.response.data[0].fileName : null,
file_size: item.response.code === 200 ? item.response.data[0].fileSize : null,
url: item.response.code === 200 ? item.response.data[0].url : null,
};
}),
);
}
},
defaultFileList: [],
};
const [form] = Form.useForm();
const [formList, setFormList] = useState([]);
const [dialogTitle, setDialogTitle] = useState('新建版本');
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetDetailObj, setDatasetDetailObj] = useState({});
const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口
const [searchParams] = useSearchParams();
const [wordList, setWordList] = useState([]);
const [activeTabKey, setActiveTabKey] = useState('1');
const [uuid, setUuid] = useState(Date.now());
const isPublic = searchParams.get('isPublic') === 'true';

const getDatasetByDetail = () => {
getDatasetById(locationParams.id).then((ret) => {
console.log(ret);
@@ -77,6 +50,9 @@ const Dataset = () => {
);
setVersion(ret.data[0]);
getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
}
});
};
@@ -86,37 +62,21 @@ const Dataset = () => {
return () => {};
}, []);
const showModal = () => {
form.resetFields();
form.setFieldsValue({ name: datasetDetailObj.name });

setDialogTitle('创建新版本');
setUuid(Date.now());
setIsModalOpen(true);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const handleExport = async () => {
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version });
const { close } = openAntdModal(AddVersionModal, {
resourceType: ResourceType.Dataset,
resourceId: locationParams.id,
initialName: datasetDetailObj.name,
onOk: () => {
getDatasetVersionList();
close();
},
});
};
const deleteDataset = () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该数据集版本将不可恢复</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
okText: '确认',
cancelText: '取消',

const deleteDataset = () => {
modalConfirm({
title: '删除后,该数据集版本将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => {
getDatasetVersionList();
@@ -125,19 +85,26 @@ const Dataset = () => {
},
});
};
const onFinish = (values) => {
addDatasetVersionDetail(formList).then((ret) => {
getDatasetVersionList();
setIsModalOpen(false);
message.success('创建成功');
});
};
// 获取版本下的文件列表
const getDatasetVersions = (params) => {
getDatasetVersionIdList(params).then((res) => {
setWordList(res?.data?.content ?? []);
});
};

const handleExport = async () => {
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version });
};

const downloadAlone = (e, record) => {
console.log(record);
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/download/${record.id}`);
};

const handleChange = (value) => {
console.log(value);
if (value) {
@@ -147,15 +114,7 @@ const Dataset = () => {
setVersion(null);
}
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const downloadAlone = (e, record) => {
console.log(record);
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/download/${record.id}`);
};

const columns = [
{
title: '序号',
@@ -187,7 +146,7 @@ const Dataset = () => {
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: (text) => <span>{formatDate(text)}</span>,
},
{
title: '操作',
@@ -264,15 +223,17 @@ const Dataset = () => {
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}
<Button
type="default"
disabled={!version}
@@ -295,105 +256,6 @@ const Dataset = () => {
</TabPane>
</Tabs>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据集名称"
name="name"
rules={[
{
required: true,
message: '请输入数据集名称',
},
]}
>
<Input disabled placeholder="请输入数据集名称" />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本',
},
]}
>
<Input placeholder="请输入数据集版本" maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={256}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label="数据集文件"
name="dataset_version_vos"
rules={[
{
required: true,
message: '请上传数据集文件',
},
]}
>
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff' }} />}
>
上传文件
</Button>
<div className={Styles.tipContent}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</Modal>
</div>
);
};

+ 79
- 0
react-ui/src/pages/Dataset/intro.less View File

@@ -0,0 +1,79 @@
.datasetIntroTopBox {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 25px 30px;
background-image: url(/assets/images/dataset-back.png);
background-size: 100% 100%;
.smallTagBox {
display: flex;
align-items: center;
color: #1664ff;
font-size: 14px;
.tagItem {
margin-right: 20px;
padding: 4px 10px;
background: rgba(22, 100, 255, 0.1);
border-radius: 4px;
}
}
}
.dataListBox {
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);
.dataButtonList {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin: 24px 0 30px 0;
color: #575757;
font-size: 16px;
}
}
.datasetIntroCneterBox {
height: 77vh;
padding: 20px 30px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}
.datasetIntroTitle {
margin: 37px 0 10px 0;
color: #1d1d20;
font-size: 15px;
}
.datasetIntroText {
margin-bottom: 30px;
color: #575757;
font-size: 14px;
}
.datasetBox {
background: #f9fafb;

:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
}
.ant-pagination {
text-align: right;
}
}
}

.plusButton {
margin: 0 18px 0 20px;
}

.tipContent {
margin-top: 5px;
color: #c73131;
}

+ 0
- 479
react-ui/src/pages/Dataset/personalData.jsx View File

@@ -1,479 +0,0 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon';
import {
addDatesetAndVesion,
deleteDataset,
getAssetIcon,
getDatasetList,
} from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';

import './index.less';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = (React.FC = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
form.setFieldsValue({
dataset_version_vos: fileList.map((item) => {
const data = item.response.data[0];
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
});
}
},
defaultFileList: [],
};
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 0,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const navgite = useNavigate();
const [clusterOptions, setClusterOptions] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const [uuid, setUuid] = useState(Date.now());
const getDatasetlist = (queryFlow) => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setUuid(Date.now());
setIsModalOpen(true);
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const nameSearch = (values) => {
console.log(values);
getDatasetlist({ ...queryFlow, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const onFinish = (values) => {
addDatesetAndVesion(values).then((ret) => {
console.log(ret);
setIsModalOpen(false);
getDatasetlist(queryFlow);
});
};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/dataset/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getDictSelectOption('available_cluster').then((data) => {
setClusterOptions(data);
});
getAssetIconList(iconParams);
getDatasetlist(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>分类</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>研究方向/应用领域</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
新建数据集
</Button>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>

<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据名称"
name="name"
required
rules={[
{
required: true,
message: '请输入数据名称e!',
},
]}
>
<Input placeholder="请输入数据名称" showCount maxLength={64} />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本!',
},
]}
>
<Input placeholder="请输入数据集版本" showCount maxLength={64} />
</Form.Item>
<Form.Item
label="数据集分类"
name="data_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择数据集分类"
options={datasetTypeList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item
label="研究方向/应用领域"
name="data_tag"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择研究方向/应用领域"
options={datasetDirectionList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item label="集群版本" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item>
<Form.Item
label="数据简介"
name="description"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据简介" showCount maxLength={256} />
</Form.Item>
<Form.Item label="选择流水线" name="range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="数据文件" name="dataset_version_vos">
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />}
>
上传文件
</Button>
<div className={Styles.tipContent}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</Modal>
</>
);
});
export default PublicData;

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

@@ -1,284 +0,0 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import { deleteDataset, getAssetIcon, getDatasetList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Pagination } from 'antd';
import moment from 'moment';
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 = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
available_range: 1,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const navgite = useNavigate();
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getDatasetlist = (queryFlow) => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const nameSearch = (values) => {
console.log(values);
getDatasetlist({ ...queryFlow, name: values });
};
const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};

const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/dataset/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getDatasetlist(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>分类</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>研究方向/应用领域</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
</>
);
};
export default PublicData;

+ 132
- 0
react-ui/src/pages/Dataset/type.tsx View File

@@ -0,0 +1,132 @@
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import {
addDatasetVersionDetail,
addModelsVersionDetail,
deleteDataset,
deleteModel,
getDatasetList,
getDatasetVersionIdList,
getDatasetVersionsById,
getModelList,
getModelVersionIdList,
getModelVersionsById,
} from '@/services/dataset/index.js';
import type { TabsProps } from 'antd';

export enum ResourceType {
Model = 'Model', // 模型
Dataset = 'Dataset', // 数据集
}

type ResourceTypeKeys = keyof typeof ResourceType;
export type ResourceTypeValues = (typeof ResourceType)[ResourceTypeKeys];

type ResourceTypeInfo = {
getList: (params: any) => Promise<any>;
getVersions: (params: any) => Promise<any>;
getFiles: (params: any) => Promise<any>;
deleteRecord: (params: any) => Promise<any>;
name: string;
typeParamKey: string;
tagParamKey: string;
fileReqParamKey: 'models_id' | 'dataset_id';
tabItems: TabsProps['items'];
typeTitle: string;
tagTitle: string;
typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue)
tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue)
iconPathPrefix: string; // 图标路径前缀
deleteModalTitle: string; // 删除弹框的title
addBtnTitle: string; // 新增按钮的title
addVersionReq: (params: any) => Promise<any>;
idParamKey: string;
uploadAction: string;
uploadAccept?: string;
};

export const resourceConfig: Record<ResourceTypeValues, ResourceTypeInfo> = {
[ResourceType.Dataset]: {
getList: getDatasetList,
getVersions: getDatasetVersionsById,
getFiles: getDatasetVersionIdList,
deleteRecord: deleteDataset,
name: '数据集',
typeParamKey: 'data_type',
tagParamKey: 'data_tag',
fileReqParamKey: 'dataset_id',
tabItems: [
{
key: CommonTabKeys.Public,
label: '数据广场',
icon: <KFIcon type="icon-shujuguangchang" />,
},
{
key: CommonTabKeys.Private,
label: '个人数据',
icon: <KFIcon type="icon-gerenshuju" />,
},
],
typeTitle: '分类',
tagTitle: '研究方向/应用领域',
typeValue: 1,
tagValue: 2,
iconPathPrefix: 'dataset',
deleteModalTitle: '确定删除该条数据集实例吗?',
addBtnTitle: '新建数据集',
addVersionReq: addDatasetVersionDetail,
idParamKey: 'dataset_id',
uploadAction: '/api/mmp/dataset/upload',
uploadAccept: '.zip,.tgz',
},
[ResourceType.Model]: {
getList: getModelList,
getVersions: getModelVersionsById,
getFiles: getModelVersionIdList,
deleteRecord: deleteModel,
name: '模型',
typeParamKey: 'model_type',
tagParamKey: 'model_tag',
fileReqParamKey: 'models_id',
tabItems: [
{
key: CommonTabKeys.Public,
label: '模型广场',
icon: <KFIcon type="icon-moxingguangchang" />,
},
{
key: CommonTabKeys.Private,
label: '个人模型',
icon: <KFIcon type="icon-gerenmoxing" />,
},
],
typeTitle: '模型框架',
tagTitle: '模型能力',
typeValue: 3,
tagValue: 4,
iconPathPrefix: 'model',
deleteModalTitle: '确定删除该条模型实例吗?',
addBtnTitle: '新建模型',
addVersionReq: addModelsVersionDetail,
idParamKey: 'models_id',
uploadAction: '/api/mmp/models/upload',
uploadAccept: undefined,
},
};

// 分类数据
export type CategoryData = {
id: number;
category_id: number;
name: string;
path: string;
};

// 数据类型
export type ResourceData = {
id: number;
name: string;
description: string;
create_by: string;
update_time: string;
};

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

@@ -0,0 +1,9 @@
.modal {
.global_param_item {
max-height: 230px;
padding: 24px 12px 0;
overflow-y: auto;
border: 1px solid #e6e6e6;
border-radius: 6px;
}
}

react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx → react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx View File

@@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal';
import { type PipelineGlobalParam } from '@/types';
import { Form, Input, Radio, Select, type FormRule } from 'antd';
import { useState } from 'react';
import styles from './addExperimentModal.less';
import styles from './index.less';

type FormData = {
name?: string;

+ 16
- 0
react-ui/src/pages/Experiment/components/ExperimentParameter/index.less View File

@@ -0,0 +1,16 @@
.experiment-parameter {
padding-top: 8px;

&__title {
display: flex;
align-items: center;
height: 43px;
margin-right: 8px;
margin-bottom: 20px;
margin-left: 8px;
padding: 0 24px;
color: @text-color;
font-size: @font-size;
background: #f8fbff;
}
}

+ 162
- 0
react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx View File

@@ -0,0 +1,162 @@
import SubAreaTitle from '@/components/SubAreaTitle';
import { PipelineNodeModelSerialize } from '@/types';
import { Form, Input, type FormProps } from 'antd';
import styles from './index.less';
const { TextArea } = Input;

type ExperimentParameterProps = {
form: FormProps['form'];
nodeData: PipelineNodeModelSerialize;
};

function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
// 控制策略
const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);

// 输入参数
const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({
key,
value,
}));

// 输出参数
const outParametersList = Object.entries(nodeData.out_parameters ?? {}).map(([key, value]) => ({
key,
value,
}));

return (
<Form
name="form"
layout="vertical"
labelCol={{
span: 24,
}}
wrapperCol={{
span: 24,
}}
form={form}
style={{
maxWidth: 600,
}}
autoComplete="off"
className={styles['experiment-parameter']}
>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle>
</div>
<Form.Item
label="任务名称"
name="label"
rules={[
{
required: true,
message: '请输入任务名称',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
label="任务ID"
name="id"
rules={[
{
required: true,
message: '请输入任务id',
},
]}
>
<Input disabled />
</Form.Item>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<Input disabled />
</Form.Item>

<Form.Item label="启动命令" name="command">
<TextArea disabled />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<Input disabled />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<TextArea disabled />
</Form.Item>
{controlStrategyList.map((item) => (
<Form.Item
key={item.key}
name={['control_strategy', item.key]}
label={item.value.label}
getValueProps={(e) => {
return { value: e.value };
}}
>
<Input disabled />
</Form.Item>
))}
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle>
</div>
{inParametersList.map((item) => (
<Form.Item
key={item.key}
name={['in_parameters', item.key]}
label={item.value.label + '(' + item.key + ')'}
rules={[{ required: item.value.require ? true : false }]}
getValueProps={(e) => {
return { value: e.value };
}}
>
<Input disabled />
</Form.Item>
))}
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle>
</div>
{outParametersList.map((item) => (
<Form.Item
key={item.key}
name={['out_parameters', item.key]}
label={item.value.label + '(' + item.key + ')'}
rules={[{ required: item.value.require ? true : false }]}
getValueProps={(e) => {
return { value: e.value };
}}
>
<Input disabled />
</Form.Item>
))}
</Form>
);
}

export default ExperimentParameter;

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

@@ -0,0 +1,38 @@
.experiment-result {
padding: 8px;
color: @text-color;
font-size: 14px;

&__content {
padding: 10px 20px 20px 20px;
background-color: rgba(234, 234, 234, 0.5);
}

&__item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}

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

&__file {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
padding: 0 20px 0 0;

&:last-child {
margin-bottom: 0;
}
}
}
}

+ 59
- 0
react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx View File

@@ -0,0 +1,59 @@
import { downLoadZip } from '@/utils/downloadfile';
import { Button } from 'antd';
import styles from './index.less';
type ExperimentResultProps = {
results?: ExperimentResultData[] | null;
};

type ExperimentResultData = {
name: string;
path: string;
type: string;
value: {
name: string;
size: string;
}[];
};

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

return (
<div className={styles['experiment-result']}>
<div className={styles['experiment-result__content']}>
{results?.map((item) => (
<div key={item.name} className={styles['experiment-result__item']}>
<div className={styles['experiment-result__item__name']}>
<span>{item.name}</span>
<Button
size="small"
type="link"
onClick={() => {
exportResult(item.path);
}}
>
下载
</Button>
{/* <a style={{ marginRight: '10px' }}>导出到模型库</a>
<a style={{ marginRight: '10px' }}>导出到数据集</a> */}
</div>
<div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}>
<span>文件名称</span>
<span>文件大小</span>
</div>
{item.value?.map((ele) => (
<div className={styles['experiment-result__item__file']} key={ele.name}>
<span>{ele.name}</span>
<span>{ele.size}</span>
</div>
))}
</div>
))}
</div>
</div>
);
}

export default ExperimentResult;

+ 34
- 0
react-ui/src/pages/Experiment/components/LogGroup/index.less View File

@@ -0,0 +1,34 @@
.log-group {
padding-bottom: 10px;

&__pod {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
background: rgba(234, 234, 234, 0.5);
cursor: pointer;

&__name {
margin-right: 10px;
color: @text-color;
font-size: 14px;
}
}

&__detail {
padding: 15px;
color: white;
font-size: 14px;
white-space: pre-line;
word-break: break-all;
background: #19253b;
}

&__more-button {
display: flex;
justify-content: center;
color: white;
background: #19253b;
}
}

react-ui/src/pages/Experiment/experimentText/logGroup.tsx → react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -1,16 +1,19 @@
/*
* @Author: 赵伟
* @Date: 2024-05-16 08:47:46
* @Description: 日志组件
*/

import { useStateRef } from '@/hooks';
import { ExperimentLog } from '@/pages/Experiment/experimentText/props';
import { ExperimentStatus } from '@/pages/Experiment/status';
import { getExperimentPodsLog } from '@/services/experiment/index.js';
import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { useEffect, useState } from 'react';
import { ExperimentStatus } from '../status';
import styles from './logGroup.less';
import styles from './index.less';

export type LogGroupProps = {
log_type: 'normal' | 'resource'; // 日志类型
pod_name?: string; // 分布式名称
log_content?: string; // 日志内容
start_time?: string; // 日志开始时间
export type LogGroupProps = ExperimentLog & {
status: ExperimentStatus; // 实验状态
};

@@ -23,7 +26,7 @@ function LogGroup({
log_type = 'normal',
pod_name = '',
log_content = '',
start_time = '',
start_time,
status = ExperimentStatus.Pending,
}: LogGroupProps) {
const [collapse, setCollapse] = useState(true);
@@ -57,21 +60,6 @@ function LogGroup({
setCompleted(true);
}
};
// 请求实时日志
// const requestExperimentPodsRealtimeLog = async () => {
// const params = {
// pod_name,
// namespace: namespace,
// container_name: log_type === 'resource' ? '' : 'main',
// };
// const res = await getExperimentPodsRealtimeLog(params);
// const { log_detail } = res.data;
// if (log_detail && log_detail.log_content) {
// setLogList((list) => list.concat(log_detail));
// } else {
// setCompleted(true);
// }
// };

// 处理折叠
const handleCollapse = async () => {
@@ -101,15 +89,15 @@ function LogGroup({
const logText = log_content + logList.map((v) => v.log_content).join('');
const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== '';
return (
<div className={styles.log_group}>
<div className={styles['log-group']}>
{log_type === 'resource' && (
<div className={styles.log_group_pod} onClick={handleCollapse}>
<div className={styles.log_group_pod_name}>{pod_name}</div>
<div className={styles['log-group__pod']} onClick={handleCollapse}>
<div className={styles['log-group__pod__name']}>{pod_name}</div>
{collapse ? <DownOutlined /> : <UpOutlined />}
</div>
)}
{showLog && <div className={styles.log_group_detail}>{logText}</div>}
<div className={styles.log_group_more_button}>
{showLog && <div className={styles['log-group__detail']}>{logText}</div>}
<div className={styles['log-group__more-button']}>
{showMoreBtn && (
<Button
type="text"

+ 3
- 0
react-ui/src/pages/Experiment/components/LogList/index.less View File

@@ -0,0 +1,3 @@
.log-list {
padding: 8px;
}

+ 21
- 0
react-ui/src/pages/Experiment/components/LogList/index.tsx View File

@@ -0,0 +1,21 @@
import { ExperimentLog } from '@/pages/Experiment/experimentText/props';
import { ExperimentStatus } from '@/pages/Experiment/status';
import LogGroup from '../LogGroup';
import styles from './index.less';

type LogListProps = {
list: ExperimentLog[];
status: ExperimentStatus;
};

function LogList({ list = [], status }: LogListProps) {
return (
<div className={styles['log-list']}>
{list.map((v) => (
<LogGroup key={v.pod_name} {...v} status={status} />
))}
</div>
);
}

export default LogList;

react-ui/src/pages/Experiment/experimentText/paramsModal.less → react-ui/src/pages/Experiment/components/ViewParamsModal/index.less View File


react-ui/src/pages/Experiment/experimentText/paramsModal.tsx → react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx View File

@@ -6,8 +6,8 @@
import parameterImg from '@/assets/img/modal-parameter.png';
import KFModal from '@/components/KFModal';
import { type PipelineGlobalParam } from '@/types';
import { getParamType } from './addExperimentModal';
import styles from './paramsModal.less';
import { getParamType } from '../AddExperimentModal';
import styles from './index.less';

type ParamsModalProps = {
open: boolean;
@@ -24,6 +24,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
onOk={onCancel}
onCancel={onCancel}
cancelButtonProps={{ style: { display: 'none' } }}
width={825}
>
<div className={styles.params_container}>
{globalParam?.map((item) => (

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

@@ -1,19 +0,0 @@
import { ExperimentStatus } from '../status';
import LogGroup, { type LogGroupProps } from './logGroup';

type LogListProps = {
list: Omit<LogGroupProps, 'status'>[];
status: ExperimentStatus;
};

function LogList({ list = [], status }: LogListProps) {
return (
<div>
{list.map((v) => (
<LogGroup key={v.pod_name} {...v} status={status} />
))}
</div>
);
}

export default LogList;

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

@@ -1,22 +0,0 @@
.modal {
:global {
// .ant-input {
// height: 30px;
// border-color: #e6e6e6;
// }
// .ant-select-single {
// height: 40px;
// }
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
}

.global_param_item {
max-height: 230px;
padding: 24px 12px 0;
overflow-y: auto;
border: 1px solid #e6e6e6;
border-radius: 6px;
}
}

+ 58
- 125
react-ui/src/pages/Experiment/experimentText/index.jsx View File

@@ -1,51 +1,26 @@
import { useVisible } from '@/hooks';
import { useStateRef, useVisible } from '@/hooks';
import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import { elapsedTime } from '@/utils/date';
import { useEmotionCss } from '@ant-design/use-emotion-css';
import { elapsedTime, formatDate } from '@/utils/date';
import G6 from '@antv/g6';
import { Button } from 'antd';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { s8 } from '../../../utils';
import ParamsModal from '../components/ViewParamsModal';
import { experimentStatusInfo } from '../status';
import styles from './index.less';
import ParamsModal from './paramsModal';
import Props from './props';

let graph = null;

function ExperimentText() {
const [message, setMessage] = useState({});
const messageRef = useRef(message);
const [message, setMessage, messageRef] = useStateRef({});
const propsRef = useRef();
const navgite = useNavigate();
const locationParams = useParams(); //新版本获取路由参数接口
let graph = null;
const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);

const timers = (time) => {
let timer = new Date(time);
let hours = timer.getHours(); //转换成时
let minutes = timer.getMinutes(); //转换成分
let secend = timer.getSeconds(); //转换成秒

let str = `${minutes}分${secend}秒`;
return str;
};
const pipelineContainer = useEmotionCss(() => {
return {
display: 'flex',
backgroundColor: '#fff',
height: '98vh',
};
});
const graphStyle = useEmotionCss(() => {
return {
width: '100%',
backgroundColor: '#f9fafb',
flex: 1,
};
});
const graphRef = useRef();
const onDragEnd = (val) => {
console.log(val, 'eee');
@@ -61,12 +36,8 @@ function ExperimentText() {
id: val.component_name + '-' + s8(),
isCluster: false,
};
console.log(graph, model);

graph.addItem('node', model, true);
console.log(graph);
};
const formChange = (val) => {};
const handlerClick = (e) => {
if (e.target.get('name') !== 'anchor-point' && e.item) {
propsRef.current.showDrawer(e, locationParams.id, messageRef.current);
@@ -74,7 +45,6 @@ function ExperimentText() {
};
const getGraphData = (data) => {
if (graph) {
console.log(graph);
graph.data(data);
graph.render();
} else {
@@ -84,77 +54,39 @@ function ExperimentText() {
}
};
const getFirstWorkflow = (val) => {
getWorkflowById(val).then((ret) => {
if (graph && ret.data && ret.data.dag) {
console.log(JSON.parse(ret.data.dag));
getExperimentIns(locationParams.id).then((res) => {
if (res.code == 200) {
console.log(ret.data, 'data');
setMessage(res.data);
const experimentStatusObjs = JSON.parse(res.data.nodes_status);
const newNodeList = JSON.parse(ret.data.dag).nodes.map((item) => {
console.log(experimentStatusObjs);
getWorkflowById(val).then((pipelineRes) => {
if (graph && pipelineRes.data && pipelineRes.data.dag) {
getExperimentIns(locationParams.id).then((experimentRes) => {
if (experimentRes.code === 200) {
setMessage(experimentRes.data);
const experimentStatusObjs = JSON.parse(experimentRes.data.nodes_status);
const newNodeList = JSON.parse(pipelineRes.data.dag).nodes.map((item) => {
return {
...item,
experimentEndTime:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].finishedAt,
experimentStartTime:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].startedAt,
experimentStatus:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].phase,
component_id:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].id,
img:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].phase
? item.img.slice(0, item.img.length - 4) +
'-' +
experimentStatusObjs[item.id].phase +
'.png'
: item.img,
experimentEndTime: experimentStatusObjs?.[item.id]?.finishedAt,
experimentStartTime: experimentStatusObjs?.[item.id]?.startedAt,
experimentStatus: experimentStatusObjs?.[item.id]?.phase,
component_id: experimentStatusObjs?.[item.id]?.id,
img: experimentStatusObjs?.[item.id]?.phase
? item.img.slice(0, item.img.length - 4) +
'-' +
experimentStatusObjs[item.id].phase +
'.png'
: item.img,
};
});
const newData = { ...JSON.parse(ret.data.dag), nodes: newNodeList };

const newData = { ...JSON.parse(pipelineRes.data.dag), nodes: newNodeList };
getGraphData(newData);
// setExperimentStatusObj(JSON.parse(ret.data.nodes_status))
}
});
}
// graph&&graph.data(JSON.parse(ret.dag))
// graph.render()
});
};
// const getExperimentIn=(val)=>{
// getExperimentIns(val).then(ret=>{
// if(ret.code==200){
// console.log(JSON.parse(ret.data.nodes_status));
// setExperimentStatusObj(JSON.parse(ret.data.nodes_status))
// setTimeout(() => {
// console.log(experimentStatusObj);

// }, 1000);
// }

// })
// }
useEffect(() => {
initGraph();
getFirstWorkflow(locationParams.workflowId);
}, []);
useEffect(() => {
// Update the refs whenever the state changes
messageRef.current = message;
}, [message]);

const initGraph = () => {
const fittingString = (str, maxWidth, fontSize) => {
@@ -375,6 +307,8 @@ function ExperimentText() {
},
// linkCenter: true,
fitView: true,
minZoom: 0.5,
maxZoom: 3,
fitViewPadding: [320, 320, 220, 320],
});
graph.on('node:click', handlerClick);
@@ -386,40 +320,39 @@ function ExperimentText() {
};
};
return (
<div className={pipelineContainer}>
<div className={styles.centerContainer}>
<div className={styles.buttonList}>
<div className={styles.allMessageItem}>
启动时间:{momnet(message.create_time).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div className={styles.allMessageItem}>
执行时长:
{message.finish_time
? elapsedTime(new Date(message.create_time), new Date(message.finish_time))
: elapsedTime(new Date(message.create_time), new Date())}
</div>
<div className={styles.allMessageItem}>
状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: experimentStatusInfo[message.status]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[message.status]?.color }}>
{experimentStatusInfo[message.status]?.label}
</span>
</div>
<Button className={styles.param_button} onClick={openParamsModal}>
执行参数
</Button>
<div className={styles['pipeline-container']}>
<div className={styles['pipeline-container__top']}>
<div className={styles['pipeline-container__top__info']}>
启动时间:{formatDate(message.create_time)}
</div>
<div className={styles['pipeline-container__top__info']}>
执行时长:
{elapsedTime(message.create_time, message.finish_time)}
</div>
<div className={styles['pipeline-container__top__info']}>
状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: experimentStatusInfo[message.status]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[message.status]?.color }}>
{experimentStatusInfo[message.status]?.label}
</span>
</div>
<div className={graphStyle} ref={graphRef} id={styles.graphStyle}></div>
<Button
className={styles['pipeline-container__top__param-button']}
onClick={openParamsModal}
>
执行参数
</Button>
</div>
<Props ref={propsRef} onParentChange={formChange}></Props>
<div className={styles['pipeline-container__graph']} ref={graphRef}></div>
<Props ref={propsRef}></Props>
<ParamsModal
open={paramsModalOpen}
onCancel={closeParamsModal}


+ 26
- 85
react-ui/src/pages/Experiment/experimentText/index.less View File

@@ -1,90 +1,31 @@
#graph {
position: relative;
width: 100%;
.pipeline-container {
height: 100%;
}
.editPipelinePropsContent {
display: flex;
align-items: center;
width: 100%;
height: 43px;
margin-bottom: 20px;
padding: 0 24px;
color: #1d1d20;
font-size: 15px;
font-family: 'Alibaba';
background: #f8fbff;
}
.centerContainer {
display: flex;
flex: 1;
flex-direction: column;
}
.buttonList {
display: flex;
align-items: center;
width: 100%;
height: 56px;
padding: 0 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
}
.drawBox{
:global{
.ant-drawer .ant-drawer-body{
overflow: hidden;
}
}
}
.experimentDrawer{
:global{
.ant-tabs >.ant-tabs-nav .ant-tabs-nav-list{
margin-left: 24px;
}
.ant-drawer .ant-drawer-body{
overflow-y: hidden;
background-color: #fff;

&__top {
display: flex;
align-items: center;
width: 100%;
height: 56px;
padding: 0 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__info {
display: flex;
align-items: center;
margin-right: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.ant-tabs {
height: calc(100% - 160px);
overflow-y: auto;
&__param-button {
margin-right: 0;
margin-left: auto;
}
}

}
.detailBox {
display: flex;
align-items: center;
margin-bottom: 15px;
color: #1d1d20;
font-size: 15px;
padding-left: 24px;

}
.allMessageItem {
display: flex;
align-items: center;
margin-right: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.param_button {
margin-right: 0;
margin-left: auto;
}
.resultTop {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.resultContent {
display: flex;

align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
padding: 0 20px 0 0;
&__graph {
width: 100%;
height: calc(100% - 56px);
background-color: @background-color;
}
}

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

@@ -1,34 +0,0 @@
.log_group {
padding-bottom: 10px;
}

.log_group_pod {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
background: rgba(234, 234, 234, 0.5);
cursor: pointer;

&_name {
margin-right: 10px;
color: #1d1d20;
font-size: 14px;
}
}

.log_group_detail {
padding: 15px;
color: white;
font-size: 14px;
white-space: pre-line;
word-break: break-all;
background: #19253b;
}

.log_group_more_button {
display: flex;
justify-content: center;
color: white;
background: #19253b;
}

+ 0
- 439
react-ui/src/pages/Experiment/experimentText/props.jsx View File

@@ -1,439 +0,0 @@
import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js';
import { elapsedTime } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile';
import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Form, Input, Tabs, message } from 'antd';
import moment from 'moment';
import { forwardRef, useImperativeHandle, useState } from 'react';
import LogList from './LogList';
import Styles from './index.less';
const { TextArea } = Input;
const Props = forwardRef(({ onParentChange }, ref) => {
const [form] = Form.useForm();
const [stagingItem, setStagingItem] = useState({});
const [resultObj, setResultObj] = useState([]);
const [logList, setLogList] = useState([]);
const statusObj = {
Running: '运行中',
Succeeded: '成功',
Pending: '等待中',
Failed: '失败',
Error: '错误',
Terminated: '终止',
Skipped: '未执行',
Omitted: '未执行',
};
const statusColorObj = {
Running: '#165bff',
Succeeded: '#63a728',
Pending: '#f981eb',
Failed: '#c73131',
Error: '#c73131',
Terminated: '#8a8a8a',
Skipped: '#8a8a8a',
Omitted: '#8a8a8ae',
};
const exportResult = (e, val) => {
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/minioStorage/download`, { path: val });
};
const timers = (time) => {
let timer = new Date(time);
let hours = timer.getHours(); //转换成时
let minutes = timer.getMinutes(); //转换成分
let secend = timer.getSeconds(); //转换成秒

let str = `${minutes}分${secend}秒`;
return str;
};
const items = [
{
key: '1',
label: '日志详情',
children: <LogList list={logList} status={stagingItem.experimentStatus}></LogList>,
icon: <ProfileOutlined />,
},
{
key: '2',
label: '配置参数',
icon: <DatabaseOutlined />,
children: (
<Form
name="form"
form={form}
layout="vertical"
labelCol={{
span: 16,
}}
wrapperCol={{
span: 24,
}}
style={{
maxWidth: 600,
}}
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/static-message.png'}
alt=""
/>
基本信息
</div>
<Form.Item
label="任务名称"
name="label"
rules={[
{
required: true,
message: '请输入任务名称',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
label="任务ID"
name="id"
rules={[
{
required: true,
message: '请输入任务id',
},
]}
>
<Input disabled />
</Form.Item>
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
任务信息
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<Input disabled />
</Form.Item>

<Form.Item label="启动命令" name="command">
<TextArea disabled />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<Input disabled />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<TextArea disabled />
</Form.Item>
{stagingItem.control_strategy &&
Object.keys(stagingItem.control_strategy) &&
Object.keys(stagingItem.control_strategy).length > 0
? Object.keys(stagingItem.control_strategy).map((item) => (
<Form.Item
key={item}
label={stagingItem.control_strategy[item].label}
disabled
name={item}
>
<Input disabled />
</Form.Item>
))
: ''}
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '13px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
输入参数
</div>
{stagingItem.in_parameters &&
Object.keys(stagingItem.in_parameters) &&
Object.keys(stagingItem.in_parameters).length > 0
? Object.keys(stagingItem.in_parameters).map((item) => (
<Form.Item
key={item}
label={stagingItem.in_parameters[item].label + '(' + item + ')'}
name={item}
disabled
rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]}
>
<Input disabled />
</Form.Item>
))
: ''}
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
输出参数
</div>
{stagingItem.out_parameters &&
Object.keys(stagingItem.out_parameters) &&
Object.keys(stagingItem.out_parameters).length > 0
? Object.keys(stagingItem.out_parameters).map((item) => (
<Form.Item
key={item}
label={stagingItem.out_parameters[item].label + '(' + item + ')'}
disabled
rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]}
name={item}
>
<Input disabled />
</Form.Item>
))
: ''}
</Form>
),
},
{
key: '3',
label: '输出结果',
children: (
<div
style={{
minHeight: '740px',
background: '#f4f4f4',
color: '#000',
fontSize: '14px',
padding: '0 10px 20px 20px',
}}
>
{resultObj && resultObj.length > 0
? resultObj.map((item) => (
<div key={item.name}>
<div className={Styles.resultTop}>
<span>{item.name}</span>
<div style={{ display: 'flex' }}>
<a
onClick={(e) => {
exportResult(e, item.path);
}}
style={{ marginRight: '10px' }}
>
下载
</a>
<a style={{ marginRight: '10px' }}>导出到模型库</a>
<a style={{ marginRight: '10px' }}>导出到数据集</a>
</div>
</div>
<div style={{ margin: '15px 0' }} className={Styles.resultContent}>
<span>文件名称</span>
<span>文件大小</span>
</div>
{item.value && item.value.length > 0
? item.value.map((ele) => (
<div className={Styles.resultContent} key={ele.name}>
<span>{ele.name}</span>
<span>{ele.size}</span>
</div>
))
: null}
</div>
))
: null}
</div>
),
icon: <ProfileOutlined />,
},
];
const [open, setOpen] = useState(false);
const afterOpenChange = () => {
if (!open) {
console.log(111, open);

console.log(stagingItem, form.getFieldsValue());
for (let i in form.getFieldsValue()) {
for (let j in stagingItem.in_parameters) {
if (i == j) {
console.log(j, i);
stagingItem.in_parameters[j].value = form.getFieldsValue()[i];
}
}
for (let p in stagingItem.out_parameters) {
if (i == p) {
stagingItem.out_parameters[p].value = form.getFieldsValue()[i];
}
}
for (let k in stagingItem.control_strategy) {
if (i == k) {
stagingItem.control_strategy[k].value = form.getFieldsValue()[i];
}
}
}
// setStagingItem({...stagingItem,})
console.log(stagingItem.control_strategy);
onParentChange({
...stagingItem,
control_strategy: JSON.stringify(stagingItem.control_strategy),
in_parameters: JSON.stringify(stagingItem.in_parameters),
out_parameters: JSON.stringify(stagingItem.out_parameters),
...form.getFieldsValue(),
});
// onParentChange({...stagingItem,...form.getFieldsValue()})
}
};
const onClose = () => {
setOpen(false);
};
const onFinish = (values) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useImperativeHandle(ref, () => ({
showDrawer(e, id, message) {
setLogList([]);
if (e.item && e.item.getModel().component_id) {
const model = e.item.getModel() || {};
const start_time = moment(model.experimentStartTime).valueOf() * 1.0e6;
const params = {
task_id: model.id,
component_id: model.component_id,
name: message.argo_ins_name,
namespace: message.argo_ins_ns,
start_time: start_time,
};
getQueryByExperimentLog(params).then((ret) => {
const { log_type, pods, log_detail } = ret.data;
if (log_type === 'normal') {
const list = [
{
...log_detail,
log_type,
},
];
setLogList(list);
} else if (log_type === 'resource') {
const list = pods.map((v) => ({
log_type,
pod_name: v,
log_content: '',
start_time,
}));
setLogList(list);
}

getNodeResult({ id, node_id: e.item.getModel().id }).then((res) => {
setResultObj(res.data);
form.resetFields();
form.setFieldsValue({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setStagingItem({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setOpen(true);
});
});
} else {
form.resetFields();
form.setFieldsValue({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setStagingItem({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setOpen(true);
}
// console.log(e.item.getModel().in_parameters);
},
}));
return (
<div className={Styles.drawBox}>
<Drawer
title="任务执行详情"
placement="right"
rootStyle={{ marginTop: '68px' }}
getContainer={false}
closeIcon={false}
onClose={onClose}
afterOpenChange={afterOpenChange}
open={open}
width={420}
className={Styles.experimentDrawer}
destroyOnClose={true}
>
<div className={Styles.detailBox} style={{ marginTop: '15px' }}>
任务名称:{stagingItem.label}
</div>
<div className={Styles.detailBox}>
执行状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: statusColorObj[stagingItem.experimentStatus],
}}
></div>
<span style={{ color: statusColorObj[stagingItem.experimentStatus] }}>
{statusObj[stagingItem.experimentStatus]}
</span>
</div>
<div className={Styles.detailBox}>
启动时间:{moment(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div className={Styles.detailBox}>
耗时:
{stagingItem.experimentEndTime
? elapsedTime(
new Date(stagingItem.experimentStartTime),
new Date(stagingItem.experimentEndTime),
)
: elapsedTime(new Date(stagingItem.experimentStartTime), new Date())}
</div>
<Tabs defaultActiveKey="1" items={items} />
</Drawer>
</div>
);
});

export default Props;

+ 37
- 0
react-ui/src/pages/Experiment/experimentText/props.less View File

@@ -0,0 +1,37 @@
.experiment-drawer {
:global {
.ant-drawer-body {
overflow-y: hidden;
}
}

&__tabs {
height: calc(100% - 170px);
:global {
.ant-tabs-nav {
padding-left: 24px;
background-color: #f8fbff;
border: 1px solid #e0eaff;
}
.ant-tabs-content-holder {
overflow-y: auto;
}
}
}

&__info {
display: flex;
align-items: center;
margin-bottom: 15px;
padding-left: 24px;
color: @text-color;
font-size: 15px;
}

&__status-dot {
width: 8px;
height: 8px;
margin-right: 6px;
border-radius: 50%;
}
}

+ 171
- 0
react-ui/src/pages/Experiment/experimentText/props.tsx View File

@@ -0,0 +1,171 @@
import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js';
import { PipelineNodeModelSerialize } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Form, Tabs } from 'antd';
import dayjs from 'dayjs';
import { forwardRef, useImperativeHandle, useState } from 'react';
import ExperimentParameter from '../components/ExperimentParameter';
import ExperimentResult from '../components/ExperimentResult';
import LogList from '../components/LogList';
import { experimentStatusInfo } from '../status';
import styles from './props.less';

export type ExperimentLog = {
log_type: 'normal' | 'resource'; // 日志类型
pod_name?: string; // 分布式名称
log_content?: string; // 日志内容
start_time?: string; // 日志开始时间
};

const Props = forwardRef((_, ref) => {
const [form] = Form.useForm();
const [experimentNodeData, setExperimentNodeData] = useState<PipelineNodeModelSerialize>(
{} as PipelineNodeModelSerialize,
);
const [experimentResults, setExperimentResults] = useState([]);
const [experimentLogList, setExperimentLogList] = useState<ExperimentLog[]>([]);

const items = [
{
key: '1',
label: '日志详情',
children: (
<LogList list={experimentLogList} status={experimentNodeData.experimentStatus}></LogList>
),
icon: <ProfileOutlined />,
},
{
key: '2',
label: '配置参数',
icon: <DatabaseOutlined />,
children: <ExperimentParameter form={form} nodeData={experimentNodeData} />,
},
{
key: '3',
label: '输出结果',
children: <ExperimentResult results={experimentResults}></ExperimentResult>,
icon: <ProfileOutlined />,
},
];
const [open, setOpen] = useState(false);
const onClose = () => {
setOpen(false);
};

// 获取实验日志
const getExperimentLog = async (params: any, start_time: number) => {
const [res] = await to(getQueryByExperimentLog(params));
if (res && res.data) {
const { log_type, pods, log_detail } = res.data;
if (log_type === 'normal') {
const list = [
{
...log_detail,
log_type,
},
];
setExperimentLogList(list);
} else if (log_type === 'resource') {
const list = pods.map((v: string) => ({
log_type,
pod_name: v,
log_content: '',
start_time,
}));
setExperimentLogList(list);
}
}
};

// 获取实验结果
const getExperimentResult = async (params: any) => {
const [res] = await to(getNodeResult(params));
if (res && res.data) {
setExperimentResults(res.data);
}
};

useImperativeHandle(ref, () => ({
showDrawer(e: any, id: string, message: any) {
setOpen(true);

// 获取实验参数
const model = e.item.getModel();
try {
const nodeData = {
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
};
setExperimentNodeData(nodeData);
form.setFieldsValue(nodeData);
} catch (error) {
console.log(error);
}

// 获取实验日志和实验结果
setExperimentLogList([]);
setExperimentResults([]);
if (e.item && e.item.getModel()) {
const model = e.item.getModel();
const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6;
const params = {
task_id: model.id,
component_id: model.component_id,
name: message.argo_ins_name,
namespace: message.argo_ins_ns,
start_time: start_time,
};
getExperimentLog(params, start_time);
getExperimentResult({ id, node_id: model.id });
}
},
}));
return (
<Drawer
title="任务执行详情"
placement="right"
getContainer={false}
closeIcon={false}
onClose={onClose}
open={open}
width={520}
className={styles['experiment-drawer']}
destroyOnClose={true}
>
<div style={{ paddingTop: '15px' }}>
<div className={styles['experiment-drawer__info']}>
任务名称:{experimentNodeData.label}
</div>
<div className={styles['experiment-drawer__info']}>
执行状态:
<div
className={styles['experiment-drawer__status-dot']}
style={{
backgroundColor: experimentStatusInfo[experimentNodeData.experimentStatus]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[experimentNodeData.experimentStatus]?.color }}>
{experimentStatusInfo[experimentNodeData.experimentStatus]?.label}
</span>
</div>
<div className={styles['experiment-drawer__info']}>
启动时间:{formatDate(experimentNodeData.experimentStartTime)}
</div>
<div className={styles['experiment-drawer__info']}>
耗时:
{elapsedTime(
experimentNodeData.experimentStartTime,
experimentNodeData.experimentEndTime,
)}
</div>
</div>
<Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} />
</Drawer>
);
});

export default Props;

+ 4
- 9
react-ui/src/pages/Experiment/index.jsx View File

@@ -14,16 +14,15 @@ import {
} from '@/services/experiment/index.js';
import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { elapsedTime } from '@/utils/date';
import { elapsedTime, formatDate } 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';
import AddExperimentModal from './components/AddExperimentModal';
import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus';
import AddExperimentModal from './experimentText/addExperimentModal';
import Styles from './index.less';
import { experimentStatusInfo } from './status';

@@ -438,13 +437,9 @@ function Experiment() {
</div>
<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')}
{elapsedTime(item.create_time, item.finish_time)}
</div>
<div style={{ width: '50%' }}>{formatDate(item.create_time)}</div>
</div>
<div className={Styles.statusBox}>
<img


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

@@ -9,9 +9,9 @@
background-color: white;
border-radius: 10px;

&__title {
display: flex;
align-items: center;
&__type {
color: @text-color;
font-size: @font-size-input-lg;
}
}
}

+ 14
- 26
react-ui/src/pages/Mirror/create.tsx View File

@@ -12,7 +12,7 @@ import { CommonTabKeys } from '@/enums';
import { createMirrorReq } from '@/services/mirror';
import { to } from '@/utils/promise';
import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage';
import { getFileListFromEvent } from '@/utils/ui';
import { getFileListFromEvent, validateUploadFiles } 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';
@@ -75,30 +75,16 @@ function MirrorCreate() {
};
} else {
const fileList = formData['fileList'] ?? [];
if (fileList.length === 0) {
message.error('请上传文件');
return;
if (validateUploadFiles(fileList)) {
const file = fileList[0];
params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
upload_type: 1,
image_type: 0,
};
}
const file = fileList[0];
if (file.status === 'uploading') {
message.error('请等待文件上传完成');
return;
} else if (file.status === 'error') {
message.error('文件上传失败,请重新上传文件');
return;
}
if (!file.response || !file.response.data) {
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));
@@ -118,6 +104,7 @@ function MirrorCreate() {
navgite(-1);
};

// 上传前认证
const beforeUpload: UploadProps['beforeUpload'] = () => {
const fileList = form.getFieldValue('fileList');
if (Array.isArray(fileList) && fileList.length >= 1) {
@@ -134,12 +121,13 @@ function MirrorCreate() {
<div>
<Form
name="mirror-create"
labelCol={{ flex: '120px' }}
labelCol={{ flex: '130px' }}
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit}
size="large"
>
<SubAreaTitle
title="基本信息"
@@ -242,7 +230,7 @@ function MirrorCreate() {
<Row gutter={10}>
<Col span={10}>
<Form.Item label="仓库类型" required>
<span>公网</span>
<span className={styles['mirror-create__content__type']}>公网</span>
</Form.Item>
</Col>
</Row>


+ 5
- 6
react-ui/src/pages/Mirror/info.tsx View File

@@ -16,6 +16,7 @@ import {
getMirrorVersionListReq,
} from '@/services/mirror';
import themes from '@/styles/theme.less';
import { formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui';
@@ -32,7 +33,6 @@ import {
type TableProps,
} from 'antd';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import MirrorStatusCell from './components/MirrorStatusCell';
import styles from './info.less';
@@ -83,8 +83,7 @@ function MirrorInfo() {
const [res] = await to(getMirrorInfoReq(id));
if (res && res.data) {
const { name = '', description = '', version_count = '', create_time: time } = res.data;
let create_time =
time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--';
const create_time = formatDate(time);
setMirrorInfo({
name,
description,
@@ -161,13 +160,13 @@ function MirrorInfo() {
dataIndex: 'tag_name',
key: 'tag_name',
width: '25%',
render: CommonTableCell,
render: CommonTableCell(),
},
{
title: '镜像地址',
dataIndex: 'url',
key: 'url',
render: CommonTableCell,
render: CommonTableCell(),
},
{
title: '状态',
@@ -181,7 +180,7 @@ function MirrorInfo() {
dataIndex: 'file_size',
key: 'file_size',
width: 150,
render: CommonTableCell,
render: CommonTableCell(),
},
{
title: '创建时间',


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

@@ -3,7 +3,7 @@
&__tabs-container {
height: 50px;
padding-left: 27px;
background-image: url('../../assets/img/page-title-bg.png');
background-image: url(@/assets/img/page-title-bg.png);
}

&__content {
@@ -20,7 +20,7 @@
}

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


+ 15
- 21
react-ui/src/pages/Mirror/list.tsx View File

@@ -54,6 +54,7 @@ function MirrorList() {
const [cacheState, setCacheState] = useCacheState();
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState<MirrorData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>(
@@ -65,11 +66,12 @@ function MirrorList() {

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

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
setSearchText('');
setInputText('');
setPagination({
current: 1,
pageSize: 10,
@@ -78,16 +80,16 @@ function MirrorList() {
setTableData([]);
setActiveTab(value);
};

// 获取镜像列表
const getMirrorList = async (params?: Record<string, any>) => {
const reqParams = {
const getMirrorList = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
name: searchText,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
...params,
};
const [res] = await to(getMirrorListReq(reqParams));
const [res] = await to(getMirrorListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
@@ -116,10 +118,7 @@ function MirrorList() {

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

// 查看详情
@@ -167,21 +166,21 @@ function MirrorList() {
dataIndex: 'name',
key: 'name',
width: '30%',
render: CommonTableCell,
render: CommonTableCell(),
},
{
title: '版本数据',
dataIndex: 'version_count',
key: 'version_count',
width: 100,
render: CommonTableCell,
render: CommonTableCell(),
},
{
title: '镜像描述',
dataIndex: 'description',
key: 'description',
render: CommonTableCell,
ellipsis: true,
render: CommonTableCell(true),
ellipsis: { showTitle: false },
},
{
title: '创建时间',
@@ -233,12 +232,7 @@ function MirrorList() {
return (
<div className={styles['mirror-list']}>
<div className={styles['mirror-list__tabs-container']}>
<Tabs
activeKey={activeTab}
items={mirrorTabItems}
onChange={hanleTabChange}
className={styles['model-tabs']}
/>
<Tabs activeKey={activeTab} items={mirrorTabItems} onChange={hanleTabChange} />
</div>
<div className={styles['mirror-list__content']}>
<div className={styles['mirror-list__content__filter']}>
@@ -246,9 +240,9 @@ function MirrorList() {
placeholder="按数据集名称筛选"
allowClear
onSearch={onSearch}
onChange={(e) => setSearchText(e.target.value)}
onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }}
value={searchText}
value={inputText}
/>
{activeTab === CommonTabKeys.Private && (
<Button


+ 5
- 87
react-ui/src/pages/Model/index.jsx View File

@@ -1,89 +1,7 @@
import { Form, Input, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
import PersonalData from './personalData';
import PublicData from './publicData';
// import {getModelList} from '@/services/dataset/index.js'
const { Search } = Input;
const { TabPane } = Tabs;
const leftdataList = [1, 2, 3];
import ResourcePage from '@/pages/Dataset/components/ResourcePage';
import { ResourceType } from '@/pages/Dataset/type';

const Dataset = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
});
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
// const getModelLists=()=>{
// getModelList(queryFlow).then(ret=>{
// console.log(ret);
// if(ret.code==200){
// setDatasetList(ret.data.content)
// setTotal(ret.data.totalElements)
// }
// })
// }

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
navgite({ pathname: '/dataset/dataset' });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
return () => {};
}, []);
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetTopBox}></div>
<div className={Styles.datasetAllBox}>
<Tabs defaultActiveKey="1">
<TabPane
tab="模型广场"
key="1"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-shujujiguangchang"></use>
</svg>
}
>
<PublicData />
</TabPane>
<TabPane
tab="个人模型"
key="2"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-gerenshujuji"></use>
</svg>
}
>
<PersonalData />
</TabPane>
</Tabs>
</div>
</div>
);
const ModelPage = () => {
return <ResourcePage resourceType={ResourceType.Model} />;
};
export default Dataset;
export default ModelPage;

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

@@ -1,327 +0,0 @@
.datasetTopBox {
display: flex;
align-items: center;
width: 100%;
height: 49px;
padding: 0 30px;
padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.datasetIntroTopBox {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 25px 30px;
background-image: url(/assets/images/dataset-back.png);

background-size: 100% 100%;
.smallTagBox {
display: flex;
align-items: center;
color: #1664ff;
font-size: 14px;
.tagItem {
margin-right: 20px;
padding: 4px 10px;
background: rgba(22, 100, 255, 0.1);
border-radius: 4px;
}
}
}
.dataListBox {
padding: 20px 30px;
color: #1d1d20;
font-size: 16px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
.dataButtonList {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin: 24px 0 30px 0;
color: #575757;
font-size: 16px;
}
}
.datasetIntroCneterBox {
height: 77vh;
padding: 20px 30px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}
.datasetIntroTitle {
margin: 37px 0 10px 0;
color: #1d1d20;
font-size: 15px;
}
.datasetIntroText {
margin-bottom: 30px;
color: #575757;
font-size: 14px;
}
.datasetBox {
font-family: 'Alibaba';
background: #f9fafb;
:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
}
.ant-pagination {
text-align: right;
}
}
}
.datasetAllBox {
:global {
.ant-tabs-nav .ant-tabs-nav-wrap {
margin: -48px 0 0 30px;
}
}
}
.plusButton {
margin: 0 18px 0 20px;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
width: 100%;
height: 87.5vh;

.datasetCneterLeftBox {
width: 340px;
height: 100%;
margin-right: 10px;
padding-top: 15px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
height: 32px;
border-bottom: 1px solid #e0eaff;
.tabItem {
width: 52px;
height: 100%;
color: #808080;
font-size: 15px;
text-align: center;
cursor: pointer;
}
}
.leftContentBox {
max-height: 80vh;
padding: 15px 20px;
overflow-x: hidden;
overflow-y: auto;
.itemTitle {
margin-bottom: 15px;
color: #1d1d20;
font-size: 14px;
}
.itemBox {
display: flex;
flex-wrap: wrap;
align-content: start;
width: 110%;
.messageBox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 92px;
height: 62px;
margin: 0 12px 20px 0;
padding: 11px 0px 7px 0px;
color: #1d1d20;
font-size: 12px;
border: 1px solid;
border-color: rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;
.ptIcon {
display: block;
}
.hoverIcon {
display: none;
}
.messageText {
width: 65px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
transition: all 0.2s;
}
}
.messageBox:hover {
background: rgba(22, 100, 255, 0.03);
border: 1px solid;
border-color: #1664ff;
.ptIcon {
display: none;
}
.hoverIcon {
display: block;
}
}
.active {
background: rgba(22, 100, 255, 0.03) !important;
border: 1px solid !important;
border-color: #1664ff !important;
.ptIcon {
display: none !important;
}
.hoverIcon {
display: block !important;
}
}
}
}
}
.datasetCneterRightBox {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.dataSource {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.dataContent {
display: flex;
flex: 1;
flex-wrap: wrap;
align-content: flex-start;
width: 100%;
.dataItem {
position: relative;
width: 23.8%;
height:164px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText {
position: absolute;
top: 20px;
left: 20px;
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
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 .itemText {
color: #1664ff;
}
}
}
}
.tipContent{
color: #c73131;
margin-top: 5px;
}
.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
}

react-ui/src/pages/Model/modelIntro.jsx → react-ui/src/pages/Model/intro.jsx View File

@@ -1,61 +1,33 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import AddVersionModal from '@/pages/Dataset/components/AddVersionModal';
import { ResourceType } from '@/pages/Dataset/type';
import {
addModelsVersionDetail,
deleteModelVersion,
getModelById,
getModelVersionIdList,
getModelVersionsById,
} from '@/services/dataset/index.js';
import { formatDate } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
import { openAntdModal } from '@/utils/modal';
import { modalConfirm } from '@/utils/ui';
import { useParams, useSearchParams } from '@umijs/max';
import { Button, Input, Select, Table, Tabs, message } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import Styles from './index.less';
import Styles from './intro.less';
const { Search } = Input;
const { TabPane } = Tabs;

const Dataset = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
setFormList(
fileList.map((item) => {
return {
...form.getFieldsValue(),
models_id: locationParams.id,
file_name: item.response.code === 200 ? item.response.data[0].fileName : null,
file_size: item.response.code === 200 ? item.response.data[0].fileSize : null,
url: item.response.code === 200 ? item.response.data[0].url : null,
};
}),
);
}
},
defaultFileList: [],
};
const [form] = Form.useForm();
const [formList, setFormList] = useState([]);
const [dialogTitle, setDialogTitle] = useState('新建版本');
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetDetailObj, setDatasetDetailObj] = useState({});
const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口
console.log(locationParams);
const [searchParams] = useSearchParams();
const [wordList, setWordList] = useState([]);
const [uuid, setUuid] = useState(Date.now());
const isPublic = searchParams.get('isPublic') === 'true';

const getModelByDetail = () => {
getModelById(locationParams.id).then((ret) => {
console.log(ret);
@@ -76,6 +48,9 @@ const Dataset = () => {
);
setVersion(ret.data[0]);
getModelVersions({ version: ret.data[0], models_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
}
});
};
@@ -85,29 +60,21 @@ const Dataset = () => {
return () => {};
}, []);
const showModal = () => {
form.resetFields();
form.setFieldsValue({ name: datasetDetailObj.name });
setDialogTitle('创建新版本');
setUuid(Date.now());
setIsModalOpen(true);
};
const handleCancel = () => {
setIsModalOpen(false);
const { close } = openAntdModal(AddVersionModal, {
resourceType: ResourceType.Model,
resourceId: locationParams.id,
initialName: datasetDetailObj.name,
onOk: () => {
getModelVersionsList();
close();
},
});
};

const deleteDataset = () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该模型版本将不可恢复</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
modalConfirm({
title: '删除后,该版本将不可恢复',
content: '是否确认删除?',
okText: '确认',
cancelText: '取消',

@@ -119,13 +86,7 @@ const Dataset = () => {
},
});
};
const onFinish = () => {
addModelsVersionDetail(formList).then((ret) => {
getModelVersionsList();
setIsModalOpen(false);
message.success('创建成功');
});
};

const getModelVersions = (params) => {
getModelVersionIdList(params).then((ret) => {
setWordList(ret?.data?.content ?? []);
@@ -151,9 +112,7 @@ const Dataset = () => {
setVersion('');
}
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};

const columns = [
{
title: '序号',
@@ -185,7 +144,7 @@ const Dataset = () => {
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: (text) => <span>{formatDate(text)}</span>,
},
{
title: '操作',
@@ -262,15 +221,18 @@ const Dataset = () => {
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}

<Button
type="default"
className={Styles.plusButton}
@@ -293,104 +255,6 @@ const Dataset = () => {
</TabPane>
</Tabs>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称',
},
]}
>
<Input disabled placeholder="请输入模型名称" />
</Form.Item>
<Form.Item
label="模型版本"
name="version"
rules={[
{
required: true,
message: '请输入模型版本',
},
]}
>
<Input placeholder="请输入模型版本" maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={256}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label="模型文件"
name="dataset_version_vos"
rules={[
{
required: true,
message: '请上传模型文件',
},
]}
>
<Upload {...props} data={{ uuid: uuid }}>
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff' }} />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
</div>
);
};

+ 78
- 0
react-ui/src/pages/Model/intro.less View File

@@ -0,0 +1,78 @@
.datasetIntroTopBox {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 25px 30px;
background-image: url(/assets/images/dataset-back.png);

background-size: 100% 100%;
.smallTagBox {
display: flex;
align-items: center;
color: #1664ff;
font-size: 14px;
.tagItem {
margin-right: 20px;
padding: 4px 10px;
background: rgba(22, 100, 255, 0.1);
border-radius: 4px;
}
}
}
.dataListBox {
padding: 20px 30px;
color: #1d1d20;
font-size: 16px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
.dataButtonList {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin: 24px 0 30px 0;
color: #575757;
font-size: 16px;
}
}
.datasetIntroCneterBox {
height: 77vh;
padding: 20px 30px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}
.datasetIntroTitle {
margin: 37px 0 10px 0;
color: #1d1d20;
font-size: 15px;
}
.datasetIntroText {
margin-bottom: 30px;
color: #575757;
font-size: 14px;
}
.datasetBox {
font-family: 'Alibaba';
background: #f9fafb;
:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
}
.ant-pagination {
text-align: right;
}
}
}
.plusButton {
margin: 0 18px 0 20px;
}

.tipContent {
margin-top: 5px;
color: #c73131;
}

+ 0
- 525
react-ui/src/pages/Model/personalData.jsx View File

@@ -1,525 +0,0 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon';
import { addModel, deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Select, Upload, message } from 'antd';
import moment from 'moment';
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 = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
form.setFieldsValue({
models_version_vos: fileList.map((item) => {
const data = item.response.data[0];
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
});
}
},
defaultFileList: [],
};
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 0,
});
const navgite = useNavigate();
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [modelTypeList, setmodelTypeList] = useState([]);
const [modelDirectionList, setmodelDirectionList] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建模型');
const [uuid, setUuid] = useState(Date.now());
const getModelLists = (queryFlow) => {
getModelList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建模型');
setUuid(Date.now());
setIsModalOpen(true);
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setmodelTypeList(ret.data.content.filter((item) => item.category_id == 3));
setmodelDirectionList(ret.data.content.filter((item) => item.category_id == 4));
} else {
setmodelTypeList([]);
setmodelDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const nameSearch = (values) => {
console.log(values);
getModelLists({ ...queryFlow, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {
const params = {
...values,
available_range: 0,
};
addModel(values).then((ret) => {
console.log(ret);
getModelLists(queryFlow);
setIsModalOpen(false);
});
};

const chooseModelType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseModelTag = (val, item) => {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/model/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getModelLists(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>模型框架</div>
<div className={Styles.itemBox}>
{modelTypeList && modelTypeList.length > 0
? modelTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span
className={Styles.messageText}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
{item.name}
</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>模型能力</div>
<div className={Styles.itemBox}>
{modelDirectionList && modelDirectionList.length > 0
? modelDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按模型名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
模型注册
</Button>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
{/* <Dropdown
className={Styles.dropdown}
key={item.name}
menu={{
items: [
{
label: '详情',
key: 'detail',
},
{
label: '删除',
key: 'delete',
},
],
onClick: (e) => {
console.log(e);
if (e.key === 'detail') {
routeToIntro(e, item);
} else if (e.key === 'delete') {
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}
},
}}
>
<div>
<img
style={{ width: '17px', marginRight: '6px' }}
src={moreBack}
alt=""
/>
</div>
</Dropdown> */}
,<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称!',
},
]}
>
<Input placeholder="请输入模型名称" showCount maxLength={64} />
</Form.Item>

<Form.Item
label="模型版本"
name="version"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入模型版本" />
</Form.Item>

<Form.Item
label="模型描述"
name="description"
rules={[
{
required: true,
message: '请输入模型描述!',
},
]}
>
<Input placeholder="请输入模型描述" showCount maxLength={256} />
</Form.Item>
{/* <Form.Item label="可见范围" name="available_range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item> */}
<Form.Item
label="模型框架"
name="model_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择模型类型"
options={modelTypeList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item
label="模型能力"
name="model_tag"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择模型标签"
options={modelDirectionList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item label="模型文件" name="models_version_vos">
<Upload {...props} data={{ uuid: uuid }}>
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
</>
);
};
export default PublicData;

+ 0
- 384
react-ui/src/pages/Model/publicData.jsx View File

@@ -1,384 +0,0 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import { deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Modal, Pagination, Radio, message } from 'antd';
import moment from 'moment';
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 = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 1,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getModelLists = (queryFlow) => {
getModelList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const nameSearch = (values) => {
console.log(values);
getModelLists({ ...queryFlow, name: values });
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseModelType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseModelTag = (val, item) => {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/model/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getModelLists(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>模型框架</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>模型能力</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据名称"
name="name"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据名称" />
</Form.Item>
<Form.Item
label="数据集版本"
name="data_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据集版本" />
</Form.Item>
<Form.Item
label="数据描述"
name="description"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据描述" />
</Form.Item>
<Form.Item label="选择流水线" name="description1">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="数据标签"
name="description3"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据标签" />
</Form.Item>
</Form>
</Modal>
</>
);
};
export default PublicData;

+ 11
- 0
react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.less View File

@@ -0,0 +1,11 @@
.mirror-status-cell {
color: @text-color;

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

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

+ 39
- 0
react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.tsx View File

@@ -0,0 +1,39 @@
/*
* @Author: 赵伟
* @Date: 2024-04-18 18:35:41
* @Description:
*/
import { MirrorVersionStatus } from '@/enums';
import styles from './index.less';

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

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

const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = {
[MirrorVersionStatus.Building]: {
text: '构建中',
classname: styles['mirror-status-cell'],
},
[MirrorVersionStatus.Available]: {
classname: styles['mirror-status-cell--success'],
text: '可用',
},
[MirrorVersionStatus.Failed]: {
classname: styles['mirror-status-cell--error'],
text: '构建失败',
},
};

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

export default MirrorStatusCell;

+ 17
- 0
react-ui/src/pages/ModelDeployment/create.less View File

@@ -0,0 +1,17 @@
.model-deployment-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;
}
}
}

+ 297
- 0
react-ui/src/pages/ModelDeployment/create.tsx View File

@@ -0,0 +1,297 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 创建模型部署
*/
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { createMirrorReq } from '@/services/mirror';
import { getComputingResourceReq } from '@/services/pipeline';
import { to } from '@/utils/promise';
import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage';
import { validateUploadFiles } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { Button, Col, Form, Input, Row, Select, UploadFile, message, type SelectProps } from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import styles from './create.less';

type FormData = {
name: string;
tag: string;
description: string;
path?: string;
upload_type: string;
fileList?: UploadFile[];
};

function ModelDeploymentCreate() {
const navgite = useNavigate();
const [form] = Form.useForm();
const [nameDisabled, setNameDisabled] = useState(false);
const [resourceStandardList, setResourceStandardList] = useState([]);

useEffect(() => {
const name = getSessionItemThenRemove(mirrorNameKey);
if (name) {
form.setFieldValue('name', name);
setNameDisabled(true);
}
getComputingResource();
}, []);

const getComputingResource = async () => {
const params = {
page: 0,
size: 1000,
resource_type: '',
};
const [res] = await to(getComputingResourceReq(params));
if (res && res.data && res.data.content) {
setResourceStandardList(res.data.content);
}
};

const filterResourceStandard: SelectProps['filterOption'] = (
input: string,
{ computing_resource = '' },
) => {
return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase());
};

// 创建公网、本地镜像
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 (validateUploadFiles(fileList)) {
const file = fileList[0];
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);
}
};

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

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

return (
<div className={styles['model-deployment-create']}>
<PageTitle title="创建推理服务"></PageTitle>
<div className={styles['model-deployment-create__content']}>
<div>
<Form
name="model-deployment-create"
labelCol={{ flex: '130px' }}
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
initialValues={{ upload_type: CommonTabKeys.Public }}
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}
disabled={nameDisabled}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Col span={20}>
<Form.Item
label="描  述"
name="description"
rules={[
{
required: true,
message: '请输入描述',
},
]}
>
<Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }}
placeholder="请输入描述,最长128字符"
maxLength={128}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
<SubAreaTitle
title="部署构建"
image={require('@/assets/img/mirror-version.png')}
style={{ marginTop: '20px', marginBottom: '24px' }}
></SubAreaTitle>

<Row gutter={10}>
<Col span={10}>
<Form.Item
label="选择模型"
name="name"
rules={[
{
required: true,
message: '请输入模型',
},
]}
>
<Input
placeholder="请输入模型"
maxLength={64}
disabled={nameDisabled}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Col span={10}>
<Form.Item
label="选择镜像"
name="name"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<Input
placeholder="请输入镜像"
maxLength={64}
disabled={nameDisabled}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Col span={10}>
<Form.Item
label="资源规格"
name="name"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<Select
showSearch
placeholder="请选择资源规格"
filterOption={filterResourceStandard}
options={resourceStandardList}
fieldNames={{
label: 'description',
value: 'standard',
}}
/>
</Form.Item>
</Col>
</Row>

<Row gutter={10}>
<Col span={10}>
<Form.Item
label="副本数量"
name="name"
rules={[
{
required: true,
message: '请输入副本数量',
},
]}
>
<Input
placeholder="请输入副本数量"
maxLength={64}
disabled={nameDisabled}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>

<Row gutter={10}>
<Col span={10}>
<Form.Item label="环境变量" name="name">
<Button type="link" style={{ padding: '0' }}>
添加环境变量
</Button>
</Form.Item>
</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 ModelDeploymentCreate;

+ 53
- 0
react-ui/src/pages/ModelDeployment/info.less View File

@@ -0,0 +1,53 @@
.model-deployment-info {
height: 100%;

&__basic {
&__item {
display: flex;
align-items: flex-start;
font-size: 16px;
line-height: 1.6;

.label {
width: 80px;
color: @text-color-secondary;
}

.value {
flex: 1;
color: @text-color;
}
}
}

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

&__title {
display: flex;
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;
}
}
}
}
}
}

+ 148
- 0
react-ui/src/pages/ModelDeployment/info.tsx View File

@@ -0,0 +1,148 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 镜像详情
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { getMirrorInfoReq } from '@/services/mirror';
import { formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { useNavigate, useParams } from '@umijs/max';
import { Col, Row, Tabs, type TabsProps } from 'antd';
import { useEffect, useState } from 'react';
import styles from './info.less';

type MirrorInfoData = {
name?: string;
description?: string;
version_count?: string;
create_time?: string;
};

type MirrorVersionData = {
id: number;
version: string;
url: string;
status: string;
file_size: string;
create_time: string;
};

const tabItems = [
{
key: '1',
label: '预测',
icon: <KFIcon type="icon-yuce" />,
},
{
key: '2',
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
},
{
key: '3',
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
},
];

function ModelDeploymentInfo() {
const navigate = useNavigate();
const urlParams = useParams();

const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({});

const [activeTab, setActiveTab] = useState<string>('1');
useEffect(() => {
getMirrorInfo();
}, []);

// 获取镜像详情
const getMirrorInfo = async () => {
const id = Number(urlParams.id);
const [res] = await to(getMirrorInfoReq(id));
if (res && res.data) {
const { name = '', description = '', version_count = '', create_time: time } = res.data;
const create_time = formatDate(time);
setMirrorInfo({
name,
description,
version_count,
create_time,
});
}
};

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
setActiveTab(value);
};

return (
<div className={styles['model-deployment-info']}>
<PageTitle title="服务详情"></PageTitle>
<div className={styles['model-deployment-info__content']}>
<div>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<div className={styles['model-deployment-info__basic']}>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>服务名称:</div>
<div className={styles['value']}>{mirrorInfo.name}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>镜像:</div>
<div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>状态:</div>
<div className={styles['value']}>{mirrorInfo.name}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>模型:</div>
<div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>环境变量:</div>
<div className={styles['value']}>{mirrorInfo.name}</div>
</div>
</Col>
</Row>
<Row gutter={40}>
<Col span={24}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>描述:</div>
<div className={styles['value']}>{mirrorInfo.description}</div>
</div>
</Col>
</Row>
</div>
<div>
<Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} />
</div>
</div>
</div>
</div>
);
}

export default ModelDeploymentInfo;

+ 21
- 0
react-ui/src/pages/ModelDeployment/list.less View File

@@ -0,0 +1,21 @@
.model-deployment {
height: 100%;
&__content {
height: calc(100% - 60px);
margin-top: 10px;
padding: 20px 30px 0;
background-color: white;
border-radius: 10px;

&__filter {
display: flex;
align-items: center;
justify-content: space-between;
}

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

+ 283
- 0
react-ui/src/pages/ModelDeployment/list.tsx View File

@@ -0,0 +1,283 @@
/*
* @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 PageTitle from '@/components/PageTitle';
import { useCacheState } from '@/hooks/pageCacheState';
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
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,
Input,
Table,
type TablePaginationConfig,
type TableProps,
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import styles from './list.less';

export type MirrorData = {
id: number;
name: string;
description: string;
create_time: string;
};

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

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

// 获取镜像列表
const getMirrorList = async () => {
const params: Record<string, any> = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText,
image_type: 1,
};
const [res] = await to(getMirrorListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

// 删除镜像
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: SearchProps['onSearch'] = (value) => {
setSearchText(value);
};

// 查看详情
const toDetail = (record: MirrorData) => {
navigate(`/modelDeployment/${record.id}`);
setCacheState({
pagination,
searchText,
});
};

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

// 创建镜像
const createMirror = () => {
navigate(`/modelDeployment/create`);
setCacheState({
pagination,
searchText,
});
};

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

const columns: TableProps<MirrorData>['columns'] = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 100,
align: 'center',
render(text, record, index) {
return <span>{(pagination.current - 1) * pagination.pageSize + index + 1}</span>;
},
},
{
title: '服务名称',
dataIndex: 'name',
key: 'name',
width: '30%',
render: CommonTableCell(),
},
{
title: '模型',
dataIndex: 'version_count',
key: 'version_count',
width: '20%',
render: CommonTableCell(),
},
{
title: '状态',
dataIndex: 'version_count',
key: 'version_count',
width: '10%',
render: CommonTableCell(),
},
{
title: '创建人',
dataIndex: 'description',
key: 'description',
render: CommonTableCell(true),
width: '20%',
ellipsis: { showTitle: false },
},
{
title: '更新时间',
dataIndex: 'create_time',
key: 'create_time',
width: '20%',
render: DateTableCell,
},
{
title: '操作',
dataIndex: 'operation',
width: 350,
key: 'operation',
render: (_: any, record: MirrorData) => (
<div>
<Button
type="link"
size="small"
key="edit"
icon={<KFIcon type="icon-bianji" />}
onClick={() => toDetail(record)}
>
编辑
</Button>
<Button
type="link"
size="small"
key="run"
icon={<KFIcon type="icon-yunhang" />}
onClick={() => toDetail(record)}
>
启动
</Button>
<Button
type="link"
size="small"
key="stop"
icon={<KFIcon type="icon-tingzhi" />}
onClick={() => toDetail(record)}
>
停止
</Button>
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleMirrorDelete(record)}
>
删除
</Button>
</ConfigProvider>
</div>
),
},
];

return (
<div className={styles['model-deployment']}>
<PageTitle title="模型列表"></PageTitle>
<div className={styles['model-deployment__content']}>
<div className={styles['model-deployment__filter']}>
<Input.Search
placeholder="按数据集名称筛选"
allowClear
onSearch={onSearch}
onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }}
value={inputText}
/>
<Button
style={{ marginLeft: '20px' }}
type="default"
onClick={createMirror}
icon={<KFIcon type="icon-xinjian2" />}
>
创建推理服务
</Button>
</div>
<div
className={classNames(
'vertical-scroll-table',
styles['model-deployment__content__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>
</div>
);
}

export default ModelDeployment;

react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.less → react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less View File


react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx → react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx View File

@@ -1,7 +1,4 @@
import {
getParamComponent,
getParamRules,
} from '@/pages/Experiment/experimentText/addExperimentModal';
import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal';
import { type PipelineGlobalParam } from '@/types';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
@@ -9,7 +6,7 @@ import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle } from 'react';
import styles from './globalParamsDrawer.less';
import styles from './index.less';

type GlobalParamsDrawerProps = {
open: boolean;
@@ -22,7 +19,7 @@ const GlobalParamsDrawer = forwardRef(
const [form] = Form.useForm();

useImperativeHandle(ref, () => ({
getFieldsValue: async () => {
validateFields: async () => {
const [values, error] = await to(form.validateFields());
if (!error && values) {
return values;
@@ -30,6 +27,9 @@ const GlobalParamsDrawer = forwardRef(
return Promise.reject(error);
}
},
getFieldsValue: () => {
return form.getFieldsValue();
},
}));

// 处理参数类型变化
@@ -162,7 +162,6 @@ const GlobalParamsDrawer = forwardRef(
)}
</Form.List>
</Form>
{/* //{contextHolder} */}
</Drawer>
);
},

+ 6
- 0
react-ui/src/pages/Pipeline/components/PropsLabel/index.less View File

@@ -0,0 +1,6 @@
.props-label {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}

+ 44
- 0
react-ui/src/pages/Pipeline/components/PropsLabel/index.tsx View File

@@ -0,0 +1,44 @@
import { Button, Dropdown, type MenuProps } from 'antd';
import { useEffect } from 'react';
import styles from './index.less';

type PropsLabelProps = {
title: string;
menuItems: MenuProps['items'];
onClick?: (key: string) => void;
};

function PropsLabel({ title, menuItems, onClick }: PropsLabelProps) {
useEffect(() => {}, []);

const handleItemClick: MenuProps['onClick'] = (e) => {
const keyPath = e.keyPath.reverse();
if (keyPath[0] === 'global') {
onClick?.(`\${${e.key}}`);
} else {
onClick?.(`{{${keyPath.join('.')}}}`);
}
};

return (
<div className={styles['props-label']}>
<span>{title}</span>
<Dropdown
menu={{
items: menuItems,
onClick: handleItemClick,
triggerSubMenuAction: 'click',
}}
trigger={['click']}
placement="topRight"
arrow
>
<Button size="small" type="link">
参数
</Button>
</Dropdown>
</div>
);
}

export default PropsLabel;

+ 0
- 4
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx View File

@@ -40,7 +40,6 @@ export type SelectorTypeInfo = {
getFiles: (params: any) => Promise<any>;
handleVersionResponse: (res: any) => any[];
modalIcon: string;
buttonIcon: string;
name: string;
litReqParamKey: 'available_range' | 'image_type';
fileReqParamKey: 'models_id' | 'dataset_id';
@@ -71,7 +70,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
handleVersionResponse: (res) => res.data || [],
name: '模型',
modalIcon: modelImg,
buttonIcon: 'local:model-select-button',
litReqParamKey: 'available_range',
fileReqParamKey: 'models_id',
tabItems: [
@@ -92,7 +90,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
handleVersionResponse: (res) => res.data || [],
name: '数据集',
modalIcon: datasetImg,
buttonIcon: 'local:dataset-select-button',
litReqParamKey: 'available_range',
fileReqParamKey: 'dataset_id',
tabItems: [
@@ -115,7 +112,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
[],
name: '镜像',
modalIcon: mirrorImg,
buttonIcon: 'local:mirror-select-button',
litReqParamKey: 'image_type',
fileReqParamKey: 'dataset_id',
tabItems: [


+ 11
- 0
react-ui/src/pages/Pipeline/editPipeline/editPipeline.less View File

@@ -3,6 +3,17 @@
width: 100%;
height: 100%;
}
.editPipelineProps {
:global {
label {
width: 100%;

&::after {
display: none;
}
}
}
}
.editPipelinePropsContent {
display: flex;
align-items: center;


+ 34
- 23
react-ui/src/pages/Pipeline/editPipeline/index.jsx View File

@@ -1,27 +1,26 @@
import KFIcon from '@/components/KFIcon';
import { useVisible } from '@/hooks';
import { useStateRef, useVisible } from '@/hooks';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import { to } from '@/utils/promise';
import { useEmotionCss } from '@ant-design/use-emotion-css';
import G6 from '@antv/g6';
import { Button, message } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { s8 } from '../../../utils';
import GlobalParamsDrawer from '../components/GlobalParamsDrawer';
import Styles from './editPipeline.less';
import GlobalParamsDrawer from './globalParamsDrawer';
import ModelMenus from './modelMenus';
import Props from './props';
import { findAllParentNodes, findFirstDuplicate } from './utils';

let graph = null;

const EditPipeline = () => {
const propsRef = useRef();
const navgite = useNavigate();
// const [contextMenu,setContextMenu]=useState({})
let contextMenu = {};
const locationParams = useParams(); //新版本获取路由参数接口
let sourceAnchorIdx, targetAnchorIdx;
const pipelineContainer = useEmotionCss(() => {
return {
display: 'flex',
@@ -59,8 +58,11 @@ const EditPipeline = () => {
});
const graphRef = useRef();
const paramsDrawerRef = useRef();
const propsRef = useRef();
const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false);
const [globalParam, setGlobalParam] = useState([]);
const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]);

let sourceAnchorIdx, targetAnchorIdx;

const onDragEnd = (val) => {
console.log(val, 'eee');
@@ -93,17 +95,24 @@ const EditPipeline = () => {
}
};
const savePipeline = async (val) => {
const [res, error] = await to(paramsDrawerRef.current.getFieldsValue());
const [res, error] = await to(paramsDrawerRef.current.validateFields());
if (error) {
message.error('全局参数配置有误');
openParamsDrawer();
return;
}

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

const [propsRes, propsError] = await to(propsRef.current.getFieldsValue());
console.log(await to(propsRef.current.getFieldsValue()));
if (propsError) {
message.error('基本信息必填项需配置');
// handlerClick();
return;
}
propsRef.current.propClose();
@@ -128,12 +137,19 @@ const EditPipeline = () => {
};
const handlerClick = (e) => {
e.stopPropagation();
// console.log(propsRef, graph);
propsRef.current.showDrawer(e);
if (e.target.get('name') !== 'anchor-point' && e.item) {
graph.setItemState(e.item, 'nodeClicked', true);
const parentNodes = findAllParentNodes(graph, e.item);
// 如果没有打开过全局参数抽屉,获取不到全局参数
const globalParams =
paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current;
propsRef.current.showDrawer(e, globalParams, parentNodes);
}
};
const getGraphData = (data) => {
console.log('graph', graph);
if (graph) {
console.log(graph);
console.log(data);
graph.data(data);
graph.render();
} else {
@@ -275,14 +291,13 @@ const EditPipeline = () => {
if (graph && ret.data && ret.data.dag) {
getGraphData(JSON.parse(ret.data.dag));
}
// graph&&graph.data(JSON.parse(ret.dag))
// graph.render()
});
};
const handlerContextMenu = (e) => {
e.stopPropagation();
// this.menuType = e.item._cfg.type;
};
// 上下文菜单
const initMenu = () => {
// const selectedNodes = this.selectedNodes;
contextMenu = new G6.Menu({
@@ -330,8 +345,8 @@ const EditPipeline = () => {
initGraph();
};
useEffect(() => {
getFirstWorkflow(locationParams.id);
initMenu();
getFirstWorkflow(locationParams.id);

return () => {
graph.off('node:mouseenter', (e) => {
@@ -449,7 +464,7 @@ const EditPipeline = () => {
},
'rect',
);
console.log(graphRef, 'graphRef');
graph = new G6.Graph({
container: graphRef.current,
grid: true,
@@ -591,6 +606,8 @@ const EditPipeline = () => {
},
// linkCenter: true,
fitView: true,
minZoom: 0.5,
maxZoom: 3,
fitViewPadding: [320, 320, 220, 320],
});
// graph.on('dblclick', (e) => {
@@ -600,13 +617,7 @@ const EditPipeline = () => {
// handlerClick(e);
// }
// });
graph.on('node:click', (e) => {
console.log(e.target.get('name'));
if (e.target.get('name') !== 'anchor-point' && e.item) {
graph.setItemState(e.item, 'nodeClicked', true);
handlerClick(e);
}
});
graph.on('node:click', handlerClick);
graph.on('aftercreateedge', (e) => {
// update the sourceAnchor and targetAnchor for the newly added edge
graph.updateItem(e.edge, {


+ 7
- 22
react-ui/src/pages/Pipeline/editPipeline/modelMenus.jsx View File

@@ -2,23 +2,6 @@ import { getComponentAll } from '@/services/pipeline/index.js';
import { Collapse } from 'antd';
import { useEffect, useState } from 'react';
import Styles from './modelMenus.less';
const items = [
{
key: '1',
label: 'This is panel header 1',
children: [1, 2, 3, 4, 5],
},
{
key: '2',
label: 'This is panel header 2',
children: [1, 2, 3, 4, 5],
},
{
key: '3',
label: 'This is panel header 3',
children: [1, 2, 3, 4, 5],
},
];
const ModelMenus = ({ onParDragEnd }) => {
const [modelMenusList, setModelMenusList] = useState([]);
useEffect(() => {
@@ -62,11 +45,13 @@ const ModelMenus = ({ onParDragEnd }) => {
}}
className={Styles.collapseItem}
>
<img
style={{ height: '16px', marginRight: '15px' }}
src={`/assets/images/${ele.icon_path}.png`}
alt=""
/>
{ele.icon_path && (
<img
style={{ height: '16px', marginRight: '15px' }}
src={`/assets/images/${ele.icon_path}.png`}
alt=""
/>
)}
{ele.component_label}
</div>
))


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

@@ -1,26 +1,31 @@
import KFIcon from '@/components/KFIcon';
import SubAreaTitle from '@/components/SubAreaTitle';
import { getComputingResourceReq } from '@/services/pipeline';
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 PropsLabel from '../components/PropsLabel';
import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal';
import Styles from './editPipeline.less';
import styles from './editPipeline.less';
import { createMenuItems } from './utils';
const { TextArea } = Input;

const Props = forwardRef(({ onParentChange }, ref) => {
const [form] = Form.useForm();

const [stagingItem, setStagingItem] = useState({});
const [open, setOpen] = useState(false);
const [selectedModel, setSelectedModel] = useState(undefined);
const [selectedDataset, setSelectedDataset] = useState(undefined);
const [resourceStandardList, setResourceStandardList] = useState([]);
const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择
const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择
const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表
const [menuItems, setMenuItems] = useState([]);

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

// 获取资源规格列表数据
const getComputingResource = async () => {
const params = {
page: 0,
@@ -35,49 +40,22 @@ const Props = forwardRef(({ onParentChange }, ref) => {

const afterOpenChange = () => {
if (!open) {
console.log(stagingItem, form.getFieldsValue());
// 禁止校验 guard-for-in
/* eslint-disable */
for (let i in form.getFieldsValue()) {
for (let j in stagingItem.in_parameters) {
if (i == j) {
console.log(j, i);
stagingItem.in_parameters[j].value = form.getFieldsValue()[i];
}
}
for (let p in stagingItem.out_parameters) {
if (i == p) {
stagingItem.out_parameters[p].value = form.getFieldsValue()[i];
}
}
for (let k in stagingItem.control_strategy) {
if (i == k) {
stagingItem.control_strategy[k].value = form.getFieldsValue()[i];
}
}
}
/* eslint-enable */
// setStagingItem({...stagingItem,})
console.log(stagingItem.control_strategy);
console.log('zzzz', form.getFieldsValue());
const control_strategy = form.getFieldValue('control_strategy');
const in_parameters = form.getFieldValue('in_parameters');
const out_parameters = form.getFieldValue('out_parameters');
onParentChange({
...stagingItem,
control_strategy: JSON.stringify(stagingItem.control_strategy),
in_parameters: JSON.stringify(stagingItem.in_parameters),
out_parameters: JSON.stringify(stagingItem.out_parameters),
...form.getFieldsValue(),
control_strategy: JSON.stringify(control_strategy),
in_parameters: JSON.stringify(in_parameters),
out_parameters: JSON.stringify(out_parameters),
});
// onParentChange({...stagingItem,...form.getFieldsValue()})
}
};
const onClose = () => {
setOpen(false);
};
const onFinish = (values) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useImperativeHandle(ref, () => ({
getFieldsValue: async () => {
const [propsRes, propsError] = await to(form.validateFields());
@@ -88,47 +66,43 @@ const Props = forwardRef(({ onParentChange }, ref) => {
return Promise.reject(propsError);
}
},
showDrawer(e) {
showDrawer(e, params, parentNodes) {
if (e.item && e.item.getModel()) {
// console.log(e.item.getModel().in_parameters);
form.resetFields();
form.setFieldsValue({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setStagingItem({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
// form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)})
// setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)})
// setTimeout(() => {
// console.log(stagingItem);
// }, (500));
const model = e.item.getModel();
try {
const nodeData = {
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
};
setStagingItem({
...nodeData,
});
form.setFieldsValue({
...nodeData,
});
} catch (error) {
console.log(error);
}
setSelectedModel(undefined);
setSelectedDataset(undefined);
setOpen(true);

// 参数下拉菜单
setMenuItems(createMenuItems(params, parentNodes));
}
},
propClose: async () => {
setOpen(false);
const [openRes, propsError] = await to(setOpen(false));
console.log(setOpen(false));
propClose: () => {
close();
},
// propClose() {

// setOpen(false);
// },
}));

// 选择数据集、模型
const selectResource = (name, item) => {
let type;
let resource = undefined;
let resource;
switch (item.item_type) {
case 'dataset':
type = ResourceSelectorType.Dataset;
@@ -142,41 +116,37 @@ const Props = forwardRef(({ onParentChange }, ref) => {
type = ResourceSelectorType.Mirror;
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);
} else {
const jsonObj = pick(res, ['id', 'version', 'path']);
const value = JSON.stringify(jsonObj);
form.setFieldValue(name, value);
}

if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
}
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);
} else {
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(null);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(null);
}
form.setFieldValue(name, '');
const jsonObj = pick(res, ['id', 'version', 'path']);
const value = JSON.stringify(jsonObj);
form.setFieldValue(name, { ...item, value });
}
close();
},

if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
}
} else {
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(null);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(null);
}
form.setFieldValue(name, '');
}
close();
},
true,
);
});
};

// 获取选择数据集、模型后面按钮 icon
@@ -191,16 +161,31 @@ const Props = forwardRef(({ onParentChange }, ref) => {
}
};

// 筛选资源规格
const filterResourceStandard = (input, { computing_resource = '' }) => {
return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase());
};

// 参数回填
const handleParameterClick = (name, value) => {
form.setFieldValue(name, value);
};

// 控制策略
const controlStrategy = stagingItem.control_strategy;
const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);

// 输入参数
const inParameters = stagingItem.in_parameters;
const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({
key,
value,
}));

// 输出参数
const outParameters = stagingItem.out_parameters;
const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map(
([key, value]) => ({ key, value }),
);

return (
<>
@@ -213,15 +198,15 @@ const Props = forwardRef(({ onParentChange }, ref) => {
onClose={onClose}
afterOpenChange={afterOpenChange}
open={open}
width={420}
width={520}
className={styles.editPipelineProps}
>
<Form
name="form"
form={form}
// layout="vertical"
layout="vertical"
labelCol={{
span: 16,
span: 24,
}}
wrapperCol={{
span: 24,
@@ -229,20 +214,10 @@ const Props = forwardRef(({ onParentChange }, ref) => {
style={{
maxWidth: 600,
}}
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '13px', marginRight: '10px' }}
src={'/assets/images/static-message.png'}
alt=""
/>
基本信息
<div className={styles.editPipelinePropsContent}>
<SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle>
</div>
<Form.Item
label="任务名称"
@@ -254,7 +229,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
},
]}
>
<Input />
<Input placeholder="请输入任务名称" allowClear />
</Form.Item>
<Form.Item
label="任务ID"
@@ -268,37 +243,53 @@ const Props = forwardRef(({ onParentChange }, ref) => {
>
<Input disabled />
</Form.Item>
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
任务信息
<div className={styles.editPipelinePropsContent}>
<SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={Styles['ref-row']}>
<div className={styles['ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入镜像" allowClear />
<Input placeholder="请输入或选择镜像" allowClear />
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={Styles['select-button']}
className={styles['select-button']}
>
选择镜像
</Button>
</Form.Item>
</div>
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<Input />
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item>

<Form.Item label="启动命令" name="command">
<TextArea />
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
@@ -321,78 +312,148 @@ const Props = forwardRef(({ onParentChange }, ref) => {
}}
/>
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<Input />
<Form.Item
name="mount_path"
label={
<PropsLabel
menuItems={menuItems}
title="挂载路径"
onClick={(value) => {
handleParameterClick('mount_path', value);
}}
/>
}
>
<Input placeholder="请输入挂载路径" allowClear />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<TextArea />
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{controlStrategy && Object.keys(controlStrategy).length > 0
? Object.keys(controlStrategy).map((item) => (
<Form.Item key={item} label={controlStrategy[item].label} name={item}>
<Input />
</Form.Item>
))
: ''}
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
输入参数
{controlStrategyList.map((item) => (
<Form.Item
key={item.key}
name={['control_strategy', item.key]}
label={
<PropsLabel
menuItems={menuItems}
title={item.value.label}
onClick={(value) => {
handleParameterClick(['control_strategy', item.key], {
...item.value,
value,
});
}}
/>
}
getValueProps={(e) => {
return { value: e.value };
}}
getValueFromEvent={(e) => {
return {
...item.value,
value: e.target.value,
};
}}
>
<Input placeholder={item.value.label} allowClear />
</Form.Item>
))}
<div className={styles.editPipelinePropsContent}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle>
</div>
{inParameters && Object.keys(inParameters).length > 0
? Object.keys(inParameters).map((item) => (
{inParametersList.map((item) => (
<Form.Item
key={item.key}
label={
<PropsLabel
menuItems={menuItems}
title={item.value.label + '(' + item.key + ')'}
onClick={(value) => {
handleParameterClick(['in_parameters', item.key], {
...item.value,
value,
});
}}
/>
}
required={item.value.require ? true : false}
>
<div className={styles['ref-row']}>
<Form.Item
key={item}
label={inParameters[item].label + '(' + item + ')'}
required={inParameters[item].require ? true : false}
name={['in_parameters', item.key]}
noStyle
rules={[{ required: item.value.require ? true : false }]}
getValueProps={(e) => {
return { value: e.value };
}}
getValueFromEvent={(e) => {
return {
...item.value,
value: e.target.value,
};
}}
>
<div className={Styles['ref-row']}>
<Form.Item
name={item}
noStyle
rules={[{ required: inParameters[item].require ? true : false }]}
>
<Input />
</Form.Item>
{inParameters[item].type === 'ref' && (
<Form.Item noStyle>
<Button
type="link"
icon={getSelectBtnIcon(inParameters[item])}
onClick={() => selectResource(item, inParameters[item])}
className={Styles['select-button']}
>
{inParameters[item].label}
</Button>
</Form.Item>
)}
</div>
<Input placeholder={item.value.label} allowClear />
</Form.Item>
))
: ''}
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
输出参数
{item.value.type === 'ref' && (
<Form.Item noStyle>
<Button
type="link"
icon={getSelectBtnIcon(item.value)}
onClick={() => selectResource(['in_parameters', item.key], item.value)}
className={styles['select-button']}
>
{item.value.label}
</Button>
</Form.Item>
)}
</div>
</Form.Item>
))}
<div className={styles.editPipelinePropsContent}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle>
</div>
{outParameters && Object.keys(outParameters).length > 0
? Object.keys(outParameters).map((item) => (
<Form.Item
key={item}
label={outParameters[item].label + '(' + item + ')'}
rules={[{ required: outParameters[item].require ? true : false }]}
name={item}
>
<Input />
</Form.Item>
))
: ''}
{outParametersList.map((item) => (
<Form.Item
key={item.key}
name={['out_parameters', item.key]}
label={
<PropsLabel
menuItems={menuItems}
title={item.value.label + '(' + item.key + ')'}
onClick={(value) => {
handleParameterClick(['out_parameters', item.key], {
...item.value,
value,
});
}}
/>
}
rules={[{ required: item.value.require ? true : false }]}
getValueProps={(e) => {
return { value: e.value };
}}
getValueFromEvent={(e) => {
return {
...item.value,
value: e.target.value,
};
}}
>
<Input placeholder={item.value.label} allowClear />
</Form.Item>
))}
</Form>
</Drawer>
</>


+ 79
- 0
react-ui/src/pages/Pipeline/editPipeline/utils.tsx View File

@@ -0,0 +1,79 @@
import { PipelineGlobalParam } from '@/types';
import { Graph, INode } from '@antv/g6';
import { type MenuProps } from 'antd';

// 找到节点所以的上游节点
export const findAllParentNodes = (graph: Graph, node: INode) => {
const parentNodes: INode[] = [];
let index = -1;
let targetNode = node;
while (targetNode) {
const neighbors: INode[] = graph.getNeighbors(targetNode, 'source');
for (const sourceNode of neighbors) {
// 避免重复,也避免循环
const idx = parentNodes.findIndex((item) => sourceNode.getID() === item.getID());
if (idx === -1 && sourceNode.getID() !== node.getID()) {
parentNodes.push(sourceNode);
}
}
targetNode = parentNodes[++index];
}

return parentNodes;
};

// 判断并找到全局参数第一个重复项,有重复项时,全局参数不允许保存
export function findFirstDuplicate(params: PipelineGlobalParam[]): string | null {
const seen = new Set();
for (const item of params) {
if (seen.has(item.param_name)) {
return item.param_name;
}
seen.add(item.param_name);
}
return null;
}

// 创建参数下拉菜单
export function createMenuItems(
params: PipelineGlobalParam[],
parentNodes: INode[],
): MenuProps['items'] {
const nodes: MenuProps['items'] = parentNodes.map((item) => {
const model = item.getModel();
const out_parameters = model.out_parameters as string | undefined | null;
const out_parametersObj = parseJsonText(out_parameters);
const outParametersList = Object.keys(out_parametersObj ?? {});
return {
key: model.id as string,
label: model.label as string,
children: outParametersList.map((key: string) => ({
key: key as string,
label: out_parametersObj[key].label,
})),
};
});

return [
{
key: 'global',
label: '全局参数',
children: params.map((item) => ({
key: item.param_name,
label: item.param_name,
})),
},
...nodes,
];
}

function parseJsonText(text?: string | null): any | null {
if (!text) {
return null;
}
try {
return JSON.parse(text);
} catch (error) {
return null;
}
}

+ 12
- 17
react-ui/src/pages/Pipeline/index.jsx View File

@@ -1,4 +1,6 @@
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import {
addWorkflow,
cloneWorkflow,
@@ -9,12 +11,12 @@ import {
} from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { modalConfirm } from '@/utils/ui';
import { Button, ConfigProvider, Form, Input, Modal, Space, Table, message } from 'antd';
import { Button, ConfigProvider, Form, Input, 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';
import Styles from './index.less';

const { TextArea } = Input;
const Pipeline = () => {
const [form] = Form.useForm();
@@ -136,13 +138,13 @@ const Pipeline = () => {
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
render: (text) => <span>{momnet(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: DateTableCell,
},
{
title: '修改时间',
dataIndex: 'update_time',
key: 'update_time',
render: (text) => <span>{momnet(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: DateTableCell,
},
{
title: '操作',
@@ -168,7 +170,7 @@ const Pipeline = () => {
key="clone"
icon={<KFIcon type="icon-fuzhi" />}
onClick={async () => {
Modal.confirm({
modalConfirm({
title: '复制',
content: '确定复制该条流水线吗?',
okText: '确认',
@@ -254,17 +256,10 @@ const Pipeline = () => {
scroll={{ y: 'calc(100% - 55px)' }}
/>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
<KFModal
title={dialogTitle}
image={require('@/assets/img/create-experiment.png')}
width={825}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
@@ -314,7 +309,7 @@ const Pipeline = () => {
/>
</Form.Item>
</Form>
</Modal>
</KFModal>
</div>
);
};


+ 4
- 47
react-ui/src/pages/Pipeline/index.less View File

@@ -8,60 +8,17 @@
padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;

}

.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
}
.PipelineBox{
.PipelineBox {
height: calc(100% - 20px);
.PipelineTable{
.PipelineTable {
height: calc(100% - 60px);
:global{
.ant-table-wrapper .ant-table{
:global {
.ant-table-wrapper .ant-table {
// overflow-y: auto;
height: calc(100% - 48px);
}
}
}

}


+ 191
- 456
react-ui/src/pages/User/Login/index.tsx View File

@@ -222,12 +222,7 @@ const Login: React.FC = () => {
getCaptchaCode();
}
} catch (error) {
const defaultLoginFailureMessage = intl.formatMessage({
id: 'pages.login.failure',
defaultMessage: '登录失败,请重试!',
});
console.log(error);
message.error(defaultLoginFailureMessage);
getCaptchaCode();
}
};
const { code } = userLoginState;
@@ -289,479 +284,219 @@ const Login: React.FC = () => {
>
账号登录
</div>
<LoginForm
title=""
className={styles.loginForm}
initialValues={{
autoLogin: true,
}}
// actions={[
// <FormattedMessage
// key="loginWith"
// id="pages.login.loginWith"
// defaultMessage="其他登录方式"
// />,
// <ActionIcons key="icons" />,
// ]}
onFinish={async (values) => {
await handleSubmit(values as API.LoginParams);
}}
>
{code !== 200 && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/admin123)',
})}
/>
)}
{type === 'account' && (
<>
<ProFormText
name="username"
initialValue="admin"
fieldProps={{
size: 'large',
prefix: <UserOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
},
]}
/>
<ProFormText.Password
name="password"
initialValue="admin123"
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: admin123',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
/>
<Row>
<Col flex={4}>
<ProFormText
style={{
float: 'right',
}}
name="code"
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleName.nameRules"
defaultMessage="请输入验证啊"
/>
),
},
]}
/>
</Col>
<Col>
<Image
src={captchaCode}
alt="验证码"
style={{
display: 'inline-block',
verticalAlign: 'top',
cursor: 'pointer',
paddingLeft: '22px',
width: '170px',
height: '66px',
}}
preview={false}
onClick={() => getCaptchaCode()}
/>
</Col>
</Row>
</>
)}

{code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
{type === 'mobile' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined />,
}}
name="mobile"
placeholder={intl.formatMessage({
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
},
{
pattern: /^1\d{10}$/,
message: (
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
/>
),
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
captchaProps={{
size: 'large',
}}
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${intl.formatMessage({
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`;
}
return intl.formatMessage({
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
});
}}
name="captcha"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
/>
),
},
]}
onGetCaptcha={async (phone) => {
const result = await getFakeCaptcha({
phone,
});
if (!result) {
return;
}
message.success('获取验证码成功!验证码为:1234');
}}
/>
</>
)}
<div
style={{
marginBottom: 24,
<div className={styles.loginForm}>
<LoginForm
title=""
initialValues={{
autoLogin: true,
}}
// actions={[
// <FormattedMessage
// key="loginWith"
// id="pages.login.loginWith"
// defaultMessage="其他登录方式"
// />,
// <ActionIcons key="icons" />,
// ]}
onFinish={async (values) => {
await handleSubmit(values as API.LoginParams);
}}
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="记住密码" />
</ProFormCheckbox>
</div>
</LoginForm>
</div>
</div>
{/* <Helmet>
<title>
{intl.formatMessage({
id: 'menu.login',
defaultMessage: '登录页',
})}
- {Settings.title}
</title>
</Helmet>
<Lang />
<div
style={{
flex: '1',
padding: '32px 0',
}}
>
<LoginForm
contentStyle={{
minWidth: 280,
maxWidth: '75vw',
}}
logo={<img alt="logo" src="/logo.svg" />}
title="Ant Design"
subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
initialValues={{
autoLogin: true,
}}
actions={[
<FormattedMessage
key="loginWith"
id="pages.login.loginWith"
defaultMessage="其他登录方式"
/>,
<ActionIcons key="icons" />,
]}
onFinish={async (values) => {
await handleSubmit(values as API.LoginParams);
}}
>
<Tabs
activeKey={type}
onChange={setType}
centered
items={[
{
key: 'account',
label: intl.formatMessage({
id: 'pages.login.accountLogin.tab',
defaultMessage: '账户密码登录',
}),
},
{
key: 'mobile',
label: intl.formatMessage({
id: 'pages.login.phoneLogin.tab',
defaultMessage: '手机号登录',
}),
},
]}
/>

{code !== 200 && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/admin123)',
})}
/>
)}
{type === 'account' && (
<>
<ProFormText
name="username"
initialValue="admin"
fieldProps={{
size: 'large',
prefix: <UserOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
},
]}
/>
<ProFormText.Password
name="password"
initialValue="admin123"
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: admin123',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
/>
<Row>
<Col flex={3}>
{code !== 200 && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/admin123)',
})}
/>
)}
{type === 'account' && (
<>
<ProFormText
style={{
float: 'right',
name="username"
initialValue="admin"
fieldProps={{
size: 'large',
prefix: <UserOutlined />,
}}
name="code"
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证',
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleName.nameRules"
defaultMessage="请输入验证啊"
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
},
]}
/>
</Col>
<Col flex={2}>
<Image
src={captchaCode}
alt="验证码"
style={{
display: 'inline-block',
verticalAlign: 'top',
cursor: 'pointer',
paddingLeft: '10px',
width: '100px',
<ProFormText.Password
name="password"
initialValue="admin123"
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
preview={false}
onClick={() => getCaptchaCode()}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: admin123',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
/>
</Col>
</Row>
</>
)}

{code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
{type === 'mobile' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined />,
}}
name="mobile"
placeholder={intl.formatMessage({
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
},
{
pattern: /^1\d{10}$/,
message: (
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
<Row>
<Col flex={4}>
<ProFormText
style={{
float: 'right',
}}
name="code"
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleName.nameRules"
defaultMessage="请输入验证啊"
/>
),
},
]}
/>
),
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
captchaProps={{
size: 'large',
}}
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${intl.formatMessage({
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`;
}
return intl.formatMessage({
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
});
}}
name="captcha"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
</Col>
<Col>
<Image
src={captchaCode}
alt="验证码"
style={{
display: 'inline-block',
verticalAlign: 'top',
cursor: 'pointer',
paddingLeft: '22px',
width: '170px',
height: '66px',
}}
preview={false}
onClick={() => getCaptchaCode()}
/>
),
},
]}
onGetCaptcha={async (phone) => {
const result = await getFakeCaptcha({
phone,
});
if (!result) {
return;
}
message.success('获取验证码成功!验证码为:1234');
</Col>
</Row>
</>
)}

{code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
{type === 'mobile' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined />,
}}
name="mobile"
placeholder={intl.formatMessage({
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
},
{
pattern: /^1\d{10}$/,
message: (
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
/>
),
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
captchaProps={{
size: 'large',
}}
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${intl.formatMessage({
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`;
}
return intl.formatMessage({
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
});
}}
name="captcha"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
/>
),
},
]}
onGetCaptcha={async (phone) => {
const result = await getFakeCaptcha({
phone,
});
if (!result) {
return;
}
message.success('获取验证码成功!验证码为:1234');
}}
/>
</>
)}
<div
style={{
marginBottom: 24,
}}
/>
</>
)}
<div
style={{
marginBottom: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
</ProFormCheckbox>
<a
style={{
float: 'right',
}}
>
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
</a>
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="记住密码" />
</ProFormCheckbox>
</div>
</LoginForm>
</div>
</LoginForm>
</div>
</div>
<Footer /> */}
</div>
);
};


+ 29
- 26
react-ui/src/pages/User/Login/login.less View File

@@ -1,28 +1,31 @@
.loginForm {
width: 520px;
}
:global .ant-pro-form-login-main {
margin: unset;
}
:global .ant-input-affix-wrapper-lg {
padding: 19px 11px;
color: rgba(29, 29, 32, 0.6);
font-size: 18px;
font-family: 'Alibaba';
border-radius: 13px;
}
:global .ant-input-affix-wrapper {
padding: 19px 11px;
color: rgba(29, 29, 32, 0.6);
font-size: 18px;
font-family: 'Alibaba';
border-radius: 13px;
}
:global .ant-btn.ant-btn-lg {
height: 76px;
color: #ffffff;
font-size: 20px;
font-family: 'Alibaba';
background: #1664ff;
border-radius: 41px;
:global {
.ant-pro-form-login-main {
width: auto !important;
max-width: auto !important;
margin: unset;
}
.ant-input-affix-wrapper-lg {
padding: 19px 11px;
color: rgba(29, 29, 32, 0.6);
font-size: 18px;
font-family: 'Alibaba';
border-radius: 13px;
}
.ant-input-affix-wrapper {
padding: 19px 11px;
color: rgba(29, 29, 32, 0.6);
font-size: 18px;
font-family: 'Alibaba';
border-radius: 13px;
}
.ant-btn.ant-btn-lg {
height: 76px;
color: #ffffff;
font-size: 20px;
font-family: 'Alibaba';
background: @primary-color;
border-radius: 41px;
}
}
}

+ 6
- 7
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

@@ -35,10 +35,10 @@ function AssetsManagement() {
title: '组件',
value: component,
},
{
title: '代码配置',
value: 0,
},
// {
// title: '代码配置',
// value: 0,
// },
{
title: '流水线模版',
value: workflow,
@@ -64,9 +64,8 @@ function AssetsManagement() {
]}
/>
</Flex>

<div className={styles['assets-management__increase']}>今日新增数量:5</div>
<Flex justify="space-between" gap="22px 0" wrap="wrap">
{/* <div className={styles['assets-management__increase']}>今日新增数量:5</div> */}
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px' }}>
{assetCounts.map((item, index) => (
<div className={styles['assets-management__summary']} key={index}>
<div className={styles['assets-management__summary__title']}>{item.title}</div>


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save