| @@ -84,7 +84,7 @@ function CodeConfigList() { | |||||
| }; | }; | ||||
| // 修改 | // 修改 | ||||
| const handleClick = (record: CodeConfigData) => { | |||||
| const handleEdit = (record: CodeConfigData) => { | |||||
| const { close } = openAntdModal(AddCodeConfigModal, { | const { close } = openAntdModal(AddCodeConfigModal, { | ||||
| opType: OperationType.Update, | opType: OperationType.Update, | ||||
| codeConfigData: record, | codeConfigData: record, | ||||
| @@ -147,7 +147,7 @@ function CodeConfigList() { | |||||
| item={item} | item={item} | ||||
| key={item.id} | key={item.id} | ||||
| onRemove={handleRemove} | onRemove={handleRemove} | ||||
| onClick={handleClick} | |||||
| onEdit={handleEdit} | |||||
| /> | /> | ||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| @@ -250,8 +250,8 @@ function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeCo | |||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入 SSH Key" | placeholder="请输入 SSH Key" | ||||
| showCount | showCount | ||||
| maxLength={1024} | |||||
| autoSize={{ minRows: 3, maxRows: 6 }} | |||||
| maxLength={4096} | |||||
| autoSize={{ minRows: 4, maxRows: 8 }} | |||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -10,10 +10,11 @@ import styles from './index.less'; | |||||
| type CodeConfigItemProps = { | type CodeConfigItemProps = { | ||||
| item: CodeConfigData; | item: CodeConfigData; | ||||
| onClick?: (item: CodeConfigData) => void; | onClick?: (item: CodeConfigData) => void; | ||||
| onEdit?: (item: CodeConfigData) => void; | |||||
| onRemove?: (item: CodeConfigData) => void; | onRemove?: (item: CodeConfigData) => void; | ||||
| }; | }; | ||||
| function CodeConfigItem({ item, onClick, onRemove }: CodeConfigItemProps) { | |||||
| function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | ||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | ||||
| @@ -26,10 +27,22 @@ function CodeConfigItem({ item, onClick, onRemove }: CodeConfigItemProps) { | |||||
| <div className={styles['code-config-item__tag']}> | <div className={styles['code-config-item__tag']}> | ||||
| {item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'} | {item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'} | ||||
| </div> | </div> | ||||
| <Button | |||||
| type="text" | |||||
| shape="circle" | |||||
| style={{ marginLeft: 'auto', marginRight: '4px' }} | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| onEdit?.(item); | |||||
| }} | |||||
| > | |||||
| <KFIcon type="icon-bianji" font={17} /> | |||||
| </Button> | |||||
| <Button | <Button | ||||
| type="text" | type="text" | ||||
| shape="circle" | shape="circle" | ||||
| style={{ marginLeft: 'auto', right: 0 }} | |||||
| style={{ marginRight: '-4px' }} | |||||
| onClick={(e) => { | onClick={(e) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| onRemove?.(item); | onRemove?.(item); | ||||
| @@ -86,6 +86,9 @@ export function canInput(parameter: PipelineNodeModelParameter) { | |||||
| const { type, item_type } = parameter; | const { type, item_type } = parameter; | ||||
| return !( | return !( | ||||
| type === 'ref' && | type === 'ref' && | ||||
| (item_type === 'dataset' || item_type === 'model' || item_type === 'image') | |||||
| (item_type === 'dataset' || | |||||
| item_type === 'model' || | |||||
| item_type === 'image' || | |||||
| item_type === 'code') | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -0,0 +1,68 @@ | |||||
| .code-config-item { | |||||
| position: relative; | |||||
| padding: 20px; | |||||
| background: white; | |||||
| border: 1px solid #eaeaea; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| &__name { | |||||
| position: relative; | |||||
| display: inline-block; | |||||
| height: 24px; | |||||
| margin: 0 10px 0 0 !important; | |||||
| color: @text-color; | |||||
| font-size: 16px; | |||||
| } | |||||
| &__tag { | |||||
| padding: 4px; | |||||
| color: @primary-color; | |||||
| font-size: 12px; | |||||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||||
| border-radius: 4px; | |||||
| } | |||||
| &__url { | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| } | |||||
| &__description { | |||||
| height: 44px; | |||||
| margin-bottom: 20px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| .multiLine(2); | |||||
| } | |||||
| &__time { | |||||
| display: flex; | |||||
| flex: 0 1 content; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| color: #808080; | |||||
| font-size: 13px; | |||||
| } | |||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| .resource-item__name { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| .resource-item__name { | |||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { Flex, Typography } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| type CodeConfigItemProps = { | |||||
| item: CodeConfigData; | |||||
| onClick?: (item: CodeConfigData) => void; | |||||
| }; | |||||
| function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| return ( | |||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | |||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__name']} | |||||
| ellipsis={{ tooltip: item.code_repo_name }} | |||||
| > | |||||
| {item.code_repo_name} | |||||
| </Typography.Paragraph> | |||||
| <div className={styles['code-config-item__tag']}> | |||||
| {item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'} | |||||
| </div> | |||||
| </Flex> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__url']} | |||||
| ellipsis={{ tooltip: item.git_url }} | |||||
| style={{ marginBottom: '8px' }} | |||||
| > | |||||
| {item.git_url} | |||||
| </Typography.Paragraph> | |||||
| <div className={styles['code-config-item__url']} style={{ marginBottom: '20px' }}> | |||||
| {item.git_branch} | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CodeConfigItem; | |||||
| @@ -0,0 +1,29 @@ | |||||
| .code-selector { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| :global { | |||||
| .ant-input-affix-wrapper .ant-input-prefix { | |||||
| margin-inline-end: 12px; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| .ant-input-group-addon { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| &__content { | |||||
| display: grid; | |||||
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |||||
| gap: 20px; | |||||
| width: 100%; | |||||
| margin-top: 30px; | |||||
| margin-bottom: 30px; | |||||
| overflow-x: hidden; | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,114 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-11 16:31:18 | |||||
| * @Description: 选择代码 | |||||
| */ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { getCodeConfigListReq } from '@/services/codeConfig'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import type { ModalProps, PaginationProps } from 'antd'; | |||||
| import { Empty, Input, Pagination } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import CodeConfigItem from '../CodeConfigItem'; | |||||
| import styles from './index.less'; | |||||
| export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| onOk?: (params: CodeConfigData | undefined) => void; | |||||
| } | |||||
| function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<PaginationProps>({ | |||||
| current: 1, | |||||
| pageSize: 20, | |||||
| }); | |||||
| const [searchText, setSearchText] = useState<string | undefined>(undefined); | |||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | |||||
| useEffect(() => { | |||||
| getDataList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取数据请求 | |||||
| const getDataList = async () => { | |||||
| const params = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| code_repo_name: searchText !== '' ? searchText : undefined, | |||||
| }; | |||||
| const [res] = await to(getCodeConfigListReq(params)); | |||||
| if (res && res.data && res.data.content) { | |||||
| setDataList(res.data.content); | |||||
| setTotal(res.data.totalElements); | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const handleSearch = (value: string) => { | |||||
| setSearchText(value); | |||||
| }; | |||||
| const handleClick = (item: CodeConfigData) => { | |||||
| onOk?.(item); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => { | |||||
| setPagination({ | |||||
| current: page, | |||||
| pageSize: pageSize, | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="选择代码配置" | |||||
| image={require('@/assets/img/edit-experiment.png')} | |||||
| width={920} | |||||
| footer={null} | |||||
| destroyOnClose | |||||
| > | |||||
| <div className={styles['code-selector']}> | |||||
| <Input.Search | |||||
| placeholder="按代码仓库名称筛选" | |||||
| allowClear | |||||
| onSearch={handleSearch} | |||||
| style={{ | |||||
| width: '100%', | |||||
| }} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| suffix={null} | |||||
| value={inputText} | |||||
| /> | |||||
| {dataList?.length !== 0 ? ( | |||||
| <> | |||||
| <div className={styles['code-selector__content']}> | |||||
| {dataList?.map((item) => ( | |||||
| <CodeConfigItem item={item} key={item.id} onClick={handleClick} /> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| /> | |||||
| </> | |||||
| ) : ( | |||||
| <div className={styles['code-selector__empty']}> | |||||
| <Empty></Empty> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default CodeSelectorModal; | |||||
| @@ -17,6 +17,7 @@ import { INode } from '@antv/g6'; | |||||
| import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd'; | import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd'; | ||||
| import { NamePath } from 'antd/es/form/interface'; | import { NamePath } from 'antd/es/form/interface'; | ||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | import { forwardRef, useImperativeHandle, useState } from 'react'; | ||||
| import CodeSelectorModal from '../CodeSelectorModal'; | |||||
| import PropsLabel from '../PropsLabel'; | import PropsLabel from '../PropsLabel'; | ||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| @@ -57,7 +58,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| formError: !!error, | formError: !!error, | ||||
| }; | }; | ||||
| // console.log('res', res); | |||||
| console.log('res', res); | |||||
| onFormChange(res); | onFormChange(res); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -79,7 +80,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| out_parameters: JSON.parse(model.out_parameters), | out_parameters: JSON.parse(model.out_parameters), | ||||
| control_strategy: JSON.parse(model.control_strategy), | control_strategy: JSON.parse(model.control_strategy), | ||||
| }; | }; | ||||
| // console.log('model', nodeData); | |||||
| console.log('model', nodeData); | |||||
| setStagingItem({ | setStagingItem({ | ||||
| ...nodeData, | ...nodeData, | ||||
| }); | }); | ||||
| @@ -115,6 +116,48 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| }, | }, | ||||
| })); | })); | ||||
| // ref 类型选择 | |||||
| const selectRefData = ( | |||||
| formItemName: NamePath, | |||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||||
| ) => { | |||||
| if (item.item_type === 'code') { | |||||
| selectCodeConfig(formItemName, item); | |||||
| } else { | |||||
| selectResource(formItemName, item); | |||||
| } | |||||
| }; | |||||
| // 选择代码配置 | |||||
| const selectCodeConfig = ( | |||||
| formItemName: NamePath, | |||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||||
| ) => { | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| console.log('res', res); | |||||
| const value = JSON.stringify({ | |||||
| id: res.id, | |||||
| name: res.code_repo_name, | |||||
| code_path: res.git_url, | |||||
| branch: res.git_branch, | |||||
| username: res.git_user_name, | |||||
| password: res.git_password, | |||||
| ssh_private_key: res.ssh_key, | |||||
| }); | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value, | |||||
| showValue: res.code_repo_name, | |||||
| fromSelect: true, | |||||
| }); | |||||
| } | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 选择数据集、模型、镜像 | // 选择数据集、模型、镜像 | ||||
| const selectResource = ( | const selectResource = ( | ||||
| formItemName: NamePath, | formItemName: NamePath, | ||||
| @@ -146,8 +189,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| const { activeTab, id, version, path } = res; | const { activeTab, id, version, path } = res; | ||||
| if (formItemName === 'image') { | if (formItemName === 'image') { | ||||
| // 单独的选择镜像 | |||||
| form.setFieldValue(formItemName, path); | form.setFieldValue(formItemName, path); | ||||
| } else { | } else { | ||||
| // 输入参数选择镜像 | |||||
| form.setFieldValue(formItemName, { | form.setFieldValue(formItemName, { | ||||
| ...item, | ...item, | ||||
| value: path, | value: path, | ||||
| @@ -160,12 +205,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| } | } | ||||
| } else { | } else { | ||||
| const { activeTab, id, name, version, path } = res; | const { activeTab, id, name, version, path } = res; | ||||
| const jsonObj = { | |||||
| const value = JSON.stringify({ | |||||
| id, | id, | ||||
| version, | version, | ||||
| path, | path, | ||||
| }; | |||||
| const value = JSON.stringify(jsonObj); | |||||
| }); | |||||
| const showValue = `${name}:${version}`; | const showValue = `${name}:${version}`; | ||||
| form.setFieldValue(formItemName, { | form.setFieldValue(formItemName, { | ||||
| ...item, | ...item, | ||||
| @@ -467,7 +511,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| size="small" | size="small" | ||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon(item.value)} | icon={getSelectBtnIcon(item.value)} | ||||
| onClick={() => selectResource(['in_parameters', item.key], item.value)} | |||||
| onClick={() => selectRefData(['in_parameters', item.key], item.value)} | |||||
| className={styles['pipeline-drawer__ref-row__select-button']} | className={styles['pipeline-drawer__ref-row__select-button']} | ||||
| > | > | ||||
| {item.value.label} | {item.value.label} | ||||