| @@ -84,7 +84,7 @@ function CodeConfigList() { | |||
| }; | |||
| // 修改 | |||
| const handleClick = (record: CodeConfigData) => { | |||
| const handleEdit = (record: CodeConfigData) => { | |||
| const { close } = openAntdModal(AddCodeConfigModal, { | |||
| opType: OperationType.Update, | |||
| codeConfigData: record, | |||
| @@ -147,7 +147,7 @@ function CodeConfigList() { | |||
| item={item} | |||
| key={item.id} | |||
| onRemove={handleRemove} | |||
| onClick={handleClick} | |||
| onEdit={handleEdit} | |||
| /> | |||
| ))} | |||
| </div> | |||
| @@ -250,8 +250,8 @@ function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeCo | |||
| <Input.TextArea | |||
| placeholder="请输入 SSH Key" | |||
| showCount | |||
| maxLength={1024} | |||
| autoSize={{ minRows: 3, maxRows: 6 }} | |||
| maxLength={4096} | |||
| autoSize={{ minRows: 4, maxRows: 8 }} | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| @@ -10,10 +10,11 @@ import styles from './index.less'; | |||
| type CodeConfigItemProps = { | |||
| item: CodeConfigData; | |||
| onClick?: (item: CodeConfigData) => void; | |||
| onEdit?: (item: CodeConfigData) => void; | |||
| onRemove?: (item: CodeConfigData) => void; | |||
| }; | |||
| function CodeConfigItem({ item, onClick, onRemove }: CodeConfigItemProps) { | |||
| function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps) { | |||
| return ( | |||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | |||
| <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']}> | |||
| {item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'} | |||
| </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 | |||
| type="text" | |||
| shape="circle" | |||
| style={{ marginLeft: 'auto', right: 0 }} | |||
| style={{ marginRight: '-4px' }} | |||
| onClick={(e) => { | |||
| e.stopPropagation(); | |||
| onRemove?.(item); | |||
| @@ -86,6 +86,9 @@ export function canInput(parameter: PipelineNodeModelParameter) { | |||
| const { type, item_type } = parameter; | |||
| return !( | |||
| 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 { NamePath } from 'antd/es/form/interface'; | |||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||
| import CodeSelectorModal from '../CodeSelectorModal'; | |||
| import PropsLabel from '../PropsLabel'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorType, | |||
| @@ -57,7 +58,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| formError: !!error, | |||
| }; | |||
| // console.log('res', res); | |||
| console.log('res', res); | |||
| onFormChange(res); | |||
| } | |||
| }; | |||
| @@ -79,7 +80,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| out_parameters: JSON.parse(model.out_parameters), | |||
| control_strategy: JSON.parse(model.control_strategy), | |||
| }; | |||
| // console.log('model', nodeData); | |||
| console.log('model', nodeData); | |||
| setStagingItem({ | |||
| ...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 = ( | |||
| formItemName: NamePath, | |||
| @@ -146,8 +189,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| const { activeTab, id, version, path } = res; | |||
| if (formItemName === 'image') { | |||
| // 单独的选择镜像 | |||
| form.setFieldValue(formItemName, path); | |||
| } else { | |||
| // 输入参数选择镜像 | |||
| form.setFieldValue(formItemName, { | |||
| ...item, | |||
| value: path, | |||
| @@ -160,12 +205,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| } | |||
| } else { | |||
| const { activeTab, id, name, version, path } = res; | |||
| const jsonObj = { | |||
| const value = JSON.stringify({ | |||
| id, | |||
| version, | |||
| path, | |||
| }; | |||
| const value = JSON.stringify(jsonObj); | |||
| }); | |||
| const showValue = `${name}:${version}`; | |||
| form.setFieldValue(formItemName, { | |||
| ...item, | |||
| @@ -467,7 +511,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| size="small" | |||
| type="link" | |||
| 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']} | |||
| > | |||
| {item.value.label} | |||