| @@ -22,8 +22,8 @@ export default { | |||||
| // 要代理的地址 | // 要代理的地址 | ||||
| // target: 'http://172.20.32.197:31213', // 开发环境 | // target: 'http://172.20.32.197:31213', // 开发环境 | ||||
| // target: 'http://172.20.32.235:31213', // 测试环境 | // target: 'http://172.20.32.235:31213', // 测试环境 | ||||
| target: 'http://172.20.32.44:8082', | |||||
| // target: 'http://172.20.32.150:8082', | |||||
| // target: 'http://172.20.32.44:8082', | |||||
| target: 'http://172.20.32.164:8082', | |||||
| // 配置了这个可以从 http 代理到 https | // 配置了这个可以从 http 代理到 https | ||||
| // 依赖 origin 的功能可能需要这个,比如 cookie | // 依赖 origin 的功能可能需要这个,比如 cookie | ||||
| changeOrigin: true, | changeOrigin: true, | ||||
| @@ -417,6 +417,18 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: '知识图谱', | |||||
| path: 'knowledge', | |||||
| routes: [ | |||||
| { | |||||
| name: '知识图谱', | |||||
| path: '', | |||||
| key: 'knowledge', | |||||
| component: './Knowledge/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -583,18 +595,6 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: '知识图谱', | |||||
| path: '/knowledge', | |||||
| routes: [ | |||||
| { | |||||
| name: '知识图谱', | |||||
| path: '', | |||||
| key: 'knowledge', | |||||
| component: './Knowledge/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | { | ||||
| path: '*', | path: '*', | ||||
| layout: false, | layout: false, | ||||
| @@ -8,7 +8,7 @@ | |||||
| "build": "max build", | "build": "max build", | ||||
| "deploy": "npm run build && npm run gh-pages", | "deploy": "npm run build && npm run gh-pages", | ||||
| "dev": "npm run start:dev", | "dev": "npm run start:dev", | ||||
| "dev-no-sso": "cross-env NO_SSO=true npm run start:dev", | |||||
| "dev-no-sso": "cross-env NO_SSO=true npm run start:mock", | |||||
| "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", | "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", | ||||
| "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", | "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", | ||||
| "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", | "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", | ||||
| @@ -35,7 +35,7 @@ | |||||
| "serve": "umi-serve", | "serve": "umi-serve", | ||||
| "start": "cross-env UMI_ENV=dev max dev", | "start": "cross-env UMI_ENV=dev max dev", | ||||
| "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", | "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", | ||||
| "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev", | |||||
| "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", | |||||
| "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", | "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", | ||||
| "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", | ||||
| @@ -21,7 +21,7 @@ import { | |||||
| } from './services/session'; | } from './services/session'; | ||||
| import './styles/menu.less'; | import './styles/menu.less'; | ||||
| import { needAuth } from './utils'; | import { needAuth } from './utils'; | ||||
| // import { closeAllModals } from './utils/modal'; | |||||
| import { closeAllModals } from './utils/modal'; | |||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| export { requestConfig as request } from './requestConfig'; | export { requestConfig as request } from './requestConfig'; | ||||
| @@ -30,15 +30,14 @@ export { requestConfig as request } from './requestConfig'; | |||||
| */ | */ | ||||
| export async function getInitialState(): Promise<GlobalInitialState> { | export async function getInitialState(): Promise<GlobalInitialState> { | ||||
| const fetchUserInfo = async () => { | const fetchUserInfo = async () => { | ||||
| globalGetSeverTime(); | |||||
| try { | try { | ||||
| globalGetSeverTime(); | |||||
| const response = await getUserInfo(); | const response = await getUserInfo(); | ||||
| return { | return { | ||||
| ...response.user, | ...response.user, | ||||
| avatar: response.user.avatar || require('@/assets/img/avatar-default.png'), | avatar: response.user.avatar || require('@/assets/img/avatar-default.png'), | ||||
| permissions: response.permissions, | permissions: response.permissions, | ||||
| roles: response.roles, | |||||
| roleNames: response.user.roles, | |||||
| roleNames: response.roles, | |||||
| } as API.CurrentUser; | } as API.CurrentUser; | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error('getInitialState', error); | console.error('getInitialState', error); | ||||
| @@ -47,11 +46,8 @@ export async function getInitialState(): Promise<GlobalInitialState> { | |||||
| return undefined; | return undefined; | ||||
| }; | }; | ||||
| // 如果不是登录页面,执行 | |||||
| const { location } = history; | |||||
| // console.log('getInitialState', needAuth(location.pathname)); | |||||
| if (needAuth(location.pathname)) { | |||||
| const token = getAccessToken(); | |||||
| if (token) { | |||||
| const currentUser = await fetchUserInfo(); | const currentUser = await fetchUserInfo(); | ||||
| return { | return { | ||||
| fetchUserInfo, | fetchUserInfo, | ||||
| @@ -72,9 +68,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| return { | return { | ||||
| ErrorBoundary: ErrorBoundary, | ErrorBoundary: ErrorBoundary, | ||||
| rightContentRender: false, | rightContentRender: false, | ||||
| waterMarkProps: { | |||||
| // content: initialState?.currentUser?.nickName, | |||||
| }, | |||||
| menu: { | menu: { | ||||
| locale: false, | locale: false, | ||||
| // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request | // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request | ||||
| @@ -85,46 +78,9 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| if (!initialState?.currentUser?.userId) { | if (!initialState?.currentUser?.userId) { | ||||
| return []; | return []; | ||||
| } | } | ||||
| // console.log('get menus') | |||||
| // initialState.currentUser 中包含了所有用户信息 | |||||
| // console.log('get routers') | |||||
| // setInitialState((preInitialState) => ({ | |||||
| // ...preInitialState, | |||||
| // menus, | |||||
| // })); | |||||
| return getRemoteMenu(); | return getRemoteMenu(); | ||||
| }, | }, | ||||
| }, | }, | ||||
| onPageChange: () => { | |||||
| const { location } = history; | |||||
| // closeAllModals(); | |||||
| // 如果没有登录,重定向到 login | |||||
| if (!initialState?.currentUser && needAuth(location.pathname)) { | |||||
| gotoLoginPage(); | |||||
| } | |||||
| }, | |||||
| layoutBgImgList: [ | |||||
| { | |||||
| src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr', | |||||
| left: 85, | |||||
| bottom: 100, | |||||
| height: '303px', | |||||
| }, | |||||
| { | |||||
| src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr', | |||||
| bottom: -68, | |||||
| right: -45, | |||||
| height: '303px', | |||||
| }, | |||||
| { | |||||
| src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr', | |||||
| bottom: 0, | |||||
| left: 0, | |||||
| width: '331px', | |||||
| }, | |||||
| ], | |||||
| // 自定义 403 页面 | |||||
| // unAccessible: <div>unAccessible</div>, | |||||
| childrenRender: (children) => { | childrenRender: (children) => { | ||||
| // 增加一个 loading 的状态 | // 增加一个 loading 的状态 | ||||
| // if (initialState?.loading) return <PageLoading />; | // if (initialState?.loading) return <PageLoading />; | ||||
| @@ -161,9 +117,26 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| }; | }; | ||||
| export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | ||||
| // console.log('onRouteChange'); | |||||
| // 路由切换时,尤其是回退时,关闭打开的弹框 | |||||
| closeAllModals(); | |||||
| const { location } = e; | const { location } = e; | ||||
| const token = getAccessToken(); | |||||
| // 没有 token,跳转到登录页面 | |||||
| if (!token && needAuth(location.pathname)) { | |||||
| gotoLoginPage(); | |||||
| return; | |||||
| } | |||||
| // 有 token, 登录页面直接跳转到首页 | |||||
| if (token && !needAuth(location.pathname)) { | |||||
| history.push('/'); | |||||
| } | |||||
| const menus = getRemoteMenu(); | const menus = getRemoteMenu(); | ||||
| // console.log('onRouteChange', menus); | |||||
| // 没有菜单,刷新页面 | |||||
| if (menus === null && needAuth(location.pathname)) { | if (menus === null && needAuth(location.pathname)) { | ||||
| history.go(0); | history.go(0); | ||||
| } | } | ||||
| @@ -181,10 +154,12 @@ export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | |||||
| export function render(oldRender: () => void) { | export function render(oldRender: () => void) { | ||||
| // console.log('render'); | // console.log('render'); | ||||
| const token = getAccessToken(); | const token = getAccessToken(); | ||||
| if (!token || token?.length === 0) { | |||||
| if (!token) { | |||||
| oldRender(); | oldRender(); | ||||
| return; | return; | ||||
| } | } | ||||
| // 有 token,获取路由 | |||||
| getRoutersInfo() | getRoutersInfo() | ||||
| .then((res) => { | .then((res) => { | ||||
| setRemoteMenu(res); | setRemoteMenu(res); | ||||
| @@ -1,11 +1,22 @@ | |||||
| .code-config-item { | .code-config-item { | ||||
| position: relative; | position: relative; | ||||
| width: calc(25% - 7.5px); | |||||
| width: calc(33.33% - 7px); | |||||
| padding: 15px; | padding: 15px; | ||||
| background-color: .addAlpha(@primary-color, 0.04) []; | background-color: .addAlpha(@primary-color, 0.04) []; | ||||
| border: 1px solid transparent; | border: 1px solid transparent; | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| cursor: pointer; | |||||
| &__checkbox { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| :global { | |||||
| .ant-checkbox + span { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__name { | &__name { | ||||
| margin-right: 8px; | margin-right: 8px; | ||||
| @@ -38,6 +49,8 @@ | |||||
| margin-bottom: 10px !important; | margin-bottom: 10px !important; | ||||
| color: @text-color-secondary; | color: @text-color-secondary; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| cursor: pointer; | |||||
| word-break: break-all; | |||||
| } | } | ||||
| &__branch { | &__branch { | ||||
| @@ -46,11 +59,17 @@ | |||||
| } | } | ||||
| &:hover { | &:hover { | ||||
| background-color: .addAlpha(@primary-color, 0.08) []; | |||||
| } | |||||
| &--active { | |||||
| border-color: @primary-color; | border-color: @primary-color; | ||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | ||||
| } | } | ||||
| &:hover &__name { | |||||
| &--active &__name { | |||||
| color: @primary-color; | color: @primary-color; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,25 +1,51 @@ | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { Flex, Typography } from 'antd'; | |||||
| import { getGitUrl } from '@/utils'; | |||||
| import { Checkbox, Flex, Typography } from 'antd'; | |||||
| import { type CheckboxChangeEvent } from 'antd/es/checkbox'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CodeConfigItemProps = { | type CodeConfigItemProps = { | ||||
| item: CodeConfigData; | item: CodeConfigData; | ||||
| onClick?: (item: CodeConfigData) => void; | |||||
| checked: boolean; | |||||
| onChange?: (item: CodeConfigData, checked: boolean) => void; | |||||
| }; | }; | ||||
| function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| function CodeConfigItem({ item, checked, onChange }: CodeConfigItemProps) { | |||||
| const [isEllipsis, setIsEllipsis] = useState(false); | const [isEllipsis, setIsEllipsis] = useState(false); | ||||
| const openProject = (e: React.MouseEvent<HTMLElement, MouseEvent>) => { | |||||
| e.stopPropagation(); | |||||
| const { git_url, git_branch } = item; | |||||
| const url = getGitUrl(git_url, git_branch); | |||||
| window.open(url, '_blank'); | |||||
| }; | |||||
| const handleChange = (e: CheckboxChangeEvent) => { | |||||
| onChange?.(item, e.target.checked); | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | |||||
| <div | |||||
| id={`code-config-item-${item.id}`} | |||||
| className={classNames(styles['code-config-item'], { | |||||
| [styles['code-config-item--active']]: checked, | |||||
| })} | |||||
| > | |||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | ||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__name']} | |||||
| ellipsis={{ tooltip: item.code_repo_name }} | |||||
| <Checkbox | |||||
| className={styles['code-config-item__checkbox']} | |||||
| checked={checked} | |||||
| onChange={handleChange} | |||||
| > | > | ||||
| {item.code_repo_name} | |||||
| </Typography.Paragraph> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__name']} | |||||
| ellipsis={{ tooltip: item.code_repo_name }} | |||||
| > | |||||
| {item.code_repo_name} | |||||
| </Typography.Paragraph> | |||||
| </Checkbox> | |||||
| <div | <div | ||||
| className={classNames( | className={classNames( | ||||
| styles['code-config-item__tag'], | styles['code-config-item__tag'], | ||||
| @@ -35,9 +61,10 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| className={styles['code-config-item__url']} | className={styles['code-config-item__url']} | ||||
| ellipsis={{ | ellipsis={{ | ||||
| rows: 2, | rows: 2, | ||||
| tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip | |||||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), | |||||
| tooltip: isEllipsis ? item.git_url : false, | |||||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), // 必须这样,不然不能省略 | |||||
| }} | }} | ||||
| onClick={openProject} | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| @@ -4,7 +4,7 @@ | |||||
| * @Description: 流水线选择代码配置表单 | * @Description: 流水线选择代码配置表单 | ||||
| */ | */ | ||||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | |||||
| import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| @@ -18,7 +18,9 @@ export { | |||||
| type ParameterInputValue, | type ParameterInputValue, | ||||
| } from '../ParameterInput'; | } from '../ParameterInput'; | ||||
| type CodeSelectProps = ParameterInputProps; | |||||
| export interface CodeSelectProps extends ParameterInputProps { | |||||
| value?: CodeConfigData; | |||||
| } | |||||
| /** 代码配置选择表单组件 */ | /** 代码配置选择表单组件 */ | ||||
| function CodeSelect({ | function CodeSelect({ | ||||
| @@ -32,26 +34,18 @@ function CodeSelect({ | |||||
| }: CodeSelectProps) { | }: CodeSelectProps) { | ||||
| // 选择代码配置 | // 选择代码配置 | ||||
| const selectResource = () => { | const selectResource = () => { | ||||
| const defaultSelected: CodeConfigData | undefined = | |||||
| value && typeof value === 'object' ? value : undefined; | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | const { close } = openAntdModal(CodeSelectorModal, { | ||||
| defaultSelected: defaultSelected, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { id, code_repo_name, git_url, git_branch, git_user_name, git_password, ssh_key } = | |||||
| res; | |||||
| const jsonObj = { | |||||
| id, | |||||
| name: code_repo_name, | |||||
| code_path: git_url, | |||||
| branch: git_branch, | |||||
| username: git_user_name, | |||||
| password: git_password, | |||||
| ssh_private_key: ssh_key, | |||||
| }; | |||||
| const jsonObjStr = JSON.stringify(jsonObj); | |||||
| const { code_repo_name } = res; | |||||
| onChange?.({ | onChange?.({ | ||||
| value: jsonObjStr, | |||||
| ...res, | |||||
| value: code_repo_name, | |||||
| showValue: code_repo_name, | showValue: code_repo_name, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| ...jsonObj, | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| onChange?.(undefined); | onChange?.(undefined); | ||||
| @@ -17,6 +17,7 @@ | |||||
| margin-bottom: 30px; | margin-bottom: 30px; | ||||
| overflow-x: hidden; | overflow-x: hidden; | ||||
| overflow-y: auto; | overflow-y: auto; | ||||
| padding-bottom: 10px; | |||||
| } | } | ||||
| &__empty { | &__empty { | ||||
| @@ -7,7 +7,8 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { getCodeConfigListReq } from '@/services/codeConfig'; | |||||
| import { getCodeConfigListReq, getCodeConfigPageNumReq } from '@/services/codeConfig'; | |||||
| import { CustomPartial } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import type { ModalProps, PaginationProps } from 'antd'; | import type { ModalProps, PaginationProps } from 'antd'; | ||||
| import { Empty, Input, Pagination } from 'antd'; | import { Empty, Input, Pagination } from 'antd'; | ||||
| @@ -17,24 +18,68 @@ import './index.less'; | |||||
| export { type CodeConfigData }; | export { type CodeConfigData }; | ||||
| export type SelectCodeData = CustomPartial< | |||||
| CodeConfigData, | |||||
| | 'id' | |||||
| | 'code_repo_name' | |||||
| | 'git_url' | |||||
| | 'git_branch' | |||||
| | 'git_user_name' | |||||
| | 'git_password' | |||||
| | 'ssh_key' | |||||
| | 'is_public' | |||||
| >; | |||||
| export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> { | export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| onOk?: (params: CodeConfigData | undefined) => void; | |||||
| defaultSelected?: SelectCodeData; | |||||
| onOk?: (params: SelectCodeData | undefined) => void; | |||||
| } | } | ||||
| /** 选择代码配置的弹窗,推荐使用函数的方式打开 */ | /** 选择代码配置的弹窗,推荐使用函数的方式打开 */ | ||||
| function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| function CodeSelectorModal({ defaultSelected, onOk, ...rest }: CodeSelectorModalProps) { | |||||
| const DefaultPageSize = 18; | |||||
| const [dataList, setDataList] = useState<CodeConfigData[]>([]); | const [dataList, setDataList] = useState<CodeConfigData[]>([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<PaginationProps>({ | |||||
| current: 1, | |||||
| pageSize: 20, | |||||
| }); | |||||
| const [searchText, setSearchText] = useState<string | undefined>(undefined); | const [searchText, setSearchText] = useState<string | undefined>(undefined); | ||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | const [inputText, setInputText] = useState<string | undefined>(undefined); | ||||
| const [selected, setSelected] = useState(defaultSelected); | |||||
| const [isScrolled, setIsScrolled] = useState(false); | |||||
| const [pagination, setPagination] = useState<PaginationProps>({ | |||||
| current: defaultSelected?.id ? 0 : 1, // 为 0 时,不请求,等待接口返回选中的代码配置在第几页 | |||||
| pageSize: DefaultPageSize, | |||||
| }); | |||||
| useEffect(() => { | |||||
| const getCodeConfigPageNum = async (id: number, size: number) => { | |||||
| const [res] = await to( | |||||
| getCodeConfigPageNumReq(id, { | |||||
| size, | |||||
| }), | |||||
| ); | |||||
| if (res) { | |||||
| setPagination({ | |||||
| current: typeof res.data === 'number' ? Math.max(0, res.data) + 1 : 1, | |||||
| pageSize: DefaultPageSize, | |||||
| }); | |||||
| } else { | |||||
| setPagination({ | |||||
| current: 1, | |||||
| pageSize: DefaultPageSize, | |||||
| }); | |||||
| } | |||||
| }; | |||||
| if (defaultSelected?.id) { | |||||
| getCodeConfigPageNum(defaultSelected?.id, DefaultPageSize); | |||||
| } | |||||
| }, [defaultSelected?.id]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取数据请求 | // 获取数据请求 | ||||
| const getDataList = async () => { | const getDataList = async () => { | ||||
| // 为 0 时,不请求,等待接口返回选中的代码配置在第几页 | |||||
| if (pagination.current === 0) { | |||||
| return; | |||||
| } | |||||
| const params = { | const params = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -50,6 +95,16 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| getDataList(); | getDataList(); | ||||
| }, [pagination, searchText]); | }, [pagination, searchText]); | ||||
| useEffect(() => { | |||||
| if (dataList.length > 0 && !isScrolled && defaultSelected?.id) { | |||||
| const selectedItem = document.getElementById(`code-config-item-${defaultSelected?.id}`); | |||||
| if (selectedItem) { | |||||
| selectedItem.scrollIntoView(); | |||||
| } | |||||
| setIsScrolled(true); | |||||
| } | |||||
| }, [isScrolled, dataList, defaultSelected?.id]); | |||||
| // 搜索 | // 搜索 | ||||
| const handleSearch = (value: string) => { | const handleSearch = (value: string) => { | ||||
| setSearchText(value); | setSearchText(value); | ||||
| @@ -59,8 +114,12 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| })); | })); | ||||
| }; | }; | ||||
| const handleClick = (item: CodeConfigData) => { | |||||
| onOk?.(item); | |||||
| const handleChange = (item: CodeConfigData, checked: boolean) => { | |||||
| if (checked) { | |||||
| setSelected(item); | |||||
| } else { | |||||
| setSelected(undefined); | |||||
| } | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -77,7 +136,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| title="选择代码配置" | title="选择代码配置" | ||||
| image={require('@/assets/img/modal-code-config.png')} | image={require('@/assets/img/modal-code-config.png')} | ||||
| width={920} | width={920} | ||||
| footer={null} | |||||
| onOk={() => onOk?.(selected)} | |||||
| destroyOnClose | destroyOnClose | ||||
| > | > | ||||
| <div className="kf-code-selector-modal"> | <div className="kf-code-selector-modal"> | ||||
| @@ -93,23 +152,31 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| prefix={ | prefix={ | ||||
| <KFIcon type="icon-sousuo" color="rgba(22,100,255,0.4" style={{ marginLeft: '10px' }} /> | <KFIcon type="icon-sousuo" color="rgba(22,100,255,0.4" style={{ marginLeft: '10px' }} /> | ||||
| } | } | ||||
| // prefix={ | |||||
| // <Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} /> | |||||
| // } | |||||
| /> | /> | ||||
| {dataList?.length !== 0 ? ( | {dataList?.length !== 0 ? ( | ||||
| <> | <> | ||||
| <div className="kf-code-selector-modal__content"> | <div className="kf-code-selector-modal__content"> | ||||
| {dataList?.map((item) => ( | {dataList?.map((item) => ( | ||||
| <CodeConfigItem item={item} key={item.id} onClick={handleClick} /> | |||||
| <CodeConfigItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| checked={item.id === selected?.id} | |||||
| onChange={handleChange} | |||||
| /> | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| <Pagination | <Pagination | ||||
| align="center" | |||||
| align="end" | |||||
| total={total} | total={total} | ||||
| showSizeChanger | showSizeChanger | ||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| defaultPageSize={DefaultPageSize} | |||||
| pageSizeOptions={[ | |||||
| DefaultPageSize, | |||||
| 2 * DefaultPageSize, | |||||
| 3 * DefaultPageSize, | |||||
| 4 * DefaultPageSize, | |||||
| 5 * DefaultPageSize, | |||||
| ]} | |||||
| showQuickJumper | showQuickJumper | ||||
| onChange={handlePageChange} | onChange={handlePageChange} | ||||
| {...pagination} | {...pagination} | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; | |||||
| import { formatEnum } from '@/utils/format'; | import { formatEnum } from '@/utils/format'; | ||||
| import { Typography, type SelectProps } from 'antd'; | import { Typography, type SelectProps } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| @@ -16,6 +17,8 @@ type FormInfoProps = { | |||||
| options?: SelectProps['options']; | options?: SelectProps['options']; | ||||
| /** 自定义节点 label、value 的字段 */ | /** 自定义节点 label、value 的字段 */ | ||||
| fieldNames?: SelectProps['fieldNames']; | fieldNames?: SelectProps['fieldNames']; | ||||
| /** 全局参数 */ | |||||
| globalParams?: PipelineGlobalParam[] | null; | |||||
| /** 自定义类名 */ | /** 自定义类名 */ | ||||
| className?: string; | className?: string; | ||||
| /** 自定义样式 */ | /** 自定义样式 */ | ||||
| @@ -32,12 +35,29 @@ function FormInfo({ | |||||
| select = false, | select = false, | ||||
| options, | options, | ||||
| fieldNames, | fieldNames, | ||||
| globalParams, | |||||
| className, | className, | ||||
| style, | style, | ||||
| }: FormInfoProps) { | }: FormInfoProps) { | ||||
| let showValue = value; | let showValue = value; | ||||
| if (value && typeof value === 'object' && valuePropName) { | if (value && typeof value === 'object' && valuePropName) { | ||||
| showValue = value[valuePropName]; | showValue = value[valuePropName]; | ||||
| const reg = /^\$\{(.*)\}$/; | |||||
| if (value.fromSelect && Array.isArray(globalParams) && globalParams.length > 0) { | |||||
| const match = reg.exec(showValue); | |||||
| if (match) { | |||||
| const paramName = match[1]; | |||||
| const foundParam = globalParams.find((v) => v.param_name === paramName); | |||||
| if (foundParam) { | |||||
| showValue = | |||||
| foundParam.param_type === PipelineGlobalParamType.Boolean // 布尔类型转换 | |||||
| ? foundParam.param_value | |||||
| ? 'true' | |||||
| : 'false' | |||||
| : foundParam.param_value; | |||||
| } | |||||
| } | |||||
| } | |||||
| } else if (select === true && options) { | } else if (select === true && options) { | ||||
| let _options: SelectProps['options'] = options; | let _options: SelectProps['options'] = options; | ||||
| if (fieldNames) { | if (fieldNames) { | ||||
| @@ -9,6 +9,7 @@ import { CloseOutlined } from '@ant-design/icons'; | |||||
| import { ConfigProvider, Form, Input, Typography } 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 { ReactNode } from 'react'; | |||||
| import './index.less'; | import './index.less'; | ||||
| // 如果值是对象时的类型 | // 如果值是对象时的类型 | ||||
| @@ -55,6 +56,8 @@ export interface ParameterInputProps { | |||||
| disabled?: boolean; | disabled?: boolean; | ||||
| /** 元素 id */ | /** 元素 id */ | ||||
| id?: string; | id?: string; | ||||
| /** 带标签的 input,设置后置标签 */ | |||||
| addonAfter?: ReactNode; | |||||
| } | } | ||||
| function ParameterInput({ | function ParameterInput({ | ||||
| @@ -75,7 +78,7 @@ function ParameterInput({ | |||||
| const valueObj = | const valueObj = | ||||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | ||||
| if (valueObj && !valueObj.showValue) { | if (valueObj && !valueObj.showValue) { | ||||
| valueObj.showValue = valueObj.value; | |||||
| valueObj.showValue = typeof valueObj.value === 'string' ? valueObj.value : ''; | |||||
| } | } | ||||
| const isSelect = valueObj?.fromSelect; | const isSelect = valueObj?.fromSelect; | ||||
| const placeholder = valueObj?.placeholder || rest?.placeholder; | const placeholder = valueObj?.placeholder || rest?.placeholder; | ||||
| @@ -1,26 +1,18 @@ | |||||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; | import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; | ||||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||||
| 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'; | ||||
| import { type SelectProps } from 'antd'; | import { type SelectProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| // id 从 number 转换为 string | |||||
| const convertId = (item: any) => ({ | |||||
| ...item, | |||||
| id: JSON.stringify({ | |||||
| id: `${item.id}`, | |||||
| name: item.name, | |||||
| identifier: item.identifier, | |||||
| owner: item.owner, | |||||
| }), | |||||
| }); | |||||
| export type SelectPropsConfig = { | export type SelectPropsConfig = { | ||||
| getOptions: () => Promise<any>; // 获取下拉数据 | |||||
| getOptions?: () => Promise<any>; // 获取下拉数据 | |||||
| fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | ||||
| optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | ||||
| filterOption?: SelectProps['filterOption']; // 过滤函数 | filterOption?: SelectProps['filterOption']; // 过滤函数 | ||||
| getValue: (value: any) => string | number; | |||||
| getLabel: (value: any) => string; | |||||
| isObjectValue: boolean; // value 是对象 | |||||
| }; | }; | ||||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | export const paramSelectConfig: Record<string, SelectPropsConfig> = { | ||||
| @@ -31,13 +23,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| size: 1000, | size: 1000, | ||||
| is_public: false, | is_public: false, | ||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| return res?.data?.content ?? []; | |||||
| }, | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: DatasetData) => { | |||||
| return value.id; | |||||
| }, | }, | ||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| getLabel: (value: DatasetData) => { | |||||
| return value.name; | |||||
| }, | }, | ||||
| optionFilterProp: 'name', | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| model: { | model: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| @@ -46,13 +41,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| size: 1000, | size: 1000, | ||||
| is_public: false, | is_public: false, | ||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| return res?.data?.content ?? []; | |||||
| }, | }, | ||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: ModelData) => { | |||||
| return value.id; | |||||
| }, | |||||
| getLabel: (value: ModelData) => { | |||||
| return value.name; | |||||
| }, | }, | ||||
| optionFilterProp: 'name', | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| service: { | service: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| @@ -60,25 +58,28 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| page: 0, | page: 0, | ||||
| size: 1000, | size: 1000, | ||||
| }); | }); | ||||
| return ( | |||||
| res?.data?.content?.map((item: ServiceData) => ({ | |||||
| label: item.service_name, | |||||
| value: JSON.stringify(pick(item, ['id', 'service_name'])), | |||||
| })) ?? [] | |||||
| ); | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'label', | |||||
| value: 'value', | |||||
| return res?.data?.content ?? []; | |||||
| }, | }, | ||||
| optionFilterProp: 'label', | optionFilterProp: 'label', | ||||
| getValue: (value: ServiceData) => { | |||||
| return value.id; | |||||
| }, | |||||
| getLabel: (value: ServiceData) => { | |||||
| return value.service_name; | |||||
| }, | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| resource: { | resource: { | ||||
| getOptions: async () => { | |||||
| // 不需要这个函数 | |||||
| return []; | |||||
| }, | |||||
| fieldNames: resourceFieldNames, | fieldNames: resourceFieldNames, | ||||
| filterOption: filterResourceStandard as SelectProps['filterOption'], | filterOption: filterResourceStandard as SelectProps['filterOption'], | ||||
| // 不会用到 | |||||
| getValue: () => { | |||||
| return ''; | |||||
| }, | |||||
| // 不会用的 | |||||
| getLabel: () => { | |||||
| return ''; | |||||
| }, | |||||
| isObjectValue: false, | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -7,7 +7,7 @@ | |||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | 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, useMemo, useState } from 'react'; | |||||
| import FormInfo from '../FormInfo'; | import FormInfo from '../FormInfo'; | ||||
| import { paramSelectConfig } from './config'; | import { paramSelectConfig } from './config'; | ||||
| @@ -36,69 +36,106 @@ function ParameterSelect({ | |||||
| dataType, | dataType, | ||||
| display = false, | display = false, | ||||
| value, | value, | ||||
| isPipeline = false, | |||||
| // isPipeline = false, | |||||
| onChange, | onChange, | ||||
| ...rest | ...rest | ||||
| }: ParameterSelectProps) { | }: ParameterSelectProps) { | ||||
| const [options, setOptions] = useState<SelectProps['options']>([]); | const [options, setOptions] = useState<SelectProps['options']>([]); | ||||
| const propsConfig = paramSelectConfig[dataType]; | const propsConfig = paramSelectConfig[dataType]; | ||||
| const valueText = typeof value === 'object' && value !== null ? value.value : value; | |||||
| const { | |||||
| getLabel, | |||||
| getValue, | |||||
| getOptions, | |||||
| filterOption, | |||||
| fieldNames, | |||||
| optionFilterProp, | |||||
| isObjectValue, | |||||
| } = propsConfig; | |||||
| const selectValue = typeof value === 'object' && value !== null ? value.value : value; | |||||
| // 数据集、模型、服务,对象转换成 json 字符串 | |||||
| const valueText = | |||||
| typeof selectValue === 'object' && selectValue !== null ? getValue(selectValue) : selectValue; | |||||
| const [resourceStandardList] = useComputingResource(); | const [resourceStandardList] = useComputingResource(); | ||||
| const computingResource = isPipeline | |||||
| ? resourceStandardList.map((v) => ({ | |||||
| ...v, | |||||
| id: String(v.id), | |||||
| })) | |||||
| : resourceStandardList; | |||||
| // const computingResource = isPipeline | |||||
| // ? resourceStandardList.map((v) => ({ | |||||
| // ...v, | |||||
| // id: String(v.id), | |||||
| // })) | |||||
| // : resourceStandardList; | |||||
| const objectOptions = useMemo(() => { | |||||
| return options?.map((v) => ({ | |||||
| label: getLabel(v), | |||||
| value: getValue(v), | |||||
| })); | |||||
| }, [getLabel, getValue, options]); | |||||
| const valueMap = useMemo(() => { | |||||
| const map = new Map<string | number, any>(); | |||||
| options?.forEach((v) => { | |||||
| map.set(getValue(v), v); | |||||
| }); | |||||
| return map; | |||||
| }, [options, getValue]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取下拉数据 | // 获取下拉数据 | ||||
| const getSelectOptions = async () => { | const getSelectOptions = async () => { | ||||
| if (!propsConfig) { | |||||
| return; | |||||
| } | |||||
| const getOptions = propsConfig.getOptions; | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| if (getOptions) { | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| getSelectOptions(); | getSelectOptions(); | ||||
| }, [propsConfig]); | |||||
| }, [getOptions]); | |||||
| const selectOptions = dataType === 'resource' ? computingResource : options; | |||||
| const selectOptions = dataType === 'resource' ? resourceStandardList : objectOptions; | |||||
| const handleChange = (text: string) => { | const handleChange = (text: string) => { | ||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: text, | |||||
| }); | |||||
| // 数据集、模型、服务,转换成对象 | |||||
| if (isObjectValue) { | |||||
| // 设置为 null 是因为 antv g6 的 bug | |||||
| // 如果值为 undefined 时, graph.changeData(data) 会保留前面的值 | |||||
| const selectValue = text ? valueMap.get(text) : null; | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: selectValue, | |||||
| }); | |||||
| } else { | |||||
| onChange?.(selectValue); | |||||
| } | |||||
| } else { | } else { | ||||
| onChange?.(text); | |||||
| const selectValue = text ? text : ''; | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: selectValue, | |||||
| }); | |||||
| } else { | |||||
| onChange?.(selectValue); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| // 只用于展示,FormInfo 组件带有 Tooltip | // 只用于展示,FormInfo 组件带有 Tooltip | ||||
| if (display) { | if (display) { | ||||
| return ( | return ( | ||||
| <FormInfo | |||||
| select | |||||
| value={valueText} | |||||
| options={selectOptions} | |||||
| fieldNames={propsConfig?.fieldNames} | |||||
| ></FormInfo> | |||||
| <FormInfo select value={valueText} options={selectOptions} fieldNames={fieldNames}></FormInfo> | |||||
| ); | ); | ||||
| } | } | ||||
| return ( | return ( | ||||
| <Select | <Select | ||||
| {...rest} | {...rest} | ||||
| filterOption={propsConfig?.filterOption} | |||||
| options={selectOptions} | options={selectOptions} | ||||
| fieldNames={propsConfig?.fieldNames} | |||||
| optionFilterProp={propsConfig?.optionFilterProp} | |||||
| fieldNames={fieldNames} | |||||
| optionFilterProp={optionFilterProp} | |||||
| filterOption={filterOption} | |||||
| value={valueText} | value={valueText} | ||||
| onChange={handleChange} | onChange={handleChange} | ||||
| showSearch | showSearch | ||||
| @@ -10,10 +10,10 @@ import ResourceSelectorModal, { | |||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| } from '@/components/ResourceSelectorModal'; | } from '@/components/ResourceSelectorModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { Button, ConfigProvider } from 'antd'; | import { Button, ConfigProvider } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { pick } from 'lodash'; | |||||
| import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | ||||
| import './index.less'; | import './index.less'; | ||||
| @@ -27,6 +27,8 @@ export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse | |||||
| interface ResourceSelectProps extends ParameterInputProps { | interface ResourceSelectProps extends ParameterInputProps { | ||||
| /** 类型,数据集、模型、镜像 */ | /** 类型,数据集、模型、镜像 */ | ||||
| type: ResourceSelectorType; | type: ResourceSelectorType; | ||||
| /** 值 */ | |||||
| value?: ResourceSelectorResponse; | |||||
| } | } | ||||
| // 获取选择数据集、模型、镜像后面按钮 icon | // 获取选择数据集、模型、镜像后面按钮 icon | ||||
| @@ -47,68 +49,51 @@ function ResourceSelect({ | |||||
| }: ResourceSelectProps) { | }: ResourceSelectProps) { | ||||
| const { componentSize } = ConfigProvider.useConfig(); | const { componentSize } = ConfigProvider.useConfig(); | ||||
| const mySize = size || componentSize; | const mySize = size || componentSize; | ||||
| let selectedResource: ResourceSelectorResponse | undefined = undefined; | |||||
| if ( | |||||
| value && | |||||
| typeof value === 'object' && | |||||
| value.activeTab && | |||||
| value.id && | |||||
| value.name && | |||||
| value.version && | |||||
| value.path && | |||||
| (type === ResourceSelectorType.Mirror || (value.identifier && value.owner)) | |||||
| ) { | |||||
| selectedResource = pick(value, [ | |||||
| 'activeTab', | |||||
| 'id', | |||||
| 'identifier', | |||||
| 'name', | |||||
| 'owner', | |||||
| 'version', | |||||
| 'path', | |||||
| ]) as ResourceSelectorResponse; | |||||
| let defaultActiveTab: CommonTabKeys | undefined, | |||||
| defaultExpandedKeys: string[] = [], | |||||
| defaultCheckedKeys: string[] = []; | |||||
| if (value && typeof value === 'object') { | |||||
| defaultActiveTab = value.activeTab; | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| if (value.image_id && value.id) { | |||||
| defaultExpandedKeys = [`${value.image_id}`]; | |||||
| defaultCheckedKeys = [`${value.image_id}-${value.id}`]; | |||||
| } | |||||
| } else if (value.id && value.version) { | |||||
| defaultExpandedKeys = [value.id]; | |||||
| defaultCheckedKeys = [`${value.id}-${value.version}`]; | |||||
| } | |||||
| } | } | ||||
| // 选择数据集、模型、镜像 | // 选择数据集、模型、镜像 | ||||
| const selectResource = () => { | const selectResource = () => { | ||||
| const { close } = openAntdModal(ResourceSelectorModal, { | const { close } = openAntdModal(ResourceSelectorModal, { | ||||
| type, | type, | ||||
| defaultExpandedKeys: selectedResource ? [selectedResource.id] : [], | |||||
| defaultCheckedKeys: selectedResource | |||||
| ? [`${selectedResource.id}-${selectedResource.version}`] | |||||
| : [], | |||||
| defaultActiveTab: selectedResource?.activeTab, | |||||
| defaultExpandedKeys: defaultExpandedKeys, | |||||
| defaultCheckedKeys: defaultCheckedKeys, | |||||
| defaultActiveTab: defaultActiveTab, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| const { activeTab, ...rest } = res; | |||||
| const { url } = rest; | |||||
| onChange?.({ | onChange?.({ | ||||
| value: path, | |||||
| showValue: path, | |||||
| ...rest, | |||||
| value: url, | |||||
| showValue: url, | |||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| const jsonObj = { | |||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| identifier, | |||||
| owner, | |||||
| }; | |||||
| const jsonObjStr = JSON.stringify(jsonObj); | |||||
| const { activeTab, ...rest } = res; | |||||
| const { name, version } = rest; | |||||
| const showValue = `${name}:${version}`; | const showValue = `${name}:${version}`; | ||||
| onChange?.({ | onChange?.({ | ||||
| value: jsonObjStr, | |||||
| ...rest, | |||||
| value: showValue, | |||||
| showValue, | showValue, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| ...jsonObj, | |||||
| }); | }); | ||||
| } | } | ||||
| } else { | } else { | ||||
| @@ -2,7 +2,7 @@ import datasetImg from '@/assets/img/modal-select-dataset.png'; | |||||
| import mirrorImg from '@/assets/img/modal-select-mirror.png'; | import mirrorImg from '@/assets/img/modal-select-mirror.png'; | ||||
| import modelImg from '@/assets/img/modal-select-model.png'; | import modelImg from '@/assets/img/modal-select-model.png'; | ||||
| import { AvailableRange, CommonTabKeys, MirrorVersionStatus } from '@/enums'; | import { AvailableRange, CommonTabKeys, MirrorVersionStatus } from '@/enums'; | ||||
| import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { DatasetData, ModelData, ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { MirrorVersionData } from '@/pages/Mirror/Info'; | import { MirrorVersionData } from '@/pages/Mirror/Info'; | ||||
| import { MirrorData } from '@/pages/Mirror/List'; | import { MirrorData } from '@/pages/Mirror/List'; | ||||
| import { | import { | ||||
| @@ -52,9 +52,9 @@ const convertResourceVersionToTreeData = ( | |||||
| ): TreeDataNode[] => { | ): TreeDataNode[] => { | ||||
| return list.map((item: ResourceVersionData) => ({ | return list.map((item: ResourceVersionData) => ({ | ||||
| ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | ||||
| version: item.name, | |||||
| title: item.name, | |||||
| key: `${parentId}-${item.name}`, | key: `${parentId}-${item.name}`, | ||||
| title: item.name, | |||||
| version: item.name, | |||||
| isLeaf: true, | isLeaf: true, | ||||
| checkable: true, | checkable: true, | ||||
| })); | })); | ||||
| @@ -66,9 +66,9 @@ const convertMirrorVersionToTreeData = ( | |||||
| list: MirrorVersionData[], | list: MirrorVersionData[], | ||||
| ): TreeDataNode[] => { | ): TreeDataNode[] => { | ||||
| return list.map((item: MirrorVersionData) => ({ | return list.map((item: MirrorVersionData) => ({ | ||||
| url: item.url, | |||||
| title: item.tag_name, | |||||
| ...item, | |||||
| key: `${parentId}-${item.id}`, | key: `${parentId}-${item.id}`, | ||||
| title: item.tag_name, | |||||
| isLeaf: true, | isLeaf: true, | ||||
| checkable: true, | checkable: true, | ||||
| })); | })); | ||||
| @@ -125,11 +125,16 @@ export class DatasetSelector implements SelectorTypeInfo { | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | ||||
| const res = await getDatasetInfo(params); | const res = await getDatasetInfo(params); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.dataset_version_vos || []; | |||||
| const dataset = res.data as DatasetData; | |||||
| const { | |||||
| relative_paths: path = '', | |||||
| dataset_version_vos: list = [], | |||||
| version_desc: versionDesc = '', | |||||
| } = dataset; | |||||
| return { | return { | ||||
| path, | path, | ||||
| content: list, | content: list, | ||||
| versionDesc, | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| return Promise.reject('获取数据集文件列表失败'); | return Promise.reject('获取数据集文件列表失败'); | ||||
| @@ -177,11 +182,17 @@ export class ModelSelector implements SelectorTypeInfo { | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | ||||
| const res = await getModelInfo(params); | const res = await getModelInfo(params); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.model_version_vos || []; | |||||
| const model = res.data as ModelData; | |||||
| const { | |||||
| relative_paths: path = '', | |||||
| model_version_vos: list = [], | |||||
| version_desc: versionDesc = '', | |||||
| } = model; | |||||
| return { | return { | ||||
| path, | path, | ||||
| content: list, | content: list, | ||||
| versionDesc, | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| return Promise.reject('获取模型文件列表失败'); | return Promise.reject('获取模型文件列表失败'); | ||||
| @@ -235,7 +246,7 @@ export class MirrorSelector implements SelectorTypeInfo { | |||||
| } | } | ||||
| async getFiles(_parentKey: string, parentNode: MirrorVersionData) { | async getFiles(_parentKey: string, parentNode: MirrorVersionData) { | ||||
| const { url } = parentNode; | |||||
| const { url, description } = parentNode; | |||||
| return { | return { | ||||
| path: url, | path: url, | ||||
| content: [ | content: [ | ||||
| @@ -244,6 +255,7 @@ export class MirrorSelector implements SelectorTypeInfo { | |||||
| file_name: `${url}`, | file_name: `${url}`, | ||||
| }, | }, | ||||
| ], | ], | ||||
| versionDesc: description, | |||||
| }; | }; | ||||
| } | } | ||||
| } | } | ||||
| @@ -65,7 +65,7 @@ | |||||
| border-bottom: 1px solid rgba(22, 100, 255, 0.1); | border-bottom: 1px solid rgba(22, 100, 255, 0.1); | ||||
| } | } | ||||
| &__files { | &__files { | ||||
| height: calc(100% - 75px); | |||||
| height: calc(100% - 61px); | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| &__file { | &__file { | ||||
| @@ -76,7 +76,22 @@ | |||||
| word-break: break-all; | word-break: break-all; | ||||
| background: rgba(4, 3, 3, 0.06); | background: rgba(4, 3, 3, 0.06); | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| &__desc { | |||||
| margin-bottom: 10px; | |||||
| padding: 10px; | |||||
| overflow-y: auto; | |||||
| color: @text-color-secondary; | |||||
| font-size: 13px; | |||||
| word-break: break-all; | |||||
| background: rgba(4, 3, 3, 0.06); | |||||
| border-radius: 4px; | |||||
| max-height: calc(100% - 61px); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,6 +8,7 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { ResourceFileData } from '@/pages/Dataset/config'; | import { ResourceFileData } from '@/pages/Dataset/config'; | ||||
| import { type MirrorVersionData } from '@/pages/Mirror/Info'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | ||||
| import { Input, Tabs, Tree } from 'antd'; | import { Input, Tabs, Tree } from 'antd'; | ||||
| @@ -19,13 +20,13 @@ export { ResourceSelectorType, selectorTypeConfig }; | |||||
| // 选择数据集、模型、镜像的返回类型 | // 选择数据集、模型、镜像的返回类型 | ||||
| export type ResourceSelectorResponse = { | export type ResourceSelectorResponse = { | ||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | activeTab: CommonTabKeys; // 是我的还是公开的 | ||||
| id: string; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| version: string; // 数据集\模型\镜像版本 | |||||
| path: string; // 数据集\模型\镜像版本路径 | |||||
| identifier: string; // 数据集\模型 identifier,镜像这个字段为空 | |||||
| owner: string; // 数据集\模型 owner,镜像这个字段为空 | |||||
| }; | |||||
| id: string; // 数据集\模型 id | |||||
| name: string; // 数据集\模型 name | |||||
| identifier: string; // 数据集\模型 identifier | |||||
| owner: string; // 数据集\模型 owner | |||||
| version: string; // 数据集\模型 version | |||||
| path: string; // 数据集\模型 版本路径 | |||||
| } & MirrorVersionData; | |||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| /** 类型,数据集、模型、镜像 */ | /** 类型,数据集、模型、镜像 */ | ||||
| @@ -84,6 +85,7 @@ function ResourceSelectorModal({ | |||||
| const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | ||||
| const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | ||||
| const [files, setFiles] = useState<ResourceFileData[]>([]); | const [files, setFiles] = useState<ResourceFileData[]>([]); | ||||
| const [versionDesc, setVersionDesc] = useState<string | undefined>(undefined); | |||||
| const [versionPath, setVersionPath] = useState(''); | const [versionPath, setVersionPath] = useState(''); | ||||
| const [searchText, setSearchText] = useState(''); | const [searchText, setSearchText] = useState(''); | ||||
| const [firstLoadList, setFirstLoadList] = useState(false); | const [firstLoadList, setFirstLoadList] = useState(false); | ||||
| @@ -119,6 +121,7 @@ function ResourceSelectorModal({ | |||||
| setCheckedKeys([]); | setCheckedKeys([]); | ||||
| setLoadedKeys([]); | setLoadedKeys([]); | ||||
| setFiles([]); | setFiles([]); | ||||
| setVersionDesc(undefined); | |||||
| setVersionPath(''); | setVersionPath(''); | ||||
| setSearchText(''); | setSearchText(''); | ||||
| getTreeData(); | getTreeData(); | ||||
| @@ -169,9 +172,11 @@ function ResourceSelectorModal({ | |||||
| if (res) { | if (res) { | ||||
| setVersionPath(res.path); | setVersionPath(res.path); | ||||
| setFiles(res.content); | setFiles(res.content); | ||||
| setVersionDesc(res.versionDesc); | |||||
| } else { | } else { | ||||
| setVersionPath(''); | setVersionPath(''); | ||||
| setFiles([]); | setFiles([]); | ||||
| setVersionDesc(undefined); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -201,6 +206,7 @@ function ResourceSelectorModal({ | |||||
| } else { | } else { | ||||
| setVersionPath(''); | setVersionPath(''); | ||||
| setFiles([]); | setFiles([]); | ||||
| setVersionDesc(undefined); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -236,15 +242,22 @@ function ResourceSelectorModal({ | |||||
| const name = (treeNode?.title ?? '') as string; | const name = (treeNode?.title ?? '') as string; | ||||
| const identifier = (treeNode?.identifier ?? '') as string; | const identifier = (treeNode?.identifier ?? '') as string; | ||||
| const owner = (treeNode?.owner ?? '') as string; | const owner = (treeNode?.owner ?? '') as string; | ||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| identifier, | |||||
| owner, | |||||
| activeTab: activeTab, | |||||
| }; | |||||
| const childNode = treeNode.children.filter((v: TreeDataNode) => v.key === last)[0]; | |||||
| const res = | |||||
| type === ResourceSelectorType.Mirror | |||||
| ? { | |||||
| activeTab: activeTab, | |||||
| ...childNode, | |||||
| } | |||||
| : { | |||||
| activeTab: activeTab, | |||||
| id: Number(id), | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| identifier, | |||||
| owner, | |||||
| }; | |||||
| onOk?.(res); | onOk?.(res); | ||||
| } else { | } else { | ||||
| onOk?.(undefined); | onOk?.(undefined); | ||||
| @@ -253,8 +266,9 @@ function ResourceSelectorModal({ | |||||
| const title = `选择${config.name}`; | const title = `选择${config.name}`; | ||||
| const palceholder = `请输入${config.name}名称`; | const palceholder = `请输入${config.name}名称`; | ||||
| const fileLen = files.length > 0 ? `(${files.length})` : ''; | |||||
| const fileTitle = | const fileTitle = | ||||
| type === ResourceSelectorType.Mirror ? '已选镜像' : `已选${config.name}文件(${files.length})`; | |||||
| type === ResourceSelectorType.Mirror ? '镜像地址' : `${config.name}版本文件${fileLen}`; | |||||
| const tabItems = config.tabItems; | const tabItems = config.tabItems; | ||||
| const titleImg = config.modalIcon; | const titleImg = config.modalIcon; | ||||
| @@ -312,14 +326,24 @@ function ResourceSelectorModal({ | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div className={styles['model-selector__right']}> | <div className={styles['model-selector__right']}> | ||||
| <div className={styles['model-selector__right__title']}>{fileTitle}</div> | |||||
| <div className={styles['model-selector__right__files']}> | |||||
| {files.map((v) => ( | |||||
| <div key={v.url} className={styles['model-selector__right__files__file']}> | |||||
| {v.file_name} | |||||
| </div> | |||||
| ))} | |||||
| <div style={{ height: '50%' }}> | |||||
| <div className={styles['model-selector__right__title']}>{fileTitle}</div> | |||||
| <div className={styles['model-selector__right__files']}> | |||||
| {files.map((v) => ( | |||||
| <div key={v.url} className={styles['model-selector__right__files__file']}> | |||||
| {v.file_name} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| </div> | </div> | ||||
| {versionDesc && ( | |||||
| <div style={{ height: '50%' }}> | |||||
| <div | |||||
| className={styles['model-selector__right__title']} | |||||
| >{`${config.name}版本描述`}</div> | |||||
| <div className={styles['model-selector__right__desc']}>{versionDesc}</div> | |||||
| </div> | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -1,15 +1,16 @@ | |||||
| import { clearSessionToken } from '@/access'; | import { clearSessionToken } from '@/access'; | ||||
| import DefaultAvatar from '@/assets/img/avatar-default.png'; | |||||
| import { getLabelStudioUrl } from '@/services/developmentEnvironment'; | import { getLabelStudioUrl } from '@/services/developmentEnvironment'; | ||||
| 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, to } from '@/utils/promise'; | import { sleep, to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import { oauthLogout } 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'; | ||||
| import { history, useModel } from '@umijs/max'; | |||||
| import { useModel, useNavigate } from '@umijs/max'; | |||||
| import { Avatar, Spin } from 'antd'; | import { Avatar, Spin } from 'antd'; | ||||
| import type { MenuInfo } from 'rc-menu/lib/interface'; | import type { MenuInfo } from 'rc-menu/lib/interface'; | ||||
| import React, { useCallback } from 'react'; | import React, { useCallback } from 'react'; | ||||
| @@ -56,10 +57,18 @@ const AvatarLogo = () => { | |||||
| }, | }, | ||||
| }; | }; | ||||
| }); | }); | ||||
| return <Avatar size="small" className={avatarClassName} src={currentUser?.avatar} alt="avatar" />; | |||||
| return ( | |||||
| <Avatar | |||||
| size="small" | |||||
| className={avatarClassName} | |||||
| src={currentUser?.avatar || DefaultAvatar} | |||||
| alt="avatar" | |||||
| /> | |||||
| ); | |||||
| }; | }; | ||||
| const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | ||||
| const navigate = useNavigate(); | |||||
| /** | /** | ||||
| * 退出登录,并且将当前的 url 保存 | * 退出登录,并且将当前的 url 保存 | ||||
| */ | */ | ||||
| @@ -77,10 +86,9 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||||
| if (clientInfo) { | if (clientInfo) { | ||||
| const { logoutUri } = clientInfo; | const { logoutUri } = clientInfo; | ||||
| location.replace(logoutUri); | location.replace(logoutUri); | ||||
| } else { | |||||
| gotoLoginPage(); | |||||
| } | } | ||||
| // setTimeout(() => { | |||||
| // gotoLoginPage(); | |||||
| // }, 100); | |||||
| }; | }; | ||||
| const actionClassName = useEmotionCss(({ token }) => { | const actionClassName = useEmotionCss(({ token }) => { | ||||
| return { | return { | ||||
| @@ -109,9 +117,9 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||||
| loginOut(); | loginOut(); | ||||
| return; | return; | ||||
| } | } | ||||
| history.push(`/account/${key}`); | |||||
| navigate(`/account/${key}`); | |||||
| }, | }, | ||||
| [setInitialState], | |||||
| [setInitialState, navigate], | |||||
| ); | ); | ||||
| const loading = ( | const loading = ( | ||||
| @@ -163,3 +163,11 @@ export enum AutoMLTrailStatus { | |||||
| CANCELLED = 'CANCELLED', // 取消 | CANCELLED = 'CANCELLED', // 取消 | ||||
| MEMOUT = 'MEMOUT', // 内存溢出 | MEMOUT = 'MEMOUT', // 内存溢出 | ||||
| } | } | ||||
| // 流水线组件类型 | |||||
| export enum ComponentType { | |||||
| Ref = 'ref', | |||||
| Select = 'select', | |||||
| Map = 'map', | |||||
| Str = 'str', | |||||
| } | |||||
| @@ -36,7 +36,7 @@ function Authorize() { | |||||
| setSessionToken(access_token, access_token, expires_in); | setSessionToken(access_token, access_token, expires_in); | ||||
| message.success('登录成功!'); | message.success('登录成功!'); | ||||
| await fetchUserInfo(); | await fetchUserInfo(); | ||||
| history.push(redirect || '/'); | |||||
| history.replace(redirect || '/'); | |||||
| } | } | ||||
| }, [fetchUserInfo, redirect, code]); | }, [fetchUserInfo, redirect, code]); | ||||
| @@ -169,16 +169,16 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| const handleMessage = (e: MessageEvent) => { | const handleMessage = (e: MessageEvent) => { | ||||
| const { type, payload } = e.data; | const { type, payload } = e.data; | ||||
| if (type === ExperimentCompleted) { | if (type === ExperimentCompleted) { | ||||
| const { experimentId, experimentInsId, status, finishTime } = payload; | |||||
| const { experimentId, experimentInsId, status /*finishTime*/ } = payload; | |||||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | ||||
| console.log( | |||||
| '实验实例状态变化', | |||||
| currentIns?.status, | |||||
| status, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| finishTime, | |||||
| ); | |||||
| // console.log( | |||||
| // '实验实例状态变化', | |||||
| // currentIns?.status, | |||||
| // status, | |||||
| // experimentId, | |||||
| // experimentInsId, | |||||
| // finishTime, | |||||
| // ); | |||||
| if ( | if ( | ||||
| !currentIns || | !currentIns || | ||||
| @@ -89,6 +89,7 @@ | |||||
| margin-bottom: 15px !important; | margin-bottom: 15px !important; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| word-break: break-all; | |||||
| } | } | ||||
| &__branch { | &__branch { | ||||
| @@ -3,6 +3,7 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 创建开发环境 | * @Description: 创建开发环境 | ||||
| */ | */ | ||||
| import CodeSelect from '@/components/CodeSelect'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| @@ -187,6 +188,14 @@ function EditorCreate() { | |||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item label="代码配置" name="code_config"> | |||||
| <CodeSelect placeholder="请选择代码配置" canInput={false} size="large" /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | ||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| 确定 | 确定 | ||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: 开发环境列表 | * @Description: 开发环境列表 | ||||
| */ | */ | ||||
| import { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { DevEditorStatus } from '@/enums'; | import { DevEditorStatus } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/useCacheState'; | import { useCacheState } from '@/hooks/useCacheState'; | ||||
| @@ -17,6 +18,7 @@ import { | |||||
| } from '@/services/developmentEnvironment'; | } from '@/services/developmentEnvironment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { parseJsonText } from '@/utils'; | import { parseJsonText } from '@/utils'; | ||||
| import { formatCodeConfig, formatDataset, formatModel } from '@/utils/format'; | |||||
| 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'; | ||||
| @@ -49,6 +51,7 @@ export type EditorData = { | |||||
| dataset?: string | DatasetData; | dataset?: string | DatasetData; | ||||
| model?: string | ModelData; | model?: string | ModelData; | ||||
| image?: string; | image?: string; | ||||
| code_config?: string | CodeConfigData; | |||||
| }; | }; | ||||
| function EditorList() { | function EditorList() { | ||||
| @@ -78,6 +81,8 @@ function EditorList() { | |||||
| item.dataset = typeof item.dataset === 'string' ? parseJsonText(item.dataset) : null; | item.dataset = typeof item.dataset === 'string' ? parseJsonText(item.dataset) : null; | ||||
| item.model = typeof item.model === 'string' ? parseJsonText(item.model) : null; | item.model = typeof item.model === 'string' ? parseJsonText(item.model) : null; | ||||
| item.image = typeof item.image === 'string' ? parseJsonText(item.image) : null; | item.image = typeof item.image === 'string' ? parseJsonText(item.image) : null; | ||||
| item.code_config = | |||||
| typeof item.code_config === 'string' ? parseJsonText(item.code_config) : null; | |||||
| }); | }); | ||||
| setTableData(content); | setTableData(content); | ||||
| setTotal(totalElements); | setTotal(totalElements); | ||||
| @@ -159,13 +164,54 @@ function EditorList() { | |||||
| }; | }; | ||||
| // 跳转编辑器页面 | // 跳转编辑器页面 | ||||
| const gotoEditorPage = (e: React.MouseEvent, record: EditorData) => { | |||||
| const gotoEditorPage = (record: EditorData, e: React.MouseEvent) => { | |||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| SessionStorage.setItem(SessionStorage.editorUrlKey, record.url); | |||||
| navigate(`/developmentEnvironment/editor`); | |||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| }); | }); | ||||
| SessionStorage.setItem(SessionStorage.editorUrlKey, record.url); | |||||
| navigate(`/developmentEnvironment/editor`); | |||||
| }; | |||||
| // 去数据集 | |||||
| const gotoDataset = (record: EditorData, e: React.MouseEvent) => { | |||||
| e.stopPropagation(); | |||||
| const dataset = record.dataset as DatasetData; | |||||
| const link = formatDataset(dataset)?.link; | |||||
| if (link) { | |||||
| setCacheState({ | |||||
| pagination, | |||||
| }); | |||||
| navigate(link); | |||||
| } | |||||
| }; | |||||
| // 去模型 | |||||
| const gotoModel = (record: EditorData, e: React.MouseEvent) => { | |||||
| e.stopPropagation(); | |||||
| const model = record.model as ModelData; | |||||
| const link = formatModel(model)?.link; | |||||
| if (link) { | |||||
| setCacheState({ | |||||
| pagination, | |||||
| }); | |||||
| navigate(link); | |||||
| } | |||||
| }; | |||||
| // 打开代码配置仓库 | |||||
| const gotoCodeConfig = (record: EditorData, e: React.MouseEvent) => { | |||||
| e.stopPropagation(); | |||||
| const codeConfig = record.code_config as CodeConfigData; | |||||
| const url = formatCodeConfig(codeConfig)?.url; | |||||
| if (url) { | |||||
| window.open(url, '_blank'); | |||||
| } | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -185,11 +231,11 @@ function EditorList() { | |||||
| title: '编辑器名称', | title: '编辑器名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| width: '16%', | |||||
| width: '12%', | |||||
| render: (text, record, index) => | render: (text, record, index) => | ||||
| record.url && record.status === DevEditorStatus.Running | record.url && record.status === DevEditorStatus.Running | ||||
| ? tableCellRender<EditorData>(true, TableCellValueType.Link, { | ? tableCellRender<EditorData>(true, TableCellValueType.Link, { | ||||
| onClick: (record, e) => gotoEditorPage(e, record), | |||||
| onClick: gotoEditorPage, | |||||
| })(text, record, index) | })(text, record, index) | ||||
| : tableCellRender<EditorData>(true, TableCellValueType.Text)(text, record, index), | : tableCellRender<EditorData>(true, TableCellValueType.Text)(text, record, index), | ||||
| }, | }, | ||||
| @@ -197,14 +243,14 @@ function EditorList() { | |||||
| title: '计算资源', | title: '计算资源', | ||||
| dataIndex: 'computing_resource', | dataIndex: 'computing_resource', | ||||
| key: 'computing_resource', | key: 'computing_resource', | ||||
| width: '12%', | |||||
| width: '11%', | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '资源规格', | title: '资源规格', | ||||
| dataIndex: 'computing_resource_id', | dataIndex: 'computing_resource_id', | ||||
| key: 'computing_resource_id', | key: 'computing_resource_id', | ||||
| width: '12%', | |||||
| width: '11%', | |||||
| render: tableCellRender(true, TableCellValueType.Custom, { | render: tableCellRender(true, TableCellValueType.Custom, { | ||||
| format: getResourceDescription, | format: getResourceDescription, | ||||
| }), | }), | ||||
| @@ -213,42 +259,55 @@ function EditorList() { | |||||
| title: '数据集', | title: '数据集', | ||||
| dataIndex: ['dataset', 'showValue'], | dataIndex: ['dataset', 'showValue'], | ||||
| key: 'dataset', | key: 'dataset', | ||||
| width: '12%', | |||||
| render: tableCellRender(true), | |||||
| width: '11%', | |||||
| render: tableCellRender(true, TableCellValueType.Link, { | |||||
| onClick: gotoDataset, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '模型', | title: '模型', | ||||
| dataIndex: ['model', 'showValue'], | dataIndex: ['model', 'showValue'], | ||||
| key: 'model', | key: 'model', | ||||
| width: '12%', | |||||
| render: tableCellRender(true), | |||||
| width: '11%', | |||||
| render: tableCellRender(true, TableCellValueType.Link, { | |||||
| onClick: gotoModel, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '代码配置', | |||||
| dataIndex: ['code_config', 'showValue'], | |||||
| key: 'code_config', | |||||
| width: '11%', | |||||
| render: tableCellRender(true, TableCellValueType.Link, { | |||||
| onClick: gotoCodeConfig, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像', | title: '镜像', | ||||
| dataIndex: ['image', 'showValue'], | dataIndex: ['image', 'showValue'], | ||||
| key: 'image', | key: 'image', | ||||
| width: '12%', | |||||
| width: '11%', | |||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '创建者', | title: '创建者', | ||||
| dataIndex: 'update_by', | dataIndex: 'update_by', | ||||
| key: 'update_by', | key: 'update_by', | ||||
| width: '12%', | |||||
| width: '11%', | |||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: '12%', | |||||
| width: '11%', | |||||
| render: tableCellRender(true, TableCellValueType.Date), | render: tableCellRender(true, TableCellValueType.Date), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| key: 'status', | key: 'status', | ||||
| width: 80, | |||||
| width: 100, | |||||
| render: EditorStatusCell, | render: EditorStatusCell, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -95,16 +95,13 @@ function ExperimentText() { | |||||
| return; | return; | ||||
| } | } | ||||
| const workflow = parseJsonText(dag); | |||||
| const workflow = dag; | |||||
| const experimentStatusObjs = parseJsonText(nodes_status); | const experimentStatusObjs = parseJsonText(nodes_status); | ||||
| if (!workflow || !workflow.nodes) { | if (!workflow || !workflow.nodes) { | ||||
| return; | return; | ||||
| } | } | ||||
| workflow.nodes.forEach((item) => { | workflow.nodes.forEach((item) => { | ||||
| item.in_parameters = parseJsonText(item.in_parameters); | |||||
| item.out_parameters = parseJsonText(item.out_parameters); | |||||
| item.control_strategy = parseJsonText(item.control_strategy); | |||||
| item.imgName = item.img.slice(0, item.img.length - 4); | item.imgName = item.img.slice(0, item.img.length - 4); | ||||
| }); | }); | ||||
| workflowRef.current = workflow; | workflowRef.current = workflow; | ||||
| @@ -140,8 +137,11 @@ function ExperimentText() { | |||||
| } else if (status === ExperimentStatus.Running) { | } else if (status === ExperimentStatus.Running) { | ||||
| // 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点 | // 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点 | ||||
| const node = | const node = | ||||
| workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ?? | |||||
| workflow.nodes[0]; | |||||
| workflow.nodes.find( | |||||
| (item) => | |||||
| item.experimentStatus === ExperimentStatus.Running || | |||||
| item.experimentStatus === ExperimentStatus.Pending, | |||||
| ) ?? workflow.nodes[0]; | |||||
| if (node) { | if (node) { | ||||
| setExperimentNodeData(node); | setExperimentNodeData(node); | ||||
| openPropsDrawer(); | openPropsDrawer(); | ||||
| @@ -567,12 +567,13 @@ function ExperimentText() { | |||||
| instanceNodeStatus={experimentNodeData.experimentStatus} | instanceNodeStatus={experimentNodeData.experimentStatus} | ||||
| instanceNodeStartTime={experimentNodeData.experimentStartTime} | instanceNodeStartTime={experimentNodeData.experimentStartTime} | ||||
| instanceNodeEndTime={experimentNodeData.experimentEndTime} | instanceNodeEndTime={experimentNodeData.experimentEndTime} | ||||
| globalParams={experimentIns?.global_param} | |||||
| ></ExperimentDrawer> | ></ExperimentDrawer> | ||||
| ) : null} | ) : null} | ||||
| <ParamsModal | <ParamsModal | ||||
| open={paramsModalOpen} | open={paramsModalOpen} | ||||
| onCancel={closeParamsModal} | onCancel={closeParamsModal} | ||||
| globalParam={experimentIns?.global_param} | |||||
| globalParams={experimentIns?.global_param} | |||||
| ></ParamsModal> | ></ParamsModal> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -6,4 +6,10 @@ | |||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 6px; | border-radius: 6px; | ||||
| } | } | ||||
| :global { | |||||
| .ant-form-item-row { | |||||
| align-items: center; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -1,7 +1,7 @@ | |||||
| import createExperimentIcon from '@/assets/img/create-experiment.png'; | import createExperimentIcon from '@/assets/img/create-experiment.png'; | ||||
| import editExperimentIcon from '@/assets/img/edit-experiment.png'; | import editExperimentIcon from '@/assets/img/edit-experiment.png'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | |||||
| import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| @@ -32,7 +32,7 @@ interface Workflow { | |||||
| // 根据参数设置输入组件 | // 根据参数设置输入组件 | ||||
| export const getParamComponent = (paramType: number): JSX.Element => { | export const getParamComponent = (paramType: number): JSX.Element => { | ||||
| // 防止后台返回不是 number 类型 | // 防止后台返回不是 number 类型 | ||||
| if (Number(paramType) === 3) { | |||||
| if (Number(paramType) === PipelineGlobalParamType.Boolean) { | |||||
| return ( | return ( | ||||
| <Radio.Group> | <Radio.Group> | ||||
| <Radio value={1}>是</Radio> | <Radio value={1}>是</Radio> | ||||
| @@ -50,7 +50,7 @@ export const getParamComponent = (paramType: number): JSX.Element => { | |||||
| export const getParamRules = (paramType: number, required: boolean = false): FormRule[] => { | export const getParamRules = (paramType: number, required: boolean = false): FormRule[] => { | ||||
| const rules = []; | const rules = []; | ||||
| // 防止后台返回不是 number 类型 | // 防止后台返回不是 number 类型 | ||||
| if (Number(paramType) === 2) { | |||||
| if (Number(paramType) === PipelineGlobalParamType.Number) { | |||||
| rules.push({ | rules.push({ | ||||
| pattern: /^-?((0(\.0*[1-9]\d*)?)|([1-9]\d*(\.\d+)?))$/, | pattern: /^-?((0(\.0*[1-9]\d*)?)|([1-9]\d*(\.\d+)?))$/, | ||||
| message: '整型必须是数字', | message: '整型必须是数字', | ||||
| @@ -64,10 +64,10 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||||
| // 根据参数设置 label | // 根据参数设置 label | ||||
| export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | ||||
| const paramTypes: Readonly<Record<number, string>> = { | |||||
| 1: '字符串', | |||||
| 2: '整型', | |||||
| 3: '布尔类型', | |||||
| const paramTypes: Readonly<Record<PipelineGlobalParamType, string>> = { | |||||
| [PipelineGlobalParamType.String]: '字符串', | |||||
| [PipelineGlobalParamType.Number]: '整型', | |||||
| [PipelineGlobalParamType.Boolean]: '布尔类型', | |||||
| }; | }; | ||||
| const label = 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>; | return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | ||||
| @@ -1,7 +1,7 @@ | |||||
| import RunDuration from '@/components/RunDuration'; | import RunDuration from '@/components/RunDuration'; | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { PipelineNodeModelSerialize, type PipelineGlobalParam } from '@/types'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | ||||
| import { Drawer, Tabs, Typography } from 'antd'; | import { Drawer, Tabs, Typography } from 'antd'; | ||||
| @@ -25,6 +25,7 @@ type ExperimentDrawerProps = { | |||||
| instanceNodeStatus?: ExperimentStatus; // 实例节点状态 | instanceNodeStatus?: ExperimentStatus; // 实例节点状态 | ||||
| instanceNodeStartTime?: string; // 开始时间 | instanceNodeStartTime?: string; // 开始时间 | ||||
| instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化 | instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化 | ||||
| globalParams?: PipelineGlobalParam[] | null; // 全局参数 | |||||
| }; | }; | ||||
| const ExperimentDrawer = ({ | const ExperimentDrawer = ({ | ||||
| @@ -41,6 +42,7 @@ const ExperimentDrawer = ({ | |||||
| instanceNodeStatus, | instanceNodeStatus, | ||||
| instanceNodeStartTime, | instanceNodeStartTime, | ||||
| instanceNodeEndTime, | instanceNodeEndTime, | ||||
| globalParams, | |||||
| }: ExperimentDrawerProps) => { | }: ExperimentDrawerProps) => { | ||||
| // 如果性能有问题,可以进一步拆解 | // 如果性能有问题,可以进一步拆解 | ||||
| const items = useMemo( | const items = useMemo( | ||||
| @@ -66,7 +68,7 @@ const ExperimentDrawer = ({ | |||||
| key: '2', | key: '2', | ||||
| label: '配置参数', | label: '配置参数', | ||||
| icon: <DatabaseOutlined />, | icon: <DatabaseOutlined />, | ||||
| children: <ExperimentParameter nodeData={instanceNodeData} />, | |||||
| children: <ExperimentParameter nodeData={instanceNodeData} globalParams={globalParams} />, | |||||
| }, | }, | ||||
| { | { | ||||
| key: '3', | key: '3', | ||||
| @@ -94,6 +96,7 @@ const ExperimentDrawer = ({ | |||||
| experimentName, | experimentName, | ||||
| experimentId, | experimentId, | ||||
| pipelineId, | pipelineId, | ||||
| globalParams, | |||||
| ], | ], | ||||
| ); | ); | ||||
| @@ -15,4 +15,24 @@ | |||||
| font-size: @font-size; | font-size: @font-size; | ||||
| background: #f8fbff; | background: #f8fbff; | ||||
| } | } | ||||
| &__form-list { | |||||
| :global { | |||||
| .ant-row { | |||||
| padding: 0 !important; | |||||
| } | |||||
| } | |||||
| &:last-child { | |||||
| :global { | |||||
| .ant-form-item { | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__list-empty { | |||||
| color: @text-color-tertiary; | |||||
| } | |||||
| } | } | ||||
| @@ -1,25 +1,92 @@ | |||||
| import FormInfo from '@/components/FormInfo'; | import FormInfo from '@/components/FormInfo'; | ||||
| import ParameterSelect from '@/components/ParameterSelect'; | |||||
| import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { Form } from 'antd'; | |||||
| import { ComponentType } from '@/enums'; | |||||
| import type { | |||||
| PipelineGlobalParam, | |||||
| PipelineNodeModelParameter, | |||||
| PipelineNodeModelSerialize, | |||||
| } from '@/types'; | |||||
| import { Flex, Form } from 'antd'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentParameterProps = { | type ExperimentParameterProps = { | ||||
| nodeData: PipelineNodeModelSerialize; | nodeData: PipelineNodeModelSerialize; | ||||
| globalParams?: PipelineGlobalParam[] | null; // 全局参数 | |||||
| }; | }; | ||||
| function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||||
| function ExperimentParameter({ nodeData, globalParams }: ExperimentParameterProps) { | |||||
| // 表单组件 | |||||
| const getFormComponent = ( | |||||
| item: { key: string; value: PipelineNodeModelParameter }, | |||||
| parentName: string, | |||||
| ) => { | |||||
| return ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={[parentName, item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| {item.value.type === ComponentType.Map && ( | |||||
| <Form.List name={[parentName, item.key, 'value']}> | |||||
| {(fields) => ( | |||||
| <> | |||||
| {fields.length > 0 ? ( | |||||
| fields.map(({ key, name, ...restField }) => ( | |||||
| <Flex | |||||
| key={key} | |||||
| gap="0 8px" | |||||
| style={{ width: '100%' }} | |||||
| className={styles['experiment-parameter__form-list']} | |||||
| > | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'name']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| > | |||||
| <FormInfo /> | |||||
| </Form.Item> | |||||
| <span style={{ lineHeight: '32px' }}>=</span> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| > | |||||
| <FormInfo valuePropName="showValue" globalParams={globalParams} /> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| )) | |||||
| ) : ( | |||||
| <div className={styles['experiment-parameter__list-empty']}>无</div> | |||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| )} | |||||
| {item.value.type === ComponentType.Select && | |||||
| (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||||
| <ParameterSelect dataType={item.value.item_type as ParameterSelectDataType} display /> | |||||
| ) : null)} | |||||
| {item.value.type !== ComponentType.Map && item.value.type !== ComponentType.Select && ( | |||||
| <FormInfo valuePropName="showValue" globalParams={globalParams} /> | |||||
| )} | |||||
| </Form.Item> | |||||
| ); | |||||
| }; | |||||
| // 基本参数 | |||||
| const basicParametersList = Object.entries(nodeData.task_info ?? {}) | |||||
| .map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 控制策略 | // 控制策略 | ||||
| // const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | |||||
| // ([key, value]) => ({ key, value }), | |||||
| // ); | |||||
| const nodeId = nodeData.id; | |||||
| const hasTaskInfo = | |||||
| nodeId && | |||||
| !nodeId.startsWith('git-clone') && | |||||
| !nodeId.startsWith('dataset-export') && | |||||
| !nodeId.startsWith('model-export'); | |||||
| const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}) | |||||
| .map(([key, value]) => ({ key, value })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 输入参数 | // 输入参数 | ||||
| const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ | const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ | ||||
| @@ -80,96 +147,56 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||||
| > | > | ||||
| <FormInfo /> | <FormInfo /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {hasTaskInfo && ( | |||||
| {basicParametersList.length + controlStrategyList.length > 0 && ( | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="任务信息" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| )} | |||||
| {/* 基本参数 */} | |||||
| {basicParametersList.map((item) => getFormComponent(item, 'task_info'))} | |||||
| {/* 控制参数 */} | |||||
| {controlStrategyList.map((item) => getFormComponent(item, 'control_strategy'))} | |||||
| {/* 输入参数 */} | |||||
| {inParametersList.length > 0 && ( | |||||
| <> | <> | ||||
| <div className={styles['experiment-parameter__title']}> | <div className={styles['experiment-parameter__title']}> | ||||
| <SubAreaTitle | <SubAreaTitle | ||||
| image={require('@/assets/img/duty-message.png')} | image={require('@/assets/img/duty-message.png')} | ||||
| title="任务信息" | |||||
| title="输入参数" | |||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| </div> | </div> | ||||
| <Form.Item | |||||
| label="镜像" | |||||
| name="image" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <FormInfo /> | |||||
| </Form.Item> | |||||
| <Form.Item label="工作目录" name="working_directory"> | |||||
| <FormInfo /> | |||||
| </Form.Item> | |||||
| {inParametersList.map((item) => getFormComponent(item, 'in_parameters'))} | |||||
| </> | |||||
| )} | |||||
| <Form.Item label="启动命令" name="command"> | |||||
| <FormInfo textArea /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterSelect dataType="resource" placeholder="请选择资源规格" display /> | |||||
| </Form.Item> | |||||
| {/* <Form.Item label="挂载路径" name="mount_path"> | |||||
| <FormInfo /> | |||||
| </Form.Item> */} | |||||
| <Form.Item label="环境变量" name="env_variables"> | |||||
| <FormInfo textArea /> | |||||
| </Form.Item> | |||||
| {/* {controlStrategyList.map((item) => ( | |||||
| <Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}> | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| </Form.Item> | |||||
| ))} */} | |||||
| {/* 输出参数 */} | |||||
| {outParametersList.length > 0 && ( | |||||
| <> | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="输出参数" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </> | </> | ||||
| )} | )} | ||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="输入参数" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| {inParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['in_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| {item.value.type === 'select' ? ( | |||||
| ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||||
| <ParameterSelect dataType={item.value.item_type as any} display /> | |||||
| ) : null | |||||
| ) : ( | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| )} | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="输出参数" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -4,6 +4,12 @@ | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| :global { | |||||
| .ant-form-item-row { | |||||
| align-items: center; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| .params-empty { | .params-empty { | ||||
| :global { | :global { | ||||
| @@ -14,10 +14,10 @@ import styles from './index.less'; | |||||
| type ParamsModalProps = { | type ParamsModalProps = { | ||||
| open: boolean; | open: boolean; | ||||
| onCancel: () => void; | onCancel: () => void; | ||||
| globalParam?: PipelineGlobalParam[] | null; | |||||
| globalParams?: PipelineGlobalParam[] | null; | |||||
| }; | }; | ||||
| function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| function ParamsModal({ open, onCancel, globalParams = [] }: ParamsModalProps) { | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| title="执行参数" | title="执行参数" | ||||
| @@ -28,13 +28,13 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| cancelButtonProps={{ style: { display: 'none' } }} | cancelButtonProps={{ style: { display: 'none' } }} | ||||
| width={825} | width={825} | ||||
| > | > | ||||
| {Array.isArray(globalParam) && globalParam.length > 0 ? ( | |||||
| {Array.isArray(globalParams) && globalParams.length > 0 ? ( | |||||
| <div className={styles['params-container']}> | <div className={styles['params-container']}> | ||||
| <Form | <Form | ||||
| name="view_params_form" | name="view_params_form" | ||||
| labelCol={{ span: 6 }} | labelCol={{ span: 6 }} | ||||
| wrapperCol={{ span: 18 }} | wrapperCol={{ span: 18 }} | ||||
| initialValues={{ global_param: globalParam }} | |||||
| initialValues={{ global_param: globalParams }} | |||||
| labelAlign="left" | labelAlign="left" | ||||
| disabled | disabled | ||||
| > | > | ||||
| @@ -45,9 +45,9 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| {...restField} | {...restField} | ||||
| key={key} | key={key} | ||||
| name={[name, 'param_value']} | name={[name, 'param_value']} | ||||
| label={getParamLabel(globalParam[name])} | |||||
| label={getParamLabel(globalParams[name])} | |||||
| > | > | ||||
| {getParamComponent(globalParam[name]['param_type'])} | |||||
| {getParamComponent(globalParams[name]['param_type'])} | |||||
| </Form.Item> | </Form.Item> | ||||
| )) | )) | ||||
| } | } | ||||
| @@ -226,14 +226,14 @@ function Experiment() { | |||||
| if (type === ExperimentCompleted) { | if (type === ExperimentCompleted) { | ||||
| const { experimentId, experimentInsId, status, finishTime } = payload; | const { experimentId, experimentInsId, status, finishTime } = payload; | ||||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | ||||
| console.log( | |||||
| '实验实例状态变化', | |||||
| currentIns?.status, | |||||
| status, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| finishTime, | |||||
| ); | |||||
| // console.log( | |||||
| // '实验实例状态变化', | |||||
| // currentIns?.status, | |||||
| // status, | |||||
| // experimentId, | |||||
| // experimentInsId, | |||||
| // finishTime, | |||||
| // ); | |||||
| if ( | if ( | ||||
| !currentIns || | !currentIns || | ||||
| @@ -323,6 +323,7 @@ function ExecuteConfig() { | |||||
| className={styles['hyper-parameter__body__name']} | className={styles['hyper-parameter__body__name']} | ||||
| {...restField} | {...restField} | ||||
| name={[name, 'name']} | name={[name, 'name']} | ||||
| dependencies={fields.map((_, i) => ['parameters', i, 'name'])} | |||||
| required | required | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| @@ -46,6 +46,7 @@ export type MirrorInfoData = { | |||||
| }; | }; | ||||
| export type MirrorVersionData = { | export type MirrorVersionData = { | ||||
| image_id: number; | |||||
| id: number; | id: number; | ||||
| version: string; | version: string; | ||||
| url: string; | url: string; | ||||
| @@ -53,6 +54,7 @@ export type MirrorVersionData = { | |||||
| file_size: string; | file_size: string; | ||||
| create_time: string; | create_time: string; | ||||
| tag_name: string; | tag_name: string; | ||||
| description: string; | |||||
| }; | }; | ||||
| function MirrorInfo() { | function MirrorInfo() { | ||||
| @@ -23,7 +23,7 @@ import { removeFormListItem } from '@/utils/ui'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| import { App, Button, Col, Flex, Form, Input, InputNumber, Row } from 'antd'; | import { App, Button, Col, Flex, Form, Input, InputNumber, Row } from 'antd'; | ||||
| import { omit, pick } from 'lodash'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { CreateServiceVersionFrom, ServiceOperationType, ServiceVersionData } from '../types'; | import { CreateServiceVersionFrom, ServiceOperationType, ServiceVersionData } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -79,7 +79,7 @@ function CreateServiceVersion() { | |||||
| if (res.model && typeof res.model === 'object') { | if (res.model && typeof res.model === 'object') { | ||||
| model = changePropertyName(res.model, { show_value: 'showValue' }); | model = changePropertyName(res.model, { show_value: 'showValue' }); | ||||
| // 接口返回是数据没有 value 值,但是 form 需要 value | // 接口返回是数据没有 value 值,但是 form 需要 value | ||||
| model.value = model.showValue; | |||||
| // model.value = model.showValue; | |||||
| } | } | ||||
| // 环境变量 | // 环境变量 | ||||
| if (res.env_variables && typeof res.env_variables === 'object') { | if (res.env_variables && typeof res.env_variables === 'object') { | ||||
| @@ -117,7 +117,6 @@ function CreateServiceVersion() { | |||||
| // 创建版本 | // 创建版本 | ||||
| const createServiceVersion = async (formData: FormData) => { | const createServiceVersion = async (formData: FormData) => { | ||||
| const envList = formData['env_variables']; | const envList = formData['env_variables']; | ||||
| const model = formData['model']; | |||||
| const envVariables = envList?.reduce((acc, cur) => { | const envVariables = envList?.reduce((acc, cur) => { | ||||
| acc[cur.key] = cur.value; | acc[cur.key] = cur.value; | ||||
| return acc; | return acc; | ||||
| @@ -125,13 +124,9 @@ function CreateServiceVersion() { | |||||
| // 根据后台要求,修改表单数据 | // 根据后台要求,修改表单数据 | ||||
| const object = { | const object = { | ||||
| ...omit(formData, ['replicas', 'env_variables', 'model']), | |||||
| ...omit(formData, ['replicas', 'env_variables']), | |||||
| replicas: Number(formData.replicas), | replicas: Number(formData.replicas), | ||||
| env_variables: envVariables, | env_variables: envVariables, | ||||
| model: changePropertyName( | |||||
| pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), | |||||
| { showValue: 'show_value' }, | |||||
| ), | |||||
| service_id: serviceId, | service_id: serviceId, | ||||
| }; | }; | ||||
| @@ -427,6 +422,7 @@ function CreateServiceVersion() { | |||||
| {...restField} | {...restField} | ||||
| name={[name, 'key']} | name={[name, 'key']} | ||||
| style={{ flex: 1 }} | style={{ flex: 1 }} | ||||
| dependencies={fields.map((_, i) => ['env_variables', i, 'key'])} | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| validator: (_, value) => { | validator: (_, value) => { | ||||
| @@ -10,6 +10,7 @@ import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/useCacheState'; | import { useCacheState } from '@/hooks/useCacheState'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | import { useComputingResource } from '@/hooks/useComputingResource'; | ||||
| import { ModelData } from '@/pages/Dataset/config'; | |||||
| import { | import { | ||||
| deleteServiceVersionReq, | deleteServiceVersionReq, | ||||
| getServiceInfoReq, | getServiceInfoReq, | ||||
| @@ -18,6 +19,7 @@ import { | |||||
| } from '@/services/modelDeployment'; | } from '@/services/modelDeployment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { formatModel } from '@/utils/format'; | |||||
| 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'; | ||||
| @@ -110,8 +112,8 @@ function ServiceInfo() { | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| content.forEach((item: ServiceVersionData) => { | content.forEach((item: ServiceVersionData) => { | ||||
| if (item.model && !item.model.show_value) { | |||||
| item.model.show_value = `${item.model.name}:${item.model.version}`; | |||||
| if (item.model && !item.model.showValue) { | |||||
| item.model.showValue = `${item.model.name}:${item.model.version}`; | |||||
| } | } | ||||
| }); | }); | ||||
| setTableData(content); | setTableData(content); | ||||
| @@ -258,6 +260,20 @@ function ServiceInfo() { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 去模型 | |||||
| const gotoModel = (record: ServiceVersionData, e: React.MouseEvent) => { | |||||
| e.stopPropagation(); | |||||
| const model = record.model as any as ModelData; | |||||
| const link = formatModel(model)?.link; | |||||
| if (link) { | |||||
| setCacheState({ | |||||
| pagination, | |||||
| }); | |||||
| navigate(link); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<ServiceVersionData>['columns'] = [ | const columns: TableProps<ServiceVersionData>['columns'] = [ | ||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| @@ -278,10 +294,12 @@ function ServiceInfo() { | |||||
| }, | }, | ||||
| { | { | ||||
| title: '模型版本', | title: '模型版本', | ||||
| dataIndex: ['model', 'show_value'], | |||||
| dataIndex: ['model', 'showValue'], | |||||
| key: 'model', | key: 'model', | ||||
| width: '20%', | width: '20%', | ||||
| render: tableCellRender(true), | |||||
| render: tableCellRender(true, TableCellValueType.Link, { | |||||
| onClick: gotoModel, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像版本', | title: '镜像版本', | ||||
| @@ -34,7 +34,7 @@ export type ServiceVersionData = { | |||||
| path: string; | path: string; | ||||
| identifier: string; | identifier: string; | ||||
| owner: string; | owner: string; | ||||
| show_value: string; | |||||
| showValue: string; | |||||
| }; | }; | ||||
| code_config: { | code_config: { | ||||
| // 代码配置 | // 代码配置 | ||||
| @@ -3,7 +3,7 @@ import { useStateRef } from '@/hooks/useStateRef'; | |||||
| import { useVisible } from '@/hooks/useVisible'; | 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, s8 } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| @@ -54,11 +54,20 @@ const EditPipeline = () => { | |||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| const { x, y } = val; | const { x, y } = val; | ||||
| const point = graph.getPointByClient(x, y); | const point = graph.getPointByClient(x, y); | ||||
| let label = val.label; | |||||
| const data = graph.save(); | |||||
| const nodeLabels = data.nodes.map((v) => v.label); | |||||
| if (nodeLabels.includes(label)) { | |||||
| label += '-' + s8(); | |||||
| } | |||||
| // 元模型 | // 元模型 | ||||
| const model = { | const model = { | ||||
| ...val, | ...val, | ||||
| x: point.x, | x: point.x, | ||||
| y: point.y, | y: point.y, | ||||
| label, | |||||
| id: val.component_name + '-' + s8(), | id: val.component_name + '-' + s8(), | ||||
| isCluster: false, | isCluster: false, | ||||
| formError: true, | formError: true, | ||||
| @@ -90,24 +99,29 @@ const EditPipeline = () => { | |||||
| // 保存 | // 保存 | ||||
| const savePipeline = async (isBack) => { | const savePipeline = async (isBack) => { | ||||
| const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); | |||||
| if (globalParamError) { | |||||
| message.error('全局参数配置有误'); | |||||
| openParamsDrawer(); | |||||
| return; | |||||
| } | |||||
| closeParamsDrawer(); | |||||
| // 验证全局参数 | |||||
| // 现在改为关闭的时候就验证了 | |||||
| // const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); | |||||
| // if (globalParamError) { | |||||
| // message.error('全局参数配置有误'); | |||||
| // openParamsDrawer(); | |||||
| // return; | |||||
| // } | |||||
| // closeParamsDrawer(); | |||||
| const [propsRes, propsError] = await to(propsRef.current.validateFields()); | |||||
| if (propsError) { | |||||
| message.error('节点必填项必须配置'); | |||||
| return; | |||||
| } | |||||
| propsRef.current.close(); | |||||
| // 以前没有遮挡【保存】按钮时有用 | |||||
| // 验证节点必填参数 | |||||
| // const [propsRes, propsError] = await to(propsRef.current.validateFields()); | |||||
| // if (propsError) { | |||||
| // message.error('节点必填项必须配置'); | |||||
| // return; | |||||
| // } | |||||
| // propsRef.current.close(); | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| const data = graph.save(); | const data = graph.save(); | ||||
| // console.log(data); | // console.log(data); | ||||
| // 验证节点必填参数 | |||||
| const errorNode = data.nodes.find((item) => item.formError === true); | const errorNode = data.nodes.find((item) => item.formError === true); | ||||
| if (errorNode) { | if (errorNode) { | ||||
| message.error(`【${errorNode.label}】节点配置验证失败`); | message.error(`【${errorNode.label}】节点配置验证失败`); | ||||
| @@ -117,11 +131,25 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| return; | return; | ||||
| } | } | ||||
| // 验证节点名称是否有重命名 | |||||
| const nodeLabels = data.nodes.map((v) => v.label); | |||||
| for (let i = 0; i < nodeLabels.length; i++) { | |||||
| const current = nodeLabels[i]; | |||||
| for (let j = i + 1; j < nodeLabels.length; j++) { | |||||
| const next = nodeLabels[j]; | |||||
| if (current === next) { | |||||
| message.error(`存在重名的【${current}】节点`); | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| const params = { | const params = { | ||||
| ...locationParams, | ...locationParams, | ||||
| name: workflowInfo?.name, | name: workflowInfo?.name, | ||||
| dag: JSON.stringify(data), | |||||
| global_param: JSON.stringify(globalParamRes.global_param), | |||||
| dag: data, | |||||
| global_param: globalParam, | |||||
| }; | }; | ||||
| saveWorkflow(params).then((ret) => { | saveWorkflow(params).then((ret) => { | ||||
| message.success('保存成功'); | message.success('保存成功'); | ||||
| @@ -290,7 +318,7 @@ const EditPipeline = () => { | |||||
| const { global_param, dag } = res.data; | const { global_param, dag } = res.data; | ||||
| setGlobalParam(global_param || []); | setGlobalParam(global_param || []); | ||||
| if (dag) { | if (dag) { | ||||
| getGraphData(parseJsonText(dag)); | |||||
| getGraphData(dag); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -299,13 +327,19 @@ const EditPipeline = () => { | |||||
| const openNodeDrawer = (node, validate = false) => { | const openNodeDrawer = (node, validate = false) => { | ||||
| // 获取所有的上游节点 | // 获取所有的上游节点 | ||||
| const parentNodes = findAllParentNodes(graph, node); | const parentNodes = findAllParentNodes(graph, node); | ||||
| // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| const globalParams = | |||||
| paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| // q全局参数 | |||||
| const globalParams = globalParamRef.current; | |||||
| // 打开节点编辑抽屉 | // 打开节点编辑抽屉 | ||||
| propsRef.current.showDrawer(node.getModel(), globalParams, parentNodes, validate); | propsRef.current.showDrawer(node.getModel(), globalParams, parentNodes, validate); | ||||
| }; | }; | ||||
| // 关闭全局参数节点,获取全局参数 | |||||
| const closeGlobalParamsDrawer = () => { | |||||
| const { global_param } = paramsDrawerRef.current.getFieldsValue(); | |||||
| setGlobalParam(global_param); | |||||
| closeParamsDrawer(); | |||||
| }; | |||||
| // 初始化图 | // 初始化图 | ||||
| const initGraph = () => { | const initGraph = () => { | ||||
| const contextMenu = initMenu(); | const contextMenu = initMenu(); | ||||
| @@ -730,7 +764,7 @@ const EditPipeline = () => { | |||||
| ref={paramsDrawerRef} | ref={paramsDrawerRef} | ||||
| open={paramsDrawerOpen} | open={paramsDrawerOpen} | ||||
| globalParam={globalParam} | globalParam={globalParam} | ||||
| onClose={closeParamsDrawer} | |||||
| onClose={closeGlobalParamsDrawer} | |||||
| ></GlobalParamsDrawer> | ></GlobalParamsDrawer> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -1,5 +1,4 @@ | |||||
| import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; | import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | |||||
| import { Graph, INode } from '@antv/g6'; | import { Graph, INode } from '@antv/g6'; | ||||
| import { type MenuProps } from 'antd'; | import { type MenuProps } from 'antd'; | ||||
| @@ -42,8 +41,7 @@ export function createMenuItems( | |||||
| ): MenuProps['items'] { | ): MenuProps['items'] { | ||||
| const nodes: MenuProps['items'] = parentNodes.map((item) => { | const nodes: MenuProps['items'] = parentNodes.map((item) => { | ||||
| const model = item.getModel(); | const model = item.getModel(); | ||||
| const out_parameters = model.out_parameters as string | undefined | null; | |||||
| const out_parametersObj = parseJsonText(out_parameters); | |||||
| const out_parametersObj = model.out_parameters as Record<string, PipelineNodeModelParameter>; | |||||
| const outParametersList = Object.keys(out_parametersObj ?? {}); | const outParametersList = Object.keys(out_parametersObj ?? {}); | ||||
| return { | return { | ||||
| key: model.id as string, | key: model.id as string, | ||||
| @@ -1,6 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | |||||
| import { type PipelineGlobalParam, PipelineGlobalParamType } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { PlusOutlined } from '@ant-design/icons'; | import { PlusOutlined } from '@ant-design/icons'; | ||||
| @@ -42,6 +42,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| form.setFieldValue(name, null); | form.setFieldValue(name, null); | ||||
| }; | }; | ||||
| // 处理删除 | |||||
| const removeParameter = (name: number, remove: (param: number) => void) => { | const removeParameter = (name: number, remove: (param: number) => void) => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '删除后,该全局参数将不可恢复', | title: '删除后,该全局参数将不可恢复', | ||||
| @@ -52,6 +53,16 @@ const GlobalParamsDrawer = forwardRef( | |||||
| }); | }); | ||||
| }; | }; | ||||
| // 处理关闭 | |||||
| const handleClose = async () => { | |||||
| try { | |||||
| await form.validateFields(); | |||||
| onClose(); | |||||
| } catch { | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| rootStyle={{ marginTop: '55px' }} | rootStyle={{ marginTop: '55px' }} | ||||
| @@ -59,7 +70,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| placement="right" | placement="right" | ||||
| closeIcon={false} | closeIcon={false} | ||||
| getContainer={false} | getContainer={false} | ||||
| onClose={onClose} | |||||
| onClose={handleClose} | |||||
| open={open} | open={open} | ||||
| width={520} | width={520} | ||||
| > | > | ||||
| @@ -81,7 +92,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| {...restField} | {...restField} | ||||
| name={[name, 'param_name']} | name={[name, 'param_name']} | ||||
| label="参数名称" | label="参数名称" | ||||
| validateTrigger={[]} | |||||
| dependencies={fields.map((_, i) => ['global_param', i, 'param_name'])} | |||||
| rules={[ | rules={[ | ||||
| { required: true, message: '请输入参数名称' }, | { required: true, message: '请输入参数名称' }, | ||||
| { | { | ||||
| @@ -97,11 +108,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input | |||||
| placeholder="请输入参数名称" | |||||
| allowClear | |||||
| onBlur={() => form.validateFields()} | |||||
| /> | |||||
| <Input placeholder="请输入参数名称" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| {...restField} | {...restField} | ||||
| @@ -124,9 +131,9 @@ const GlobalParamsDrawer = forwardRef( | |||||
| <Radio.Group | <Radio.Group | ||||
| onChange={() => handleTypeChange(['global_param', name, 'param_value'])} | onChange={() => handleTypeChange(['global_param', name, 'param_value'])} | ||||
| > | > | ||||
| <Radio value={1}>字符串</Radio> | |||||
| <Radio value={2}>整型</Radio> | |||||
| <Radio value={3}>布尔类型</Radio> | |||||
| <Radio value={PipelineGlobalParamType.String}>字符串</Radio> | |||||
| <Radio value={PipelineGlobalParamType.Number}>整型</Radio> | |||||
| <Radio value={PipelineGlobalParamType.Boolean}>布尔类型</Radio> | |||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -21,10 +21,7 @@ | |||||
| background: #f8fbff; | background: #f8fbff; | ||||
| } | } | ||||
| &__ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__component { | |||||
| &__select-button { | &__select-button { | ||||
| display: flex; | display: flex; | ||||
| flex: none; | flex: none; | ||||
| @@ -34,5 +31,29 @@ | |||||
| padding-right: 0; | padding-right: 0; | ||||
| padding-left: 0; | padding-left: 0; | ||||
| } | } | ||||
| &__list-row { | |||||
| :global { | |||||
| .ant-row { | |||||
| padding: 0 !important; | |||||
| } | |||||
| } | |||||
| &:last-child { | |||||
| :global { | |||||
| .ant-form-item { | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__add-button { | |||||
| border-color: .addAlpha(@primary-color, 0.5) []; | |||||
| box-shadow: none !important; | |||||
| &:hover { | |||||
| border-style: solid; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | |||||
| import CodeSelectorModal, { CodeConfigData } 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, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | ||||
| @@ -7,7 +7,7 @@ import ResourceSelectorModal, { | |||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| } from '@/components/ResourceSelectorModal'; | } from '@/components/ResourceSelectorModal'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { CommonTabKeys, ComponentType } from '@/enums'; | |||||
| import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils'; | import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils'; | ||||
| import { | import { | ||||
| PipelineGlobalParam, | PipelineGlobalParam, | ||||
| @@ -17,14 +17,22 @@ import { | |||||
| } from '@/types'; | } from '@/types'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { removeFormListItem } from '@/utils/ui'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||||
| import { INode } from '@antv/g6'; | import { INode } from '@antv/g6'; | ||||
| import { Button, Drawer, Form, Input, MenuProps } from 'antd'; | |||||
| import { Button, Drawer, Flex, Form, Input, MenuProps } from 'antd'; | |||||
| import { RuleObject } from 'antd/es/form'; | import { RuleObject } from 'antd/es/form'; | ||||
| import { NamePath } from 'antd/es/form/interface'; | import { NamePath } from 'antd/es/form/interface'; | ||||
| import { omit } from 'lodash'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | import { forwardRef, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../PropsLabel'; | import PropsLabel from '../PropsLabel'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const { TextArea } = Input; | |||||
| // 表单列表数据 | |||||
| export type FormListVariable = { | |||||
| name: string; // 参数名 | |||||
| value: string; // 参数值 | |||||
| }; | |||||
| type PipelineNodeParameterProps = { | type PipelineNodeParameterProps = { | ||||
| onFormChange: (data: PipelineNodeModelSerialize) => void; | onFormChange: (data: PipelineNodeModelSerialize) => void; | ||||
| @@ -35,12 +43,6 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | ||||
| {} as PipelineNodeModelSerialize, | {} as PipelineNodeModelSerialize, | ||||
| ); | ); | ||||
| const nodeId = Form.useWatch('id', form) as string; | |||||
| const hasTaskInfo = | |||||
| nodeId && | |||||
| !nodeId.startsWith('git-clone') && | |||||
| !nodeId.startsWith('dataset-export') && | |||||
| !nodeId.startsWith('model-export'); | |||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | ||||
| @@ -51,11 +53,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| // 不管是否验证成功,都需要获取表单数据 | // 不管是否验证成功,都需要获取表单数据 | ||||
| const fields = form.getFieldsValue(); | const fields = form.getFieldsValue(); | ||||
| // 保存字段顺序 | |||||
| // const control_strategy = { | |||||
| // ...stagingItem.control_strategy, | |||||
| // ...fields.control_strategy, | |||||
| // }; | |||||
| // 保持原有字段和顺序 | |||||
| const task_info = { | |||||
| ...stagingItem.task_info, | |||||
| ...fields.task_info, | |||||
| }; | |||||
| const control_strategy = { | |||||
| ...stagingItem.control_strategy, | |||||
| ...fields.control_strategy, | |||||
| }; | |||||
| const in_parameters = { | const in_parameters = { | ||||
| ...stagingItem.in_parameters, | ...stagingItem.in_parameters, | ||||
| @@ -66,17 +73,18 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| ...fields.out_parameters, | ...fields.out_parameters, | ||||
| }; | }; | ||||
| // console.log('getFieldsValue', fields); | |||||
| console.log('getFieldsValue', fields); | |||||
| const res = { | const res = { | ||||
| ...stagingItem, | |||||
| ...fields, | |||||
| // control_strategy: JSON.stringify(control_strategy), | |||||
| in_parameters: JSON.stringify(in_parameters), | |||||
| out_parameters: JSON.stringify(out_parameters), | |||||
| ...omit(stagingItem, ['control_strategy', 'task_info', 'in_parameters', 'out_parameters']), | |||||
| ...omit(fields, ['control_strategy', 'task_info', 'in_parameters', 'out_parameters']), | |||||
| task_info: task_info, | |||||
| control_strategy: control_strategy, | |||||
| in_parameters: in_parameters, | |||||
| out_parameters: out_parameters, | |||||
| formError: !!error, | formError: !!error, | ||||
| }; | }; | ||||
| // console.log('res', res); | |||||
| console.log('res', res); | |||||
| onFormChange(res); | onFormChange(res); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -94,19 +102,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| validate: boolean = false, | validate: boolean = false, | ||||
| ) { | ) { | ||||
| try { | try { | ||||
| const nodeData: PipelineNodeModelSerialize = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| // control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| // console.log('model', nodeData); | |||||
| setStagingItem({ | setStagingItem({ | ||||
| ...nodeData, | |||||
| ...model, | |||||
| }); | }); | ||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({ | form.setFieldsValue({ | ||||
| ...nodeData, | |||||
| ...model, | |||||
| }); | }); | ||||
| if (validate) { | if (validate) { | ||||
| form.validateFields(); | form.validateFields(); | ||||
| @@ -155,23 +156,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| formItemName: NamePath, | formItemName: NamePath, | ||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | ||||
| ) => { | ) => { | ||||
| const defaultSelected = form.getFieldValue(formItemName)?.value as CodeConfigData; | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | const { close } = openAntdModal(CodeSelectorModal, { | ||||
| defaultSelected, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { id, code_repo_name, git_url, git_branch, git_user_name, git_password, ssh_key } = | |||||
| res; | |||||
| const value = JSON.stringify({ | |||||
| id, | |||||
| name: code_repo_name, | |||||
| code_path: git_url, | |||||
| branch: git_branch, | |||||
| username: git_user_name, | |||||
| password: git_password, | |||||
| ssh_private_key: ssh_key, | |||||
| }); | |||||
| const { code_repo_name } = res; | |||||
| form.setFieldValue(formItemName, { | form.setFieldValue(formItemName, { | ||||
| ...item, | ...item, | ||||
| value, | |||||
| value: res, | |||||
| showValue: code_repo_name, | showValue: code_repo_name, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| }); | }); | ||||
| @@ -211,36 +204,24 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| const { activeTab, id, version, path } = res; | |||||
| if (formItemName === 'image') { | |||||
| // 单独的选择镜像 | |||||
| form.setFieldValue(formItemName, path); | |||||
| } else { | |||||
| // 输入参数选择镜像 | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: path, | |||||
| showValue: path, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | |||||
| } else { | |||||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| const value = JSON.stringify({ | |||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| identifier, | |||||
| owner, | |||||
| const { activeTab, ...rest } = res; | |||||
| const { url, id, image_id } = rest; | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: rest, | |||||
| showValue: url, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [`${image_id}`], | |||||
| checkedKeys: [`${image_id}-${id}`], | |||||
| }); | }); | ||||
| } else { | |||||
| const { activeTab, ...rest } = res; | |||||
| const { id, name, version } = rest; | |||||
| const showValue = `${name}:${version}`; | const showValue = `${name}:${version}`; | ||||
| form.setFieldValue(formItemName, { | form.setFieldValue(formItemName, { | ||||
| ...item, | ...item, | ||||
| value, | |||||
| value: rest, | |||||
| showValue, | showValue, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| @@ -249,19 +230,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| }); | }); | ||||
| } | } | ||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Mirror && formItemName === 'image') { | |||||
| form.setFieldValue(formItemName, undefined); | |||||
| } else { | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: undefined, | |||||
| showValue: undefined, | |||||
| fromSelect: false, | |||||
| activeTab: undefined, | |||||
| expandedKeys: [], | |||||
| checkedKeys: [], | |||||
| }); | |||||
| } | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: undefined, | |||||
| showValue: undefined, | |||||
| fromSelect: false, | |||||
| activeTab: undefined, | |||||
| expandedKeys: [], | |||||
| checkedKeys: [], | |||||
| }); | |||||
| } | } | ||||
| form.validateFields([formItemName]); | form.validateFields([formItemName]); | ||||
| close(); | close(); | ||||
| @@ -297,16 +274,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| // form item label | // form item label | ||||
| const getLabel = ( | const getLabel = ( | ||||
| item: { key: string; value: PipelineNodeModelParameter }, | item: { key: string; value: PipelineNodeModelParameter }, | ||||
| namePrefix: string, | |||||
| parentName: string, | |||||
| ) => { | ) => { | ||||
| return item.value.type === 'select' ? ( | |||||
| return item.value.type === ComponentType.Select || item.value.type === ComponentType.Map ? ( | |||||
| item.value.label + '(' + item.key + ')' | item.value.label + '(' + item.key + ')' | ||||
| ) : ( | ) : ( | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={menuItems} | menuItems={menuItems} | ||||
| title={item.value.label + '(' + item.key + ')'} | title={item.value.label + '(' + item.key + ')'} | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| handleParameterClick([namePrefix, item.key], { | |||||
| handleParameterClick([parentName, item.key], { | |||||
| ...item.value, | ...item.value, | ||||
| value, | value, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| @@ -354,21 +331,228 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| return rules; | return rules; | ||||
| }; | }; | ||||
| // 表单组件 | |||||
| const getFormComponent = ( | |||||
| item: { key: string; value: PipelineNodeModelParameter }, | |||||
| parentName: string, | |||||
| ) => { | |||||
| return ( | |||||
| <> | |||||
| {item.value.type === ComponentType.Ref && ( | |||||
| <Flex align="center"> | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <ParameterInput | |||||
| canInput={canInput(item.value)} | |||||
| placeholder={item.value.placeholder} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectRefData([parentName, item.key], item.value)} | |||||
| className={styles['pipeline-drawer__component__select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| )} | |||||
| {item.value.type === ComponentType.Select && | |||||
| (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <ParameterSelect | |||||
| isPipeline | |||||
| dataType={item.value.item_type as ParameterSelectDataType} | |||||
| placeholder={item.value.placeholder} | |||||
| /> | |||||
| </Form.Item> | |||||
| ) : null)} | |||||
| {item.value.type === ComponentType.Map && ( | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <Form.List name={[parentName, item.key, 'value']}> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex | |||||
| key={key} | |||||
| gap="0 8px" | |||||
| style={{ width: '100%' }} | |||||
| className={styles['pipeline-drawer__component__list-row']} | |||||
| > | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'name']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| dependencies={fields.map((_, i) => [ | |||||
| parentName, | |||||
| item.key, | |||||
| 'value', | |||||
| i, | |||||
| 'name', | |||||
| ])} | |||||
| rules={[ | |||||
| { | |||||
| validator: (_, value) => { | |||||
| if (!value) { | |||||
| return Promise.reject(new Error('请输入变量名')); | |||||
| } | |||||
| if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value)) { | |||||
| return Promise.reject( | |||||
| new Error( | |||||
| '变量名只支持字母、数字、下划线、中横线并且必须以字母或下划线开头', | |||||
| ), | |||||
| ); | |||||
| } | |||||
| // 判断不能重名 | |||||
| const list = form | |||||
| .getFieldValue([parentName, item.key, 'value']) | |||||
| .filter( | |||||
| (item: FormListVariable | undefined) => | |||||
| item !== undefined && item !== null, | |||||
| ); | |||||
| const names = list.map((item: FormListVariable) => item.name); | |||||
| if (new Set(names).size !== names.length) { | |||||
| return Promise.reject(new Error('变量名不能重复')); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入变量名" allowClear /> | |||||
| </Form.Item> | |||||
| <span style={{ lineHeight: '32px' }}>=</span> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| rules={[ | |||||
| { | |||||
| validator: requiredValidator, | |||||
| message: '请输入变量值', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| {/* <Input placeholder="请输入变量值" allowClear /> */} | |||||
| <ParameterInput | |||||
| placeholder="请输入变量值" | |||||
| allowClear | |||||
| addonAfter={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="" | |||||
| onClick={(value) => { | |||||
| handleParameterClick( | |||||
| [parentName, item.key, 'value', name, 'value'], | |||||
| { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }, | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| ></ParameterInput> | |||||
| </Form.Item> | |||||
| <Flex | |||||
| style={{ | |||||
| width: '76px', | |||||
| height: '32px', | |||||
| }} | |||||
| align="center" | |||||
| > | |||||
| <Button | |||||
| style={{ | |||||
| marginRight: '3px', | |||||
| }} | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| icon={<MinusCircleOutlined />} | |||||
| onClick={() => { | |||||
| removeFormListItem( | |||||
| form, | |||||
| 'env_variables', | |||||
| name, | |||||
| remove, | |||||
| ['key', 'value'], | |||||
| '删除后,该变量将不可恢复', | |||||
| ); | |||||
| }} | |||||
| ></Button> | |||||
| {index === fields.length - 1 && ( | |||||
| <Button | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| icon={<PlusCircleOutlined />} | |||||
| onClick={() => add()} | |||||
| ></Button> | |||||
| )} | |||||
| </Flex> | |||||
| </Flex> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <Button | |||||
| className={styles['pipeline-drawer__component__add-button']} | |||||
| color="primary" | |||||
| variant="dashed" | |||||
| icon={<PlusOutlined />} | |||||
| block | |||||
| onClick={() => add()} | |||||
| > | |||||
| 新增 | |||||
| </Button> | |||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </Form.Item> | |||||
| )} | |||||
| {item.value.type === ComponentType.Str && ( | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <ParameterInput | |||||
| canInput={canInput(item.value)} | |||||
| placeholder={item.value.placeholder} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| // 基本参数 | |||||
| const basicParametersList = Object.entries(stagingItem.task_info ?? {}) | |||||
| .map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 控制策略 | // 控制策略 | ||||
| // const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | |||||
| // ([key, value]) => ({ key, value }), | |||||
| // ); | |||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}) | |||||
| .map(([key, value]) => ({ key, value })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 输入参数 | // 输入参数 | ||||
| const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| const inParametersList = Object.entries(stagingItem.in_parameters ?? {}) | |||||
| .map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 输出参数 | // 输出参数 | ||||
| const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| const outParametersList = Object.entries(stagingItem.out_parameters ?? {}) | |||||
| .map(([key, value]) => ({ key, value })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| @@ -380,7 +564,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| onClose={onClose} | onClose={onClose} | ||||
| afterOpenChange={afterOpenChange} | afterOpenChange={afterOpenChange} | ||||
| open={open} | open={open} | ||||
| width={520} | |||||
| width={620} | |||||
| className={styles['pipeline-drawer']} | className={styles['pipeline-drawer']} | ||||
| > | > | ||||
| <Form | <Form | ||||
| @@ -429,114 +613,37 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| > | > | ||||
| <Input disabled /> | <Input disabled /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {hasTaskInfo && ( | |||||
| <> | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="任务信息" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| <Form.Item label="镜像" required> | |||||
| <div className={styles['pipeline-drawer__ref-row']}> | |||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | |||||
| <Input placeholder="请输入或选择镜像" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| icon={getSelectBtnIcon({ item_type: 'image' })} | |||||
| onClick={() => selectResource('image', { item_type: 'image' })} | |||||
| className={styles['pipeline-drawer__ref-row__select-button']} | |||||
| > | |||||
| 选择镜像 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </div> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="working_directory" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="工作目录" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('working_directory', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入工作目录" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="command" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="启动命令" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('command', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入启动命令" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterSelect dataType="resource" placeholder="请选择资源规格" /> | |||||
| </Form.Item> | |||||
| {/* <Form.Item | |||||
| name="mount_path" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="挂载路径" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('mount_path', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入挂载路径" allowClear /> | |||||
| </Form.Item> */} | |||||
| <Form.Item | |||||
| name="env_variables" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="环境变量" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('env_variables', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入环境变量" allowClear /> | |||||
| </Form.Item> | |||||
| {/* 控制参数 */} | |||||
| {/* {controlStrategyList.map((item) => ( | |||||
| {basicParametersList.length + controlStrategyList.length > 0 && ( | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="任务信息" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| )} | |||||
| {/* 基本参数 */} | |||||
| {basicParametersList.map((item) => ( | |||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| name={['control_strategy', item.key]} | |||||
| label={getLabel(item, 'task_info')} | |||||
| required={item.value.require ? true : false} | required={item.value.require ? true : false} | ||||
| > | |||||
| {getFormComponent(item, 'task_info')} | |||||
| </Form.Item> | |||||
| ))} | |||||
| {/* 控制参数 */} | |||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| label={getLabel(item, 'control_strategy')} | label={getLabel(item, 'control_strategy')} | ||||
| rules={getFormRules(item)} | |||||
| required={item.value.require ? true : false} | |||||
| > | > | ||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| {getFormComponent(item, 'control_strategy')} | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} */} | |||||
| </> | |||||
| )} | |||||
| ))} | |||||
| {/* 输入参数 */} | {/* 输入参数 */} | ||||
| {inParametersList.length > 0 && ( | {inParametersList.length > 0 && ( | ||||
| @@ -553,42 +660,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| label={getLabel(item, 'in_parameters')} | label={getLabel(item, 'in_parameters')} | ||||
| required={item.value.require ? true : false} | required={item.value.require ? true : false} | ||||
| > | > | ||||
| <div className={styles['pipeline-drawer__ref-row']}> | |||||
| <Form.Item name={['in_parameters', item.key]} rules={getFormRules(item)} noStyle> | |||||
| {item.value.type === 'select' ? ( | |||||
| ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||||
| <ParameterSelect | |||||
| isPipeline | |||||
| dataType={item.value.item_type as ParameterSelectDataType} | |||||
| placeholder={item.value.placeholder} | |||||
| /> | |||||
| ) : null | |||||
| ) : ( | |||||
| <ParameterInput | |||||
| canInput={canInput(item.value)} | |||||
| placeholder={item.value.placeholder} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| )} | |||||
| </Form.Item> | |||||
| {item.value.type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectRefData(['in_parameters', item.key], item.value)} | |||||
| className={styles['pipeline-drawer__ref-row__select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| {getFormComponent(item, 'in_parameters')} | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| </> | </> | ||||
| )} | )} | ||||
| {/* 输出参数 */} | {/* 输出参数 */} | ||||
| {outParametersList.length > 0 && ( | {outParametersList.length > 0 && ( | ||||
| <> | <> | ||||
| @@ -24,7 +24,7 @@ const { TextArea } = Input; | |||||
| const Pipeline = () => { | const Pipeline = () => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [formId, setFormId] = useState(null); | |||||
| const [editRecord, setEditRecord] = useState(null); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建流水线'); | const [dialogTitle, setDialogTitle] = useState('新建流水线'); | ||||
| const [pipeList, setPipeList] = useState([]); | const [pipeList, setPipeList] = useState([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| @@ -75,7 +75,7 @@ const Pipeline = () => { | |||||
| if (ret.code === 200) { | if (ret.code === 200) { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({ ...ret.data }); | form.setFieldsValue({ ...ret.data }); | ||||
| setFormId(ret.data.id); | |||||
| setEditRecord(ret.data); | |||||
| setDialogTitle('编辑流水线'); | setDialogTitle('编辑流水线'); | ||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| } | } | ||||
| @@ -99,7 +99,7 @@ const Pipeline = () => { | |||||
| // 显示 modal | // 显示 modal | ||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| setFormId(null); | |||||
| setEditRecord(null); | |||||
| setDialogTitle('新建流水线'); | setDialogTitle('新建流水线'); | ||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| @@ -111,8 +111,8 @@ const Pipeline = () => { | |||||
| // 表单提交 | // 表单提交 | ||||
| const onFinish = (values) => { | const onFinish = (values) => { | ||||
| if (formId) { | |||||
| editWorkflow({ ...values, id: formId }).then((ret) => { | |||||
| if (editRecord) { | |||||
| editWorkflow({ ...editRecord, ...values }).then((ret) => { | |||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| message.success('编辑成功'); | message.success('编辑成功'); | ||||
| getList(); | getList(); | ||||
| @@ -1,60 +0,0 @@ | |||||
| .avatarHolder { | |||||
| position: relative; | |||||
| display: inline-block; | |||||
| height: 120px; | |||||
| margin-bottom: 16px; | |||||
| text-align: center; | |||||
| & > img { | |||||
| width: 120px; | |||||
| height: 120px; | |||||
| margin-bottom: 20px; | |||||
| border-radius: 50%; | |||||
| } | |||||
| &:hover:after { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| right: 0; | |||||
| bottom: 0; | |||||
| left: 0; | |||||
| color: #eee; | |||||
| font-size: 24px; | |||||
| font-style: normal; | |||||
| line-height: 110px; | |||||
| background: rgba(0, 0, 0, 0.5); | |||||
| border-radius: 50%; | |||||
| cursor: pointer; | |||||
| content: '+'; | |||||
| -webkit-font-smoothing: antialiased; | |||||
| -moz-osx-font-smoothing: grayscale; | |||||
| } | |||||
| } | |||||
| .teamTitle { | |||||
| margin-bottom: 12px; | |||||
| color: @heading-color; | |||||
| font-weight: 500; | |||||
| } | |||||
| .team { | |||||
| :global { | |||||
| .ant-avatar { | |||||
| margin-right: 12px; | |||||
| } | |||||
| } | |||||
| a { | |||||
| display: block; | |||||
| margin-bottom: 24px; | |||||
| overflow: hidden; | |||||
| color: @text-color; | |||||
| white-space: nowrap; | |||||
| text-overflow: ellipsis; | |||||
| word-break: break-all; | |||||
| transition: color 0.3s; | |||||
| &:hover { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,91 +1,107 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { updateUserProfile } from '@/services/system/user'; | import { updateUserProfile } from '@/services/system/user'; | ||||
| import { ProForm, ProFormRadio, ProFormText } from '@ant-design/pro-components'; | |||||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||||
| import { Form, message, Row } from 'antd'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, message, Radio } from 'antd'; | |||||
| import React from 'react'; | import React from 'react'; | ||||
| export type BaseInfoProps = { | export type BaseInfoProps = { | ||||
| values: Partial<API.CurrentUser> | undefined; | |||||
| values: Partial<API.CurrentUser>; | |||||
| open: boolean; | |||||
| onFinished?: (isSuccess: boolean) => void; | |||||
| }; | }; | ||||
| const BaseInfo: React.FC<BaseInfoProps> = (props) => { | |||||
| const BaseInfo: React.FC<BaseInfoProps> = ({ open, onFinished, values: initialValues }) => { | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const intl = useIntl(); | |||||
| const handleFinish = async (values: Record<string, any>) => { | |||||
| const data = { ...props.values, ...values } as API.CurrentUser; | |||||
| const resp = await updateUserProfile(data); | |||||
| if (resp.code === 200) { | |||||
| const handleFinish = async (formData: Record<string, any>) => { | |||||
| const data = { userId: initialValues.userId, ...formData } as API.CurrentUser; | |||||
| const [res] = await to(updateUserProfile(data)); | |||||
| if (res) { | |||||
| message.success('修改成功'); | message.success('修改成功'); | ||||
| } else { | |||||
| message.warning(resp.msg); | |||||
| onFinished?.(true); | |||||
| } | } | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <> | |||||
| <ProForm form={form} onFinish={handleFinish} initialValues={props.values}> | |||||
| <Row> | |||||
| <ProFormText | |||||
| name="nickName" | |||||
| label={intl.formatMessage({ | |||||
| id: 'system.user.nick_name', | |||||
| defaultMessage: '用户昵称', | |||||
| })} | |||||
| width="xl" | |||||
| placeholder="请输入用户昵称" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| </Row> | |||||
| <Row> | |||||
| <ProFormText | |||||
| name="phonenumber" | |||||
| label={intl.formatMessage({ | |||||
| id: 'system.user.phonenumber', | |||||
| defaultMessage: '手机号码', | |||||
| })} | |||||
| width="xl" | |||||
| placeholder="请输入手机号码" | |||||
| rules={[ | |||||
| { | |||||
| required: false, | |||||
| message: ( | |||||
| <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| </Row> | |||||
| <Row> | |||||
| <ProFormText | |||||
| name="email" | |||||
| label={intl.formatMessage({ | |||||
| id: 'system.user.email', | |||||
| defaultMessage: '邮箱', | |||||
| })} | |||||
| width="xl" | |||||
| placeholder="请输入邮箱" | |||||
| rules={[ | |||||
| { | |||||
| type: 'email', | |||||
| message: '无效的邮箱地址!', | |||||
| }, | |||||
| { | |||||
| required: false, | |||||
| message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />, | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| </Row> | |||||
| <Row> | |||||
| <ProFormRadio.Group | |||||
| <KFModal | |||||
| width={800} | |||||
| title="修改基本信息" | |||||
| open={open} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'basic-info-form', | |||||
| }} | |||||
| onCancel={() => onFinished?.(false)} | |||||
| destroyOnClose | |||||
| > | |||||
| <Form | |||||
| name="basic-info-form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| size="large" | |||||
| autoComplete="off" | |||||
| scrollToFirstError | |||||
| initialValues={initialValues} | |||||
| onFinish={handleFinish} | |||||
| > | |||||
| <Form.Item | |||||
| name="nickName" | |||||
| label="用户昵称" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入用户昵称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入用户昵称" allowClear></Input> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="phonenumber" | |||||
| label="手机号码" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入手机号码', | |||||
| }, | |||||
| { | |||||
| pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, | |||||
| message: '请输入正确的手机号码', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入手机号码" allowClear></Input> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="email" | |||||
| label="邮箱" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入邮箱', | |||||
| }, | |||||
| { | |||||
| type: 'email', | |||||
| message: '请输入正确的邮箱地址', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入邮箱" allowClear></Input> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="sex" | |||||
| label="性别" | |||||
| rules={[ | |||||
| { | |||||
| required: false, | |||||
| message: '请选择性别', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Radio.Group | |||||
| options={[ | options={[ | ||||
| { | { | ||||
| label: '男', | label: '男', | ||||
| @@ -96,22 +112,10 @@ const BaseInfo: React.FC<BaseInfoProps> = (props) => { | |||||
| value: '1', | value: '1', | ||||
| }, | }, | ||||
| ]} | ]} | ||||
| name="sex" | |||||
| label={intl.formatMessage({ | |||||
| id: 'system.user.sex', | |||||
| defaultMessage: 'sex', | |||||
| })} | |||||
| width="xl" | |||||
| rules={[ | |||||
| { | |||||
| required: false, | |||||
| message: <FormattedMessage id="请输入性别!" defaultMessage="请输入性别!" />, | |||||
| }, | |||||
| ]} | |||||
| /> | /> | ||||
| </Row> | |||||
| </ProForm> | |||||
| </> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,81 +1,90 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { updateUserPwd } from '@/services/system/user'; | import { updateUserPwd } from '@/services/system/user'; | ||||
| import { ProForm, ProFormText } from '@ant-design/pro-components'; | |||||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||||
| import { Form, message } from 'antd'; | |||||
| import React from 'react'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, message } from 'antd'; | |||||
| const ResetPassword: React.FC = () => { | |||||
| export type ResetPasswordProps = { | |||||
| open: boolean; | |||||
| onFinished?: (isSuccess: boolean) => void; | |||||
| }; | |||||
| const ResetPassword = ({ open, onFinished }: ResetPasswordProps) => { | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const intl = useIntl(); | |||||
| const handleFinish = async (values: Record<string, any>) => { | const handleFinish = async (values: Record<string, any>) => { | ||||
| const resp = await updateUserPwd(values.oldPassword, values.newPassword); | |||||
| if (resp.code === 200) { | |||||
| message.success('密码重置成功。'); | |||||
| } else { | |||||
| message.warning(resp.msg); | |||||
| const [res] = await to(updateUserPwd(values.oldPassword, values.newPassword)); | |||||
| if (res) { | |||||
| message.success('密码重置成功'); | |||||
| onFinished?.(true); | |||||
| } | } | ||||
| }; | }; | ||||
| const checkPassword = (_rule: any, value: string) => { | const checkPassword = (_rule: any, value: string) => { | ||||
| const login_password = form.getFieldValue('newPassword'); | const login_password = form.getFieldValue('newPassword'); | ||||
| if (value === login_password) { | |||||
| return Promise.resolve(); | |||||
| if (!value) { | |||||
| return Promise.reject(new Error('请输入确认密码')); | |||||
| } else if (value !== login_password) { | |||||
| return Promise.reject(new Error('两次密码输入不一致')); | |||||
| } | } | ||||
| return Promise.reject(new Error('两次密码输入不一致')); | |||||
| return Promise.resolve(); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <> | |||||
| <ProForm form={form} onFinish={handleFinish}> | |||||
| <ProFormText.Password | |||||
| <KFModal | |||||
| width={800} | |||||
| title="重置密码" | |||||
| open={open} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'reset-pwd-form', | |||||
| }} | |||||
| onCancel={() => onFinished?.(false)} | |||||
| destroyOnClose | |||||
| > | |||||
| <Form | |||||
| form={form} | |||||
| name="reset-pwd-form" | |||||
| layout="vertical" | |||||
| size="large" | |||||
| autoComplete="off" | |||||
| scrollToFirstError | |||||
| onFinish={handleFinish} | |||||
| > | |||||
| <Form.Item | |||||
| name="oldPassword" | name="oldPassword" | ||||
| label={intl.formatMessage({ | |||||
| id: 'system.user.old_password', | |||||
| defaultMessage: '旧密码', | |||||
| })} | |||||
| width="xl" | |||||
| placeholder="请输入旧密码" | |||||
| label="旧密码" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: <FormattedMessage id="请输入旧密码!" defaultMessage="请输入旧密码!" />, | |||||
| message: '请输入旧密码', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| /> | |||||
| <ProFormText.Password | |||||
| > | |||||
| <Input.Password placeholder="请输入旧密码" allowClear></Input.Password> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="newPassword" | name="newPassword" | ||||
| label={intl.formatMessage({ | |||||
| id: 'system.user.new_password', | |||||
| defaultMessage: '新密码', | |||||
| })} | |||||
| width="xl" | |||||
| placeholder="请输入新密码" | |||||
| label="新密码" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: <FormattedMessage id="请输入新密码!" defaultMessage="请输入新密码!" />, | |||||
| message: '请输入新密码', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| /> | |||||
| <ProFormText.Password | |||||
| > | |||||
| <Input.Password placeholder="请输入新密码" allowClear></Input.Password> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="confirmPassword" | name="confirmPassword" | ||||
| label={intl.formatMessage({ | |||||
| id: 'system.user.confirm_password', | |||||
| defaultMessage: '确认密码', | |||||
| })} | |||||
| width="xl" | |||||
| placeholder="请输入确认密码" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: <FormattedMessage id="请输入确认密码!" defaultMessage="请输入确认密码!" />, | |||||
| }, | |||||
| { validator: checkPassword }, | |||||
| ]} | |||||
| /> | |||||
| </ProForm> | |||||
| </> | |||||
| label="确认密码" | |||||
| dependencies={['newPassword']} | |||||
| required | |||||
| rules={[{ validator: checkPassword }]} | |||||
| > | |||||
| <Input.Password placeholder="请输入确认密码" allowClear></Input.Password> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -0,0 +1,64 @@ | |||||
| @avaterSize: 180px; | |||||
| .avatarHolder { | |||||
| position: relative; | |||||
| display: inline-block; | |||||
| height: @avaterSize; | |||||
| margin-bottom: 30px; | |||||
| text-align: center; | |||||
| & > img { | |||||
| width: @avaterSize; | |||||
| height: @avaterSize; | |||||
| border-radius: 50%; | |||||
| } | |||||
| // &:hover:after { | |||||
| // position: absolute; | |||||
| // top: 0; | |||||
| // right: 0; | |||||
| // bottom: 0; | |||||
| // left: 0; | |||||
| // color: #eee; | |||||
| // font-size: 24px; | |||||
| // font-style: normal; | |||||
| // line-height: @avaterSize; | |||||
| // background: rgba(0, 0, 0, 0.5); | |||||
| // border-radius: 50%; | |||||
| // cursor: pointer; | |||||
| // content: '+'; | |||||
| // -webkit-font-smoothing: antialiased; | |||||
| // -moz-osx-font-smoothing: grayscale; | |||||
| // } | |||||
| } | |||||
| .user-center { | |||||
| height: calc(100% - 50px - 120px); | |||||
| padding: 30px; | |||||
| background: white; | |||||
| border-radius: 8px; | |||||
| width: 50%; | |||||
| margin: 60px auto 0; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| overflow-y: auto; | |||||
| :global { | |||||
| .ant-list { | |||||
| width: 100%; | |||||
| .ant-list-item { | |||||
| height: 80px; | |||||
| border-block-end: 1px solid rgba(5, 5, 5, 0.06); | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__buttons { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-top: 60px; | |||||
| flex-direction: row; | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,9 @@ | |||||
| import { getUserInfo } from '@/services/session'; | |||||
| import DefaultAvatar from '@/assets/img/avatar-default.png'; | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { | import { | ||||
| ClusterOutlined, | ClusterOutlined, | ||||
| HeartOutlined, | |||||
| MailOutlined, | MailOutlined, | ||||
| ManOutlined, | ManOutlined, | ||||
| MobileOutlined, | MobileOutlined, | ||||
| @@ -8,45 +11,52 @@ import { | |||||
| UserOutlined, | UserOutlined, | ||||
| } from '@ant-design/icons'; | } from '@ant-design/icons'; | ||||
| import { PageLoading } from '@ant-design/pro-components'; | import { PageLoading } from '@ant-design/pro-components'; | ||||
| import { useRequest } from '@umijs/max'; | |||||
| import { Card, Col, Divider, List, Row } from 'antd'; | |||||
| import React, { useState } from 'react'; | |||||
| import styles from './Center.less'; | |||||
| import { useModel } from '@umijs/max'; | |||||
| import { Button, List } from 'antd'; | |||||
| import { useCallback, useState } from 'react'; | |||||
| import { flushSync } from 'react-dom'; | |||||
| import AvatarCropper from './components/AvatarCropper'; | import AvatarCropper from './components/AvatarCropper'; | ||||
| import BaseInfo from './components/BaseInfo'; | |||||
| import ResetPassword from './components/ResetPassword'; | |||||
| import BaseInfoModal from './components/BaseInfo'; | |||||
| import ResetPasswordModal from './components/ResetPassword'; | |||||
| import styles from './index.less'; | |||||
| const operationTabList = [ | |||||
| { | |||||
| key: 'base', | |||||
| tab: <span>基本资料</span>, | |||||
| }, | |||||
| { | |||||
| key: 'password', | |||||
| tab: <span>重置密码</span>, | |||||
| }, | |||||
| ]; | |||||
| const Center = () => { | |||||
| const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false); | |||||
| const [infoModalOpen, setInfoModalOpen] = useState<boolean>(false); | |||||
| const [resetModalOpen, setRestModalOpen] = useState<boolean>(false); | |||||
| export type tabKeyType = 'base' | 'password'; | |||||
| const { initialState, setInitialState } = useModel('@@initialState'); | |||||
| const { currentUser, fetchUserInfo } = initialState || {}; | |||||
| const Center: React.FC = () => { | |||||
| const [tabKey, setTabKey] = useState<tabKeyType>('base'); | |||||
| const refreshUserInfo = useCallback(async () => { | |||||
| if (fetchUserInfo) { | |||||
| const [res] = await to(fetchUserInfo()); | |||||
| if (res) { | |||||
| flushSync(() => { | |||||
| setInitialState((s) => ({ ...s, currentUser: res })); | |||||
| }); | |||||
| } | |||||
| } | |||||
| }, [setInitialState, fetchUserInfo]); | |||||
| const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false); | |||||
| const handleBaseInfoChange = (success: boolean) => { | |||||
| setInfoModalOpen(false); | |||||
| // 获取用户信息 | |||||
| const { data: userInfo, loading } = useRequest(async () => { | |||||
| return { data: await getUserInfo() }; | |||||
| }); | |||||
| if (loading) { | |||||
| return <div>loading...</div>; | |||||
| } | |||||
| if (success) { | |||||
| refreshUserInfo(); | |||||
| } | |||||
| }; | |||||
| const currentUser = userInfo?.user; | |||||
| const handleResetPassword = (success: boolean) => { | |||||
| setRestModalOpen(false); | |||||
| if (success) { | |||||
| } | |||||
| }; | |||||
| // 渲染用户信息 | // 渲染用户信息 | ||||
| const renderUserInfo = ({ | const renderUserInfo = ({ | ||||
| userName, | userName, | ||||
| nickName, | |||||
| phonenumber, | phonenumber, | ||||
| email, | email, | ||||
| sex, | sex, | ||||
| @@ -65,6 +75,17 @@ const Center: React.FC = () => { | |||||
| </div> | </div> | ||||
| <div>{userName}</div> | <div>{userName}</div> | ||||
| </List.Item> | </List.Item> | ||||
| <List.Item> | |||||
| <div> | |||||
| <HeartOutlined | |||||
| style={{ | |||||
| marginRight: 8, | |||||
| }} | |||||
| /> | |||||
| 昵称 | |||||
| </div> | |||||
| <div>{nickName}</div> | |||||
| </List.Item> | |||||
| <List.Item> | <List.Item> | ||||
| <div> | <div> | ||||
| <ManOutlined | <ManOutlined | ||||
| @@ -109,75 +130,53 @@ const Center: React.FC = () => { | |||||
| </div> | </div> | ||||
| <div>{dept?.deptName}</div> | <div>{dept?.deptName}</div> | ||||
| </List.Item> | </List.Item> | ||||
| <List.Item> | |||||
| <div> | |||||
| <TeamOutlined | |||||
| style={{ | |||||
| marginRight: 8, | |||||
| }} | |||||
| /> | |||||
| 角色 | |||||
| </div> | |||||
| <div>{currentUser?.roles?.map((item: any) => item.roleName)?.join(',')}</div> | |||||
| </List.Item> | |||||
| </List> | </List> | ||||
| ); | ); | ||||
| }; | }; | ||||
| // 渲染tab切换 | |||||
| const renderChildrenByTabKey = (tabValue: tabKeyType) => { | |||||
| if (tabValue === 'base') { | |||||
| return <BaseInfo values={currentUser} />; | |||||
| } | |||||
| if (tabValue === 'password') { | |||||
| return <ResetPassword />; | |||||
| } | |||||
| return null; | |||||
| }; | |||||
| if (!currentUser) { | if (!currentUser) { | ||||
| return <PageLoading />; | return <PageLoading />; | ||||
| } | } | ||||
| return ( | return ( | ||||
| <div> | |||||
| <Row gutter={[16, 24]}> | |||||
| <Col lg={8} md={24}> | |||||
| <Card title="个人信息" bordered={false} loading={loading}> | |||||
| {!loading && ( | |||||
| <div style={{ textAlign: 'center' }}> | |||||
| <div | |||||
| className={styles.avatarHolder} | |||||
| onClick={() => { | |||||
| setCropperModalOpen(true); | |||||
| }} | |||||
| > | |||||
| <img src={currentUser.avatar} draggable={false} alt="" /> | |||||
| </div> | |||||
| {renderUserInfo(currentUser)} | |||||
| <Divider dashed /> | |||||
| <div className={styles.team}> | |||||
| <div className={styles.teamTitle}>角色</div> | |||||
| <Row gutter={36}> | |||||
| {currentUser.roles && | |||||
| currentUser.roles.map((item: any) => ( | |||||
| <Col key={item.roleId} lg={24} xl={12}> | |||||
| <TeamOutlined | |||||
| style={{ | |||||
| marginRight: 8, | |||||
| }} | |||||
| /> | |||||
| {item.roleName} | |||||
| </Col> | |||||
| ))} | |||||
| </Row> | |||||
| </div> | |||||
| </div> | |||||
| )} | |||||
| </Card> | |||||
| </Col> | |||||
| <Col lg={16} md={24}> | |||||
| <Card | |||||
| bordered={false} | |||||
| tabList={operationTabList} | |||||
| activeTabKey={tabKey} | |||||
| onTabChange={(_tabKey: string) => { | |||||
| setTabKey(_tabKey as tabKeyType); | |||||
| }} | |||||
| <div style={{ height: '100%' }}> | |||||
| <PageTitle title="个人中心"></PageTitle> | |||||
| <div className={styles['user-center']}> | |||||
| <div | |||||
| className={styles.avatarHolder} | |||||
| // onClick={() => { | |||||
| // setCropperModalOpen(true); | |||||
| // }} | |||||
| > | |||||
| <img src={currentUser.avatar || DefaultAvatar} draggable={false} alt="" /> | |||||
| </div> | |||||
| {renderUserInfo(currentUser)} | |||||
| <div className={styles['user-center__buttons']}> | |||||
| <Button | |||||
| type="primary" | |||||
| size="large" | |||||
| style={{ marginRight: 50 }} | |||||
| onClick={() => setInfoModalOpen(true)} | |||||
| > | > | ||||
| {renderChildrenByTabKey(tabKey)} | |||||
| </Card> | |||||
| </Col> | |||||
| </Row> | |||||
| 修改基本信息 | |||||
| </Button> | |||||
| <Button type="primary" size="large" onClick={() => setRestModalOpen(true)}> | |||||
| 重置密码 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <AvatarCropper | <AvatarCropper | ||||
| onFinished={() => { | onFinished={() => { | ||||
| setCropperModalOpen(false); | setCropperModalOpen(false); | ||||
| @@ -185,6 +184,17 @@ const Center: React.FC = () => { | |||||
| open={cropperModalOpen} | open={cropperModalOpen} | ||||
| data={currentUser.avatar} | data={currentUser.avatar} | ||||
| /> | /> | ||||
| <BaseInfoModal | |||||
| open={infoModalOpen} | |||||
| values={currentUser} | |||||
| onFinished={handleBaseInfoChange} | |||||
| ></BaseInfoModal> | |||||
| <ResetPasswordModal | |||||
| open={resetModalOpen} | |||||
| onFinished={handleResetPassword} | |||||
| ></ResetPasswordModal> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -97,7 +97,7 @@ const Login = () => { | |||||
| await fetchUserInfo(); | await fetchUserInfo(); | ||||
| const urlParams = new URL(window.location.href).searchParams; | const urlParams = new URL(window.location.href).searchParams; | ||||
| history.push(urlParams.get('redirect') || '/'); | |||||
| history.replace(urlParams.get('redirect') || '/'); | |||||
| } else { | } else { | ||||
| if (error?.data?.code === 500 && error?.data?.msg === '验证码错误') { | if (error?.data?.code === 500 && error?.data?.msg === '验证码错误') { | ||||
| captchaInputRef.current?.focus({ | captchaInputRef.current?.focus({ | ||||
| @@ -31,7 +31,7 @@ function UserSpace({ users = [] }: UserSpaceProps) { | |||||
| } | } | ||||
| ></Avatar> | ></Avatar> | ||||
| <div className={styles['user-space__name']}>{currentUser?.nickName}</div> | <div className={styles['user-space__name']}>{currentUser?.nickName}</div> | ||||
| <div className={styles['user-space__role']}>{currentUser?.roleNames?.[0]?.roleName}</div> | |||||
| <div className={styles['user-space__role']}>{currentUser?.roles?.[0]?.roleName}</div> | |||||
| <Divider | <Divider | ||||
| dashed | dashed | ||||
| style={{ borderColor: 'rgba(22, 100, 255, 0.19)', margin: '20px 0' }} | style={{ borderColor: 'rgba(22, 100, 255, 0.19)', margin: '20px 0' }} | ||||
| @@ -37,3 +37,12 @@ export function getCodeConfigDetailReq(id) { | |||||
| method: 'GET', | method: 'GET', | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取代码配置项在第几页 | |||||
| export function getCodeConfigPageNumReq(id, params) { | |||||
| return request(`/api/mmp/codeConfig/getPageNum/${id}`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | |||||
| } | |||||
| @@ -29,11 +29,17 @@ export type GlobalInitialState = { | |||||
| clientInfo?: ClientInfo; | clientInfo?: ClientInfo; | ||||
| }; | }; | ||||
| export enum PipelineGlobalParamType { | |||||
| String = 1, | |||||
| Number = 2, | |||||
| Boolean = 3, | |||||
| } | |||||
| // 流水线全局参数 | // 流水线全局参数 | ||||
| export type PipelineGlobalParam = { | export type PipelineGlobalParam = { | ||||
| param_name: string; | param_name: string; | ||||
| description: string; | description: string; | ||||
| param_type: number; | |||||
| param_type: PipelineGlobalParamType; | |||||
| param_value: number | string | boolean; | param_value: number | string | boolean; | ||||
| is_sensitive: number; | is_sensitive: number; | ||||
| }; | }; | ||||
| @@ -66,9 +72,10 @@ export type PipelineNodeModel = { | |||||
| label: string; | label: string; | ||||
| experimentStartTime: string; | experimentStartTime: string; | ||||
| experimentEndTime?: string | null; | experimentEndTime?: string | null; | ||||
| control_strategy: string; | |||||
| in_parameters: string; | |||||
| out_parameters: string; | |||||
| control_strategy: Record<string, PipelineNodeModelParameter>; | |||||
| in_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| task_info: Record<string, PipelineNodeModelParameter>; | |||||
| out_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| component_label: string; | component_label: string; | ||||
| icon_path: string; | icon_path: string; | ||||
| workflowId?: string; | workflowId?: string; | ||||
| @@ -82,11 +89,12 @@ export type PipelineNodeModelParameter = { | |||||
| label: string; | label: string; | ||||
| value: any; | value: any; | ||||
| require?: number; | require?: number; | ||||
| visible: boolean; | |||||
| placeholder?: string; | placeholder?: string; | ||||
| describe?: string; | describe?: string; | ||||
| fromSelect?: boolean; | |||||
| showValue?: any; | |||||
| editable?: number; | |||||
| editable?: boolean; | |||||
| showValue?: any; // 前端显示用 | |||||
| fromSelect?: boolean; // 前端显示用 | |||||
| activeTab?: string; // ResourceSelectorModal tab | activeTab?: string; // ResourceSelectorModal tab | ||||
| expandedKeys?: string[]; // ResourceSelectorModal expandedKeys | expandedKeys?: string[]; // ResourceSelectorModal expandedKeys | ||||
| checkedKeys?: string[]; // ResourceSelectorModal checkedKeys | checkedKeys?: string[]; // ResourceSelectorModal checkedKeys | ||||
| @@ -110,14 +118,7 @@ export type KeysToCamelCase<T> = { | |||||
| }; | }; | ||||
| // 序列化后的流水线节点 | // 序列化后的流水线节点 | ||||
| export type PipelineNodeModelSerialize = Omit< | |||||
| PipelineNodeModel, | |||||
| 'in_parameters' | 'out_parameters' | |||||
| > & { | |||||
| // control_strategy: Record<string, PipelineNodeModelParameter>; | |||||
| in_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| out_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| }; | |||||
| export type PipelineNodeModelSerialize = PipelineNodeModel; | |||||
| // 资源规格 | // 资源规格 | ||||
| export type ComputingResource = { | export type ComputingResource = { | ||||
| @@ -151,3 +152,6 @@ export type UploadFileRes = { | |||||
| fileSize: number; | fileSize: number; | ||||
| url: string; | url: string; | ||||
| }; | }; | ||||
| // 定义一个类型,取一个类型的有些字段必须的,其它的是可选 | |||||
| export type CustomPartial<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>; | |||||
| @@ -1,4 +1,5 @@ | |||||
| import { BasicInfoLink } from '@/components/BasicInfo/types'; | import { BasicInfoLink } from '@/components/BasicInfo/types'; | ||||
| import { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal'; | import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal'; | ||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | ||||
| import { | import { | ||||
| @@ -12,13 +13,6 @@ import { getGitUrl } from '@/utils'; | |||||
| // 格式化日期 | // 格式化日期 | ||||
| export { formatDate } from '@/utils/date'; | export { formatDate } from '@/utils/date'; | ||||
| type SelectedCodeConfig = { | |||||
| code_path: string; | |||||
| branch: string; | |||||
| showValue?: string; // 前端使用的 | |||||
| show_value?: string; // 后端使用的 | |||||
| }; | |||||
| /** | /** | ||||
| * 格式化数据集数组 | * 格式化数据集数组 | ||||
| * | * | ||||
| @@ -77,7 +71,7 @@ export const formatMirror = (mirror: ResourceSelectorResponse): string | undefin | |||||
| if (!mirror) { | if (!mirror) { | ||||
| return undefined; | return undefined; | ||||
| } | } | ||||
| return mirror.path; | |||||
| return mirror.url; | |||||
| }; | }; | ||||
| /** | /** | ||||
| @@ -87,20 +81,20 @@ export const formatMirror = (mirror: ResourceSelectorResponse): string | undefin | |||||
| * @return 基本信息链接对象 | * @return 基本信息链接对象 | ||||
| */ | */ | ||||
| export const formatCodeConfig = ( | export const formatCodeConfig = ( | ||||
| project?: ProjectDependency | SelectedCodeConfig, | |||||
| project?: ProjectDependency | CodeConfigData, | |||||
| ): BasicInfoLink | undefined => { | ): BasicInfoLink | undefined => { | ||||
| if (!project) { | if (!project) { | ||||
| return undefined; | return undefined; | ||||
| } | } | ||||
| // 创建表单,CodeSelect 组件返回,目前有流水线、模型部署、超参数自动寻优创建时选择了代码配置 | |||||
| if ('code_path' in project) { | |||||
| const { showValue, show_value, code_path, branch } = project; | |||||
| // 创建表单,CodeSelect 组件返回,目前有流水线、超参数自动寻优、主动学习、开发环境创建时选择了代码配置 | |||||
| if ('code_repo_name' in project) { | |||||
| const { code_repo_name, git_url, git_branch } = project; | |||||
| return { | return { | ||||
| value: showValue || show_value, | |||||
| url: getGitUrl(code_path, branch), | |||||
| value: code_repo_name, | |||||
| url: getGitUrl(git_url, git_branch), | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| // 数据集和模型的代码配置 | |||||
| // 数据集和模型详情的代码配置 | |||||
| const { url, branch, name } = project; | const { url, branch, name } = project; | ||||
| return { | return { | ||||
| value: name, | value: name, | ||||
| @@ -194,7 +188,6 @@ export const formatEnum = (options: EnumOptions[]): FormatEnumFunc => { | |||||
| }; | }; | ||||
| }; | }; | ||||
| /** | /** | ||||
| * 格式化数字 | * 格式化数字 | ||||
| * | * | ||||
| @@ -202,10 +195,10 @@ export const formatEnum = (options: EnumOptions[]): FormatEnumFunc => { | |||||
| * @param toFixed - 保留几位小数 | * @param toFixed - 保留几位小数 | ||||
| * @return 格式化的数字,如果不是数字,返回 '--' | * @return 格式化的数字,如果不是数字,返回 '--' | ||||
| */ | */ | ||||
| export const formatNumber = (value?: number | null, toFixed?: number) : number | string => { | |||||
| if (typeof value !== "number") { | |||||
| return '--' | |||||
| export const formatNumber = (value?: number | null, toFixed?: number): number | string => { | |||||
| if (typeof value !== 'number') { | |||||
| return '--'; | |||||
| } | } | ||||
| return toFixed ? Number(value).toFixed(toFixed) : value | |||||
| } | |||||
| return toFixed ? Number(value).toFixed(toFixed) : value; | |||||
| }; | |||||
| @@ -155,7 +155,7 @@ function renderCell<T>( | |||||
| record: T, | record: T, | ||||
| options?: TableCellValueOptions<T>, | options?: TableCellValueOptions<T>, | ||||
| ) { | ) { | ||||
| return type === TableCellValueType.Link | |||||
| return type === TableCellValueType.Link && text | |||||
| ? renderLink(text, ellipsis, record, options) | ? renderLink(text, ellipsis, record, options) | ||||
| : renderText(text, ellipsis, options); | : renderText(text, ellipsis, options); | ||||
| } | } | ||||