| @@ -0,0 +1,7 @@ | |||
| .kf-confirm-modal { | |||
| &__content { | |||
| width: 100%; | |||
| font-size: 18px; | |||
| text-align: center; | |||
| } | |||
| } | |||
| @@ -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 ( | |||
| <KFModal | |||
| className={classNames(['kf-confirm-modal', className])} | |||
| {...rest} | |||
| centered={centered ?? true} | |||
| maskClosable={maskClosable ?? false} | |||
| title={title} | |||
| image={image ?? require('@/assets/img/edit-experiment.png')} | |||
| > | |||
| <div className="kf-confirm-modal__content">{content}</div> | |||
| </KFModal> | |||
| ); | |||
| } | |||
| export default KFConfirmModal; | |||
| @@ -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 }, | |||
| ]; | |||
| @@ -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} | |||
| /> | |||
| ))} | |||
| </div> | |||
| @@ -123,7 +123,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| message: '版本只支持字母、数字、下划线、点、横杠', | |||
| message: '版本只支持字母、数字、点、下划线、中横线', | |||
| }, | |||
| { | |||
| validator: (_rule, value) => { | |||
| @@ -108,7 +108,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| message: '版本只支持字母、数字、下划线、点、横杠', | |||
| message: '版本只支持字母、数字、点、下划线、中横线', | |||
| }, | |||
| { | |||
| validator: (_rule, value) => { | |||
| @@ -123,7 +123,7 @@ function AddVersionModal({ | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| message: '版本只支持字母、数字、下划线、点、横杠', | |||
| message: '版本只支持字母、数字、点、下划线、中横线', | |||
| }, | |||
| { | |||
| validator: (_rule, value) => { | |||
| @@ -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) => { | |||
| @@ -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'); | |||
| }; | |||
| @@ -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); | |||
| } | |||
| }; | |||
| @@ -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<ServiceData | undefined>(undefined); | |||
| const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(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: | |||
| '请输入正确的挂载路径,以 / 开头,只支持字母、数字、点、下划线、中横线、斜杠', | |||
| }, | |||
| ]} | |||
| > | |||
| @@ -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') { | |||
| @@ -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, | |||
| ); | |||
| @@ -0,0 +1,6 @@ | |||
| .basic-info { | |||
| a:hover { | |||
| text-decoration: underline @underline-color; | |||
| text-underline-offset: 3px; | |||
| } | |||
| } | |||
| @@ -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 ( | |||
| <a href={url} target="_blank" rel="noreferrer"> | |||
| {info?.code_config?.show_value} | |||
| @@ -53,7 +56,7 @@ function BasicInfo({ info }: BasicInfoProps) { | |||
| }; | |||
| return ( | |||
| <div> | |||
| <div className={styles['basic-info']}> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <LabelValue label="服务名称:" value={info?.service_name}></LabelValue> | |||
| @@ -1,7 +1,7 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-18 18:35:41 | |||
| * @Description: 模型部署状态 | |||
| * @Description: 服务运行状态 | |||
| */ | |||
| import { ServiceRunStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| @@ -57,7 +57,7 @@ function ServerLog({ info }: ServerLogProps) { | |||
| getModelDeploymentLog(); | |||
| }, [info, logTime]); | |||
| // 获取模型部署日志 | |||
| // 获取服务日志 | |||
| const getModelDeploymentLog = async () => { | |||
| if (info && logTime && logTime.length === 2) { | |||
| const params = { | |||
| @@ -14,7 +14,7 @@ function UserGuide({ info }: UserGuideProps) { | |||
| getModelDeploymentDocs(); | |||
| }, [info]); | |||
| // 获取模型部署文档 | |||
| // 获取服务文档 | |||
| const getModelDeploymentDocs = async () => { | |||
| if (info) { | |||
| const [res] = await to(getServiceVersionDocsReq(info.id)); | |||
| @@ -57,3 +57,12 @@ export enum ServiceOperationType { | |||
| Update = 'Update', // 更新 | |||
| Restart = 'Restart', // 重启 | |||
| } | |||
| // 操作类型 | |||
| export enum CreateServiceVersionFrom { | |||
| CreateService = 'CreateService', // 来自创建服务 | |||
| ServiceInfo = 'ServiceInfo', // 来自服务详情 | |||
| } | |||
| // 去创建服务版本消息 | |||
| export const createServiceVersionMessage = 'createServiceVersion'; | |||
| @@ -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<string, any>, mapping: Record<str | |||
| } | |||
| /** | |||
| * 计算显示的字符串 | |||
| * @param tr 要裁剪的字符串 | |||
| * @param maxWidth 最大宽度 | |||
| * @param fontSize 字体大小 | |||
| * @return 处理后的字符串 | |||
| * 计算能显示的字符串 | |||
| * @param {string} str 要裁剪的字符串 | |||
| * @param {number} maxWidth 最大宽度 | |||
| * @param {number} fontSize 字体大小 | |||
| * @return {string} 处理后的字符串 | |||
| */ | |||
| export const fittingString = (str: string, maxWidth: number, fontSize: number) => { | |||
| 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; | |||
| }; | |||
| @@ -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: ( | |||
| <div> | |||
| <img | |||
| src={require('@/assets/img/delete-icon.png')} | |||
| src={ | |||
| isDelete | |||
| ? require('@/assets/img/delete-icon.png') | |||
| : require('@/assets/img/comfirm-icon.png') | |||
| } | |||
| style={{ width: '120px', marginBottom: '24px' }} | |||
| draggable={false} | |||
| alt="" | |||
| @@ -28,8 +44,8 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) | |||
| </div> | |||
| ), | |||
| content: content && <div style={{ color: themes.textColor, fontSize: '15px' }}>{content}</div>, | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| okText: okText, | |||
| cancelText: cancelText, | |||
| onOk: onOk, | |||
| }); | |||
| } | |||
| @@ -243,11 +243,11 @@ | |||
| <version>3.6.0</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>cn.hutool</groupId> | |||
| <artifactId>hutool-all</artifactId> | |||
| <version>5.8.5</version> | |||
| </dependency> | |||
| <!-- <dependency>--> | |||
| <!-- <groupId>cn.hutool</groupId>--> | |||
| <!-- <artifactId>hutool-all</artifactId>--> | |||
| <!-- <version>5.8.5</version>--> | |||
| <!-- </dependency>--> | |||
| </dependencies> | |||
| <build> | |||
| @@ -1,6 +1,6 @@ | |||
| package com.ruoyi.platform.service.impl; | |||
| import cn.hutool.core.util.StrUtil; | |||
| //import cn.hutool.core.util.StrUtil; | |||
| import com.alibaba.fastjson2.JSON; | |||
| import com.alibaba.fastjson2.JSONArray; | |||
| import com.alibaba.fastjson2.JSONObject; | |||
| @@ -1216,9 +1216,9 @@ public class ModelsServiceImpl implements ModelsService { | |||
| modelMetaVo.setMetrics(result); | |||
| } | |||
| void test(){ | |||
| String str = "Hello, Hutool!"; | |||
| System.out.println(StrUtil.isEmpty(str)); // 判断字符串是否为空 | |||
| System.out.println(StrUtil.sub(str, 0, 5)); | |||
| } | |||
| // void test(){ | |||
| // String str = "Hello, Hutool!"; | |||
| // System.out.println(StrUtil.isEmpty(str)); // 判断字符串是否为空 | |||
| // System.out.println(StrUtil.sub(str, 0, 5)); | |||
| // } | |||
| } | |||