| @@ -360,6 +360,16 @@ export default [ | |||
| path: 'role-auth/user/:id', | |||
| component: './System/Role/authUser', | |||
| }, | |||
| { | |||
| name: '日志', | |||
| path: 'log', | |||
| routes: [ | |||
| { | |||
| path: '', | |||
| redirect: '/system/log/operlog', | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -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> | |||
| @@ -3,8 +3,9 @@ import { AvailableRange } from '@/enums'; | |||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||
| import { addCodeConfigReq, updateCodeConfigReq } from '@/services/codeConfig'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Form, Input, Radio, message, type ModalProps } from 'antd'; | |||
| import { Form, Input, Radio, message, type FormRule, type ModalProps } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useMemo } from 'react'; | |||
| export enum VerifyMode { | |||
| Password = 0, // 用户名密码 | |||
| @@ -25,7 +26,32 @@ interface AddCodeConfigModalProps extends Omit<ModalProps, 'onOk'> { | |||
| } | |||
| function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeConfigModalProps) { | |||
| // 上传请求 | |||
| const [form] = Form.useForm(); | |||
| const isPublic = Form.useWatch('code_repo_vis', form) === AvailableRange.Public; | |||
| const urlExample = useMemo( | |||
| () => | |||
| isPublic | |||
| ? 'https://gitlink.org.cn/ci4s/ci4sManagement-cloud.git' | |||
| : 'git@code.gitlink.org.cn:ci4s/ci4sManagement-cloud.git', | |||
| [isPublic], | |||
| ); | |||
| // /^(git@[\w.-]+:[\w./-]+\.git)$/ | |||
| const urlRules: FormRule[] = useMemo( | |||
| () => | |||
| isPublic | |||
| ? [ | |||
| { | |||
| type: 'url', | |||
| message: '请输入正确的 Git 地址', | |||
| }, | |||
| ] | |||
| : ([] as FormRule[]), | |||
| [isPublic], | |||
| ); | |||
| // 创建 | |||
| const createCodeConfig = async (formData: FormData) => { | |||
| const params: FormData & { id?: number } = { | |||
| ...formData, | |||
| @@ -78,14 +104,12 @@ function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeCo | |||
| > | |||
| <Form | |||
| name="form" | |||
| form={form} | |||
| layout="vertical" | |||
| onFinish={onFinish} | |||
| initialValues={initialValues} | |||
| autoComplete="off" | |||
| > | |||
| {/* 禁止 Chrome 自动填充 */} | |||
| {/* <Input type="text" style={{ display: 'none' }} /> | |||
| <Input type="password" style={{ display: 'none' }} /> */} | |||
| <Form.Item | |||
| label="代码仓库名称" | |||
| name="code_repo_name" | |||
| @@ -122,13 +146,15 @@ function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeCo | |||
| required: true, | |||
| message: '请输入 Git 地址', | |||
| }, | |||
| { | |||
| type: 'url', | |||
| message: '请输入正确的 Git 地址', | |||
| }, | |||
| ...urlRules, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入 Git 地址" showCount allowClear maxLength={256} /> | |||
| <Input | |||
| placeholder={`请输入 Git 地址,如: ${urlExample}`} | |||
| showCount | |||
| allowClear | |||
| maxLength={256} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="代码分支/Tag" | |||
| @@ -224,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> | |||
| @@ -22,8 +22,15 @@ | |||
| font-size: 16px; | |||
| } | |||
| &__tag { | |||
| padding: 4px; | |||
| color: @primary-color; | |||
| font-size: 12px; | |||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||
| border-radius: 4px; | |||
| } | |||
| &__url { | |||
| margin-bottom: 10px; | |||
| color: @text-color-secondary; | |||
| font-size: 14px; | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import clock from '@/assets/img/clock.png'; | |||
| import creatByImg from '@/assets/img/creatBy.png'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { AvailableRange } from '@/enums'; | |||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { Button, Flex, Typography } from 'antd'; | |||
| @@ -8,13 +9,14 @@ import styles from './index.less'; | |||
| type CodeConfigItemProps = { | |||
| item: CodeConfigData; | |||
| onClick: (item: CodeConfigData) => void; | |||
| onRemove: (item: CodeConfigData) => void; | |||
| 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)}> | |||
| <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']} | |||
| @@ -22,18 +24,43 @@ function CodeConfigItem({ item, onClick, onRemove }: CodeConfigItemProps) { | |||
| > | |||
| {item.code_repo_name} | |||
| </Typography.Paragraph> | |||
| <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={{ marginRight: '-4px' }} | |||
| onClick={(e) => { | |||
| e.stopPropagation(); | |||
| onRemove(item); | |||
| onRemove?.(item); | |||
| }} | |||
| > | |||
| <KFIcon type="icon-shanchu" font={17} /> | |||
| </Button> | |||
| </Flex> | |||
| <div className={styles['code-config-item__description']}>{item.git_url}</div> | |||
| <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> | |||
| <Flex justify="space-between"> | |||
| <div className={styles['code-config-item__time']}> | |||
| <img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" /> | |||
| @@ -0,0 +1,61 @@ | |||
| .resource-item { | |||
| position: relative; | |||
| width: calc(25% - 15px); | |||
| padding: 20px; | |||
| background: white; | |||
| border: 1px solid #eaeaea; | |||
| border-radius: 4px; | |||
| cursor: pointer; | |||
| @media screen and (max-width: 1860px) { | |||
| & { | |||
| width: calc(33.33% - 13.33px); | |||
| } | |||
| } | |||
| &__name { | |||
| position: relative; | |||
| display: inline-block; | |||
| height: 24px; | |||
| margin: 0 10px 0 0 !important; | |||
| color: @text-color; | |||
| font-size: 16px; | |||
| } | |||
| &__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,54 @@ | |||
| import clock from '@/assets/img/clock.png'; | |||
| import creatByImg from '@/assets/img/creatBy.png'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { Button, Flex, Typography } from 'antd'; | |||
| import { ResourceData } from '../../config'; | |||
| import styles from './index.less'; | |||
| type ResourceItemProps = { | |||
| item: ResourceData; | |||
| isPublic: boolean; | |||
| onRemove: (item: ResourceData) => void; | |||
| onClick: (item: ResourceData) => void; | |||
| }; | |||
| function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) { | |||
| return ( | |||
| <div className={styles['resource-item']} onClick={() => onClick(item)}> | |||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | |||
| <Typography.Paragraph | |||
| className={styles['resource-item__name']} | |||
| ellipsis={{ tooltip: item.name }} | |||
| > | |||
| {item.name} | |||
| </Typography.Paragraph> | |||
| {!isPublic && ( | |||
| <Button | |||
| type="text" | |||
| shape="circle" | |||
| onClick={(e) => { | |||
| e.stopPropagation(); | |||
| onRemove(item); | |||
| }} | |||
| > | |||
| <KFIcon type="icon-shanchu" font={17} /> | |||
| </Button> | |||
| )} | |||
| </Flex> | |||
| <div className={styles['resource-item__description']}>{item.description}</div> | |||
| <Flex justify="space-between"> | |||
| <div className={styles['resource-item__time']}> | |||
| <img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" /> | |||
| <span>{item.create_by}</span> | |||
| </div> | |||
| <div className={styles['resource-item__time']}> | |||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||
| <span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span> | |||
| </div> | |||
| </Flex> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ResourceItem; | |||
| @@ -28,7 +28,7 @@ const JobLogDetailForm: React.FC<JobLogFormProps> = (props) => { | |||
| return ( | |||
| <KFModal | |||
| width={640} | |||
| width={680} | |||
| title={intl.formatMessage({ | |||
| id: 'monitor.job.log.title', | |||
| defaultMessage: '定时任务调度日志', | |||
| @@ -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} | |||
| @@ -22,8 +22,11 @@ export type ConfigFormProps = { | |||
| const ConfigForm: React.FC<ConfigFormProps> = (props) => { | |||
| const [form] = Form.useForm(); | |||
| const { configTypeOptions } = props; | |||
| const formLayout = { | |||
| labelCol: { span: 4 }, | |||
| wrapperCol: { span: 20 }, | |||
| }; | |||
| useEffect(() => { | |||
| form.resetFields(); | |||
| @@ -32,7 +35,7 @@ const ConfigForm: React.FC<ConfigFormProps> = (props) => { | |||
| configName: props.values.configName, | |||
| configKey: props.values.configKey, | |||
| configValue: props.values.configValue, | |||
| configType: props.values.configType, | |||
| configType: props.values.configType || Object.keys(configTypeOptions)[0], | |||
| createBy: props.values.createBy, | |||
| createTime: props.values.createTime, | |||
| updateBy: props.values.updateBy, | |||
| @@ -54,7 +57,7 @@ const ConfigForm: React.FC<ConfigFormProps> = (props) => { | |||
| return ( | |||
| <KFModal | |||
| width={640} | |||
| width={680} | |||
| title={intl.formatMessage({ | |||
| id: 'system.config.title', | |||
| defaultMessage: '编辑参数配置', | |||
| @@ -71,6 +74,10 @@ const ConfigForm: React.FC<ConfigFormProps> = (props) => { | |||
| submitter={false} | |||
| layout="horizontal" | |||
| onFinish={handleFinish} | |||
| {...formLayout} | |||
| size="large" | |||
| labelAlign="right" | |||
| autoComplete="off" | |||
| > | |||
| <ProFormDigit | |||
| name="configId" | |||
| @@ -331,7 +331,7 @@ const ConfigTableList: React.FC = () => { | |||
| handleRefreshCache(); | |||
| }} | |||
| > | |||
| <ReloadOutlined /> | |||
| <ReloadOutlined />{' '} | |||
| <FormattedMessage id="system.config.refreshCache" defaultMessage="刷新缓存" /> | |||
| </Button>, | |||
| ]} | |||
| @@ -23,8 +23,11 @@ export type DataFormProps = { | |||
| const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| const [form] = Form.useForm(); | |||
| const { statusOptions } = props; | |||
| const formLayout = { | |||
| labelCol: { span: 4 }, | |||
| wrapperCol: { span: 20 }, | |||
| }; | |||
| useEffect(() => { | |||
| form.resetFields(); | |||
| @@ -36,8 +39,8 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| dictType: props.values.dictType, | |||
| cssClass: props.values.cssClass, | |||
| listClass: props.values.listClass, | |||
| isDefault: props.values.isDefault, | |||
| status: props.values.status, | |||
| isDefault: props.values.isDefault || 'N', | |||
| status: props.values.status || Object.keys(statusOptions)[0], | |||
| createBy: props.values.createBy, | |||
| createTime: props.values.createTime, | |||
| updateBy: props.values.updateBy, | |||
| @@ -59,7 +62,7 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| return ( | |||
| <KFModal | |||
| width={640} | |||
| width={680} | |||
| title={intl.formatMessage({ | |||
| id: 'system.dict.data.title', | |||
| defaultMessage: '编辑字典数据', | |||
| @@ -76,6 +79,10 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| submitter={false} | |||
| layout="horizontal" | |||
| onFinish={handleFinish} | |||
| {...formLayout} | |||
| size="large" | |||
| labelAlign="right" | |||
| autoComplete="off" | |||
| > | |||
| <ProFormDigit | |||
| name="dictCode" | |||
| @@ -184,7 +191,7 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| id: 'system.dict.data.dict_sort', | |||
| defaultMessage: '字典排序', | |||
| })} | |||
| colProps={{ md: 12, xl: 12 }} | |||
| colProps={{ md: 12, xl: 24 }} | |||
| placeholder="请输入字典排序" | |||
| rules={[ | |||
| { | |||
| @@ -203,7 +210,6 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| Y: '是', | |||
| N: '否', | |||
| }} | |||
| initialValue={'N'} | |||
| colProps={{ md: 12, xl: 24 }} | |||
| placeholder="请输入是否默认" | |||
| rules={[ | |||
| @@ -220,7 +226,6 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| id: 'system.dict.data.status', | |||
| defaultMessage: '状态', | |||
| })} | |||
| initialValue={'0'} | |||
| colProps={{ md: 12, xl: 24 }} | |||
| placeholder="请输入状态" | |||
| rules={[ | |||
| @@ -22,8 +22,11 @@ export type LogininforFormProps = { | |||
| const LogininforForm: React.FC<LogininforFormProps> = (props) => { | |||
| const [form] = Form.useForm(); | |||
| const { statusOptions } = props; | |||
| const formLayout = { | |||
| labelCol: { span: 4 }, | |||
| wrapperCol: { span: 20 }, | |||
| }; | |||
| useEffect(() => { | |||
| form.resetFields(); | |||
| @@ -54,7 +57,7 @@ const LogininforForm: React.FC<LogininforFormProps> = (props) => { | |||
| return ( | |||
| <KFModal | |||
| width={640} | |||
| width={680} | |||
| title={intl.formatMessage({ | |||
| id: 'system.logininfor.title', | |||
| defaultMessage: '编辑系统访问记录', | |||
| @@ -65,7 +68,16 @@ const LogininforForm: React.FC<LogininforFormProps> = (props) => { | |||
| onOk={handleOk} | |||
| onCancel={handleCancel} | |||
| > | |||
| <ProForm form={form} grid={true} layout="horizontal" onFinish={handleFinish}> | |||
| <ProForm | |||
| form={form} | |||
| grid={true} | |||
| layout="horizontal" | |||
| onFinish={handleFinish} | |||
| {...formLayout} | |||
| size="large" | |||
| labelAlign="right" | |||
| autoComplete="off" | |||
| > | |||
| <ProFormDigit | |||
| name="infoId" | |||
| label={intl.formatMessage({ | |||
| @@ -24,8 +24,11 @@ export type NoticeFormProps = { | |||
| const NoticeForm: React.FC<NoticeFormProps> = (props) => { | |||
| const [form] = Form.useForm(); | |||
| const { noticeTypeOptions, statusOptions } = props; | |||
| const formLayout = { | |||
| labelCol: { span: 4 }, | |||
| wrapperCol: { span: 20 }, | |||
| }; | |||
| useEffect(() => { | |||
| form.resetFields(); | |||
| @@ -34,7 +37,7 @@ const NoticeForm: React.FC<NoticeFormProps> = (props) => { | |||
| noticeTitle: props.values.noticeTitle, | |||
| noticeType: props.values.noticeType, | |||
| noticeContent: props.values.noticeContent, | |||
| status: props.values.status, | |||
| status: props.values.status || Object.keys(statusOptions)[0], | |||
| createBy: props.values.createBy, | |||
| createTime: props.values.createTime, | |||
| updateBy: props.values.updateBy, | |||
| @@ -56,7 +59,7 @@ const NoticeForm: React.FC<NoticeFormProps> = (props) => { | |||
| return ( | |||
| <KFModal | |||
| width={640} | |||
| width={680} | |||
| title={intl.formatMessage({ | |||
| id: 'system.notice.title', | |||
| defaultMessage: '编辑通知公告', | |||
| @@ -73,6 +76,10 @@ const NoticeForm: React.FC<NoticeFormProps> = (props) => { | |||
| submitter={false} | |||
| layout="horizontal" | |||
| onFinish={handleFinish} | |||
| {...formLayout} | |||
| size="large" | |||
| labelAlign="right" | |||
| autoComplete="off" | |||
| > | |||
| <ProFormDigit | |||
| name="noticeId" | |||
| @@ -29,7 +29,7 @@ const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => { | |||
| return ( | |||
| <KFModal | |||
| width={640} | |||
| width={680} | |||
| title={intl.formatMessage({ | |||
| id: 'monitor.operlog.title', | |||
| defaultMessage: '编辑操作日志记录', | |||
| @@ -245,17 +245,17 @@ const OperlogTableList: React.FC = () => { | |||
| tableAlertRender={false} | |||
| tableAlertOptionRender={false} | |||
| toolBarRender={() => [ | |||
| <Button | |||
| type="primary" | |||
| key="add" | |||
| hidden={!access.hasPerms('system:operlog:add')} | |||
| onClick={async () => { | |||
| setCurrentRow(undefined); | |||
| setModalVisible(true); | |||
| }} | |||
| > | |||
| <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" /> | |||
| </Button>, | |||
| // <Button | |||
| // type="primary" | |||
| // key="add" | |||
| // hidden={!access.hasPerms('system:operlog:add')} | |||
| // onClick={async () => { | |||
| // setCurrentRow(undefined); | |||
| // setModalVisible(true); | |||
| // }} | |||
| // > | |||
| // <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" /> | |||
| // </Button>, | |||
| <Button | |||
| type="primary" | |||
| key="remove" | |||
| @@ -33,6 +33,10 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||
| ); | |||
| const [deptTreeExpandKey, setDeptTreeExpandKey] = useState<Key[]>([]); | |||
| const [checkStrictly, setCheckStrictly] = useState<boolean>(true); | |||
| const formLayout = { | |||
| labelCol: { span: 4 }, | |||
| wrapperCol: { span: 20 }, | |||
| }; | |||
| useEffect(() => { | |||
| setDeptIds(deptCheckedKeys); | |||
| @@ -91,7 +95,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||
| return ( | |||
| <KFModal | |||
| width={640} | |||
| width={680} | |||
| title={intl.formatMessage({ | |||
| id: 'system.user.auth.role', | |||
| defaultMessage: '分配角色', | |||
| @@ -111,6 +115,10 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||
| login_password: '', | |||
| confirm_password: '', | |||
| }} | |||
| {...formLayout} | |||
| size="large" | |||
| labelAlign="right" | |||
| autoComplete="off" | |||
| > | |||
| <ProFormDigit | |||
| name="roleId" | |||
| @@ -73,7 +73,11 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| props.onCancel(); | |||
| }; | |||
| const handleFinish = async (values: Record<string, any>) => { | |||
| props.onSubmit(values as UserFormData); | |||
| const params = { | |||
| ...values, | |||
| userId: props.values.userId, | |||
| }; | |||
| props.onSubmit(params as UserFormData); | |||
| }; | |||
| return ( | |||
| @@ -189,9 +193,6 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| hidden={userId} | |||
| placeholder="请输入用户账号" | |||
| colProps={{ md: 12, xl: 12 }} | |||
| fieldProps={{ | |||
| autoComplete: 'off', | |||
| }} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -22,7 +22,6 @@ const Login = () => { | |||
| const [captchaCode, setCaptchaCode] = useState<string>(''); | |||
| const [uuid, setUuid] = useState<string>(''); | |||
| const [form] = Form.useForm(); | |||
| const [usernameReadOnly, setUsernameReadOnly] = useState<boolean>(true); | |||
| const captchaInputRef = useRef<InputRef>(null); | |||
| useEffect(() => { | |||
| @@ -82,7 +81,9 @@ const Login = () => { | |||
| history.push(urlParams.get('redirect') || '/'); | |||
| } else { | |||
| if (error?.data?.code === 500 && error?.data?.msg === '验证码错误') { | |||
| captchaInputRef.current?.focus(); | |||
| captchaInputRef.current?.focus({ | |||
| cursor: 'all', | |||
| }); | |||
| } | |||
| clearSessionToken(); | |||
| @@ -140,8 +141,6 @@ const Login = () => { | |||
| placeholder="请输入用户名" | |||
| prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />} | |||
| allowClear | |||
| readOnly={usernameReadOnly} | |||
| onFocus={() => setUsernameReadOnly(false)} | |||
| /> | |||
| </Form.Item> | |||
| @@ -39,7 +39,7 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| } | |||
| } else { | |||
| if (getLocalRoute(route, menuItem)) { | |||
| return; | |||
| continue; | |||
| } | |||
| const names: string[] = menuItem.component.split('/'); | |||
| let path = ''; | |||
| @@ -24,8 +24,15 @@ export const menuItemRender = (isSubMenu: boolean) => { | |||
| <span className="kf-menu-item__name">{item.name}</span> | |||
| </> | |||
| ); | |||
| if (isSubMenu) { | |||
| return <div className="kf-menu-item">{childen}</div>; | |||
| } else if (item.isUrl) { | |||
| return ( | |||
| <a href={item.path || ''} target="_blank" className="kf-menu-item" rel="noreferrer"> | |||
| {childen} | |||
| </a> | |||
| ); | |||
| } else { | |||
| return ( | |||
| <Link to={item.path || ''} className="kf-menu-item"> | |||