| @@ -78,7 +78,6 @@ export default defineConfig({ | |||
| */ | |||
| title: '智能材料科研平台', | |||
| layout: { | |||
| locale: false, | |||
| ...defaultSettings, | |||
| }, | |||
| // keepalive: [/./], | |||
| @@ -7,10 +7,11 @@ const Settings: ProLayoutProps & { | |||
| pwa?: boolean; | |||
| logo?: string; | |||
| } = { | |||
| locale: 'zh-CN', | |||
| navTheme: 'light', | |||
| // 拂晓蓝 | |||
| colorPrimary: '#1664ff', | |||
| layout: 'mix', | |||
| // layout: 'mix', | |||
| contentWidth: 'Fluid', | |||
| fixedHeader: false, | |||
| fixSiderbar: false, | |||
| @@ -18,7 +19,6 @@ const Settings: ProLayoutProps & { | |||
| colorWeak: false, | |||
| title: '智能材料科研平台', | |||
| pwa: true, | |||
| logo: '/assets/images/left-top-logo.png', | |||
| 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 | |||
| @@ -14,11 +14,19 @@ export default [ | |||
| { | |||
| path: '/', | |||
| redirect: '/workspace', | |||
| breadcrumb: '工作空间', | |||
| }, | |||
| { | |||
| path: '*', | |||
| layout: false, | |||
| component: './404', | |||
| name: 'workspace', | |||
| path: '/workspace', | |||
| routes: [ | |||
| { | |||
| name: '工作空间', | |||
| path: '', | |||
| key: 'workspace', | |||
| component: './Workspace/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| path: '/user', | |||
| @@ -35,40 +43,64 @@ export default [ | |||
| path: '/account', | |||
| routes: [ | |||
| { | |||
| name: 'acenter', | |||
| name: '用户中心', | |||
| path: '/account/center', | |||
| component: './User/Center', | |||
| }, | |||
| { | |||
| name: 'asettings', | |||
| name: '用户设置', | |||
| path: '/account/settings', | |||
| component: './User/Settings', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'datasetPreparation', | |||
| name: '数据准备', | |||
| path: '/datasetPreparation', | |||
| routes: [ | |||
| { | |||
| name: 'datasetAnnotation', | |||
| path: '', | |||
| redirect: '/datasetPreparation/datasetAnnotation', | |||
| }, | |||
| { | |||
| name: '数据标注', | |||
| path: 'datasetAnnotation', | |||
| 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', | |||
| routes: [ | |||
| { | |||
| path: '', | |||
| redirect: '/pipeline/template', | |||
| }, | |||
| { | |||
| name: '流水线模板', | |||
| path: 'template', | |||
| path: '/pipeline/template', | |||
| routes: [ | |||
| { | |||
| name: '流水线模板', | |||
| @@ -77,8 +109,8 @@ export default [ | |||
| }, | |||
| { | |||
| name: '流水线详情', | |||
| path: ':id', | |||
| component: './Pipeline/editPipeline/index', | |||
| path: 'info/:id', | |||
| component: './Pipeline/Info/index', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -92,8 +124,8 @@ export default [ | |||
| component: './Experiment/index', | |||
| }, | |||
| { | |||
| name: '实验训练', | |||
| path: ':workflowId/:id', | |||
| name: '实验实例', | |||
| path: 'instance/:workflowId/:id', | |||
| component: './Experiment/Info/index', | |||
| }, | |||
| { | |||
| @@ -106,58 +138,25 @@ export default [ | |||
| ], | |||
| }, | |||
| { | |||
| name: 'developmentEnvironment', | |||
| path: '/developmentEnvironment', | |||
| name: 'AI资产', | |||
| path: '/dataset', | |||
| routes: [ | |||
| { | |||
| name: '开发环境', | |||
| 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: '数据集', | |||
| path: 'dataset', | |||
| routes: [ | |||
| { | |||
| name: '数据集列表', | |||
| name: '数据集', | |||
| path: '', | |||
| component: './Dataset/index', | |||
| }, | |||
| { | |||
| name: '数据集简介', | |||
| path: ':id', | |||
| path: 'info/:id', | |||
| component: './Dataset/intro', | |||
| }, | |||
| ], | |||
| @@ -167,13 +166,13 @@ export default [ | |||
| path: 'model', | |||
| routes: [ | |||
| { | |||
| name: '模型列表', | |||
| name: '模型', | |||
| path: '', | |||
| component: './Model/index', | |||
| }, | |||
| { | |||
| name: '模型简介', | |||
| path: ':id', | |||
| path: 'info/:id', | |||
| component: './Model/intro', | |||
| }, | |||
| ], | |||
| @@ -183,13 +182,13 @@ export default [ | |||
| path: 'mirror', | |||
| routes: [ | |||
| { | |||
| name: '镜像列表', | |||
| name: '镜像', | |||
| path: '', | |||
| component: './Mirror/List', | |||
| }, | |||
| { | |||
| name: '镜像详情', | |||
| path: ':id', | |||
| path: 'info/:id', | |||
| 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', | |||
| routes: [ | |||
| { | |||
| name: '模型列表', | |||
| name: '模型部署', | |||
| path: '', | |||
| component: './ModelDeployment/List', | |||
| }, | |||
| { | |||
| name: '镜像详情', | |||
| path: ':id', | |||
| name: '模型部署详情', | |||
| path: 'info/:id', | |||
| component: './ModelDeployment/Info', | |||
| }, | |||
| { | |||
| name: '创建镜像', | |||
| name: '创建推理服务', | |||
| path: 'create', | |||
| component: './ModelDeployment/Create', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'appsDeployment', | |||
| name: '应用开发', | |||
| path: '/appsDeployment', | |||
| routes: [ | |||
| { | |||
| @@ -248,7 +234,7 @@ export default [ | |||
| ], | |||
| }, | |||
| { | |||
| name: 'see', | |||
| name: '监控运维', | |||
| path: '/see', | |||
| 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', | |||
| 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', | |||
| path: '/docs', | |||
| @@ -298,4 +363,9 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| path: '*', | |||
| layout: false, | |||
| component: './404', | |||
| }, | |||
| ]; | |||
| @@ -20,18 +20,14 @@ import { | |||
| import './styles/menu.less'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| // const isDev = process.env.NODE_ENV === 'development'; | |||
| import { type GlobalInitialState } from '@/types'; | |||
| import { menuItemRender } from '@/utils/menuRender'; | |||
| import { gotoLoginPage } from './utils/ui'; | |||
| /** | |||
| * @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 () => { | |||
| try { | |||
| const response = await getUserInfo(); | |||
| @@ -56,18 +52,20 @@ export async function getInitialState(): Promise<{ | |||
| fetchUserInfo, | |||
| currentUser, | |||
| settings: defaultSettings as Partial<LayoutSettings>, | |||
| collapsed: false, | |||
| }; | |||
| } | |||
| return { | |||
| fetchUserInfo, | |||
| settings: defaultSettings as Partial<LayoutSettings>, | |||
| collapsed: false, | |||
| }; | |||
| } | |||
| // ProLayout 支持的api https://procomponents.ant.design/components/layout | |||
| export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| return { | |||
| rightContentRender: () => <RightContent />, | |||
| rightContentRender: false, | |||
| waterMarkProps: { | |||
| // content: initialState?.currentUser?.nickName, | |||
| }, | |||
| @@ -91,7 +89,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| return getRemoteMenu(); | |||
| }, | |||
| }, | |||
| // footerRender: () => <Footer />, | |||
| onPageChange: () => { | |||
| const { location } = history; | |||
| // 如果没有登录,重定向到 login | |||
| @@ -127,14 +124,20 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| // </Link>, | |||
| // ] | |||
| // : [], | |||
| menuHeaderRender: false, | |||
| // 自定义 403 页面 | |||
| // unAccessible: <div>unAccessible</div>, | |||
| // 增加一个 loading 的状态 | |||
| childrenRender: (children) => { | |||
| // 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: { | |||
| onClick: () => { | |||
| // 点击菜单项,删除所有的页面 state 缓存 | |||
| @@ -143,6 +146,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| }, | |||
| }, | |||
| ...initialState?.settings, | |||
| logo: require('@/assets/img/logo.png'), | |||
| token: { | |||
| sider: { | |||
| colorTextMenu: themes['textColor'], | |||
| @@ -233,20 +237,24 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||
| memo.theme.components.Tabs = { | |||
| titleFontSize: 16, | |||
| }; | |||
| memo.theme.components.Form = { | |||
| 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.hashed = false; | |||
| // memo.appConfig = { | |||
| // message: { | |||
| // // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭 | |||
| // maxCount: 3, | |||
| // }, | |||
| // }; | |||
| memo.appConfig = { | |||
| message: { | |||
| // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭 | |||
| maxCount: 3, | |||
| }, | |||
| }; | |||
| 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 { | |||
| .ant-modal-content { | |||
| padding: 40px 67px; | |||
| background-image: url(/assets/images/modal-back.png); | |||
| background-image: url(@/assets/img/modal-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100%; | |||
| @@ -4,6 +4,7 @@ | |||
| padding: 4px 11px; | |||
| border: 1px solid #d9d9d9; | |||
| border-radius: 6px; | |||
| cursor: pointer; | |||
| &:hover { | |||
| 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 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 styles from './index.less'; | |||
| // import { SelectLang } from '@umijs/max'; | |||
| export type SiderTheme = 'light' | 'dark'; | |||
| 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) { | |||
| return null; | |||
| } | |||
| const handleMenuCollapse = () => { | |||
| setInitialState((preInitialState) => ({ | |||
| ...preInitialState, | |||
| collapsed: !preInitialState?.collapsed, | |||
| })); | |||
| }; | |||
| return ( | |||
| <div className={className}> | |||
| <div className={styles['right-content']}> | |||
| {/* <span | |||
| className={actionClassName} | |||
| onClick={() => { | |||
| @@ -49,6 +34,18 @@ const GlobalHeaderRight: React.FC = () => { | |||
| > | |||
| <QuestionCircleOutlined /> | |||
| </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} /> | |||
| {/* <SelectLang className={actionClassName} /> */} | |||
| </div> | |||
| @@ -76,7 +76,7 @@ body { | |||
| } | |||
| .ant-pro-layout .ant-layout-sider.ant-pro-sider { | |||
| height: 100vh; | |||
| padding-top: 56px; | |||
| // padding-top: 56px; | |||
| } | |||
| .ant-pro-layout .ant-pro-layout-container { | |||
| 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-content { | |||
| padding: 40px 67px; | |||
| background-image: url(/assets/images/modal-back.png); | |||
| background-image: url(@/assets/img/modal-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100%; | |||
| @@ -179,3 +179,11 @@ | |||
| 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; | |||
| margin-bottom: 10px; | |||
| 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-position: top center; | |||
| background-size: 100% 100%; | |||
| @@ -131,7 +131,7 @@ function ResourceList( | |||
| activeTag: dataTag, | |||
| }); | |||
| const prefix = config.prefix; | |||
| navigate(`/dataset/${prefix}/${record.id}`); | |||
| navigate(`/dataset/${prefix}/info/${record.id}`); | |||
| }; | |||
| // 分页切换 | |||
| @@ -84,7 +84,7 @@ function EditorCreate() { | |||
| return ( | |||
| <div className={styles['editor-create']}> | |||
| <PageTitle title="创建编辑器"></PageTitle> | |||
| <PageTitle title="创建开发环境"></PageTitle> | |||
| <div className={styles['editor-create__content']}> | |||
| <div> | |||
| <Form | |||
| @@ -96,6 +96,7 @@ function EditorCreate() { | |||
| initialValues={{ computing_resource: ComputingResourceType.GPU }} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| autoComplete="off" | |||
| > | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| @@ -213,7 +213,7 @@ function EditorList() { | |||
| icon={<KFIcon type="icon-tiaoshi" />} | |||
| onClick={() => startEditor(record.id)} | |||
| > | |||
| 再次调试 | |||
| 启动 | |||
| </Button> | |||
| )} | |||
| <ConfigProvider | |||
| @@ -247,7 +247,7 @@ function EditorList() { | |||
| onClick={createEditor} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 创建编辑器 | |||
| 创建开发环境 | |||
| </Button> | |||
| <Button | |||
| style={{ marginLeft: '20px' }} | |||
| @@ -268,6 +268,7 @@ function EditorList() { | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| showTotal: () => `共${total}条`, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| @@ -438,6 +438,13 @@ function ExperimentText() { | |||
| // 绑定事件 | |||
| const bindEvents = () => { | |||
| const closeDrawer = () => { | |||
| closePropsDrawer(); | |||
| setTimeout(() => { | |||
| setExperimentNodeData(null); | |||
| }, 200); | |||
| }; | |||
| graph.on('node:click', (e) => { | |||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||
| const model = e.item.getModel(); | |||
| @@ -452,10 +459,10 @@ function ExperimentText() { | |||
| graph.setItemState(e.item, 'hover', false); | |||
| }); | |||
| graph.on('canvas:click', (e) => { | |||
| closePropsDrawer(); | |||
| setTimeout(() => { | |||
| setExperimentNodeData(null); | |||
| }, 200); | |||
| closeDrawer(); | |||
| }); | |||
| graph.on('edge:click', (e) => { | |||
| closeDrawer(); | |||
| }); | |||
| }; | |||
| @@ -27,7 +27,7 @@ | |||
| width: 100%; | |||
| height: calc(100% - 56px); | |||
| 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%; | |||
| } | |||
| @@ -49,7 +49,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| className={styles['experiment-parameter']} | |||
| > | |||
| <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> | |||
| <Form.Item | |||
| label="任务名称" | |||
| @@ -76,7 +79,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <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> | |||
| <Form.Item | |||
| label="镜像" | |||
| @@ -128,7 +134,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| </Form.Item> | |||
| ))} | |||
| <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> | |||
| {inParametersList.map((item) => ( | |||
| <Form.Item | |||
| @@ -145,7 +154,10 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| </Form.Item> | |||
| ))} | |||
| <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> | |||
| {outParametersList.map((item) => ( | |||
| <Form.Item | |||
| @@ -30,7 +30,7 @@ import { experimentStatusInfo } from './status'; | |||
| const timerIds = new Map(); | |||
| function Experiment() { | |||
| const navgite = useNavigate(); | |||
| const navigate = useNavigate(); | |||
| const [experimentList, setExperimentList] = useState([]); | |||
| const [workflowList, setWorkflowList] = useState([]); | |||
| const [queryFlow, setQueryFlow] = useState({ | |||
| @@ -275,12 +275,12 @@ function Experiment() { | |||
| // 跳转到流水线 | |||
| const gotoPipeline = (e, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: `/pipeline/template/${record.workflow_id}` }); | |||
| navigate({ pathname: `/pipeline/template/info/${record.workflow_id}` }); | |||
| }; | |||
| // 跳转到实验实例详情 | |||
| const gotoInstanceInfo = (item, record) => { | |||
| navgite({ pathname: `/pipeline/experiment/${record.workflow_id}/${item.id}` }); | |||
| navigate({ pathname: `/pipeline/experiment/instance/${record.workflow_id}/${item.id}` }); | |||
| }; | |||
| // 处理 TensorBoard 操作 | |||
| @@ -327,7 +327,7 @@ function Experiment() { | |||
| }, | |||
| ], | |||
| onClick: ({ key }) => { | |||
| navgite(`/pipeline/experiment/compare?type=${key}&id=${experimentId}`); | |||
| navigate(`/pipeline/experiment/compare?type=${key}&id=${experimentId}`); | |||
| }, | |||
| }; | |||
| }; | |||
| @@ -8,7 +8,7 @@ | |||
| height: 49px; | |||
| margin-bottom: 10px; | |||
| 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-position: top center; | |||
| background-size: 100% 100%; | |||
| @@ -11,41 +11,41 @@ export const experimentStatusInfo: Record<ExperimentStatus, ExperimentStatusInfo | |||
| Running: { | |||
| label: '运行中', | |||
| color: themes.primaryColor, | |||
| icon: '/assets/images/running-icon.png', | |||
| icon: '/assets/images/experiment-status/running-icon.png', | |||
| }, | |||
| Succeeded: { | |||
| label: '成功', | |||
| color: themes.successColor, | |||
| icon: '/assets/images/success-icon.png', | |||
| icon: '/assets/images/experiment-status/success-icon.png', | |||
| }, | |||
| Pending: { | |||
| label: '等待中', | |||
| color: themes.pendingColor, | |||
| icon: '/assets/images/pending-icon.png', | |||
| icon: '/assets/images/experiment-status/pending-icon.png', | |||
| }, | |||
| Failed: { | |||
| label: '失败', | |||
| color: themes.errorColor, | |||
| icon: '/assets/images/fail-icon.png', | |||
| icon: '/assets/images/experiment-status/fail-icon.png', | |||
| }, | |||
| Error: { | |||
| label: '错误', | |||
| color: themes.errorColor, | |||
| icon: '/assets/images/fail-icon.png', | |||
| icon: '/assets/images/experiment-status/fail-icon.png', | |||
| }, | |||
| Terminated: { | |||
| label: '终止', | |||
| color: themes.abortColor, | |||
| icon: '/assets/images/omitted-icon.png', | |||
| icon: '/assets/images/experiment-status/omitted-icon.png', | |||
| }, | |||
| Skipped: { | |||
| label: '未执行', | |||
| color: themes.abortColor, | |||
| icon: '/assets/images/omitted-icon.png', | |||
| icon: '/assets/images/experiment-status/omitted-icon.png', | |||
| }, | |||
| Omitted: { | |||
| label: '未执行', | |||
| 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 }} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| autoComplete="off" | |||
| > | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| @@ -125,7 +125,7 @@ function MirrorList() { | |||
| // 查看详情 | |||
| const toDetail = (record: MirrorData) => { | |||
| navigate(`/dataset/mirror/${record.id}`); | |||
| navigate(`/dataset/mirror/info/${record.id}`); | |||
| setCacheState({ | |||
| activeTab, | |||
| pagination, | |||
| @@ -12,7 +12,7 @@ | |||
| &__graph { | |||
| height: calc(100% - 92px); | |||
| 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%; | |||
| } | |||
| } | |||
| @@ -16,7 +16,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||
| const gotoExperimentPage = () => { | |||
| if (data.train_task?.ins_id) { | |||
| 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'); | |||
| } | |||
| }; | |||
| @@ -28,7 +28,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||
| if (data.current_model_id === resourceId) { | |||
| onVersionChange?.(data.version); | |||
| } 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); | |||
| } | |||
| }; | |||
| @@ -100,7 +100,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||
| function DatasetInfo({ data }: { data: TrainDataset }) { | |||
| const gotoDatasetPage = () => { | |||
| const { origin } = location; | |||
| const url = `${origin}/dataset/dataset/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`; | |||
| const url = `${origin}/dataset/dataset/info/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`; | |||
| window.open(url, '_blank'); | |||
| }; | |||
| @@ -141,6 +141,7 @@ function ModelDeploymentCreate() { | |||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| autoComplete="off" | |||
| > | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| @@ -162,7 +162,7 @@ function ModelDeployment() { | |||
| searchStatus, | |||
| }); | |||
| navigate(`/modelDeployment/${record.service_id}`); | |||
| navigate(`/modelDeployment/info/${record.service_id}`); | |||
| }; | |||
| // 分页切换 | |||
| @@ -232,7 +232,7 @@ const JobTableList: React.FC = () => { | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon=<EditOutlined /> | |||
| icon={<EditOutlined />} | |||
| hidden={!access.hasPerms('monitor:job:edit')} | |||
| onClick={() => { | |||
| setModalVisible(true); | |||
| @@ -246,7 +246,7 @@ const JobTableList: React.FC = () => { | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| icon=<DeleteOutlined /> | |||
| icon={<DeleteOutlined />} | |||
| hidden={!access.hasPerms('monitor:job:remove')} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| @@ -319,7 +319,7 @@ const JobTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.Monitor.Job> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -217,7 +217,7 @@ const JobLogTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.Monitor.JobLog> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -3,7 +3,7 @@ import { DeleteOutlined } from '@ant-design/icons'; | |||
| import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useAccess, useIntl } from '@umijs/max'; | |||
| import type { FormInstance } from 'antd'; | |||
| import { Button, message, Modal } from 'antd'; | |||
| import { Button, Modal, message } from 'antd'; | |||
| import React, { useEffect, useRef } from 'react'; | |||
| /* * | |||
| @@ -102,7 +102,7 @@ const OnlineUserTableList: React.FC = () => { | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| icon=<DeleteOutlined /> | |||
| icon={<DeleteOutlined />} | |||
| hidden={!access.hasPerms('monitor:online:forceLogout')} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| @@ -2,17 +2,16 @@ import KFIcon from '@/components/KFIcon'; | |||
| import { useStateRef, useVisible } from '@/hooks'; | |||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { fittingString } from '@/utils'; | |||
| import { fittingString, s8 } from '@/utils'; | |||
| import { to } from '@/utils/promise'; | |||
| import G6 from '@antv/g6'; | |||
| import { useNavigate, useParams } from '@umijs/max'; | |||
| import { App, Button } from 'antd'; | |||
| import { useEffect, useRef } from 'react'; | |||
| import { useNavigate, useParams } from 'react-router-dom'; | |||
| import { s8 } from '../../../utils'; | |||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | |||
| import ModelMenu from '../components/ModelMenu'; | |||
| import Props from '../components/PipelineNodeDrawer'; | |||
| import styles from './index.less'; | |||
| import Props from './props'; | |||
| import { findAllParentNodes } from './utils'; | |||
| let graph = null; | |||
| @@ -23,7 +23,7 @@ | |||
| width: 100%; | |||
| height: calc(100% - 52px); | |||
| 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%; | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ import ParameterSelect from '@/components/ParameterSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils'; | |||
| import { | |||
| PipelineGlobalParam, | |||
| PipelineNodeModel, | |||
| @@ -16,13 +17,12 @@ import { INode } from '@antv/g6'; | |||
| import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd'; | |||
| import { NamePath } from 'antd/es/form/interface'; | |||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||
| import PropsLabel from '../components/PropsLabel'; | |||
| import PropsLabel from '../PropsLabel'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '../components/ResourceSelectorModal'; | |||
| import styles from './props.less'; | |||
| import { canInput, createMenuItems } from './utils'; | |||
| } from '../ResourceSelectorModal'; | |||
| import styles from './index.less'; | |||
| const { TextArea } = Input; | |||
| type PipelineNodeParameterProps = { | |||
| @@ -299,7 +299,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| scrollToFirstError | |||
| > | |||
| <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> | |||
| <Form.Item | |||
| label="任务名称" | |||
| @@ -326,7 +329,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| <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> | |||
| <Form.Item label="镜像" required> | |||
| <div className={styles['pipeline-drawer__ref-row']}> | |||
| @@ -436,7 +442,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| </Form.Item> | |||
| ))} | |||
| <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> | |||
| {inParametersList.map((item) => ( | |||
| <Form.Item | |||
| @@ -469,7 +478,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| </Form.Item> | |||
| ))} | |||
| <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> | |||
| {outParametersList.map((item) => ( | |||
| <Form.Item | |||
| @@ -21,7 +21,7 @@ import Styles from './index.less'; | |||
| const { TextArea } = Input; | |||
| const Pipeline = () => { | |||
| const [form] = Form.useForm(); | |||
| const navgite = useNavigate(); | |||
| const navigate = useNavigate(); | |||
| const [formId, setFormId] = useState(null); | |||
| const [dialogTitle, setDialogTitle] = useState('新建流水线'); | |||
| @@ -43,7 +43,7 @@ const Pipeline = () => { | |||
| }; | |||
| const routeToEdit = (e, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: `/pipeline/template/${record.id}` }); | |||
| navigate({ pathname: `/pipeline/template/info/${record.id}` }); | |||
| }; | |||
| const showModal = () => { | |||
| form.resetFields(); | |||
| @@ -66,7 +66,7 @@ const Pipeline = () => { | |||
| setIsModalOpen(false); | |||
| message.success('新建成功'); | |||
| 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; | |||
| margin-bottom: 10px; | |||
| 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-position: top left; | |||
| background-size: 100% 100%; | |||
| @@ -258,7 +258,7 @@ const ConfigTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.Config> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -234,7 +234,7 @@ const DeptTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.Dept> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -259,7 +259,7 @@ const DictTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.DictType> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -303,7 +303,7 @@ const DictDataTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.DictData> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -180,7 +180,7 @@ const LogininforTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.Monitor.Logininfor> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -221,7 +221,7 @@ const MenuTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.Menu> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -259,7 +259,7 @@ const NoticeTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.Notice> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -227,7 +227,7 @@ const OperlogTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.Monitor.Operlog> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -225,7 +225,7 @@ const PostTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.Post> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -163,7 +163,7 @@ const AuthUserTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.User> | |||
| headerTitle={intl.formatMessage({ | |||
| @@ -261,7 +261,7 @@ const RoleTableList: React.FC = () => { | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon=<EditOutlined /> | |||
| icon={<EditOutlined />} | |||
| hidden={!access.hasPerms('system:role:edit')} | |||
| onClick={() => { | |||
| getRoleMenuList(record.roleId).then((res) => { | |||
| @@ -288,7 +288,7 @@ const RoleTableList: React.FC = () => { | |||
| size="small" | |||
| danger | |||
| key="batchRemove" | |||
| icon=<DeleteOutlined /> | |||
| icon={<DeleteOutlined />} | |||
| hidden={!access.hasPerms('system:role:remove')} | |||
| onClick={async () => { | |||
| Modal.confirm({ | |||
| @@ -360,7 +360,7 @@ const RoleTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| {contextHolder} | |||
| <div style={{ width: '100%', float: 'right' }}> | |||
| <ProTable<API.System.Role> | |||
| @@ -286,7 +286,7 @@ const UserTableList: React.FC = () => { | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon=<EditOutlined /> | |||
| icon={<EditOutlined />} | |||
| hidden={!access.hasPerms('system:user:edit')} | |||
| onClick={async () => { | |||
| fetchUserInfo(record.userId); | |||
| @@ -302,7 +302,7 @@ const UserTableList: React.FC = () => { | |||
| type="link" | |||
| size="small" | |||
| danger | |||
| icon=<DeleteOutlined /> | |||
| icon={<DeleteOutlined />} | |||
| key="batchRemove" | |||
| hidden={!access.hasPerms('system:user:remove')} | |||
| onClick={async () => { | |||
| @@ -365,7 +365,7 @@ const UserTableList: React.FC = () => { | |||
| ]; | |||
| return ( | |||
| <PageContainer> | |||
| <PageContainer header={{ breadcrumb: {} }}> | |||
| {contextHolder} | |||
| <Row gutter={[16, 24]}> | |||
| <Col lg={6} md={24}> | |||
| @@ -1,187 +1,48 @@ | |||
| import { clearSessionToken, setSessionToken } from '@/access'; | |||
| import { getFakeCaptcha } from '@/services/ant-design-pro/login'; | |||
| 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 { flushSync } from 'react-dom'; | |||
| 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 ( | |||
| <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> | |||
| ); | |||
| }; | |||
| const LoginMessage: React.FC<{ | |||
| content: string; | |||
| }> = ({ content }) => { | |||
| return ( | |||
| <Alert | |||
| style={{ | |||
| marginBottom: 24, | |||
| }} | |||
| message={content} | |||
| type="error" | |||
| showIcon | |||
| /> | |||
| ); | |||
| }; | |||
| const Login: React.FC = () => { | |||
| const [userLoginState, setUserLoginState] = useState<API.LoginResult>({ code: 200 }); | |||
| const [type, setType] = useState<string>('account'); | |||
| const { initialState, setInitialState } = useModel('@@initialState'); | |||
| const [captchaCode, setCaptchaCode] = 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 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 () => { | |||
| @@ -196,302 +57,134 @@ const Login: React.FC = () => { | |||
| } | |||
| }; | |||
| // 登录 | |||
| 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 { | |||
| 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(); | |||
| } | |||
| }; | |||
| const { code } = userLoginState; | |||
| const loginType = type; | |||
| useEffect(() => { | |||
| getCaptchaCode(); | |||
| }, []); | |||
| 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 | |||
| src="/assets/images/left-top-logo.png" | |||
| style={{ height: '42px', marginRight: '10px' }} | |||
| src={require('@/assets/img/logo.png')} | |||
| style={{ width: '32px', marginRight: '12px' }} | |||
| alt="" | |||
| /> | |||
| 智能材料科研平台 | |||
| </div> | |||
| <div className={centerTitleBoX}> | |||
| <span style={{ whiteSpace: 'nowrap' }}>智能材料科研平台</span> | |||
| <div className={styles['user-login__left__title']}> | |||
| <span>智能材料科研平台</span> | |||
| <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="" | |||
| /> | |||
| </div> | |||
| <div className={centerMessage}> | |||
| <span style={{ whiteSpace: 'nowrap' }}>大语言模型运维 统一管理平台</span> | |||
| <div className={styles['user-login__left__message']}> | |||
| <span>大语言模型运维 统一管理平台</span> | |||
| </div> | |||
| <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="" | |||
| /> | |||
| </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 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> | |||
| @@ -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 React, { lazy } from 'react'; | |||
| let remoteMenu: any = null; | |||
| let remoteMenu: any = []; | |||
| export function getRemoteMenu() { | |||
| return remoteMenu; | |||
| @@ -22,6 +22,7 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| if (routeChild.path === menuItem.path) { | |||
| hasItem = true; | |||
| newItem = routeChild; | |||
| break; | |||
| } | |||
| } | |||
| if (!hasItem) { | |||
| @@ -42,7 +43,7 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| path += '/'; | |||
| } | |||
| if (name !== 'index') { | |||
| path += name.at(0)?.toUpperCase() + name.substr(1); | |||
| path += name.at(0)?.toUpperCase() + name.substring(1); | |||
| } else { | |||
| path += name; | |||
| } | |||
| @@ -101,10 +102,8 @@ export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] { | |||
| return childrens.map((item: API.RoutersMenuItem) => { | |||
| return { | |||
| 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, | |||
| hideChildrenInMenu: item.hidden, | |||
| hideInMenu: item.hidden, | |||
| @@ -5,6 +5,16 @@ | |||
| */ | |||
| 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 = { | |||
| @@ -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'; | |||
| // 编辑器 url | |||
| export const editorUrl = 'editor-url'; | |||
| export const editorUrlKey = 'editor-url'; | |||
| export const getSessionStorageItem = (key: string, isObject: boolean = false) => { | |||
| const jsonStr = sessionStorage.getItem(key); | |||
| @@ -19,7 +19,7 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) | |||
| title: ( | |||
| <div> | |||
| <img | |||
| src="/assets/images/delete-icon.png" | |||
| src={require('@/assets/img/delete-icon.png')} | |||
| style={{ width: '120px', marginBottom: '24px' }} | |||
| alt="" | |||
| /> | |||
| @@ -73,6 +73,7 @@ public class JupyterServiceImpl implements JupyterService { | |||
| V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage,storageClassName); | |||
| Integer podPort = k8sClientUtil.createPod(podName, namespace, port, mountPath, pvc, image); | |||
| return masterIp + ":" + podPort; | |||
| } | |||
| @Override | |||
| @@ -84,12 +85,13 @@ public class JupyterServiceImpl implements JupyterService { | |||
| // 提取数据集,模型信息,得到数据集模型的path | |||
| 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()); | |||
| String modelPath = (String) model.get("path"); | |||
| String modelPath = "argo-workflow" + "/" + model.get("path"); | |||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||
| //手动构造pod名称 | |||
| //构造pod名称 | |||
| String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id; | |||
| String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc"; | |||
| //新建编辑器的pvc | |||
| @@ -99,7 +101,6 @@ public class JupyterServiceImpl implements JupyterService { | |||
| // 调用修改后的 createPod 方法,传入额外的参数 | |||
| Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, minioPvcName, datasetPath, modelPath); | |||
| String url = masterIp + ":" + podPort; | |||
| redisService.setCacheObject(podName,masterIp + ":" + podPort); | |||
| devEnvironment.setStatus("Pending"); | |||
| @@ -109,6 +110,7 @@ public class JupyterServiceImpl implements JupyterService { | |||
| } | |||
| @Override | |||
| public String stopJupyterService(Integer id) throws Exception { | |||
| DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); | |||
| @@ -116,8 +118,9 @@ public class JupyterServiceImpl implements JupyterService { | |||
| throw new Exception("开发环境配置不存在"); | |||
| } | |||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||
| //手动构造pod名称 | |||
| //构造pod和svc名称 | |||
| String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id; | |||
| String svcName = loginUser.getUsername().toLowerCase() + "-editor-pod" + "-" + id + "-svc"; | |||
| //得到pod | |||
| V1Pod pod = k8sClientUtil.getNSPodList(namespace, podName); | |||
| if(pod == null){ | |||
| @@ -125,10 +128,13 @@ public class JupyterServiceImpl implements JupyterService { | |||
| } | |||
| // 使用 Kubernetes API 删除 Pod | |||
| String deleteResult = k8sClientUtil.deletePod(podName, namespace); | |||
| // 删除service | |||
| k8sClientUtil.deleteService(svcName, namespace); | |||
| devEnvironment.setStatus("Terminated"); | |||
| this.devEnvironmentService.update(devEnvironment); | |||
| return deleteResult + ",编辑器已停止"; | |||
| } | |||
| @Override | |||
| @@ -104,5 +104,6 @@ public class AIM64EncoderUtil { | |||
| String searchQuery = encode(map, false); | |||
| // 返回查询字符串 | |||
| return searchQuery; | |||
| } | |||
| } | |||
| @@ -419,7 +419,6 @@ public class K8sClientUtil { | |||
| .withVolumeMounts(volumeMounts) | |||
| .endContainer() | |||
| .withVolumes(volumes) | |||
| .withTerminationGracePeriodSeconds(14400L) | |||
| .endSpec() | |||
| .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 是否存在 | |||
| * | |||
| @@ -7,7 +7,8 @@ import org.springframework.stereotype.Component; | |||
| import java.util.List; | |||
| @Component | |||
| public class MlflowUtil { | |||
| public class | |||
| MlflowUtil { | |||
| private static String trackingUri = "http://172.20.32.181:32005"; | |||
| private MlflowClient client; | |||