Browse Source

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

dev-lhz
cp3hnu 1 year ago
parent
commit
c6b8fff02f
83 changed files with 767 additions and 679 deletions
  1. +0
    -1
      react-ui/config/config.ts
  2. +2
    -2
      react-ui/config/defaultSettings.ts
  3. +148
    -78
      react-ui/config/routes.ts
  4. BIN
      react-ui/public/assets/images/delete-icon.png
  5. +0
    -0
      react-ui/public/assets/images/experiment-status/fail-icon.png
  6. +0
    -0
      react-ui/public/assets/images/experiment-status/omitted-icon.png
  7. +0
    -0
      react-ui/public/assets/images/experiment-status/pending-icon.png
  8. +0
    -0
      react-ui/public/assets/images/experiment-status/running-icon.png
  9. +0
    -0
      react-ui/public/assets/images/experiment-status/success-icon.png
  10. BIN
      react-ui/public/assets/images/left-top-logo-1.png
  11. BIN
      react-ui/public/assets/images/pipeline-back.png
  12. BIN
      react-ui/public/assets/images/upload-icon.png
  13. BIN
      react-ui/public/favicon.ico
  14. +26
    -18
      react-ui/src/app.tsx
  15. +0
    -0
      react-ui/src/assets/img/dataset-intro-top.png
  16. BIN
      react-ui/src/assets/img/delete-icon.png
  17. +0
    -0
      react-ui/src/assets/img/duty-message.png
  18. +0
    -0
      react-ui/src/assets/img/login-ai-logo.png
  19. BIN
      react-ui/src/assets/img/login-captcha.png
  20. +0
    -0
      react-ui/src/assets/img/login-left-image.png
  21. BIN
      react-ui/src/assets/img/login-password.png
  22. BIN
      react-ui/src/assets/img/login-user.png
  23. +0
    -0
      react-ui/src/assets/img/logo.png
  24. +0
    -0
      react-ui/src/assets/img/modal-back.png
  25. +0
    -0
      react-ui/src/assets/img/pipeline-canvas-bg.png
  26. +0
    -0
      react-ui/src/assets/img/static-message.png
  27. +51
    -0
      react-ui/src/components/KFBreadcrumb/index.tsx
  28. +1
    -1
      react-ui/src/components/KFModal/index.less
  29. +1
    -0
      react-ui/src/components/ParameterInput/index.less
  30. +10
    -0
      react-ui/src/components/RightContent/index.less
  31. +26
    -29
      react-ui/src/components/RightContent/index.tsx
  32. +20
    -1
      react-ui/src/global.less
  33. +1
    -1
      react-ui/src/iconfont/iconfont.js
  34. +9
    -1
      react-ui/src/overrides.less
  35. +1
    -1
      react-ui/src/pages/Dataset/components/ResourceIntro/index.less
  36. +1
    -1
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  37. +2
    -1
      react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx
  38. +3
    -2
      react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
  39. +11
    -4
      react-ui/src/pages/Experiment/Info/index.jsx
  40. +1
    -1
      react-ui/src/pages/Experiment/Info/index.less
  41. +16
    -4
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx
  42. +4
    -4
      react-ui/src/pages/Experiment/index.jsx
  43. +1
    -1
      react-ui/src/pages/Experiment/index.less
  44. +8
    -8
      react-ui/src/pages/Experiment/status.ts
  45. +1
    -0
      react-ui/src/pages/Mirror/Create/index.tsx
  46. +1
    -1
      react-ui/src/pages/Mirror/List/index.tsx
  47. +1
    -1
      react-ui/src/pages/Model/components/ModelEvolution/index.less
  48. +3
    -3
      react-ui/src/pages/Model/components/NodeTooltips/index.tsx
  49. +1
    -0
      react-ui/src/pages/ModelDeployment/Create/index.tsx
  50. +1
    -1
      react-ui/src/pages/ModelDeployment/List/index.tsx
  51. +3
    -3
      react-ui/src/pages/Monitor/Job/index.tsx
  52. +1
    -1
      react-ui/src/pages/Monitor/JobLog/index.tsx
  53. +2
    -2
      react-ui/src/pages/Monitor/Online/index.tsx
  54. +3
    -4
      react-ui/src/pages/Pipeline/Info/index.jsx
  55. +1
    -1
      react-ui/src/pages/Pipeline/Info/index.less
  56. +0
    -0
      react-ui/src/pages/Pipeline/Info/utils.tsx
  57. +0
    -0
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.less
  58. +20
    -8
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  59. +3
    -3
      react-ui/src/pages/Pipeline/index.jsx
  60. +1
    -1
      react-ui/src/pages/Pipeline/index.less
  61. +1
    -1
      react-ui/src/pages/System/Config/index.tsx
  62. +1
    -1
      react-ui/src/pages/System/Dept/index.tsx
  63. +1
    -1
      react-ui/src/pages/System/Dict/index.tsx
  64. +1
    -1
      react-ui/src/pages/System/DictData/index.tsx
  65. +1
    -1
      react-ui/src/pages/System/Logininfor/index.tsx
  66. +1
    -1
      react-ui/src/pages/System/Menu/index.tsx
  67. +1
    -1
      react-ui/src/pages/System/Notice/index.tsx
  68. +1
    -1
      react-ui/src/pages/System/Operlog/index.tsx
  69. +1
    -1
      react-ui/src/pages/System/Post/index.tsx
  70. +1
    -1
      react-ui/src/pages/System/Role/authUser.tsx
  71. +3
    -3
      react-ui/src/pages/System/Role/index.tsx
  72. +3
    -3
      react-ui/src/pages/System/User/index.tsx
  73. +128
    -435
      react-ui/src/pages/User/Login/index.tsx
  74. +156
    -25
      react-ui/src/pages/User/Login/login.less
  75. +5
    -6
      react-ui/src/services/session.ts
  76. +10
    -0
      react-ui/src/types.ts
  77. +31
    -0
      react-ui/src/utils/localStorage.ts
  78. +1
    -1
      react-ui/src/utils/sessionStorage.ts
  79. +1
    -1
      react-ui/src/utils/ui.tsx
  80. +11
    -5
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java
  81. +1
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/AIM64EncoderUtil.java
  82. +20
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java
  83. +2
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/MlflowUtil.java

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

