| @@ -32,9 +32,9 @@ export default function BasicTableInfo({ | |||
| return ( | |||
| <div className={classNames('kf-basic-table-info', className)} style={style}> | |||
| {showDatas.map((item) => ( | |||
| {showDatas.map((item, index) => ( | |||
| <BasicInfoItem | |||
| key={item.label} | |||
| key={`${item.label}-${index}`} | |||
| data={item} | |||
| labelWidth={labelWidth} | |||
| classPrefix="kf-basic-table-info" | |||
| @@ -63,7 +63,7 @@ function AutoMLList() { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| ml_name: searchText, | |||
| ml_name: searchText || undefined, | |||
| }; | |||
| const [res] = await to(getAutoMLListReq(params)); | |||
| if (res && res.data) { | |||
| @@ -76,6 +76,10 @@ function AutoMLList() { | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| setSearchText(value); | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| }; | |||
| // 删除模型部署 | |||
| @@ -59,7 +59,7 @@ function CodeConfigList() { | |||
| const params = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| code_repo_name: searchText !== '' ? searchText : undefined, | |||
| code_repo_name: searchText || undefined, | |||
| }; | |||
| const [res] = await to(getCodeConfigListReq(params)); | |||
| if (res && res.data && res.data.content) { | |||
| @@ -83,6 +83,10 @@ function CodeConfigList() { | |||
| // 搜索 | |||
| const handleSearch = (value: string) => { | |||
| setSearchText(value); | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| }; | |||
| // 删除 | |||
| @@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal'; | |||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { addDataset } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { getFileListFromEvent, limitUploadFileType, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| @@ -42,6 +42,8 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| Authorization: getAccessToken() || '', | |||
| }, | |||
| defaultFileList: [], | |||
| accept: '.zip,.tgz', | |||
| beforeUpload: limitUploadFileType('zip,tgz'), | |||
| }; | |||
| // 获取集群版本数据 | |||
| @@ -200,7 +202,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| }, | |||
| ]} | |||
| > | |||
| <Upload {...uploadProps} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||
| <Upload {...uploadProps} data={{ uuid: uuid }}> | |||
| <Button | |||
| className={styles['upload-button']} | |||
| type="default" | |||
| @@ -21,8 +21,9 @@ import styles from '../AddDatasetModal/index.less'; | |||
| interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | |||
| resourceType: ResourceType; | |||
| resourceId: number; | |||
| identifier: string; | |||
| resoureName: string; | |||
| owner: string; | |||
| identifier: string; | |||
| is_public: boolean; | |||
| onOk: () => void; | |||
| } | |||
| @@ -31,6 +32,7 @@ function AddVersionModal({ | |||
| resourceType, | |||
| resourceId, | |||
| resoureName, | |||
| owner, | |||
| identifier, | |||
| is_public, | |||
| onOk, | |||
| @@ -46,6 +48,8 @@ function AddVersionModal({ | |||
| Authorization: getAccessToken() || '', | |||
| }, | |||
| defaultFileList: [], | |||
| beforeUpload: config.beforeUpload, | |||
| accept: config.uploadAccept, | |||
| }; | |||
| // 上传请求 | |||
| @@ -74,6 +78,7 @@ function AddVersionModal({ | |||
| id: resourceId, | |||
| identifier, | |||
| is_public, | |||
| owner, | |||
| [config.filePropKey]: version_vos, | |||
| ...omit(formData, 'fileList'), | |||
| [config.sourceParamKey]: DataSource.Create, | |||
| @@ -83,7 +88,7 @@ function AddVersionModal({ | |||
| }; | |||
| const name = config.name; | |||
| const accept = config.uploadAccept; | |||
| return ( | |||
| <KFModal | |||
| {...rest} | |||
| @@ -170,7 +175,7 @@ function AddVersionModal({ | |||
| }, | |||
| ]} | |||
| > | |||
| <Upload {...uploadProps} data={{ uuid: uuid }} accept={accept}> | |||
| <Upload {...uploadProps} data={{ uuid: uuid }}> | |||
| <Button | |||
| className={styles['upload-button']} | |||
| type="default" | |||
| @@ -179,7 +184,7 @@ function AddVersionModal({ | |||
| 上传文件 | |||
| </Button> | |||
| {resourceType === ResourceType.Dataset && ( | |||
| <div className={styles['upload-tip']}>只允许上传 .zip 格式文件</div> | |||
| <div className={styles['upload-tip']}>只允许上传 .zip 和 .tgz 格式文件</div> | |||
| )} | |||
| </Upload> | |||
| </Form.Item> | |||
| @@ -3,8 +3,7 @@ | |||
| flex-direction: column; | |||
| align-items: center; | |||
| width: 92px; | |||
| height: 62px; | |||
| padding: 11px 8px 7px; | |||
| padding: 10px 8px 8px; | |||
| color: @text-color; | |||
| font-size: 12px; | |||
| border: 1px solid rgba(22, 100, 255, 0.05); | |||
| @@ -17,14 +16,6 @@ | |||
| &__active-icon { | |||
| display: none; | |||
| } | |||
| &__name { | |||
| width: 100%; | |||
| margin-top: 4px; | |||
| overflow: hidden; | |||
| white-space: nowrap; | |||
| text-align: center; | |||
| text-overflow: ellipsis; | |||
| } | |||
| &:hover, | |||
| &--active { | |||
| @@ -1,3 +1,4 @@ | |||
| import { Typography } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||
| import styles from './index.less'; | |||
| @@ -32,7 +33,13 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP | |||
| alt="" | |||
| draggable={false} | |||
| /> | |||
| <span className={styles['category-item__name']}>{item.name}</span> | |||
| <Typography.Text | |||
| ellipsis={{ tooltip: item.name }} | |||
| style={{ color: 'inherit', marginTop: '4px' }} | |||
| > | |||
| {item.name} | |||
| </Typography.Text> | |||
| {/* <span className={styles['category-item__name']}>{item.name}</span> */} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -119,6 +119,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| resourceType: resourceType, | |||
| resourceId: resourceId, | |||
| resoureName: info.name, | |||
| owner: info.owner, | |||
| identifier: info.identifier, | |||
| is_public: info.is_public, | |||
| onOk: () => { | |||
| @@ -89,7 +89,7 @@ function ResourceList( | |||
| is_public: isPublic, | |||
| [config.typeParamKey]: dataType, | |||
| [config.tagParamKey]: dataTag, | |||
| name: searchText !== '' ? searchText : undefined, | |||
| name: searchText || undefined, | |||
| }; | |||
| const request = config.getList; | |||
| const [res] = await to(request(params)); | |||
| @@ -115,6 +115,10 @@ function ResourceList( | |||
| // 搜索 | |||
| const handleSearch = (value: string) => { | |||
| setSearchText(value); | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| }; | |||
| // 删除 | |||
| @@ -14,7 +14,8 @@ import { | |||
| getModelList, | |||
| getModelVersionList, | |||
| } from '@/services/dataset/index.js'; | |||
| import type { TabsProps } from 'antd'; | |||
| import { limitUploadFileType } from '@/utils/ui'; | |||
| import type { TabsProps, UploadFile } from 'antd'; | |||
| export enum ResourceType { | |||
| Model = 'Model', // 模型 | |||
| @@ -49,6 +50,7 @@ type ResourceTypeInfo = { | |||
| addBtnTitle: string; // 新增按钮的title | |||
| uploadAction: string; // 上传接口 url | |||
| uploadAccept?: string; // 上传文件类型 | |||
| beforeUpload?: (file: UploadFile) => boolean | string; | |||
| downloadAllAction: string; // 批量下载接口 url | |||
| downloadSingleAction: string; // 单个下载接口 url | |||
| }; | |||
| @@ -87,6 +89,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| addBtnTitle: '新建数据集', | |||
| uploadAction: '/api/mmp/newdataset/upload', | |||
| uploadAccept: '.zip,.tgz', | |||
| beforeUpload: limitUploadFileType('zip,tgz'), | |||
| downloadAllAction: '/api/mmp/newdataset/downloadAllFiles', | |||
| downloadSingleAction: '/api/mmp/newdataset/downloadSingleFile', | |||
| }, | |||
| @@ -87,7 +87,7 @@ function MirrorList() { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| name: searchText, | |||
| name: searchText || undefined, | |||
| image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | |||
| }; | |||
| const [res] = await to(getMirrorListReq(params)); | |||
| @@ -120,6 +120,10 @@ function MirrorList() { | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| setSearchText(value); | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| }; | |||
| // 查看详情 | |||
| @@ -69,7 +69,7 @@ function ModelDeployment() { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| service_name: searchText, | |||
| service_name: searchText || undefined, | |||
| service_type: serviceType, | |||
| }; | |||
| const [res] = await to(getServiceListReq(params)); | |||
| @@ -102,6 +102,10 @@ function ModelDeployment() { | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| setSearchText(value); | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| }; | |||
| // 处理删除 | |||
| @@ -110,7 +110,7 @@ function ServiceInfo() { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| version: searchText, | |||
| version: searchText || undefined, | |||
| run_state: serviceStatus, | |||
| service_id: serviceId, | |||
| }; | |||
| @@ -159,6 +159,10 @@ function ServiceInfo() { | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| setSearchText(value); | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| }; | |||
| // 处理删除 | |||
| @@ -40,7 +40,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||
| const params = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| code_repo_name: searchText !== '' ? searchText : undefined, | |||
| code_repo_name: searchText || undefined, | |||
| }; | |||
| const [res] = await to(getCodeConfigListReq(params)); | |||
| if (res && res.data && res.data.content) { | |||
| @@ -52,6 +52,10 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||
| // 搜索 | |||
| const handleSearch = (value: string) => { | |||
| setSearchText(value); | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| }; | |||
| const handleClick = (item: CodeConfigData) => { | |||
| @@ -44,14 +44,14 @@ const convertMirrorToTreeData = (list: MirrorData[]): TreeDataNode[] => { | |||
| })); | |||
| }; | |||
| // 数据集版本列表转为树形结构 | |||
| const convertDatasetVersionToTreeData = ( | |||
| // 数据集、模型版本列表转为树形结构 | |||
| const convertResourceVersionToTreeData = ( | |||
| parentId: string, | |||
| info: ResourceData, | |||
| list: ResourceVersionData[], | |||
| ): TreeDataNode[] => { | |||
| return list.map((item: ResourceVersionData) => ({ | |||
| ...pick(info, ['id', 'name', 'owner', 'identifier']), | |||
| ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | |||
| version: item.name, | |||
| title: item.name, | |||
| key: `${parentId}-${item.name}`, | |||
| @@ -85,6 +85,7 @@ interface SelectorTypeInfo { | |||
| readonly buttontTitle: string; // 按钮 title | |||
| } | |||
| // 数据集选择 | |||
| export class DatasetSelector implements SelectorTypeInfo { | |||
| readonly name = '数据集'; | |||
| readonly modalIcon = datasetImg; | |||
| @@ -114,14 +115,14 @@ export class DatasetSelector implements SelectorTypeInfo { | |||
| const res = await getDatasetVersionList(pick(parentNode, ['owner', 'identifier'])); | |||
| if (res && res.data) { | |||
| const list = res.data; | |||
| return convertDatasetVersionToTreeData(parentKey, parentNode, list); | |||
| return convertResourceVersionToTreeData(parentKey, parentNode, list); | |||
| } else { | |||
| return Promise.reject('获取数据集版本列表失败'); | |||
| } | |||
| } | |||
| async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { | |||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); | |||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | |||
| const res = await getDatasetInfo(params); | |||
| if (res && res.data) { | |||
| const path = res.data.relative_paths || ''; | |||
| @@ -136,6 +137,7 @@ export class DatasetSelector implements SelectorTypeInfo { | |||
| } | |||
| } | |||
| // 模型选择 | |||
| export class ModelSelector implements SelectorTypeInfo { | |||
| readonly name = '模型'; | |||
| readonly modalIcon = modelImg; | |||
| @@ -165,14 +167,14 @@ export class ModelSelector implements SelectorTypeInfo { | |||
| const res = await getModelVersionList(pick(parentNode, ['owner', 'identifier'])); | |||
| if (res && res.data) { | |||
| const list = res.data; | |||
| return convertDatasetVersionToTreeData(key, parentNode, list); | |||
| return convertResourceVersionToTreeData(key, parentNode, list); | |||
| } else { | |||
| return Promise.reject('获取模型版本列表失败'); | |||
| } | |||
| } | |||
| async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { | |||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); | |||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | |||
| const res = await getModelInfo(params); | |||
| if (res && res.data) { | |||
| const path = res.data.relative_paths || ''; | |||
| @@ -187,6 +189,7 @@ export class ModelSelector implements SelectorTypeInfo { | |||
| } | |||
| } | |||
| // 镜像选择 | |||
| export class MirrorSelector implements SelectorTypeInfo { | |||
| readonly name = '镜像'; | |||
| readonly modalIcon = mirrorImg; | |||
| @@ -221,7 +221,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| allowClear | |||
| rules={[ | |||
| { | |||
| required: false, | |||
| required: true, | |||
| message: <FormattedMessage id="请输入密码!" defaultMessage="请输入密码!" />, | |||
| }, | |||
| ]} | |||
| @@ -8,7 +8,7 @@ import { removeAllPageCacheState } from '@/hooks/pageCacheState'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { type ClientInfo } from '@/types'; | |||
| import { history } from '@umijs/max'; | |||
| import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; | |||
| import { Modal, Upload, message, type ModalFuncProps, type UploadFile } from 'antd'; | |||
| import { closeAllModals } from './modal'; | |||
| import SessionStorage from './sessionStorage'; | |||
| @@ -52,23 +52,6 @@ export function modalConfirm({ | |||
| }); | |||
| } | |||
| // 从事件中获取上传文件列表,用于 Upload + Form 中 | |||
| export const getFileListFromEvent = (e: any) => { | |||
| const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || []; | |||
| return fileList.map((item) => { | |||
| if (item.status === 'done') { | |||
| const { response } = item; | |||
| if (response?.code !== 200) { | |||
| return { | |||
| ...item, | |||
| status: 'error', | |||
| }; | |||
| } | |||
| } | |||
| return item; | |||
| }); | |||
| }; | |||
| /** | |||
| * 跳转到登录页 | |||
| * @param toHome 是否跳转到首页 | |||
| @@ -97,6 +80,23 @@ export const gotoOAuth2 = () => { | |||
| } | |||
| }; | |||
| // 从事件中获取上传文件列表,用于 Upload + Form 中 | |||
| export const getFileListFromEvent = (e: any) => { | |||
| const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || []; | |||
| return fileList.map((item) => { | |||
| if (item.status === 'done') { | |||
| const { response } = item; | |||
| if (response?.code !== 200) { | |||
| return { | |||
| ...item, | |||
| status: 'error', | |||
| }; | |||
| } | |||
| } | |||
| return item; | |||
| }); | |||
| }; | |||
| /** | |||
| * 验证文件上传 | |||
| * | |||
| @@ -128,6 +128,20 @@ export const validateUploadFiles = (files: UploadFile[], required: boolean = tru | |||
| return !hasError; | |||
| }; | |||
| // 限制上传文件类型 | |||
| export const limitUploadFileType = (type: string) => { | |||
| return (file: UploadFile): boolean | string => { | |||
| const acceptTypes = type.split(',').map((item) => item.trim()); | |||
| const fileType = file.name.split('.').pop()?.trim(); | |||
| if (!(fileType && acceptTypes.includes(fileType))) { | |||
| message.error(`文件类型不正确,只支持 "${acceptTypes.join('、')}" 类型的文件`); | |||
| file.status = 'error'; | |||
| return Upload.LIST_IGNORE; | |||
| } | |||
| return true; | |||
| }; | |||
| }; | |||
| /** | |||
| * 滚动到底部 | |||
| * | |||