| @@ -62,3 +62,6 @@ mvnw | |||||
| *storybook.log | *storybook.log | ||||
| /react-ui/docs | /react-ui/docs | ||||
| /react-ui/types/tsconfig.tsbuildinfo | |||||
| /react-ui/storybook-static | |||||
| /react-ui/.storybook/scripts | |||||
| @@ -4,7 +4,6 @@ export default function(babel) { | |||||
| visitor: { | visitor: { | ||||
| ImportDeclaration(path) { | ImportDeclaration(path) { | ||||
| const source = path.node.source.value; | const source = path.node.source.value; | ||||
| // console.log("zzzz", source); | |||||
| if (source.endsWith('.less')) { | if (source.endsWith('.less')) { | ||||
| if (path.node.specifiers.length > 0) { | if (path.node.specifiers.length > 0) { | ||||
| path.node.source.value += "?modules"; | path.node.source.value += "?modules"; | ||||
| @@ -16,7 +16,11 @@ const config: StorybookConfig = { | |||||
| name: '@storybook/react-webpack5', | name: '@storybook/react-webpack5', | ||||
| options: {}, | options: {}, | ||||
| }, | }, | ||||
| staticDirs: ['../public', { from: '../docs', to: '/docs' }], | |||||
| staticDirs: [ | |||||
| '../public', | |||||
| { from: '../docs', to: '/docs' }, | |||||
| { from: '../docs/index.html', to: '/docs/index.html' }, | |||||
| ], | |||||
| docs: { | docs: { | ||||
| defaultName: 'Documentation', | defaultName: 'Documentation', | ||||
| }, | }, | ||||
| @@ -0,0 +1,3 @@ | |||||
| # Dockerfile | |||||
| FROM nginx:alpine | |||||
| COPY storybook-static/ /usr/share/nginx/html | |||||
| @@ -143,6 +143,11 @@ export default [ | |||||
| path: 'compare', | path: 'compare', | ||||
| component: './Experiment/Comparison/index', | component: './Experiment/Comparison/index', | ||||
| }, | }, | ||||
| { | |||||
| name: '实验可视化对比', | |||||
| path: 'compare-visual', | |||||
| component: './Experiment/Aim/index', | |||||
| }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -307,7 +312,18 @@ export default [ | |||||
| { | { | ||||
| name: '镜像详情', | name: '镜像详情', | ||||
| path: 'info/:id', | path: 'info/:id', | ||||
| component: './Mirror/Info', | |||||
| routes: [ | |||||
| { | |||||
| name: '镜像详情', | |||||
| path: '', | |||||
| component: './Mirror/Info', | |||||
| }, | |||||
| { | |||||
| name: '新增镜像版本', | |||||
| path: 'add-version', | |||||
| component: './Mirror/Create', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| name: '创建镜像', | name: '创建镜像', | ||||
| @@ -16,7 +16,7 @@ | |||||
| "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", | "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", | ||||
| "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", | "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", | ||||
| "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", | "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", | ||||
| "docs": "typedoc --entryPointStrategy expand --entryPoints 'src/utils' --skipErrorChecking --out docs", | |||||
| "docs": "typedoc", | |||||
| "gh-pages": "gh-pages -d dist", | "gh-pages": "gh-pages -d dist", | ||||
| "i18n-remove": "pro i18n-remove --locale=zh-CN --write", | "i18n-remove": "pro i18n-remove --locale=zh-CN --write", | ||||
| "postinstall": "max setup", | "postinstall": "max setup", | ||||
| @@ -40,6 +40,7 @@ | |||||
| "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | ||||
| "storybook": "storybook dev -p 6006", | "storybook": "storybook dev -p 6006", | ||||
| "storybook-build": "storybook build", | "storybook-build": "storybook build", | ||||
| "storybook-deploy": "./.storybook/scripts/upload-deploy.sh", | |||||
| "storybook-docs": "storybook dev --docs", | "storybook-docs": "storybook dev --docs", | ||||
| "storybook-docs-build": "storybook build --docs", | "storybook-docs-build": "storybook build --docs", | ||||
| "test": "jest", | "test": "jest", | ||||
| @@ -4,16 +4,6 @@ | |||||
| font-display: swap; | font-display: swap; | ||||
| } | } | ||||
| @font-face { | |||||
| font-family: 'TaoBaoMaiCaiTi'; | |||||
| src: url('./TaoBaoMaiCaiTi-Regular.woff2') format('woff2'), /* 最优先使用 woff2 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.woff') format('woff'), /* 兼容性较好的 woff */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.ttf') format('truetype'), /* ttf 作为备选 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.otf') format('opentype'); /* otf 作为最后选项 */ | |||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||||
| } | |||||
| @font-face { | @font-face { | ||||
| font-family: 'DingTalk-JinBuTi'; | font-family: 'DingTalk-JinBuTi'; | ||||
| src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | ||||
| @@ -1,13 +1,16 @@ | |||||
| import RightContent from '@/components/RightContent'; | import RightContent from '@/components/RightContent'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { type GlobalInitialState } from '@/types'; | |||||
| import { menuItemRender } from '@/utils/menuRender'; | |||||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | ||||
| import { RuntimeConfig, history } from '@umijs/max'; | import { RuntimeConfig, history } from '@umijs/max'; | ||||
| import { RuntimeAntdConfig } from 'umi'; | import { RuntimeAntdConfig } from 'umi'; | ||||
| import defaultSettings from '../config/defaultSettings'; | import defaultSettings from '../config/defaultSettings'; | ||||
| import '../public/fonts/font.css'; | import '../public/fonts/font.css'; | ||||
| import { getAccessToken } from './access'; | import { getAccessToken } from './access'; | ||||
| import ErrorBoundary from './components/ErrorBoundary'; | |||||
| import './dayjsConfig'; | import './dayjsConfig'; | ||||
| import { removeAllPageCacheState } from './hooks/pageCacheState'; | |||||
| import { removeAllPageCacheState } from './hooks/useCacheState'; | |||||
| import { | import { | ||||
| getRemoteMenu, | getRemoteMenu, | ||||
| getRoutersInfo, | getRoutersInfo, | ||||
| @@ -16,14 +19,9 @@ import { | |||||
| setRemoteMenu, | setRemoteMenu, | ||||
| } from './services/session'; | } from './services/session'; | ||||
| import './styles/menu.less'; | import './styles/menu.less'; | ||||
| export { requestConfig as request } from './requestConfig'; | |||||
| // const isDev = process.env.NODE_ENV === 'development'; | |||||
| import { type GlobalInitialState } from '@/types'; | |||||
| // import '@/utils/clipboard'; | |||||
| import { menuItemRender } from '@/utils/menuRender'; | |||||
| import ErrorBoundary from './components/ErrorBoundary'; | |||||
| import { needAuth } from './utils'; | import { needAuth } from './utils'; | ||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| export { requestConfig as request } from './requestConfig'; | |||||
| /** | /** | ||||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | ||||
| @@ -139,7 +137,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| onClick: () => { | onClick: () => { | ||||
| // 点击菜单项,删除所有的页面 state 缓存 | // 点击菜单项,删除所有的页面 state 缓存 | ||||
| removeAllPageCacheState(); | removeAllPageCacheState(); | ||||
| // console.log('click menu'); | |||||
| }, | }, | ||||
| }, | }, | ||||
| ...initialState?.settings, | ...initialState?.settings, | ||||
| @@ -2,6 +2,7 @@ import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { Flex, Typography } from 'antd'; | import { Flex, Typography } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CodeConfigItemProps = { | type CodeConfigItemProps = { | ||||
| @@ -10,6 +11,7 @@ type CodeConfigItemProps = { | |||||
| }; | }; | ||||
| function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | ||||
| const [isEllipsis, setIsEllipsis] = useState(false); | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | ||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | ||||
| @@ -32,11 +34,20 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| </Flex> | </Flex> | ||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| className={styles['code-config-item__url']} | className={styles['code-config-item__url']} | ||||
| ellipsis={{ rows: 2, tooltip: item.git_url }} | |||||
| ellipsis={{ | |||||
| rows: 2, | |||||
| tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip | |||||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), | |||||
| }} | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__branch']} | |||||
| ellipsis={{ tooltip: item.git_branch }} | |||||
| > | |||||
| {item.git_branch} | |||||
| </Typography.Paragraph> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -0,0 +1,26 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { Tooltip } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| export type CopyingTextProps = { | |||||
| text: string; | |||||
| }; | |||||
| function CopyingText({ text }: CopyingTextProps) { | |||||
| return ( | |||||
| <div className={styles['copying-text']}> | |||||
| <span className={styles['copying-text__text']}>{text}</span> | |||||
| <Tooltip title="复制"> | |||||
| <KFIcon | |||||
| id="copying" | |||||
| data-clipboard-text={text} | |||||
| type="icon-fuzhi2" | |||||
| className={styles['copying-text__icon']} | |||||
| color="#606b7a" | |||||
| /> | |||||
| </Tooltip> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CopyingText; | |||||
| @@ -3,6 +3,7 @@ import KFSpin from '@/components/KFSpin'; | |||||
| import { getLabelStudioUrl } from '@/services/developmentEnvironment'; | import { getLabelStudioUrl } from '@/services/developmentEnvironment'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import { FloatButton } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { createPortal } from 'react-dom'; | import { createPortal } from 'react-dom'; | ||||
| @@ -13,6 +14,7 @@ export enum IframePageType { | |||||
| AppDevelopment = 'AppDevelopment', // 应用开发 | AppDevelopment = 'AppDevelopment', // 应用开发 | ||||
| DevEnv = 'DevEnv', // 开发环境 | DevEnv = 'DevEnv', // 开发环境 | ||||
| GitLink = 'GitLink', // git link | GitLink = 'GitLink', // git link | ||||
| Aim = 'Aim', // 实验对比 | |||||
| } | } | ||||
| const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | ||||
| @@ -29,12 +31,20 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||||
| }); | }); | ||||
| case IframePageType.GitLink: // git link | case IframePageType.GitLink: // git link | ||||
| return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); | return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); | ||||
| case IframePageType.Aim: // Aim | |||||
| return () => | |||||
| Promise.resolve({ | |||||
| code: 200, | |||||
| data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '', | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| type IframePageProps = { | type IframePageProps = { | ||||
| /** 子系统 */ | /** 子系统 */ | ||||
| type: IframePageType; | type: IframePageType; | ||||
| /** 是否可以在页签上打开 */ | |||||
| openInTab?: boolean; | |||||
| /** 自定义样式类名 */ | /** 自定义样式类名 */ | ||||
| className?: string; | className?: string; | ||||
| /** 自定义样式 */ | /** 自定义样式 */ | ||||
| @@ -42,7 +52,7 @@ type IframePageProps = { | |||||
| }; | }; | ||||
| /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | ||||
| function IframePage({ type, className, style }: IframePageProps) { | |||||
| function IframePage({ type, openInTab = false, className, style }: IframePageProps) { | |||||
| const [iframeUrl, setIframeUrl] = useState(''); | const [iframeUrl, setIframeUrl] = useState(''); | ||||
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| @@ -68,6 +78,7 @@ function IframePage({ type, className, style }: IframePageProps) { | |||||
| <div className={classNames('kf-iframe-page', className)} style={style}> | <div className={classNames('kf-iframe-page', className)} style={style}> | ||||
| {loading && createPortal(<KFSpin size="large" />, document.body)} | {loading && createPortal(<KFSpin size="large" />, document.body)} | ||||
| <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> | <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> | ||||
| {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -4,7 +4,7 @@ | |||||
| right: 0; | right: 0; | ||||
| bottom: 0; | bottom: 0; | ||||
| left: 0; | left: 0; | ||||
| z-index: 1001; | |||||
| z-index: 1001; // 设置大于 Modal 的 z-index | |||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -22,7 +22,7 @@ | |||||
| border-radius: 4px; | border-radius: 4px; | ||||
| &__value { | &__value { | ||||
| .singleLine(); | |||||
| //.singleLine(); | |||||
| margin-right: 8px; | margin-right: 8px; | ||||
| font-size: @font-size-input; | font-size: @font-size-input; | ||||
| line-height: 1.5714285714285714; | line-height: 1.5714285714285714; | ||||
| @@ -6,7 +6,7 @@ | |||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { CloseOutlined } from '@ant-design/icons'; | import { CloseOutlined } from '@ant-design/icons'; | ||||
| import { ConfigProvider, Form, Input } from 'antd'; | |||||
| import { ConfigProvider, Form, Input, Typography } from 'antd'; | |||||
| import { RuleObject } from 'antd/es/form'; | import { RuleObject } from 'antd/es/form'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import './index.less'; | import './index.less'; | ||||
| @@ -120,7 +120,12 @@ function ParameterInput({ | |||||
| > | > | ||||
| {valueObj?.showValue ? ( | {valueObj?.showValue ? ( | ||||
| <div className="parameter-input__content"> | <div className="parameter-input__content"> | ||||
| <span className="parameter-input__content__value">{valueObj?.showValue}</span> | |||||
| <Typography.Text | |||||
| className="parameter-input__content__value" | |||||
| ellipsis={{ tooltip: valueObj.showValue }} | |||||
| > | |||||
| {valueObj.showValue} | |||||
| </Typography.Text> | |||||
| <CloseOutlined | <CloseOutlined | ||||
| className="parameter-input__content__close-icon" | className="parameter-input__content__close-icon" | ||||
| onClick={handleRemove} | onClick={handleRemove} | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/resource'; | |||||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; | |||||
| import { ServiceData } from '@/pages/ModelDeployment/types'; | import { ServiceData } from '@/pages/ModelDeployment/types'; | ||||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | ||||
| import { getServiceListReq } from '@/services/modelDeployment'; | import { getServiceListReq } from '@/services/modelDeployment'; | ||||
| @@ -4,7 +4,7 @@ | |||||
| * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | ||||
| */ | */ | ||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Select, type SelectProps } from 'antd'; | import { Select, type SelectProps } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| @@ -16,13 +16,17 @@ export type ParameterSelectObject = { | |||||
| [key: string]: any; | [key: string]: any; | ||||
| }; | }; | ||||
| export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource'; | |||||
| export interface ParameterSelectProps extends SelectProps { | export interface ParameterSelectProps extends SelectProps { | ||||
| /** 类型 */ | /** 类型 */ | ||||
| dataType: 'dataset' | 'model' | 'service' | 'resource'; | |||||
| dataType: ParameterSelectDataType; | |||||
| /** 是否只是展示信息 */ | /** 是否只是展示信息 */ | ||||
| display?: boolean; | display?: boolean; | ||||
| /** 值,支持对象,对象必须包含 value */ | /** 值,支持对象,对象必须包含 value */ | ||||
| value?: string | ParameterSelectObject; | value?: string | ParameterSelectObject; | ||||
| /** 用于流水线, 流水线资源规格要求 id 为字符串 */ | |||||
| isPipeline?: boolean; | |||||
| /** 修改后回调 */ | /** 修改后回调 */ | ||||
| onChange?: (value: string | ParameterSelectObject) => void; | onChange?: (value: string | ParameterSelectObject) => void; | ||||
| } | } | ||||
| @@ -32,6 +36,7 @@ function ParameterSelect({ | |||||
| dataType, | dataType, | ||||
| display = false, | display = false, | ||||
| value, | value, | ||||
| isPipeline = false, | |||||
| onChange, | onChange, | ||||
| ...rest | ...rest | ||||
| }: ParameterSelectProps) { | }: ParameterSelectProps) { | ||||
| @@ -39,6 +44,12 @@ function ParameterSelect({ | |||||
| const propsConfig = paramSelectConfig[dataType]; | const propsConfig = paramSelectConfig[dataType]; | ||||
| const valueText = typeof value === 'object' && value !== null ? value.value : value; | const valueText = typeof value === 'object' && value !== null ? value.value : value; | ||||
| const [resourceStandardList] = useComputingResource(); | const [resourceStandardList] = useComputingResource(); | ||||
| const computingResource = isPipeline | |||||
| ? resourceStandardList.map((v) => ({ | |||||
| ...v, | |||||
| id: String(v.id), | |||||
| })) | |||||
| : resourceStandardList; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取下拉数据 | // 获取下拉数据 | ||||
| @@ -56,7 +67,7 @@ function ParameterSelect({ | |||||
| getSelectOptions(); | getSelectOptions(); | ||||
| }, [propsConfig]); | }, [propsConfig]); | ||||
| const selectOptions = dataType === 'resource' ? resourceStandardList : options; | |||||
| const selectOptions = dataType === 'resource' ? computingResource : options; | |||||
| const handleChange = (text: string) => { | const handleChange = (text: string) => { | ||||
| if (typeof value === 'object' && value !== null) { | if (typeof value === 'object' && value !== null) { | ||||
| @@ -224,6 +224,8 @@ export class MirrorSelector implements SelectorTypeInfo { | |||||
| image_id: parentKey, | image_id: parentKey, | ||||
| page: 0, | page: 0, | ||||
| size: 2000, | size: 2000, | ||||
| status: 'available', | |||||
| state: 1, | |||||
| }); | }); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const list = res.data.content || []; | const list = res.data.content || []; | ||||
| @@ -2,8 +2,9 @@ import { clearSessionToken } from '@/access'; | |||||
| import { setRemoteMenu } from '@/services/session'; | import { setRemoteMenu } from '@/services/session'; | ||||
| import { logout } from '@/services/system/auth'; | import { logout } from '@/services/system/auth'; | ||||
| import { ClientInfo } from '@/types'; | import { ClientInfo } from '@/types'; | ||||
| import { sleep } from '@/utils/promise'; | |||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import { gotoLoginPage } from '@/utils/ui'; | |||||
| import { gotoLoginPage, oauthLogout } from '@/utils/ui'; | |||||
| import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | ||||
| import { setAlpha } from '@ant-design/pro-components'; | import { setAlpha } from '@ant-design/pro-components'; | ||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | import { useEmotionCss } from '@ant-design/use-emotion-css'; | ||||
| @@ -62,7 +63,9 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||||
| * 退出登录,并且将当前的 url 保存 | * 退出登录,并且将当前的 url 保存 | ||||
| */ | */ | ||||
| const loginOut = async () => { | const loginOut = async () => { | ||||
| await logout(); | |||||
| oauthLogout('http://172.20.32.197:31209/oauth/logout'); | |||||
| // 至少 1 秒后跳转,希望子系统能完成注销 | |||||
| await Promise.all([logout(), sleep(1000)]); | |||||
| clearSessionToken(); | clearSessionToken(); | ||||
| setRemoteMenu(null); | setRemoteMenu(null); | ||||
| gotoLoginPage(); | gotoLoginPage(); | ||||
| @@ -160,3 +160,8 @@ ol { | |||||
| input:-webkit-autofill { | input:-webkit-autofill { | ||||
| transition: background-color 5000s ease-in-out 0s; | transition: background-color 5000s ease-in-out 0s; | ||||
| } | } | ||||
| .ant-typography { | |||||
| color: inherit; | |||||
| font-size: inherit; | |||||
| } | |||||
| @@ -1,202 +0,0 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-15 10:01:29 | |||||
| * @Description: 自定义 hooks | |||||
| */ | |||||
| import { FormInstance } from 'antd'; | |||||
| import { debounce } from 'lodash'; | |||||
| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | |||||
| /** | |||||
| * 生成具有初始值的状态引用 | |||||
| * | |||||
| * @param initialValue - 状态的初始值 | |||||
| * @return 包含状态值、状态设置函数和可变引用对象的数组 | |||||
| */ | |||||
| export function useStateRef<T>(initialValue: T) { | |||||
| const [value, setValue] = useState(initialValue); | |||||
| const ref = useRef(value); | |||||
| useEffect(() => { | |||||
| ref.current = value; | |||||
| }, [value]); | |||||
| return [value, setValue, ref] as const; | |||||
| } | |||||
| /** | |||||
| * 生成一个自定义钩子,用于管理模态框的可见性状态。 | |||||
| * | |||||
| * @param initialValue - 模态框的初始可见性状态。 | |||||
| * @return 一个数组,包含可见性状态和打开和关闭模态框的函数。 | |||||
| */ | |||||
| export function useVisible(initialValue: boolean) { | |||||
| const [visible, setVisible] = useState(initialValue); | |||||
| const ref = useRef(initialValue); | |||||
| const open = useCallback(() => { | |||||
| setVisible(true); | |||||
| }, []); | |||||
| const close = useCallback(() => { | |||||
| setVisible(false); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| ref.current = visible; | |||||
| }, [visible]); | |||||
| return [visible, open, close, ref] as const; | |||||
| } | |||||
| type Callback<T> = (state: T) => void; | |||||
| /** | |||||
| * 生成一个具有回调机制的可变状态值和更新它的函数。 | |||||
| * | |||||
| * @param initialValue - 初始状态值。 | |||||
| * @return 一个元组,包含当前状态值和用于更新状态的函数。 | |||||
| */ | |||||
| export function useCallbackState<T>(initialValue: T) { | |||||
| const [state, _setState] = useState<T>(initialValue); | |||||
| const callbackQueue = useRef<Callback<T>[]>([]); | |||||
| useEffect(() => { | |||||
| callbackQueue.current.forEach((cb) => cb(state)); | |||||
| callbackQueue.current = []; | |||||
| }, [state]); | |||||
| const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => { | |||||
| _setState(newValue); | |||||
| if (callback && typeof callback === 'function') { | |||||
| callbackQueue.current.push(callback); | |||||
| } | |||||
| }; | |||||
| return [state, setState] as const; | |||||
| } | |||||
| /** | |||||
| * 用于追踪 DOM 元素尺寸的 hook。 | |||||
| * | |||||
| * @param initialWidth - 初始宽度。 | |||||
| * @param initialHeight - 初始高度。 | |||||
| * @param deps - 依赖列表。 | |||||
| * @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。 | |||||
| */ | |||||
| export function useDomSize<T extends HTMLElement>( | |||||
| initialWidth: number, | |||||
| initialHeight: number, | |||||
| deps: React.DependencyList = [], | |||||
| ) { | |||||
| const domRef = useRef<T>(null); | |||||
| const [width, setWidth] = useState(initialWidth); | |||||
| const [height, setHeight] = useState(initialHeight); | |||||
| useEffect(() => { | |||||
| const setDomHeight = () => { | |||||
| if (domRef.current) { | |||||
| setHeight(domRef.current.offsetHeight); | |||||
| setWidth(domRef.current.offsetWidth); | |||||
| } | |||||
| }; | |||||
| const debounceFunc = debounce(setDomHeight, 100); | |||||
| setDomHeight(); | |||||
| window.addEventListener('resize', debounceFunc); | |||||
| return () => { | |||||
| window.removeEventListener('resize', debounceFunc); | |||||
| }; | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [domRef, ...deps]); | |||||
| return [domRef, { width, height }] as const; | |||||
| } | |||||
| /** | |||||
| * 用于在 modal 关闭时重置 Form 表单的 hook。 | |||||
| * | |||||
| * @param form - Ant Design Form 表单实例 | |||||
| * @param open - modal 是否打开 | |||||
| */ | |||||
| export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => { | |||||
| const prevOpenRef = useRef<boolean>(); | |||||
| useEffect(() => { | |||||
| prevOpenRef.current = open; | |||||
| }, [open]); | |||||
| const prevOpen = prevOpenRef.current; | |||||
| useEffect(() => { | |||||
| if (!open && prevOpen) { | |||||
| form.resetFields(); | |||||
| } | |||||
| }, [form, prevOpen, open]); | |||||
| }; | |||||
| /** | |||||
| * Executes the effect function when the specified condition is true. | |||||
| * | |||||
| * @param effect - The effect function to execute. | |||||
| * @param when - The condition to trigger the effect. | |||||
| * @param deps - The dependencies for the effect. | |||||
| */ | |||||
| export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => { | |||||
| const requestFns = useRef<(() => void)[]>([]); | |||||
| useEffect(() => { | |||||
| if (when) { | |||||
| effect(); | |||||
| } else { | |||||
| requestFns.current.splice(0, 1, effect); | |||||
| } | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, deps); | |||||
| useEffect(() => { | |||||
| if (when) { | |||||
| const fn = requestFns.current.pop(); | |||||
| fn?.(); | |||||
| } | |||||
| }, [when]); | |||||
| }; | |||||
| // 选择、全选操作 | |||||
| export const useCheck = <T>(list: T[]) => { | |||||
| const [selected, setSelected] = useState<T[]>([]); | |||||
| const checked = useMemo(() => { | |||||
| return selected.length === list.length && selected.length > 0; | |||||
| }, [selected, list]); | |||||
| const indeterminate = useMemo(() => { | |||||
| return selected.length > 0 && selected.length < list.length; | |||||
| }, [selected, list]); | |||||
| const checkAll = useCallback(() => { | |||||
| setSelected(checked ? [] : list); | |||||
| }, [list, checked]); | |||||
| const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]); | |||||
| const checkSingle = useCallback( | |||||
| (item: T) => { | |||||
| setSelected((prev) => { | |||||
| if (isSingleChecked(item)) { | |||||
| return prev.filter((i) => i !== item); | |||||
| } else { | |||||
| return [...prev, item]; | |||||
| } | |||||
| }); | |||||
| }, | |||||
| [isSingleChecked], | |||||
| ); | |||||
| return [ | |||||
| selected, | |||||
| setSelected, | |||||
| checked, | |||||
| indeterminate, | |||||
| checkAll, | |||||
| isSingleChecked, | |||||
| checkSingle, | |||||
| ] as const; | |||||
| }; | |||||
| @@ -29,13 +29,18 @@ const removeCacheState = (key: string) => { | |||||
| } | } | ||||
| }; | }; | ||||
| // 移除所有页面 state 缓存 | |||||
| /** | |||||
| * 移除所有页面 state 缓存 | |||||
| */ | |||||
| export const removeAllPageCacheState = () => { | export const removeAllPageCacheState = () => { | ||||
| pageKeys.forEach((key) => { | pageKeys.forEach((key) => { | ||||
| sessionStorage.removeItem(key); | sessionStorage.removeItem(key); | ||||
| }); | }); | ||||
| }; | }; | ||||
| /** | |||||
| * 缓存页面数据 | |||||
| */ | |||||
| export const useCacheState = () => { | export const useCacheState = () => { | ||||
| const { pathname } = window.location; | const { pathname } = window.location; | ||||
| const key = 'pagecache:' + pathname; | const key = 'pagecache:' + pathname; | ||||
| @@ -0,0 +1,25 @@ | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| type Callback<T> = (state: T) => void; | |||||
| /** | |||||
| * 生成一个具有回调机制的可变状态值和更新它的函数。谨慎使用 | |||||
| * | |||||
| * @param initialValue - 初始状态值。 | |||||
| * @return 一个元组,包含当前状态值和用于更新状态的函数。 | |||||
| */ | |||||
| export function useCallbackState<T>(initialValue: T) { | |||||
| const [state, _setState] = useState<T>(initialValue); | |||||
| const callbackQueue = useRef<Callback<T>[]>([]); | |||||
| useEffect(() => { | |||||
| callbackQueue.current.forEach((cb) => cb(state)); | |||||
| callbackQueue.current = []; | |||||
| }, [state]); | |||||
| const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => { | |||||
| _setState(newValue); | |||||
| if (callback && typeof callback === 'function') { | |||||
| callbackQueue.current.push(callback); | |||||
| } | |||||
| }; | |||||
| return [state, setState] as const; | |||||
| } | |||||
| @@ -0,0 +1,47 @@ | |||||
| import { useCallback, useMemo, useState } from 'react'; | |||||
| /** | |||||
| * 选择、全选操作 | |||||
| * @param list - 需要进行选择的列表 | |||||
| * @return [选中的项, 设置选中的方法, 是否全选, 是否部分选中, 全选方法,是否单个选中,选中单个方法] | |||||
| */ | |||||
| export const useCheck = <T>(list: T[]) => { | |||||
| const [selected, setSelected] = useState<T[]>([]); | |||||
| const checked = useMemo(() => { | |||||
| return selected.length === list.length && selected.length > 0; | |||||
| }, [selected, list]); | |||||
| const indeterminate = useMemo(() => { | |||||
| return selected.length > 0 && selected.length < list.length; | |||||
| }, [selected, list]); | |||||
| const checkAll = useCallback(() => { | |||||
| setSelected(checked ? [] : list); | |||||
| }, [list, checked]); | |||||
| const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]); | |||||
| const checkSingle = useCallback( | |||||
| (item: T) => { | |||||
| setSelected((prev) => { | |||||
| if (isSingleChecked(item)) { | |||||
| return prev.filter((i) => i !== item); | |||||
| } else { | |||||
| return [...prev, item]; | |||||
| } | |||||
| }); | |||||
| }, | |||||
| [isSingleChecked], | |||||
| ); | |||||
| return [ | |||||
| selected, | |||||
| setSelected, | |||||
| checked, | |||||
| indeterminate, | |||||
| checkAll, | |||||
| isSingleChecked, | |||||
| checkSingle, | |||||
| ] as const; | |||||
| }; | |||||
| @@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from 'react'; | |||||
| const computingResource: ComputingResource[] = []; | const computingResource: ComputingResource[] = []; | ||||
| // 过滤资源规格 | |||||
| /** 过滤资源规格 */ | |||||
| export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | ||||
| input: string, | input: string, | ||||
| option?: ComputingResource, | option?: ComputingResource, | ||||
| @@ -22,13 +22,13 @@ export const filterResourceStandard: SelectProps<string, ComputingResource>['fil | |||||
| ); | ); | ||||
| }; | }; | ||||
| // 资源规格字段 | |||||
| /** 资源规格字段 */ | |||||
| export const resourceFieldNames = { | export const resourceFieldNames = { | ||||
| label: 'description', | label: 'description', | ||||
| value: 'id', | value: 'id', | ||||
| }; | }; | ||||
| // 获取资源规格 | |||||
| /** 获取资源规格 */ | |||||
| export function useComputingResource() { | export function useComputingResource() { | ||||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | ||||
| @@ -0,0 +1,40 @@ | |||||
| import { debounce } from 'lodash'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| /** | |||||
| * 用于追踪 DOM 元素尺寸的 hook。 | |||||
| * | |||||
| * @param initialWidth - 初始宽度。 | |||||
| * @param initialHeight - 初始高度。 | |||||
| * @param deps - 依赖列表。 | |||||
| * @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。 | |||||
| */ | |||||
| export function useDomSize<T extends HTMLElement>( | |||||
| initialWidth: number, | |||||
| initialHeight: number, | |||||
| deps: React.DependencyList = [], | |||||
| ) { | |||||
| const domRef = useRef<T>(null); | |||||
| const [width, setWidth] = useState(initialWidth); | |||||
| const [height, setHeight] = useState(initialHeight); | |||||
| useEffect(() => { | |||||
| const setDomHeight = () => { | |||||
| if (domRef.current) { | |||||
| setHeight(domRef.current.offsetHeight); | |||||
| setWidth(domRef.current.offsetWidth); | |||||
| } | |||||
| }; | |||||
| const debounceFunc = debounce(setDomHeight, 100); | |||||
| setDomHeight(); | |||||
| window.addEventListener('resize', debounceFunc); | |||||
| return () => { | |||||
| window.removeEventListener('resize', debounceFunc); | |||||
| }; | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, deps); | |||||
| return [domRef, { width, height }] as const; | |||||
| } | |||||
| @@ -1,6 +1,8 @@ | |||||
| // 处理 react-draggable 组件拖动结束时,响应了点击事件的 | |||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| /** | |||||
| * 处理 react-draggable 组件拖动结束时,响应了点击事件的 | |||||
| */ | |||||
| export const useDraggable = (onClick: () => void) => { | export const useDraggable = (onClick: () => void) => { | ||||
| const [isDragging, setIsDragging] = useState(false); | const [isDragging, setIsDragging] = useState(false); | ||||
| @@ -0,0 +1,24 @@ | |||||
| import { useEffect, useRef } from 'react'; | |||||
| /** | |||||
| * 当指定的条件为真时执行 Effect 函数。 | |||||
| * | |||||
| * @param effect - The effect function to execute. | |||||
| * @param when - The condition to trigger the effect. | |||||
| * @param deps - The dependencies for the effect. | |||||
| */ | |||||
| export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => { | |||||
| const requestFn = useRef<(() => void) | undefined>(effect); | |||||
| useEffect(() => { | |||||
| requestFn.current = effect; | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [...deps, effect]); | |||||
| useEffect(() => { | |||||
| if (when && requestFn.current) { | |||||
| requestFn.current(); | |||||
| requestFn.current = undefined; | |||||
| } | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [...deps, when]); | |||||
| }; | |||||
| @@ -0,0 +1,24 @@ | |||||
| import { FormInstance } from 'antd'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| /** | |||||
| * 用于在 modal 关闭时重置 Form 表单的 hook。 | |||||
| * | |||||
| * @param form - Ant Design Form 表单实例 | |||||
| * @param open - modal 是否打开 | |||||
| */ | |||||
| export const useResetForm = (form: FormInstance, open: boolean) => { | |||||
| const prevOpenRef = useRef<boolean>(); | |||||
| useEffect(() => { | |||||
| prevOpenRef.current = open; | |||||
| }, [open]); | |||||
| const prevOpen = prevOpenRef.current; | |||||
| useEffect(() => { | |||||
| if (!open && prevOpen) { | |||||
| form.resetFields(); | |||||
| } | |||||
| }, [form, prevOpen, open]); | |||||
| }; | |||||
| @@ -0,0 +1,46 @@ | |||||
| import { parseJsonText } from '@/utils'; | |||||
| import { useCallback, useRef } from 'react'; | |||||
| export const useSSE = (onMessage: (data: any) => void) => { | |||||
| const evtSourceRef = useRef<EventSource | null>(null); | |||||
| const setupSSE = useCallback( | |||||
| (name: string, namespace: string) => { | |||||
| const { origin } = location; | |||||
| const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | |||||
| const evtSource = new EventSource( | |||||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | |||||
| { withCredentials: false }, | |||||
| ); | |||||
| evtSource.onmessage = (event) => { | |||||
| const data = event?.data; | |||||
| if (!data) { | |||||
| return; | |||||
| } | |||||
| const dataJson = parseJsonText(data); | |||||
| if (dataJson) { | |||||
| const nodes = dataJson?.result?.object?.status?.nodes; | |||||
| if (nodes) { | |||||
| onMessage(nodes); | |||||
| } | |||||
| } | |||||
| }; | |||||
| evtSource.onerror = (error) => { | |||||
| console.error('SSE error: ', error); | |||||
| }; | |||||
| evtSourceRef.current = evtSource; | |||||
| }, | |||||
| [onMessage], | |||||
| ); | |||||
| const closeSSE = useCallback(() => { | |||||
| if (evtSourceRef.current) { | |||||
| evtSourceRef.current.close(); | |||||
| evtSourceRef.current = null; | |||||
| } | |||||
| }, []); | |||||
| return [setupSSE, closeSSE]; | |||||
| }; | |||||
| @@ -0,0 +1,19 @@ | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| /** | |||||
| * 生成具有初始值的状态引用 | |||||
| * | |||||
| * @param initialValue - 状态的初始值 | |||||
| * @return 包含状态值、状态设置函数和可变引用对象的数组 | |||||
| */ | |||||
| export function useStateRef<T>(initialValue: T) { | |||||
| const [value, setValue] = useState(initialValue); | |||||
| const ref = useRef(value); | |||||
| useEffect(() => { | |||||
| ref.current = value; | |||||
| }, [value]); | |||||
| return [value, setValue, ref] as const; | |||||
| } | |||||
| @@ -0,0 +1,26 @@ | |||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| /** | |||||
| * 生成一个自定义钩子,用于管理模态框的可见性状态。 | |||||
| * | |||||
| * @param initialValue - 模态框的初始可见性状态。 | |||||
| * @return 一个数组,包含 visible、打开函数、关闭函数和 visible ref。 | |||||
| */ | |||||
| export function useVisible(initialValue: boolean) { | |||||
| const [visible, setVisible] = useState(initialValue); | |||||
| const ref = useRef(initialValue); | |||||
| const open = useCallback(() => { | |||||
| setVisible(true); | |||||
| }, []); | |||||
| const close = useCallback(() => { | |||||
| setVisible(false); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| ref.current = visible; | |||||
| }, [visible]); | |||||
| return [visible, open, close, ref] as const; | |||||
| } | |||||
| @@ -261,8 +261,3 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .ant-typography { | |||||
| color: inherit; | |||||
| font-size: inherit; | |||||
| } | |||||
| @@ -186,6 +186,7 @@ function AutoMLInstance() { | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | icon: <KFIcon type="icon-Trialliebiao" />, | ||||
| children: ( | children: ( | ||||
| <ExperimentHistory | <ExperimentHistory | ||||
| calcMetrics={autoMLInfo?.scoring_functions} | |||||
| fileUrl={instanceInfo?.run_history_path} | fileUrl={instanceInfo?.run_history_path} | ||||
| isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | ||||
| /> | /> | ||||
| @@ -1,26 +0,0 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { Typography } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| export type CopyingTextProps = { | |||||
| text: string; | |||||
| }; | |||||
| function CopyingText({ text }: CopyingTextProps) { | |||||
| return ( | |||||
| <div className={styles['copying-text']}> | |||||
| <Typography.Text ellipsis={{ tooltip: text }} className={styles['copying-text__text']}> | |||||
| {text} | |||||
| </Typography.Text> | |||||
| <KFIcon | |||||
| id="copying" | |||||
| data-clipboard-text={text} | |||||
| type="icon-fuzhi2" | |||||
| className={styles['copying-text__icon']} | |||||
| color="#606b7a" | |||||
| /> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CopyingText; | |||||
| @@ -8,8 +8,9 @@ import TrialStatusCell from '../TrialStatusCell'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentHistoryProps = { | type ExperimentHistoryProps = { | ||||
| fileUrl?: string; | |||||
| isClassification: boolean; | |||||
| calcMetrics?: string; // 计算指标 | |||||
| fileUrl?: string; // 文件url | |||||
| isClassification: boolean; // 是否是分类 | |||||
| }; | }; | ||||
| type TableData = { | type TableData = { | ||||
| @@ -22,7 +23,7 @@ type TableData = { | |||||
| althorithm?: string; | althorithm?: string; | ||||
| }; | }; | ||||
| function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { | |||||
| function ExperimentHistory({ calcMetrics, fileUrl, isClassification }: ExperimentHistoryProps) { | |||||
| const [tableData, setTableData] = useState<TableData[]>([]); | const [tableData, setTableData] = useState<TableData[]>([]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取实验运行历史记录 | // 获取实验运行历史记录 | ||||
| @@ -33,7 +34,7 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| const list: TableData[] = data.map((item) => { | const list: TableData[] = data.map((item) => { | ||||
| return { | return { | ||||
| id: item[0]?.[0], | id: item[0]?.[0], | ||||
| accuracy: item[1]?.[5]?.accuracy, | |||||
| accuracy: calcMetrics ? item[1]?.[5]?.[calcMetrics] : undefined, | |||||
| duration: item[1]?.[5]?.duration, | duration: item[1]?.[5]?.duration, | ||||
| train_loss: item[1]?.[5]?.train_loss, | train_loss: item[1]?.[5]?.train_loss, | ||||
| status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | ||||
| @@ -64,12 +65,6 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| width: 80, | width: 80, | ||||
| render: tableCellRender(false), | render: tableCellRender(false), | ||||
| }, | }, | ||||
| { | |||||
| title: '准确率', | |||||
| dataIndex: 'accuracy', | |||||
| key: 'accuracy', | |||||
| render: tableCellRender(true), | |||||
| }, | |||||
| { | { | ||||
| title: '耗时', | title: '耗时', | ||||
| dataIndex: 'duration', | dataIndex: 'duration', | ||||
| @@ -103,6 +98,15 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| if (calcMetrics) { | |||||
| columns.splice(0, 0, { | |||||
| title: `指标:${calcMetrics}`, | |||||
| dataIndex: 'accuracy', | |||||
| key: 'accuracy', | |||||
| render: tableCellRender(true), | |||||
| }); | |||||
| } | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-history']}> | <div className={styles['experiment-history']}> | ||||
| <div className={styles['experiment-history__content']}> | <div className={styles['experiment-history__content']}> | ||||
| @@ -1,6 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { useCheck } from '@/hooks'; | |||||
| import { useCheck } from '@/hooks/useCheck'; | |||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { type ExperimentInstance } from '@/types'; | import { type ExperimentInstance } from '@/types'; | ||||
| @@ -58,7 +58,8 @@ function ExperimentInstanceComponent({ | |||||
| // 删除实验实例确认 | // 删除实验实例确认 | ||||
| const handleRemove = (instance: ExperimentInstance) => { | const handleRemove = (instance: ExperimentInstance) => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '确定删除该条实例吗?', | |||||
| title: '删除后,该实验实例将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | onOk: () => { | ||||
| deleteExperimentInstance(instance.id); | deleteExperimentInstance(instance.id); | ||||
| }, | }, | ||||
| @@ -96,6 +97,18 @@ function ExperimentInstanceComponent({ | |||||
| } | } | ||||
| }; | }; | ||||
| // 终止实验实例 | |||||
| const handleTerminate = (instance: ExperimentInstance) => { | |||||
| modalConfirm({ | |||||
| title: '终止后,该次实验运行将不可恢复', | |||||
| content: '是否确认终止?', | |||||
| isDelete: false, | |||||
| onOk: () => { | |||||
| terminateExperimentInstance(instance); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 终止实验实例 | // 终止实验实例 | ||||
| const terminateExperimentInstance = async (instance: ExperimentInstance) => { | const terminateExperimentInstance = async (instance: ExperimentInstance) => { | ||||
| const request = config.stopInsReq; | const request = config.stopInsReq; | ||||
| @@ -188,7 +201,7 @@ function ExperimentInstanceComponent({ | |||||
| item.status === ExperimentStatus.Terminated | item.status === ExperimentStatus.Terminated | ||||
| } | } | ||||
| icon={<KFIcon type="icon-zhongzhi" />} | icon={<KFIcon type="icon-zhongzhi" />} | ||||
| onClick={() => terminateExperimentInstance(item)} | |||||
| onClick={() => handleTerminate(item)} | |||||
| > | > | ||||
| 终止 | 终止 | ||||
| </Button> | </Button> | ||||
| @@ -7,7 +7,7 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { AutoMLData } from '@/pages/AutoML/types'; | import { AutoMLData } from '@/pages/AutoML/types'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| @@ -93,17 +93,14 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| const [res] = await to(request(record.id)); | const [res] = await to(request(record.id)); | ||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | // 否则直接刷新这一页的数据 | ||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | ...prev, | ||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getAutoMLList(); | |||||
| } | |||||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -188,6 +185,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| if (expanded) { | if (expanded) { | ||||
| setExpandedRowKeys([record.id]); | setExpandedRowKeys([record.id]); | ||||
| getExperimentInsList(record.id, 0); | getExperimentInsList(record.id, 0); | ||||
| refreshExperimentList(); | |||||
| } else { | } else { | ||||
| setExpandedRowKeys([]); | setExpandedRowKeys([]); | ||||
| } | } | ||||
| @@ -75,8 +75,15 @@ function CodeConfigList() { | |||||
| const deleteRecord = async (id: number) => { | const deleteRecord = async (id: number) => { | ||||
| const [res] = await to(deleteCodeConfigReq(id)); | const [res] = await to(deleteCodeConfigReq(id)); | ||||
| if (res) { | if (res) { | ||||
| getDataList(); | |||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | |||||
| current: dataList!.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -70,7 +70,12 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__branch']} | |||||
| ellipsis={{ tooltip: item.git_branch }} | |||||
| > | |||||
| {item.git_branch} | |||||
| </Typography.Paragraph> | |||||
| </div> | </div> | ||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['code-config-item__user']}> | <div className={styles['code-config-item__user']}> | ||||
| @@ -4,7 +4,12 @@ import KFModal from '@/components/KFModal'; | |||||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | ||||
| import { addDataset } from '@/services/dataset/index.js'; | import { addDataset } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, limitUploadFileType, validateUploadFiles } from '@/utils/ui'; | |||||
| import { | |||||
| getFileListFromEvent, | |||||
| limitUploadFileType, | |||||
| removeUploadedFile, | |||||
| validateUploadFiles, | |||||
| } from '@/utils/ui'; | |||||
| import { | import { | ||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| @@ -29,11 +34,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| // const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||||
| // useEffect(() => { | |||||
| // getClusterOptions(); | |||||
| // }, []); | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -44,16 +44,9 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| defaultFileList: [], | defaultFileList: [], | ||||
| accept: '.zip,.tgz', | accept: '.zip,.tgz', | ||||
| beforeUpload: limitUploadFileType('zip,tgz'), | beforeUpload: limitUploadFileType('zip,tgz'), | ||||
| onRemove: removeUploadedFile, | |||||
| }; | }; | ||||
| // 获取集群版本数据 | |||||
| // const getClusterOptions = async () => { | |||||
| // const [res] = await to(getDictSelectOption('available_cluster')); | |||||
| // if (res) { | |||||
| // setClusterOptions(res); | |||||
| // } | |||||
| // }; | |||||
| // 上传请求 | // 上传请求 | ||||
| const createDataset = async (params: any) => { | const createDataset = async (params: any) => { | ||||
| const [res] = await to(addDataset(params)); | const [res] = await to(addDataset(params)); | ||||
| @@ -113,7 +106,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={50} /> | |||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={40} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="数据集版本" | label="数据集版本" | ||||
| @@ -159,27 +152,42 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {/* <Form.Item label="集群版本" name="available_cluster"> | |||||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | |||||
| </Form.Item> */} | |||||
| <Form.Item | <Form.Item | ||||
| label="数据集简介" | |||||
| label="数据集描述" | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入数据集简介', | |||||
| message: '请输入数据集描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入数据集简介" | |||||
| placeholder="请输入数据集描述" | |||||
| showCount | showCount | ||||
| maxLength={200} | maxLength={200} | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="version_desc" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={200} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal'; | |||||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | ||||
| import { addModel } from '@/services/dataset/index.js'; | import { addModel } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||||
| import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui'; | |||||
| import { | import { | ||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| @@ -37,6 +37,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| Authorization: getAccessToken() || '', | Authorization: getAccessToken() || '', | ||||
| }, | }, | ||||
| defaultFileList: [], | defaultFileList: [], | ||||
| onRemove: removeUploadedFile, | |||||
| }; | }; | ||||
| // 上传请求 | // 上传请求 | ||||
| @@ -96,7 +97,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={50} /> | |||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={40} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型版本" | label="模型版本" | ||||
| @@ -143,23 +144,41 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型简介" | |||||
| label="模型描述" | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入模型简介', | |||||
| message: '请输入模型描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入模型简介" | |||||
| placeholder="请输入模型描述" | |||||
| maxLength={200} | maxLength={200} | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="version_desc" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={200} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -3,7 +3,7 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||||
| import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui'; | |||||
| import { | import { | ||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| @@ -50,6 +50,7 @@ function AddVersionModal({ | |||||
| defaultFileList: [], | defaultFileList: [], | ||||
| beforeUpload: config.beforeUpload, | beforeUpload: config.beforeUpload, | ||||
| accept: config.uploadAccept, | accept: config.uploadAccept, | ||||
| onRemove: removeUploadedFile, | |||||
| }; | }; | ||||
| // 上传请求 | // 上传请求 | ||||
| @@ -13,6 +13,7 @@ import { | |||||
| } from '@/pages/Dataset/config'; | } from '@/pages/Dataset/config'; | ||||
| import GraphLegend from '@/pages/Model/components/GraphLegend'; | import GraphLegend from '@/pages/Model/components/GraphLegend'; | ||||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | ||||
| import { VersionChangedMessage } from '@/utils/constant'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| @@ -124,6 +125,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| onOk: () => { | onOk: () => { | ||||
| getVersionList(true); | getVersionList(true); | ||||
| close(); | close(); | ||||
| window.postMessage({ type: VersionChangedMessage }); | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -170,6 +172,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| getVersionList(true); | getVersionList(true); | ||||
| window.postMessage({ type: VersionChangedMessage }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -21,7 +21,7 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ | |||||
| value: data.name, | value: data.name, | ||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | |||||
| label: '数据集版本', | |||||
| value: data.version, | value: data.version, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -64,7 +64,7 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [ | |||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | |||||
| label: '模型版本', | |||||
| value: data.version, | value: data.version, | ||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| @@ -107,8 +107,15 @@ function ResourceList( | |||||
| const request = config.deleteRecord; | const request = config.deleteRecord; | ||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res) { | if (res) { | ||||
| getDataList(); | |||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | |||||
| current: dataList!.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { getAssetIcon } from '@/services/dataset/index.js'; | import { getAssetIcon } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Flex, Tabs, type TabsProps } from 'antd'; | import { Flex, Tabs, type TabsProps } from 'antd'; | ||||
| @@ -1,3 +1,9 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2025-03-24 15:41:42 | |||||
| * @Description: 版本文件列表 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { | import { | ||||
| ResourceData, | ResourceData, | ||||
| @@ -11,7 +11,6 @@ | |||||
| text-align: center; | text-align: center; | ||||
| background: @background; | background: @background; | ||||
| border-radius: 4px 4px 0 0; | border-radius: 4px 4px 0 0; | ||||
| .singleLine(); | |||||
| } | } | ||||
| .text() { | .text() { | ||||
| @@ -20,7 +19,6 @@ | |||||
| font-size: 13px; | font-size: 13px; | ||||
| line-height: 22px; | line-height: 22px; | ||||
| word-break: break-all; | word-break: break-all; | ||||
| .singleLine(); | |||||
| } | } | ||||
| .version-container(@background) { | .version-container(@background) { | ||||
| @@ -88,7 +88,7 @@ function VersionCompareModal({ | |||||
| format: formatProject, | format: formatProject, | ||||
| }, | }, | ||||
| { | { | ||||
| key: 'description', | |||||
| key: 'version_desc', | |||||
| text: '版本描述', | text: '版本描述', | ||||
| }, | }, | ||||
| ] | ] | ||||
| @@ -123,7 +123,7 @@ function VersionCompareModal({ | |||||
| format: formatTrainTask, | format: formatTrainTask, | ||||
| }, | }, | ||||
| { | { | ||||
| key: 'description', | |||||
| key: 'version_desc', | |||||
| text: '版本描述', | text: '版本描述', | ||||
| }, | }, | ||||
| ], | ], | ||||
| @@ -193,7 +193,14 @@ function VersionCompareModal({ | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| <div className={styles['version-compare__left']}> | <div className={styles['version-compare__left']}> | ||||
| <div className={styles['version-compare__left__title']}>{v1.version}</div> | |||||
| <div className={styles['version-compare__left__title']}> | |||||
| <Typography.Text | |||||
| ellipsis={{ tooltip: v1.version }} | |||||
| style={{ width: '100%', lineHeight: 'inherit' }} | |||||
| > | |||||
| {v1.version} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| {fields.map(({ key, format }) => { | {fields.map(({ key, format }) => { | ||||
| const text = getValue(v1, key as keyof typeof v1, format); | const text = getValue(v1, key as keyof typeof v1, format); | ||||
| return ( | return ( | ||||
| @@ -203,7 +210,7 @@ function VersionCompareModal({ | |||||
| [styles['version-compare__left__text--different']]: isDifferent(key), | [styles['version-compare__left__text--different']]: isDifferent(key), | ||||
| })} | })} | ||||
| > | > | ||||
| <Typography.Text ellipsis={{ tooltip: text }}> | |||||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ width: '100%' }}> | |||||
| {isEmpty(text) ? '--' : text} | {isEmpty(text) ? '--' : text} | ||||
| </Typography.Text> | </Typography.Text> | ||||
| </div> | </div> | ||||
| @@ -211,7 +218,14 @@ function VersionCompareModal({ | |||||
| })} | })} | ||||
| </div> | </div> | ||||
| <div className={styles['version-compare__right']}> | <div className={styles['version-compare__right']}> | ||||
| <div className={styles['version-compare__right__title']}>{v2.version}</div> | |||||
| <div className={styles['version-compare__right__title']}> | |||||
| <Typography.Text | |||||
| ellipsis={{ tooltip: v2.version }} | |||||
| style={{ width: '100%', lineHeight: 'inherit' }} | |||||
| > | |||||
| {v2.version} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| {fields.map(({ key, format }) => { | {fields.map(({ key, format }) => { | ||||
| const text = getValue(v2, key as keyof typeof v2, format); | const text = getValue(v2, key as keyof typeof v2, format); | ||||
| return ( | return ( | ||||
| @@ -221,7 +235,7 @@ function VersionCompareModal({ | |||||
| [styles['version-compare__right__text--different']]: isDifferent(key), | [styles['version-compare__right__text--different']]: isDifferent(key), | ||||
| })} | })} | ||||
| > | > | ||||
| <Typography.Text ellipsis={{ tooltip: text }}> | |||||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ width: '100%' }}> | |||||
| {isEmpty(text) ? '--' : text} | {isEmpty(text) ? '--' : text} | ||||
| </Typography.Text> | </Typography.Text> | ||||
| </div> | </div> | ||||
| @@ -104,16 +104,16 @@ function EditorCreate() { | |||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="任务名称" | |||||
| label="编辑器名称" | |||||
| name="name" | name="name" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入任务名称', | |||||
| message: '请输入编辑器名称', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入任务名称" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder="请输入编辑器名称" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -6,7 +6,9 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { DevEditorStatus } from '@/enums'; | import { DevEditorStatus } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||||
| import { | import { | ||||
| deleteEditorReq, | deleteEditorReq, | ||||
| getEditorListReq, | getEditorListReq, | ||||
| @@ -14,6 +16,7 @@ import { | |||||
| stopEditorReq, | stopEditorReq, | ||||
| } from '@/services/developmentEnvironment'; | } from '@/services/developmentEnvironment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { parseJsonText } from '@/utils'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| @@ -42,6 +45,10 @@ export type EditorData = { | |||||
| update_by: string; | update_by: string; | ||||
| create_time: string; | create_time: string; | ||||
| url: string; | url: string; | ||||
| computing_resource_id: number; | |||||
| dataset?: string | DatasetData; | |||||
| model?: string | ModelData; | |||||
| image?: string; | |||||
| }; | }; | ||||
| function EditorList() { | function EditorList() { | ||||
| @@ -56,6 +63,7 @@ function EditorList() { | |||||
| pageSize: 10, | pageSize: 10, | ||||
| }, | }, | ||||
| ); | ); | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| // 获取编辑器列表 | // 获取编辑器列表 | ||||
| const getEditorList = useCallback(async () => { | const getEditorList = useCallback(async () => { | ||||
| @@ -66,6 +74,10 @@ function EditorList() { | |||||
| const [res] = await to(getEditorListReq(params)); | const [res] = await to(getEditorListReq(params)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| content.forEach((item: EditorData) => { | |||||
| item.dataset = typeof item.dataset === 'string' ? parseJsonText(item.dataset) : null; | |||||
| item.model = typeof item.model === 'string' ? parseJsonText(item.model) : null; | |||||
| }); | |||||
| setTableData(content); | setTableData(content); | ||||
| setTotal(totalElements); | setTotal(totalElements); | ||||
| } | } | ||||
| @@ -80,17 +92,14 @@ function EditorList() { | |||||
| const [res] = await to(deleteEditorReq(id)); | const [res] = await to(deleteEditorReq(id)); | ||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | // 否则直接刷新这一页的数据 | ||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | ...prev, | ||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getEditorList(); | |||||
| } | |||||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -105,11 +114,18 @@ function EditorList() { | |||||
| // 停止编辑器 | // 停止编辑器 | ||||
| const stopEditor = async (id: number) => { | const stopEditor = async (id: number) => { | ||||
| const [res] = await to(stopEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getEditorList(); | |||||
| } | |||||
| modalConfirm({ | |||||
| title: '停止后,该编辑器将不可使用', | |||||
| content: '是否确认停止?', | |||||
| isDelete: false, | |||||
| onOk: async () => { | |||||
| const [res] = await to(stopEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getEditorList(); | |||||
| } | |||||
| }, | |||||
| }); | |||||
| }; | }; | ||||
| // 制作镜像 | // 制作镜像 | ||||
| @@ -168,44 +184,72 @@ function EditorList() { | |||||
| title: '编辑器名称', | title: '编辑器名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| width: '30%', | |||||
| render: (text, record) => | |||||
| record.url && record.status === DevEditorStatus.Running ? ( | |||||
| <a className="kf-table-row-link" onClick={(e) => gotoEditorPage(e, record)}> | |||||
| {text} | |||||
| </a> | |||||
| ) : ( | |||||
| <span>{text ?? '--'}</span> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: '10%', | |||||
| render: EditorStatusCell, | |||||
| width: '20%', | |||||
| render: (text, record, index) => | |||||
| record.url && record.status === DevEditorStatus.Running | |||||
| ? tableCellRender<EditorData>(true, TableCellValueType.Link, { | |||||
| onClick: (record, e) => gotoEditorPage(e, record), | |||||
| })(text, record, index) | |||||
| : tableCellRender<EditorData>(true, TableCellValueType.Text)(text, record, index), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '资源', | |||||
| title: '计算资源', | |||||
| dataIndex: 'computing_resource', | dataIndex: 'computing_resource', | ||||
| key: 'computing_resource', | key: 'computing_resource', | ||||
| width: '20%', | |||||
| width: 100, | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | |||||
| title: '资源规格', | |||||
| dataIndex: 'computing_resource_id', | |||||
| key: 'computing_resource_id', | |||||
| width: '20%', | |||||
| render: tableCellRender(true, TableCellValueType.Custom, { | |||||
| format: getResourceDescription, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '数据集', | |||||
| dataIndex: ['dataset', 'showValue'], | |||||
| key: 'dataset', | |||||
| width: '15%', | |||||
| render: tableCellRender(true), | |||||
| }, | |||||
| { | |||||
| title: '模型', | |||||
| dataIndex: ['model', 'showValue'], | |||||
| key: 'model', | |||||
| width: '15%', | |||||
| render: tableCellRender(true), | |||||
| }, | |||||
| { | |||||
| title: '镜像', | |||||
| dataIndex: ['image'], | |||||
| key: 'image', | |||||
| width: '15%', | |||||
| render: tableCellRender(true), | |||||
| }, | |||||
| { | { | ||||
| title: '创建者', | title: '创建者', | ||||
| dataIndex: 'update_by', | dataIndex: 'update_by', | ||||
| key: 'update_by', | key: 'update_by', | ||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| width: '15%', | |||||
| render: tableCellRender(true), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: '20%', | |||||
| width: 180, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | render: tableCellRender(false, TableCellValueType.Date), | ||||
| }, | }, | ||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: 80, | |||||
| render: EditorStatusCell, | |||||
| }, | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| @@ -20,7 +20,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||||
| }), | }), | ||||
| ); | ); | ||||
| if (res) { | if (res) { | ||||
| message.success('创建成功,请到 “AI资产” - “个人镜像” 中查看'); | |||||
| message.success('创建成功,请到 “多形态资源库” - “个人镜像” 中查看'); | |||||
| onOk?.(); | onOk?.(); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -51,20 +51,20 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||||
| message: '请输入镜像名称', | message: '请输入镜像名称', | ||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-z0-9/_-]*$/, | |||||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||||
| pattern: /^[a-z0-9/._-]*$/, | |||||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear /> | <Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像Tag" | |||||
| label="镜像版本" | |||||
| name="tag_name" | name="tag_name" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像Tag', | |||||
| message: '请输入镜像版本', | |||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-zA-Z0-9._-]+$/, | pattern: /^[a-zA-Z0-9._-]+$/, | ||||
| @@ -72,7 +72,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像描述" | label="镜像描述" | ||||
| @@ -87,7 +87,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入镜像描述" | placeholder="请输入镜像描述" | ||||
| autoSize={{ minRows: 3, maxRows: 6 }} | autoSize={{ minRows: 3, maxRows: 6 }} | ||||
| maxLength={256} | |||||
| maxLength={128} | |||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| @@ -0,0 +1,12 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2025-03-31 16:38:59 | |||||
| * @Description: 实验对比 Aim | |||||
| */ | |||||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||||
| function AimPage() { | |||||
| return <IframePage type={IframePageType.Aim}></IframePage>; | |||||
| } | |||||
| export default AimPage; | |||||
| @@ -12,8 +12,9 @@ import { | |||||
| } from '@/services/experiment'; | } from '@/services/experiment'; | ||||
| import { tableSorter } from '@/utils'; | import { tableSorter } from '@/utils'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| import { useSearchParams } from '@umijs/max'; | |||||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||||
| import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd'; | import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useMemo, useState } from 'react'; | import { useEffect, useMemo, useState } from 'react'; | ||||
| @@ -46,6 +47,7 @@ function ExperimentComparison() { | |||||
| }); | }); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const navigate = useNavigate(); | |||||
| const config = comparisonConfig[comparisonType]; | const config = comparisonConfig[comparisonType]; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -73,7 +75,9 @@ function ExperimentComparison() { | |||||
| const [res] = await to(getExpMetricsReq(selectedRowKeys)); | const [res] = await to(getExpMetricsReq(selectedRowKeys)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const url = res.data; | const url = res.data; | ||||
| window.open(url, '_blank'); | |||||
| // window.open(url, '_blank'); | |||||
| SessionStorage.setItem(SessionStorage.aimUrlKey, url); | |||||
| navigate('../compare-visual'); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -1,5 +1,6 @@ | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { useStateRef } from '@/hooks/useStateRef'; | |||||
| import { useVisible } from '@/hooks/useVisible'; | |||||
| import { getExperimentIns } from '@/services/experiment/index.js'; | import { getExperimentIns } from '@/services/experiment/index.js'; | ||||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | import { getWorkflowById } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| @@ -179,11 +180,12 @@ function ExperimentText() { | |||||
| if (!statusNode) { | if (!statusNode) { | ||||
| return; | return; | ||||
| } | } | ||||
| const { finishedAt, startedAt, phase, id } = statusNode; | |||||
| const { finishedAt, startedAt, phase, id, message } = statusNode; | |||||
| workflowNode.experimentStartTime = startedAt; | workflowNode.experimentStartTime = startedAt; | ||||
| workflowNode.experimentEndTime = finishedAt; | workflowNode.experimentEndTime = finishedAt; | ||||
| workflowNode.experimentStatus = phase; | workflowNode.experimentStatus = phase; | ||||
| workflowNode.workflowId = id; | workflowNode.workflowId = id; | ||||
| workflowNode.message = message; | |||||
| workflowNode.img = phase | workflowNode.img = phase | ||||
| ? `${workflowNode.imgName}-${phase}.png` | ? `${workflowNode.imgName}-${phase}.png` | ||||
| : `${workflowNode.imgName}.png`; | : `${workflowNode.imgName}.png`; | ||||
| @@ -3,7 +3,7 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Form, Input, Radio, Select, type FormRule } from 'antd'; | |||||
| import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | |||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -63,13 +63,14 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||||
| }; | }; | ||||
| // 根据参数设置 label | // 根据参数设置 label | ||||
| export const getParamType = (param: PipelineGlobalParam): string => { | |||||
| export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | |||||
| const paramTypes: Readonly<Record<number, string>> = { | const paramTypes: Readonly<Record<number, string>> = { | ||||
| 1: '字符串', | 1: '字符串', | ||||
| 2: '整型', | 2: '整型', | ||||
| 3: '布尔类型', | 3: '布尔类型', | ||||
| }; | }; | ||||
| return param.param_name + `(${paramTypes[param.param_type]})`; | |||||
| const label = param.param_name + `(${paramTypes[param.param_type]})`; | |||||
| return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | |||||
| }; | }; | ||||
| function AddExperimentModal({ | function AddExperimentModal({ | ||||
| @@ -99,8 +100,8 @@ function AddExperimentModal({ | |||||
| }; | }; | ||||
| const paramLayout = { | const paramLayout = { | ||||
| labelCol: { span: 8 }, | |||||
| wrapperCol: { span: 16 }, | |||||
| labelCol: { span: 6 }, | |||||
| wrapperCol: { span: 18 }, | |||||
| }; | }; | ||||
| // 除了流水线选择发生变化 | // 除了流水线选择发生变化 | ||||
| @@ -157,7 +158,6 @@ function AddExperimentModal({ | |||||
| form={form} | form={form} | ||||
| {...layout} | {...layout} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| labelWrap | |||||
| > | > | ||||
| <Form.Item | <Form.Item | ||||
| label="实验名称" | label="实验名称" | ||||
| @@ -215,9 +215,9 @@ function AddExperimentModal({ | |||||
| {...restField} | {...restField} | ||||
| {...paramLayout} | {...paramLayout} | ||||
| key={key} | key={key} | ||||
| label={getParamType(globalParam[name])} | |||||
| label={getParamLabel(globalParam[name])} | |||||
| name={[name, 'param_value']} | name={[name, 'param_value']} | ||||
| rules={getParamRules(globalParam[name]['param_type'])} | |||||
| rules={getParamRules(globalParam[name]['param_type'], true)} | |||||
| > | > | ||||
| {getParamComponent( | {getParamComponent( | ||||
| globalParam[name]['param_type'], | globalParam[name]['param_type'], | ||||
| @@ -13,7 +13,6 @@ | |||||
| } | } | ||||
| &__tabs { | &__tabs { | ||||
| height: calc(100% - 169px); | |||||
| :global { | :global { | ||||
| .ant-tabs-nav { | .ant-tabs-nav { | ||||
| padding-left: 24px; | padding-left: 24px; | ||||
| @@ -35,7 +34,7 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| margin-bottom: 15px; | margin-bottom: 15px; | ||||
| padding-left: 24px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 15px; | font-size: 15px; | ||||
| } | } | ||||
| @@ -3,7 +3,7 @@ import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | import { PipelineNodeModelSerialize } from '@/types'; | ||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | ||||
| import { Drawer, Tabs } from 'antd'; | |||||
| import { Drawer, Tabs, Typography } from 'antd'; | |||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import ExperimentParameter from '../ExperimentParameter'; | import ExperimentParameter from '../ExperimentParameter'; | ||||
| import ExperimentResult from '../ExperimentResult'; | import ExperimentResult from '../ExperimentResult'; | ||||
| @@ -129,6 +129,14 @@ const ExperimentDrawer = ({ | |||||
| '--' | '--' | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| {instanceNodeData.message && ( | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| <div style={{ flex: 'none' }}>消息:</div> | |||||
| <Typography.Text ellipsis={{ tooltip: instanceNodeData.message }}> | |||||
| {instanceNodeData.message ?? '--'} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| )} | |||||
| <div className={styles['experiment-drawer__info']}> | <div className={styles['experiment-drawer__info']}> | ||||
| 启动时间:{formatDate(instanceNodeStartTime)} | 启动时间:{formatDate(instanceNodeStartTime)} | ||||
| </div> | </div> | ||||
| @@ -137,7 +145,14 @@ const ExperimentDrawer = ({ | |||||
| {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} | {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} /> | |||||
| <Tabs | |||||
| defaultActiveKey="1" | |||||
| items={items} | |||||
| className={styles['experiment-drawer__tabs']} | |||||
| style={{ | |||||
| height: instanceNodeData.message ? 'calc(100% - 169px - 39px)' : 'calc(100% - 169px)', | |||||
| }} | |||||
| /> | |||||
| </Drawer> | </Drawer> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,6 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { useCheck } from '@/hooks'; | |||||
| import { useCheck } from '@/hooks/useCheck'; | |||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { | import { | ||||
| deleteManyExperimentIns, | deleteManyExperimentIns, | ||||
| @@ -62,7 +62,8 @@ function ExperimentInstanceComponent({ | |||||
| // 删除实验实例确认 | // 删除实验实例确认 | ||||
| const handleRemove = (instance: ExperimentInstance) => { | const handleRemove = (instance: ExperimentInstance) => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '确定删除该条实例吗?', | |||||
| title: '删除后,该实验实例将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | onOk: () => { | ||||
| deleteExperimentInstance(instance.id); | deleteExperimentInstance(instance.id); | ||||
| }, | }, | ||||
| @@ -101,7 +102,8 @@ function ExperimentInstanceComponent({ | |||||
| // 终止实验实例 | // 终止实验实例 | ||||
| const handleTerminate = (instance: ExperimentInstance) => { | const handleTerminate = (instance: ExperimentInstance) => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '确定要终止此次实验运行吗?', | |||||
| title: '终止后,该次实验运行将不可恢复', | |||||
| content: '是否确认终止?', | |||||
| isDelete: false, | isDelete: false, | ||||
| onOk: () => { | onOk: () => { | ||||
| terminateExperimentInstance(instance); | terminateExperimentInstance(instance); | ||||
| @@ -5,7 +5,7 @@ | |||||
| */ | */ | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { useStateRef } from '@/hooks'; | |||||
| import { useStateRef } from '@/hooks/useStateRef'; | |||||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | import { getExperimentPodsLog } from '@/services/experiment/index.js'; | ||||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| @@ -1,31 +1,14 @@ | |||||
| .params_container { | |||||
| max-height: 230px; | |||||
| padding: 15px 15px 0; | |||||
| .params-container { | |||||
| max-height: calc(100vh - 300px); | |||||
| padding: 24px 24px 0; | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| &_line { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 15px; | |||||
| &_label { | |||||
| width: 180px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &_value { | |||||
| flex: 1; | |||||
| width: 100px; | |||||
| margin-left: 15px; | |||||
| padding: 10px 20px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 20px; | |||||
| background: #f6f6f6; | |||||
| border: 1px solid #e0e0e1; | |||||
| border-radius: 4px; | |||||
| } | |||||
| .params-empty { | |||||
| :global { | |||||
| .kf-empty__image { | |||||
| width: 300px; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,9 +4,11 @@ | |||||
| * @Description: 查看实验使用的参数 | * @Description: 查看实验使用的参数 | ||||
| */ | */ | ||||
| import parameterImg from '@/assets/img/modal-parameter.png'; | import parameterImg from '@/assets/img/modal-parameter.png'; | ||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { getParamType } from '../AddExperimentModal'; | |||||
| import { Form } from 'antd'; | |||||
| import { getParamComponent, getParamLabel } from '../AddExperimentModal'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ParamsModalProps = { | type ParamsModalProps = { | ||||
| @@ -26,14 +28,44 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| cancelButtonProps={{ style: { display: 'none' } }} | cancelButtonProps={{ style: { display: 'none' } }} | ||||
| width={825} | width={825} | ||||
| > | > | ||||
| <div className={styles.params_container}> | |||||
| {globalParam?.map((item) => ( | |||||
| <div key={item.param_name} className={styles.params_container_line}> | |||||
| <span className={styles.params_container_line_label}>{getParamType(item)}</span> | |||||
| <span className={styles.params_container_line_value}>{item.param_value}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| {Array.isArray(globalParam) && globalParam.length > 0 ? ( | |||||
| <div className={styles['params-container']}> | |||||
| <Form | |||||
| name="view_params_form" | |||||
| labelCol={{ span: 6 }} | |||||
| wrapperCol={{ span: 18 }} | |||||
| initialValues={{ global_param: globalParam }} | |||||
| labelAlign="left" | |||||
| disabled | |||||
| > | |||||
| <Form.List name="global_param"> | |||||
| {(fields) => | |||||
| fields.map(({ key, name, ...restField }) => ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| key={key} | |||||
| name={[name, 'param_value']} | |||||
| label={getParamLabel(globalParam[name])} | |||||
| > | |||||
| {getParamComponent( | |||||
| globalParam[name]['param_type'], | |||||
| globalParam[name]['is_sensitive'], | |||||
| )} | |||||
| </Form.Item> | |||||
| )) | |||||
| } | |||||
| </Form.List> | |||||
| </Form> | |||||
| </div> | |||||
| ) : ( | |||||
| <KFEmpty | |||||
| className={styles['params-empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content="该流水线没有设置全局参数" | |||||
| hasFooter={false} | |||||
| /> | |||||
| )} | |||||
| </KFModal> | </KFModal> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,7 +1,7 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { | import { | ||||
| deleteExperimentById, | deleteExperimentById, | ||||
| getExperiment, | getExperiment, | ||||
| @@ -206,6 +206,7 @@ function Experiment() { | |||||
| setExpandedRowKeys(null); | setExpandedRowKeys(null); | ||||
| } else { | } else { | ||||
| getQueryByExperiment(record.id, 0); | getQueryByExperiment(record.id, 0); | ||||
| refreshExperimentList(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -285,8 +286,6 @@ function Experiment() { | |||||
| message.success('运行成功'); | message.success('运行成功'); | ||||
| refreshExperimentList(); | refreshExperimentList(); | ||||
| refreshExperimentIns(id); | refreshExperimentIns(id); | ||||
| } else { | |||||
| message.error('运行失败'); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -377,6 +376,31 @@ function Experiment() { | |||||
| getQueryByExperiment(expandedRowKeys, page); | getQueryByExperiment(expandedRowKeys, page); | ||||
| }; | }; | ||||
| // 处理删除 | |||||
| const handleExperimentDelete = (record) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该实验将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteExperimentById(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | |||||
| current: experimentList.length === 1 ? Math.max(1, prev.current - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| title: '实验名称', | title: '实验名称', | ||||
| @@ -475,22 +499,7 @@ function Experiment() { | |||||
| size="small" | size="small" | ||||
| key="batchRemove" | key="batchRemove" | ||||
| icon={<KFIcon type="icon-shanchu" />} | icon={<KFIcon type="icon-shanchu" />} | ||||
| onClick={() => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该实验将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteExperimentById(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getExperimentList(); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| onClick={() => handleExperimentDelete(record)} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| @@ -499,6 +508,7 @@ function Experiment() { | |||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| return ( | return ( | ||||
| <div className={styles['experiment-list']}> | <div className={styles['experiment-list']}> | ||||
| <PageTitle title="实验列表"></PageTitle> | <PageTitle title="实验列表"></PageTitle> | ||||
| @@ -1,6 +1,6 @@ | |||||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | ||||
| import { hyperParameterOptimizedMode } from '@/enums'; | import { hyperParameterOptimizedMode } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { | import { | ||||
| schedulerAlgorithms, | schedulerAlgorithms, | ||||
| @@ -44,7 +44,7 @@ const mirrorRadioItems: KFRadioItem[] = [ | |||||
| function MirrorCreate() { | function MirrorCreate() { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [nameDisabled, setNameDisabled] = useState(false); | |||||
| const [isAddVersion, setIsAddVersion] = useState(false); // 是制作镜像还是新增镜像版本 | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -60,7 +60,7 @@ function MirrorCreate() { | |||||
| const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | ||||
| if (name) { | if (name) { | ||||
| form.setFieldValue('name', name); | form.setFieldValue('name', name); | ||||
| setNameDisabled(true); | |||||
| setIsAddVersion(true); | |||||
| } | } | ||||
| return () => { | return () => { | ||||
| SessionStorage.removeItem(SessionStorage.mirrorNameKey); | SessionStorage.removeItem(SessionStorage.mirrorNameKey); | ||||
| @@ -70,32 +70,37 @@ function MirrorCreate() { | |||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| const createPublicMirror = async (formData: FormData) => { | const createPublicMirror = async (formData: FormData) => { | ||||
| const upload_type = formData['upload_type']; | const upload_type = formData['upload_type']; | ||||
| let params; | |||||
| if (upload_type === CommonTabKeys.Public) { | if (upload_type === CommonTabKeys.Public) { | ||||
| params = { | |||||
| const params = { | |||||
| ...omit(formData, ['upload_type']), | ...omit(formData, ['upload_type']), | ||||
| upload_type: 0, | upload_type: 0, | ||||
| image_type: 0, | image_type: 0, | ||||
| }; | }; | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| } else { | } else { | ||||
| const fileList = formData['fileList'] ?? []; | const fileList = formData['fileList'] ?? []; | ||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const file = fileList[0]; | const file = fileList[0]; | ||||
| params = { | |||||
| const params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | ...omit(formData, ['fileList', 'upload_type']), | ||||
| path: file.response.data.url, | path: file.response.data.url, | ||||
| file_size: file.response.data.fileSize, | file_size: file.response.data.fileSize, | ||||
| file_name: file.response.data.fileName, | |||||
| upload_type: 1, | upload_type: 1, | ||||
| image_type: 0, | image_type: 0, | ||||
| }; | }; | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| }; | }; | ||||
| // 提交 | // 提交 | ||||
| @@ -118,14 +123,16 @@ function MirrorCreate() { | |||||
| return true; | return true; | ||||
| }; | }; | ||||
| const descTitle = isAddVersion ? '版本描述' : '镜像描述'; | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-create']}> | <div className={styles['mirror-create']}> | ||||
| <PageTitle title="创建镜像"></PageTitle> | |||||
| <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> | |||||
| <div className={styles['mirror-create__content']}> | <div className={styles['mirror-create__content']}> | ||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| name="mirror-create" | name="mirror-create" | ||||
| labelCol={{ flex: '130px' }} | |||||
| labelCol={{ flex: '135px' }} | |||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| @@ -142,7 +149,7 @@ function MirrorCreate() { | |||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像名称及Tag" | |||||
| label="镜像名称和版本" | |||||
| name="name" | name="name" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| @@ -150,15 +157,15 @@ function MirrorCreate() { | |||||
| message: '请输入镜像名称', | message: '请输入镜像名称', | ||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-z0-9/_-]*$/, | |||||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||||
| pattern: /^[a-z0-9/._-]*$/, | |||||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input | <Input | ||||
| placeholder="请输入镜像名称" | placeholder="请输入镜像名称" | ||||
| maxLength={64} | maxLength={64} | ||||
| disabled={nameDisabled} | |||||
| disabled={isAddVersion} | |||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| @@ -174,33 +181,33 @@ function MirrorCreate() { | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像Tag', | |||||
| message: '请输入镜像版本', | |||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-zA-Z0-9._-]+$/, | pattern: /^[a-zA-Z0-9._-]+$/, | ||||
| message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| message: '镜像版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={20}> | <Col span={20}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像描述" | |||||
| label={descTitle} | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像描述', | |||||
| message: `请输入${descTitle}`, | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| placeholder="请输入镜像描述,最长128字符" | |||||
| placeholder={`请输入${descTitle}`} | |||||
| maxLength={128} | maxLength={128} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| @@ -283,7 +290,7 @@ function MirrorCreate() { | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请上传镜像地址', | |||||
| message: '请上传镜像文件', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| @@ -303,7 +310,7 @@ function MirrorCreate() { | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | ||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| 创建镜像 | |||||
| 确定 | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| @@ -7,8 +7,8 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { MirrorVersionStatus } from '@/enums'; | import { MirrorVersionStatus } from '@/enums'; | ||||
| import { useDomSize } from '@/hooks'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { useDomSize } from '@/hooks/useDomSize'; | |||||
| import { | import { | ||||
| deleteMirrorVersionReq, | deleteMirrorVersionReq, | ||||
| getMirrorInfoReq, | getMirrorInfoReq, | ||||
| @@ -117,17 +117,14 @@ function MirrorInfo() { | |||||
| const [res] = await to(deleteMirrorVersionReq(id)); | const [res] = await to(deleteMirrorVersionReq(id)); | ||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | // 否则直接刷新这一页的数据 | ||||
| // 避免回到第一页 | |||||
| if (tableData.length === 1) { | |||||
| setPagination((prev) => ({ | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | ...prev, | ||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getMirrorVersionList(); | |||||
| } | |||||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -155,7 +152,7 @@ function MirrorInfo() { | |||||
| }; | }; | ||||
| const createMirrorVersion = () => { | const createMirrorVersion = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`add-version`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| @@ -174,20 +171,27 @@ function MirrorInfo() { | |||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| render: tableCellRender(), | |||||
| width: '25%', | |||||
| render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }), | |||||
| }, | |||||
| { | |||||
| title: '版本描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: tableCellRender(true), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| key: 'status', | key: 'status', | ||||
| width: 150, | |||||
| width: 100, | |||||
| render: MirrorStatusCell, | render: MirrorStatusCell, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像大小', | title: '镜像大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: 150, | |||||
| width: 120, | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -200,7 +204,7 @@ function MirrorInfo() { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: 150, | |||||
| width: 120, | |||||
| key: 'operation', | key: 'operation', | ||||
| hidden: isPublic, | hidden: isPublic, | ||||
| render: (_: any, record: MirrorVersionData) => ( | render: (_: any, record: MirrorVersionData) => ( | ||||
| @@ -5,7 +5,7 @@ | |||||
| */ | */ | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| @@ -103,17 +103,14 @@ function MirrorList() { | |||||
| const [res] = await to(deleteMirrorReq(id)); | const [res] = await to(deleteMirrorReq(id)); | ||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | // 否则直接刷新这一页的数据 | ||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | ...prev, | ||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getMirrorList(); | |||||
| } | |||||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -128,7 +125,7 @@ function MirrorList() { | |||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: MirrorData) => { | const toDetail = (record: MirrorData) => { | ||||
| navigate(`/dataset/mirror/info/${record.id}`); | |||||
| navigate(`info/${record.id}`); | |||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| pagination, | pagination, | ||||
| @@ -149,7 +146,7 @@ function MirrorList() { | |||||
| // 创建镜像 | // 创建镜像 | ||||
| const createMirror = () => { | const createMirror = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`create`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| @@ -262,7 +259,7 @@ function MirrorList() { | |||||
| onClick={createMirror} | onClick={createMirror} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 制作镜像 | |||||
| 创建镜像 | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| <Button | <Button | ||||
| @@ -4,12 +4,12 @@ | |||||
| * @Description: 模型演化 | * @Description: 模型演化 | ||||
| */ | */ | ||||
| import { useEffectWhen } from '@/hooks'; | |||||
| import { useEffectWhen } from '@/hooks/useEffectWhen'; | |||||
| import { getModelAtlasReq } from '@/services/dataset/index.js'; | import { getModelAtlasReq } from '@/services/dataset/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; | import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; | ||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| import NodeTooltips from '../NodeTooltips'; | import NodeTooltips from '../NodeTooltips'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -73,17 +73,45 @@ function ModelEvolution({ | |||||
| }; | }; | ||||
| }, []); | }, []); | ||||
| useEffectWhen( | |||||
| () => { | |||||
| if (version) { | |||||
| getModelAtlas(); | |||||
| } else { | |||||
| clearGraphData(); | |||||
| } | |||||
| }, | |||||
| isActive, | |||||
| [resourceId, version], | |||||
| ); | |||||
| const getModelAtlas = useCallback(async () => { | |||||
| // 请求失败或者版本不存在时,清除图形 | |||||
| function clearGraphData() { | |||||
| graph.data({ | |||||
| nodes: [], | |||||
| edges: [], | |||||
| }); | |||||
| graph.render(); | |||||
| graph.fitView(); | |||||
| } | |||||
| if (!resourceId || !identifier || !version) { | |||||
| clearGraphData(); | |||||
| return; | |||||
| } | |||||
| const params = { | |||||
| id: resourceId, | |||||
| identifier, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(getModelAtlasReq(params)); | |||||
| if (res && res.data) { | |||||
| const data = normalizeTreeData(res.data); | |||||
| apiData.current = data; | |||||
| hierarchyNodes.current = traverseHierarchically(data); | |||||
| const graphData = getGraphData(data, hierarchyNodes.current); | |||||
| graph.data(graphData); | |||||
| graph.render(); | |||||
| graph.fitView(); | |||||
| setShowNodeTooltip(false); | |||||
| setEnterTooltip(false); | |||||
| } else { | |||||
| clearGraphData(); | |||||
| } | |||||
| }, [resourceId, identifier, version]); | |||||
| useEffectWhen(getModelAtlas, isActive, [resourceId, identifier, version]); | |||||
| // 初始化图 | // 初始化图 | ||||
| const initGraph = () => { | const initGraph = () => { | ||||
| @@ -249,40 +277,6 @@ function ModelEvolution({ | |||||
| }, 100); | }, 100); | ||||
| }; | }; | ||||
| // 获取模型依赖 | |||||
| const getModelAtlas = async () => { | |||||
| const params = { | |||||
| id: resourceId, | |||||
| identifier, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(getModelAtlasReq(params)); | |||||
| if (res && res.data) { | |||||
| const data = normalizeTreeData(res.data); | |||||
| apiData.current = data; | |||||
| hierarchyNodes.current = traverseHierarchically(data); | |||||
| const graphData = getGraphData(data, hierarchyNodes.current); | |||||
| graph.data(graphData); | |||||
| graph.render(); | |||||
| graph.fitView(); | |||||
| setShowNodeTooltip(false); | |||||
| setEnterTooltip(false); | |||||
| } else { | |||||
| clearGraphData(); | |||||
| } | |||||
| }; | |||||
| // 请求失败或者版本不存在时,清除图形 | |||||
| function clearGraphData() { | |||||
| graph.data({ | |||||
| nodes: [], | |||||
| edges: [], | |||||
| }); | |||||
| graph.render(); | |||||
| graph.fitView(); | |||||
| } | |||||
| return ( | return ( | ||||
| <div className={styles['model-evolution']}> | <div className={styles['model-evolution']}> | ||||
| <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | ||||
| @@ -1,8 +1,9 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import TableColTitle from '@/components/TableColTitle'; | import TableColTitle from '@/components/TableColTitle'; | ||||
| import { useCheck } from '@/hooks'; | |||||
| import { useCheck } from '@/hooks/useCheck'; | |||||
| import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | ||||
| import { tableSorter } from '@/utils'; | import { tableSorter } from '@/utils'; | ||||
| import { VersionChangedMessage } from '@/utils/constant'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender from '@/utils/table'; | import tableCellRender from '@/utils/table'; | ||||
| import { Checkbox, Flex, Table, type TablePaginationConfig, type TableProps } from 'antd'; | import { Checkbox, Flex, Table, type TablePaginationConfig, type TableProps } from 'antd'; | ||||
| @@ -27,10 +28,10 @@ type ModelMetricsProps = { | |||||
| resourceId: number; | resourceId: number; | ||||
| identifier: string; | identifier: string; | ||||
| owner: string; | owner: string; | ||||
| version: string; | |||||
| version: string; // 当前版本 | |||||
| }; | }; | ||||
| function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsProps) { | |||||
| function ModelMetrics({ resourceId, identifier, owner, version, refreshTag }: ModelMetricsProps) { | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | const [pagination, setPagination] = useState<TablePaginationConfig>({ | ||||
| current: 1, | current: 1, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| @@ -59,6 +60,24 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| checkSingleMetrics, | checkSingleMetrics, | ||||
| ] = useCheck(allMetricsNames); | ] = useCheck(allMetricsNames); | ||||
| // 新增,删除版本时,重置分页,然后刷新版本列表 | |||||
| useEffect(() => { | |||||
| const handleMessage = (e: MessageEvent) => { | |||||
| const { type } = e.data; | |||||
| if (type === VersionChangedMessage) { | |||||
| setPagination({ | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| } | |||||
| }; | |||||
| window.addEventListener('message', handleMessage); | |||||
| return () => { | |||||
| window.removeEventListener('message', handleMessage); | |||||
| }; | |||||
| }, []); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取模型版本列表,带有参数和指标数据 | // 获取模型版本列表,带有参数和指标数据 | ||||
| const getModelPageVersions = async () => { | const getModelPageVersions = async () => { | ||||
| @@ -128,6 +147,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| } | } | ||||
| }; | }; | ||||
| // 行勾选 | |||||
| const rowSelection: TableProps<TableData>['rowSelection'] = { | const rowSelection: TableProps<TableData>['rowSelection'] = { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| fixed: 'left', | fixed: 'left', | ||||
| @@ -140,6 +160,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| }), | }), | ||||
| }; | }; | ||||
| // 计算的表格数据 | |||||
| const showTableData = useMemo(() => { | const showTableData = useMemo(() => { | ||||
| const index = tableData.findIndex((item) => item.name === version); | const index = tableData.findIndex((item) => item.name === version); | ||||
| if (index !== -1) { | if (index !== -1) { | ||||
| @@ -8,11 +8,11 @@ import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys, serviceTypeOptions } from '@/enums'; | import { CommonTabKeys, serviceTypeOptions } from '@/enums'; | ||||
| import { createServiceReq, getServiceInfoReq, updateServiceReq } from '@/services/modelDeployment'; | import { createServiceReq, getServiceInfoReq, updateServiceReq } from '@/services/modelDeployment'; | ||||
| import { ServiceCreatedMessage } from '@/utils/constant'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | ||||
| import { useEffect } from 'react'; | import { useEffect } from 'react'; | ||||
| import { createServiceVersionMessage } from '../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| // 表单数据 | // 表单数据 | ||||
| @@ -63,7 +63,7 @@ function CreateService() { | |||||
| navigate(-1); | navigate(-1); | ||||
| if (!serviceId) { | if (!serviceId) { | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| window.postMessage({ type: createServiceVersionMessage, payload: res.data.id }); | |||||
| window.postMessage({ type: ServiceCreatedMessage, payload: res.data.id }); | |||||
| }, 500); | }, 500); | ||||
| } | } | ||||
| } | } | ||||
| @@ -6,7 +6,7 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import { serviceTypeOptions } from '@/enums'; | import { serviceTypeOptions } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| @@ -31,9 +31,9 @@ import { | |||||
| CreateServiceVersionFrom, | CreateServiceVersionFrom, | ||||
| ServiceData, | ServiceData, | ||||
| ServiceOperationType, | ServiceOperationType, | ||||
| createServiceVersionMessage, | |||||
| } from '../types'; | } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import { ServiceCreatedMessage } from '@/utils/constant'; | |||||
| const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions]; | const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions]; | ||||
| @@ -95,7 +95,7 @@ function ModelDeployment() { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const handleMessage = (e: MessageEvent) => { | const handleMessage = (e: MessageEvent) => { | ||||
| const { type, payload } = e.data; | const { type, payload } = e.data; | ||||
| if (type === createServiceVersionMessage) { | |||||
| if (type === ServiceCreatedMessage) { | |||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '创建服务成功', | title: '创建服务成功', | ||||
| content: '是否创建服务版本?', | content: '是否创建服务版本?', | ||||
| @@ -119,17 +119,14 @@ function ModelDeployment() { | |||||
| const [res] = await to(deleteServiceReq(record.id)); | const [res] = await to(deleteServiceReq(record.id)); | ||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | // 否则直接刷新这一页的数据 | ||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | ...prev, | ||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getServiceList(); | |||||
| } | |||||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -18,6 +18,7 @@ | |||||
| &__table { | &__table { | ||||
| flex: 1; | flex: 1; | ||||
| min-height: 0; | |||||
| margin-top: 24px; | margin-top: 24px; | ||||
| } | } | ||||
| } | } | ||||
| @@ -8,8 +8,8 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { | import { | ||||
| deleteServiceVersionReq, | deleteServiceVersionReq, | ||||
| getServiceInfoReq, | getServiceInfoReq, | ||||
| @@ -132,18 +132,15 @@ function ServiceInfo() { | |||||
| const [res] = await to(deleteServiceVersionReq(record.id)); | const [res] = await to(deleteServiceVersionReq(record.id)); | ||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | // 否则直接刷新这一页的数据 | ||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | ...prev, | ||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getServiceInfo(); | |||||
| getServiceVersions(); | |||||
| } | |||||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| getServiceInfo(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -432,7 +429,7 @@ function ServiceInfo() { | |||||
| onClick={() => createServiceVersion(ServiceOperationType.Create)} | onClick={() => createServiceVersion(ServiceOperationType.Create)} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 新增版本 | |||||
| 新增服务版本 | |||||
| </Button> | </Button> | ||||
| <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | ||||
| 版本对比 | 版本对比 | ||||
| @@ -6,14 +6,13 @@ | |||||
| flex-direction: column; | flex-direction: column; | ||||
| height: calc(100% - 60px); | height: calc(100% - 60px); | ||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 0; | |||||
| padding: 10px 30px 0; | |||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| &__tabs { | &__tabs { | ||||
| flex: 1; | flex: 1; | ||||
| min-height: 0; | min-height: 0; | ||||
| margin-top: 20px; | |||||
| padding-bottom: 10px; | padding-bottom: 10px; | ||||
| :global { | :global { | ||||
| @@ -3,9 +3,9 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 服务版本详情 | * @Description: 服务版本详情 | ||||
| */ | */ | ||||
| import FullScreenFrame from '@/components/FullScreenFrame'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { getServiceVersionInfoReq } from '@/services/modelDeployment'; | import { getServiceVersionInfoReq } from '@/services/modelDeployment'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useParams } from '@umijs/max'; | import { useParams } from '@umijs/max'; | ||||
| @@ -18,6 +18,7 @@ import { ServiceVersionData } from '../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export enum ModelDeploymentTabKey { | export enum ModelDeploymentTabKey { | ||||
| Basic = 'Basic', // 基本信息 | |||||
| Predict = 'Predict', // 预测 | Predict = 'Predict', // 预测 | ||||
| Guide = 'Guide', // 调用指南 | Guide = 'Guide', // 调用指南 | ||||
| Log = 'Log', // 服务日志 | Log = 'Log', // 服务日志 | ||||
| @@ -43,10 +44,23 @@ function ServiceVersionInfo() { | |||||
| }, [id]); | }, [id]); | ||||
| const tabItems = [ | const tabItems = [ | ||||
| { | |||||
| key: ModelDeploymentTabKey.Basic, | |||||
| label: '基本信息', | |||||
| icon: <KFIcon type="icon-jibenxinxi" />, | |||||
| children: <VersionBasicInfo info={versionInfo} />, | |||||
| }, | |||||
| { | { | ||||
| key: ModelDeploymentTabKey.Predict, | key: ModelDeploymentTabKey.Predict, | ||||
| label: '预测', | label: '预测', | ||||
| icon: <KFIcon type="icon-yuce" />, | icon: <KFIcon type="icon-yuce" />, | ||||
| children: ( | |||||
| <div style={{ height: '100%', width: '100%' }}> | |||||
| {versionInfo?.page_path && ( | |||||
| <FullScreenFrame url={versionInfo?.page_path}></FullScreenFrame> | |||||
| )} | |||||
| </div> | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| key: ModelDeploymentTabKey.Guide, | key: ModelDeploymentTabKey.Guide, | ||||
| @@ -66,12 +80,6 @@ function ServiceVersionInfo() { | |||||
| <div className={styles['service-version-info']}> | <div className={styles['service-version-info']}> | ||||
| <PageTitle title="服务版本详情"></PageTitle> | <PageTitle title="服务版本详情"></PageTitle> | ||||
| <div className={styles['service-version-info__content']}> | <div className={styles['service-version-info__content']}> | ||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <VersionBasicInfo info={versionInfo} /> | |||||
| <div className={styles['service-version-info__content__tabs']}> | <div className={styles['service-version-info__content__tabs']}> | ||||
| <Tabs items={tabItems} /> | <Tabs items={tabItems} /> | ||||
| </div> | </div> | ||||
| @@ -1,6 +1,6 @@ | |||||
| import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | ||||
| import { ServiceRunStatus } from '@/enums'; | import { ServiceRunStatus } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { formatCodeConfig, formatModel } from '@/utils/format'; | import { formatCodeConfig, formatModel } from '@/utils/format'; | ||||
| @@ -79,6 +79,10 @@ function VersionBasicInfo({ info }: BasicInfoProps) { | |||||
| label: 'API URL', | label: 'API URL', | ||||
| value: info?.url, | value: info?.url, | ||||
| }, | }, | ||||
| { | |||||
| label: '文档地址', | |||||
| value: info?.doc_path, | |||||
| }, | |||||
| { | { | ||||
| label: '副本数量', | label: '副本数量', | ||||
| value: info?.replicas, | value: info?.replicas, | ||||
| @@ -104,7 +108,14 @@ function VersionBasicInfo({ info }: BasicInfoProps) { | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| return <BasicInfo datas={datas} labelWidth={66} labelAlign="justify"></BasicInfo>; | |||||
| return ( | |||||
| <BasicInfo | |||||
| datas={datas} | |||||
| labelWidth={66} | |||||
| labelAlign="justify" | |||||
| style={{ marginTop: 10 }} | |||||
| ></BasicInfo> | |||||
| ); | |||||
| } | } | ||||
| export default VersionBasicInfo; | export default VersionBasicInfo; | ||||
| @@ -1,6 +1,6 @@ | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { ServiceRunStatus } from '@/enums'; | import { ServiceRunStatus } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | ||||
| import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | ||||
| import { isEmpty } from '@/utils'; | import { isEmpty } from '@/utils'; | ||||
| @@ -49,6 +49,8 @@ export type ServiceVersionData = { | |||||
| update_time: string; | update_time: string; | ||||
| create_time: string; | create_time: string; | ||||
| created_by: string; | created_by: string; | ||||
| doc_path?: string; // 文档地址 | |||||
| page_path?: string; // 预测地址 | |||||
| }; | }; | ||||
| // 操作类型 | // 操作类型 | ||||
| @@ -63,6 +65,3 @@ export enum CreateServiceVersionFrom { | |||||
| CreateService = 'CreateService', // 来自创建服务 | CreateService = 'CreateService', // 来自创建服务 | ||||
| ServiceInfo = 'ServiceInfo', // 来自服务详情 | ServiceInfo = 'ServiceInfo', // 来自服务详情 | ||||
| } | } | ||||
| // 去创建服务版本消息 | |||||
| export const createServiceVersionMessage = 'createServiceVersion'; | |||||
| @@ -1,5 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { useStateRef } from '@/hooks/useStateRef'; | |||||
| import { useVisible } from '@/hooks/useVisible'; | |||||
| 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, parseJsonText, s8 } from '@/utils'; | import { fittingString, parseJsonText, s8 } from '@/utils'; | ||||
| @@ -136,31 +136,35 @@ const GlobalParamsDrawer = forwardRef( | |||||
| cur.global_param?.[name]?.param_type | cur.global_param?.[name]?.param_type | ||||
| } | } | ||||
| > | > | ||||
| {({ getFieldValue }) => ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_value']} | |||||
| label="值" | |||||
| rules={getParamRules( | |||||
| getFieldValue(['global_param', name, 'param_type']), | |||||
| true, | |||||
| )} | |||||
| > | |||||
| {getParamComponent(getFieldValue(['global_param', name, 'param_type']))} | |||||
| </Form.Item> | |||||
| )} | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'is_sensitive']} | |||||
| label="脱敏显示" | |||||
| rules={[{ required: true, message: '请选择' }]} | |||||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>是</Radio> | |||||
| <Radio value={0}>否</Radio> | |||||
| </Radio.Group> | |||||
| {({ getFieldValue }) => { | |||||
| const type = getFieldValue(['global_param', name, 'param_type']); | |||||
| return ( | |||||
| <> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_value']} | |||||
| label="值" | |||||
| rules={getParamRules(type, true)} | |||||
| > | |||||
| {getParamComponent(type)} | |||||
| </Form.Item> | |||||
| {type !== 3 && ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'is_sensitive']} | |||||
| label="脱敏显示" | |||||
| rules={[{ required: true, message: '请选择' }]} | |||||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>是</Radio> | |||||
| <Radio value={0}>否</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | </Form.Item> | ||||
| <Tooltip title="删除参数"> | <Tooltip title="删除参数"> | ||||
| <Button | <Button | ||||
| @@ -1,7 +1,7 @@ | |||||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | import CodeSelectorModal from '@/components/CodeSelectorModal'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; | import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; | ||||
| import ParameterSelect from '@/components/ParameterSelect'; | |||||
| import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | |||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| @@ -520,7 +520,8 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| {item.value.type === 'select' ? ( | {item.value.type === 'select' ? ( | ||||
| ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | ||||
| <ParameterSelect | <ParameterSelect | ||||
| dataType={item.value.item_type as any} | |||||
| isPipeline | |||||
| dataType={item.value.item_type as ParameterSelectDataType} | |||||
| placeholder={item.value.placeholder} | placeholder={item.value.placeholder} | ||||
| /> | /> | ||||
| ) : null | ) : null | ||||
| @@ -1,7 +1,7 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { | import { | ||||
| addWorkflow, | addWorkflow, | ||||
| cloneWorkflow, | cloneWorkflow, | ||||
| @@ -11,6 +11,7 @@ import { | |||||
| removeWorkflow, | removeWorkflow, | ||||
| } from '@/services/pipeline/index.js'; | } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd'; | import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd'; | ||||
| @@ -127,6 +128,47 @@ const Pipeline = () => { | |||||
| } | } | ||||
| }; | }; | ||||
| // 处理删除 | |||||
| const handlePipelineDelete = (record) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该流水线将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: async () => { | |||||
| const { id } = record; | |||||
| const [res] = await to(removeWorkflow(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| setPagination((prev) => { | |||||
| return { | |||||
| ...prev, | |||||
| current: pipeList.length === 1 ? Math.max(1, prev.current - 1) : prev.current, | |||||
| }; | |||||
| }); | |||||
| } | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 处理复制 | |||||
| const handlePipelineCopy = (record) => { | |||||
| modalConfirm({ | |||||
| title: '确定复制该条流水线吗?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| isDelete: false, | |||||
| onOk: async () => { | |||||
| const { id } = record; | |||||
| const [res] = await to(cloneWorkflow(id)); | |||||
| if (res) { | |||||
| message.success('复制成功'); | |||||
| getList(); | |||||
| } | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 当前页面切换 | // 当前页面切换 | ||||
| const paginationChange = async (current, pageSize) => { | const paginationChange = async (current, pageSize) => { | ||||
| setPagination({ | setPagination({ | ||||
| @@ -199,30 +241,7 @@ const Pipeline = () => { | |||||
| size="small" | size="small" | ||||
| key="clone" | key="clone" | ||||
| icon={<KFIcon type="icon-fuzhi" />} | icon={<KFIcon type="icon-fuzhi" />} | ||||
| onClick={async () => { | |||||
| modalConfirm({ | |||||
| title: '确定复制该条流水线吗?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| isDelete: false, | |||||
| onOk: () => { | |||||
| cloneWorkflow(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('复制成功'); | |||||
| getList(); | |||||
| } else { | |||||
| message.error('复制失败'); | |||||
| } | |||||
| }); | |||||
| // if (success) { | |||||
| // if (actionRef.current) { | |||||
| // actionRef.current.reload(); | |||||
| // } | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| onClick={() => handlePipelineCopy(record)} | |||||
| > | > | ||||
| 复制 | 复制 | ||||
| </Button> | </Button> | ||||
| @@ -238,28 +257,7 @@ const Pipeline = () => { | |||||
| size="small" | size="small" | ||||
| key="batchRemove" | key="batchRemove" | ||||
| icon={<KFIcon type="icon-shanchu" />} | icon={<KFIcon type="icon-shanchu" />} | ||||
| onClick={() => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该流水线将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| removeWorkflow(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getList(); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| // if (success) { | |||||
| // if (actionRef.current) { | |||||
| // actionRef.current.reload(); | |||||
| // } | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| onClick={() => handlePipelineDelete(record)} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| @@ -25,19 +25,19 @@ enum TaskType { | |||||
| const taskTypeOptions = [ | const taskTypeOptions = [ | ||||
| { | { | ||||
| value: 'dev_environment', | |||||
| value: TaskType.DevEnvironment, | |||||
| label: '开发环境', | label: '开发环境', | ||||
| }, | }, | ||||
| { | { | ||||
| value: 'workflow', | |||||
| value: TaskType.Workflow, | |||||
| label: '实验', | label: '实验', | ||||
| }, | }, | ||||
| { | { | ||||
| value: 'ray', | |||||
| value: TaskType.Ray, | |||||
| label: '超参数自动寻优', | label: '超参数自动寻优', | ||||
| }, | }, | ||||
| { | { | ||||
| value: 'service', | |||||
| value: TaskType.Service, | |||||
| label: '服务', | label: '服务', | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -188,7 +188,7 @@ function PointsDetail() { | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '描述', | |||||
| title: '资源规格', | |||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| @@ -216,6 +216,11 @@ function PointsDetail() { | |||||
| label: '进行中', | label: '进行中', | ||||
| color: themes['primaryColor'], | color: themes['primaryColor'], | ||||
| }, | }, | ||||
| { | |||||
| value: 2, | |||||
| label: '准备中', | |||||
| color: themes['pendingColor'], | |||||
| }, | |||||
| ]), | ]), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -17,6 +17,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const loginPassword = Form.useWatch('password', form); | const loginPassword = Form.useWatch('password', form); | ||||
| const userId = props.values.userId; | const userId = props.values.userId; | ||||
| const originPassword = props.values.originPassword; | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -26,7 +27,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||||
| props.onCancel(); | props.onCancel(); | ||||
| }; | }; | ||||
| const handleFinish = async (values: Record<string, any>) => { | const handleFinish = async (values: Record<string, any>) => { | ||||
| props.onSubmit({ ...values, userId } as FormValueType); | |||||
| props.onSubmit({ password: values.password, userId, originPassword } as FormValueType); | |||||
| }; | }; | ||||
| const checkPassword = (rule: any, value: string) => { | const checkPassword = (rule: any, value: string) => { | ||||
| @@ -63,8 +63,8 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| loginIp: props.values.loginIp, | loginIp: props.values.loginIp, | ||||
| loginDate: props.values.loginDate, | loginDate: props.values.loginDate, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| gitLinkUsername: props.values.gitLinkUsername, | |||||
| gitLinkPassword: props.values.gitLinkPassword, | |||||
| // gitLinkUsername: props.values.gitLinkUsername, | |||||
| // gitLinkPassword: props.values.gitLinkPassword, | |||||
| credit: props.values.credit, | credit: props.values.credit, | ||||
| }); | }); | ||||
| }, [form, props, statusOptions]); | }, [form, props, statusOptions]); | ||||
| @@ -80,6 +80,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| const params = { | const params = { | ||||
| ...values, | ...values, | ||||
| userId: props.values.userId, | userId: props.values.userId, | ||||
| originPassword: props.values.originPassword, | |||||
| }; | }; | ||||
| props.onSubmit(params as UserFormData); | props.onSubmit(params as UserFormData); | ||||
| }; | }; | ||||
| @@ -150,7 +151,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| colProps={{ md: 12, xl: 12 }} | colProps={{ md: 12, xl: 12 }} | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: false, | |||||
| required: true, | |||||
| message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />, | message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -174,7 +175,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| colProps={{ md: 12, xl: 12 }} | colProps={{ md: 12, xl: 12 }} | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: false, | |||||
| required: true, | |||||
| message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />, | message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -194,7 +195,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| id: 'system.user.user_name', | id: 'system.user.user_name', | ||||
| defaultMessage: '用户账号', | defaultMessage: '用户账号', | ||||
| })} | })} | ||||
| hidden={userId} | |||||
| disabled={!!props.values.userId} | |||||
| placeholder="请输入用户账号" | placeholder="请输入用户账号" | ||||
| colProps={{ md: 12, xl: 12 }} | colProps={{ md: 12, xl: 12 }} | ||||
| rules={[ | rules={[ | ||||
| @@ -202,9 +203,9 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| required: true, | required: true, | ||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/, | |||||
| pattern: /^[a-zA-Z](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/, | |||||
| message: | message: | ||||
| '只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以数字或字母开头与结尾', | |||||
| '只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以字母开头,数字或字母结尾', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| /> | /> | ||||
| @@ -214,14 +215,23 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| id: 'system.user.password', | id: 'system.user.password', | ||||
| defaultMessage: '密码', | defaultMessage: '密码', | ||||
| })} | })} | ||||
| hidden={userId} | |||||
| placeholder="请输入密码" | placeholder="请输入密码" | ||||
| colProps={{ md: 12, xl: 12 }} | colProps={{ md: 12, xl: 12 }} | ||||
| fieldProps={{ | fieldProps={{ | ||||
| autoComplete: 'new-password', | autoComplete: 'new-password', | ||||
| }} | }} | ||||
| allowClear | allowClear | ||||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入密码!' }]} | |||||
| rules={ | |||||
| props.values.userId | |||||
| ? [] | |||||
| : [ | |||||
| { required: true, message: '请输入密码!' }, | |||||
| { | |||||
| pattern: /^[A-Za-z0-9!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]{8,16}$/, | |||||
| message: '密码长度为8 ~ 16位,只支持字母数字和符号', | |||||
| }, | |||||
| ] | |||||
| } | |||||
| /> | /> | ||||
| <ProFormSelect | <ProFormSelect | ||||
| valueEnum={sexOptions} | valueEnum={sexOptions} | ||||
| @@ -279,7 +289,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| colProps={{ md: 12, xl: 12 }} | colProps={{ md: 12, xl: 12 }} | ||||
| rules={[{ required: true, message: '请选择角色!' }]} | rules={[{ required: true, message: '请选择角色!' }]} | ||||
| /> | /> | ||||
| <ProFormText | |||||
| {/* <ProFormText | |||||
| name="gitLinkUsername" | name="gitLinkUsername" | ||||
| label="Git 用户名" | label="Git 用户名" | ||||
| placeholder="请输入 Git 用户名" | placeholder="请输入 Git 用户名" | ||||
| @@ -300,17 +310,25 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| autoComplete: 'new-password', | autoComplete: 'new-password', | ||||
| }} | }} | ||||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} | rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} | ||||
| /> | |||||
| /> */} | |||||
| <ProFormDigit | <ProFormDigit | ||||
| name="credit" | name="credit" | ||||
| label="算力积分" | label="算力积分" | ||||
| placeholder="请输入算力积分" | placeholder="请输入算力积分" | ||||
| colProps={{ xs: 24, md: 12, xl: 12 }} | colProps={{ xs: 24, md: 12, xl: 12 }} | ||||
| max={100000} | |||||
| min={0} | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入算力积分', | message: '请输入算力积分', | ||||
| }, | }, | ||||
| { | |||||
| type: 'number', | |||||
| min: 0, | |||||
| max: 100000, | |||||
| message: '请输入0 ~ 100000之间的数', | |||||
| }, | |||||
| ]} | ]} | ||||
| /> | /> | ||||
| <ProFormTextArea | <ProFormTextArea | ||||
| @@ -4,7 +4,6 @@ import { getRoleList } from '@/services/system/role'; | |||||
| import { | import { | ||||
| addUser, | addUser, | ||||
| changeUserStatus, | changeUserStatus, | ||||
| exportUser, | |||||
| getDeptTree, | getDeptTree, | ||||
| getUser, | getUser, | ||||
| getUserList, | getUserList, | ||||
| @@ -13,6 +12,7 @@ import { | |||||
| updateAuthRole, | updateAuthRole, | ||||
| updateUser, | updateUser, | ||||
| } from '@/services/system/user'; | } from '@/services/system/user'; | ||||
| import { downloadXlsx } from '@/utils/downloadfile'; | |||||
| import { | import { | ||||
| DeleteOutlined, | DeleteOutlined, | ||||
| DownOutlined, | DownOutlined, | ||||
| @@ -132,15 +132,12 @@ const handleRemoveOne = async (selectedRow: API.System.User) => { | |||||
| /** | /** | ||||
| * 导出数据 | * 导出数据 | ||||
| * | |||||
| * | |||||
| */ | */ | ||||
| const handleExport = async () => { | |||||
| const handleExport = async (deptId: string) => { | |||||
| const hide = message.loading('正在导出'); | const hide = message.loading('正在导出'); | ||||
| try { | try { | ||||
| await exportUser(); | |||||
| await downloadXlsx('/api/system/user/export', 'POST', { data: { deptId: deptId } }); | |||||
| hide(); | hide(); | ||||
| message.success('导出成功'); | |||||
| return true; | return true; | ||||
| } catch (error) { | } catch (error) { | ||||
| hide(); | hide(); | ||||
| @@ -470,7 +467,7 @@ const UserTableList: React.FC = () => { | |||||
| key="export" | key="export" | ||||
| hidden={!access.hasPerms('system:user:export')} | hidden={!access.hasPerms('system:user:export')} | ||||
| onClick={async () => { | onClick={async () => { | ||||
| handleExport(); | |||||
| handleExport(selectDept.id); | |||||
| }} | }} | ||||
| > | > | ||||
| <PlusOutlined />{' '} | <PlusOutlined />{' '} | ||||
| @@ -563,7 +560,7 @@ const UserTableList: React.FC = () => { | |||||
| /> | /> | ||||
| <ResetPwd | <ResetPwd | ||||
| onSubmit={async (values: any) => { | onSubmit={async (values: any) => { | ||||
| const success = await resetUserPwd(values.userId, values.password); | |||||
| const success = await resetUserPwd(values); | |||||
| if (success) { | if (success) { | ||||
| setResetPwdModalVisible(false); | setResetPwdModalVisible(false); | ||||
| setSelectedRows([]); | setSelectedRows([]); | ||||
| @@ -581,7 +578,7 @@ const UserTableList: React.FC = () => { | |||||
| /> | /> | ||||
| <AuthRoleForm | <AuthRoleForm | ||||
| onSubmit={async (values: any) => { | onSubmit={async (values: any) => { | ||||
| const success = await updateAuthRole(values); | |||||
| const success = await updateAuthRole(currentRow!.userId, values.roleIds); | |||||
| if (success) { | if (success) { | ||||
| setAuthRoleModalVisible(false); | setAuthRoleModalVisible(false); | ||||
| setSelectedRows([]); | setSelectedRows([]); | ||||
| @@ -7,7 +7,7 @@ | |||||
| // 媒体查询 | // 媒体查询 | ||||
| @media screen and (max-width: 1600px) { | @media screen and (max-width: 1600px) { | ||||
| flex: 1 1 content; | |||||
| flex: auto; | |||||
| } | } | ||||
| &__icon { | &__icon { | ||||
| @@ -24,8 +24,7 @@ | |||||
| &__count { | &__count { | ||||
| color: @text-color; | color: @text-color; | ||||
| font-weight: 700; | |||||
| font-size: 25px; | |||||
| font-size: 26px; | |||||
| font-family: DingTalk-JinBuTi; | font-family: DingTalk-JinBuTi; | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,9 +3,9 @@ | |||||
| flex: none; | flex: none; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| align-items: center; | align-items: center; | ||||
| width: 326px; | width: 326px; | ||||
| height: 228px; | height: 228px; | ||||
| padding: 0 20px; | |||||
| .backgroundFullImage(url(@/assets/img/user-points-bg.png)); | .backgroundFullImage(url(@/assets/img/user-points-bg.png)); | ||||
| &__label { | &__label { | ||||
| @@ -16,12 +16,15 @@ | |||||
| } | } | ||||
| &__value { | &__value { | ||||
| width: 100%; | |||||
| margin-top: 8px; | margin-top: 8px; | ||||
| margin-bottom: 12px; | margin-bottom: 12px; | ||||
| color: @primary-color; | color: @primary-color; | ||||
| font-size: 36px; | font-size: 36px; | ||||
| font-family: DingTalk-JinBuTi; | font-family: DingTalk-JinBuTi; | ||||
| line-height: 43px; | line-height: 43px; | ||||
| text-align: center; | |||||
| .singleLine(); | |||||
| } | } | ||||
| &__button { | &__button { | ||||
| @@ -2,6 +2,7 @@ import { PointsStatistics } from '@/pages/Points/index'; | |||||
| import { getPointsStatisticsReq } from '@/services/points'; | import { getPointsStatisticsReq } from '@/services/points'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { Typography } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -23,8 +24,13 @@ function UserPoints() { | |||||
| return ( | return ( | ||||
| <div className={styles['user-points']}> | <div className={styles['user-points']}> | ||||
| <span className={styles['user-points__label']}>当前可用算力积分</span> | |||||
| <span className={styles['user-points__value']}>{statistics?.userCredit ?? '--'}</span> | |||||
| <div className={styles['user-points__label']}>当前可用算力积分</div> | |||||
| <Typography.Paragraph | |||||
| className={styles['user-points__value']} | |||||
| ellipsis={{ tooltip: statistics?.userCredit ?? '--' }} | |||||
| > | |||||
| {statistics?.userCredit ?? '--'} | |||||
| </Typography.Paragraph> | |||||
| <div | <div | ||||
| className={styles['user-points__button']} | className={styles['user-points__button']} | ||||
| onClick={() => { | onClick={() => { | ||||
| @@ -27,6 +27,7 @@ | |||||
| &__statistics { | &__statistics { | ||||
| flex: none; | flex: none; | ||||
| min-width: 431px; | |||||
| background: linear-gradient( | background: linear-gradient( | ||||
| 123.08deg, | 123.08deg, | ||||
| rgba(138, 138, 138, 0.06) 1.32%, | rgba(138, 138, 138, 0.06) 1.32%, | ||||
| @@ -36,7 +37,7 @@ | |||||
| // 媒体查询 | // 媒体查询 | ||||
| @media screen and (max-width: 1600px) { | @media screen and (max-width: 1600px) { | ||||
| flex: 1 1 content; | |||||
| flex: 1; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { useDraggable } from '@/hooks/draggable'; | |||||
| import { useDraggable } from '@/hooks/useDraggable'; | |||||
| import { getWorkspaceOverviewReq } from '@/services/workspace'; | import { getWorkspaceOverviewReq } from '@/services/workspace'; | ||||
| import { ExperimentInstance } from '@/types'; | import { ExperimentInstance } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| @@ -53,7 +53,7 @@ export const requestConfig: RequestConfig = { | |||||
| ], | ], | ||||
| responseInterceptors: [ | responseInterceptors: [ | ||||
| [ | [ | ||||
| (response: AxiosResponse) => { | |||||
| async (response: AxiosResponse) => { | |||||
| const { status, data, config } = response || {}; | const { status, data, config } = response || {}; | ||||
| const options = config as RequestOptions; | const options = config as RequestOptions; | ||||
| const skipErrorHandler = options?.skipErrorHandler; | const skipErrorHandler = options?.skipErrorHandler; | ||||
| @@ -63,20 +63,45 @@ export const requestConfig: RequestConfig = { | |||||
| Loading.hide(); | Loading.hide(); | ||||
| } | } | ||||
| if (status >= 200 && status < 300) { | if (status >= 200 && status < 300) { | ||||
| if (status === 204) { | |||||
| // 无内容或者无需验证 | |||||
| if (status === 204 || skipValidating) { | |||||
| return response; | return response; | ||||
| } else if (data && (skipValidating || data instanceof Blob || data.code === 200)) { | |||||
| } | |||||
| if (data && data.code === 200) { | |||||
| return response; | |||||
| } | |||||
| // Blob 数据 | |||||
| if (data && data instanceof Blob && data.size > 0) { | |||||
| // 下载文件失败时,返回的是 JSON 数据,格式为:{code: 500, msg: "xxx"} | |||||
| if (data.type === 'application/json') { | |||||
| try { | |||||
| const text = await data.text(); | |||||
| const json = JSON.parse(text); | |||||
| if (json.code === 500) { | |||||
| popupError(json.msg || '请求失败', skipErrorHandler); | |||||
| return Promise.reject(json); | |||||
| } | |||||
| } catch (error) { | |||||
| console.error('JSON 解析失败', error); | |||||
| } | |||||
| } | |||||
| return response; | return response; | ||||
| } else if (data && data.code === 401) { | |||||
| } | |||||
| // Token 失效 | |||||
| if (data && data.code === 401) { | |||||
| clearSessionToken(); | clearSessionToken(); | ||||
| setRemoteMenu(null); | setRemoteMenu(null); | ||||
| gotoLoginPage(false); | gotoLoginPage(false); | ||||
| popupError('请重新登录'); | popupError('请重新登录'); | ||||
| return Promise.reject(response); | return Promise.reject(response); | ||||
| } else { | |||||
| popupError(data?.msg ?? '请求失败', skipErrorHandler); | |||||
| return Promise.reject(response); | |||||
| } | } | ||||
| popupError(data?.msg ?? '请求失败', skipErrorHandler); | |||||
| return Promise.reject(response); | |||||
| } else { | } else { | ||||
| popupError('请求失败', skipErrorHandler); | popupError('请求失败', skipErrorHandler); | ||||
| return Promise.reject(response); | return Promise.reject(response); | ||||