diff --git a/react-ui/src/assets/img/comfirm-icon.png b/react-ui/src/assets/img/comfirm-icon.png new file mode 100644 index 00000000..b0eebfe8 Binary files /dev/null and b/react-ui/src/assets/img/comfirm-icon.png differ diff --git a/react-ui/src/components/KFConfirmModal/index.less b/react-ui/src/components/KFConfirmModal/index.less new file mode 100644 index 00000000..5c82b5ec --- /dev/null +++ b/react-ui/src/components/KFConfirmModal/index.less @@ -0,0 +1,7 @@ +.kf-confirm-modal { + &__content { + width: 100%; + font-size: 18px; + text-align: center; + } +} diff --git a/react-ui/src/components/KFConfirmModal/index.tsx b/react-ui/src/components/KFConfirmModal/index.tsx new file mode 100644 index 00000000..d494ad2a --- /dev/null +++ b/react-ui/src/components/KFConfirmModal/index.tsx @@ -0,0 +1,37 @@ +/* + * @Author: 赵伟 + * @Date: 2024-10-10 10:54:25 + * @Description: 自定义 Confirm Modal + */ + +import classNames from 'classnames'; +import KFModal, { KFModalProps } from '../KFModal'; +import './index.less'; + +export interface KFConfirmModalProps extends KFModalProps { + content: string; +} +function KFConfirmModal({ + title, + image, + className = '', + centered, + maskClosable, + content, + ...rest +}: KFConfirmModalProps) { + return ( + + {content} + + ); +} + +export default KFConfirmModal; diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index 4b3ded7b..e489515c 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -80,7 +80,7 @@ export enum ServiceType { export const serviceTypeOptions = [ { label: '视频', value: ServiceType.Video }, - { label: '图像', value: ServiceType.Image }, + { label: '图片', value: ServiceType.Image }, { label: '音频', value: ServiceType.Audio }, { label: '文本', value: ServiceType.Text }, ]; diff --git a/react-ui/src/pages/CodeConfig/List/index.tsx b/react-ui/src/pages/CodeConfig/List/index.tsx index b61e9016..847d30ec 100644 --- a/react-ui/src/pages/CodeConfig/List/index.tsx +++ b/react-ui/src/pages/CodeConfig/List/index.tsx @@ -1,6 +1,7 @@ import KFEmpty, { EmptyType } from '@/components/KFEmpty'; import KFIcon from '@/components/KFIcon'; import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; +import { getGitUrl } from '@/utils'; import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; @@ -99,6 +100,13 @@ function CodeConfigList() { }); }; + // 查看 + const handleClick = (record: CodeConfigData) => { + const { git_url, git_branch } = record; + const url = getGitUrl(git_url, git_branch); + window.open(url, '_blank'); + }; + // 新建 const createCodeConfig = () => { const { close } = openAntdModal(AddCodeConfigModal, { @@ -152,6 +160,7 @@ function CodeConfigList() { key={item.id} onRemove={handleRemove} onEdit={handleEdit} + onClick={handleClick} /> ))} diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx index bac2c897..87d215bd 100644 --- a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx @@ -123,7 +123,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr }, { pattern: /^[a-zA-Z0-9._-]+$/, - message: '版本只支持字母、数字、下划线、点、横杠', + message: '版本只支持字母、数字、点、下划线、中横线', }, { validator: (_rule, value) => { diff --git a/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx b/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx index f93eab57..663a86e7 100644 --- a/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx @@ -108,7 +108,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) }, { pattern: /^[a-zA-Z0-9._-]+$/, - message: '版本只支持字母、数字、下划线、点、横杠', + message: '版本只支持字母、数字、点、下划线、中横线', }, { validator: (_rule, value) => { diff --git a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx index abff34a0..a91159fc 100644 --- a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx @@ -123,7 +123,7 @@ function AddVersionModal({ }, { pattern: /^[a-zA-Z0-9._-]+$/, - message: '版本只支持字母、数字、下划线、点、横杠', + message: '版本只支持字母、数字、点、下划线、中横线', }, { validator: (_rule, value) => { diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx index 21907f63..2ee7fb24 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx @@ -9,6 +9,7 @@ import { ResourceType, TrainTask, } from '@/pages/Dataset/config'; +import { getGitUrl } from '@/utils'; import styles from './index.less'; type ResourceIntroProps = { @@ -54,9 +55,7 @@ const getProjectUrl = (project?: ProjectDependency) => { return undefined; } const { url, branch } = project; - if (url.endsWith('.git')) { - return `${url.substring(0, url.length - 4)}/tree/${branch}`; - } + return getGitUrl(url, branch); }; const formatProject = (project?: ProjectDependency) => { diff --git a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx index b868de23..8a0f055e 100644 --- a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx +++ b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx @@ -1,4 +1,5 @@ import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; +import { getGitUrl } from '@/utils'; import { formatDate } from '@/utils/date'; import { useNavigate } from '@umijs/max'; import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; @@ -127,10 +128,7 @@ function DatasetInfo({ data }: { data: TrainDataset }) { function ProjectInfo({ data }: { data: ProjectDependency }) { const gotoProjectPage = () => { const { url, branch } = data; - let projectUrl = url; - if (url.endsWith('.git')) { - projectUrl = `${url.substring(0, url.length - 4)}/tree/${branch}`; - } + const projectUrl = getGitUrl(url, branch); window.open(projectUrl, '_blank'); }; diff --git a/react-ui/src/pages/ModelDeployment/CreateService/index.tsx b/react-ui/src/pages/ModelDeployment/CreateService/index.tsx index a827828f..62e26412 100644 --- a/react-ui/src/pages/ModelDeployment/CreateService/index.tsx +++ b/react-ui/src/pages/ModelDeployment/CreateService/index.tsx @@ -18,7 +18,7 @@ import { useNavigate } from '@umijs/max'; import { App, Button, Col, Form, Input, Row, Select } from 'antd'; import { pick } from 'lodash'; import { useEffect, useState } from 'react'; -import { ServiceData, ServiceOperationType } from '../types'; +import { ServiceData, ServiceOperationType, createServiceVersionMessage } from '../types'; import styles from './index.less'; // 表单数据 @@ -59,9 +59,12 @@ function CreateService() { ...formData, }; const [res] = await to(request(params)); - if (res) { + if (res && res.data) { message.success('操作成功'); navigate(-1); + setTimeout(() => { + window.postMessage({ type: createServiceVersionMessage, payload: res.data.id }); + }, 500); } }; diff --git a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx index 39013390..364cb033 100644 --- a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx +++ b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx @@ -31,7 +31,12 @@ import { useNavigate, useParams } from '@umijs/max'; import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd'; import { omit, pick } from 'lodash'; import { useEffect, useState } from 'react'; -import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; +import { + CreateServiceVersionFrom, + ServiceData, + ServiceOperationType, + ServiceVersionData, +} from '../types'; import styles from './index.less'; // 表单数据 @@ -53,6 +58,7 @@ function CreateServiceVersion() { const [form] = Form.useForm(); const [resourceStandardList, filterResourceStandard] = useComputingResource(); const [operationType, setOperationType] = useState(ServiceOperationType.Create); + const [lastPage, setLastPage] = useState(CreateServiceVersionFrom.ServiceInfo); const { message } = App.useApp(); const [serviceInfo, setServiceInfo] = useState(undefined); const [versionInfo, setVersionInfo] = useState(undefined); @@ -60,10 +66,15 @@ function CreateServiceVersion() { const id = params.id; useEffect(() => { - const res: (ServiceVersionData & { operationType: ServiceOperationType }) | undefined = - getSessionStorageItem(serviceVersionInfoKey, true); + const res: + | (ServiceVersionData & { + operationType: ServiceOperationType; + lastPage: CreateServiceVersionFrom; + }) + | undefined = getSessionStorageItem(serviceVersionInfoKey, true); if (res) { setOperationType(res.operationType); + setLastPage(res.lastPage); setVersionInfo(res); let model, codeConfig, envVariables; if (res.model && typeof res.model === 'object') { @@ -156,7 +167,11 @@ function CreateServiceVersion() { const [res] = await to(request(params)); if (res) { message.success('操作成功'); - navigate(-1); + if (lastPage === CreateServiceVersionFrom.ServiceInfo) { + navigate(-1); + } else { + navigate(`/modelDeployment/serviceInfo/${serviceInfo?.id}`, { replace: true }); + } } }; @@ -234,7 +249,7 @@ function CreateServiceVersion() { }, { pattern: /^[a-zA-Z0-9._-]+$/, - message: '版本只支持字母、数字、下划线、点、横杠', + message: '版本只支持字母、数字、点、下划线、中横线', }, ]} > @@ -401,7 +416,8 @@ function CreateServiceVersion() { }, { pattern: /^\/[a-zA-Z0-9._/-]+$/, - message: '请输入正确的挂载绝对路径', + message: + '请输入正确的挂载路径,以 / 开头,只支持字母、数字、点、下划线、中横线、斜杠', }, ]} > diff --git a/react-ui/src/pages/ModelDeployment/List/index.tsx b/react-ui/src/pages/ModelDeployment/List/index.tsx index f0e4cb70..238e4b40 100644 --- a/react-ui/src/pages/ModelDeployment/List/index.tsx +++ b/react-ui/src/pages/ModelDeployment/List/index.tsx @@ -12,7 +12,11 @@ import { useCacheState } from '@/hooks/pageCacheState'; import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; -import { serviceInfoKey, setSessionStorageItem } from '@/utils/sessionStorage'; +import { + serviceInfoKey, + serviceVersionInfoKey, + setSessionStorageItem, +} from '@/utils/sessionStorage'; import { modalConfirm } from '@/utils/ui'; import { useNavigate } from '@umijs/max'; import { @@ -28,7 +32,12 @@ import { import { type SearchProps } from 'antd/es/input'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; -import { ServiceData, ServiceOperationType } from '../types'; +import { + CreateServiceVersionFrom, + ServiceData, + ServiceOperationType, + createServiceVersionMessage, +} from '../types'; import styles from './index.less'; const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions]; @@ -49,6 +58,13 @@ function ModelDeployment() { }, ); + useEffect(() => { + window.addEventListener('message', handleMessage); + return () => { + window.removeEventListener('message', handleMessage); + }; + }, []); + useEffect(() => { getServiceList(); }, [pagination, searchText, serviceType]); @@ -137,6 +153,35 @@ function ModelDeployment() { navigate(`/modelDeployment/serviceInfo/${record.id}`); }; + const handleMessage = (e: MessageEvent) => { + const { type, payload } = e.data; + if (type === createServiceVersionMessage) { + modalConfirm({ + title: '创建服务成功', + content: '是否创建服务版本?', + isDelete: false, + cancelText: '稍后创建', + onOk: () => { + gotoCreateServiceVersion(payload); + }, + }); + } + }; + + // 去创建服务版本 + const gotoCreateServiceVersion = (serviceId: number) => { + setSessionStorageItem( + serviceVersionInfoKey, + { + operationType: ServiceOperationType.Create, + lastPage: CreateServiceVersionFrom.CreateService, + }, + true, + ); + + navigate(`/modelDeployment/addVersion/${serviceId}`); + }; + // 分页切换 const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { if (action === 'paginate') { diff --git a/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx b/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx index 21091b5a..771a1c4f 100644 --- a/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx +++ b/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx @@ -38,7 +38,12 @@ import { type SearchProps } from 'antd/es/input'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; import ServiceRunStatusCell from '../components/ModelDeployStatusCell'; -import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; +import { + CreateServiceVersionFrom, + ServiceData, + ServiceOperationType, + ServiceVersionData, +} from '../types'; import styles from './index.less'; const allServiceStatusOptions = [{ label: '全部', value: '' }, ...serviceStatusOptions]; @@ -171,13 +176,14 @@ function ServiceInfo() { }); }; - // 创建、更新、重启模型部署 + // 创建、更新、重启服务版本 const createServiceVersion = (type: ServiceOperationType, record?: ServiceVersionData) => { setSessionStorageItem( serviceVersionInfoKey, { ...record, operationType: type, + lastPage: CreateServiceVersionFrom.ServiceInfo, }, true, ); diff --git a/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.less b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.less new file mode 100644 index 00000000..0995b6c7 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.less @@ -0,0 +1,6 @@ +.basic-info { + a:hover { + text-decoration: underline @underline-color; + text-underline-offset: 3px; + } +} diff --git a/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx index e6f62cae..f741f750 100644 --- a/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx +++ b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx @@ -1,10 +1,12 @@ import LabelValue from '@/components/LabelValue'; import { useComputingResource } from '@/hooks/resource'; import { ServiceVersionData } from '@/pages/ModelDeployment/types'; +import { getGitUrl } from '@/utils'; import { formatDate } from '@/utils/date'; import { Link } from '@umijs/max'; import { Col, Row } from 'antd'; import ServiceRunStatusCell from '../ModelDeployStatusCell'; +import styles from './index.less'; type BasicInfoProps = { info?: ServiceVersionData; @@ -15,7 +17,7 @@ function BasicInfo({ info }: BasicInfoProps) { // 格式化环境变量 const formatEnvText = () => { - if (!info?.env_variables) { + if (!info?.env_variables || Object.keys(info.env_variables).length === 0) { return '--'; } const env = info.env_variables; @@ -26,7 +28,8 @@ function BasicInfo({ info }: BasicInfoProps) { const formatCodeConfig = () => { if (info && info.code_config) { - const url = `${info.code_config.code_path}/tree/${info.code_config.branch}`; + const { code_path, branch } = info.code_config; + const url = getGitUrl(code_path, branch); return ( {info?.code_config?.show_value} @@ -53,7 +56,7 @@ function BasicInfo({ info }: BasicInfoProps) { }; return ( - + diff --git a/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx b/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx index 7029c8fd..e11f8e43 100644 --- a/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx +++ b/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx @@ -1,7 +1,7 @@ /* * @Author: 赵伟 * @Date: 2024-04-18 18:35:41 - * @Description: 模型部署状态 + * @Description: 服务运行状态 */ import { ServiceRunStatus } from '@/enums'; import styles from './index.less'; diff --git a/react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx b/react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx index ad74986f..2f6d0368 100644 --- a/react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx +++ b/react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx @@ -57,7 +57,7 @@ function ServerLog({ info }: ServerLogProps) { getModelDeploymentLog(); }, [info, logTime]); - // 获取模型部署日志 + // 获取服务日志 const getModelDeploymentLog = async () => { if (info && logTime && logTime.length === 2) { const params = { diff --git a/react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx b/react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx index c2b73d47..8abd15be 100644 --- a/react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx +++ b/react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx @@ -14,7 +14,7 @@ function UserGuide({ info }: UserGuideProps) { getModelDeploymentDocs(); }, [info]); - // 获取模型部署文档 + // 获取服务文档 const getModelDeploymentDocs = async () => { if (info) { const [res] = await to(getServiceVersionDocsReq(info.id)); diff --git a/react-ui/src/pages/ModelDeployment/types.ts b/react-ui/src/pages/ModelDeployment/types.ts index d53655b2..a932b71c 100644 --- a/react-ui/src/pages/ModelDeployment/types.ts +++ b/react-ui/src/pages/ModelDeployment/types.ts @@ -57,3 +57,12 @@ export enum ServiceOperationType { Update = 'Update', // 更新 Restart = 'Restart', // 重启 } + +// 操作类型 +export enum CreateServiceVersionFrom { + CreateService = 'CreateService', // 来自创建服务 + ServiceInfo = 'ServiceInfo', // 来自服务详情 +} + +// 去创建服务版本消息 +export const createServiceVersionMessage = 'createServiceVersion'; diff --git a/react-ui/src/utils/index.ts b/react-ui/src/utils/index.ts index 8ef057ba..4938e2ca 100644 --- a/react-ui/src/utils/index.ts +++ b/react-ui/src/utils/index.ts @@ -31,7 +31,7 @@ export function parseJsonText(text?: string | null): any | null { } } -// 判断是否为对象 +// 判断是否为一般对象 function isPlainObject(value: any) { if (value === null || typeof value !== 'object') return false; let proto = Object.getPrototypeOf(value); @@ -160,13 +160,13 @@ export function changePropertyName(obj: Record, mapping: Record { +export const fittingString = (str: string, maxWidth: number, fontSize: number): string => { if (!str) { return ''; } @@ -200,3 +200,24 @@ export const fittingString = (str: string, maxWidth: number, fontSize: number) = export const isEmptyString = (str: any): boolean => { return str === '' || str === undefined || str === null; }; + +/** + * 获取 git 仓库的 url + * + * @param {string} url - the url of the git repository + * @param {string} branch - the branch of the repository + * @return {string} the url of the repository + * + * If `branch` is given, the url will be in the format of 'http://gitlab.com/user/repo/tree/branch'. + * Otherwise, the url will be in the format of 'http://gitlab.com/user/repo'. + */ +export const getGitUrl = (url: string, branch: string): string => { + if (!url) { + return ''; + } + const gitUrl = url.replace(/\.git$/, ''); + if (branch) { + return `${gitUrl}/tree/${branch}`; + } + return gitUrl; +}; diff --git a/react-ui/src/utils/ui.tsx b/react-ui/src/utils/ui.tsx index 7625a771..9034a67b 100644 --- a/react-ui/src/utils/ui.tsx +++ b/react-ui/src/utils/ui.tsx @@ -10,8 +10,20 @@ import { history } from '@umijs/max'; import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; import { closeAllModals } from './modal'; -// 自定义 Confirm 弹框 -export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) { +type ModalConfirmProps = ModalFuncProps & { + isDelete?: boolean; +}; + +// 自定义删除 Confirm 弹框 +export function modalConfirm({ + title, + content, + okText = '确认', + cancelText = '取消', + isDelete = true, + onOk, + ...rest +}: ModalConfirmProps) { Modal.confirm({ ...rest, width: 600, @@ -19,7 +31,11 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) title: ( ), content: content && {content}, - okText: '确认', - cancelText: '取消', + okText: okText, + cancelText: cancelText, onOk: onOk, }); }