| @@ -19,7 +19,7 @@ const Settings: ProLayoutProps & { | |||||
| title: '智能软件开发平台', | title: '智能软件开发平台', | ||||
| pwa: true, | pwa: true, | ||||
| logo: '/assets/images/left-top-logo.png', | logo: '/assets/images/left-top-logo.png', | ||||
| iconfontUrl: '//at.alicdn.com/t/c/font_4511326_1cmi0j3dj1x.js', | |||||
| iconfontUrl: '//at.alicdn.com/t/c/font_4511326_a182r7rksx5.js', | |||||
| token: { | token: { | ||||
| // 参见ts声明,demo 见文档,通过token 修改样式 | // 参见ts声明,demo 见文档,通过token 修改样式 | ||||
| //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F | //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F | ||||
| @@ -131,7 +131,7 @@ export default [ | |||||
| { | { | ||||
| name: '数据集简介', | name: '数据集简介', | ||||
| path: ':id', | path: ':id', | ||||
| component: './Dataset/datasetIntro', | |||||
| component: './Dataset/intro', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -147,7 +147,7 @@ export default [ | |||||
| { | { | ||||
| name: '模型简介', | name: '模型简介', | ||||
| path: ':id', | path: ':id', | ||||
| component: './Model/modelIntro', | |||||
| component: './Model/intro', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -188,14 +188,23 @@ export default [ | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| name: 'modelDseployment', | |||||
| path: '/modelDseployment', | |||||
| name: 'modelDeployment', | |||||
| path: '/modelDeployment', | |||||
| routes: [ | routes: [ | ||||
| { | { | ||||
| name: '模型部署', | |||||
| name: '模型列表', | |||||
| path: '', | path: '', | ||||
| key: 'modelDseployment', | |||||
| component: './missingPage.jsx', | |||||
| component: './ModelDeployment/list', | |||||
| }, | |||||
| { | |||||
| name: '镜像详情', | |||||
| path: ':id', | |||||
| component: './ModelDeployment/info', | |||||
| }, | |||||
| { | |||||
| name: '创建镜像', | |||||
| path: 'create', | |||||
| component: './ModelDeployment/create', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -17,9 +17,10 @@ import { | |||||
| patchRouteWithRemoteMenus, | patchRouteWithRemoteMenus, | ||||
| setRemoteMenu, | setRemoteMenu, | ||||
| } from './services/session'; | } from './services/session'; | ||||
| import './styles/menu.less'; | |||||
| export { requestConfig as request } from './requestConfig'; | export { requestConfig as request } from './requestConfig'; | ||||
| // const isDev = process.env.NODE_ENV === 'development'; | // const isDev = process.env.NODE_ENV === 'development'; | ||||
| import { menuItemRender } from '@/utils/menuRender'; | |||||
| /** | /** | ||||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | ||||
| * */ | * */ | ||||
| @@ -139,10 +140,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| onClick: () => { | onClick: () => { | ||||
| // 点击菜单项,删除所有的页面 state 缓存 | // 点击菜单项,删除所有的页面 state 缓存 | ||||
| removeAllPageCacheState(); | removeAllPageCacheState(); | ||||
| // console.log('click menu'); | |||||
| }, | }, | ||||
| // onSelect: (e) => { | |||||
| // console.log(e); | |||||
| // }, | |||||
| }, | }, | ||||
| ...initialState?.settings, | ...initialState?.settings, | ||||
| token: { | token: { | ||||
| @@ -150,60 +149,49 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| colorTextMenu: themes['textColor'], | colorTextMenu: themes['textColor'], | ||||
| colorTextMenuSelected: themes['primaryColor'], | colorTextMenuSelected: themes['primaryColor'], | ||||
| colorTextMenuActive: themes['primaryColor'], | colorTextMenuActive: themes['primaryColor'], | ||||
| colorTextMenuItemHover: themes['primaryColor'], | |||||
| colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)', | colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)', | ||||
| colorMenuBackground: themes['siderBGColor'], | colorMenuBackground: themes['siderBGColor'], | ||||
| }, | }, | ||||
| }, | }, | ||||
| // menuItemRender: (itemProps, defaultDom, props) => { | |||||
| // console.log('menuItemProps', itemProps); | |||||
| // console.log('defaultDom', defaultDom); | |||||
| // console.log('props', props); | |||||
| // const { pathname } = window.location; | |||||
| // const isSelected = pathname === itemProps.path; | |||||
| // // 根据菜单项的状态动态显示不同的 icon | |||||
| // const icon = isSelected ? 'icon-developmentEnvironment-icon' : 'icon-kaifahuanjing'; | |||||
| // return ( | |||||
| // <Link to={itemProps.path || ''} target={itemProps.target}> | |||||
| // <KFIcon type={icon} /> | |||||
| // {itemProps.name} | |||||
| // </Link> | |||||
| // ); | |||||
| // }, | |||||
| menuItemRender: menuItemRender(false), | |||||
| subMenuItemRender: menuItemRender(true), | |||||
| }; | }; | ||||
| }; | }; | ||||
| export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | ||||
| const { location } = e; | const { location } = e; | ||||
| const menus = getRemoteMenu(); | const menus = getRemoteMenu(); | ||||
| console.log('onRouteChange', e); | |||||
| // console.log('onRouteChange', e); | |||||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | if (menus === null && location.pathname !== PageEnum.LOGIN) { | ||||
| console.log('refresh'); | |||||
| history.go(0); | history.go(0); | ||||
| } | } | ||||
| }; | }; | ||||
| export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { | export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { | ||||
| console.log('patchRoutes', e); | |||||
| //console.log('patchRoutes', e); | |||||
| }; | }; | ||||
| export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | ||||
| console.log('patchClientRoutes', e); | |||||
| //console.log('patchClientRoutes', e); | |||||
| patchRouteWithRemoteMenus(e.routes); | patchRouteWithRemoteMenus(e.routes); | ||||
| }; | }; | ||||
| 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 || token?.length === 0) { | ||||
| oldRender(); | oldRender(); | ||||
| return; | return; | ||||
| } | } | ||||
| getRoutersInfo().then((res) => { | |||||
| setRemoteMenu(res); | |||||
| oldRender(); | |||||
| }); | |||||
| getRoutersInfo() | |||||
| .then((res) => { | |||||
| setRemoteMenu(res); | |||||
| oldRender(); | |||||
| }) | |||||
| .catch(() => { | |||||
| oldRender(); | |||||
| }); | |||||
| } | } | ||||
| // 主题修改 | // 主题修改 | ||||
| @@ -215,6 +203,7 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| colorError: themes['errorColor'], | colorError: themes['errorColor'], | ||||
| colorWarning: themes['warningColor'], | colorWarning: themes['warningColor'], | ||||
| colorLink: themes['primaryColor'], | colorLink: themes['primaryColor'], | ||||
| colorText: themes['textColor'], | |||||
| }; | }; | ||||
| memo.theme.components ??= {}; | memo.theme.components ??= {}; | ||||
| memo.theme.components.Tabs = {}; | memo.theme.components.Tabs = {}; | ||||
| @@ -229,10 +218,11 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', | defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', | ||||
| defaultActiveColor: themes['primaryColor'], | defaultActiveColor: themes['primaryColor'], | ||||
| contentFontSize: parseInt(themes['fontSize']), | contentFontSize: parseInt(themes['fontSize']), | ||||
| controlHeight: 34, | |||||
| }; | }; | ||||
| memo.theme.components.Input = { | memo.theme.components.Input = { | ||||
| inputFontSize: parseInt(themes['fontSize']), | |||||
| inputFontSize: parseInt(themes['fontSizeInput']), | |||||
| inputFontSizeLG: parseInt(themes['fontSizeInputLg']), | |||||
| paddingBlockLG: 10, | |||||
| }; | }; | ||||
| memo.theme.components.Table = { | memo.theme.components.Table = { | ||||
| headerBg: 'rgba(242, 244, 247, 0.36)', | headerBg: 'rgba(242, 244, 247, 0.36)', | ||||
| @@ -241,6 +231,11 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| memo.theme.components.Tabs = { | memo.theme.components.Tabs = { | ||||
| titleFontSize: 16, | titleFontSize: 16, | ||||
| }; | }; | ||||
| memo.theme.components.Form = { | |||||
| labelColor: 'rgba(29, 29, 32, 0.8);', | |||||
| }; | |||||
| memo.theme.cssVar = true; | memo.theme.cssVar = true; | ||||
| // memo.theme.hashed = false; | // memo.theme.hashed = false; | ||||
| @@ -4,8 +4,18 @@ | |||||
| * @Description: 自定义 Table 单元格,没有数据时展示 -- | * @Description: 自定义 Table 单元格,没有数据时展示 -- | ||||
| */ | */ | ||||
| function CommonTableCell(text?: string | null) { | |||||
| import { Tooltip } from 'antd'; | |||||
| function renderCell(text?: string | null) { | |||||
| return <span>{text ?? '--'}</span>; | return <span>{text ?? '--'}</span>; | ||||
| } | } | ||||
| function CommonTableCell(ellipsis: boolean = false) { | |||||
| if (ellipsis) { | |||||
| return (text?: string | null) => <Tooltip title={text}>{renderCell(text)}</Tooltip>; | |||||
| } else { | |||||
| return renderCell; | |||||
| } | |||||
| } | |||||
| export default CommonTableCell; | export default CommonTableCell; | ||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: 自定义 Table 日期类单元格 | * @Description: 自定义 Table 日期类单元格 | ||||
| */ | */ | ||||
| import { formatDate } from '@/utils/date'; | |||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| function DateTableCell(text?: string | null) { | function DateTableCell(text?: string | null) { | ||||
| @@ -13,7 +14,7 @@ function DateTableCell(text?: string | null) { | |||||
| if (!dayjs(text).isValid()) { | if (!dayjs(text).isValid()) { | ||||
| return <span>无效的日期</span>; | return <span>无效的日期</span>; | ||||
| } | } | ||||
| return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>; | |||||
| return <span>{formatDate(text)}</span>; | |||||
| } | } | ||||
| export default DateTableCell; | export default DateTableCell; | ||||
| @@ -23,8 +23,6 @@ | |||||
| border-radius: 10px; | border-radius: 10px; | ||||
| } | } | ||||
| .ant-btn-default { | .ant-btn-default { | ||||
| color: @text-color; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border-color: transparent; | border-color: transparent; | ||||
| } | } | ||||
| .ant-btn + .ant-btn { | .ant-btn + .ant-btn { | ||||
| @@ -12,11 +12,12 @@ import './index.less'; | |||||
| export interface KFModalProps extends ModalProps { | export interface KFModalProps extends ModalProps { | ||||
| image?: string; | image?: string; | ||||
| } | } | ||||
| function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) { | |||||
| function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) { | |||||
| return ( | return ( | ||||
| <Modal | <Modal | ||||
| className={classNames(['kf-modal', className])} | className={classNames(['kf-modal', className])} | ||||
| {...rest} | {...rest} | ||||
| centered={centered ?? true} | |||||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | title={<ModalTitle title={title} image={image}></ModalTitle>} | ||||
| > | > | ||||
| {children} | {children} | ||||
| @@ -23,6 +23,11 @@ | |||||
| &--active { | &--active { | ||||
| color: @primary-color; | color: @primary-color; | ||||
| border: 1px solid @primary-color; | border: 1px solid @primary-color; | ||||
| &:hover { | |||||
| color: @primary-color; | |||||
| border: 1px solid @primary-color; | |||||
| } | |||||
| } | } | ||||
| & + & { | & + & { | ||||
| @@ -3,5 +3,5 @@ | |||||
| align-items: center; | align-items: center; | ||||
| height: 50px; | height: 50px; | ||||
| padding-left: 30px; | padding-left: 30px; | ||||
| background-image: url('../../assets/img/page-title-bg.png'); | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| } | } | ||||
| @@ -2,6 +2,7 @@ import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||||
| import { useModel } from '@umijs/max'; | import { useModel } from '@umijs/max'; | ||||
| import React from 'react'; | import React from 'react'; | ||||
| import Avatar from './AvatarDropdown'; | import Avatar from './AvatarDropdown'; | ||||
| // import { SelectLang } from '@umijs/max'; | |||||
| export type SiderTheme = 'light' | 'dark'; | export type SiderTheme = 'light' | 'dark'; | ||||
| @@ -126,3 +126,13 @@ export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => { | |||||
| } | } | ||||
| }, [form, prevOpen, open]); | }, [form, prevOpen, open]); | ||||
| }; | }; | ||||
| export const useInputModel = <T>(initialValue: T) => { | |||||
| const [value, setValue] = useState<T>(initialValue); | |||||
| const updateValue = useCallback((e: any) => { | |||||
| setValue(e.target?.value); | |||||
| }, []); | |||||
| return [value, updateValue]; | |||||
| }; | |||||
| @@ -57,11 +57,6 @@ | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| } | } | ||||
| // Input | |||||
| .ant-input-textarea-affix-wrapper.ant-input-affix-wrapper { | |||||
| padding: 0; | |||||
| } | |||||
| // Modal | // Modal | ||||
| .ant-modal { | .ant-modal { | ||||
| .ant-modal-close { | .ant-modal-close { | ||||
| @@ -81,18 +76,24 @@ | |||||
| } | } | ||||
| } | } | ||||
| .ant-form-item .ant-form-item-label > label { | |||||
| font-size: @font-size; | |||||
| } | |||||
| // 输入框高度为46px | |||||
| .ant-input-affix-wrapper { | .ant-input-affix-wrapper { | ||||
| height: 46px; | |||||
| padding: 1px 11px; | |||||
| padding-top: 2px; | |||||
| padding-bottom: 2px; | |||||
| .ant-input { | |||||
| height: 40px; | |||||
| } | |||||
| } | } | ||||
| // 选择框高度为46px | |||||
| .ant-select-single { | .ant-select-single { | ||||
| height: 46px; | height: 46px; | ||||
| } | } | ||||
| .ant-select-single .ant-select-selector .ant-select-selection-placeholder { | |||||
| line-height: 46px; | |||||
| } | |||||
| } | } | ||||
| // Confirm Modal | // Confirm Modal | ||||
| @@ -128,8 +129,6 @@ | |||||
| border-radius: 10px; | border-radius: 10px; | ||||
| } | } | ||||
| .ant-btn-default { | .ant-btn-default { | ||||
| color: @text-color; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border-color: transparent; | border-color: transparent; | ||||
| } | } | ||||
| .ant-btn + .ant-btn { | .ant-btn + .ant-btn { | ||||
| @@ -137,3 +136,20 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| // 表单类型为large时,font-size为15px | |||||
| .ant-form-large { | |||||
| .ant-form-item-label { | |||||
| label { | |||||
| font-size: @font-size; | |||||
| } | |||||
| } | |||||
| } | |||||
| // 取消 hover 颜色变化 | |||||
| .ant-menu .ant-menu-title-content { | |||||
| transition: color 0s; | |||||
| a { | |||||
| transition: color 0s; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| .upload-tip { | |||||
| margin-top: 5px; | |||||
| color: @error-color; | |||||
| } | |||||
| .upload-button { | |||||
| height: 46px; | |||||
| font-size: 15px; | |||||
| } | |||||
| @@ -0,0 +1,198 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { addDatesetAndVesion } from '@/services/dataset/index.js'; | |||||
| import { getDictSelectOption } from '@/services/system/dict'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||||
| import { | |||||
| Button, | |||||
| Form, | |||||
| Input, | |||||
| Radio, | |||||
| Select, | |||||
| Upload, | |||||
| UploadFile, | |||||
| message, | |||||
| type ModalProps, | |||||
| type UploadProps, | |||||
| } from 'antd'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { CategoryData } from '../../type'; | |||||
| import styles from './index.less'; | |||||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| typeList: CategoryData[]; | |||||
| tagList: CategoryData[]; | |||||
| onOk: () => void; | |||||
| } | |||||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | |||||
| const [uuid] = useState(Date.now()); | |||||
| const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||||
| useEffect(() => { | |||||
| getClusterOptions(); | |||||
| }, []); | |||||
| // 上传组件参数 | |||||
| const uploadProps: UploadProps = { | |||||
| action: '/api/mmp/dataset/upload', | |||||
| headers: { | |||||
| Authorization: getAccessToken() || '', | |||||
| }, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| // 获取集群版本数据 | |||||
| const getClusterOptions = async () => { | |||||
| const [res] = await to(getDictSelectOption('available_cluster')); | |||||
| if (res) { | |||||
| setClusterOptions(res); | |||||
| } | |||||
| }; | |||||
| // 上传请求 | |||||
| const createDataset = async (params: any) => { | |||||
| const [res] = await to(addDatesetAndVesion(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: any) => { | |||||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | |||||
| if (validateUploadFiles(fileList)) { | |||||
| const params = { | |||||
| ...omit(formData, ['fileList']), | |||||
| dataset_version_vos: fileList.map((item) => { | |||||
| const data = item.response?.data?.[0] ?? {}; | |||||
| return { | |||||
| file_name: data.fileName, | |||||
| file_size: data.fileSize, | |||||
| url: data.url, | |||||
| }; | |||||
| }), | |||||
| }; | |||||
| createDataset(params); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="新建数据集" | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| destroyOnClose | |||||
| > | |||||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||||
| <Form.Item | |||||
| label="数据集名称" | |||||
| name="name" | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据集名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集版本" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据集版本', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item label="数据集分类" name="data_type"> | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择数据集分类" | |||||
| options={typeList} | |||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label="研究方向/应用领域" name="data_tag"> | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择研究方向/应用领域" | |||||
| options={tagList} | |||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label="集群版本" name="available_cluster"> | |||||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集简介" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据集简介', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入数据集简介" | |||||
| showCount | |||||
| maxLength={256} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label="选择流水线" name="range"> | |||||
| <Radio.Group> | |||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集文件" | |||||
| name="fileList" | |||||
| valuePropName="fileList" | |||||
| getValueFromEvent={getFileListFromEvent} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请上传数据集文件', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Upload {...uploadProps} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||||
| <Button | |||||
| className={styles['upload-button']} | |||||
| type="default" | |||||
| icon={<KFIcon type="icon-shangchuan" />} | |||||
| > | |||||
| 上传文件 | |||||
| </Button> | |||||
| <div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div> | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default AddDatasetModal; | |||||
| @@ -0,0 +1,176 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { CategoryData } from '@/pages/Dataset/type'; | |||||
| import { addModel } from '@/services/dataset/index.js'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||||
| import { | |||||
| Button, | |||||
| Form, | |||||
| Input, | |||||
| Select, | |||||
| Upload, | |||||
| UploadFile, | |||||
| message, | |||||
| type ModalProps, | |||||
| type UploadProps, | |||||
| } from 'antd'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useState } from 'react'; | |||||
| import styles from '../AddDatasetModal/index.less'; | |||||
| interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| typeList: CategoryData[]; | |||||
| tagList: CategoryData[]; | |||||
| onOk: () => void; | |||||
| } | |||||
| function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | |||||
| const [uuid] = useState(Date.now()); | |||||
| // 上传组件参数 | |||||
| const uploadProps: UploadProps = { | |||||
| action: '/api/mmp/models/upload', | |||||
| headers: { | |||||
| Authorization: getAccessToken() || '', | |||||
| }, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| // 上传请求 | |||||
| const createModel = async (params: any) => { | |||||
| const [res] = await to(addModel(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: any) => { | |||||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | |||||
| if (validateUploadFiles(fileList)) { | |||||
| const params = { | |||||
| ...omit(formData, ['fileList']), | |||||
| models_version_vos: fileList.map((item) => { | |||||
| const data = item.response?.data?.[0] ?? {}; | |||||
| return { | |||||
| file_name: data.fileName, | |||||
| file_size: data.fileSize, | |||||
| url: data.url, | |||||
| }; | |||||
| }), | |||||
| }; | |||||
| createModel(params); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="新建模型" | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| > | |||||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||||
| <Form.Item | |||||
| label="模型名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型名称!', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型版本" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型版本', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型版本" allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型简介" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型简介', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入模型简介" | |||||
| showCount | |||||
| maxLength={256} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| {/* <Form.Item label="可见范围" name="available_range"> | |||||
| <Radio.Group> | |||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> */} | |||||
| <Form.Item label="模型框架" name="model_type"> | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择模型类型" | |||||
| options={typeList} | |||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label="模型能力" name="model_tag"> | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择模型标签" | |||||
| options={tagList} | |||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型文件" | |||||
| name="fileList" | |||||
| valuePropName="fileList" | |||||
| getValueFromEvent={getFileListFromEvent} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请上传模型文件', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Upload {...uploadProps} data={{ uuid: uuid }}> | |||||
| <Button | |||||
| className={styles['upload-button']} | |||||
| type="default" | |||||
| icon={<KFIcon type="icon-shangchuan" />} | |||||
| > | |||||
| 上传文件 | |||||
| </Button> | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default AddModelModal; | |||||
| @@ -0,0 +1,169 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/type'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||||
| import { | |||||
| Button, | |||||
| Form, | |||||
| Input, | |||||
| Upload, | |||||
| UploadFile, | |||||
| message, | |||||
| type ModalProps, | |||||
| type UploadProps, | |||||
| } from 'antd'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useState } from 'react'; | |||||
| import styles from '../AddDatasetModal/index.less'; | |||||
| interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| resourceType: ResourceType; | |||||
| resourceId: number; | |||||
| initialName: string; | |||||
| onOk: () => void; | |||||
| } | |||||
| function AddVersionModal({ | |||||
| resourceType, | |||||
| resourceId, | |||||
| initialName, | |||||
| onOk, | |||||
| ...rest | |||||
| }: AddVersionModalProps) { | |||||
| const [uuid] = useState(Date.now()); | |||||
| // 上传组件参数 | |||||
| const uploadProps: UploadProps = { | |||||
| action: resourceConfig[resourceType].uploadAction, | |||||
| headers: { | |||||
| Authorization: getAccessToken() || '', | |||||
| }, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| // 上传请求 | |||||
| const createDatasetVersion = async (params: any) => { | |||||
| const request = resourceConfig[resourceType].addVersionReq; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: any) => { | |||||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | |||||
| if (validateUploadFiles(fileList)) { | |||||
| const otherParams = omit(formData, ['fileList']); | |||||
| const params = fileList.map((item) => { | |||||
| const data = item.response?.data?.[0] ?? {}; | |||||
| return { | |||||
| ...otherParams, | |||||
| [resourceConfig[resourceType].idParamKey]: resourceId, | |||||
| file_name: data.fileName, | |||||
| file_size: data.fileSize, | |||||
| url: data.url, | |||||
| }; | |||||
| }); | |||||
| createDatasetVersion(params); | |||||
| } | |||||
| }; | |||||
| const name = resourceConfig[resourceType].name; | |||||
| const accept = resourceConfig[resourceType].uploadAccept; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="创建新版本" | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| initialValues={{ | |||||
| name: initialName, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label={`${name}名称`} | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: `请输入${name}名称`, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled placeholder={`请输入${name}名称`} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label={`${name}版本`} | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: `请输入${name}版本`, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={256} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label={`${name}文件`} | |||||
| name="fileList" | |||||
| valuePropName="fileList" | |||||
| getValueFromEvent={getFileListFromEvent} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: `请上传${name}文件`, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Upload {...uploadProps} data={{ uuid: uuid }} accept={accept}> | |||||
| <Button | |||||
| className={styles['upload-button']} | |||||
| type="default" | |||||
| icon={<KFIcon type="icon-shangchuan" />} | |||||
| > | |||||
| 上传文件 | |||||
| </Button> | |||||
| {resourceType === ResourceType.Dataset && ( | |||||
| <div className={styles['upload-tip']}>只允许上传.zip格式文件</div> | |||||
| )} | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default AddVersionModal; | |||||
| @@ -0,0 +1,41 @@ | |||||
| .category-item { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| width: 92px; | |||||
| height: 62px; | |||||
| padding: 11px 8px 7px; | |||||
| color: @text-color; | |||||
| font-size: 12px; | |||||
| border: 1px solid rgba(22, 100, 255, 0.05); | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| &__icon { | |||||
| display: block; | |||||
| } | |||||
| &__active-icon { | |||||
| display: none; | |||||
| } | |||||
| &__name { | |||||
| width: 100%; | |||||
| margin-top: 4px; | |||||
| overflow: hidden; | |||||
| white-space: nowrap; | |||||
| text-align: center; | |||||
| text-overflow: ellipsis; | |||||
| } | |||||
| &:hover, | |||||
| &--active { | |||||
| background: rgba(22, 100, 255, 0.03); | |||||
| border: 1px solid @primary-color; | |||||
| .category-item__icon { | |||||
| display: none; | |||||
| } | |||||
| .category-item__active-icon { | |||||
| display: block; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| import classNames from 'classnames'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||||
| import styles from './index.less'; | |||||
| type CategoryItemProps = { | |||||
| resourceType: ResourceType; | |||||
| item: CategoryData; | |||||
| isSelected: boolean; | |||||
| onClick: (item: CategoryData) => void; | |||||
| }; | |||||
| function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) { | |||||
| return ( | |||||
| <div | |||||
| className={classNames(styles['category-item'], { | |||||
| [styles['category-item--active']]: isSelected, | |||||
| })} | |||||
| onClick={() => onClick(item)} | |||||
| > | |||||
| <img | |||||
| className={styles['category-item__icon']} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={styles['category-item__active-icon']} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={styles['category-item__name']}>{item.name}</span> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CategoryItem; | |||||
| @@ -0,0 +1,22 @@ | |||||
| .category-list { | |||||
| width: 340px; | |||||
| height: 100%; | |||||
| margin-right: 10px; | |||||
| padding: 15px 0; | |||||
| background: white; | |||||
| border-radius: 4px; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__content { | |||||
| height: calc(100% - 32px - 15px); | |||||
| margin-top: 15px; | |||||
| padding-left: 20px; | |||||
| overflow-y: auto; | |||||
| &__title { | |||||
| margin-bottom: 15px; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,71 @@ | |||||
| import { Flex, Input } from 'antd'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||||
| import CategoryItem from '../CategoryItem'; | |||||
| import styles from './index.less'; | |||||
| export type CategoryValue = { | |||||
| dataType: number | undefined; | |||||
| dataTag: number | undefined; | |||||
| }; | |||||
| type CategoryProps = { | |||||
| resourceType: ResourceType; // 资源类型,数据集还是模型 | |||||
| typeList: CategoryData[]; | |||||
| tagList: CategoryData[]; | |||||
| activeType?: number; | |||||
| activeTag?: number; | |||||
| onTypeSelect: (value: CategoryData) => void; | |||||
| onTagSelect: (value: CategoryData) => void; | |||||
| onSearch: (value: string) => void; | |||||
| }; | |||||
| function CategoryList({ | |||||
| resourceType, | |||||
| typeList, | |||||
| tagList, | |||||
| activeType, | |||||
| activeTag, | |||||
| onTypeSelect, | |||||
| onTagSelect, | |||||
| onSearch, | |||||
| }: CategoryProps) { | |||||
| return ( | |||||
| <div className={styles['category-list']}> | |||||
| <div style={{ padding: '0 20px' }}> | |||||
| <Input.Search placeholder="搜索" allowClear onSearch={onSearch} /> | |||||
| </div> | |||||
| <div className={styles['category-list__content']}> | |||||
| <div className={styles['category-list__content__title']}> | |||||
| {resourceConfig[resourceType].typeTitle} | |||||
| </div> | |||||
| <Flex wrap="wrap" gap="20px 12px"> | |||||
| {typeList?.map((item) => ( | |||||
| <CategoryItem | |||||
| key={item.id} | |||||
| resourceType={resourceType} | |||||
| item={item} | |||||
| onClick={onTypeSelect} | |||||
| isSelected={item.id === activeType} | |||||
| ></CategoryItem> | |||||
| ))} | |||||
| </Flex> | |||||
| <div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}> | |||||
| {resourceConfig[resourceType].tagTitle} | |||||
| </div> | |||||
| <Flex wrap="wrap" gap="20px 12px"> | |||||
| {tagList?.map((item) => ( | |||||
| <CategoryItem | |||||
| key={item.id} | |||||
| resourceType={resourceType} | |||||
| item={item} | |||||
| onClick={onTagSelect} | |||||
| isSelected={item.id === activeTag} | |||||
| ></CategoryItem> | |||||
| ))} | |||||
| </Flex> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CategoryList; | |||||
| @@ -0,0 +1,39 @@ | |||||
| .resource-list { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| height: 100%; | |||||
| padding: 20px 0; | |||||
| background: white; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__header { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin-bottom: 30px; | |||||
| padding: 0 30px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &__content { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-wrap: wrap; | |||||
| gap: 20px; | |||||
| align-content: flex-start; | |||||
| width: 100%; | |||||
| margin-bottom: 30px; | |||||
| padding: 0 30px; | |||||
| overflow-y: auto; | |||||
| } | |||||
| :global { | |||||
| .ant-pagination { | |||||
| margin-right: 30px; | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,210 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import AddModelModal from '@/pages/Dataset/components/AddModelModal'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { Button, Input, Pagination, PaginationProps, message } from 'antd'; | |||||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | |||||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../type'; | |||||
| import AddDatasetModal from '../AddDatasetModal'; | |||||
| import ResourceItem from '../Resourcetem'; | |||||
| import styles from './index.less'; | |||||
| export type ResourceListRef = { | |||||
| reset: () => void; | |||||
| }; | |||||
| type ResourceListProps = { | |||||
| resourceType: ResourceType; | |||||
| dataType?: number; | |||||
| dataTag?: number; | |||||
| isPublic: boolean; | |||||
| typeList: CategoryData[]; | |||||
| tagList: CategoryData[]; | |||||
| initialSearchText?: string; | |||||
| initialPagination?: PaginationProps; | |||||
| setCacheState: (state?: any) => void; | |||||
| }; | |||||
| function ResourceList( | |||||
| { | |||||
| resourceType, | |||||
| dataType, | |||||
| dataTag, | |||||
| typeList, | |||||
| tagList, | |||||
| isPublic, | |||||
| initialSearchText, | |||||
| initialPagination, | |||||
| setCacheState, | |||||
| }: ResourceListProps, | |||||
| ref: Ref<ResourceListRef>, | |||||
| ) { | |||||
| const navigate = useNavigate(); | |||||
| const [dataList, setDataList] = useState<ResourceData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<PaginationProps>( | |||||
| initialPagination ?? { | |||||
| current: 1, | |||||
| pageSize: 20, | |||||
| }, | |||||
| ); | |||||
| const [searchText, setSearchText] = useState(initialSearchText); | |||||
| const [inputText, setInputText] = useState(initialSearchText); | |||||
| useEffect(() => { | |||||
| getDataList(); | |||||
| }, [resourceType, dataType, dataTag, pagination, searchText, isPublic]); | |||||
| useImperativeHandle( | |||||
| ref, | |||||
| () => { | |||||
| return { | |||||
| reset: () => { | |||||
| setPagination({ | |||||
| current: 1, | |||||
| pageSize: 20, | |||||
| }); | |||||
| setSearchText(''); | |||||
| setInputText(''); | |||||
| setDataList([]); | |||||
| }, | |||||
| }; | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| // 获取数据请求 | |||||
| const getDataList = async () => { | |||||
| const params = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| [resourceConfig[resourceType].typeParamKey]: dataType, | |||||
| [resourceConfig[resourceType].tagParamKey]: dataTag, | |||||
| available_range: isPublic ? 1 : 0, | |||||
| name: searchText !== '' ? searchText : undefined, | |||||
| }; | |||||
| const request = resourceConfig[resourceType].getList; | |||||
| const [res] = await to(request(params)); | |||||
| if (res && res.data && res.data.content) { | |||||
| setDataList(res.data.content); | |||||
| setTotal(res.data.totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除请求 | |||||
| const deleteRecord = async (id: number) => { | |||||
| const request = resourceConfig[resourceType].deleteRecord; | |||||
| const [res] = await to(request(id)); | |||||
| if (res) { | |||||
| getDataList(); | |||||
| message.success('删除成功'); | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const handleSearch = (value: string) => { | |||||
| setSearchText(value); | |||||
| }; | |||||
| // 删除 | |||||
| const handleRemove = (record: ResourceData) => { | |||||
| modalConfirm({ | |||||
| title: resourceConfig[resourceType].deleteModalTitle, | |||||
| onOk: () => { | |||||
| deleteRecord(record.id); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 跳转 | |||||
| const handleClick = (record: ResourceData) => { | |||||
| setCacheState({ | |||||
| activeTab: isPublic ? CommonTabKeys.Public : CommonTabKeys.Private, | |||||
| pagination, | |||||
| searchText, | |||||
| activeType: dataType, | |||||
| activeTag: dataTag, | |||||
| }); | |||||
| if (resourceType === ResourceType.Dataset) { | |||||
| navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`); | |||||
| } else { | |||||
| navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`); | |||||
| } | |||||
| }; | |||||
| // 分页切换 | |||||
| const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => { | |||||
| setPagination({ | |||||
| current: page, | |||||
| pageSize: pageSize, | |||||
| }); | |||||
| }; | |||||
| // 新建弹框 | |||||
| const showModal = () => { | |||||
| const Modal = resourceType === ResourceType.Dataset ? AddDatasetModal : AddModelModal; | |||||
| const { close } = openAntdModal(Modal, { | |||||
| typeList: typeList, | |||||
| tagList: tagList, | |||||
| onOk: () => { | |||||
| getDataList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['resource-list']}> | |||||
| <div className={styles['resource-list__header']}> | |||||
| <span>数据总数:{total}个</span> | |||||
| <div> | |||||
| <Input.Search | |||||
| placeholder="按数据名称筛选" | |||||
| allowClear | |||||
| onSearch={handleSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| }} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| value={inputText} | |||||
| /> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginLeft: '20px' }} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| {resourceConfig[resourceType].addBtnTitle} | |||||
| </Button> | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['resource-list__content']}> | |||||
| {dataList?.map((item) => ( | |||||
| <ResourceItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| isPublic={isPublic} | |||||
| onRemove={handleRemove} | |||||
| onClick={handleClick} | |||||
| ></ResourceItem> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| /> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default forwardRef(ResourceList); | |||||
| @@ -0,0 +1,8 @@ | |||||
| .resource-page { | |||||
| height: 100%; | |||||
| &__tabs-container { | |||||
| height: 50px; | |||||
| padding-left: 27px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,113 @@ | |||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { getAssetIcon } from '@/services/dataset/index.js'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Flex, Tabs, type TabsProps } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||||
| import CategoryList from '../CategoryList'; | |||||
| import ResourceList, { ResourceListRef } from '../ResourceList'; | |||||
| import styles from './index.less'; | |||||
| type ResourcePageProps = { | |||||
| resourceType: ResourceType; // 资源类型,数据集还是模型 | |||||
| }; | |||||
| function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | |||||
| const [typeList, setTypeList] = useState<CategoryData[]>([]); | |||||
| const [tagList, setTagList] = useState<CategoryData[]>([]); | |||||
| const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType); | |||||
| const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag); | |||||
| const dataListRef = useRef<ResourceListRef>(null); | |||||
| useEffect(() => { | |||||
| getAssetIconList(); | |||||
| }, []); | |||||
| // 分类搜索 | |||||
| const handleCategorySearch = (value: string) => { | |||||
| getAssetIconList(value); | |||||
| }; | |||||
| // 选择类型 | |||||
| const chooseType = (record: CategoryData) => { | |||||
| setActiveType((prev) => (prev === record.id ? undefined : record.id)); | |||||
| }; | |||||
| // 选择 Tag | |||||
| const chooseTag = (record: CategoryData) => { | |||||
| setActiveTag((prev) => (prev === record.id ? undefined : record.id)); | |||||
| }; | |||||
| // 获取分类 | |||||
| const getAssetIconList = async (name: string = '') => { | |||||
| const params = { | |||||
| name: name, | |||||
| page: 0, | |||||
| size: 10000, | |||||
| }; | |||||
| const [res] = await to(getAssetIcon(params)); | |||||
| if (res && res.data && res.data.content) { | |||||
| const { content } = res.data; | |||||
| setTypeList( | |||||
| content.filter( | |||||
| (item: CategoryData) => | |||||
| Number(item.category_id) === resourceConfig[resourceType].typeValue, | |||||
| ), | |||||
| ); | |||||
| setTagList( | |||||
| content.filter( | |||||
| (item: CategoryData) => | |||||
| Number(item.category_id) === resourceConfig[resourceType].tagValue, | |||||
| ), | |||||
| ); | |||||
| } | |||||
| }; | |||||
| // 切换 Tab,重置数据 | |||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||||
| dataListRef.current?.reset(); | |||||
| setActiveTab(value); | |||||
| }; | |||||
| const isPublic = activeTab === CommonTabKeys.Public; | |||||
| return ( | |||||
| <div className={styles['resource-page']}> | |||||
| <div className={styles['resource-page__tabs-container']}> | |||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| items={resourceConfig[resourceType].tabItems} | |||||
| onChange={hanleTabChange} | |||||
| /> | |||||
| </div> | |||||
| <Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}> | |||||
| <CategoryList | |||||
| resourceType={resourceType} | |||||
| typeList={typeList} | |||||
| tagList={tagList} | |||||
| activeType={activeType} | |||||
| activeTag={activeTag} | |||||
| onTypeSelect={chooseType} | |||||
| onTagSelect={chooseTag} | |||||
| onSearch={handleCategorySearch} | |||||
| /> | |||||
| <ResourceList | |||||
| ref={dataListRef} | |||||
| resourceType={resourceType} | |||||
| typeList={typeList} | |||||
| tagList={tagList} | |||||
| isPublic={isPublic} | |||||
| dataType={activeType} | |||||
| dataTag={activeTag} | |||||
| initialSearchText={cacheState?.searchText} | |||||
| initialPagination={cacheState?.initialPagination} | |||||
| setCacheState={setCacheState} | |||||
| ></ResourceList> | |||||
| </Flex> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ResourcePage; | |||||
| @@ -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 '../../type'; | |||||
| 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; | |||||
| @@ -1,90 +1,7 @@ | |||||
| import { getDatasetList } from '@/services/dataset/index.js'; | |||||
| import { Form, Input, Tabs } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import Styles from './index.less'; | |||||
| import PersonalData from './personalData'; | |||||
| import PublicData from './publicData'; | |||||
| const { Search } = Input; | |||||
| const { TabPane } = Tabs; | |||||
| const leftdataList = [1, 2, 3]; | |||||
| import ResourcePage from './components/ResourcePage'; | |||||
| import { ResourceType } from './type'; | |||||
| const Dataset = () => { | |||||
| const [queryFlow, setQueryFlow] = useState({ | |||||
| page: 0, | |||||
| size: 10, | |||||
| name: null, | |||||
| }); | |||||
| const navgite = useNavigate(); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetList, setDatasetList] = useState([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [form] = Form.useForm(); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | |||||
| const getDatasetlist = () => { | |||||
| getDatasetList(queryFlow).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200) { | |||||
| setDatasetList(ret.data.content); | |||||
| setTotal(ret.data.totalElements); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const showModal = () => { | |||||
| form.resetFields(); | |||||
| setDialogTitle('新建数据集'); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const handleOk = () => { | |||||
| console.log(1111); | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const onFinish = (values) => {}; | |||||
| const routeToIntro = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| navgite({ pathname: '/dataset/dataset' }); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| useEffect(() => { | |||||
| //getDatasetlist(); | |||||
| return () => {}; | |||||
| }, []); | |||||
| return ( | |||||
| <div className={Styles.datasetBox}> | |||||
| <div className={Styles.datasetTopBox}></div> | |||||
| <div className={Styles.datasetAllBox}> | |||||
| <Tabs defaultActiveKey="1"> | |||||
| <TabPane | |||||
| tab="数据广场" | |||||
| key="1" | |||||
| icon={ | |||||
| <svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true"> | |||||
| <use xlinkHref="#icon-shujujiguangchang"></use> | |||||
| </svg> | |||||
| } | |||||
| > | |||||
| <PublicData /> | |||||
| </TabPane> | |||||
| <TabPane | |||||
| tab="个人数据" | |||||
| key="2" | |||||
| icon={ | |||||
| <svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true"> | |||||
| <use xlinkHref="#icon-gerenshujuji"></use> | |||||
| </svg> | |||||
| } | |||||
| > | |||||
| <PersonalData /> | |||||
| </TabPane> | |||||
| </Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| const DatasetPage = () => { | |||||
| return <ResourcePage resourceType={ResourceType.Dataset} />; | |||||
| }; | }; | ||||
| export default Dataset; | |||||
| export default DatasetPage; | |||||
| @@ -1,337 +0,0 @@ | |||||
| .datasetTopBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 49px; | |||||
| padding: 0 30px; | |||||
| padding-right: 30px; | |||||
| font-family: 'Alibaba'; | |||||
| background-image: url(/assets/images/pipeline-back.png); | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: alibaba; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| font-family: 'Alibaba'; | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .datasetAllBox { | |||||
| :global { | |||||
| .ant-tabs-nav .ant-tabs-nav-wrap { | |||||
| margin: -48px 0 0 30px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .datasetCneterBox { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 87.5vh; | |||||
| :global { | |||||
| .ant-btn { | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| .datasetCneterLeftBox { | |||||
| width: 340px; | |||||
| height: 100%; | |||||
| margin-right: 10px; | |||||
| padding-top: 15px; | |||||
| font-family: 'Alibaba'; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| .custTab { | |||||
| display: flex; | |||||
| height: 32px; | |||||
| border-bottom: 1px solid #e0eaff; | |||||
| .tabItem { | |||||
| width: 52px; | |||||
| height: 100%; | |||||
| color: #808080; | |||||
| font-size: 15px; | |||||
| text-align: center; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| .leftContentBox { | |||||
| max-height: 80vh; | |||||
| padding: 15px 20px; | |||||
| overflow-x: hidden; | |||||
| overflow-y: auto; | |||||
| font-family: 'Alibaba'; | |||||
| .itemTitle { | |||||
| margin-bottom: 15px; | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| } | |||||
| .itemBox { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| align-content: start; | |||||
| width: 110%; | |||||
| .messageBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 92px; | |||||
| height: 62px; | |||||
| margin: 0 12px 20px 0; | |||||
| padding: 11px 0px 7px 0px; | |||||
| color: #1d1d20; | |||||
| font-size: 12px; | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.05); | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| .ptIcon { | |||||
| display: block; | |||||
| } | |||||
| .hoverIcon { | |||||
| display: none; | |||||
| } | |||||
| .messageText { | |||||
| width: 65px; | |||||
| overflow: hidden; | |||||
| white-space: nowrap; | |||||
| text-align: center; | |||||
| text-overflow: ellipsis; | |||||
| } | |||||
| } | |||||
| .messageBox:hover { | |||||
| background: rgba(22, 100, 255, 0.03); | |||||
| border: 1px solid; | |||||
| border-color: #1664ff; | |||||
| .ptIcon { | |||||
| display: none; | |||||
| } | |||||
| .hoverIcon { | |||||
| display: block; | |||||
| } | |||||
| } | |||||
| .active { | |||||
| background: rgba(22, 100, 255, 0.03) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: #1664ff !important; | |||||
| .ptIcon { | |||||
| display: none !important; | |||||
| } | |||||
| .hoverIcon { | |||||
| display: block !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .datasetCneterRightBox { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| height: 100%; | |||||
| overflow-y: auto; | |||||
| padding: 22px 30px 26px 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| .dataSource { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin-bottom: 30px; | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| font-size: 15px; | |||||
| } | |||||
| .dataContent { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-wrap: wrap; | |||||
| align-content: flex-start; | |||||
| width: 100%; | |||||
| .dataItem { | |||||
| position: relative; | |||||
| width: 23.8%; | |||||
| height:164px; | |||||
| margin: 0 20px 25px 0; | |||||
| background: #ffffff; | |||||
| border: 1px solid; | |||||
| border-color: #eaeaea; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| .dropdown{ | |||||
| position: absolute; | |||||
| right: 20px; | |||||
| top: 15px; | |||||
| } | |||||
| .itemText { | |||||
| position: absolute; | |||||
| top: 20px; | |||||
| left: 20px; | |||||
| height: 6px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: 'Alibaba'; | |||||
| line-height: 0px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| rgba(22, 100, 255, 0.3) 0, | |||||
| rgba(22, 100, 255, 0) 100% | |||||
| ); | |||||
| } | |||||
| .itemDescripition { | |||||
| position: absolute; | |||||
| top: 57px; | |||||
| left: 20px; | |||||
| display: -webkit-box; | |||||
| padding-right: 28px; | |||||
| overflow: hidden; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| word-break: break-all; | |||||
| -webkit-line-clamp: 2; | |||||
| -webkit-box-orient: vertical; | |||||
| } | |||||
| .itemTime { | |||||
| position: absolute; | |||||
| bottom: 22px; | |||||
| left: 20px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | |||||
| font-size: 13px; | |||||
| } | |||||
| .itemIcon { | |||||
| position: absolute; | |||||
| right: 20px; | |||||
| bottom: 22px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | |||||
| font-size: 13px; | |||||
| } | |||||
| } | |||||
| .dataItem:hover { | |||||
| border-color: #1664ff; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | |||||
| .dataItem:hover .itemText { | |||||
| color: #1664ff; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .tipContent{ | |||||
| color: #c73131; | |||||
| margin-top: 5px; | |||||
| } | |||||
| .modal { | |||||
| :global { | |||||
| .ant-modal-content { | |||||
| width: 825px; | |||||
| padding: 20px 67px; | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100%; | |||||
| border-radius: 21px; | |||||
| } | |||||
| .ant-modal-header { | |||||
| margin: 20px 0; | |||||
| background-color: transparent; | |||||
| } | |||||
| .ant-input { | |||||
| height: 40px; | |||||
| border-color: #e6e6e6; | |||||
| } | |||||
| .ant-form-item .ant-form-item-label > label { | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| } | |||||
| .ant-modal-footer { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| margin: 40px 0 30px 0; | |||||
| } | |||||
| .ant-btn { | |||||
| width: 110px; | |||||
| height: 40px; | |||||
| font-size: 18px; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border-color: transparent; | |||||
| border-radius: 10px; | |||||
| } | |||||
| .ant-btn-primary { | |||||
| background: #1664ff; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,61 +1,34 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { ResourceType } from '@/pages/Dataset/type'; | |||||
| import { | import { | ||||
| addDatasetVersionDetail, | |||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| getDatasetById, | getDatasetById, | ||||
| getDatasetVersionIdList, | getDatasetVersionIdList, | ||||
| getDatasetVersionsById, | getDatasetVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { formatDate } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { Button, Input, Select, Table, Tabs, message } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useParams } from 'react-router-dom'; | |||||
| import Styles from './index.less'; | |||||
| import AddVersionModal from './components/AddVersionModal'; | |||||
| import Styles from './intro.less'; | |||||
| const { Search } = Input; | const { Search } = Input; | ||||
| const { TabPane } = Tabs; | const { TabPane } = Tabs; | ||||
| const Dataset = () => { | const Dataset = () => { | ||||
| const props = { | |||||
| action: '/api/mmp/dataset/upload', | |||||
| // headers: { | |||||
| // 'X-Requested-With': null | |||||
| // }, | |||||
| headers: { | |||||
| Authorization: getAccessToken(), | |||||
| 'X-Requested-With': null, | |||||
| }, | |||||
| onChange({ file, fileList }) { | |||||
| if (file.status !== 'uploading') { | |||||
| console.log(file, fileList); | |||||
| setFormList( | |||||
| fileList.map((item) => { | |||||
| return { | |||||
| ...form.getFieldsValue(), | |||||
| dataset_id: locationParams.id, | |||||
| file_name: item.response.code === 200 ? item.response.data[0].fileName : null, | |||||
| file_size: item.response.code === 200 ? item.response.data[0].fileSize : null, | |||||
| url: item.response.code === 200 ? item.response.data[0].url : null, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| } | |||||
| }, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| const [form] = Form.useForm(); | |||||
| const [formList, setFormList] = useState([]); | const [formList, setFormList] = useState([]); | ||||
| const [dialogTitle, setDialogTitle] = useState('新建版本'); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | const [datasetDetailObj, setDatasetDetailObj] = useState({}); | ||||
| const [version, setVersion] = useState(null); | const [version, setVersion] = useState(null); | ||||
| const [versionList, setVersionList] = useState([]); | const [versionList, setVersionList] = useState([]); | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| const [searchParams] = useSearchParams(); | |||||
| const [wordList, setWordList] = useState([]); | const [wordList, setWordList] = useState([]); | ||||
| const [activeTabKey, setActiveTabKey] = useState('1'); | const [activeTabKey, setActiveTabKey] = useState('1'); | ||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const getDatasetByDetail = () => { | const getDatasetByDetail = () => { | ||||
| getDatasetById(locationParams.id).then((ret) => { | getDatasetById(locationParams.id).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| @@ -77,6 +50,9 @@ const Dataset = () => { | |||||
| ); | ); | ||||
| setVersion(ret.data[0]); | setVersion(ret.data[0]); | ||||
| getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); | getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); | ||||
| } else { | |||||
| setVersion(null); | |||||
| setWordList([]); | |||||
| } | } | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -86,37 +62,21 @@ const Dataset = () => { | |||||
| return () => {}; | return () => {}; | ||||
| }, []); | }, []); | ||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields(); | |||||
| form.setFieldsValue({ name: datasetDetailObj.name }); | |||||
| setDialogTitle('创建新版本'); | |||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleExport = async () => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version }); | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: ResourceType.Dataset, | |||||
| resourceId: locationParams.id, | |||||
| initialName: datasetDetailObj.name, | |||||
| onOk: () => { | |||||
| getDatasetVersionList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | }; | ||||
| const deleteDataset = () => { | |||||
| Modal.confirm({ | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该数据集版本将不可恢复</div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| const deleteDataset = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该数据集版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | onOk: () => { | ||||
| deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { | deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { | ||||
| getDatasetVersionList(); | getDatasetVersionList(); | ||||
| @@ -125,19 +85,26 @@ const Dataset = () => { | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| const onFinish = (values) => { | |||||
| addDatasetVersionDetail(formList).then((ret) => { | |||||
| getDatasetVersionList(); | |||||
| setIsModalOpen(false); | |||||
| message.success('创建成功'); | |||||
| }); | |||||
| }; | |||||
| // 获取版本下的文件列表 | // 获取版本下的文件列表 | ||||
| const getDatasetVersions = (params) => { | const getDatasetVersions = (params) => { | ||||
| getDatasetVersionIdList(params).then((res) => { | getDatasetVersionIdList(params).then((res) => { | ||||
| setWordList(res?.data?.content ?? []); | setWordList(res?.data?.content ?? []); | ||||
| }); | }); | ||||
| }; | }; | ||||
| const handleExport = async () => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version }); | |||||
| }; | |||||
| const downloadAlone = (e, record) => { | |||||
| console.log(record); | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/download/${record.id}`); | |||||
| }; | |||||
| const handleChange = (value) => { | const handleChange = (value) => { | ||||
| console.log(value); | console.log(value); | ||||
| if (value) { | if (value) { | ||||
| @@ -147,15 +114,7 @@ const Dataset = () => { | |||||
| setVersion(null); | setVersion(null); | ||||
| } | } | ||||
| }; | }; | ||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const downloadAlone = (e, record) => { | |||||
| console.log(record); | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/download/${record.id}`); | |||||
| }; | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| @@ -187,7 +146,7 @@ const Dataset = () => { | |||||
| title: '更新时间', | title: '更新时间', | ||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| render: (text) => <span>{formatDate(text)}</span>, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -264,15 +223,17 @@ const Dataset = () => { | |||||
| <div | <div | ||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | ||||
| > | > | ||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| disabled={!version} | disabled={!version} | ||||
| @@ -295,105 +256,6 @@ const Dataset = () => { | |||||
| </TabPane> | </TabPane> | ||||
| </Tabs> | </Tabs> | ||||
| </div> | </div> | ||||
| <Modal | |||||
| title={ | |||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||||
| <img | |||||
| style={{ width: '20px', marginRight: '10px' }} | |||||
| src={`/assets/images/pipeline-edit-icon.png`} | |||||
| alt="" | |||||
| /> | |||||
| {dialogTitle} | |||||
| </div> | |||||
| } | |||||
| open={isModalOpen} | |||||
| className={Styles.modal} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| onCancel={handleCancel} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="数据集名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据集名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled placeholder="请输入数据集名称" /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集版本" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据集版本', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入数据集版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={256} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集文件" | |||||
| name="dataset_version_vos" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请上传数据集文件', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||||
| <Button | |||||
| style={{ | |||||
| fontSize: '14px', | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| }} | |||||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||||
| > | |||||
| 上传文件 | |||||
| </Button> | |||||
| <div className={Styles.tipContent}>只允许上传.zip,.tgz格式文件</div> | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </Modal> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -0,0 +1,79 @@ | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: alibaba; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .tipContent { | |||||
| margin-top: 5px; | |||||
| color: #c73131; | |||||
| } | |||||
| @@ -1,479 +0,0 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import clock from '@/assets/img/clock.png'; | |||||
| import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import deleteIcon from '@/assets/img/delete-icon.png'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | |||||
| addDatesetAndVesion, | |||||
| deleteDataset, | |||||
| getAssetIcon, | |||||
| getDatasetList, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import { getDictSelectOption } from '@/services/system/dict'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import React, { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import './index.less'; | |||||
| import Styles from './index.less'; | |||||
| const { Search } = Input; | |||||
| const leftdataList = [1, 2, 3]; | |||||
| const PublicData = (React.FC = () => { | |||||
| const props = { | |||||
| action: '/api/mmp/dataset/upload', | |||||
| // headers: { | |||||
| // 'X-Requested-With': null | |||||
| // }, | |||||
| headers: { | |||||
| Authorization: getAccessToken(), | |||||
| 'X-Requested-With': null, | |||||
| }, | |||||
| onChange({ file, fileList }) { | |||||
| if (file.status !== 'uploading') { | |||||
| console.log(file, fileList); | |||||
| form.setFieldsValue({ | |||||
| dataset_version_vos: fileList.map((item) => { | |||||
| const data = item.response.data[0]; | |||||
| return { | |||||
| file_name: data.fileName, | |||||
| file_size: data.fileSize, | |||||
| url: data.url, | |||||
| }; | |||||
| }), | |||||
| }); | |||||
| } | |||||
| }, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| const [queryFlow, setQueryFlow] = useState({ | |||||
| page: 0, | |||||
| size: 20, | |||||
| name: null, | |||||
| available_range: 0, | |||||
| }); | |||||
| const [iconParams, setIconParams] = useState({ | |||||
| name: null, | |||||
| page: 0, | |||||
| size: 10000, | |||||
| }); | |||||
| const [activeType, setActiveType] = useState(null); | |||||
| const [activeTag, setActiveTag] = useState(null); | |||||
| const [datasetTypeList, setDatasetTypeList] = useState([]); | |||||
| const [datasetDirectionList, setDatasetDirectionList] = useState([]); | |||||
| const navgite = useNavigate(); | |||||
| const [clusterOptions, setClusterOptions] = useState([]); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetList, setDatasetList] = useState([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [form] = Form.useForm(); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | |||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const getDatasetlist = (queryFlow) => { | |||||
| getDatasetList(queryFlow).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200) { | |||||
| setDatasetList(ret.data.content); | |||||
| setTotal(ret.data.totalElements); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const showModal = () => { | |||||
| form.resetFields(); | |||||
| setDialogTitle('新建数据集'); | |||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const getAssetIconList = (params) => { | |||||
| getAssetIcon(params).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||||
| setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1)); | |||||
| setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2)); | |||||
| } else { | |||||
| setDatasetTypeList([]); | |||||
| setDatasetDirectionList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const onSearch = (values) => { | |||||
| console.log(values); | |||||
| getAssetIconList({ ...iconParams, name: values }); | |||||
| }; | |||||
| const nameSearch = (values) => { | |||||
| console.log(values); | |||||
| getDatasetlist({ ...queryFlow, name: values }); | |||||
| }; | |||||
| const handleOk = () => { | |||||
| console.log(1111); | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const chooseDatasetType = (val, item) => { | |||||
| console.log(val, item); | |||||
| if (item.id == queryFlow.data_type) { | |||||
| setActiveType(''); | |||||
| setQueryFlow({ ...queryFlow, data_type: null }); | |||||
| getDatasetlist({ ...queryFlow, data_type: null }); | |||||
| } else { | |||||
| setActiveType(item.id); | |||||
| setQueryFlow({ ...queryFlow, data_type: item.id }); | |||||
| getDatasetlist({ ...queryFlow, data_type: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const chooseDatasetTag = (val, item) => { | |||||
| console.log(val, item); | |||||
| if (item.id == queryFlow.data_tag) { | |||||
| setActiveTag(''); | |||||
| setQueryFlow({ ...queryFlow, data_tag: null }); | |||||
| getDatasetlist({ ...queryFlow, data_tag: null }); | |||||
| } else { | |||||
| setActiveTag(item.id); | |||||
| setQueryFlow({ ...queryFlow, data_tag: item.id }); | |||||
| getDatasetlist({ ...queryFlow, data_tag: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const onFinish = (values) => { | |||||
| addDatesetAndVesion(values).then((ret) => { | |||||
| console.log(ret); | |||||
| setIsModalOpen(false); | |||||
| getDatasetlist(queryFlow); | |||||
| }); | |||||
| }; | |||||
| const routeToIntro = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| console.log(record); | |||||
| navgite({ pathname: `/dataset/dataset/${record.id}` }); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const onPageChange = (pageNum, pageSize) => { | |||||
| console.log(pageNum, pageSize); | |||||
| setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getDictSelectOption('available_cluster').then((data) => { | |||||
| setClusterOptions(data); | |||||
| }); | |||||
| getAssetIconList(iconParams); | |||||
| getDatasetlist(queryFlow); | |||||
| return () => {}; | |||||
| }, []); | |||||
| return ( | |||||
| <> | |||||
| <div className={Styles.datasetCneterBox}> | |||||
| <div className={Styles.datasetCneterLeftBox}> | |||||
| <div className={Styles.leftContentBox}> | |||||
| <Search | |||||
| placeholder="搜索" | |||||
| allowClear | |||||
| onSearch={onSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| marginBottom: '15px', | |||||
| }} | |||||
| /> | |||||
| <div className={Styles.itemTitle}>分类</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {datasetTypeList && datasetTypeList.length > 0 | |||||
| ? datasetTypeList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeType ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseDatasetType(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={Styles.messageText}>{item.name}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| <div className={Styles.itemTitle}>研究方向/应用领域</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {datasetDirectionList && datasetDirectionList.length > 0 | |||||
| ? datasetDirectionList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeTag ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseDatasetTag(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={Styles.messageText}>{item.name}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetCneterRightBox}> | |||||
| <div className={Styles.dataSource}> | |||||
| <span>数据总数:{total}个</span> | |||||
| <div> | |||||
| <Search | |||||
| placeholder="按数据名称筛选" | |||||
| allowClear | |||||
| onSearch={nameSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| }} | |||||
| /> | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginLeft: '20px' }} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 新建数据集 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.dataContent}> | |||||
| {datasetList && datasetList.length > 0 | |||||
| ? datasetList.map((item) => { | |||||
| return ( | |||||
| <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> | |||||
| <span className={Styles.itemText}>{item.name}</span> | |||||
| <img | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| modalConfirm({ | |||||
| title: '确定删除该条数据集实例吗?', | |||||
| onOk: () => { | |||||
| deleteDataset(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getModelLists(queryFlow); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| className={Styles.dropdown} | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={deleteIcon} | |||||
| alt="" | |||||
| /> | |||||
| <div className={Styles.itemDescripition}>{item.description}</div> | |||||
| <div className={Styles.itemTime}> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={creatByImg} | |||||
| alt="" | |||||
| /> | |||||
| <span>{item.create_by}</span> | |||||
| </div> | |||||
| <div className={Styles.itemIcon}> | |||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={onPageChange} | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <Modal | |||||
| title={ | |||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||||
| <img | |||||
| style={{ width: '20px', marginRight: '10px' }} | |||||
| src={`/assets/images/pipeline-edit-icon.png`} | |||||
| alt="" | |||||
| /> | |||||
| {dialogTitle} | |||||
| </div> | |||||
| } | |||||
| open={isModalOpen} | |||||
| className={Styles.modal} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| onCancel={handleCancel} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="数据名称" | |||||
| name="name" | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据名称e!', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入数据名称" showCount maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集版本" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入数据集版本!', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入数据集版本" showCount maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集分类" | |||||
| name="data_type" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择数据集分类" | |||||
| options={datasetTypeList.map((item) => { | |||||
| return { value: item.id, label: item.name }; | |||||
| })} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="研究方向/应用领域" | |||||
| name="data_tag" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择研究方向/应用领域" | |||||
| options={datasetDirectionList.map((item) => { | |||||
| return { value: item.id, label: item.name }; | |||||
| })} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label="集群版本" name="available_cluster"> | |||||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据简介" | |||||
| name="description" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入数据简介" showCount maxLength={256} /> | |||||
| </Form.Item> | |||||
| <Form.Item label="选择流水线" name="range"> | |||||
| <Radio.Group> | |||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Form.Item label="数据文件" name="dataset_version_vos"> | |||||
| <Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||||
| <Button | |||||
| style={{ | |||||
| fontSize: '14px', | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| }} | |||||
| icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />} | |||||
| > | |||||
| 上传文件 | |||||
| </Button> | |||||
| <div className={Styles.tipContent}>只允许上传.zip,.tgz格式文件</div> | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </Modal> | |||||
| </> | |||||
| ); | |||||
| }); | |||||
| export default PublicData; | |||||
| @@ -1,284 +0,0 @@ | |||||
| import clock from '@/assets/img/clock.png'; | |||||
| import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import deleteIcon from '@/assets/img/delete-icon.png'; | |||||
| import { deleteDataset, getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { Form, Input, Pagination } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import Styles from './index.less'; | |||||
| const { Search } = Input; | |||||
| const leftdataList = [1, 2, 3]; | |||||
| const PublicData = () => { | |||||
| const [queryFlow, setQueryFlow] = useState({ | |||||
| page: 0, | |||||
| size: 10, | |||||
| name: null, | |||||
| available_range: 1, | |||||
| }); | |||||
| const [iconParams, setIconParams] = useState({ | |||||
| name: null, | |||||
| page: 0, | |||||
| size: 10000, | |||||
| }); | |||||
| const navgite = useNavigate(); | |||||
| const [datasetTypeList, setDatasetTypeList] = useState([]); | |||||
| const [datasetDirectionList, setDatasetDirectionList] = useState([]); | |||||
| const [activeType, setActiveType] = useState(null); | |||||
| const [activeTag, setActiveTag] = useState(null); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetList, setDatasetList] = useState([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [form] = Form.useForm(); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | |||||
| const getDatasetlist = (queryFlow) => { | |||||
| getDatasetList(queryFlow).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200) { | |||||
| setDatasetList(ret.data.content); | |||||
| setTotal(ret.data.totalElements); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const onSearch = (values) => { | |||||
| console.log(values); | |||||
| getAssetIconList({ ...iconParams, name: values }); | |||||
| }; | |||||
| const getAssetIconList = (params) => { | |||||
| getAssetIcon(params).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||||
| setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1)); | |||||
| setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2)); | |||||
| } else { | |||||
| setDatasetTypeList([]); | |||||
| setDatasetDirectionList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const nameSearch = (values) => { | |||||
| console.log(values); | |||||
| getDatasetlist({ ...queryFlow, name: values }); | |||||
| }; | |||||
| const showModal = () => { | |||||
| form.resetFields(); | |||||
| setDialogTitle('新建数据集'); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const handleOk = () => { | |||||
| console.log(1111); | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const chooseDatasetType = (val, item) => { | |||||
| console.log(val, item); | |||||
| if (item.id == queryFlow.data_type) { | |||||
| setActiveType(''); | |||||
| setQueryFlow({ ...queryFlow, data_type: null }); | |||||
| getDatasetlist({ ...queryFlow, data_type: null }); | |||||
| } else { | |||||
| setActiveType(item.id); | |||||
| setQueryFlow({ ...queryFlow, data_type: item.id }); | |||||
| getDatasetlist({ ...queryFlow, data_type: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const chooseDatasetTag = (val, item) => { | |||||
| console.log(val, item); | |||||
| if (item.id == queryFlow.data_tag) { | |||||
| setActiveTag(''); | |||||
| setQueryFlow({ ...queryFlow, data_tag: null }); | |||||
| getDatasetlist({ ...queryFlow, data_tag: null }); | |||||
| } else { | |||||
| setActiveTag(item.id); | |||||
| setQueryFlow({ ...queryFlow, data_tag: item.id }); | |||||
| getDatasetlist({ ...queryFlow, data_tag: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const routeToIntro = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| console.log(record); | |||||
| navgite({ pathname: `/dataset/dataset/${record.id}` }); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const onPageChange = (pageNum, pageSize) => { | |||||
| console.log(pageNum, pageSize); | |||||
| setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getAssetIconList(iconParams); | |||||
| getDatasetlist(queryFlow); | |||||
| return () => {}; | |||||
| }, []); | |||||
| return ( | |||||
| <> | |||||
| <div className={Styles.datasetCneterBox}> | |||||
| <div className={Styles.datasetCneterLeftBox}> | |||||
| <div className={Styles.leftContentBox}> | |||||
| <Search | |||||
| placeholder="搜索" | |||||
| allowClear | |||||
| onSearch={onSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| marginBottom: '15px', | |||||
| }} | |||||
| /> | |||||
| <div className={Styles.itemTitle}>分类</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {datasetTypeList && datasetTypeList.length > 0 | |||||
| ? datasetTypeList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeType ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseDatasetType(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={Styles.messageText}>{item.name}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| <div className={Styles.itemTitle}>研究方向/应用领域</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {datasetDirectionList && datasetDirectionList.length > 0 | |||||
| ? datasetDirectionList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeTag ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseDatasetTag(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/dataset/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={Styles.messageText}>{item.name}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetCneterRightBox}> | |||||
| <div className={Styles.dataSource}> | |||||
| <span>数据总数:{total}个</span> | |||||
| <div> | |||||
| <Search | |||||
| placeholder="按数据名称筛选" | |||||
| allowClear | |||||
| onSearch={nameSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| }} | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.dataContent}> | |||||
| {datasetList && datasetList.length > 0 | |||||
| ? datasetList.map((item) => { | |||||
| return ( | |||||
| <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> | |||||
| <span className={Styles.itemText}>{item.name}</span> | |||||
| <img | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| modalConfirm({ | |||||
| title: '确定删除该条数据集实例吗?', | |||||
| onOk: () => { | |||||
| deleteDataset(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getModelLists(queryFlow); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| className={Styles.dropdown} | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={deleteIcon} | |||||
| alt="" | |||||
| /> | |||||
| <div className={Styles.itemDescripition}>{item.description}</div> | |||||
| <div className={Styles.itemTime}> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={creatByImg} | |||||
| alt="" | |||||
| /> | |||||
| <span>{item.create_by}</span> | |||||
| </div> | |||||
| <div className={Styles.itemIcon}> | |||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={onPageChange} | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PublicData; | |||||
| @@ -0,0 +1,132 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { | |||||
| addDatasetVersionDetail, | |||||
| addModelsVersionDetail, | |||||
| deleteDataset, | |||||
| deleteModel, | |||||
| getDatasetList, | |||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| getModelList, | |||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import type { TabsProps } from 'antd'; | |||||
| export enum ResourceType { | |||||
| Model = 'Model', // 模型 | |||||
| Dataset = 'Dataset', // 数据集 | |||||
| } | |||||
| type ResourceTypeKeys = keyof typeof ResourceType; | |||||
| export type ResourceTypeValues = (typeof ResourceType)[ResourceTypeKeys]; | |||||
| type ResourceTypeInfo = { | |||||
| getList: (params: any) => Promise<any>; | |||||
| getVersions: (params: any) => Promise<any>; | |||||
| getFiles: (params: any) => Promise<any>; | |||||
| deleteRecord: (params: any) => Promise<any>; | |||||
| name: string; | |||||
| typeParamKey: string; | |||||
| tagParamKey: string; | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||||
| tabItems: TabsProps['items']; | |||||
| typeTitle: string; | |||||
| tagTitle: string; | |||||
| typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | |||||
| tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | |||||
| iconPathPrefix: string; // 图标路径前缀 | |||||
| deleteModalTitle: string; // 删除弹框的title | |||||
| addBtnTitle: string; // 新增按钮的title | |||||
| addVersionReq: (params: any) => Promise<any>; | |||||
| idParamKey: string; | |||||
| uploadAction: string; | |||||
| uploadAccept?: string; | |||||
| }; | |||||
| export const resourceConfig: Record<ResourceTypeValues, ResourceTypeInfo> = { | |||||
| [ResourceType.Dataset]: { | |||||
| getList: getDatasetList, | |||||
| getVersions: getDatasetVersionsById, | |||||
| getFiles: getDatasetVersionIdList, | |||||
| deleteRecord: deleteDataset, | |||||
| name: '数据集', | |||||
| typeParamKey: 'data_type', | |||||
| tagParamKey: 'data_tag', | |||||
| fileReqParamKey: 'dataset_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '数据广场', | |||||
| icon: <KFIcon type="icon-shujuguangchang" />, | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '个人数据', | |||||
| icon: <KFIcon type="icon-gerenshuju" />, | |||||
| }, | |||||
| ], | |||||
| typeTitle: '分类', | |||||
| tagTitle: '研究方向/应用领域', | |||||
| typeValue: 1, | |||||
| tagValue: 2, | |||||
| iconPathPrefix: 'dataset', | |||||
| deleteModalTitle: '确定删除该条数据集实例吗?', | |||||
| addBtnTitle: '新建数据集', | |||||
| addVersionReq: addDatasetVersionDetail, | |||||
| idParamKey: 'dataset_id', | |||||
| uploadAction: '/api/mmp/dataset/upload', | |||||
| uploadAccept: '.zip,.tgz', | |||||
| }, | |||||
| [ResourceType.Model]: { | |||||
| getList: getModelList, | |||||
| getVersions: getModelVersionsById, | |||||
| getFiles: getModelVersionIdList, | |||||
| deleteRecord: deleteModel, | |||||
| name: '模型', | |||||
| typeParamKey: 'model_type', | |||||
| tagParamKey: 'model_tag', | |||||
| fileReqParamKey: 'models_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '模型广场', | |||||
| icon: <KFIcon type="icon-moxingguangchang" />, | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '个人模型', | |||||
| icon: <KFIcon type="icon-gerenmoxing" />, | |||||
| }, | |||||
| ], | |||||
| typeTitle: '模型框架', | |||||
| tagTitle: '模型能力', | |||||
| typeValue: 3, | |||||
| tagValue: 4, | |||||
| iconPathPrefix: 'model', | |||||
| deleteModalTitle: '确定删除该条模型实例吗?', | |||||
| addBtnTitle: '新建模型', | |||||
| addVersionReq: addModelsVersionDetail, | |||||
| idParamKey: 'models_id', | |||||
| uploadAction: '/api/mmp/models/upload', | |||||
| uploadAccept: undefined, | |||||
| }, | |||||
| }; | |||||
| // 分类数据 | |||||
| export type CategoryData = { | |||||
| id: number; | |||||
| category_id: number; | |||||
| name: string; | |||||
| path: string; | |||||
| }; | |||||
| // 数据类型 | |||||
| export type ResourceData = { | |||||
| id: number; | |||||
| name: string; | |||||
| description: string; | |||||
| create_by: string; | |||||
| update_time: string; | |||||
| }; | |||||
| @@ -0,0 +1,9 @@ | |||||
| .modal { | |||||
| .global_param_item { | |||||
| max-height: 230px; | |||||
| padding: 24px 12px 0; | |||||
| overflow-y: auto; | |||||
| border: 1px solid #e6e6e6; | |||||
| border-radius: 6px; | |||||
| } | |||||
| } | |||||
| @@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal'; | |||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { Form, Input, Radio, Select, type FormRule } from 'antd'; | import { Form, Input, Radio, Select, type FormRule } from 'antd'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import styles from './addExperimentModal.less'; | |||||
| import styles from './index.less'; | |||||
| type FormData = { | type FormData = { | ||||
| name?: string; | name?: string; | ||||
| @@ -0,0 +1,16 @@ | |||||
| .experiment-parameter { | |||||
| padding-top: 8px; | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 43px; | |||||
| margin-right: 8px; | |||||
| margin-bottom: 20px; | |||||
| margin-left: 8px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8fbff; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,162 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { Form, Input, type FormProps } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| const { TextArea } = Input; | |||||
| type ExperimentParameterProps = { | |||||
| form: FormProps['form']; | |||||
| nodeData: PipelineNodeModelSerialize; | |||||
| }; | |||||
| function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| // 控制策略 | |||||
| const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| // 输入参数 | |||||
| const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| // 输出参数 | |||||
| const outParametersList = Object.entries(nodeData.out_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| return ( | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| form={form} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| autoComplete="off" | |||||
| className={styles['experiment-parameter']} | |||||
| > | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle> | |||||
| </div> | |||||
| <Form.Item | |||||
| label="任务名称" | |||||
| name="label" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="任务ID" | |||||
| name="id" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务id', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle> | |||||
| </div> | |||||
| <Form.Item | |||||
| label="镜像" | |||||
| name="image" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="工作目录" name="working_directory"> | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="启动命令" name="command"> | |||||
| <TextArea disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="挂载路径" name="mount_path"> | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="环境变量" name="env_variables"> | |||||
| <TextArea disabled /> | |||||
| </Form.Item> | |||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={item.value.label} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/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 }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle image="/assets/images/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 }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </Form> | |||||
| ); | |||||
| } | |||||
| export default ExperimentParameter; | |||||
| @@ -0,0 +1,38 @@ | |||||
| .experiment-result { | |||||
| padding: 8px; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| &__content { | |||||
| padding: 10px 20px 20px 20px; | |||||
| background-color: rgba(234, 234, 234, 0.5); | |||||
| } | |||||
| &__item { | |||||
| margin-bottom: 20px; | |||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| &__name { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 10px 0; | |||||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | |||||
| } | |||||
| &__file { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| margin-bottom: 10px; | |||||
| padding: 0 20px 0 0; | |||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,59 @@ | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { Button } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| type ExperimentResultProps = { | |||||
| results?: ExperimentResultData[] | null; | |||||
| }; | |||||
| type ExperimentResultData = { | |||||
| name: string; | |||||
| path: string; | |||||
| type: string; | |||||
| value: { | |||||
| name: string; | |||||
| size: string; | |||||
| }[]; | |||||
| }; | |||||
| function ExperimentResult({ results }: ExperimentResultProps) { | |||||
| const exportResult = (val: string) => { | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: val }); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['experiment-result']}> | |||||
| <div className={styles['experiment-result__content']}> | |||||
| {results?.map((item) => ( | |||||
| <div key={item.name} className={styles['experiment-result__item']}> | |||||
| <div className={styles['experiment-result__item__name']}> | |||||
| <span>{item.name}</span> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportResult(item.path); | |||||
| }} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | |||||
| </div> | |||||
| <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value?.map((ele) => ( | |||||
| <div className={styles['experiment-result__item__file']} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ExperimentResult; | |||||
| @@ -0,0 +1,34 @@ | |||||
| .log-group { | |||||
| padding-bottom: 10px; | |||||
| &__pod { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 15px; | |||||
| background: rgba(234, 234, 234, 0.5); | |||||
| cursor: pointer; | |||||
| &__name { | |||||
| margin-right: 10px; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| &__detail { | |||||
| padding: 15px; | |||||
| color: white; | |||||
| font-size: 14px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| background: #19253b; | |||||
| } | |||||
| &__more-button { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| color: white; | |||||
| background: #19253b; | |||||
| } | |||||
| } | |||||
| @@ -1,16 +1,19 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-05-16 08:47:46 | |||||
| * @Description: 日志组件 | |||||
| */ | |||||
| import { useStateRef } from '@/hooks'; | import { useStateRef } from '@/hooks'; | ||||
| import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; | |||||
| import { ExperimentStatus } from '@/pages/Experiment/status'; | |||||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | import { getExperimentPodsLog } from '@/services/experiment/index.js'; | ||||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { ExperimentStatus } from '../status'; | |||||
| import styles from './logGroup.less'; | |||||
| import styles from './index.less'; | |||||
| export type LogGroupProps = { | |||||
| log_type: 'normal' | 'resource'; // 日志类型 | |||||
| pod_name?: string; // 分布式名称 | |||||
| log_content?: string; // 日志内容 | |||||
| start_time?: string; // 日志开始时间 | |||||
| export type LogGroupProps = ExperimentLog & { | |||||
| status: ExperimentStatus; // 实验状态 | status: ExperimentStatus; // 实验状态 | ||||
| }; | }; | ||||
| @@ -23,7 +26,7 @@ function LogGroup({ | |||||
| log_type = 'normal', | log_type = 'normal', | ||||
| pod_name = '', | pod_name = '', | ||||
| log_content = '', | log_content = '', | ||||
| start_time = '', | |||||
| start_time, | |||||
| status = ExperimentStatus.Pending, | status = ExperimentStatus.Pending, | ||||
| }: LogGroupProps) { | }: LogGroupProps) { | ||||
| const [collapse, setCollapse] = useState(true); | const [collapse, setCollapse] = useState(true); | ||||
| @@ -57,21 +60,6 @@ function LogGroup({ | |||||
| setCompleted(true); | setCompleted(true); | ||||
| } | } | ||||
| }; | }; | ||||
| // 请求实时日志 | |||||
| // const requestExperimentPodsRealtimeLog = async () => { | |||||
| // const params = { | |||||
| // pod_name, | |||||
| // namespace: namespace, | |||||
| // container_name: log_type === 'resource' ? '' : 'main', | |||||
| // }; | |||||
| // const res = await getExperimentPodsRealtimeLog(params); | |||||
| // const { log_detail } = res.data; | |||||
| // if (log_detail && log_detail.log_content) { | |||||
| // setLogList((list) => list.concat(log_detail)); | |||||
| // } else { | |||||
| // setCompleted(true); | |||||
| // } | |||||
| // }; | |||||
| // 处理折叠 | // 处理折叠 | ||||
| const handleCollapse = async () => { | const handleCollapse = async () => { | ||||
| @@ -101,15 +89,15 @@ function LogGroup({ | |||||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | const logText = log_content + logList.map((v) => v.log_content).join(''); | ||||
| const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; | const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; | ||||
| return ( | return ( | ||||
| <div className={styles.log_group}> | |||||
| <div className={styles['log-group']}> | |||||
| {log_type === 'resource' && ( | {log_type === 'resource' && ( | ||||
| <div className={styles.log_group_pod} onClick={handleCollapse}> | |||||
| <div className={styles.log_group_pod_name}>{pod_name}</div> | |||||
| <div className={styles['log-group__pod']} onClick={handleCollapse}> | |||||
| <div className={styles['log-group__pod__name']}>{pod_name}</div> | |||||
| {collapse ? <DownOutlined /> : <UpOutlined />} | {collapse ? <DownOutlined /> : <UpOutlined />} | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {showLog && <div className={styles.log_group_detail}>{logText}</div>} | |||||
| <div className={styles.log_group_more_button}> | |||||
| {showLog && <div className={styles['log-group__detail']}>{logText}</div>} | |||||
| <div className={styles['log-group__more-button']}> | |||||
| {showMoreBtn && ( | {showMoreBtn && ( | ||||
| <Button | <Button | ||||
| type="text" | type="text" | ||||
| @@ -0,0 +1,3 @@ | |||||
| .log-list { | |||||
| padding: 8px; | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; | |||||
| import { ExperimentStatus } from '@/pages/Experiment/status'; | |||||
| import LogGroup from '../LogGroup'; | |||||
| import styles from './index.less'; | |||||
| type LogListProps = { | |||||
| list: ExperimentLog[]; | |||||
| status: ExperimentStatus; | |||||
| }; | |||||
| function LogList({ list = [], status }: LogListProps) { | |||||
| return ( | |||||
| <div className={styles['log-list']}> | |||||
| {list.map((v) => ( | |||||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default LogList; | |||||
| @@ -6,8 +6,8 @@ | |||||
| import parameterImg from '@/assets/img/modal-parameter.png'; | import parameterImg from '@/assets/img/modal-parameter.png'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { getParamType } from './addExperimentModal'; | |||||
| import styles from './paramsModal.less'; | |||||
| import { getParamType } from '../AddExperimentModal'; | |||||
| import styles from './index.less'; | |||||
| type ParamsModalProps = { | type ParamsModalProps = { | ||||
| open: boolean; | open: boolean; | ||||
| @@ -24,6 +24,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| onOk={onCancel} | onOk={onCancel} | ||||
| onCancel={onCancel} | onCancel={onCancel} | ||||
| cancelButtonProps={{ style: { display: 'none' } }} | cancelButtonProps={{ style: { display: 'none' } }} | ||||
| width={825} | |||||
| > | > | ||||
| <div className={styles.params_container}> | <div className={styles.params_container}> | ||||
| {globalParam?.map((item) => ( | {globalParam?.map((item) => ( | ||||
| @@ -1,19 +0,0 @@ | |||||
| import { ExperimentStatus } from '../status'; | |||||
| import LogGroup, { type LogGroupProps } from './logGroup'; | |||||
| type LogListProps = { | |||||
| list: Omit<LogGroupProps, 'status'>[]; | |||||
| status: ExperimentStatus; | |||||
| }; | |||||
| function LogList({ list = [], status }: LogListProps) { | |||||
| return ( | |||||
| <div> | |||||
| {list.map((v) => ( | |||||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default LogList; | |||||
| @@ -1,22 +0,0 @@ | |||||
| .modal { | |||||
| :global { | |||||
| // .ant-input { | |||||
| // height: 30px; | |||||
| // border-color: #e6e6e6; | |||||
| // } | |||||
| // .ant-select-single { | |||||
| // height: 40px; | |||||
| // } | |||||
| .ant-form-item .ant-form-item-label > label { | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| } | |||||
| } | |||||
| .global_param_item { | |||||
| max-height: 230px; | |||||
| padding: 24px 12px 0; | |||||
| overflow-y: auto; | |||||
| border: 1px solid #e6e6e6; | |||||
| border-radius: 6px; | |||||
| } | |||||
| } | |||||
| @@ -1,51 +1,26 @@ | |||||
| import { useVisible } from '@/hooks'; | |||||
| import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { getExperimentIns } from '@/services/experiment/index.js'; | import { getExperimentIns } from '@/services/experiment/index.js'; | ||||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | import { getWorkflowById } from '@/services/pipeline/index.js'; | ||||
| import { elapsedTime } from '@/utils/date'; | |||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import momnet from 'moment'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | import { s8 } from '../../../utils'; | ||||
| import ParamsModal from '../components/ViewParamsModal'; | |||||
| import { experimentStatusInfo } from '../status'; | import { experimentStatusInfo } from '../status'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import ParamsModal from './paramsModal'; | |||||
| import Props from './props'; | import Props from './props'; | ||||
| let graph = null; | |||||
| function ExperimentText() { | function ExperimentText() { | ||||
| const [message, setMessage] = useState({}); | |||||
| const messageRef = useRef(message); | |||||
| const [message, setMessage, messageRef] = useStateRef({}); | |||||
| const propsRef = useRef(); | const propsRef = useRef(); | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| let graph = null; | |||||
| const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | ||||
| const timers = (time) => { | |||||
| let timer = new Date(time); | |||||
| let hours = timer.getHours(); //转换成时 | |||||
| let minutes = timer.getMinutes(); //转换成分 | |||||
| let secend = timer.getSeconds(); //转换成秒 | |||||
| let str = `${minutes}分${secend}秒`; | |||||
| return str; | |||||
| }; | |||||
| const pipelineContainer = useEmotionCss(() => { | |||||
| return { | |||||
| display: 'flex', | |||||
| backgroundColor: '#fff', | |||||
| height: '98vh', | |||||
| }; | |||||
| }); | |||||
| const graphStyle = useEmotionCss(() => { | |||||
| return { | |||||
| width: '100%', | |||||
| backgroundColor: '#f9fafb', | |||||
| flex: 1, | |||||
| }; | |||||
| }); | |||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val, 'eee'); | console.log(val, 'eee'); | ||||
| @@ -61,12 +36,8 @@ function ExperimentText() { | |||||
| id: val.component_name + '-' + s8(), | id: val.component_name + '-' + s8(), | ||||
| isCluster: false, | isCluster: false, | ||||
| }; | }; | ||||
| console.log(graph, model); | |||||
| graph.addItem('node', model, true); | graph.addItem('node', model, true); | ||||
| console.log(graph); | |||||
| }; | }; | ||||
| const formChange = (val) => {}; | |||||
| const handlerClick = (e) => { | const handlerClick = (e) => { | ||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | if (e.target.get('name') !== 'anchor-point' && e.item) { | ||||
| propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | ||||
| @@ -74,7 +45,6 @@ function ExperimentText() { | |||||
| }; | }; | ||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| if (graph) { | if (graph) { | ||||
| console.log(graph); | |||||
| graph.data(data); | graph.data(data); | ||||
| graph.render(); | graph.render(); | ||||
| } else { | } else { | ||||
| @@ -84,77 +54,39 @@ function ExperimentText() { | |||||
| } | } | ||||
| }; | }; | ||||
| const getFirstWorkflow = (val) => { | const getFirstWorkflow = (val) => { | ||||
| getWorkflowById(val).then((ret) => { | |||||
| if (graph && ret.data && ret.data.dag) { | |||||
| console.log(JSON.parse(ret.data.dag)); | |||||
| getExperimentIns(locationParams.id).then((res) => { | |||||
| if (res.code == 200) { | |||||
| console.log(ret.data, 'data'); | |||||
| setMessage(res.data); | |||||
| const experimentStatusObjs = JSON.parse(res.data.nodes_status); | |||||
| const newNodeList = JSON.parse(ret.data.dag).nodes.map((item) => { | |||||
| console.log(experimentStatusObjs); | |||||
| getWorkflowById(val).then((pipelineRes) => { | |||||
| if (graph && pipelineRes.data && pipelineRes.data.dag) { | |||||
| getExperimentIns(locationParams.id).then((experimentRes) => { | |||||
| if (experimentRes.code === 200) { | |||||
| setMessage(experimentRes.data); | |||||
| const experimentStatusObjs = JSON.parse(experimentRes.data.nodes_status); | |||||
| const newNodeList = JSON.parse(pipelineRes.data.dag).nodes.map((item) => { | |||||
| return { | return { | ||||
| ...item, | ...item, | ||||
| experimentEndTime: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].finishedAt, | |||||
| experimentStartTime: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].startedAt, | |||||
| experimentStatus: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].phase, | |||||
| component_id: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].id, | |||||
| img: | |||||
| experimentStatusObjs && | |||||
| experimentStatusObjs[item.id] && | |||||
| experimentStatusObjs[item.id].phase | |||||
| ? item.img.slice(0, item.img.length - 4) + | |||||
| '-' + | |||||
| experimentStatusObjs[item.id].phase + | |||||
| '.png' | |||||
| : item.img, | |||||
| experimentEndTime: experimentStatusObjs?.[item.id]?.finishedAt, | |||||
| experimentStartTime: experimentStatusObjs?.[item.id]?.startedAt, | |||||
| experimentStatus: experimentStatusObjs?.[item.id]?.phase, | |||||
| component_id: experimentStatusObjs?.[item.id]?.id, | |||||
| img: experimentStatusObjs?.[item.id]?.phase | |||||
| ? item.img.slice(0, item.img.length - 4) + | |||||
| '-' + | |||||
| experimentStatusObjs[item.id].phase + | |||||
| '.png' | |||||
| : item.img, | |||||
| }; | }; | ||||
| }); | }); | ||||
| const newData = { ...JSON.parse(ret.data.dag), nodes: newNodeList }; | |||||
| const newData = { ...JSON.parse(pipelineRes.data.dag), nodes: newNodeList }; | |||||
| getGraphData(newData); | getGraphData(newData); | ||||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||||
| // graph.render() | |||||
| }); | }); | ||||
| }; | }; | ||||
| // const getExperimentIn=(val)=>{ | |||||
| // getExperimentIns(val).then(ret=>{ | |||||
| // if(ret.code==200){ | |||||
| // console.log(JSON.parse(ret.data.nodes_status)); | |||||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | |||||
| // setTimeout(() => { | |||||
| // console.log(experimentStatusObj); | |||||
| // }, 1000); | |||||
| // } | |||||
| // }) | |||||
| // } | |||||
| useEffect(() => { | useEffect(() => { | ||||
| initGraph(); | initGraph(); | ||||
| getFirstWorkflow(locationParams.workflowId); | getFirstWorkflow(locationParams.workflowId); | ||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| // Update the refs whenever the state changes | |||||
| messageRef.current = message; | |||||
| }, [message]); | |||||
| const initGraph = () => { | const initGraph = () => { | ||||
| const fittingString = (str, maxWidth, fontSize) => { | const fittingString = (str, maxWidth, fontSize) => { | ||||
| @@ -375,6 +307,8 @@ function ExperimentText() { | |||||
| }, | }, | ||||
| // linkCenter: true, | // linkCenter: true, | ||||
| fitView: true, | fitView: true, | ||||
| minZoom: 0.5, | |||||
| maxZoom: 3, | |||||
| fitViewPadding: [320, 320, 220, 320], | fitViewPadding: [320, 320, 220, 320], | ||||
| }); | }); | ||||
| graph.on('node:click', handlerClick); | graph.on('node:click', handlerClick); | ||||
| @@ -386,40 +320,39 @@ function ExperimentText() { | |||||
| }; | }; | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={pipelineContainer}> | |||||
| <div className={styles.centerContainer}> | |||||
| <div className={styles.buttonList}> | |||||
| <div className={styles.allMessageItem}> | |||||
| 启动时间:{momnet(message.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| </div> | |||||
| <div className={styles.allMessageItem}> | |||||
| 执行时长: | |||||
| {message.finish_time | |||||
| ? elapsedTime(new Date(message.create_time), new Date(message.finish_time)) | |||||
| : elapsedTime(new Date(message.create_time), new Date())} | |||||
| </div> | |||||
| <div className={styles.allMessageItem}> | |||||
| 状态: | |||||
| <div | |||||
| style={{ | |||||
| width: '8px', | |||||
| height: '8px', | |||||
| borderRadius: '50%', | |||||
| marginRight: '6px', | |||||
| backgroundColor: experimentStatusInfo[message.status]?.color, | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: experimentStatusInfo[message.status]?.color }}> | |||||
| {experimentStatusInfo[message.status]?.label} | |||||
| </span> | |||||
| </div> | |||||
| <Button className={styles.param_button} onClick={openParamsModal}> | |||||
| 执行参数 | |||||
| </Button> | |||||
| <div className={styles['pipeline-container']}> | |||||
| <div className={styles['pipeline-container__top']}> | |||||
| <div className={styles['pipeline-container__top__info']}> | |||||
| 启动时间:{formatDate(message.create_time)} | |||||
| </div> | |||||
| <div className={styles['pipeline-container__top__info']}> | |||||
| 执行时长: | |||||
| {elapsedTime(message.create_time, message.finish_time)} | |||||
| </div> | |||||
| <div className={styles['pipeline-container__top__info']}> | |||||
| 状态: | |||||
| <div | |||||
| style={{ | |||||
| width: '8px', | |||||
| height: '8px', | |||||
| borderRadius: '50%', | |||||
| marginRight: '6px', | |||||
| backgroundColor: experimentStatusInfo[message.status]?.color, | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: experimentStatusInfo[message.status]?.color }}> | |||||
| {experimentStatusInfo[message.status]?.label} | |||||
| </span> | |||||
| </div> | </div> | ||||
| <div className={graphStyle} ref={graphRef} id={styles.graphStyle}></div> | |||||
| <Button | |||||
| className={styles['pipeline-container__top__param-button']} | |||||
| onClick={openParamsModal} | |||||
| > | |||||
| 执行参数 | |||||
| </Button> | |||||
| </div> | </div> | ||||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||||
| <div className={styles['pipeline-container__graph']} ref={graphRef}></div> | |||||
| <Props ref={propsRef}></Props> | |||||
| <ParamsModal | <ParamsModal | ||||
| open={paramsModalOpen} | open={paramsModalOpen} | ||||
| onCancel={closeParamsModal} | onCancel={closeParamsModal} | ||||
| @@ -1,90 +1,31 @@ | |||||
| #graph { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| .pipeline-container { | |||||
| height: 100%; | height: 100%; | ||||
| } | |||||
| .editPipelinePropsContent { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 43px; | |||||
| margin-bottom: 20px; | |||||
| padding: 0 24px; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| font-family: 'Alibaba'; | |||||
| background: #f8fbff; | |||||
| } | |||||
| .centerContainer { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| } | |||||
| .buttonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 56px; | |||||
| padding: 0 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| } | |||||
| .drawBox{ | |||||
| :global{ | |||||
| .ant-drawer .ant-drawer-body{ | |||||
| overflow: hidden; | |||||
| } | |||||
| } | |||||
| } | |||||
| .experimentDrawer{ | |||||
| :global{ | |||||
| .ant-tabs >.ant-tabs-nav .ant-tabs-nav-list{ | |||||
| margin-left: 24px; | |||||
| } | |||||
| .ant-drawer .ant-drawer-body{ | |||||
| overflow-y: hidden; | |||||
| background-color: #fff; | |||||
| &__top { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 56px; | |||||
| padding: 0 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__info { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-right: 30px; | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| font-size: 15px; | |||||
| } | } | ||||
| .ant-tabs { | |||||
| height: calc(100% - 160px); | |||||
| overflow-y: auto; | |||||
| &__param-button { | |||||
| margin-right: 0; | |||||
| margin-left: auto; | |||||
| } | } | ||||
| } | } | ||||
| } | |||||
| .detailBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 15px; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| padding-left: 24px; | |||||
| } | |||||
| .allMessageItem { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-right: 30px; | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| font-size: 15px; | |||||
| } | |||||
| .param_button { | |||||
| margin-right: 0; | |||||
| margin-left: auto; | |||||
| } | |||||
| .resultTop { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 10px 0; | |||||
| border-bottom: 1px solid #eee; | |||||
| } | |||||
| .resultContent { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| margin-bottom: 10px; | |||||
| padding: 0 20px 0 0; | |||||
| &__graph { | |||||
| width: 100%; | |||||
| height: calc(100% - 56px); | |||||
| background-color: @background-color; | |||||
| } | |||||
| } | } | ||||
| @@ -1,34 +0,0 @@ | |||||
| .log_group { | |||||
| padding-bottom: 10px; | |||||
| } | |||||
| .log_group_pod { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 15px; | |||||
| background: rgba(234, 234, 234, 0.5); | |||||
| cursor: pointer; | |||||
| &_name { | |||||
| margin-right: 10px; | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| .log_group_detail { | |||||
| padding: 15px; | |||||
| color: white; | |||||
| font-size: 14px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| background: #19253b; | |||||
| } | |||||
| .log_group_more_button { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| color: white; | |||||
| background: #19253b; | |||||
| } | |||||
| @@ -1,439 +0,0 @@ | |||||
| import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js'; | |||||
| import { elapsedTime } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||||
| import { Drawer, Form, Input, Tabs, message } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||||
| import LogList from './LogList'; | |||||
| import Styles from './index.less'; | |||||
| const { TextArea } = Input; | |||||
| const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const [form] = Form.useForm(); | |||||
| const [stagingItem, setStagingItem] = useState({}); | |||||
| const [resultObj, setResultObj] = useState([]); | |||||
| const [logList, setLogList] = useState([]); | |||||
| const statusObj = { | |||||
| Running: '运行中', | |||||
| Succeeded: '成功', | |||||
| Pending: '等待中', | |||||
| Failed: '失败', | |||||
| Error: '错误', | |||||
| Terminated: '终止', | |||||
| Skipped: '未执行', | |||||
| Omitted: '未执行', | |||||
| }; | |||||
| const statusColorObj = { | |||||
| Running: '#165bff', | |||||
| Succeeded: '#63a728', | |||||
| Pending: '#f981eb', | |||||
| Failed: '#c73131', | |||||
| Error: '#c73131', | |||||
| Terminated: '#8a8a8a', | |||||
| Skipped: '#8a8a8a', | |||||
| Omitted: '#8a8a8ae', | |||||
| }; | |||||
| const exportResult = (e, val) => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: val }); | |||||
| }; | |||||
| const timers = (time) => { | |||||
| let timer = new Date(time); | |||||
| let hours = timer.getHours(); //转换成时 | |||||
| let minutes = timer.getMinutes(); //转换成分 | |||||
| let secend = timer.getSeconds(); //转换成秒 | |||||
| let str = `${minutes}分${secend}秒`; | |||||
| return str; | |||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: '日志详情', | |||||
| children: <LogList list={logList} status={stagingItem.experimentStatus}></LogList>, | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: '配置参数', | |||||
| icon: <DatabaseOutlined />, | |||||
| children: ( | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 16, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| > | |||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/static-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 基本信息 | |||||
| </div> | |||||
| <Form.Item | |||||
| label="任务名称" | |||||
| name="label" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="任务ID" | |||||
| name="id" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务id', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 任务信息 | |||||
| </div> | |||||
| <Form.Item | |||||
| label="镜像" | |||||
| name="image" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="工作目录" name="working_directory"> | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="启动命令" name="command"> | |||||
| <TextArea disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="挂载路径" name="mount_path"> | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <Form.Item label="环境变量" name="env_variables"> | |||||
| <TextArea disabled /> | |||||
| </Form.Item> | |||||
| {stagingItem.control_strategy && | |||||
| Object.keys(stagingItem.control_strategy) && | |||||
| Object.keys(stagingItem.control_strategy).length > 0 | |||||
| ? Object.keys(stagingItem.control_strategy).map((item) => ( | |||||
| <Form.Item | |||||
| key={item} | |||||
| label={stagingItem.control_strategy[item].label} | |||||
| disabled | |||||
| name={item} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '13px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 输入参数 | |||||
| </div> | |||||
| {stagingItem.in_parameters && | |||||
| Object.keys(stagingItem.in_parameters) && | |||||
| Object.keys(stagingItem.in_parameters).length > 0 | |||||
| ? Object.keys(stagingItem.in_parameters).map((item) => ( | |||||
| <Form.Item | |||||
| key={item} | |||||
| label={stagingItem.in_parameters[item].label + '(' + item + ')'} | |||||
| name={item} | |||||
| disabled | |||||
| rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 输出参数 | |||||
| </div> | |||||
| {stagingItem.out_parameters && | |||||
| Object.keys(stagingItem.out_parameters) && | |||||
| Object.keys(stagingItem.out_parameters).length > 0 | |||||
| ? Object.keys(stagingItem.out_parameters).map((item) => ( | |||||
| <Form.Item | |||||
| key={item} | |||||
| label={stagingItem.out_parameters[item].label + '(' + item + ')'} | |||||
| disabled | |||||
| rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]} | |||||
| name={item} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| </Form> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: '输出结果', | |||||
| children: ( | |||||
| <div | |||||
| style={{ | |||||
| minHeight: '740px', | |||||
| background: '#f4f4f4', | |||||
| color: '#000', | |||||
| fontSize: '14px', | |||||
| padding: '0 10px 20px 20px', | |||||
| }} | |||||
| > | |||||
| {resultObj && resultObj.length > 0 | |||||
| ? resultObj.map((item) => ( | |||||
| <div key={item.name}> | |||||
| <div className={Styles.resultTop}> | |||||
| <span>{item.name}</span> | |||||
| <div style={{ display: 'flex' }}> | |||||
| <a | |||||
| onClick={(e) => { | |||||
| exportResult(e, item.path); | |||||
| }} | |||||
| style={{ marginRight: '10px' }} | |||||
| > | |||||
| 下载 | |||||
| </a> | |||||
| <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> | |||||
| </div> | |||||
| </div> | |||||
| <div style={{ margin: '15px 0' }} className={Styles.resultContent}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value && item.value.length > 0 | |||||
| ? item.value.map((ele) => ( | |||||
| <div className={Styles.resultContent} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | |||||
| )) | |||||
| : null} | |||||
| </div> | |||||
| )) | |||||
| : null} | |||||
| </div> | |||||
| ), | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| ]; | |||||
| const [open, setOpen] = useState(false); | |||||
| const afterOpenChange = () => { | |||||
| if (!open) { | |||||
| console.log(111, open); | |||||
| console.log(stagingItem, form.getFieldsValue()); | |||||
| for (let i in form.getFieldsValue()) { | |||||
| for (let j in stagingItem.in_parameters) { | |||||
| if (i == j) { | |||||
| console.log(j, i); | |||||
| stagingItem.in_parameters[j].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let p in stagingItem.out_parameters) { | |||||
| if (i == p) { | |||||
| stagingItem.out_parameters[p].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let k in stagingItem.control_strategy) { | |||||
| if (i == k) { | |||||
| stagingItem.control_strategy[k].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| } | |||||
| // setStagingItem({...stagingItem,}) | |||||
| console.log(stagingItem.control_strategy); | |||||
| onParentChange({ | |||||
| ...stagingItem, | |||||
| control_strategy: JSON.stringify(stagingItem.control_strategy), | |||||
| in_parameters: JSON.stringify(stagingItem.in_parameters), | |||||
| out_parameters: JSON.stringify(stagingItem.out_parameters), | |||||
| ...form.getFieldsValue(), | |||||
| }); | |||||
| // onParentChange({...stagingItem,...form.getFieldsValue()}) | |||||
| } | |||||
| }; | |||||
| const onClose = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| const onFinish = (values) => { | |||||
| console.log('Success:', values); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | |||||
| showDrawer(e, id, message) { | |||||
| setLogList([]); | |||||
| if (e.item && e.item.getModel().component_id) { | |||||
| const model = e.item.getModel() || {}; | |||||
| const start_time = moment(model.experimentStartTime).valueOf() * 1.0e6; | |||||
| const params = { | |||||
| task_id: model.id, | |||||
| component_id: model.component_id, | |||||
| name: message.argo_ins_name, | |||||
| namespace: message.argo_ins_ns, | |||||
| start_time: start_time, | |||||
| }; | |||||
| getQueryByExperimentLog(params).then((ret) => { | |||||
| const { log_type, pods, log_detail } = ret.data; | |||||
| if (log_type === 'normal') { | |||||
| const list = [ | |||||
| { | |||||
| ...log_detail, | |||||
| log_type, | |||||
| }, | |||||
| ]; | |||||
| setLogList(list); | |||||
| } else if (log_type === 'resource') { | |||||
| const list = pods.map((v) => ({ | |||||
| log_type, | |||||
| pod_name: v, | |||||
| log_content: '', | |||||
| start_time, | |||||
| })); | |||||
| setLogList(list); | |||||
| } | |||||
| getNodeResult({ id, node_id: e.item.getModel().id }).then((res) => { | |||||
| setResultObj(res.data); | |||||
| form.resetFields(); | |||||
| form.setFieldsValue({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| setStagingItem({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| setOpen(true); | |||||
| }); | |||||
| }); | |||||
| } else { | |||||
| form.resetFields(); | |||||
| form.setFieldsValue({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| setStagingItem({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| setOpen(true); | |||||
| } | |||||
| // console.log(e.item.getModel().in_parameters); | |||||
| }, | |||||
| })); | |||||
| return ( | |||||
| <div className={Styles.drawBox}> | |||||
| <Drawer | |||||
| title="任务执行详情" | |||||
| placement="right" | |||||
| rootStyle={{ marginTop: '68px' }} | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| afterOpenChange={afterOpenChange} | |||||
| open={open} | |||||
| width={420} | |||||
| className={Styles.experimentDrawer} | |||||
| destroyOnClose={true} | |||||
| > | |||||
| <div className={Styles.detailBox} style={{ marginTop: '15px' }}> | |||||
| 任务名称:{stagingItem.label} | |||||
| </div> | |||||
| <div className={Styles.detailBox}> | |||||
| 执行状态: | |||||
| <div | |||||
| style={{ | |||||
| width: '8px', | |||||
| height: '8px', | |||||
| borderRadius: '50%', | |||||
| marginRight: '6px', | |||||
| backgroundColor: statusColorObj[stagingItem.experimentStatus], | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: statusColorObj[stagingItem.experimentStatus] }}> | |||||
| {statusObj[stagingItem.experimentStatus]} | |||||
| </span> | |||||
| </div> | |||||
| <div className={Styles.detailBox}> | |||||
| 启动时间:{moment(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')} | |||||
| </div> | |||||
| <div className={Styles.detailBox}> | |||||
| 耗时: | |||||
| {stagingItem.experimentEndTime | |||||
| ? elapsedTime( | |||||
| new Date(stagingItem.experimentStartTime), | |||||
| new Date(stagingItem.experimentEndTime), | |||||
| ) | |||||
| : elapsedTime(new Date(stagingItem.experimentStartTime), new Date())} | |||||
| </div> | |||||
| <Tabs defaultActiveKey="1" items={items} /> | |||||
| </Drawer> | |||||
| </div> | |||||
| ); | |||||
| }); | |||||
| export default Props; | |||||
| @@ -0,0 +1,37 @@ | |||||
| .experiment-drawer { | |||||
| :global { | |||||
| .ant-drawer-body { | |||||
| overflow-y: hidden; | |||||
| } | |||||
| } | |||||
| &__tabs { | |||||
| height: calc(100% - 170px); | |||||
| :global { | |||||
| .ant-tabs-nav { | |||||
| padding-left: 24px; | |||||
| background-color: #f8fbff; | |||||
| border: 1px solid #e0eaff; | |||||
| } | |||||
| .ant-tabs-content-holder { | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__info { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 15px; | |||||
| padding-left: 24px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &__status-dot { | |||||
| width: 8px; | |||||
| height: 8px; | |||||
| margin-right: 6px; | |||||
| border-radius: 50%; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,171 @@ | |||||
| import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||||
| import { Drawer, Form, Tabs } from 'antd'; | |||||
| import dayjs from 'dayjs'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||||
| import ExperimentParameter from '../components/ExperimentParameter'; | |||||
| import ExperimentResult from '../components/ExperimentResult'; | |||||
| import LogList from '../components/LogList'; | |||||
| import { experimentStatusInfo } from '../status'; | |||||
| import styles from './props.less'; | |||||
| export type ExperimentLog = { | |||||
| log_type: 'normal' | 'resource'; // 日志类型 | |||||
| pod_name?: string; // 分布式名称 | |||||
| log_content?: string; // 日志内容 | |||||
| start_time?: string; // 日志开始时间 | |||||
| }; | |||||
| const Props = forwardRef((_, ref) => { | |||||
| const [form] = Form.useForm(); | |||||
| const [experimentNodeData, setExperimentNodeData] = useState<PipelineNodeModelSerialize>( | |||||
| {} as PipelineNodeModelSerialize, | |||||
| ); | |||||
| const [experimentResults, setExperimentResults] = useState([]); | |||||
| const [experimentLogList, setExperimentLogList] = useState<ExperimentLog[]>([]); | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: '日志详情', | |||||
| children: ( | |||||
| <LogList list={experimentLogList} status={experimentNodeData.experimentStatus}></LogList> | |||||
| ), | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: '配置参数', | |||||
| icon: <DatabaseOutlined />, | |||||
| children: <ExperimentParameter form={form} nodeData={experimentNodeData} />, | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: '输出结果', | |||||
| children: <ExperimentResult results={experimentResults}></ExperimentResult>, | |||||
| icon: <ProfileOutlined />, | |||||
| }, | |||||
| ]; | |||||
| const [open, setOpen] = useState(false); | |||||
| const onClose = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| // 获取实验日志 | |||||
| const getExperimentLog = async (params: any, start_time: number) => { | |||||
| const [res] = await to(getQueryByExperimentLog(params)); | |||||
| if (res && res.data) { | |||||
| const { log_type, pods, log_detail } = res.data; | |||||
| if (log_type === 'normal') { | |||||
| const list = [ | |||||
| { | |||||
| ...log_detail, | |||||
| log_type, | |||||
| }, | |||||
| ]; | |||||
| setExperimentLogList(list); | |||||
| } else if (log_type === 'resource') { | |||||
| const list = pods.map((v: string) => ({ | |||||
| log_type, | |||||
| pod_name: v, | |||||
| log_content: '', | |||||
| start_time, | |||||
| })); | |||||
| setExperimentLogList(list); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 获取实验结果 | |||||
| const getExperimentResult = async (params: any) => { | |||||
| const [res] = await to(getNodeResult(params)); | |||||
| if (res && res.data) { | |||||
| setExperimentResults(res.data); | |||||
| } | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | |||||
| showDrawer(e: any, id: string, message: any) { | |||||
| setOpen(true); | |||||
| // 获取实验参数 | |||||
| const model = e.item.getModel(); | |||||
| try { | |||||
| const nodeData = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| setExperimentNodeData(nodeData); | |||||
| form.setFieldsValue(nodeData); | |||||
| } catch (error) { | |||||
| console.log(error); | |||||
| } | |||||
| // 获取实验日志和实验结果 | |||||
| setExperimentLogList([]); | |||||
| setExperimentResults([]); | |||||
| if (e.item && e.item.getModel()) { | |||||
| const model = e.item.getModel(); | |||||
| const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; | |||||
| const params = { | |||||
| task_id: model.id, | |||||
| component_id: model.component_id, | |||||
| name: message.argo_ins_name, | |||||
| namespace: message.argo_ins_ns, | |||||
| start_time: start_time, | |||||
| }; | |||||
| getExperimentLog(params, start_time); | |||||
| getExperimentResult({ id, node_id: model.id }); | |||||
| } | |||||
| }, | |||||
| })); | |||||
| return ( | |||||
| <Drawer | |||||
| title="任务执行详情" | |||||
| placement="right" | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| open={open} | |||||
| width={520} | |||||
| className={styles['experiment-drawer']} | |||||
| destroyOnClose={true} | |||||
| > | |||||
| <div style={{ paddingTop: '15px' }}> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 任务名称:{experimentNodeData.label} | |||||
| </div> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 执行状态: | |||||
| <div | |||||
| className={styles['experiment-drawer__status-dot']} | |||||
| style={{ | |||||
| backgroundColor: experimentStatusInfo[experimentNodeData.experimentStatus]?.color, | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: experimentStatusInfo[experimentNodeData.experimentStatus]?.color }}> | |||||
| {experimentStatusInfo[experimentNodeData.experimentStatus]?.label} | |||||
| </span> | |||||
| </div> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 启动时间:{formatDate(experimentNodeData.experimentStartTime)} | |||||
| </div> | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| 耗时: | |||||
| {elapsedTime( | |||||
| experimentNodeData.experimentStartTime, | |||||
| experimentNodeData.experimentEndTime, | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| <Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} /> | |||||
| </Drawer> | |||||
| ); | |||||
| }); | |||||
| export default Props; | |||||
| @@ -14,16 +14,15 @@ import { | |||||
| } from '@/services/experiment/index.js'; | } from '@/services/experiment/index.js'; | ||||
| import { getWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflow } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { elapsedTime } from '@/utils/date'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { Button, ConfigProvider, Space, Table, message } from 'antd'; | import { Button, ConfigProvider, Space, Table, message } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import momnet from 'moment'; | |||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import AddExperimentModal from './components/AddExperimentModal'; | |||||
| import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus'; | import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus'; | ||||
| import AddExperimentModal from './experimentText/addExperimentModal'; | |||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| import { experimentStatusInfo } from './status'; | import { experimentStatusInfo } from './status'; | ||||
| @@ -438,13 +437,9 @@ function Experiment() { | |||||
| </div> | </div> | ||||
| <div className={Styles.description}> | <div className={Styles.description}> | ||||
| <div style={{ width: '50%' }}> | <div style={{ width: '50%' }}> | ||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| </div> | |||||
| <div style={{ width: '50%' }}> | |||||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| {elapsedTime(item.create_time, item.finish_time)} | |||||
| </div> | </div> | ||||
| <div style={{ width: '50%' }}>{formatDate(item.create_time)}</div> | |||||
| </div> | </div> | ||||
| <div className={Styles.statusBox}> | <div className={Styles.statusBox}> | ||||
| <img | <img | ||||
| @@ -9,9 +9,9 @@ | |||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__type { | |||||
| color: @text-color; | |||||
| font-size: @font-size-input-lg; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -12,7 +12,7 @@ import { CommonTabKeys } from '@/enums'; | |||||
| import { createMirrorReq } from '@/services/mirror'; | import { createMirrorReq } from '@/services/mirror'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage'; | import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage'; | ||||
| import { getFileListFromEvent } from '@/utils/ui'; | |||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| @@ -75,30 +75,16 @@ function MirrorCreate() { | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| const fileList = formData['fileList'] ?? []; | const fileList = formData['fileList'] ?? []; | ||||
| if (fileList.length === 0) { | |||||
| message.error('请上传文件'); | |||||
| return; | |||||
| if (validateUploadFiles(fileList)) { | |||||
| const file = fileList[0]; | |||||
| params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | |||||
| path: file.response.data.url, | |||||
| file_size: file.response.data.fileSize, | |||||
| upload_type: 1, | |||||
| image_type: 0, | |||||
| }; | |||||
| } | } | ||||
| const file = fileList[0]; | |||||
| if (file.status === 'uploading') { | |||||
| message.error('请等待文件上传完成'); | |||||
| return; | |||||
| } else if (file.status === 'error') { | |||||
| message.error('文件上传失败,请重新上传文件'); | |||||
| return; | |||||
| } | |||||
| if (!file.response || !file.response.data) { | |||||
| message.error('文件上传失败,请重新上传文件'); | |||||
| return; | |||||
| } | |||||
| params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | |||||
| path: file.response.data.url, | |||||
| file_size: file.response.data.fileSize, | |||||
| upload_type: 1, | |||||
| image_type: 0, | |||||
| }; | |||||
| } | } | ||||
| const [res] = await to(createMirrorReq(params)); | const [res] = await to(createMirrorReq(params)); | ||||
| @@ -118,6 +104,7 @@ function MirrorCreate() { | |||||
| navgite(-1); | navgite(-1); | ||||
| }; | }; | ||||
| // 上传前认证 | |||||
| const beforeUpload: UploadProps['beforeUpload'] = () => { | const beforeUpload: UploadProps['beforeUpload'] = () => { | ||||
| const fileList = form.getFieldValue('fileList'); | const fileList = form.getFieldValue('fileList'); | ||||
| if (Array.isArray(fileList) && fileList.length >= 1) { | if (Array.isArray(fileList) && fileList.length >= 1) { | ||||
| @@ -134,12 +121,13 @@ function MirrorCreate() { | |||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| name="mirror-create" | name="mirror-create" | ||||
| labelCol={{ flex: '120px' }} | |||||
| labelCol={{ flex: '130px' }} | |||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| initialValues={{ upload_type: CommonTabKeys.Public }} | initialValues={{ upload_type: CommonTabKeys.Public }} | ||||
| onFinish={handleSubmit} | onFinish={handleSubmit} | ||||
| size="large" | |||||
| > | > | ||||
| <SubAreaTitle | <SubAreaTitle | ||||
| title="基本信息" | title="基本信息" | ||||
| @@ -242,7 +230,7 @@ function MirrorCreate() { | |||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item label="仓库类型" required> | <Form.Item label="仓库类型" required> | ||||
| <span>公网</span> | |||||
| <span className={styles['mirror-create__content__type']}>公网</span> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -16,6 +16,7 @@ import { | |||||
| getMirrorVersionListReq, | getMirrorVersionListReq, | ||||
| } from '@/services/mirror'; | } from '@/services/mirror'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| @@ -32,7 +33,6 @@ import { | |||||
| type TableProps, | type TableProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import dayjs from 'dayjs'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import MirrorStatusCell from './components/MirrorStatusCell'; | import MirrorStatusCell from './components/MirrorStatusCell'; | ||||
| import styles from './info.less'; | import styles from './info.less'; | ||||
| @@ -83,8 +83,7 @@ function MirrorInfo() { | |||||
| const [res] = await to(getMirrorInfoReq(id)); | const [res] = await to(getMirrorInfoReq(id)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { name = '', description = '', version_count = '', create_time: time } = res.data; | const { name = '', description = '', version_count = '', create_time: time } = res.data; | ||||
| let create_time = | |||||
| time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--'; | |||||
| const create_time = formatDate(time); | |||||
| setMirrorInfo({ | setMirrorInfo({ | ||||
| name, | name, | ||||
| description, | description, | ||||
| @@ -161,13 +160,13 @@ function MirrorInfo() { | |||||
| dataIndex: 'tag_name', | dataIndex: 'tag_name', | ||||
| key: 'tag_name', | key: 'tag_name', | ||||
| width: '25%', | width: '25%', | ||||
| render: CommonTableCell, | |||||
| render: CommonTableCell(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| render: CommonTableCell, | |||||
| render: CommonTableCell(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| @@ -181,7 +180,7 @@ function MirrorInfo() { | |||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: 150, | width: 150, | ||||
| render: CommonTableCell, | |||||
| render: CommonTableCell(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| @@ -3,7 +3,7 @@ | |||||
| &__tabs-container { | &__tabs-container { | ||||
| height: 50px; | height: 50px; | ||||
| padding-left: 27px; | padding-left: 27px; | ||||
| background-image: url('../../assets/img/page-title-bg.png'); | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| } | } | ||||
| &__content { | &__content { | ||||
| @@ -20,7 +20,7 @@ | |||||
| } | } | ||||
| &__table { | &__table { | ||||
| height: calc(100% - 34px - 28px); | |||||
| height: calc(100% - 32px - 28px); | |||||
| margin-top: 28px; | margin-top: 28px; | ||||
| } | } | ||||
| } | } | ||||
| @@ -54,6 +54,7 @@ function MirrorList() { | |||||
| const [cacheState, setCacheState] = useCacheState(); | const [cacheState, setCacheState] = useCacheState(); | ||||
| const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | ||||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | const [searchText, setSearchText] = useState(cacheState?.searchText); | ||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | const [tableData, setTableData] = useState<MirrorData[]>([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | const [pagination, setPagination] = useState<TablePaginationConfig>( | ||||
| @@ -65,11 +66,12 @@ function MirrorList() { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getMirrorList(); | getMirrorList(); | ||||
| }, [activeTab, pagination]); | |||||
| }, [activeTab, pagination, searchText]); | |||||
| // 切换 Tab,重置数据 | // 切换 Tab,重置数据 | ||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | const hanleTabChange: TabsProps['onChange'] = (value) => { | ||||
| setSearchText(''); | setSearchText(''); | ||||
| setInputText(''); | |||||
| setPagination({ | setPagination({ | ||||
| current: 1, | current: 1, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| @@ -78,16 +80,16 @@ function MirrorList() { | |||||
| setTableData([]); | setTableData([]); | ||||
| setActiveTab(value); | setActiveTab(value); | ||||
| }; | }; | ||||
| // 获取镜像列表 | // 获取镜像列表 | ||||
| const getMirrorList = async (params?: Record<string, any>) => { | |||||
| const reqParams = { | |||||
| const getMirrorList = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| name: searchText, | name: searchText, | ||||
| image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | ||||
| ...params, | |||||
| }; | }; | ||||
| const [res] = await to(getMirrorListReq(reqParams)); | |||||
| const [res] = await to(getMirrorListReq(params)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| setTableData(content); | setTableData(content); | ||||
| @@ -116,10 +118,7 @@ function MirrorList() { | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch: SearchProps['onSearch'] = (value) => { | const onSearch: SearchProps['onSearch'] = (value) => { | ||||
| // 带参数是为了点清除时,searchText 更新不及时的问题 | |||||
| getMirrorList({ | |||||
| name: value, | |||||
| }); | |||||
| setSearchText(value); | |||||
| }; | }; | ||||
| // 查看详情 | // 查看详情 | ||||
| @@ -167,21 +166,21 @@ function MirrorList() { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| width: '30%', | width: '30%', | ||||
| render: CommonTableCell, | |||||
| render: CommonTableCell(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '版本数据', | title: '版本数据', | ||||
| dataIndex: 'version_count', | dataIndex: 'version_count', | ||||
| key: 'version_count', | key: 'version_count', | ||||
| width: 100, | width: 100, | ||||
| render: CommonTableCell, | |||||
| render: CommonTableCell(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像描述', | title: '镜像描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| render: CommonTableCell, | |||||
| ellipsis: true, | |||||
| render: CommonTableCell(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| @@ -233,12 +232,7 @@ function MirrorList() { | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-list']}> | <div className={styles['mirror-list']}> | ||||
| <div className={styles['mirror-list__tabs-container']}> | <div className={styles['mirror-list__tabs-container']}> | ||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| items={mirrorTabItems} | |||||
| onChange={hanleTabChange} | |||||
| className={styles['model-tabs']} | |||||
| /> | |||||
| <Tabs activeKey={activeTab} items={mirrorTabItems} onChange={hanleTabChange} /> | |||||
| </div> | </div> | ||||
| <div className={styles['mirror-list__content']}> | <div className={styles['mirror-list__content']}> | ||||
| <div className={styles['mirror-list__content__filter']}> | <div className={styles['mirror-list__content__filter']}> | ||||
| @@ -246,9 +240,9 @@ function MirrorList() { | |||||
| placeholder="按数据集名称筛选" | placeholder="按数据集名称筛选" | ||||
| allowClear | allowClear | ||||
| onSearch={onSearch} | onSearch={onSearch} | ||||
| onChange={(e) => setSearchText(e.target.value)} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | style={{ width: 300 }} | ||||
| value={searchText} | |||||
| value={inputText} | |||||
| /> | /> | ||||
| {activeTab === CommonTabKeys.Private && ( | {activeTab === CommonTabKeys.Private && ( | ||||
| <Button | <Button | ||||
| @@ -1,89 +1,7 @@ | |||||
| import { Form, Input, Tabs } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import Styles from './index.less'; | |||||
| import PersonalData from './personalData'; | |||||
| import PublicData from './publicData'; | |||||
| // import {getModelList} from '@/services/dataset/index.js' | |||||
| const { Search } = Input; | |||||
| const { TabPane } = Tabs; | |||||
| const leftdataList = [1, 2, 3]; | |||||
| import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | |||||
| import { ResourceType } from '@/pages/Dataset/type'; | |||||
| const Dataset = () => { | |||||
| const [queryFlow, setQueryFlow] = useState({ | |||||
| page: 0, | |||||
| size: 10, | |||||
| name: null, | |||||
| }); | |||||
| const navgite = useNavigate(); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetList, setDatasetList] = useState([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [form] = Form.useForm(); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | |||||
| // const getModelLists=()=>{ | |||||
| // getModelList(queryFlow).then(ret=>{ | |||||
| // console.log(ret); | |||||
| // if(ret.code==200){ | |||||
| // setDatasetList(ret.data.content) | |||||
| // setTotal(ret.data.totalElements) | |||||
| // } | |||||
| // }) | |||||
| // } | |||||
| const showModal = () => { | |||||
| form.resetFields(); | |||||
| setDialogTitle('新建数据集'); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const handleOk = () => { | |||||
| console.log(1111); | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const onFinish = (values) => {}; | |||||
| const routeToIntro = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| navgite({ pathname: '/dataset/dataset' }); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| useEffect(() => { | |||||
| return () => {}; | |||||
| }, []); | |||||
| return ( | |||||
| <div className={Styles.datasetBox}> | |||||
| <div className={Styles.datasetTopBox}></div> | |||||
| <div className={Styles.datasetAllBox}> | |||||
| <Tabs defaultActiveKey="1"> | |||||
| <TabPane | |||||
| tab="模型广场" | |||||
| key="1" | |||||
| icon={ | |||||
| <svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true"> | |||||
| <use xlinkHref="#icon-shujujiguangchang"></use> | |||||
| </svg> | |||||
| } | |||||
| > | |||||
| <PublicData /> | |||||
| </TabPane> | |||||
| <TabPane | |||||
| tab="个人模型" | |||||
| key="2" | |||||
| icon={ | |||||
| <svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true"> | |||||
| <use xlinkHref="#icon-gerenshujuji"></use> | |||||
| </svg> | |||||
| } | |||||
| > | |||||
| <PersonalData /> | |||||
| </TabPane> | |||||
| </Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| const ModelPage = () => { | |||||
| return <ResourcePage resourceType={ResourceType.Model} />; | |||||
| }; | }; | ||||
| export default Dataset; | |||||
| export default ModelPage; | |||||
| @@ -1,327 +0,0 @@ | |||||
| .datasetTopBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 49px; | |||||
| padding: 0 30px; | |||||
| padding-right: 30px; | |||||
| background-image: url(/assets/images/pipeline-back.png); | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| font-family: 'Alibaba'; | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .datasetAllBox { | |||||
| :global { | |||||
| .ant-tabs-nav .ant-tabs-nav-wrap { | |||||
| margin: -48px 0 0 30px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .datasetCneterBox { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 87.5vh; | |||||
| .datasetCneterLeftBox { | |||||
| width: 340px; | |||||
| height: 100%; | |||||
| margin-right: 10px; | |||||
| padding-top: 15px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| .custTab { | |||||
| display: flex; | |||||
| height: 32px; | |||||
| border-bottom: 1px solid #e0eaff; | |||||
| .tabItem { | |||||
| width: 52px; | |||||
| height: 100%; | |||||
| color: #808080; | |||||
| font-size: 15px; | |||||
| text-align: center; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| .leftContentBox { | |||||
| max-height: 80vh; | |||||
| padding: 15px 20px; | |||||
| overflow-x: hidden; | |||||
| overflow-y: auto; | |||||
| .itemTitle { | |||||
| margin-bottom: 15px; | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| } | |||||
| .itemBox { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| align-content: start; | |||||
| width: 110%; | |||||
| .messageBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 92px; | |||||
| height: 62px; | |||||
| margin: 0 12px 20px 0; | |||||
| padding: 11px 0px 7px 0px; | |||||
| color: #1d1d20; | |||||
| font-size: 12px; | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.05); | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| .ptIcon { | |||||
| display: block; | |||||
| } | |||||
| .hoverIcon { | |||||
| display: none; | |||||
| } | |||||
| .messageText { | |||||
| width: 65px; | |||||
| overflow: hidden; | |||||
| white-space: nowrap; | |||||
| text-align: center; | |||||
| text-overflow: ellipsis; | |||||
| transition: all 0.2s; | |||||
| } | |||||
| } | |||||
| .messageBox:hover { | |||||
| background: rgba(22, 100, 255, 0.03); | |||||
| border: 1px solid; | |||||
| border-color: #1664ff; | |||||
| .ptIcon { | |||||
| display: none; | |||||
| } | |||||
| .hoverIcon { | |||||
| display: block; | |||||
| } | |||||
| } | |||||
| .active { | |||||
| background: rgba(22, 100, 255, 0.03) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: #1664ff !important; | |||||
| .ptIcon { | |||||
| display: none !important; | |||||
| } | |||||
| .hoverIcon { | |||||
| display: block !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .datasetCneterRightBox { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| height: 100%; | |||||
| overflow-y: auto; | |||||
| padding: 22px 30px 26px 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| .dataSource { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin-bottom: 30px; | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| font-size: 15px; | |||||
| } | |||||
| .dataContent { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-wrap: wrap; | |||||
| align-content: flex-start; | |||||
| width: 100%; | |||||
| .dataItem { | |||||
| position: relative; | |||||
| width: 23.8%; | |||||
| height:164px; | |||||
| margin: 0 20px 25px 0; | |||||
| background: #ffffff; | |||||
| border: 1px solid; | |||||
| border-color: #eaeaea; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| .dropdown{ | |||||
| position: absolute; | |||||
| right: 20px; | |||||
| top: 15px; | |||||
| } | |||||
| .itemText { | |||||
| position: absolute; | |||||
| top: 20px; | |||||
| left: 20px; | |||||
| height: 6px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: 'Alibaba'; | |||||
| line-height: 0px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| rgba(22, 100, 255, 0.3) 0, | |||||
| rgba(22, 100, 255, 0) 100% | |||||
| ); | |||||
| } | |||||
| .itemDescripition { | |||||
| position: absolute; | |||||
| top: 57px; | |||||
| left: 20px; | |||||
| display: -webkit-box; | |||||
| padding-right: 28px; | |||||
| overflow: hidden; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| word-break: break-all; | |||||
| -webkit-line-clamp: 2; | |||||
| -webkit-box-orient: vertical; | |||||
| } | |||||
| .itemTime { | |||||
| position: absolute; | |||||
| bottom: 22px; | |||||
| left: 20px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | |||||
| font-size: 13px; | |||||
| } | |||||
| .itemIcon { | |||||
| position: absolute; | |||||
| right: 20px; | |||||
| bottom: 22px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | |||||
| font-size: 13px; | |||||
| } | |||||
| } | |||||
| .dataItem:hover { | |||||
| border-color: #1664ff; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | |||||
| .dataItem:hover .itemText { | |||||
| color: #1664ff; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .tipContent{ | |||||
| color: #c73131; | |||||
| margin-top: 5px; | |||||
| } | |||||
| .modal { | |||||
| :global { | |||||
| .ant-modal-content { | |||||
| width: 825px; | |||||
| padding: 20px 67px; | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100%; | |||||
| border-radius: 21px; | |||||
| } | |||||
| .ant-modal-header { | |||||
| margin: 20px 0; | |||||
| background-color: transparent; | |||||
| } | |||||
| .ant-input { | |||||
| height: 40px; | |||||
| border-color: #e6e6e6; | |||||
| } | |||||
| .ant-form-item .ant-form-item-label > label { | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| } | |||||
| .ant-modal-footer { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| margin: 40px 0 30px 0; | |||||
| } | |||||
| .ant-btn { | |||||
| width: 110px; | |||||
| height: 40px; | |||||
| font-size: 18px; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border-color: transparent; | |||||
| border-radius: 10px; | |||||
| } | |||||
| .ant-btn-primary { | |||||
| background: #1664ff; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,61 +1,33 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||||
| import { ResourceType } from '@/pages/Dataset/type'; | |||||
| import { | import { | ||||
| addModelsVersionDetail, | |||||
| deleteModelVersion, | deleteModelVersion, | ||||
| getModelById, | getModelById, | ||||
| getModelVersionIdList, | getModelVersionIdList, | ||||
| getModelVersionsById, | getModelVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { formatDate } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { Button, Input, Select, Table, Tabs, message } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useParams } from 'react-router-dom'; | |||||
| import Styles from './index.less'; | |||||
| import Styles from './intro.less'; | |||||
| const { Search } = Input; | const { Search } = Input; | ||||
| const { TabPane } = Tabs; | const { TabPane } = Tabs; | ||||
| const Dataset = () => { | const Dataset = () => { | ||||
| const props = { | |||||
| action: '/api/mmp/dataset/upload', | |||||
| // headers: { | |||||
| // 'X-Requested-With': null | |||||
| // }, | |||||
| headers: { | |||||
| Authorization: getAccessToken(), | |||||
| 'X-Requested-With': null, | |||||
| }, | |||||
| onChange({ file, fileList }) { | |||||
| if (file.status !== 'uploading') { | |||||
| console.log(file, fileList); | |||||
| setFormList( | |||||
| fileList.map((item) => { | |||||
| return { | |||||
| ...form.getFieldsValue(), | |||||
| models_id: locationParams.id, | |||||
| file_name: item.response.code === 200 ? item.response.data[0].fileName : null, | |||||
| file_size: item.response.code === 200 ? item.response.data[0].fileSize : null, | |||||
| url: item.response.code === 200 ? item.response.data[0].url : null, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| } | |||||
| }, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| const [form] = Form.useForm(); | |||||
| const [formList, setFormList] = useState([]); | const [formList, setFormList] = useState([]); | ||||
| const [dialogTitle, setDialogTitle] = useState('新建版本'); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | const [datasetDetailObj, setDatasetDetailObj] = useState({}); | ||||
| const [version, setVersion] = useState(null); | const [version, setVersion] = useState(null); | ||||
| const [versionList, setVersionList] = useState([]); | const [versionList, setVersionList] = useState([]); | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| console.log(locationParams); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [wordList, setWordList] = useState([]); | const [wordList, setWordList] = useState([]); | ||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const getModelByDetail = () => { | const getModelByDetail = () => { | ||||
| getModelById(locationParams.id).then((ret) => { | getModelById(locationParams.id).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| @@ -76,6 +48,9 @@ const Dataset = () => { | |||||
| ); | ); | ||||
| setVersion(ret.data[0]); | setVersion(ret.data[0]); | ||||
| getModelVersions({ version: ret.data[0], models_id: locationParams.id }); | getModelVersions({ version: ret.data[0], models_id: locationParams.id }); | ||||
| } else { | |||||
| setVersion(null); | |||||
| setWordList([]); | |||||
| } | } | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -85,29 +60,21 @@ const Dataset = () => { | |||||
| return () => {}; | return () => {}; | ||||
| }, []); | }, []); | ||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields(); | |||||
| form.setFieldsValue({ name: datasetDetailObj.name }); | |||||
| setDialogTitle('创建新版本'); | |||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: ResourceType.Model, | |||||
| resourceId: locationParams.id, | |||||
| initialName: datasetDetailObj.name, | |||||
| onOk: () => { | |||||
| getModelVersionsList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | }; | ||||
| const deleteDataset = () => { | const deleteDataset = () => { | ||||
| Modal.confirm({ | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该模型版本将不可恢复</div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | okText: '确认', | ||||
| cancelText: '取消', | cancelText: '取消', | ||||
| @@ -119,13 +86,7 @@ const Dataset = () => { | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| const onFinish = () => { | |||||
| addModelsVersionDetail(formList).then((ret) => { | |||||
| getModelVersionsList(); | |||||
| setIsModalOpen(false); | |||||
| message.success('创建成功'); | |||||
| }); | |||||
| }; | |||||
| const getModelVersions = (params) => { | const getModelVersions = (params) => { | ||||
| getModelVersionIdList(params).then((ret) => { | getModelVersionIdList(params).then((ret) => { | ||||
| setWordList(ret?.data?.content ?? []); | setWordList(ret?.data?.content ?? []); | ||||
| @@ -151,9 +112,7 @@ const Dataset = () => { | |||||
| setVersion(''); | setVersion(''); | ||||
| } | } | ||||
| }; | }; | ||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| @@ -185,7 +144,7 @@ const Dataset = () => { | |||||
| title: '更新时间', | title: '更新时间', | ||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| render: (text) => <span>{formatDate(text)}</span>, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -262,15 +221,18 @@ const Dataset = () => { | |||||
| <div | <div | ||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | ||||
| > | > | ||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| @@ -293,104 +255,6 @@ const Dataset = () => { | |||||
| </TabPane> | </TabPane> | ||||
| </Tabs> | </Tabs> | ||||
| </div> | </div> | ||||
| <Modal | |||||
| title={ | |||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||||
| <img | |||||
| style={{ width: '20px', marginRight: '10px' }} | |||||
| src={`/assets/images/pipeline-edit-icon.png`} | |||||
| alt="" | |||||
| /> | |||||
| {dialogTitle} | |||||
| </div> | |||||
| } | |||||
| open={isModalOpen} | |||||
| className={Styles.modal} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| onCancel={handleCancel} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="模型名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled placeholder="请输入模型名称" /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型版本" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型版本', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={256} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型文件" | |||||
| name="dataset_version_vos" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请上传模型文件', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button | |||||
| style={{ | |||||
| fontSize: '14px', | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| }} | |||||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||||
| > | |||||
| 上传文件 | |||||
| </Button> | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </Modal> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -0,0 +1,78 @@ | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| font-family: 'Alibaba'; | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .tipContent { | |||||
| margin-top: 5px; | |||||
| color: #c73131; | |||||
| } | |||||
| @@ -1,525 +0,0 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import clock from '@/assets/img/clock.png'; | |||||
| import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import deleteIcon from '@/assets/img/delete-icon.png'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { addModel, deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Pagination, Select, Upload, message } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import Styles from './index.less'; | |||||
| const { Search } = Input; | |||||
| const leftdataList = [1, 2, 3]; | |||||
| const PublicData = () => { | |||||
| const props = { | |||||
| action: '/api/mmp/dataset/upload', | |||||
| // headers: { | |||||
| // 'X-Requested-With': null | |||||
| // }, | |||||
| headers: { | |||||
| Authorization: getAccessToken(), | |||||
| 'X-Requested-With': null, | |||||
| }, | |||||
| onChange({ file, fileList }) { | |||||
| if (file.status !== 'uploading') { | |||||
| console.log(file, fileList); | |||||
| form.setFieldsValue({ | |||||
| models_version_vos: fileList.map((item) => { | |||||
| const data = item.response.data[0]; | |||||
| return { | |||||
| file_name: data.fileName, | |||||
| file_size: data.fileSize, | |||||
| url: data.url, | |||||
| }; | |||||
| }), | |||||
| }); | |||||
| } | |||||
| }, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| const [queryFlow, setQueryFlow] = useState({ | |||||
| page: 0, | |||||
| size: 20, | |||||
| name: null, | |||||
| available_range: 0, | |||||
| }); | |||||
| const navgite = useNavigate(); | |||||
| const [iconParams, setIconParams] = useState({ | |||||
| name: null, | |||||
| page: 0, | |||||
| size: 10000, | |||||
| }); | |||||
| const [activeType, setActiveType] = useState(null); | |||||
| const [activeTag, setActiveTag] = useState(null); | |||||
| const [modelTypeList, setmodelTypeList] = useState([]); | |||||
| const [modelDirectionList, setmodelDirectionList] = useState([]); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetList, setDatasetList] = useState([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [form] = Form.useForm(); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建模型'); | |||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const getModelLists = (queryFlow) => { | |||||
| getModelList(queryFlow).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200) { | |||||
| setDatasetList(ret.data.content); | |||||
| setTotal(ret.data.totalElements); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const showModal = () => { | |||||
| form.resetFields(); | |||||
| setDialogTitle('新建模型'); | |||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const getAssetIconList = (params) => { | |||||
| getAssetIcon(params).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||||
| setmodelTypeList(ret.data.content.filter((item) => item.category_id == 3)); | |||||
| setmodelDirectionList(ret.data.content.filter((item) => item.category_id == 4)); | |||||
| } else { | |||||
| setmodelTypeList([]); | |||||
| setmodelDirectionList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const onSearch = (values) => { | |||||
| console.log(values); | |||||
| getAssetIconList({ ...iconParams, name: values }); | |||||
| }; | |||||
| const nameSearch = (values) => { | |||||
| console.log(values); | |||||
| getModelLists({ ...queryFlow, name: values }); | |||||
| }; | |||||
| const handleOk = () => { | |||||
| console.log(1111); | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const onFinish = (values) => { | |||||
| const params = { | |||||
| ...values, | |||||
| available_range: 0, | |||||
| }; | |||||
| addModel(values).then((ret) => { | |||||
| console.log(ret); | |||||
| getModelLists(queryFlow); | |||||
| setIsModalOpen(false); | |||||
| }); | |||||
| }; | |||||
| const chooseModelType = (val, item) => { | |||||
| console.log(val, item); | |||||
| if (item.id == queryFlow.model_type) { | |||||
| setActiveType(''); | |||||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||||
| getModelLists({ ...queryFlow, model_type: null }); | |||||
| } else { | |||||
| setActiveType(item.id); | |||||
| setQueryFlow({ ...queryFlow, model_type: item.id }); | |||||
| getModelLists({ ...queryFlow, model_type: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const chooseModelTag = (val, item) => { | |||||
| if (item.id == queryFlow.model_tag) { | |||||
| setActiveTag(''); | |||||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||||
| getModelLists({ ...queryFlow, model_tag: null }); | |||||
| } else { | |||||
| setActiveTag(item.id); | |||||
| setQueryFlow({ ...queryFlow, model_tag: item.id }); | |||||
| getModelLists({ ...queryFlow, model_tag: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const routeToIntro = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| console.log(record); | |||||
| navgite({ pathname: `/dataset/model/${record.id}` }); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const onPageChange = (pageNum, pageSize) => { | |||||
| console.log(pageNum, pageSize); | |||||
| setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getAssetIconList(iconParams); | |||||
| getModelLists(queryFlow); | |||||
| return () => {}; | |||||
| }, []); | |||||
| return ( | |||||
| <> | |||||
| <div className={Styles.datasetCneterBox}> | |||||
| <div className={Styles.datasetCneterLeftBox}> | |||||
| <div className={Styles.leftContentBox}> | |||||
| <Search | |||||
| placeholder="搜索" | |||||
| allowClear | |||||
| onSearch={onSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| marginBottom: '15px', | |||||
| }} | |||||
| /> | |||||
| <div className={Styles.itemTitle}>模型框架</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {modelTypeList && modelTypeList.length > 0 | |||||
| ? modelTypeList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeType ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseModelType(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span | |||||
| className={Styles.messageText} | |||||
| onClick={(e) => { | |||||
| chooseModelTag(e, item); | |||||
| }} | |||||
| > | |||||
| {item.name} | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| <div className={Styles.itemTitle}>模型能力</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {modelDirectionList && modelDirectionList.length > 0 | |||||
| ? modelDirectionList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeTag ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseModelTag(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={Styles.messageText}>{item.name}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetCneterRightBox}> | |||||
| <div className={Styles.dataSource}> | |||||
| <span>数据总数:{total}个</span> | |||||
| <div> | |||||
| <Search | |||||
| placeholder="按模型名称筛选" | |||||
| allowClear | |||||
| onSearch={nameSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| }} | |||||
| /> | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginLeft: '20px' }} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 模型注册 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.dataContent}> | |||||
| {datasetList && datasetList.length > 0 | |||||
| ? datasetList.map((item) => { | |||||
| return ( | |||||
| <div | |||||
| className={Styles.dataItem} | |||||
| onClick={(e) => { | |||||
| routeToIntro(e, item); | |||||
| }} | |||||
| > | |||||
| <span className={Styles.itemText}>{item.name}</span> | |||||
| <img | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| modalConfirm({ | |||||
| title: '确定删除该条模型实例吗?', | |||||
| onOk: () => { | |||||
| deleteModel(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getModelLists(queryFlow); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| className={Styles.dropdown} | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={deleteIcon} | |||||
| alt="" | |||||
| /> | |||||
| {/* <Dropdown | |||||
| className={Styles.dropdown} | |||||
| key={item.name} | |||||
| menu={{ | |||||
| items: [ | |||||
| { | |||||
| label: '详情', | |||||
| key: 'detail', | |||||
| }, | |||||
| { | |||||
| label: '删除', | |||||
| key: 'delete', | |||||
| }, | |||||
| ], | |||||
| onClick: (e) => { | |||||
| console.log(e); | |||||
| if (e.key === 'detail') { | |||||
| routeToIntro(e, item); | |||||
| } else if (e.key === 'delete') { | |||||
| modalConfirm({ | |||||
| title: '确定删除该条模型实例吗?', | |||||
| onOk: () => { | |||||
| deleteModel(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getModelLists(queryFlow); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| } | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <div> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={moreBack} | |||||
| alt="" | |||||
| /> | |||||
| </div> | |||||
| </Dropdown> */} | |||||
| ,<div className={Styles.itemDescripition}>{item.description}</div> | |||||
| <div className={Styles.itemTime}> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={creatByImg} | |||||
| alt="" | |||||
| /> | |||||
| <span>{item.create_by}</span> | |||||
| </div> | |||||
| <div className={Styles.itemIcon}> | |||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={onPageChange} | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <Modal | |||||
| title={ | |||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||||
| <img | |||||
| style={{ width: '20px', marginRight: '10px' }} | |||||
| src={`/assets/images/pipeline-edit-icon.png`} | |||||
| alt="" | |||||
| /> | |||||
| {dialogTitle} | |||||
| </div> | |||||
| } | |||||
| open={isModalOpen} | |||||
| className={Styles.modal} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| onCancel={handleCancel} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="模型名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型名称!', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型名称" showCount maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型版本" | |||||
| name="version" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入模型版本" /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型描述!', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型描述" showCount maxLength={256} /> | |||||
| </Form.Item> | |||||
| {/* <Form.Item label="可见范围" name="available_range"> | |||||
| <Radio.Group> | |||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> */} | |||||
| <Form.Item | |||||
| label="模型框架" | |||||
| name="model_type" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择模型类型" | |||||
| options={modelTypeList.map((item) => { | |||||
| return { value: item.id, label: item.name }; | |||||
| })} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型能力" | |||||
| name="model_tag" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择模型标签" | |||||
| options={modelDirectionList.map((item) => { | |||||
| return { value: item.id, label: item.name }; | |||||
| })} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label="模型文件" name="models_version_vos"> | |||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button | |||||
| style={{ | |||||
| fontSize: '14px', | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| }} | |||||
| icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />} | |||||
| > | |||||
| 上传文件 | |||||
| </Button> | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </Modal> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PublicData; | |||||
| @@ -1,384 +0,0 @@ | |||||
| import clock from '@/assets/img/clock.png'; | |||||
| import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import deleteIcon from '@/assets/img/delete-icon.png'; | |||||
| import { deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { Form, Input, Modal, Pagination, Radio, message } from 'antd'; | |||||
| import moment from 'moment'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import Styles from './index.less'; | |||||
| const { Search } = Input; | |||||
| const leftdataList = [1, 2, 3]; | |||||
| const PublicData = () => { | |||||
| const [queryFlow, setQueryFlow] = useState({ | |||||
| page: 0, | |||||
| size: 20, | |||||
| name: null, | |||||
| available_range: 1, | |||||
| }); | |||||
| const [iconParams, setIconParams] = useState({ | |||||
| name: null, | |||||
| page: 0, | |||||
| size: 10000, | |||||
| }); | |||||
| const [activeType, setActiveType] = useState(null); | |||||
| const [activeTag, setActiveTag] = useState(null); | |||||
| const [datasetTypeList, setDatasetTypeList] = useState([]); | |||||
| const [datasetDirectionList, setDatasetDirectionList] = useState([]); | |||||
| const navgite = useNavigate(); | |||||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||||
| const [datasetList, setDatasetList] = useState([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [form] = Form.useForm(); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建数据'); | |||||
| const getModelLists = (queryFlow) => { | |||||
| getModelList(queryFlow).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200) { | |||||
| setDatasetList(ret.data.content); | |||||
| setTotal(ret.data.totalElements); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const showModal = () => { | |||||
| form.resetFields(); | |||||
| setDialogTitle('新建数据集'); | |||||
| setIsModalOpen(true); | |||||
| }; | |||||
| const nameSearch = (values) => { | |||||
| console.log(values); | |||||
| getModelLists({ ...queryFlow, name: values }); | |||||
| }; | |||||
| const getAssetIconList = (params) => { | |||||
| getAssetIcon(params).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { | |||||
| setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3)); | |||||
| setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4)); | |||||
| } else { | |||||
| setDatasetTypeList([]); | |||||
| setDatasetDirectionList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const onSearch = (values) => { | |||||
| console.log(values); | |||||
| getAssetIconList({ ...iconParams, name: values }); | |||||
| }; | |||||
| const handleOk = () => { | |||||
| console.log(1111); | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| setIsModalOpen(false); | |||||
| }; | |||||
| const chooseModelType = (val, item) => { | |||||
| console.log(val, item); | |||||
| if (item.id == queryFlow.model_type) { | |||||
| setActiveType(''); | |||||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||||
| getModelLists({ ...queryFlow, model_type: null }); | |||||
| } else { | |||||
| setActiveType(item.id); | |||||
| setQueryFlow({ ...queryFlow, model_type: item.id }); | |||||
| getModelLists({ ...queryFlow, model_type: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const chooseModelTag = (val, item) => { | |||||
| if (item.id == queryFlow.model_tag) { | |||||
| setActiveTag(''); | |||||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||||
| getModelLists({ ...queryFlow, model_tag: null }); | |||||
| } else { | |||||
| setActiveTag(item.id); | |||||
| setQueryFlow({ ...queryFlow, model_tag: item.id }); | |||||
| getModelLists({ ...queryFlow, model_tag: item.id }); | |||||
| } | |||||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||||
| // getDatasetlist() | |||||
| // }) | |||||
| }; | |||||
| const onFinish = (values) => {}; | |||||
| const routeToIntro = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| console.log(record); | |||||
| navgite({ pathname: `/dataset/model/${record.id}` }); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const onPageChange = (pageNum, pageSize) => { | |||||
| console.log(pageNum, pageSize); | |||||
| setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getAssetIconList(iconParams); | |||||
| getModelLists(queryFlow); | |||||
| return () => {}; | |||||
| }, []); | |||||
| return ( | |||||
| <> | |||||
| <div className={Styles.datasetCneterBox}> | |||||
| <div className={Styles.datasetCneterLeftBox}> | |||||
| <div className={Styles.leftContentBox}> | |||||
| <Search | |||||
| placeholder="搜索" | |||||
| allowClear | |||||
| onSearch={onSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| marginBottom: '15px', | |||||
| }} | |||||
| /> | |||||
| <div className={Styles.itemTitle}>模型框架</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {datasetTypeList && datasetTypeList.length > 0 | |||||
| ? datasetTypeList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeType ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseModelType(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={Styles.messageText}>{item.name}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| <div className={Styles.itemTitle}>模型能力</div> | |||||
| <div className={Styles.itemBox}> | |||||
| {datasetDirectionList && datasetDirectionList.length > 0 | |||||
| ? datasetDirectionList.map((item) => { | |||||
| return ( | |||||
| <div> | |||||
| <div | |||||
| className={[ | |||||
| Styles.messageBox, | |||||
| item.id === activeTag ? Styles.active : null, | |||||
| ].join(' ')} | |||||
| onClick={(e) => { | |||||
| chooseModelTag(e, item); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| className={Styles.ptIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| <img | |||||
| className={Styles.hoverIcon} | |||||
| style={{ width: '22px' }} | |||||
| src={`/assets/images/model/${item.path}-hover.png`} | |||||
| alt="" | |||||
| /> | |||||
| <span className={Styles.messageText}>{item.name}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetCneterRightBox}> | |||||
| <div className={Styles.dataSource}> | |||||
| <span>数据总数:{total}个</span> | |||||
| <div> | |||||
| <Search | |||||
| placeholder="按数据名称筛选" | |||||
| allowClear | |||||
| onSearch={nameSearch} | |||||
| style={{ | |||||
| width: 300, | |||||
| }} | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.dataContent}> | |||||
| {datasetList && datasetList.length > 0 | |||||
| ? datasetList.map((item) => { | |||||
| return ( | |||||
| <div | |||||
| className={Styles.dataItem} | |||||
| onClick={(e) => { | |||||
| routeToIntro(e, item); | |||||
| }} | |||||
| > | |||||
| <span className={Styles.itemText}>{item.name}</span> | |||||
| <img | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| modalConfirm({ | |||||
| title: '确定删除该条模型实例吗?', | |||||
| onOk: () => { | |||||
| deleteModel(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getModelLists(queryFlow); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| className={Styles.dropdown} | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={deleteIcon} | |||||
| alt="" | |||||
| /> | |||||
| <div className={Styles.itemDescripition}>{item.description}</div> | |||||
| <div className={Styles.itemTime}> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={creatByImg} | |||||
| alt="" | |||||
| /> | |||||
| <span>{item.create_by}</span> | |||||
| </div> | |||||
| <div className={Styles.itemIcon}> | |||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||||
| <span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }) | |||||
| : ''} | |||||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={onPageChange} | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <Modal | |||||
| title={ | |||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||||
| <img | |||||
| style={{ width: '20px', marginRight: '10px' }} | |||||
| src={`/assets/images/pipeline-edit-icon.png`} | |||||
| alt="" | |||||
| /> | |||||
| {dialogTitle} | |||||
| </div> | |||||
| } | |||||
| open={isModalOpen} | |||||
| className={Styles.modal} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| onCancel={handleCancel} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="数据名称" | |||||
| name="name" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入数据名称" /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据集版本" | |||||
| name="data_type" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入数据集版本" /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据描述" | |||||
| name="description" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入数据描述" /> | |||||
| </Form.Item> | |||||
| <Form.Item label="选择流水线" name="description1"> | |||||
| <Radio.Group> | |||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="数据标签" | |||||
| name="description3" | |||||
| rules={ | |||||
| [ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ] | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入数据标签" /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </Modal> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PublicData; | |||||
| @@ -0,0 +1,11 @@ | |||||
| .mirror-status-cell { | |||||
| color: @text-color; | |||||
| &--success { | |||||
| color: @success-color; | |||||
| } | |||||
| &--error { | |||||
| color: @error-color; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-18 18:35:41 | |||||
| * @Description: | |||||
| */ | |||||
| import { MirrorVersionStatus } from '@/enums'; | |||||
| import styles from './index.less'; | |||||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | |||||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | |||||
| export type MirrorVersionStatusInfo = { | |||||
| text: string; | |||||
| classname: string; | |||||
| }; | |||||
| const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||||
| [MirrorVersionStatus.Building]: { | |||||
| text: '构建中', | |||||
| classname: styles['mirror-status-cell'], | |||||
| }, | |||||
| [MirrorVersionStatus.Available]: { | |||||
| classname: styles['mirror-status-cell--success'], | |||||
| text: '可用', | |||||
| }, | |||||
| [MirrorVersionStatus.Failed]: { | |||||
| classname: styles['mirror-status-cell--error'], | |||||
| text: '构建失败', | |||||
| }, | |||||
| }; | |||||
| function MirrorStatusCell(status: MirrorVersionStatus) { | |||||
| if (status === null || status === undefined || !statusInfo[status]) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||||
| } | |||||
| export default MirrorStatusCell; | |||||
| @@ -0,0 +1,17 @@ | |||||
| .model-deployment-create { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 10px; | |||||
| overflow: auto; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__type { | |||||
| color: @text-color; | |||||
| font-size: @font-size-input-lg; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,297 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 创建模型部署 | |||||
| */ | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { createMirrorReq } from '@/services/mirror'; | |||||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage'; | |||||
| import { validateUploadFiles } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { Button, Col, Form, Input, Row, Select, UploadFile, message, type SelectProps } from 'antd'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './create.less'; | |||||
| type FormData = { | |||||
| name: string; | |||||
| tag: string; | |||||
| description: string; | |||||
| path?: string; | |||||
| upload_type: string; | |||||
| fileList?: UploadFile[]; | |||||
| }; | |||||
| function ModelDeploymentCreate() { | |||||
| const navgite = useNavigate(); | |||||
| const [form] = Form.useForm(); | |||||
| const [nameDisabled, setNameDisabled] = useState(false); | |||||
| const [resourceStandardList, setResourceStandardList] = useState([]); | |||||
| useEffect(() => { | |||||
| const name = getSessionItemThenRemove(mirrorNameKey); | |||||
| if (name) { | |||||
| form.setFieldValue('name', name); | |||||
| setNameDisabled(true); | |||||
| } | |||||
| getComputingResource(); | |||||
| }, []); | |||||
| const getComputingResource = async () => { | |||||
| const params = { | |||||
| page: 0, | |||||
| size: 1000, | |||||
| resource_type: '', | |||||
| }; | |||||
| const [res] = await to(getComputingResourceReq(params)); | |||||
| if (res && res.data && res.data.content) { | |||||
| setResourceStandardList(res.data.content); | |||||
| } | |||||
| }; | |||||
| const filterResourceStandard: SelectProps['filterOption'] = ( | |||||
| input: string, | |||||
| { computing_resource = '' }, | |||||
| ) => { | |||||
| return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | |||||
| }; | |||||
| // 创建公网、本地镜像 | |||||
| const createPublicMirror = async (formData: FormData) => { | |||||
| const upload_type = formData['upload_type']; | |||||
| let params; | |||||
| if (upload_type === CommonTabKeys.Public) { | |||||
| params = { | |||||
| ...omit(formData, ['upload_type']), | |||||
| upload_type: 0, | |||||
| image_type: 0, | |||||
| }; | |||||
| } else { | |||||
| const fileList = formData['fileList'] ?? []; | |||||
| if (validateUploadFiles(fileList)) { | |||||
| const file = fileList[0]; | |||||
| params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | |||||
| path: file.response.data.url, | |||||
| file_size: file.response.data.fileSize, | |||||
| upload_type: 1, | |||||
| image_type: 0, | |||||
| }; | |||||
| } | |||||
| } | |||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navgite(-1); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const handleSubmit = (values: FormData) => { | |||||
| createPublicMirror(values); | |||||
| }; | |||||
| // 取消 | |||||
| const cancel = () => { | |||||
| navgite(-1); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['model-deployment-create']}> | |||||
| <PageTitle title="创建推理服务"></PageTitle> | |||||
| <div className={styles['model-deployment-create__content']}> | |||||
| <div> | |||||
| <Form | |||||
| name="model-deployment-create" | |||||
| labelCol={{ flex: '130px' }} | |||||
| wrapperCol={{ flex: 1 }} | |||||
| labelAlign="left" | |||||
| form={form} | |||||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||||
| onFinish={handleSubmit} | |||||
| size="large" | |||||
| > | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="服务名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入服务名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入服务名称" | |||||
| maxLength={64} | |||||
| disabled={nameDisabled} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={10}> | |||||
| <Col span={20}> | |||||
| <Form.Item | |||||
| label="描 述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| placeholder="请输入描述,最长128字符" | |||||
| maxLength={128} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <SubAreaTitle | |||||
| title="部署构建" | |||||
| image={require('@/assets/img/mirror-version.png')} | |||||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="选择模型" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入模型" | |||||
| maxLength={64} | |||||
| disabled={nameDisabled} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="选择镜像" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入镜像" | |||||
| maxLength={64} | |||||
| disabled={nameDisabled} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| showSearch | |||||
| placeholder="请选择资源规格" | |||||
| filterOption={filterResourceStandard} | |||||
| options={resourceStandardList} | |||||
| fieldNames={{ | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="副本数量" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入副本数量', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入副本数量" | |||||
| maxLength={64} | |||||
| disabled={nameDisabled} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item label="环境变量" name="name"> | |||||
| <Button type="link" style={{ padding: '0' }}> | |||||
| 添加环境变量 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||||
| <Button type="primary" htmlType="submit"> | |||||
| 确定 | |||||
| </Button> | |||||
| <Button | |||||
| type="default" | |||||
| htmlType="button" | |||||
| onClick={cancel} | |||||
| style={{ marginLeft: '20px' }} | |||||
| > | |||||
| 取消 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ModelDeploymentCreate; | |||||
| @@ -0,0 +1,53 @@ | |||||
| .model-deployment-info { | |||||
| height: 100%; | |||||
| &__basic { | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| .label { | |||||
| width: 80px; | |||||
| color: @text-color-secondary; | |||||
| } | |||||
| .value { | |||||
| flex: 1; | |||||
| color: @text-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| &__table { | |||||
| :global { | |||||
| .ant-table-wrapper { | |||||
| height: 100%; | |||||
| .ant-spin-nested-loading { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-spin-container { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-table { | |||||
| height: calc(100% - 74px); | |||||
| overflow: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,148 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 镜像详情 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { getMirrorInfoReq } from '@/services/mirror'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useNavigate, useParams } from '@umijs/max'; | |||||
| import { Col, Row, Tabs, type TabsProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './info.less'; | |||||
| type MirrorInfoData = { | |||||
| name?: string; | |||||
| description?: string; | |||||
| version_count?: string; | |||||
| create_time?: string; | |||||
| }; | |||||
| type MirrorVersionData = { | |||||
| id: number; | |||||
| version: string; | |||||
| url: string; | |||||
| status: string; | |||||
| file_size: string; | |||||
| create_time: string; | |||||
| }; | |||||
| const tabItems = [ | |||||
| { | |||||
| key: '1', | |||||
| label: '预测', | |||||
| icon: <KFIcon type="icon-yuce" />, | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: '调用指南', | |||||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: '服务日志', | |||||
| icon: <KFIcon type="icon-fuwurizhi" />, | |||||
| }, | |||||
| ]; | |||||
| function ModelDeploymentInfo() { | |||||
| const navigate = useNavigate(); | |||||
| const urlParams = useParams(); | |||||
| const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | |||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| useEffect(() => { | |||||
| getMirrorInfo(); | |||||
| }, []); | |||||
| // 获取镜像详情 | |||||
| const getMirrorInfo = async () => { | |||||
| const id = Number(urlParams.id); | |||||
| const [res] = await to(getMirrorInfoReq(id)); | |||||
| if (res && res.data) { | |||||
| const { name = '', description = '', version_count = '', create_time: time } = res.data; | |||||
| const create_time = formatDate(time); | |||||
| setMirrorInfo({ | |||||
| name, | |||||
| description, | |||||
| version_count, | |||||
| create_time, | |||||
| }); | |||||
| } | |||||
| }; | |||||
| // 切换 Tab,重置数据 | |||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||||
| setActiveTab(value); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['model-deployment-info']}> | |||||
| <PageTitle title="服务详情"></PageTitle> | |||||
| <div className={styles['model-deployment-info__content']}> | |||||
| <div> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['model-deployment-info__basic']}> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>服务名称:</div> | |||||
| <div className={styles['value']}>{mirrorInfo.name}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>镜像:</div> | |||||
| <div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>状态:</div> | |||||
| <div className={styles['value']}>{mirrorInfo.name}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>模型:</div> | |||||
| <div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>环境变量:</div> | |||||
| <div className={styles['value']}>{mirrorInfo.name}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={24}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>描述:</div> | |||||
| <div className={styles['value']}>{mirrorInfo.description}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| </div> | |||||
| <div> | |||||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ModelDeploymentInfo; | |||||
| @@ -0,0 +1,21 @@ | |||||
| .model-deployment { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__filter { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| } | |||||
| &__table { | |||||
| height: calc(100% - 32px - 28px); | |||||
| margin-top: 28px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,283 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 模型部署列表 | |||||
| */ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { | |||||
| App, | |||||
| Button, | |||||
| ConfigProvider, | |||||
| Input, | |||||
| Table, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| } from 'antd'; | |||||
| import { type SearchProps } from 'antd/es/input'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './list.less'; | |||||
| export type MirrorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| description: string; | |||||
| create_time: string; | |||||
| }; | |||||
| function ModelDeployment() { | |||||
| const navigate = useNavigate(); | |||||
| const { message } = App.useApp(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<Required<TablePaginationConfig>>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| useEffect(() => { | |||||
| getMirrorList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取镜像列表 | |||||
| const getMirrorList = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current - 1, | |||||
| size: pagination.pageSize, | |||||
| name: searchText, | |||||
| image_type: 1, | |||||
| }; | |||||
| const [res] = await to(getMirrorListReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除镜像 | |||||
| const deleteMirror = async (id: number) => { | |||||
| const [res] = await to(deleteMirrorReq(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getMirrorList(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||||
| setSearchText(value); | |||||
| }; | |||||
| // 查看详情 | |||||
| const toDetail = (record: MirrorData) => { | |||||
| navigate(`/modelDeployment/${record.id}`); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | |||||
| const handleMirrorDelete = (record: MirrorData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该镜像将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteMirror(record.id); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建镜像 | |||||
| const createMirror = () => { | |||||
| navigate(`/modelDeployment/create`); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| }); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | |||||
| const columns: TableProps<MirrorData>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 100, | |||||
| align: 'center', | |||||
| render(text, record, index) { | |||||
| return <span>{(pagination.current - 1) * pagination.pageSize + index + 1}</span>; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: '服务名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '30%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '模型', | |||||
| dataIndex: 'version_count', | |||||
| key: 'version_count', | |||||
| width: '20%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'version_count', | |||||
| key: 'version_count', | |||||
| width: '10%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '创建人', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: CommonTableCell(true), | |||||
| width: '20%', | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'create_time', | |||||
| key: 'create_time', | |||||
| width: '20%', | |||||
| render: DateTableCell, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 350, | |||||
| key: 'operation', | |||||
| render: (_: any, record: MirrorData) => ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 编辑 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-yunhang" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 启动 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="stop" | |||||
| icon={<KFIcon type="icon-tingzhi" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 停止 | |||||
| </Button> | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleMirrorDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['model-deployment']}> | |||||
| <PageTitle title="模型列表"></PageTitle> | |||||
| <div className={styles['model-deployment__content']}> | |||||
| <div className={styles['model-deployment__filter']}> | |||||
| <Input.Search | |||||
| placeholder="按数据集名称筛选" | |||||
| allowClear | |||||
| onSearch={onSearch} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| value={inputText} | |||||
| /> | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| type="default" | |||||
| onClick={createMirror} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 创建推理服务 | |||||
| </Button> | |||||
| </div> | |||||
| <div | |||||
| className={classNames( | |||||
| 'vertical-scroll-table', | |||||
| styles['model-deployment__content__table'], | |||||
| )} | |||||
| > | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ModelDeployment; | |||||
| @@ -1,7 +1,4 @@ | |||||
| import { | |||||
| getParamComponent, | |||||
| getParamRules, | |||||
| } from '@/pages/Experiment/experimentText/addExperimentModal'; | |||||
| import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | |||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| @@ -9,7 +6,7 @@ import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; | |||||
| import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd'; | import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd'; | ||||
| import { NamePath } from 'antd/es/form/interface'; | import { NamePath } from 'antd/es/form/interface'; | ||||
| import { forwardRef, useImperativeHandle } from 'react'; | import { forwardRef, useImperativeHandle } from 'react'; | ||||
| import styles from './globalParamsDrawer.less'; | |||||
| import styles from './index.less'; | |||||
| type GlobalParamsDrawerProps = { | type GlobalParamsDrawerProps = { | ||||
| open: boolean; | open: boolean; | ||||
| @@ -22,7 +19,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| useImperativeHandle(ref, () => ({ | useImperativeHandle(ref, () => ({ | ||||
| getFieldsValue: async () => { | |||||
| validateFields: async () => { | |||||
| const [values, error] = await to(form.validateFields()); | const [values, error] = await to(form.validateFields()); | ||||
| if (!error && values) { | if (!error && values) { | ||||
| return values; | return values; | ||||
| @@ -30,6 +27,9 @@ const GlobalParamsDrawer = forwardRef( | |||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| } | } | ||||
| }, | }, | ||||
| getFieldsValue: () => { | |||||
| return form.getFieldsValue(); | |||||
| }, | |||||
| })); | })); | ||||
| // 处理参数类型变化 | // 处理参数类型变化 | ||||
| @@ -162,7 +162,6 @@ const GlobalParamsDrawer = forwardRef( | |||||
| )} | )} | ||||
| </Form.List> | </Form.List> | ||||
| </Form> | </Form> | ||||
| {/* //{contextHolder} */} | |||||
| </Drawer> | </Drawer> | ||||
| ); | ); | ||||
| }, | }, | ||||
| @@ -0,0 +1,6 @@ | |||||
| .props-label { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| import { Button, Dropdown, type MenuProps } from 'antd'; | |||||
| import { useEffect } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type PropsLabelProps = { | |||||
| title: string; | |||||
| menuItems: MenuProps['items']; | |||||
| onClick?: (key: string) => void; | |||||
| }; | |||||
| function PropsLabel({ title, menuItems, onClick }: PropsLabelProps) { | |||||
| useEffect(() => {}, []); | |||||
| const handleItemClick: MenuProps['onClick'] = (e) => { | |||||
| const keyPath = e.keyPath.reverse(); | |||||
| if (keyPath[0] === 'global') { | |||||
| onClick?.(`\${${e.key}}`); | |||||
| } else { | |||||
| onClick?.(`{{${keyPath.join('.')}}}`); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['props-label']}> | |||||
| <span>{title}</span> | |||||
| <Dropdown | |||||
| menu={{ | |||||
| items: menuItems, | |||||
| onClick: handleItemClick, | |||||
| triggerSubMenuAction: 'click', | |||||
| }} | |||||
| trigger={['click']} | |||||
| placement="topRight" | |||||
| arrow | |||||
| > | |||||
| <Button size="small" type="link"> | |||||
| 参数 | |||||
| </Button> | |||||
| </Dropdown> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default PropsLabel; | |||||
| @@ -40,7 +40,6 @@ export type SelectorTypeInfo = { | |||||
| getFiles: (params: any) => Promise<any>; | getFiles: (params: any) => Promise<any>; | ||||
| handleVersionResponse: (res: any) => any[]; | handleVersionResponse: (res: any) => any[]; | ||||
| modalIcon: string; | modalIcon: string; | ||||
| buttonIcon: string; | |||||
| name: string; | name: string; | ||||
| litReqParamKey: 'available_range' | 'image_type'; | litReqParamKey: 'available_range' | 'image_type'; | ||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | fileReqParamKey: 'models_id' | 'dataset_id'; | ||||
| @@ -71,7 +70,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn | |||||
| handleVersionResponse: (res) => res.data || [], | handleVersionResponse: (res) => res.data || [], | ||||
| name: '模型', | name: '模型', | ||||
| modalIcon: modelImg, | modalIcon: modelImg, | ||||
| buttonIcon: 'local:model-select-button', | |||||
| litReqParamKey: 'available_range', | litReqParamKey: 'available_range', | ||||
| fileReqParamKey: 'models_id', | fileReqParamKey: 'models_id', | ||||
| tabItems: [ | tabItems: [ | ||||
| @@ -92,7 +90,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn | |||||
| handleVersionResponse: (res) => res.data || [], | handleVersionResponse: (res) => res.data || [], | ||||
| name: '数据集', | name: '数据集', | ||||
| modalIcon: datasetImg, | modalIcon: datasetImg, | ||||
| buttonIcon: 'local:dataset-select-button', | |||||
| litReqParamKey: 'available_range', | litReqParamKey: 'available_range', | ||||
| fileReqParamKey: 'dataset_id', | fileReqParamKey: 'dataset_id', | ||||
| tabItems: [ | tabItems: [ | ||||
| @@ -115,7 +112,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn | |||||
| [], | [], | ||||
| name: '镜像', | name: '镜像', | ||||
| modalIcon: mirrorImg, | modalIcon: mirrorImg, | ||||
| buttonIcon: 'local:mirror-select-button', | |||||
| litReqParamKey: 'image_type', | litReqParamKey: 'image_type', | ||||
| fileReqParamKey: 'dataset_id', | fileReqParamKey: 'dataset_id', | ||||
| tabItems: [ | tabItems: [ | ||||
| @@ -3,6 +3,17 @@ | |||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| } | } | ||||
| .editPipelineProps { | |||||
| :global { | |||||
| label { | |||||
| width: 100%; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .editPipelinePropsContent { | .editPipelinePropsContent { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -1,27 +1,26 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { useVisible } from '@/hooks'; | |||||
| import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | import { useEmotionCss } from '@ant-design/use-emotion-css'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { Button, message } from 'antd'; | import { Button, message } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | import { s8 } from '../../../utils'; | ||||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | |||||
| import Styles from './editPipeline.less'; | import Styles from './editPipeline.less'; | ||||
| import GlobalParamsDrawer from './globalParamsDrawer'; | |||||
| import ModelMenus from './modelMenus'; | import ModelMenus from './modelMenus'; | ||||
| import Props from './props'; | import Props from './props'; | ||||
| import { findAllParentNodes, findFirstDuplicate } from './utils'; | |||||
| let graph = null; | let graph = null; | ||||
| const EditPipeline = () => { | const EditPipeline = () => { | ||||
| const propsRef = useRef(); | |||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| // const [contextMenu,setContextMenu]=useState({}) | |||||
| let contextMenu = {}; | let contextMenu = {}; | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| let sourceAnchorIdx, targetAnchorIdx; | |||||
| const pipelineContainer = useEmotionCss(() => { | const pipelineContainer = useEmotionCss(() => { | ||||
| return { | return { | ||||
| display: 'flex', | display: 'flex', | ||||
| @@ -59,8 +58,11 @@ const EditPipeline = () => { | |||||
| }); | }); | ||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const paramsDrawerRef = useRef(); | const paramsDrawerRef = useRef(); | ||||
| const propsRef = useRef(); | |||||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | ||||
| const [globalParam, setGlobalParam] = useState([]); | |||||
| const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]); | |||||
| let sourceAnchorIdx, targetAnchorIdx; | |||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val, 'eee'); | console.log(val, 'eee'); | ||||
| @@ -93,17 +95,24 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| }; | }; | ||||
| const savePipeline = async (val) => { | const savePipeline = async (val) => { | ||||
| const [res, error] = await to(paramsDrawerRef.current.getFieldsValue()); | |||||
| const [res, error] = await to(paramsDrawerRef.current.validateFields()); | |||||
| if (error) { | if (error) { | ||||
| message.error('全局参数配置有误'); | message.error('全局参数配置有误'); | ||||
| openParamsDrawer(); | openParamsDrawer(); | ||||
| return; | return; | ||||
| } | } | ||||
| const duplicateName = findFirstDuplicate(res.global_param || []); | |||||
| if (duplicateName) { | |||||
| message.error('全局参数配置有重复的参数名称:' + duplicateName); | |||||
| openParamsDrawer(); | |||||
| return; | |||||
| } | |||||
| const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | ||||
| console.log(await to(propsRef.current.getFieldsValue())); | console.log(await to(propsRef.current.getFieldsValue())); | ||||
| if (propsError) { | if (propsError) { | ||||
| message.error('基本信息必填项需配置'); | message.error('基本信息必填项需配置'); | ||||
| // handlerClick(); | |||||
| return; | return; | ||||
| } | } | ||||
| propsRef.current.propClose(); | propsRef.current.propClose(); | ||||
| @@ -128,12 +137,19 @@ const EditPipeline = () => { | |||||
| }; | }; | ||||
| const handlerClick = (e) => { | const handlerClick = (e) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| // console.log(propsRef, graph); | |||||
| propsRef.current.showDrawer(e); | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| graph.setItemState(e.item, 'nodeClicked', true); | |||||
| const parentNodes = findAllParentNodes(graph, e.item); | |||||
| // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| const globalParams = | |||||
| paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| propsRef.current.showDrawer(e, globalParams, parentNodes); | |||||
| } | |||||
| }; | }; | ||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| console.log('graph', graph); | |||||
| if (graph) { | if (graph) { | ||||
| console.log(graph); | |||||
| console.log(data); | |||||
| graph.data(data); | graph.data(data); | ||||
| graph.render(); | graph.render(); | ||||
| } else { | } else { | ||||
| @@ -275,14 +291,13 @@ const EditPipeline = () => { | |||||
| if (graph && ret.data && ret.data.dag) { | if (graph && ret.data && ret.data.dag) { | ||||
| getGraphData(JSON.parse(ret.data.dag)); | getGraphData(JSON.parse(ret.data.dag)); | ||||
| } | } | ||||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||||
| // graph.render() | |||||
| }); | }); | ||||
| }; | }; | ||||
| const handlerContextMenu = (e) => { | const handlerContextMenu = (e) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| // this.menuType = e.item._cfg.type; | // this.menuType = e.item._cfg.type; | ||||
| }; | }; | ||||
| // 上下文菜单 | |||||
| const initMenu = () => { | const initMenu = () => { | ||||
| // const selectedNodes = this.selectedNodes; | // const selectedNodes = this.selectedNodes; | ||||
| contextMenu = new G6.Menu({ | contextMenu = new G6.Menu({ | ||||
| @@ -330,8 +345,8 @@ const EditPipeline = () => { | |||||
| initGraph(); | initGraph(); | ||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getFirstWorkflow(locationParams.id); | |||||
| initMenu(); | initMenu(); | ||||
| getFirstWorkflow(locationParams.id); | |||||
| return () => { | return () => { | ||||
| graph.off('node:mouseenter', (e) => { | graph.off('node:mouseenter', (e) => { | ||||
| @@ -449,7 +464,7 @@ const EditPipeline = () => { | |||||
| }, | }, | ||||
| 'rect', | 'rect', | ||||
| ); | ); | ||||
| console.log(graphRef, 'graphRef'); | |||||
| graph = new G6.Graph({ | graph = new G6.Graph({ | ||||
| container: graphRef.current, | container: graphRef.current, | ||||
| grid: true, | grid: true, | ||||
| @@ -591,6 +606,8 @@ const EditPipeline = () => { | |||||
| }, | }, | ||||
| // linkCenter: true, | // linkCenter: true, | ||||
| fitView: true, | fitView: true, | ||||
| minZoom: 0.5, | |||||
| maxZoom: 3, | |||||
| fitViewPadding: [320, 320, 220, 320], | fitViewPadding: [320, 320, 220, 320], | ||||
| }); | }); | ||||
| // graph.on('dblclick', (e) => { | // graph.on('dblclick', (e) => { | ||||
| @@ -600,13 +617,7 @@ const EditPipeline = () => { | |||||
| // handlerClick(e); | // handlerClick(e); | ||||
| // } | // } | ||||
| // }); | // }); | ||||
| graph.on('node:click', (e) => { | |||||
| console.log(e.target.get('name')); | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| graph.setItemState(e.item, 'nodeClicked', true); | |||||
| handlerClick(e); | |||||
| } | |||||
| }); | |||||
| graph.on('node:click', handlerClick); | |||||
| graph.on('aftercreateedge', (e) => { | graph.on('aftercreateedge', (e) => { | ||||
| // update the sourceAnchor and targetAnchor for the newly added edge | // update the sourceAnchor and targetAnchor for the newly added edge | ||||
| graph.updateItem(e.edge, { | graph.updateItem(e.edge, { | ||||
| @@ -2,23 +2,6 @@ import { getComponentAll } from '@/services/pipeline/index.js'; | |||||
| import { Collapse } from 'antd'; | import { Collapse } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import Styles from './modelMenus.less'; | import Styles from './modelMenus.less'; | ||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: 'This is panel header 1', | |||||
| children: [1, 2, 3, 4, 5], | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: 'This is panel header 2', | |||||
| children: [1, 2, 3, 4, 5], | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: 'This is panel header 3', | |||||
| children: [1, 2, 3, 4, 5], | |||||
| }, | |||||
| ]; | |||||
| const ModelMenus = ({ onParDragEnd }) => { | const ModelMenus = ({ onParDragEnd }) => { | ||||
| const [modelMenusList, setModelMenusList] = useState([]); | const [modelMenusList, setModelMenusList] = useState([]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -62,11 +45,13 @@ const ModelMenus = ({ onParDragEnd }) => { | |||||
| }} | }} | ||||
| className={Styles.collapseItem} | className={Styles.collapseItem} | ||||
| > | > | ||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| {ele.icon_path && ( | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| )} | |||||
| {ele.component_label} | {ele.component_label} | ||||
| </div> | </div> | ||||
| )) | )) | ||||
| @@ -1,26 +1,31 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { getComputingResourceReq } from '@/services/pipeline'; | import { getComputingResourceReq } from '@/services/pipeline'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | import { Button, Drawer, Form, Input, Select } from 'antd'; | ||||
| import { pick } from 'lodash'; | import { pick } from 'lodash'; | ||||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../components/PropsLabel'; | |||||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | ||||
| import Styles from './editPipeline.less'; | |||||
| import styles from './editPipeline.less'; | |||||
| import { createMenuItems } from './utils'; | |||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Props = forwardRef(({ onParentChange }, ref) => { | const Props = forwardRef(({ onParentChange }, ref) => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [stagingItem, setStagingItem] = useState({}); | const [stagingItem, setStagingItem] = useState({}); | ||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [selectedModel, setSelectedModel] = useState(undefined); | |||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); | |||||
| const [resourceStandardList, setResourceStandardList] = useState([]); | |||||
| const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | |||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | |||||
| const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 | |||||
| const [menuItems, setMenuItems] = useState([]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getComputingResource(); | getComputingResource(); | ||||
| }, []); | }, []); | ||||
| // 获取资源规格列表数据 | |||||
| const getComputingResource = async () => { | const getComputingResource = async () => { | ||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| @@ -35,49 +40,22 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| console.log(stagingItem, form.getFieldsValue()); | |||||
| // 禁止校验 guard-for-in | |||||
| /* eslint-disable */ | |||||
| for (let i in form.getFieldsValue()) { | |||||
| for (let j in stagingItem.in_parameters) { | |||||
| if (i == j) { | |||||
| console.log(j, i); | |||||
| stagingItem.in_parameters[j].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let p in stagingItem.out_parameters) { | |||||
| if (i == p) { | |||||
| stagingItem.out_parameters[p].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let k in stagingItem.control_strategy) { | |||||
| if (i == k) { | |||||
| stagingItem.control_strategy[k].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| } | |||||
| /* eslint-enable */ | |||||
| // setStagingItem({...stagingItem,}) | |||||
| console.log(stagingItem.control_strategy); | |||||
| console.log('zzzz', form.getFieldsValue()); | |||||
| const control_strategy = form.getFieldValue('control_strategy'); | |||||
| const in_parameters = form.getFieldValue('in_parameters'); | |||||
| const out_parameters = form.getFieldValue('out_parameters'); | |||||
| onParentChange({ | onParentChange({ | ||||
| ...stagingItem, | ...stagingItem, | ||||
| control_strategy: JSON.stringify(stagingItem.control_strategy), | |||||
| in_parameters: JSON.stringify(stagingItem.in_parameters), | |||||
| out_parameters: JSON.stringify(stagingItem.out_parameters), | |||||
| ...form.getFieldsValue(), | ...form.getFieldsValue(), | ||||
| control_strategy: JSON.stringify(control_strategy), | |||||
| in_parameters: JSON.stringify(in_parameters), | |||||
| out_parameters: JSON.stringify(out_parameters), | |||||
| }); | }); | ||||
| // onParentChange({...stagingItem,...form.getFieldsValue()}) | |||||
| } | } | ||||
| }; | }; | ||||
| const onClose = () => { | const onClose = () => { | ||||
| setOpen(false); | setOpen(false); | ||||
| }; | }; | ||||
| const onFinish = (values) => { | |||||
| console.log('Success:', values); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | useImperativeHandle(ref, () => ({ | ||||
| getFieldsValue: async () => { | getFieldsValue: async () => { | ||||
| const [propsRes, propsError] = await to(form.validateFields()); | const [propsRes, propsError] = await to(form.validateFields()); | ||||
| @@ -88,47 +66,43 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| return Promise.reject(propsError); | return Promise.reject(propsError); | ||||
| } | } | ||||
| }, | }, | ||||
| showDrawer(e) { | |||||
| showDrawer(e, params, parentNodes) { | |||||
| if (e.item && e.item.getModel()) { | if (e.item && e.item.getModel()) { | ||||
| // console.log(e.item.getModel().in_parameters); | |||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| setStagingItem({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| // form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setTimeout(() => { | |||||
| // console.log(stagingItem); | |||||
| // }, (500)); | |||||
| const model = e.item.getModel(); | |||||
| try { | |||||
| const nodeData = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| setStagingItem({ | |||||
| ...nodeData, | |||||
| }); | |||||
| form.setFieldsValue({ | |||||
| ...nodeData, | |||||
| }); | |||||
| } catch (error) { | |||||
| console.log(error); | |||||
| } | |||||
| setSelectedModel(undefined); | setSelectedModel(undefined); | ||||
| setSelectedDataset(undefined); | setSelectedDataset(undefined); | ||||
| setOpen(true); | setOpen(true); | ||||
| // 参数下拉菜单 | |||||
| setMenuItems(createMenuItems(params, parentNodes)); | |||||
| } | } | ||||
| }, | }, | ||||
| propClose: async () => { | |||||
| setOpen(false); | |||||
| const [openRes, propsError] = await to(setOpen(false)); | |||||
| console.log(setOpen(false)); | |||||
| propClose: () => { | |||||
| close(); | |||||
| }, | }, | ||||
| // propClose() { | |||||
| // setOpen(false); | |||||
| // }, | |||||
| })); | })); | ||||
| // 选择数据集、模型 | // 选择数据集、模型 | ||||
| const selectResource = (name, item) => { | const selectResource = (name, item) => { | ||||
| let type; | let type; | ||||
| let resource = undefined; | |||||
| let resource; | |||||
| switch (item.item_type) { | switch (item.item_type) { | ||||
| case 'dataset': | case 'dataset': | ||||
| type = ResourceSelectorType.Dataset; | type = ResourceSelectorType.Dataset; | ||||
| @@ -142,41 +116,37 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| type = ResourceSelectorType.Mirror; | type = ResourceSelectorType.Mirror; | ||||
| break; | break; | ||||
| } | } | ||||
| const { close } = openAntdModal( | |||||
| ResourceSelectorModal, | |||||
| { | |||||
| type, | |||||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||||
| defaultActiveTab: resource?.activeTab, | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| form.setFieldValue(name, res); | |||||
| } else { | |||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||||
| const value = JSON.stringify(jsonObj); | |||||
| form.setFieldValue(name, value); | |||||
| } | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| } | |||||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||||
| type, | |||||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||||
| defaultActiveTab: resource?.activeTab, | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| form.setFieldValue(name, res); | |||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(null); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(null); | |||||
| } | |||||
| form.setFieldValue(name, ''); | |||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||||
| const value = JSON.stringify(jsonObj); | |||||
| form.setFieldValue(name, { ...item, value }); | |||||
| } | } | ||||
| close(); | |||||
| }, | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| } | |||||
| } else { | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(null); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(null); | |||||
| } | |||||
| form.setFieldValue(name, ''); | |||||
| } | |||||
| close(); | |||||
| }, | }, | ||||
| true, | |||||
| ); | |||||
| }); | |||||
| }; | }; | ||||
| // 获取选择数据集、模型后面按钮 icon | // 获取选择数据集、模型后面按钮 icon | ||||
| @@ -191,16 +161,31 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } | } | ||||
| }; | }; | ||||
| // 筛选资源规格 | |||||
| const filterResourceStandard = (input, { computing_resource = '' }) => { | const filterResourceStandard = (input, { computing_resource = '' }) => { | ||||
| return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | ||||
| }; | }; | ||||
| // 参数回填 | |||||
| const handleParameterClick = (name, value) => { | |||||
| form.setFieldValue(name, value); | |||||
| }; | |||||
| // 控制策略 | // 控制策略 | ||||
| const controlStrategy = stagingItem.control_strategy; | |||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| // 输入参数 | // 输入参数 | ||||
| const inParameters = stagingItem.in_parameters; | |||||
| const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| // 输出参数 | // 输出参数 | ||||
| const outParameters = stagingItem.out_parameters; | |||||
| const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -213,15 +198,15 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| onClose={onClose} | onClose={onClose} | ||||
| afterOpenChange={afterOpenChange} | afterOpenChange={afterOpenChange} | ||||
| open={open} | open={open} | ||||
| width={420} | |||||
| width={520} | |||||
| className={styles.editPipelineProps} | |||||
| > | > | ||||
| <Form | <Form | ||||
| name="form" | name="form" | ||||
| form={form} | form={form} | ||||
| // layout="vertical" | |||||
| layout="vertical" | layout="vertical" | ||||
| labelCol={{ | labelCol={{ | ||||
| span: 16, | |||||
| span: 24, | |||||
| }} | }} | ||||
| wrapperCol={{ | wrapperCol={{ | ||||
| span: 24, | span: 24, | ||||
| @@ -229,20 +214,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| style={{ | style={{ | ||||
| maxWidth: 600, | maxWidth: 600, | ||||
| }} | }} | ||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | autoComplete="off" | ||||
| > | > | ||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '13px', marginRight: '10px' }} | |||||
| src={'/assets/images/static-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 基本信息 | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| <Form.Item | <Form.Item | ||||
| label="任务名称" | label="任务名称" | ||||
| @@ -254,7 +229,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input /> | |||||
| <Input placeholder="请输入任务名称" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="任务ID" | label="任务ID" | ||||
| @@ -268,37 +243,53 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| > | > | ||||
| <Input disabled /> | <Input disabled /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 任务信息 | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| <Form.Item label="镜像" required> | <Form.Item label="镜像" required> | ||||
| <div className={Styles['ref-row']}> | |||||
| <div className={styles['ref-row']}> | |||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | ||||
| <Input placeholder="请输入镜像" allowClear /> | |||||
| <Input placeholder="请输入或选择镜像" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item noStyle> | <Form.Item noStyle> | ||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon({ item_type: 'image' })} | icon={getSelectBtnIcon({ item_type: 'image' })} | ||||
| onClick={() => selectResource('image', { item_type: 'image' })} | onClick={() => selectResource('image', { item_type: 'image' })} | ||||
| className={Styles['select-button']} | |||||
| className={styles['select-button']} | |||||
| > | > | ||||
| 选择镜像 | 选择镜像 | ||||
| </Button> | </Button> | ||||
| </Form.Item> | </Form.Item> | ||||
| </div> | </div> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="工作目录" name="working_directory"> | |||||
| <Input /> | |||||
| <Form.Item | |||||
| name="working_directory" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="工作目录" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('working_directory', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入工作目录" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="启动命令" name="command"> | |||||
| <TextArea /> | |||||
| <Form.Item | |||||
| name="command" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="启动命令" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('command', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入启动命令" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="资源规格" | label="资源规格" | ||||
| @@ -321,78 +312,148 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }} | }} | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="挂载路径" name="mount_path"> | |||||
| <Input /> | |||||
| <Form.Item | |||||
| name="mount_path" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="挂载路径" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('mount_path', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入挂载路径" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="环境变量" name="env_variables"> | |||||
| <TextArea /> | |||||
| <Form.Item | |||||
| name="env_variables" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="环境变量" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('env_variables', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入环境变量" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| {controlStrategy && Object.keys(controlStrategy).length > 0 | |||||
| ? Object.keys(controlStrategy).map((item) => ( | |||||
| <Form.Item key={item} label={controlStrategy[item].label} name={item}> | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 输入参数 | |||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['control_strategy', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| {inParameters && Object.keys(inParameters).length > 0 | |||||
| ? Object.keys(inParameters).map((item) => ( | |||||
| {inParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['in_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| required={item.value.require ? true : false} | |||||
| > | |||||
| <div className={styles['ref-row']}> | |||||
| <Form.Item | <Form.Item | ||||
| key={item} | |||||
| label={inParameters[item].label + '(' + item + ')'} | |||||
| required={inParameters[item].require ? true : false} | |||||
| name={['in_parameters', item.key]} | |||||
| noStyle | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | > | ||||
| <div className={Styles['ref-row']}> | |||||
| <Form.Item | |||||
| name={item} | |||||
| noStyle | |||||
| rules={[{ required: inParameters[item].require ? true : false }]} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| {inParameters[item].type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(inParameters[item])} | |||||
| onClick={() => selectResource(item, inParameters[item])} | |||||
| className={Styles['select-button']} | |||||
| > | |||||
| {inParameters[item].label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| )) | |||||
| : ''} | |||||
| <div className={Styles.editPipelinePropsContent}> | |||||
| <img | |||||
| style={{ width: '15px', marginRight: '10px' }} | |||||
| src={'/assets/images/duty-message.png'} | |||||
| alt="" | |||||
| /> | |||||
| 输出参数 | |||||
| {item.value.type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectResource(['in_parameters', item.key], item.value)} | |||||
| className={styles['select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle> | |||||
| </div> | </div> | ||||
| {outParameters && Object.keys(outParameters).length > 0 | |||||
| ? Object.keys(outParameters).map((item) => ( | |||||
| <Form.Item | |||||
| key={item} | |||||
| label={outParameters[item].label + '(' + item + ')'} | |||||
| rules={[{ required: outParameters[item].require ? true : false }]} | |||||
| name={item} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['out_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </Form> | </Form> | ||||
| </Drawer> | </Drawer> | ||||
| </> | </> | ||||
| @@ -0,0 +1,79 @@ | |||||
| import { PipelineGlobalParam } from '@/types'; | |||||
| import { Graph, INode } from '@antv/g6'; | |||||
| import { type MenuProps } from 'antd'; | |||||
| // 找到节点所以的上游节点 | |||||
| export const findAllParentNodes = (graph: Graph, node: INode) => { | |||||
| const parentNodes: INode[] = []; | |||||
| let index = -1; | |||||
| let targetNode = node; | |||||
| while (targetNode) { | |||||
| const neighbors: INode[] = graph.getNeighbors(targetNode, 'source'); | |||||
| for (const sourceNode of neighbors) { | |||||
| // 避免重复,也避免循环 | |||||
| const idx = parentNodes.findIndex((item) => sourceNode.getID() === item.getID()); | |||||
| if (idx === -1 && sourceNode.getID() !== node.getID()) { | |||||
| parentNodes.push(sourceNode); | |||||
| } | |||||
| } | |||||
| targetNode = parentNodes[++index]; | |||||
| } | |||||
| return parentNodes; | |||||
| }; | |||||
| // 判断并找到全局参数第一个重复项,有重复项时,全局参数不允许保存 | |||||
| export function findFirstDuplicate(params: PipelineGlobalParam[]): string | null { | |||||
| const seen = new Set(); | |||||
| for (const item of params) { | |||||
| if (seen.has(item.param_name)) { | |||||
| return item.param_name; | |||||
| } | |||||
| seen.add(item.param_name); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 创建参数下拉菜单 | |||||
| export function createMenuItems( | |||||
| params: PipelineGlobalParam[], | |||||
| parentNodes: INode[], | |||||
| ): MenuProps['items'] { | |||||
| const nodes: MenuProps['items'] = parentNodes.map((item) => { | |||||
| const model = item.getModel(); | |||||
| const out_parameters = model.out_parameters as string | undefined | null; | |||||
| const out_parametersObj = parseJsonText(out_parameters); | |||||
| const outParametersList = Object.keys(out_parametersObj ?? {}); | |||||
| return { | |||||
| key: model.id as string, | |||||
| label: model.label as string, | |||||
| children: outParametersList.map((key: string) => ({ | |||||
| key: key as string, | |||||
| label: out_parametersObj[key].label, | |||||
| })), | |||||
| }; | |||||
| }); | |||||
| return [ | |||||
| { | |||||
| key: 'global', | |||||
| label: '全局参数', | |||||
| children: params.map((item) => ({ | |||||
| key: item.param_name, | |||||
| label: item.param_name, | |||||
| })), | |||||
| }, | |||||
| ...nodes, | |||||
| ]; | |||||
| } | |||||
| function parseJsonText(text?: string | null): any | null { | |||||
| if (!text) { | |||||
| return null; | |||||
| } | |||||
| try { | |||||
| return JSON.parse(text); | |||||
| } catch (error) { | |||||
| return null; | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,6 @@ | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | |||||
| import { | import { | ||||
| addWorkflow, | addWorkflow, | ||||
| cloneWorkflow, | cloneWorkflow, | ||||
| @@ -9,12 +11,12 @@ import { | |||||
| } from '@/services/pipeline/index.js'; | } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { Button, ConfigProvider, Form, Input, Modal, Space, Table, message } from 'antd'; | |||||
| import { Button, ConfigProvider, Form, Input, Space, Table, message } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import momnet from 'moment'; | |||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Pipeline = () => { | const Pipeline = () => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| @@ -136,13 +138,13 @@ const Pipeline = () => { | |||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| render: (text) => <span>{momnet(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| render: DateTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '修改时间', | title: '修改时间', | ||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| render: (text) => <span>{momnet(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| render: DateTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -168,7 +170,7 @@ const Pipeline = () => { | |||||
| key="clone" | key="clone" | ||||
| icon={<KFIcon type="icon-fuzhi" />} | icon={<KFIcon type="icon-fuzhi" />} | ||||
| onClick={async () => { | onClick={async () => { | ||||
| Modal.confirm({ | |||||
| modalConfirm({ | |||||
| title: '复制', | title: '复制', | ||||
| content: '确定复制该条流水线吗?', | content: '确定复制该条流水线吗?', | ||||
| okText: '确认', | okText: '确认', | ||||
| @@ -254,17 +256,10 @@ const Pipeline = () => { | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | scroll={{ y: 'calc(100% - 55px)' }} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| <Modal | |||||
| title={ | |||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | |||||
| <img | |||||
| style={{ width: '20px', marginRight: '10px' }} | |||||
| src={`/assets/images/pipeline-edit-icon.png`} | |||||
| alt="" | |||||
| /> | |||||
| {dialogTitle} | |||||
| </div> | |||||
| } | |||||
| <KFModal | |||||
| title={dialogTitle} | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| open={isModalOpen} | open={isModalOpen} | ||||
| className={Styles.modal} | className={Styles.modal} | ||||
| okButtonProps={{ | okButtonProps={{ | ||||
| @@ -314,7 +309,7 @@ const Pipeline = () => { | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| </Form> | </Form> | ||||
| </Modal> | |||||
| </KFModal> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -8,60 +8,17 @@ | |||||
| padding-right: 30px; | padding-right: 30px; | ||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| .modal { | |||||
| :global { | |||||
| .ant-modal-content { | |||||
| width: 825px; | |||||
| padding: 20px 67px; | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100%; | |||||
| border-radius: 21px; | |||||
| } | |||||
| .ant-modal-header { | |||||
| margin: 20px 0; | |||||
| background-color: transparent; | |||||
| } | |||||
| .ant-input { | |||||
| height: 40px; | |||||
| border-color: #e6e6e6; | |||||
| } | |||||
| .ant-form-item .ant-form-item-label > label { | |||||
| color: rgba(29, 29, 32, 0.8); | |||||
| } | |||||
| .ant-modal-footer { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| margin: 40px 0 30px 0; | |||||
| } | |||||
| .ant-btn { | |||||
| width: 110px; | |||||
| height: 40px; | |||||
| font-size: 18px; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border-color: transparent; | |||||
| border-radius: 10px; | |||||
| } | |||||
| .ant-btn-primary { | |||||
| background: #1664ff; | |||||
| } | |||||
| } | |||||
| } | |||||
| .PipelineBox{ | |||||
| .PipelineBox { | |||||
| height: calc(100% - 20px); | height: calc(100% - 20px); | ||||
| .PipelineTable{ | |||||
| .PipelineTable { | |||||
| height: calc(100% - 60px); | height: calc(100% - 60px); | ||||
| :global{ | |||||
| .ant-table-wrapper .ant-table{ | |||||
| :global { | |||||
| .ant-table-wrapper .ant-table { | |||||
| // overflow-y: auto; | // overflow-y: auto; | ||||
| height: calc(100% - 48px); | height: calc(100% - 48px); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -222,12 +222,7 @@ const Login: React.FC = () => { | |||||
| getCaptchaCode(); | getCaptchaCode(); | ||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| const defaultLoginFailureMessage = intl.formatMessage({ | |||||
| id: 'pages.login.failure', | |||||
| defaultMessage: '登录失败,请重试!', | |||||
| }); | |||||
| console.log(error); | |||||
| message.error(defaultLoginFailureMessage); | |||||
| getCaptchaCode(); | |||||
| } | } | ||||
| }; | }; | ||||
| const { code } = userLoginState; | const { code } = userLoginState; | ||||
| @@ -289,479 +284,219 @@ const Login: React.FC = () => { | |||||
| > | > | ||||
| 账号登录 | 账号登录 | ||||
| </div> | </div> | ||||
| <LoginForm | |||||
| title="" | |||||
| className={styles.loginForm} | |||||
| initialValues={{ | |||||
| autoLogin: true, | |||||
| }} | |||||
| // actions={[ | |||||
| // <FormattedMessage | |||||
| // key="loginWith" | |||||
| // id="pages.login.loginWith" | |||||
| // defaultMessage="其他登录方式" | |||||
| // />, | |||||
| // <ActionIcons key="icons" />, | |||||
| // ]} | |||||
| onFinish={async (values) => { | |||||
| await handleSubmit(values as API.LoginParams); | |||||
| }} | |||||
| > | |||||
| {code !== 200 && loginType === 'account' && ( | |||||
| <LoginMessage | |||||
| content={intl.formatMessage({ | |||||
| id: 'pages.login.accountLogin.errorMessage', | |||||
| defaultMessage: '账户或密码错误(admin/admin123)', | |||||
| })} | |||||
| /> | |||||
| )} | |||||
| {type === 'account' && ( | |||||
| <> | |||||
| <ProFormText | |||||
| name="username" | |||||
| initialValue="admin" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <UserOutlined />, | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.username.placeholder', | |||||
| defaultMessage: '用户名: admin', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.username.required" | |||||
| defaultMessage="请输入用户名!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormText.Password | |||||
| name="password" | |||||
| initialValue="admin123" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.password.placeholder', | |||||
| defaultMessage: '密码: admin123', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.password.required" | |||||
| defaultMessage="请输入密码!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <Row> | |||||
| <Col flex={4}> | |||||
| <ProFormText | |||||
| style={{ | |||||
| float: 'right', | |||||
| }} | |||||
| name="code" | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.searchTable.updateForm.ruleName.nameRules" | |||||
| defaultMessage="请输入验证啊" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| </Col> | |||||
| <Col> | |||||
| <Image | |||||
| src={captchaCode} | |||||
| alt="验证码" | |||||
| style={{ | |||||
| display: 'inline-block', | |||||
| verticalAlign: 'top', | |||||
| cursor: 'pointer', | |||||
| paddingLeft: '22px', | |||||
| width: '170px', | |||||
| height: '66px', | |||||
| }} | |||||
| preview={false} | |||||
| onClick={() => getCaptchaCode()} | |||||
| /> | |||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| )} | |||||
| {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />} | |||||
| {type === 'mobile' && ( | |||||
| <> | |||||
| <ProFormText | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <MobileOutlined />, | |||||
| }} | |||||
| name="mobile" | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.phoneNumber.placeholder', | |||||
| defaultMessage: '手机号', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.required" | |||||
| defaultMessage="请输入手机号!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| pattern: /^1\d{10}$/, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.invalid" | |||||
| defaultMessage="手机号格式错误!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormCaptcha | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | |||||
| captchaProps={{ | |||||
| size: 'large', | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证码', | |||||
| })} | |||||
| captchaTextRender={(timing, count) => { | |||||
| if (timing) { | |||||
| return `${count} ${intl.formatMessage({ | |||||
| id: 'pages.getCaptchaSecondText', | |||||
| defaultMessage: '获取验证码', | |||||
| })}`; | |||||
| } | |||||
| return intl.formatMessage({ | |||||
| id: 'pages.login.phoneLogin.getVerificationCode', | |||||
| defaultMessage: '获取验证码', | |||||
| }); | |||||
| }} | |||||
| name="captcha" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.captcha.required" | |||||
| defaultMessage="请输入验证码!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| onGetCaptcha={async (phone) => { | |||||
| const result = await getFakeCaptcha({ | |||||
| phone, | |||||
| }); | |||||
| if (!result) { | |||||
| return; | |||||
| } | |||||
| message.success('获取验证码成功!验证码为:1234'); | |||||
| }} | |||||
| /> | |||||
| </> | |||||
| )} | |||||
| <div | |||||
| style={{ | |||||
| marginBottom: 24, | |||||
| <div className={styles.loginForm}> | |||||
| <LoginForm | |||||
| title="" | |||||
| initialValues={{ | |||||
| autoLogin: true, | |||||
| }} | |||||
| // actions={[ | |||||
| // <FormattedMessage | |||||
| // key="loginWith" | |||||
| // id="pages.login.loginWith" | |||||
| // defaultMessage="其他登录方式" | |||||
| // />, | |||||
| // <ActionIcons key="icons" />, | |||||
| // ]} | |||||
| onFinish={async (values) => { | |||||
| await handleSubmit(values as API.LoginParams); | |||||
| }} | }} | ||||
| > | > | ||||
| <ProFormCheckbox noStyle name="autoLogin"> | |||||
| <FormattedMessage id="pages.login.rememberMe" defaultMessage="记住密码" /> | |||||
| </ProFormCheckbox> | |||||
| </div> | |||||
| </LoginForm> | |||||
| </div> | |||||
| </div> | |||||
| {/* <Helmet> | |||||
| <title> | |||||
| {intl.formatMessage({ | |||||
| id: 'menu.login', | |||||
| defaultMessage: '登录页', | |||||
| })} | |||||
| - {Settings.title} | |||||
| </title> | |||||
| </Helmet> | |||||
| <Lang /> | |||||
| <div | |||||
| style={{ | |||||
| flex: '1', | |||||
| padding: '32px 0', | |||||
| }} | |||||
| > | |||||
| <LoginForm | |||||
| contentStyle={{ | |||||
| minWidth: 280, | |||||
| maxWidth: '75vw', | |||||
| }} | |||||
| logo={<img alt="logo" src="/logo.svg" />} | |||||
| title="Ant Design" | |||||
| subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })} | |||||
| initialValues={{ | |||||
| autoLogin: true, | |||||
| }} | |||||
| actions={[ | |||||
| <FormattedMessage | |||||
| key="loginWith" | |||||
| id="pages.login.loginWith" | |||||
| defaultMessage="其他登录方式" | |||||
| />, | |||||
| <ActionIcons key="icons" />, | |||||
| ]} | |||||
| onFinish={async (values) => { | |||||
| await handleSubmit(values as API.LoginParams); | |||||
| }} | |||||
| > | |||||
| <Tabs | |||||
| activeKey={type} | |||||
| onChange={setType} | |||||
| centered | |||||
| items={[ | |||||
| { | |||||
| key: 'account', | |||||
| label: intl.formatMessage({ | |||||
| id: 'pages.login.accountLogin.tab', | |||||
| defaultMessage: '账户密码登录', | |||||
| }), | |||||
| }, | |||||
| { | |||||
| key: 'mobile', | |||||
| label: intl.formatMessage({ | |||||
| id: 'pages.login.phoneLogin.tab', | |||||
| defaultMessage: '手机号登录', | |||||
| }), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| {code !== 200 && loginType === 'account' && ( | |||||
| <LoginMessage | |||||
| content={intl.formatMessage({ | |||||
| id: 'pages.login.accountLogin.errorMessage', | |||||
| defaultMessage: '账户或密码错误(admin/admin123)', | |||||
| })} | |||||
| /> | |||||
| )} | |||||
| {type === 'account' && ( | |||||
| <> | |||||
| <ProFormText | |||||
| name="username" | |||||
| initialValue="admin" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <UserOutlined />, | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.username.placeholder', | |||||
| defaultMessage: '用户名: admin', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.username.required" | |||||
| defaultMessage="请输入用户名!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormText.Password | |||||
| name="password" | |||||
| initialValue="admin123" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.password.placeholder', | |||||
| defaultMessage: '密码: admin123', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.password.required" | |||||
| defaultMessage="请输入密码!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <Row> | |||||
| <Col flex={3}> | |||||
| {code !== 200 && loginType === 'account' && ( | |||||
| <LoginMessage | |||||
| content={intl.formatMessage({ | |||||
| id: 'pages.login.accountLogin.errorMessage', | |||||
| defaultMessage: '账户或密码错误(admin/admin123)', | |||||
| })} | |||||
| /> | |||||
| )} | |||||
| {type === 'account' && ( | |||||
| <> | |||||
| <ProFormText | <ProFormText | ||||
| style={{ | |||||
| float: 'right', | |||||
| name="username" | |||||
| initialValue="admin" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <UserOutlined />, | |||||
| }} | }} | ||||
| name="code" | |||||
| placeholder={intl.formatMessage({ | placeholder={intl.formatMessage({ | ||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证', | |||||
| id: 'pages.login.username.placeholder', | |||||
| defaultMessage: '用户名: admin', | |||||
| })} | })} | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: ( | message: ( | ||||
| <FormattedMessage | <FormattedMessage | ||||
| id="pages.searchTable.updateForm.ruleName.nameRules" | |||||
| defaultMessage="请输入验证啊" | |||||
| id="pages.login.username.required" | |||||
| defaultMessage="请输入用户名!" | |||||
| /> | /> | ||||
| ), | ), | ||||
| }, | }, | ||||
| ]} | ]} | ||||
| /> | /> | ||||
| </Col> | |||||
| <Col flex={2}> | |||||
| <Image | |||||
| src={captchaCode} | |||||
| alt="验证码" | |||||
| style={{ | |||||
| display: 'inline-block', | |||||
| verticalAlign: 'top', | |||||
| cursor: 'pointer', | |||||
| paddingLeft: '10px', | |||||
| width: '100px', | |||||
| <ProFormText.Password | |||||
| name="password" | |||||
| initialValue="admin123" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | }} | ||||
| preview={false} | |||||
| onClick={() => getCaptchaCode()} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.password.placeholder', | |||||
| defaultMessage: '密码: admin123', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.password.required" | |||||
| defaultMessage="请输入密码!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | /> | ||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| )} | |||||
| {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />} | |||||
| {type === 'mobile' && ( | |||||
| <> | |||||
| <ProFormText | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <MobileOutlined />, | |||||
| }} | |||||
| name="mobile" | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.phoneNumber.placeholder', | |||||
| defaultMessage: '手机号', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.required" | |||||
| defaultMessage="请输入手机号!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| pattern: /^1\d{10}$/, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.invalid" | |||||
| defaultMessage="手机号格式错误!" | |||||
| <Row> | |||||
| <Col flex={4}> | |||||
| <ProFormText | |||||
| style={{ | |||||
| float: 'right', | |||||
| }} | |||||
| name="code" | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.searchTable.updateForm.ruleName.nameRules" | |||||
| defaultMessage="请输入验证啊" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | /> | ||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormCaptcha | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | |||||
| captchaProps={{ | |||||
| size: 'large', | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证码', | |||||
| })} | |||||
| captchaTextRender={(timing, count) => { | |||||
| if (timing) { | |||||
| return `${count} ${intl.formatMessage({ | |||||
| id: 'pages.getCaptchaSecondText', | |||||
| defaultMessage: '获取验证码', | |||||
| })}`; | |||||
| } | |||||
| return intl.formatMessage({ | |||||
| id: 'pages.login.phoneLogin.getVerificationCode', | |||||
| defaultMessage: '获取验证码', | |||||
| }); | |||||
| }} | |||||
| name="captcha" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.captcha.required" | |||||
| defaultMessage="请输入验证码!" | |||||
| </Col> | |||||
| <Col> | |||||
| <Image | |||||
| src={captchaCode} | |||||
| alt="验证码" | |||||
| style={{ | |||||
| display: 'inline-block', | |||||
| verticalAlign: 'top', | |||||
| cursor: 'pointer', | |||||
| paddingLeft: '22px', | |||||
| width: '170px', | |||||
| height: '66px', | |||||
| }} | |||||
| preview={false} | |||||
| onClick={() => getCaptchaCode()} | |||||
| /> | /> | ||||
| ), | |||||
| }, | |||||
| ]} | |||||
| onGetCaptcha={async (phone) => { | |||||
| const result = await getFakeCaptcha({ | |||||
| phone, | |||||
| }); | |||||
| if (!result) { | |||||
| return; | |||||
| } | |||||
| message.success('获取验证码成功!验证码为:1234'); | |||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| )} | |||||
| {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />} | |||||
| {type === 'mobile' && ( | |||||
| <> | |||||
| <ProFormText | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <MobileOutlined />, | |||||
| }} | |||||
| name="mobile" | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.phoneNumber.placeholder', | |||||
| defaultMessage: '手机号', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.required" | |||||
| defaultMessage="请输入手机号!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| pattern: /^1\d{10}$/, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.invalid" | |||||
| defaultMessage="手机号格式错误!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormCaptcha | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | |||||
| captchaProps={{ | |||||
| size: 'large', | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证码', | |||||
| })} | |||||
| captchaTextRender={(timing, count) => { | |||||
| if (timing) { | |||||
| return `${count} ${intl.formatMessage({ | |||||
| id: 'pages.getCaptchaSecondText', | |||||
| defaultMessage: '获取验证码', | |||||
| })}`; | |||||
| } | |||||
| return intl.formatMessage({ | |||||
| id: 'pages.login.phoneLogin.getVerificationCode', | |||||
| defaultMessage: '获取验证码', | |||||
| }); | |||||
| }} | |||||
| name="captcha" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.captcha.required" | |||||
| defaultMessage="请输入验证码!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| onGetCaptcha={async (phone) => { | |||||
| const result = await getFakeCaptcha({ | |||||
| phone, | |||||
| }); | |||||
| if (!result) { | |||||
| return; | |||||
| } | |||||
| message.success('获取验证码成功!验证码为:1234'); | |||||
| }} | |||||
| /> | |||||
| </> | |||||
| )} | |||||
| <div | |||||
| style={{ | |||||
| marginBottom: 24, | |||||
| }} | }} | ||||
| /> | |||||
| </> | |||||
| )} | |||||
| <div | |||||
| style={{ | |||||
| marginBottom: 24, | |||||
| }} | |||||
| > | |||||
| <ProFormCheckbox noStyle name="autoLogin"> | |||||
| <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" /> | |||||
| </ProFormCheckbox> | |||||
| <a | |||||
| style={{ | |||||
| float: 'right', | |||||
| }} | |||||
| > | |||||
| <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" /> | |||||
| </a> | |||||
| > | |||||
| <ProFormCheckbox noStyle name="autoLogin"> | |||||
| <FormattedMessage id="pages.login.rememberMe" defaultMessage="记住密码" /> | |||||
| </ProFormCheckbox> | |||||
| </div> | |||||
| </LoginForm> | |||||
| </div> | </div> | ||||
| </LoginForm> | |||||
| </div> | |||||
| </div> | </div> | ||||
| <Footer /> */} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,28 +1,31 @@ | |||||
| .loginForm { | .loginForm { | ||||
| width: 520px; | |||||
| } | |||||
| :global .ant-pro-form-login-main { | |||||
| margin: unset; | |||||
| } | |||||
| :global .ant-input-affix-wrapper-lg { | |||||
| padding: 19px 11px; | |||||
| color: rgba(29, 29, 32, 0.6); | |||||
| font-size: 18px; | |||||
| font-family: 'Alibaba'; | |||||
| border-radius: 13px; | |||||
| } | |||||
| :global .ant-input-affix-wrapper { | |||||
| padding: 19px 11px; | |||||
| color: rgba(29, 29, 32, 0.6); | |||||
| font-size: 18px; | |||||
| font-family: 'Alibaba'; | |||||
| border-radius: 13px; | |||||
| } | |||||
| :global .ant-btn.ant-btn-lg { | |||||
| height: 76px; | |||||
| color: #ffffff; | |||||
| font-size: 20px; | |||||
| font-family: 'Alibaba'; | |||||
| background: #1664ff; | |||||
| border-radius: 41px; | |||||
| :global { | |||||
| .ant-pro-form-login-main { | |||||
| width: auto !important; | |||||
| max-width: auto !important; | |||||
| margin: unset; | |||||
| } | |||||
| .ant-input-affix-wrapper-lg { | |||||
| padding: 19px 11px; | |||||
| color: rgba(29, 29, 32, 0.6); | |||||
| font-size: 18px; | |||||
| font-family: 'Alibaba'; | |||||
| border-radius: 13px; | |||||
| } | |||||
| .ant-input-affix-wrapper { | |||||
| padding: 19px 11px; | |||||
| color: rgba(29, 29, 32, 0.6); | |||||
| font-size: 18px; | |||||
| font-family: 'Alibaba'; | |||||
| border-radius: 13px; | |||||
| } | |||||
| .ant-btn.ant-btn-lg { | |||||
| height: 76px; | |||||
| color: #ffffff; | |||||
| font-size: 20px; | |||||
| font-family: 'Alibaba'; | |||||
| background: @primary-color; | |||||
| border-radius: 41px; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -35,10 +35,10 @@ function AssetsManagement() { | |||||
| title: '组件', | title: '组件', | ||||
| value: component, | value: component, | ||||
| }, | }, | ||||
| { | |||||
| title: '代码配置', | |||||
| value: 0, | |||||
| }, | |||||
| // { | |||||
| // title: '代码配置', | |||||
| // value: 0, | |||||
| // }, | |||||
| { | { | ||||
| title: '流水线模版', | title: '流水线模版', | ||||
| value: workflow, | value: workflow, | ||||
| @@ -64,9 +64,8 @@ function AssetsManagement() { | |||||
| ]} | ]} | ||||
| /> | /> | ||||
| </Flex> | </Flex> | ||||
| <div className={styles['assets-management__increase']}>今日新增数量:5</div> | |||||
| <Flex justify="space-between" gap="22px 0" wrap="wrap"> | |||||
| {/* <div className={styles['assets-management__increase']}>今日新增数量:5</div> */} | |||||
| <Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px' }}> | |||||
| {assetCounts.map((item, index) => ( | {assetCounts.map((item, index) => ( | ||||
| <div className={styles['assets-management__summary']} key={index}> | <div className={styles['assets-management__summary']} key={index}> | ||||
| <div className={styles['assets-management__summary__title']}>{item.title}</div> | <div className={styles['assets-management__summary__title']}>{item.title}</div> | ||||