@@ -78,7 +78,6 @@ export default defineConfig({
*/ */
title: '智能材料科研平台', title: '智能材料科研平台',
layout: { layout: {
locale: false,
...defaultSettings, ...defaultSettings,
}, },
// keepalive: [/./], // keepalive: [/./],


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

@@ -7,10 +7,11 @@ const Settings: ProLayoutProps & {
pwa?: boolean; pwa?: boolean;
logo?: string; logo?: string;
} = { } = {
locale: 'zh-CN',
navTheme: 'light', navTheme: 'light',
// 拂晓蓝 // 拂晓蓝
colorPrimary: '#1664ff', colorPrimary: '#1664ff',
layout: 'mix',
// layout: 'mix',
contentWidth: 'Fluid', contentWidth: 'Fluid',
fixedHeader: false, fixedHeader: false,
fixSiderbar: false, fixSiderbar: false,
@@ -18,7 +19,6 @@ const Settings: ProLayoutProps & {
colorWeak: false, colorWeak: false,
title: '智能材料科研平台', title: '智能材料科研平台',
pwa: true, pwa: true,
logo: '/assets/images/left-top-logo.png',
token: { token: {
// 参见ts声明,demo 见文档,通过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 //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


+ 148
- 78
react-ui/config/routes.ts View File

@@ -14,11 +14,19 @@ export default [
{ {
path: '/', path: '/',
redirect: '/workspace', redirect: '/workspace',
breadcrumb: '工作空间',
}, },
{ {
path: '*',
layout: false,
component: './404',
name: 'workspace',
path: '/workspace',
routes: [
{
name: '工作空间',
path: '',
key: 'workspace',
component: './Workspace/index',
},
],
}, },
{ {
path: '/user', path: '/user',
@@ -35,40 +43,64 @@ export default [
path: '/account', path: '/account',
routes: [ routes: [
{ {
name: 'acenter',
name: '用户中心',
path: '/account/center', path: '/account/center',
component: './User/Center', component: './User/Center',
}, },
{ {
name: 'asettings',
name: '用户设置',
path: '/account/settings', path: '/account/settings',
component: './User/Settings', component: './User/Settings',
}, },
], ],
}, },
{ {
name: 'datasetPreparation',
name: '数据准备',
path: '/datasetPreparation', path: '/datasetPreparation',
routes: [ routes: [
{ {
name: 'datasetAnnotation',
path: '',
redirect: '/datasetPreparation/datasetAnnotation',
},
{
name: '数据标注',
path: 'datasetAnnotation', path: 'datasetAnnotation',
component: './DatasetPreparation/DatasetAnnotation/index', component: './DatasetPreparation/DatasetAnnotation/index',
}, },
],
},
{
name: 'developmentEnvironment',
path: '/developmentEnvironment',
routes: [
{
name: '开发环境',
path: '',
component: './DevelopmentEnvironment/Editor',
},
{ {
name: '训练',
path: 'pytorchtext/:id/:name',
component: './Pipeline/editPipeline/index',
name: '创建编辑器',
path: 'create',
component: './DevelopmentEnvironment/Create',
},
{
name: '编辑器',
path: 'editor',
component: './DevelopmentEnvironment/Editor',
}, },
], ],
}, },
{ {
name: 'pipeline',
name: '流水线',
path: '/pipeline', path: '/pipeline',
routes: [ routes: [
{
path: '',
redirect: '/pipeline/template',
},
{ {
name: '流水线模板', name: '流水线模板',
path: 'template',
path: '/pipeline/template',
routes: [ routes: [
{ {
name: '流水线模板', name: '流水线模板',
@@ -77,8 +109,8 @@ export default [
}, },
{ {
name: '流水线详情', name: '流水线详情',
path: ':id',
component: './Pipeline/editPipeline/index',
path: 'info/:id',
component: './Pipeline/Info/index',
}, },
], ],
}, },
@@ -92,8 +124,8 @@ export default [
component: './Experiment/index', component: './Experiment/index',
}, },
{ {
name: '实验训练',
path: ':workflowId/:id',
name: '实验实例',
path: 'instance/:workflowId/:id',
component: './Experiment/Info/index', component: './Experiment/Info/index',
}, },
{ {
@@ -106,58 +138,25 @@ export default [
], ],
}, },
{ {
name: 'developmentEnvironment',
path: '/developmentEnvironment',
name: 'AI资产',
path: '/dataset',
routes: [ routes: [
{ {
name: '开发环境',
path: '', path: '',
component: './DevelopmentEnvironment/Editor',
},
{
name: '创建编辑器',
path: 'create',
component: './DevelopmentEnvironment/Create',
},
{
name: '编辑器',
path: 'editor',
component: './DevelopmentEnvironment/Editor',
redirect: '/dataset/dataset',
}, },
],
},
{
name: 'system',
path: '/system',
routes: [
{
name: '字典数据',
path: '/system/dict-data/index/:id',
component: './System/DictData',
},
{
name: '分配用户',
path: '/system/role-auth/user/:id',
component: './System/Role/authUser',
},
],
},
{
name: 'dataset',
path: '/dataset',
routes: [
{ {
name: '数据集', name: '数据集',
path: 'dataset', path: 'dataset',
routes: [ routes: [
{ {
name: '数据集列表',
name: '数据集',
path: '', path: '',
component: './Dataset/index', component: './Dataset/index',
}, },
{ {
name: '数据集简介', name: '数据集简介',
path: ':id',
path: 'info/:id',
component: './Dataset/intro', component: './Dataset/intro',
}, },
], ],
@@ -167,13 +166,13 @@ export default [
path: 'model', path: 'model',
routes: [ routes: [
{ {
name: '模型列表',
name: '模型',
path: '', path: '',
component: './Model/index', component: './Model/index',
}, },
{ {
name: '模型简介', name: '模型简介',
path: ':id',
path: 'info/:id',
component: './Model/intro', component: './Model/intro',
}, },
], ],
@@ -183,13 +182,13 @@ export default [
path: 'mirror', path: 'mirror',
routes: [ routes: [
{ {
name: '镜像列表',
name: '镜像',
path: '', path: '',
component: './Mirror/List', component: './Mirror/List',
}, },
{ {
name: '镜像详情', name: '镜像详情',
path: ':id',
path: 'info/:id',
component: './Mirror/Info', component: './Mirror/Info',
}, },
{ {
@@ -201,42 +200,29 @@ export default [
}, },
], ],
}, },

{
name: 'workspace',
path: '/workspace',
routes: [
{
name: '工作空间',
path: '',
key: 'workspace',
component: './Workspace/index',
},
],
},
{ {
name: 'modelDeployment',
name: '模型部署',
path: '/modelDeployment', path: '/modelDeployment',
routes: [ routes: [
{ {
name: '模型列表',
name: '模型部署',
path: '', path: '',
component: './ModelDeployment/List', component: './ModelDeployment/List',
}, },
{ {
name: '镜像详情',
path: ':id',
name: '模型部署详情',
path: 'info/:id',
component: './ModelDeployment/Info', component: './ModelDeployment/Info',
}, },
{ {
name: '创建镜像',
name: '创建推理服务',
path: 'create', path: 'create',
component: './ModelDeployment/Create', component: './ModelDeployment/Create',
}, },
], ],
}, },
{ {
name: 'appsDeployment',
name: '应用开发',
path: '/appsDeployment', path: '/appsDeployment',
routes: [ routes: [
{ {
@@ -248,7 +234,7 @@ export default [
], ],
}, },
{ {
name: 'see',
name: '监控运维',
path: '/see', path: '/see',
routes: [ routes: [
{ {
@@ -259,6 +245,30 @@ export default [
}, },
], ],
}, },
{
name: '资源',
path: '/readad',
routes: [
{
name: '资源',
path: '',
key: 'readad',
component: './missingPage.jsx',
},
],
},
{
name: '组件',
path: '/compent',
routes: [
{
name: '组件',
path: '',
key: 'compent',
component: './missingPage.jsx',
},
],
},
{ {
name: 'monitor', name: 'monitor',
path: '/monitor', path: '/monitor',
@@ -286,6 +296,61 @@ export default [
}, },
], ],
}, },
{
name: '系统管理',
path: '/system',
routes: [
{
path: '',
redirect: '/system/user',
},
{
name: '用户管理',
path: '/system/user',
component: './System/User',
},
{
name: '角色管理',
path: '/system/role',
component: './System/Role',
},
{
name: '定时任务',
path: '/system/job',
component: './Monitor/Job',
},
{
name: '菜单管理',
path: '/system/menu',
component: './System/Menu',
},
{
name: '部门管理',
path: '/system/dept',
component: './System/Dept',
},
{
name: '岗位管理',
path: '/system/post',
component: './System/Post',
},
{
name: '字典管理',
path: '/system/dict',
component: './System/Dict',
},
{
name: '字典数据',
path: '/system/dict-data/index/:id',
component: './System/DictData',
},
{
name: '分配用户',
path: '/system/role-auth/user/:id',
component: './System/Role/authUser',
},
],
},
{ {
name: 'docs', name: 'docs',
path: '/docs', path: '/docs',
@@ -298,4 +363,9 @@ export default [
}, },
], ],
}, },
{
path: '*',
layout: false,
component: './404',
},
]; ];

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

Before After
Width: 313  |  Height: 184  |  Size: 14 kB

react-ui/public/assets/images/fail-icon.png → react-ui/public/assets/images/experiment-status/fail-icon.png View File


react-ui/public/assets/images/omitted-icon.png → react-ui/public/assets/images/experiment-status/omitted-icon.png View File


react-ui/public/assets/images/pending-icon.png → react-ui/public/assets/images/experiment-status/pending-icon.png View File


react-ui/public/assets/images/running-icon.png → react-ui/public/assets/images/experiment-status/running-icon.png View File


react-ui/public/assets/images/success-icon.png → react-ui/public/assets/images/experiment-status/success-icon.png View File


BIN
react-ui/public/assets/images/left-top-logo-1.png View File

Before After
Width: 79  |  Height: 86  |  Size: 4.8 kB

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

Before After
Width: 3360  |  Height: 98  |  Size: 151 kB

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

Before After
Width: 35  |  Height: 28  |  Size: 939 B

BIN
react-ui/public/favicon.ico View File

Before After

+ 26
- 18
react-ui/src/app.tsx View File

@@ -20,18 +20,14 @@ import {
import './styles/menu.less'; import './styles/menu.less';
export { requestConfig as request } from './requestConfig'; export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development'; // const isDev = process.env.NODE_ENV === 'development';
import { type GlobalInitialState } from '@/types';
import { menuItemRender } from '@/utils/menuRender'; import { menuItemRender } from '@/utils/menuRender';
import { gotoLoginPage } from './utils/ui'; import { gotoLoginPage } from './utils/ui';

/** /**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state * @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */ * */
export async function getInitialState(): Promise<{
settings?: Partial<LayoutSettings>;
currentUser?: API.CurrentUser;
loading?: boolean;
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
// console.log('getInitialState');
export async function getInitialState(): Promise<GlobalInitialState> {
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
try { try {
const response = await getUserInfo(); const response = await getUserInfo();
@@ -56,18 +52,20 @@ export async function getInitialState(): Promise<{
fetchUserInfo, fetchUserInfo,
currentUser, currentUser,
settings: defaultSettings as Partial<LayoutSettings>, settings: defaultSettings as Partial<LayoutSettings>,
collapsed: false,
}; };
} }
return { return {
fetchUserInfo, fetchUserInfo,
settings: defaultSettings as Partial<LayoutSettings>, settings: defaultSettings as Partial<LayoutSettings>,
collapsed: false,
}; };
} }


// ProLayout 支持的api https://procomponents.ant.design/components/layout // ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RuntimeConfig['layout'] = ({ initialState }) => { export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
return { return {
rightContentRender: () => <RightContent />,
rightContentRender: false,
waterMarkProps: { waterMarkProps: {
// content: initialState?.currentUser?.nickName, // content: initialState?.currentUser?.nickName,
}, },
@@ -91,7 +89,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
return getRemoteMenu(); return getRemoteMenu();
}, },
}, },
// footerRender: () => <Footer />,
onPageChange: () => { onPageChange: () => {
const { location } = history; const { location } = history;
// 如果没有登录,重定向到 login // 如果没有登录,重定向到 login
@@ -127,14 +124,20 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
// </Link>, // </Link>,
// ] // ]
// : [], // : [],
menuHeaderRender: false,
// 自定义 403 页面 // 自定义 403 页面
// unAccessible: <div>unAccessible</div>, // unAccessible: <div>unAccessible</div>,
// 增加一个 loading 的状态 // 增加一个 loading 的状态
childrenRender: (children) => { childrenRender: (children) => {
// if (initialState?.loading) return <PageLoading />; // if (initialState?.loading) return <PageLoading />;
return <>{children}</>;
return (
<div className="kf-page-container">
<RightContent></RightContent>
<div className="kf-page-container__content">{children}</div>
</div>
);
}, },
collapsedButtonRender: false,
collapsed: initialState?.collapsed,
menuProps: { menuProps: {
onClick: () => { onClick: () => {
// 点击菜单项,删除所有的页面 state 缓存 // 点击菜单项,删除所有的页面 state 缓存
@@ -143,6 +146,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
}, },
}, },
...initialState?.settings, ...initialState?.settings,
logo: require('@/assets/img/logo.png'),
token: { token: {
sider: { sider: {
colorTextMenu: themes['textColor'], colorTextMenu: themes['textColor'],
@@ -233,20 +237,24 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme.components.Tabs = { memo.theme.components.Tabs = {
titleFontSize: 16, titleFontSize: 16,
}; };

memo.theme.components.Form = { memo.theme.components.Form = {
labelColor: 'rgba(29, 29, 32, 0.8);', labelColor: 'rgba(29, 29, 32, 0.8);',
}; };
memo.theme.components.Breadcrumb = {
iconFontSize: parseInt(themes['fontSize']),
linkColor: 'rgba(29, 29, 32, 0.7)',
separatorColor: 'rgba(29, 29, 32, 0.7)',
};


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


// memo.appConfig = {
// message: {
// // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭
// maxCount: 3,
// },
// };
memo.appConfig = {
message: {
// 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭
maxCount: 3,
},
};


return memo; return memo;
}; };

react-ui/public/assets/images/dataset-back.png → react-ui/src/assets/img/dataset-intro-top.png View File


BIN
react-ui/src/assets/img/delete-icon.png View File

Before After
Width: 200  |  Height: 200  |  Size: 3.3 kB Width: 313  |  Height: 184  |  Size: 14 kB

react-ui/public/assets/images/duty-message.png → react-ui/src/assets/img/duty-message.png View File


react-ui/public/assets/images/ai-logo.png → react-ui/src/assets/img/login-ai-logo.png View File


BIN
react-ui/src/assets/img/login-captcha.png View File

Before After
Width: 46  |  Height: 53  |  Size: 1.3 kB

react-ui/public/assets/images/left-back-logo.png → react-ui/src/assets/img/login-left-image.png View File


BIN
react-ui/src/assets/img/login-password.png View File

Before After
Width: 45  |  Height: 53  |  Size: 1.1 kB

BIN
react-ui/src/assets/img/login-user.png View File

Before After
Width: 47  |  Height: 51  |  Size: 1.3 kB

react-ui/public/assets/images/left-top-logo.png → react-ui/src/assets/img/logo.png View File


react-ui/public/assets/images/modal-back.png → react-ui/src/assets/img/modal-back.png View File


react-ui/public/assets/images/pipeline-canvas-back.png → react-ui/src/assets/img/pipeline-canvas-bg.png View File


react-ui/public/assets/images/static-message.png → react-ui/src/assets/img/static-message.png View File


+ 51
- 0
react-ui/src/components/KFBreadcrumb/index.tsx View File

@@ -0,0 +1,51 @@
import { Breadcrumb, type BreadcrumbProps } from 'antd';
import { Link, matchPath, useLocation } from 'umi';
// import routes from '../../../config/config'; // 导入你的路由配置
type Route = {
path: string;
breadcrumb?: string;
routes?: Route[];
redirect?: string;
name?: string;
component?: string;
layout?: boolean;
key?: string;
};

const routes: Route[] = [];

const KFBreadcrumb = () => {
const location = useLocation();

const items: BreadcrumbProps['items'] = [];

// 遍历路由表,生成面包屑数据
const generateBreadcrumbs = (pathname: string, routes: Route[], prefix: string = '') => {
for (const route of routes) {
if (route.redirect || route.layout === false || !route.path || route.path === '*') {
continue;
}
const match = matchPath(
{ path: `${prefix}/${route.path}`, end: route.routes ? false : true },
pathname,
);
if (match) {
items.push({
path: route.path.startsWith('/') ? route.path : `${prefix}/${route.path}`,
title: <Link to={route.path}>{route.breadcrumb}</Link>,
});
}
if (route.routes) {
generateBreadcrumbs(pathname, route.routes, `${prefix}/${route.path}`);
}
}
};

generateBreadcrumbs(location.pathname, routes);

// const itemRender = (route, params, routes, paths) => {};

return <Breadcrumb items={items}></Breadcrumb>;
};

export default KFBreadcrumb;

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

@@ -1,7 +1,7 @@
.kf-modal { .kf-modal {
.ant-modal-content { .ant-modal-content {
padding: 40px 67px; padding: 40px 67px;
background-image: url(/assets/images/modal-back.png);
background-image: url(@/assets/img/modal-back.png);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: top center; background-position: top center;
background-size: 100%; background-size: 100%;


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

@@ -4,6 +4,7 @@
padding: 4px 11px; padding: 4px 11px;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 6px; border-radius: 6px;
cursor: pointer;


&:hover { &:hover {
border-color: @primary-color; border-color: @primary-color;


+ 10
- 0
react-ui/src/components/RightContent/index.less View File

@@ -0,0 +1,10 @@
.right-content {
display: flex;
gap: 8px;
align-items: center;
height: 55px;
margin-right: -10px;
padding: 0 16px;
background-color: white;
border-bottom: 1px solid #e9edf0;
}

+ 26
- 29
react-ui/src/components/RightContent/index.tsx View File

@@ -1,46 +1,31 @@
import { useEmotionCss } from '@ant-design/use-emotion-css';
import { useModel } from '@umijs/max'; import { useModel } from '@umijs/max';
import React from 'react'; import React from 'react';
// import KFBreadcrumb from '../KFBreadcrumb';
import KFIcon from '@/components/KFIcon';
import { ProBreadcrumb } from '@ant-design/pro-components';
import { Button } from 'antd';
import Avatar from './AvatarDropdown'; import Avatar from './AvatarDropdown';
import styles from './index.less';
// import { SelectLang } from '@umijs/max'; // import { SelectLang } from '@umijs/max';


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


const GlobalHeaderRight: React.FC = () => { const GlobalHeaderRight: React.FC = () => {
const className = useEmotionCss(() => {
return {
display: 'flex',
height: '48px',
marginLeft: 'auto',
overflow: 'hidden',
gap: 8,
};
});

// const actionClassName = useEmotionCss(({ token }) => {
// return {
// display: 'flex',
// float: 'right',
// height: '48px',
// marginLeft: 'auto',
// overflow: 'hidden',
// cursor: 'pointer',
// padding: '0 12px',
// borderRadius: token.borderRadius,
// '&:hover': {
// backgroundColor: token.colorBgTextHover,
// },
// };
// });

const { initialState } = useModel('@@initialState');
const { initialState, setInitialState } = useModel('@@initialState');


if (!initialState || !initialState.settings) { if (!initialState || !initialState.settings) {
return null; return null;
} }


const handleMenuCollapse = () => {
setInitialState((preInitialState) => ({
...preInitialState,
collapsed: !preInitialState?.collapsed,
}));
};

return ( return (
<div className={className}>
<div className={styles['right-content']}>
{/* <span {/* <span
className={actionClassName} className={actionClassName}
onClick={() => { onClick={() => {
@@ -49,6 +34,18 @@ const GlobalHeaderRight: React.FC = () => {
> >
<QuestionCircleOutlined /> <QuestionCircleOutlined />
</span> */} </span> */}

<Button
type="text"
style={{ marginRight: '4px' }}
icon={<KFIcon type="icon-collapsed" font={18} style={{ verticalAlign: '-3px' }} />}
onClick={handleMenuCollapse}
></Button>

<ProBreadcrumb></ProBreadcrumb>

{/* <KFBreadcrumb /> */}

<Avatar menu={true} /> <Avatar menu={true} />
{/* <SelectLang className={actionClassName} /> */} {/* <SelectLang className={actionClassName} /> */}
</div> </div>


+ 20
- 1
react-ui/src/global.less View File

@@ -76,7 +76,7 @@ body {
} }
.ant-pro-layout .ant-layout-sider.ant-pro-sider { .ant-pro-layout .ant-layout-sider.ant-pro-sider {
height: 100vh; height: 100vh;
padding-top: 56px;
// padding-top: 56px;
} }
.ant-pro-layout .ant-pro-layout-container { .ant-pro-layout .ant-pro-layout-container {
height: 100vh; height: 100vh;
@@ -133,3 +133,22 @@ ol {
} }
} }
} }

.kf-page-container {
height: 100%;

&__content {
height: calc(100% - 55px);
}
}

.kf-menu-collapsed {
position: fixed;
top: 0;
left: 0;
z-index: 999;
}

input:-webkit-autofill {
transition: background-color 5000s ease-in-out 0s;
}

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


+ 9
- 1
react-ui/src/overrides.less View File

@@ -126,7 +126,7 @@
.ant-modal-confirm { .ant-modal-confirm {
.ant-modal-content { .ant-modal-content {
padding: 40px 67px; padding: 40px 67px;
background-image: url(/assets/images/modal-back.png);
background-image: url(@/assets/img/modal-back.png);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: top center; background-position: top center;
background-size: 100%; background-size: 100%;
@@ -179,3 +179,11 @@
transition: color 0s; transition: color 0s;
} }
} }

.ant-pro-sider-collapsed-button {
inset-block-start: 65px !important;
}

.ant-pro-layout .ant-pro-sider-logo > a > h1 {
margin-inline-start: 12px;
}

+ 1
- 1
react-ui/src/pages/Dataset/components/ResourceIntro/index.less View File

@@ -6,7 +6,7 @@
height: 110px; height: 110px;
margin-bottom: 10px; margin-bottom: 10px;
padding: 20px 30px 0; padding: 20px 30px 0;
background-image: url(/assets/images/dataset-back.png);
background-image: url(@/assets/img/dataset-intro-top.png);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: top center; background-position: top center;
background-size: 100% 100%; background-size: 100% 100%;


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

@@ -131,7 +131,7 @@ function ResourceList(
activeTag: dataTag, activeTag: dataTag,
}); });
const prefix = config.prefix; const prefix = config.prefix;
navigate(`/dataset/${prefix}/${record.id}`);
navigate(`/dataset/${prefix}/info/${record.id}`);
}; };


// 分页切换 // 分页切换


+ 2
- 1
react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx View File

@@ -84,7 +84,7 @@ function EditorCreate() {


return ( return (
<div className={styles['editor-create']}> <div className={styles['editor-create']}>
<PageTitle title="创建编辑器"></PageTitle>
<PageTitle title="创建开发环境"></PageTitle>
<div className={styles['editor-create__content']}> <div className={styles['editor-create__content']}>
<div> <div>
<Form <Form
@@ -96,6 +96,7 @@ function EditorCreate() {
initialValues={{ computing_resource: ComputingResourceType.GPU }} initialValues={{ computing_resource: ComputingResourceType.GPU }}
onFinish={handleSubmit} onFinish={handleSubmit}
size="large" size="large"
autoComplete="off"
> >
<SubAreaTitle <SubAreaTitle
title="基本信息" title="基本信息"


+ 3
- 2
react-ui/src/pages/DevelopmentEnvironment/List/index.tsx View File

@@ -213,7 +213,7 @@ function EditorList() {
icon={<KFIcon type="icon-tiaoshi" />} icon={<KFIcon type="icon-tiaoshi" />}
onClick={() => startEditor(record.id)} onClick={() => startEditor(record.id)}
> >
再次调试
启动
</Button> </Button>
)} )}
<ConfigProvider <ConfigProvider
@@ -247,7 +247,7 @@ function EditorList() {
onClick={createEditor} onClick={createEditor}
icon={<KFIcon type="icon-xinjian2" />} icon={<KFIcon type="icon-xinjian2" />}
> >
创建编辑器
创建开发环境
</Button> </Button>
<Button <Button
style={{ marginLeft: '20px' }} style={{ marginLeft: '20px' }}
@@ -268,6 +268,7 @@ function EditorList() {
total: total, total: total,
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showQuickJumper: true,
showTotal: () => `共${total}条`,
}} }}
onChange={handleTableChange} onChange={handleTableChange}
rowKey="id" rowKey="id"


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

@@ -438,6 +438,13 @@ function ExperimentText() {


// 绑定事件 // 绑定事件
const bindEvents = () => { const bindEvents = () => {
const closeDrawer = () => {
closePropsDrawer();
setTimeout(() => {
setExperimentNodeData(null);
}, 200);
};

graph.on('node:click', (e) => { graph.on('node:click', (e) => {
if (e.target.get('name') !== 'anchor-point' && e.item) { if (e.target.get('name') !== 'anchor-point' && e.item) {
const model = e.item.getModel(); const model = e.item.getModel();
@@ -452,10 +459,10 @@ function ExperimentText() {
graph.setItemState(e.item, 'hover', false); graph.setItemState(e.item, 'hover', false);
}); });
graph.on('canvas:click', (e) => { graph.on('canvas:click', (e) => {
closePropsDrawer();
setTimeout(() => {
setExperimentNodeData(null);
}, 200);
closeDrawer();
});
graph.on('edge:click', (e) => {
closeDrawer();
}); });
}; };




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

@@ -27,7 +27,7 @@
width: 100%; width: 100%;
height: calc(100% - 56px); height: calc(100% - 56px);
background-color: @background-color; background-color: @background-color;
background-image: url(/assets/images/pipeline-canvas-back.png);
background-image: url(@/assets/img/pipeline-canvas-bg.png);
background-size: 100% 100%; background-size: 100% 100%;
} }




+ 16
- 4
react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx View File

@@ -49,7 +49,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
className={styles['experiment-parameter']} className={styles['experiment-parameter']}
> >
<div className={styles['experiment-parameter__title']}> <div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/static-message.png')}
title="基本信息"
></SubAreaTitle>
</div> </div>
<Form.Item <Form.Item
label="任务名称" label="任务名称"
@@ -76,7 +79,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>
<div className={styles['experiment-parameter__title']}> <div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div> </div>
<Form.Item <Form.Item
label="镜像" label="镜像"
@@ -128,7 +134,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
</Form.Item> </Form.Item>
))} ))}
<div className={styles['experiment-parameter__title']}> <div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="输入参数"
></SubAreaTitle>
</div> </div>
{inParametersList.map((item) => ( {inParametersList.map((item) => (
<Form.Item <Form.Item
@@ -145,7 +154,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
</Form.Item> </Form.Item>
))} ))}
<div className={styles['experiment-parameter__title']}> <div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="输出参数"
></SubAreaTitle>
</div> </div>
{outParametersList.map((item) => ( {outParametersList.map((item) => (
<Form.Item <Form.Item


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

@@ -30,7 +30,7 @@ import { experimentStatusInfo } from './status';
const timerIds = new Map(); const timerIds = new Map();


function Experiment() { function Experiment() {
const navgite = useNavigate();
const navigate = useNavigate();
const [experimentList, setExperimentList] = useState([]); const [experimentList, setExperimentList] = useState([]);
const [workflowList, setWorkflowList] = useState([]); const [workflowList, setWorkflowList] = useState([]);
const [queryFlow, setQueryFlow] = useState({ const [queryFlow, setQueryFlow] = useState({
@@ -275,12 +275,12 @@ function Experiment() {
// 跳转到流水线 // 跳转到流水线
const gotoPipeline = (e, record) => { const gotoPipeline = (e, record) => {
e.stopPropagation(); e.stopPropagation();
navgite({ pathname: `/pipeline/template/${record.workflow_id}` });
navigate({ pathname: `/pipeline/template/info/${record.workflow_id}` });
}; };


// 跳转到实验实例详情 // 跳转到实验实例详情
const gotoInstanceInfo = (item, record) => { const gotoInstanceInfo = (item, record) => {
navgite({ pathname: `/pipeline/experiment/${record.workflow_id}/${item.id}` });
navigate({ pathname: `/pipeline/experiment/instance/${record.workflow_id}/${item.id}` });
}; };


// 处理 TensorBoard 操作 // 处理 TensorBoard 操作
@@ -327,7 +327,7 @@ function Experiment() {
}, },
], ],
onClick: ({ key }) => { onClick: ({ key }) => {
navgite(`/pipeline/experiment/compare?type=${key}&id=${experimentId}`);
navigate(`/pipeline/experiment/compare?type=${key}&id=${experimentId}`);
}, },
}; };
}; };


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

@@ -8,7 +8,7 @@
height: 49px; height: 49px;
margin-bottom: 10px; margin-bottom: 10px;
padding-right: 30px; padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
background-image: url(@/assets/img/page-title-bg.png);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: top center; background-position: top center;
background-size: 100% 100%; background-size: 100% 100%;


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

@@ -11,41 +11,41 @@ export const experimentStatusInfo: Record<ExperimentStatus, ExperimentStatusInfo
Running: { Running: {
label: '运行中', label: '运行中',
color: themes.primaryColor, color: themes.primaryColor,
icon: '/assets/images/running-icon.png',
icon: '/assets/images/experiment-status/running-icon.png',
}, },
Succeeded: { Succeeded: {
label: '成功', label: '成功',
color: themes.successColor, color: themes.successColor,
icon: '/assets/images/success-icon.png',
icon: '/assets/images/experiment-status/success-icon.png',
}, },
Pending: { Pending: {
label: '等待中', label: '等待中',
color: themes.pendingColor, color: themes.pendingColor,
icon: '/assets/images/pending-icon.png',
icon: '/assets/images/experiment-status/pending-icon.png',
}, },
Failed: { Failed: {
label: '失败', label: '失败',
color: themes.errorColor, color: themes.errorColor,
icon: '/assets/images/fail-icon.png',
icon: '/assets/images/experiment-status/fail-icon.png',
}, },
Error: { Error: {
label: '错误', label: '错误',
color: themes.errorColor, color: themes.errorColor,
icon: '/assets/images/fail-icon.png',
icon: '/assets/images/experiment-status/fail-icon.png',
}, },
Terminated: { Terminated: {
label: '终止', label: '终止',
color: themes.abortColor, color: themes.abortColor,
icon: '/assets/images/omitted-icon.png',
icon: '/assets/images/experiment-status/omitted-icon.png',
}, },
Skipped: { Skipped: {
label: '未执行', label: '未执行',
color: themes.abortColor, color: themes.abortColor,
icon: '/assets/images/omitted-icon.png',
icon: '/assets/images/experiment-status/omitted-icon.png',
}, },
Omitted: { Omitted: {
label: '未执行', label: '未执行',
color: themes.abortColor, color: themes.abortColor,
icon: '/assets/images/omitted-icon.png',
icon: '/assets/images/experiment-status/omitted-icon.png',
}, },
}; };

+ 1
- 0
react-ui/src/pages/Mirror/Create/index.tsx View File

@@ -136,6 +136,7 @@ function MirrorCreate() {
initialValues={{ upload_type: CommonTabKeys.Public }} initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit} onFinish={handleSubmit}
size="large" size="large"
autoComplete="off"
> >
<SubAreaTitle <SubAreaTitle
title="基本信息" title="基本信息"


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

@@ -125,7 +125,7 @@ function MirrorList() {


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


+ 1
- 1
react-ui/src/pages/Model/components/ModelEvolution/index.less View File

@@ -12,7 +12,7 @@
&__graph { &__graph {
height: calc(100% - 92px); height: calc(100% - 92px);
background-color: @background-color; background-color: @background-color;
background-image: url(/assets/images/pipeline-canvas-back.png);
background-image: url(@/assets/img/pipeline-canvas-bg.png);
background-size: 100% 100%; background-size: 100% 100%;
} }
} }

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

@@ -16,7 +16,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
const gotoExperimentPage = () => { const gotoExperimentPage = () => {
if (data.train_task?.ins_id) { if (data.train_task?.ins_id) {
const { origin } = location; const { origin } = location;
const url = `${origin}/pipeline/experiment/${data.workflow_id}/${data.train_task.ins_id}`;
const url = `${origin}/pipeline/experiment/instance/${data.workflow_id}/${data.train_task.ins_id}`;
window.open(url, '_blank'); window.open(url, '_blank');
} }
}; };
@@ -28,7 +28,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
if (data.current_model_id === resourceId) { if (data.current_model_id === resourceId) {
onVersionChange?.(data.version); onVersionChange?.(data.version);
} else { } else {
const path = `/dataset/model/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`;
const path = `/dataset/model/info/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`;
navigate(path); navigate(path);
} }
}; };
@@ -100,7 +100,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
function DatasetInfo({ data }: { data: TrainDataset }) { function DatasetInfo({ data }: { data: TrainDataset }) {
const gotoDatasetPage = () => { const gotoDatasetPage = () => {
const { origin } = location; const { origin } = location;
const url = `${origin}/dataset/dataset/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`;
const url = `${origin}/dataset/dataset/info/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`;
window.open(url, '_blank'); window.open(url, '_blank');
}; };




+ 1
- 0
react-ui/src/pages/ModelDeployment/Create/index.tsx View File

@@ -141,6 +141,7 @@ function ModelDeploymentCreate() {
initialValues={{ upload_type: CommonTabKeys.Public }} initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit} onFinish={handleSubmit}
size="large" size="large"
autoComplete="off"
> >
<SubAreaTitle <SubAreaTitle
title="基本信息" title="基本信息"


+ 1
- 1
react-ui/src/pages/ModelDeployment/List/index.tsx View File

@@ -162,7 +162,7 @@ function ModelDeployment() {
searchStatus, searchStatus,
}); });


navigate(`/modelDeployment/${record.service_id}`);
navigate(`/modelDeployment/info/${record.service_id}`);
}; };


// 分页切换 // 分页切换


+ 3
- 3
react-ui/src/pages/Monitor/Job/index.tsx View File

@@ -232,7 +232,7 @@ const JobTableList: React.FC = () => {
type="link" type="link"
size="small" size="small"
key="edit" key="edit"
icon=<EditOutlined />
icon={<EditOutlined />}
hidden={!access.hasPerms('monitor:job:edit')} hidden={!access.hasPerms('monitor:job:edit')}
onClick={() => { onClick={() => {
setModalVisible(true); setModalVisible(true);
@@ -246,7 +246,7 @@ const JobTableList: React.FC = () => {
size="small" size="small"
danger danger
key="batchRemove" key="batchRemove"
icon=<DeleteOutlined />
icon={<DeleteOutlined />}
hidden={!access.hasPerms('monitor:job:remove')} hidden={!access.hasPerms('monitor:job:remove')}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
@@ -319,7 +319,7 @@ const JobTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.Monitor.Job> <ProTable<API.Monitor.Job>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/Monitor/JobLog/index.tsx View File

@@ -217,7 +217,7 @@ const JobLogTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.Monitor.JobLog> <ProTable<API.Monitor.JobLog>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 2
- 2
react-ui/src/pages/Monitor/Online/index.tsx View File

@@ -3,7 +3,7 @@ import { DeleteOutlined } from '@ant-design/icons';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { FormattedMessage, useAccess, useIntl } from '@umijs/max'; import { FormattedMessage, useAccess, useIntl } from '@umijs/max';
import type { FormInstance } from 'antd'; import type { FormInstance } from 'antd';
import { Button, message, Modal } from 'antd';
import { Button, Modal, message } from 'antd';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';


/* * /* *
@@ -102,7 +102,7 @@ const OnlineUserTableList: React.FC = () => {
size="small" size="small"
danger danger
key="batchRemove" key="batchRemove"
icon=<DeleteOutlined />
icon={<DeleteOutlined />}
hidden={!access.hasPerms('monitor:online:forceLogout')} hidden={!access.hasPerms('monitor:online:forceLogout')}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({


react-ui/src/pages/Pipeline/editPipeline/index.jsx → react-ui/src/pages/Pipeline/Info/index.jsx View File

@@ -2,17 +2,16 @@ import KFIcon from '@/components/KFIcon';
import { useStateRef, useVisible } from '@/hooks'; import { useStateRef, useVisible } from '@/hooks';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { fittingString } from '@/utils';
import { fittingString, s8 } from '@/utils';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import G6 from '@antv/g6'; import G6 from '@antv/g6';
import { useNavigate, useParams } from '@umijs/max';
import { App, Button } from 'antd'; import { App, Button } from 'antd';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { s8 } from '../../../utils';
import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; import GlobalParamsDrawer from '../components/GlobalParamsDrawer';
import ModelMenu from '../components/ModelMenu'; import ModelMenu from '../components/ModelMenu';
import Props from '../components/PipelineNodeDrawer';
import styles from './index.less'; import styles from './index.less';
import Props from './props';
import { findAllParentNodes } from './utils'; import { findAllParentNodes } from './utils';


let graph = null; let graph = null;

react-ui/src/pages/Pipeline/editPipeline/index.less → react-ui/src/pages/Pipeline/Info/index.less View File

@@ -23,7 +23,7 @@
width: 100%; width: 100%;
height: calc(100% - 52px); height: calc(100% - 52px);
background-color: @background-color; background-color: @background-color;
background-image: url(/assets/images/pipeline-canvas-back.png);
background-image: url(@/assets/img/pipeline-canvas-bg.png);
background-size: 100% 100%; background-size: 100% 100%;
} }
} }

react-ui/src/pages/Pipeline/editPipeline/utils.tsx → react-ui/src/pages/Pipeline/Info/utils.tsx View File


react-ui/src/pages/Pipeline/editPipeline/props.less → react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.less View File


react-ui/src/pages/Pipeline/editPipeline/props.tsx → react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -4,6 +4,7 @@ import ParameterSelect from '@/components/ParameterSelect';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums'; import { CommonTabKeys } from '@/enums';
import { useComputingResource } from '@/hooks/resource'; import { useComputingResource } from '@/hooks/resource';
import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils';
import { import {
PipelineGlobalParam, PipelineGlobalParam,
PipelineNodeModel, PipelineNodeModel,
@@ -16,13 +17,12 @@ import { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd'; import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd';
import { NamePath } from 'antd/es/form/interface'; import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState } from 'react';
import PropsLabel from '../components/PropsLabel';
import PropsLabel from '../PropsLabel';
import ResourceSelectorModal, { import ResourceSelectorModal, {
ResourceSelectorType, ResourceSelectorType,
selectorTypeConfig, selectorTypeConfig,
} from '../components/ResourceSelectorModal';
import styles from './props.less';
import { canInput, createMenuItems } from './utils';
} from '../ResourceSelectorModal';
import styles from './index.less';
const { TextArea } = Input; const { TextArea } = Input;


type PipelineNodeParameterProps = { type PipelineNodeParameterProps = {
@@ -299,7 +299,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
scrollToFirstError scrollToFirstError
> >
<div className={styles['pipeline-drawer__title']}> <div className={styles['pipeline-drawer__title']}>
<SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/static-message.png')}
title="基本信息"
></SubAreaTitle>
</div> </div>
<Form.Item <Form.Item
label="任务名称" label="任务名称"
@@ -326,7 +329,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>
<div className={styles['pipeline-drawer__title']}> <div className={styles['pipeline-drawer__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div> </div>
<Form.Item label="镜像" required> <Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}> <div className={styles['pipeline-drawer__ref-row']}>
@@ -436,7 +442,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
</Form.Item> </Form.Item>
))} ))}
<div className={styles['pipeline-drawer__title']}> <div className={styles['pipeline-drawer__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="输入参数"
></SubAreaTitle>
</div> </div>
{inParametersList.map((item) => ( {inParametersList.map((item) => (
<Form.Item <Form.Item
@@ -469,7 +478,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
</Form.Item> </Form.Item>
))} ))}
<div className={styles['pipeline-drawer__title']}> <div className={styles['pipeline-drawer__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="输出参数"
></SubAreaTitle>
</div> </div>
{outParametersList.map((item) => ( {outParametersList.map((item) => (
<Form.Item <Form.Item

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

@@ -21,7 +21,7 @@ import Styles from './index.less';
const { TextArea } = Input; const { TextArea } = Input;
const Pipeline = () => { const Pipeline = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const navgite = useNavigate();
const navigate = useNavigate();


const [formId, setFormId] = useState(null); const [formId, setFormId] = useState(null);
const [dialogTitle, setDialogTitle] = useState('新建流水线'); const [dialogTitle, setDialogTitle] = useState('新建流水线');
@@ -43,7 +43,7 @@ const Pipeline = () => {
}; };
const routeToEdit = (e, record) => { const routeToEdit = (e, record) => {
e.stopPropagation(); e.stopPropagation();
navgite({ pathname: `/pipeline/template/${record.id}` });
navigate({ pathname: `/pipeline/template/info/${record.id}` });
}; };
const showModal = () => { const showModal = () => {
form.resetFields(); form.resetFields();
@@ -66,7 +66,7 @@ const Pipeline = () => {
setIsModalOpen(false); setIsModalOpen(false);
message.success('新建成功'); message.success('新建成功');
if (ret.code === 200) { if (ret.code === 200) {
navgite({ pathname: `/pipeline/template/${ret.data.id}` });
navigate({ pathname: `/pipeline/template/info/${ret.data.id}` });
} }
}); });
} }


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

@@ -6,7 +6,7 @@
height: 49px; height: 49px;
margin-bottom: 10px; margin-bottom: 10px;
padding-right: 30px; padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
background-image: url(@/assets/img/page-title-bg.png);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: top left; background-position: top left;
background-size: 100% 100%; background-size: 100% 100%;


+ 1
- 1
react-ui/src/pages/System/Config/index.tsx View File

@@ -258,7 +258,7 @@ const ConfigTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.Config> <ProTable<API.System.Config>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Dept/index.tsx View File

@@ -234,7 +234,7 @@ const DeptTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.Dept> <ProTable<API.System.Dept>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Dict/index.tsx View File

@@ -259,7 +259,7 @@ const DictTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.DictType> <ProTable<API.System.DictType>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/DictData/index.tsx View File

@@ -303,7 +303,7 @@ const DictDataTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.DictData> <ProTable<API.System.DictData>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Logininfor/index.tsx View File

@@ -180,7 +180,7 @@ const LogininforTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.Monitor.Logininfor> <ProTable<API.Monitor.Logininfor>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Menu/index.tsx View File

@@ -221,7 +221,7 @@ const MenuTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.Menu> <ProTable<API.System.Menu>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Notice/index.tsx View File

@@ -259,7 +259,7 @@ const NoticeTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.Notice> <ProTable<API.System.Notice>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Operlog/index.tsx View File

@@ -227,7 +227,7 @@ const OperlogTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.Monitor.Operlog> <ProTable<API.Monitor.Operlog>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Post/index.tsx View File

@@ -225,7 +225,7 @@ const PostTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.Post> <ProTable<API.System.Post>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 1
- 1
react-ui/src/pages/System/Role/authUser.tsx View File

@@ -163,7 +163,7 @@ const AuthUserTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.User> <ProTable<API.System.User>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({


+ 3
- 3
react-ui/src/pages/System/Role/index.tsx View File

@@ -261,7 +261,7 @@ const RoleTableList: React.FC = () => {
type="link" type="link"
size="small" size="small"
key="edit" key="edit"
icon=<EditOutlined />
icon={<EditOutlined />}
hidden={!access.hasPerms('system:role:edit')} hidden={!access.hasPerms('system:role:edit')}
onClick={() => { onClick={() => {
getRoleMenuList(record.roleId).then((res) => { getRoleMenuList(record.roleId).then((res) => {
@@ -288,7 +288,7 @@ const RoleTableList: React.FC = () => {
size="small" size="small"
danger danger
key="batchRemove" key="batchRemove"
icon=<DeleteOutlined />
icon={<DeleteOutlined />}
hidden={!access.hasPerms('system:role:remove')} hidden={!access.hasPerms('system:role:remove')}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
@@ -360,7 +360,7 @@ const RoleTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
{contextHolder} {contextHolder}
<div style={{ width: '100%', float: 'right' }}> <div style={{ width: '100%', float: 'right' }}>
<ProTable<API.System.Role> <ProTable<API.System.Role>


+ 3
- 3
react-ui/src/pages/System/User/index.tsx View File

@@ -286,7 +286,7 @@ const UserTableList: React.FC = () => {
type="link" type="link"
size="small" size="small"
key="edit" key="edit"
icon=<EditOutlined />
icon={<EditOutlined />}
hidden={!access.hasPerms('system:user:edit')} hidden={!access.hasPerms('system:user:edit')}
onClick={async () => { onClick={async () => {
fetchUserInfo(record.userId); fetchUserInfo(record.userId);
@@ -302,7 +302,7 @@ const UserTableList: React.FC = () => {
type="link" type="link"
size="small" size="small"
danger danger
icon=<DeleteOutlined />
icon={<DeleteOutlined />}
key="batchRemove" key="batchRemove"
hidden={!access.hasPerms('system:user:remove')} hidden={!access.hasPerms('system:user:remove')}
onClick={async () => { onClick={async () => {
@@ -365,7 +365,7 @@ const UserTableList: React.FC = () => {
]; ];


return ( return (
<PageContainer>
<PageContainer header={{ breadcrumb: {} }}>
{contextHolder} {contextHolder}
<Row gutter={[16, 24]}> <Row gutter={[16, 24]}>
<Col lg={6} md={24}> <Col lg={6} md={24}>


+ 128
- 435
react-ui/src/pages/User/Login/index.tsx View File

@@ -1,187 +1,48 @@
import { clearSessionToken, setSessionToken } from '@/access'; import { clearSessionToken, setSessionToken } from '@/access';
import { getFakeCaptcha } from '@/services/ant-design-pro/login';
import { getCaptchaImg, login } from '@/services/system/auth'; import { getCaptchaImg, login } from '@/services/system/auth';
import {
AlipayCircleOutlined,
LockOutlined,
MobileOutlined,
TaobaoCircleOutlined,
UserOutlined,
WeiboCircleOutlined,
} from '@ant-design/icons';
import {
LoginForm,
ProFormCaptcha,
ProFormCheckbox,
ProFormText,
} from '@ant-design/pro-components';
import { useEmotionCss } from '@ant-design/use-emotion-css';
import { FormattedMessage, SelectLang, history, useIntl, useModel } from '@umijs/max';
import { Alert, Col, Image, Row, message } from 'antd';
import { loginPasswordKey, loginUserKey, rememberPasswordKey } from '@/utils/localStorage';
import { to } from '@/utils/promise';
import { history, useModel } from '@umijs/max';
import { Button, Checkbox, Flex, Form, Image, Input, message } from 'antd';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import styles from './login.less'; import styles from './login.less';


const ActionIcons = () => {
const langClassName = useEmotionCss(({ token }) => {
return {
marginLeft: '8px',
color: 'rgba(0, 0, 0, 0.2)',
fontSize: '24px',
verticalAlign: 'middle',
cursor: 'pointer',
transition: 'color 0.3s',
'&:hover': {
color: token.colorPrimaryActive,
},
};
});

return (
<>
<AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} />
<TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} />
<WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} />
</>
);
};

const Lang = () => {
const langClassName = useEmotionCss(({ token }) => {
return {
width: 42,
height: 42,
lineHeight: '42px',
position: 'fixed',
right: 16,
borderRadius: token.borderRadius,
':hover': {
backgroundColor: token.colorBgTextHover,
},
};
});

const LoginInputPrefix = ({ icon }: { icon: string }) => {
return ( return (
<div className={langClassName} data-lang>
{SelectLang && <SelectLang />}
<div className={styles['login-input-prefix']}>
<img className={styles['login-input-prefix__icon']} src={icon} alt="" />
<div className={styles['login-input-prefix__line']}></div>
</div> </div>
); );
}; };


const LoginMessage: React.FC<{
content: string;
}> = ({ content }) => {
return (
<Alert
style={{
marginBottom: 24,
}}
message={content}
type="error"
showIcon
/>
);
};

const Login: React.FC = () => { const Login: React.FC = () => {
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({ code: 200 });
const [type, setType] = useState<string>('account');
const { initialState, setInitialState } = useModel('@@initialState'); const { initialState, setInitialState } = useModel('@@initialState');
const [captchaCode, setCaptchaCode] = useState<string>(''); const [captchaCode, setCaptchaCode] = useState<string>('');
const [uuid, setUuid] = useState<string>(''); const [uuid, setUuid] = useState<string>('');
const [form] = Form.useForm();
const [usernameReadOnly, setUsernameReadOnly] = useState<boolean>(true);


const containerClassName = useEmotionCss(() => {
return {
display: 'flex',
height: '100vh',
backgroundColor: '#fff',
backgroundSize: '100% 100%',
};
});
const containerLeftBox = useEmotionCss(() => {
return {
background: 'linear-gradient(180deg,#e2ecff 0%,#f6fafe 100%)',
width: '43%',
height: '100%',
position: 'relative',
};
});
const leftTopBoX = useEmotionCss(() => {
return {
display: 'flex',
position: 'absolute',
top: '55px',
left: '60px',
fontWeight: '500',
color: '#1d1d20',
fontSize: '36px',
fontFamily: 'Alibaba',
};
});
const centerTitleBoX = useEmotionCss(() => {
return {
display: 'flex',
position: 'absolute',
top: '163px',
left: '50%',
transform: 'translateX(-50%)',
fontWeight: '500',
color: '#111111',
fontSize: '45px',
fontFamily: 'Alibaba',
};
});
const centerMessage = useEmotionCss(() => {
return {
display: 'flex',
position: 'absolute',
top: '242px',
left: '50%',
transform: 'translateX(-50%)',
fontWeight: '500',
color: '#606b7a',
fontSize: '26px',
fontFamily: 'Alibaba',
letterSpacing: '8px',
};
});
const containerRightBox = useEmotionCss(() => {
return {
background: '#fff',
width: '57%',
height: '100%',
position: 'relative',
};
});
const rightTopTitle = useEmotionCss(() => {
return {
display: 'flex',
position: 'absolute',
alignItems: 'center',
top: '147px',
left: '230px',
fontFamily: 'Alibaba',
};
});
const containerLoginForm = useEmotionCss(() => {
return {
width: '640px',
position: 'absolute',
background: '#ffffff',
borderRadius: '10px',
boxShadow: '0px 3px 20px rgba(153, 153, 153, 0.16)',
padding: '62px 36px 45px 36px',
left: '230px',
top: '220px',
};
});
const intl = useIntl();
useEffect(() => {
getCaptchaCode();
const autoLogin = localStorage.getItem(rememberPasswordKey) ?? 'false';
if (autoLogin === 'true') {
const username = localStorage.getItem(loginUserKey);
const password = localStorage.getItem(loginPasswordKey);
form.setFieldsValue({ username: username, password: password, autoLogin: true });
} else {
form.setFieldsValue({ username: '', password: '', autoLogin: false });
}
}, []);


const getCaptchaCode = async () => { const getCaptchaCode = async () => {
const response = await getCaptchaImg();
const imgdata = `data:image/png;base64,${response.img}`;
setCaptchaCode(imgdata);
setUuid(response.uuid);
const [res] = await to(getCaptchaImg());
if (res) {
const imgdata = `data:image/png;base64,${res.img}`;
setCaptchaCode(imgdata);
setUuid(res.uuid);
}
}; };


const fetchUserInfo = async () => { const fetchUserInfo = async () => {
@@ -196,302 +57,134 @@ const Login: React.FC = () => {
} }
}; };


// 登录
const handleSubmit = async (values: API.LoginParams) => { const handleSubmit = async (values: API.LoginParams) => {
try {
// 登录
const response = await login({ ...values, uuid });
if (response.code === 200) {
const defaultLoginSuccessMessage = intl.formatMessage({
id: 'pages.login.success',
defaultMessage: '登录成功!',
});
const current = new Date();
const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
setSessionToken(response.data?.access_token, response.data?.access_token, expireTime);
message.success(defaultLoginSuccessMessage);
await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams;
history.push(urlParams.get('redirect') || '/');
return;
const [response] = await to(login({ ...values, uuid }));
if (response && response.data) {
const current = new Date();
const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
const { access_token } = response.data;
setSessionToken(access_token, access_token, expireTime);
message.success('登录成功!');

localStorage.setItem(rememberPasswordKey, values.autoLogin ? 'true' : 'false');
if (values.autoLogin) {
localStorage.setItem(loginUserKey, values.username ?? '');
localStorage.setItem(loginPasswordKey, values.password ?? '');
} else { } else {
clearSessionToken();
// 如果失败去设置用户错误信息
setUserLoginState({ ...response, type });
getCaptchaCode();
localStorage.removeItem(loginUserKey);
localStorage.removeItem(loginPasswordKey);
} }
} catch (error) {

await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams;
history.push(urlParams.get('redirect') || '/');
} else {
clearSessionToken();
getCaptchaCode(); getCaptchaCode();
} }
}; };
const { code } = userLoginState;
const loginType = type;

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


return ( return (
<div className={containerClassName}>
<div className={containerLeftBox}>
<div className={leftTopBoX}>
<div className={styles['user-login']}>
<div className={styles['user-login__left']}>
<div className={styles['user-login__left__top']}>
<img <img
src="/assets/images/left-top-logo.png"
style={{ height: '42px', marginRight: '10px' }}
src={require('@/assets/img/logo.png')}
style={{ width: '32px', marginRight: '12px' }}
alt="" alt=""
/> />
智能材料科研平台 智能材料科研平台
</div> </div>
<div className={centerTitleBoX}>
<span style={{ whiteSpace: 'nowrap' }}>智能材料科研平台</span>

<div className={styles['user-login__left__title']}>
<span>智能材料科研平台</span>
<img <img
src="/assets/images/ai-logo.png"
style={{ height: '47px', marginTop: '-10px' }}
src={require('@/assets/img/login-ai-logo.png')}
className={styles['user-login__left__title__img']}
alt="" alt=""
/> />
</div> </div>
<div className={centerMessage}>
<span style={{ whiteSpace: 'nowrap' }}>大语言模型运维 统一管理平台</span>
<div className={styles['user-login__left__message']}>
<span>大语言模型运维 统一管理平台</span>
</div> </div>
<img <img
src="/assets/images/left-back-logo.png"
style={{
width: '90%',
position: 'absolute',
top: '326px',
left: '50%',
transform: 'translateX(-50%)',
}}
className={styles['user-login__left__bottom-img']}
src={require('@/assets/img/login-left-image.png')}
alt="" alt=""
/> />
</div> </div>
<div className={containerRightBox}>
<div className={rightTopTitle}>
<span style={{ color: '#111111', fontSize: '36px' }}>hello~</span>
<span style={{ color: '#606b7a', fontSize: '32px', marginLeft: '10px' }}>欢迎登陆</span>
<span style={{ color: '#1664ff', fontSize: '32px' }}>智能材料科研平台</span>
</div>
<div className={containerLoginForm}>
<div
style={{
color: '#1d1d20',
fontSize: '22px',
marginLeft: '30px',
fontFamily: 'Alibaba',
}}
>
账号登录
<div className={styles['user-login__right']}>
<div>
<div className={styles['user-login__right__title']}>
<span style={{ color: '#111111' }}>欢迎登录</span>
<span>智能材料科研平台</span>
</div> </div>
<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);
}}
>
{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: '请输入密码',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
<div className={styles['user-login__right__content']}>
<div className={styles['user-login__right__content__title']}>账号登录</div>
<div className={styles['user-login__right__content__form']}>
<Form
labelCol={{ span: 0 }}
wrapperCol={{ span: 24 }}
initialValues={{ autoLogin: true }}
onFinish={handleSubmit}
autoComplete="off"
form={form}
>
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
<Input
placeholder="请输入用户名"
prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />}
allowClear
readOnly={usernameReadOnly}
onFocus={() => setUsernameReadOnly(false)}
/> />
<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.login.captcha.placeholder"
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>
</>
)}
</Form.Item>


{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="手机号格式错误!"
/>
),
},
]}
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password
placeholder="请输入密码"
prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />}
allowClear
/> />
<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');
}}
</Form.Item>

<Flex align="start" style={{ height: '98px' }}>
<div style={{ flex: 1 }}>
<Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}>
<Input
placeholder="请输入验证码"
prefix={
<LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} />
}
allowClear
/>
</Form.Item>
</div>
<Image
className={styles['user-login__right__content__form__captcha']}
src={captchaCode}
alt="验证码"
preview={false}
onClick={() => getCaptchaCode()}
/> />
</>
)}
<div
style={{
marginBottom: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="记住密码" />
</ProFormCheckbox>
</div>
</LoginForm>
</Flex>

<Form.Item
name="autoLogin"
valuePropName="checked"
labelCol={{ span: 0 }}
wrapperCol={{ span: 16 }}
>
<Checkbox>记住密码</Checkbox>
</Form.Item>

<Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}>
<Button type="primary" htmlType="submit">
登录
</Button>
</Form.Item>
</Form>
</div>
</div> </div>
</div> </div>
</div> </div>


+ 156
- 25
react-ui/src/pages/User/Login/login.less View File

@@ -1,31 +1,162 @@
.loginForm {
:global {
.ant-pro-form-login-main {
width: auto !important;
max-width: auto !important;
margin: unset;
.user-login {
display: flex;
height: 100vh;
background-color: #fff;

&__left {
position: relative;
width: 43%;
height: 100%;
padding-top: 56px;
background: linear-gradient(180deg, #e2ecff 0%, #f6fafe 100%);

&__top {
display: flex;
align-items: center;
margin-left: 50px;
color: @text-color;
font-size: 30px;
font-family: Alibaba;
} }
.ant-input-affix-wrapper-lg {
padding: 19px 11px;
color: rgba(29, 29, 32, 0.6);
font-size: 18px;
font-family: 'Alibaba';
border-radius: 13px;

&__title {
position: relative;
display: flex;
justify-content: center;
margin-top: 70px;
margin-left: 85px;
color: #111111;
font-weight: 500;
font-size: 45px;
font-family: Alibaba;

&__img {
position: relative;
top: -10px;
width: 85px;
height: 47px;
}
} }
.ant-input-affix-wrapper {
padding: 19px 11px;
color: rgba(29, 29, 32, 0.6);
font-size: 18px;
font-family: 'Alibaba';
border-radius: 13px;

&__message {
display: flex;
justify-content: center;
margin-top: 18px;
color: #606b7a;
font-size: 26px;
font-family: Alibaba;
} }
.ant-btn.ant-btn-lg {
height: 76px;
color: #ffffff;
font-size: 20px;
font-family: 'Alibaba';
background: @primary-color;
border-radius: 41px;

&__bottom-img {
width: 100%;
height: calc(100% - 300px);
margin-top: 50px;
object-fit: contain;
} }
} }

&__right {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 57%;
height: 100%;
background: #fff;

&__title {
margin-bottom: 24px;
color: @primary-color;
font-size: 36px;
font-family: Alibaba;
}

&__content {
width: 640px;
padding: 60px 60px 45px;
background-color: white;
border-radius: 10px;
box-shadow: 0px 3px 20px rgba(153, 153, 153, 0.16);

&__title {
margin-bottom: 40px;
color: @text-color;
font-size: 22px;
font-family: Alibaba;
}

&__form {
&__captcha {
width: 170px;
height: 66px;
padding-left: 22px;
cursor: pointer;
}
:global {
.ant-form-item {
margin-bottom: 32px;

&:last-child {
margin-bottom: 0;
}
}
.ant-input-affix-wrapper {
padding: 10px 11px;
color: @text-color;
font-size: 18px !important;
background-color: white;
border: 1px solid #caced8;
border-radius: 13px;

.ant-input {
height: 44px;
font-size: 18px !important;
}
}

.ant-btn {
width: 100%;
height: 76px;
font-size: 20px;
border-radius: 38px;
}
}
}
}
}

input {
font-size: 18px !important;
}

input:-webkit-autofill {
font-size: 18px !important;
transition: background-color 5000s ease-in-out 0s;
-webkit-text-fill-color: @text-color !important;
}

input:-webkit-autofill::first-line {
font-size: 18px !important;
}
}

.login-input-prefix {
display: flex;
align-items: center;
height: 44px;
margin-right: 15px;

&__icon {
width: 24px;
height: 26px;
margin-right: 18px;
margin-left: 14px;
}

&__line {
width: 1px;
height: 30px;
background-color: #caced8;
}
} }

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

@@ -2,7 +2,7 @@ import { MenuDataItem } from '@ant-design/pro-components';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import React, { lazy } from 'react'; import React, { lazy } from 'react';


let remoteMenu: any = null;
let remoteMenu: any = [];


export function getRemoteMenu() { export function getRemoteMenu() {
return remoteMenu; return remoteMenu;
@@ -22,6 +22,7 @@ function patchRouteItems(route: any, menu: any, parentPath: string) {
if (routeChild.path === menuItem.path) { if (routeChild.path === menuItem.path) {
hasItem = true; hasItem = true;
newItem = routeChild; newItem = routeChild;
break;
} }
} }
if (!hasItem) { if (!hasItem) {
@@ -42,7 +43,7 @@ function patchRouteItems(route: any, menu: any, parentPath: string) {
path += '/'; path += '/';
} }
if (name !== 'index') { if (name !== 'index') {
path += name.at(0)?.toUpperCase() + name.substr(1);
path += name.at(0)?.toUpperCase() + name.substring(1);
} else { } else {
path += name; path += name;
} }
@@ -101,10 +102,8 @@ export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
return childrens.map((item: API.RoutersMenuItem) => { return childrens.map((item: API.RoutersMenuItem) => {
return { return {
path: item.path, path: item.path,
// icon:'icon-a-057_fenlei',
icon: 'icon-' + item.meta.icon,
// icon: item.meta.icon,
name: item.meta.title,
icon: item.meta?.icon ? 'icon-' + item.meta.icon : undefined,
name: item.meta?.title,
routes: item.children ? convertCompatRouters(item.children) : undefined, routes: item.children ? convertCompatRouters(item.children) : undefined,
hideChildrenInMenu: item.hidden, hideChildrenInMenu: item.hidden,
hideInMenu: item.hidden, hideInMenu: item.hidden,


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

@@ -5,6 +5,16 @@
*/ */


import { ExperimentStatus, TensorBoardStatus } from '@/enums'; import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import type { Settings as LayoutSettings } from '@ant-design/pro-components';

// 全局初始状态类型
export type GlobalInitialState = {
settings?: Partial<LayoutSettings>;
currentUser?: API.CurrentUser;
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
loading?: boolean;
collapsed?: boolean;
};


// 流水线全局参数 // 流水线全局参数
export type PipelineGlobalParam = { export type PipelineGlobalParam = {


+ 31
- 0
react-ui/src/utils/localStorage.ts View File

@@ -0,0 +1,31 @@
// 登录的用户名
export const loginUserKey = 'login-user';
// 登录的密码
export const loginPasswordKey = 'login-password';
// 记住密码
export const rememberPasswordKey = 'login-remember-password';

export const getLocalStorageItem = (key: string, isObject: boolean = false) => {
const jsonStr = localStorage.getItem(key);
if (!isObject) {
return jsonStr;
}
if (jsonStr) {
try {
return JSON.parse(jsonStr);
} catch (error) {
return undefined;
}
}
return undefined;
};

export const setLocalStorageItem = (key: string, state?: any, isObject: boolean = false) => {
if (state) {
localStorage.setItem(key, isObject ? JSON.stringify(state) : state);
}
};

export const removeLocalStorageItem = (key: string) => {
localStorage.removeItem(key);
};

+ 1
- 1
react-ui/src/utils/sessionStorage.ts View File

@@ -3,7 +3,7 @@ export const mirrorNameKey = 'mirror-name';
// 模型部署 // 模型部署
export const modelDeploymentInfoKey = 'model-deployment-info'; export const modelDeploymentInfoKey = 'model-deployment-info';
// 编辑器 url // 编辑器 url
export const editorUrl = 'editor-url';
export const editorUrlKey = 'editor-url';


export const getSessionStorageItem = (key: string, isObject: boolean = false) => { export const getSessionStorageItem = (key: string, isObject: boolean = false) => {
const jsonStr = sessionStorage.getItem(key); const jsonStr = sessionStorage.getItem(key);


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

@@ -19,7 +19,7 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps)
title: ( title: (
<div> <div>
<img <img
src="/assets/images/delete-icon.png"
src={require('@/assets/img/delete-icon.png')}
style={{ width: '120px', marginBottom: '24px' }} style={{ width: '120px', marginBottom: '24px' }}
alt="" alt=""
/> />


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

@@ -73,6 +73,7 @@ public class JupyterServiceImpl implements JupyterService {
V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage,storageClassName); V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage,storageClassName);
Integer podPort = k8sClientUtil.createPod(podName, namespace, port, mountPath, pvc, image); Integer podPort = k8sClientUtil.createPod(podName, namespace, port, mountPath, pvc, image);
return masterIp + ":" + podPort; return masterIp + ":" + podPort;

} }


@Override @Override
@@ -84,12 +85,13 @@ public class JupyterServiceImpl implements JupyterService {


// 提取数据集,模型信息,得到数据集模型的path // 提取数据集,模型信息,得到数据集模型的path
Map<String, Object> dataset = JacksonUtil.parseJSONStr2Map(devEnvironment.getDataset()); Map<String, Object> dataset = JacksonUtil.parseJSONStr2Map(devEnvironment.getDataset());
String datasetPath = (String) dataset.get("path");
String datasetPath = "argo-workflow" + "/" + dataset.get("path");

Map<String, Object> model = JacksonUtil.parseJSONStr2Map(devEnvironment.getModel()); Map<String, Object> model = JacksonUtil.parseJSONStr2Map(devEnvironment.getModel());
String modelPath = (String) model.get("path");
String modelPath = "argo-workflow" + "/" + model.get("path");


LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
//手动构造pod名称
//构造pod名称
String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id; String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id;
String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc"; String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc";
//新建编辑器的pvc //新建编辑器的pvc
@@ -99,7 +101,6 @@ public class JupyterServiceImpl implements JupyterService {


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

String url = masterIp + ":" + podPort; String url = masterIp + ":" + podPort;
redisService.setCacheObject(podName,masterIp + ":" + podPort); redisService.setCacheObject(podName,masterIp + ":" + podPort);
devEnvironment.setStatus("Pending"); devEnvironment.setStatus("Pending");
@@ -109,6 +110,7 @@ public class JupyterServiceImpl implements JupyterService {


} }



@Override @Override
public String stopJupyterService(Integer id) throws Exception { public String stopJupyterService(Integer id) throws Exception {
DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id);
@@ -116,8 +118,9 @@ public class JupyterServiceImpl implements JupyterService {
throw new Exception("开发环境配置不存在"); throw new Exception("开发环境配置不存在");
} }
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
//手动构造pod名称
//构造pod和svc名称
String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id; String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id;
String svcName = loginUser.getUsername().toLowerCase() + "-editor-pod" + "-" + id + "-svc";
//得到pod //得到pod
V1Pod pod = k8sClientUtil.getNSPodList(namespace, podName); V1Pod pod = k8sClientUtil.getNSPodList(namespace, podName);
if(pod == null){ if(pod == null){
@@ -125,10 +128,13 @@ public class JupyterServiceImpl implements JupyterService {
} }
// 使用 Kubernetes API 删除 Pod // 使用 Kubernetes API 删除 Pod
String deleteResult = k8sClientUtil.deletePod(podName, namespace); String deleteResult = k8sClientUtil.deletePod(podName, namespace);
// 删除service
k8sClientUtil.deleteService(svcName, namespace);


devEnvironment.setStatus("Terminated"); devEnvironment.setStatus("Terminated");
this.devEnvironmentService.update(devEnvironment); this.devEnvironmentService.update(devEnvironment);
return deleteResult + ",编辑器已停止"; return deleteResult + ",编辑器已停止";

} }


@Override @Override


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

@@ -104,5 +104,6 @@ public class AIM64EncoderUtil {
String searchQuery = encode(map, false); String searchQuery = encode(map, false);
// 返回查询字符串 // 返回查询字符串
return searchQuery; return searchQuery;

} }
} }

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

@@ -419,7 +419,6 @@ public class K8sClientUtil {
.withVolumeMounts(volumeMounts) .withVolumeMounts(volumeMounts)
.endContainer() .endContainer()
.withVolumes(volumes) .withVolumes(volumes)
.withTerminationGracePeriodSeconds(14400L)
.endSpec() .endSpec()
.build(); .build();


@@ -574,6 +573,26 @@ public class K8sClientUtil {
} }
} }



/**
* 删除 Service
*
* @param svcName Service 名称
* @param namespace 命名空间
* @throws ApiException 异常
*/
public String deleteService(String svcName, String namespace) throws ApiException {
CoreV1Api api = new CoreV1Api(apiClient);
try {
V1Status result = api.deleteNamespacedService(svcName, namespace, null, null, null, null, null, null);
return "Service " + svcName + " 删除请求已发送";
} catch (ApiException e) {
log.error("删除service异常:" + e.getResponseBody(), e);
throw e;
}
}


/** /**
* 检查 Pod 是否存在 * 检查 Pod 是否存在
* *


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

@@ -7,7 +7,8 @@ import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;


@Component @Component
public class MlflowUtil {
public class
MlflowUtil {
private static String trackingUri = "http://172.20.32.181:32005"; private static String trackingUri = "http://172.20.32.181:32005";
private MlflowClient client; private MlflowClient client;




Loading…
Cancel
Save