| @@ -78,7 +78,6 @@ export default defineConfig({ | |||||
| */ | */ | ||||
| title: '智能材料科研平台', | title: '智能材料科研平台', | ||||
| layout: { | layout: { | ||||
| locale: false, | |||||
| ...defaultSettings, | ...defaultSettings, | ||||
| }, | }, | ||||
| // keepalive: [/./], | // keepalive: [/./], | ||||
| @@ -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 | ||||
| @@ -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', | |||||
| }, | |||||
| ]; | ]; | ||||
| @@ -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; | ||||
| }; | }; | ||||
| @@ -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,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%; | ||||
| @@ -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; | ||||
| @@ -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; | |||||
| } | |||||
| @@ -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> | ||||
| @@ -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; | |||||
| } | |||||
| @@ -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; | |||||
| } | |||||
| @@ -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%; | ||||
| @@ -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}`); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -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="基本信息" | ||||
| @@ -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" | ||||
| @@ -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(); | |||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -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%; | ||||
| } | } | ||||
| @@ -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 | ||||
| @@ -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}`); | |||||
| }, | }, | ||||
| }; | }; | ||||
| }; | }; | ||||
| @@ -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%; | ||||
| @@ -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', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -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="基本信息" | ||||
| @@ -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, | ||||
| @@ -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%; | ||||
| } | } | ||||
| } | } | ||||
| @@ -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'); | ||||
| }; | }; | ||||
| @@ -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="基本信息" | ||||
| @@ -162,7 +162,7 @@ function ModelDeployment() { | |||||
| searchStatus, | searchStatus, | ||||
| }); | }); | ||||
| navigate(`/modelDeployment/${record.service_id}`); | |||||
| navigate(`/modelDeployment/info/${record.service_id}`); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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; | ||||
| @@ -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%; | ||||
| } | } | ||||
| } | } | ||||
| @@ -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 | ||||
| @@ -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}` }); | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -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%; | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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({ | ||||
| @@ -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> | ||||
| @@ -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}> | ||||
| @@ -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> | ||||
| @@ -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; | |||||
| } | |||||
| } | } | ||||
| @@ -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, | ||||
| @@ -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 = { | ||||
| @@ -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); | |||||
| }; | |||||
| @@ -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); | ||||
| @@ -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="" | ||||
| /> | /> | ||||
| @@ -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 | ||||
| @@ -104,5 +104,6 @@ public class AIM64EncoderUtil { | |||||
| String searchQuery = encode(map, false); | String searchQuery = encode(map, false); | ||||
| // 返回查询字符串 | // 返回查询字符串 | ||||
| return searchQuery; | return searchQuery; | ||||
| } | } | ||||
| } | } | ||||
| @@ -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 是否存在 | ||||
| * | * | ||||
| @@ -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; | ||||