diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 1351cd2c..7edf6db2 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -107,7 +107,12 @@ export default [ { name: '开发环境', path: '', - component: './DevelopmentEnvironment/index', + component: './DevelopmentEnvironment/List', + }, + { + name: '创建编辑器', + path: 'create', + component: './DevelopmentEnvironment/Create', }, ], }, diff --git a/react-ui/src/assets/img/editor-parameter.png b/react-ui/src/assets/img/editor-parameter.png new file mode 100644 index 00000000..b5fd9f41 Binary files /dev/null and b/react-ui/src/assets/img/editor-parameter.png differ diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx index 4b838b2d..0fc08551 100644 --- a/react-ui/src/components/ParameterInput/index.tsx +++ b/react-ui/src/components/ParameterInput/index.tsx @@ -43,7 +43,7 @@ function ParameterInput({ } const isSelect = valueObj?.fromSelect; const InputComponent = textArea ? Input.TextArea : Input; - const placeholder = valueObj?.placeholder; + const placeholder = valueObj?.placeholder || rest?.placeholder; return ( <> diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index c0238791..b31aee3a 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -20,6 +20,7 @@ export enum ModelDeploymentStatus { Pending = 'Pending', // 挂起中 } +// 模型部署状态选项列表 export const modelDeploymentStatusOptions = [ { label: '全部', value: '' }, { label: '启动中', value: ModelDeploymentStatus.Init }, @@ -28,3 +29,12 @@ export const modelDeploymentStatusOptions = [ { label: '失败', value: ModelDeploymentStatus.Failed }, { label: '挂起中', value: ModelDeploymentStatus.Pending }, ]; + +// 开发环境编辑器状态 +export enum DevEditorStatus { + Pending = 'Pending', // 启动中 + Running = 'Running', // 运行中 + Terminated = 'Terminated', // 已终止 + Failed = 'Failed', // 失败 + Unknown = 'Unknown', // 未启动 +} diff --git a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx index 9b20147a..5cb8b9a7 100644 --- a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx @@ -33,10 +33,11 @@ function AddVersionModal({ ...rest }: AddVersionModalProps) { const [uuid] = useState(Date.now()); + const config = resourceConfig[resourceType]; // 上传组件参数 const uploadProps: UploadProps = { - action: resourceConfig[resourceType].uploadAction, + action: config.uploadAction, headers: { Authorization: getAccessToken() || '', }, @@ -45,7 +46,7 @@ function AddVersionModal({ // 上传请求 const createDatasetVersion = async (params: any) => { - const request = resourceConfig[resourceType].addVersion; + const request = config.addVersion; const [res] = await to(request(params)); if (res) { message.success('创建成功'); @@ -62,7 +63,7 @@ function AddVersionModal({ const data = item.response?.data?.[0] ?? {}; return { ...otherParams, - [resourceConfig[resourceType].idParamKey]: resourceId, + [config.idParamKey]: resourceId, file_name: data.fileName, file_size: data.fileSize, url: data.url, @@ -72,8 +73,8 @@ function AddVersionModal({ } }; - const name = resourceConfig[resourceType].name; - const accept = resourceConfig[resourceType].uploadAccept; + const name = config.name; + const accept = config.uploadAccept; return ( {item.name} diff --git a/react-ui/src/pages/Dataset/components/CategoryList/index.tsx b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx index 87980da7..8582168e 100644 --- a/react-ui/src/pages/Dataset/components/CategoryList/index.tsx +++ b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx @@ -29,15 +29,14 @@ function CategoryList({ onTagSelect, onSearch, }: CategoryProps) { + const config = resourceConfig[resourceType]; return (
-
- {resourceConfig[resourceType].typeTitle} -
+
{config.typeTitle}
{typeList?.map((item) => (
- {resourceConfig[resourceType].tagTitle} + {config.tagTitle}
{tagList?.map((item) => ( diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx index 91a63582..bcbd123f 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx @@ -28,16 +28,17 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { const [version, setVersion] = useState(undefined); const [activeTab, setActiveTab] = useState(defaultTab); const resourceId = Number(locationParams.id); - const typeName = resourceConfig[resourceType].name; // 数据集/模型 + const config = resourceConfig[resourceType]; + const typeName = config.name; // 数据集/模型 useEffect(() => { getModelByDetail(); getVersionList(); - }, []); + }, [resourceId]); // 获取详情 const getModelByDetail = async () => { - const request = resourceConfig[resourceType].getInfo; + const request = config.getInfo; const [res] = await to(request(resourceId)); if (res) { setInfo(res.data); @@ -46,7 +47,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { // 获取版本列表 const getVersionList = async () => { - const request = resourceConfig[resourceType].getVersions; + const request = config.getVersions; const [res] = await to(request(resourceId)); if (res && res.data && res.data.length > 0) { setVersionList( @@ -122,10 +123,8 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { }); } - const infoTypePropertyName = resourceConfig[resourceType] - .infoTypePropertyName as keyof ResourceData; - const infoTagPropertyName = resourceConfig[resourceType] - .infoTagPropertyName as keyof ResourceData; + const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; + const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; return (
diff --git a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx index 830a9e1c..e7891141 100644 --- a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx @@ -54,6 +54,7 @@ function ResourceList( const [searchText, setSearchText] = useState(initialSearchText); const [inputText, setInputText] = useState(initialSearchText); const { message } = App.useApp(); + const config = resourceConfig[resourceType]; useEffect(() => { getDataList(); @@ -82,12 +83,12 @@ function ResourceList( const params = { page: pagination.current! - 1, size: pagination.pageSize, - [resourceConfig[resourceType].typeParamKey]: dataType, - [resourceConfig[resourceType].tagParamKey]: dataTag, + [config.typeParamKey]: dataType, + [config.tagParamKey]: dataTag, available_range: isPublic ? 1 : 0, name: searchText !== '' ? searchText : undefined, }; - const request = resourceConfig[resourceType].getList; + const request = config.getList; const [res] = await to(request(params)); if (res && res.data && res.data.content) { setDataList(res.data.content); @@ -97,7 +98,7 @@ function ResourceList( // 删除请求 const deleteRecord = async (id: number) => { - const request = resourceConfig[resourceType].deleteRecord; + const request = config.deleteRecord; const [res] = await to(request(id)); if (res) { getDataList(); @@ -113,7 +114,7 @@ function ResourceList( // 删除 const handleRemove = (record: ResourceData) => { modalConfirm({ - title: resourceConfig[resourceType].deleteModalTitle, + title: config.deleteModalTitle, onOk: () => { deleteRecord(record.id); }, @@ -129,7 +130,7 @@ function ResourceList( activeType: dataType, activeTag: dataTag, }); - const prefix = resourceConfig[resourceType].prefix; + const prefix = config.prefix; navigate(`/dataset/${prefix}/${record.id}`); }; @@ -176,7 +177,7 @@ function ResourceList( onClick={showModal} icon={} > - {resourceConfig[resourceType].addBtnTitle} + {config.addBtnTitle} )}
diff --git a/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx index d5e1ed43..e5180a01 100644 --- a/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx @@ -21,6 +21,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) { const [activeType, setActiveType] = useState(cacheState?.activeType); const [activeTag, setActiveTag] = useState(cacheState?.activeTag); const dataListRef = useRef(null); + const config = resourceConfig[resourceType]; useEffect(() => { getAssetIconList(); @@ -52,16 +53,10 @@ function ResourcePage({ resourceType }: ResourcePageProps) { if (res && res.data && res.data.content) { const { content } = res.data; setTypeList( - content.filter( - (item: CategoryData) => - Number(item.category_id) === resourceConfig[resourceType].typeValue, - ), + content.filter((item: CategoryData) => Number(item.category_id) === config.typeValue), ); setTagList( - content.filter( - (item: CategoryData) => - Number(item.category_id) === resourceConfig[resourceType].tagValue, - ), + content.filter((item: CategoryData) => Number(item.category_id) === config.tagValue), ); } }; @@ -76,11 +71,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) { return (
- +
([]); const { message } = App.useApp(); + const config = resourceConfig[resourceType]; // 获取版本文件列表 useEffectWhen( @@ -59,9 +60,9 @@ function ResourceVersion({ const getFileList = async (version: string) => { const params = { version, - [resourceConfig[resourceType].fileReqParamKey]: resourceId, + [config.fileReqParamKey]: resourceId, }; - const request = resourceConfig[resourceType].getFiles; + const request = config.getFiles; const [res] = await to(request(params)); if (res) { setFileList(res?.data?.content ?? []); @@ -70,9 +71,9 @@ function ResourceVersion({ // 删除版本 const deleteVersion = async () => { - const request = resourceConfig[resourceType].deleteVersion; + const request = config.deleteVersion; const params = { - [resourceConfig[resourceType].idParamKey]: resourceId, + [config.idParamKey]: resourceId, version, }; const [res] = await to(request(params)); @@ -111,13 +112,13 @@ function ResourceVersion({ // 全部导出 const handleExport = async () => { - const url = resourceConfig[resourceType].downloadAllAction; + const url = config.downloadAllAction; downLoadZip(url, { models_id: resourceId, version }); }; // 单个导出 const downloadAlone = (record: ResourceFileData) => { - const url = resourceConfig[resourceType].downloadSingleAction; + const url = config.downloadSingleAction; downLoadZip(`${url}/${record.id}`); }; diff --git a/react-ui/src/pages/DevelopmentEnvironment/Create/index.less b/react-ui/src/pages/DevelopmentEnvironment/Create/index.less new file mode 100644 index 00000000..cd1dcb27 --- /dev/null +++ b/react-ui/src/pages/DevelopmentEnvironment/Create/index.less @@ -0,0 +1,17 @@ +.editor-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; + } + } +} diff --git a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx new file mode 100644 index 00000000..036fc12c --- /dev/null +++ b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx @@ -0,0 +1,344 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 创建镜像 + */ +import KFIcon from '@/components/KFIcon'; +import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; +import PageTitle from '@/components/PageTitle'; +import ParameterInput from '@/components/ParameterInput'; +import SubAreaTitle from '@/components/SubAreaTitle'; +import { useComputingResource } from '@/hooks/resource'; +import ResourceSelectorModal, { + ResourceSelectorResponse, + ResourceSelectorType, + selectorTypeConfig, +} from '@/pages/Pipeline/components/ResourceSelectorModal'; +import { createEditorReq } from '@/services/developmentEnvironment'; +import { openAntdModal } from '@/utils/modal'; +import { to } from '@/utils/promise'; +import { useNavigate } from '@umijs/max'; +import { App, Button, Col, Form, Input, Row, Select } from 'antd'; +import { pick } from 'lodash'; +import { useState } from 'react'; +import styles from './index.less'; + +type FormData = { + name: string; + computing_resource: string; + standard: string; + image: string; + model: ResourceSelectorResponse; + dataset: ResourceSelectorResponse; +}; + +enum ComputingResourceType { + GPU = 'GPU', + NPU = 'NPU', +} + +const EditorRadioItems: KFRadioItem[] = [ + { + key: ComputingResourceType.GPU, + title: '英伟达GPU', + icon: , + }, + { + key: ComputingResourceType.NPU, + title: '昇腾NPU', + icon: , + }, +]; + +function EditorCreate() { + const navgite = useNavigate(); + const [form] = Form.useForm(); + const { message } = App.useApp(); + const [resourceStandardList, filterResourceStandard] = useComputingResource(); + const [selectedModel, setSelectedModel] = useState( + undefined, + ); // 选择的模型,为了再次打开时恢复原来的选择 + const [selectedDataset, setSelectedDataset] = useState( + undefined, + ); // 选择的数据集,为了再次打开时恢复原来的选择 + const [selectedMirror, setSelectedMirror] = useState( + undefined, + ); // 选择的镜像,为了再次打开时恢复原来的选择 + + // 创建编辑器 + const createEditor = async (formData: FormData) => { + // const { model, dataset } = formData; + // const params = { + // ...formData, + // model: JSON.stringify(omit(model, ['showValue'])), + // dataset: JSON.stringify(dataset, ['showValue']), + // }; + const [res] = await to(createEditorReq(formData)); + if (res) { + message.success('创建成功'); + navgite(-1); + } + }; + + // 提交 + const handleSubmit = (values: FormData) => { + createEditor(values); + }; + + // 取消 + const cancel = () => { + navgite(-1); + }; + // 获取选择数据集、模型后面按钮 icon + const getSelectBtnIcon = (type: ResourceSelectorType) => { + return ; + }; + + // 选择模型、镜像、数据集 + const selectResource = (name: string, type: ResourceSelectorType) => { + let resource: ResourceSelectorResponse | undefined; + switch (type) { + case ResourceSelectorType.Model: + resource = selectedModel; + break; + case ResourceSelectorType.Dataset: + resource = selectedDataset; + break; + default: + resource = selectedMirror; + 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.path); + setSelectedMirror(res); + } else { + const showValue = `${res.name}:${res.version}`; + form.setFieldValue(name, { + ...pick(res, ['id', 'version', 'path']), + showValue, + }); + if (type === ResourceSelectorType.Model) { + setSelectedModel(res); + } else if (type === ResourceSelectorType.Dataset) { + setSelectedDataset(res); + } + } + } else { + if (type === ResourceSelectorType.Model) { + setSelectedModel(undefined); + } else if (type === ResourceSelectorType.Dataset) { + setSelectedDataset(undefined); + } else if (type === ResourceSelectorType.Mirror) { + setSelectedMirror(undefined); + } + form.setFieldValue(name, ''); + } + close(); + }, + }); + }; + + return ( +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + , + } + : undefined + } + rules={[ + { required: true, message: '请输入模型版本' }, + { + validator: (_, value) => { + if (value && versions.includes(value)) { + return Promise.reject('模型版本已存在'); + } else { + return Promise.resolve(); + } + }, + }, + ]} + > + + + + + + + + ); +} + +export default ExportModelModal; diff --git a/react-ui/src/pages/Experiment/training/index.jsx b/react-ui/src/pages/Experiment/training/index.jsx index 991e9855..db217197 100644 --- a/react-ui/src/pages/Experiment/training/index.jsx +++ b/react-ui/src/pages/Experiment/training/index.jsx @@ -21,8 +21,8 @@ function ExperimentText() { const navgite = useNavigate(); const locationParams = useParams(); //新版本获取路由参数接口 const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); - const graphRef = useRef(); + const getGraphData = (data) => { if (graph) { // 修改历史数据有蓝色边框的问题 @@ -202,9 +202,6 @@ function ExperimentText() { }, }, defaultEdge: { - // type: 'quadratic', - // type: 'cubic-vertical', - style: { endArrow: { // 设置终点箭头 @@ -226,16 +223,6 @@ function ExperimentText() { }, }, }, - defaultCombo: { - type: 'rect', - fixCollapseSize: 70, - style: { - fill: '#00e0ff0d', - stroke: '#00e0ff', - lineDash: [5, 10], - cursor: 'pointer', - }, - }, }); graph.on('node:click', (e) => { if (e.target.get('name') !== 'anchor-point' && e.item) { diff --git a/react-ui/src/pages/Mirror/Info/index.less b/react-ui/src/pages/Mirror/Info/index.less index 650310b6..adee2d49 100644 --- a/react-ui/src/pages/Mirror/Info/index.less +++ b/react-ui/src/pages/Mirror/Info/index.less @@ -31,23 +31,5 @@ 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; - } - } - } - } } } diff --git a/react-ui/src/pages/Mirror/Info/index.tsx b/react-ui/src/pages/Mirror/Info/index.tsx index c99c919d..60e19ab0 100644 --- a/react-ui/src/pages/Mirror/Info/index.tsx +++ b/react-ui/src/pages/Mirror/Info/index.tsx @@ -32,7 +32,6 @@ import { type TablePaginationConfig, type TableProps, } from 'antd'; -import classNames from 'classnames'; import { useEffect, useMemo, useState } from 'react'; import MirrorStatusCell from '../components/MirrorStatusCell'; import styles from './index.less'; @@ -282,7 +281,7 @@ function MirrorInfo() {
{(showNodeTooltip || enterTooltip) && ( diff --git a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx index 3c3f4225..a878321a 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx @@ -3,8 +3,8 @@ import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } // @ts-ignore import Hierarchy from '@antv/hierarchy'; -export const nodeWidth = 110; -export const nodeHeight = 50; +export const nodeWidth = 90; +export const nodeHeight = 40; export const vGap = nodeHeight + 20; export const hGap = nodeWidth; export const ellipseWidth = nodeWidth; @@ -64,6 +64,7 @@ export type ModalDetail = { export interface ModelDepsAPIData { current_model_id: number; version: string; + workflow_id: number; exp_ins_id: number; model_type: NodeType.children | NodeType.current | NodeType.parent; current_model_name: string; diff --git a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx index a4b3f13b..217222da 100644 --- a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx +++ b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx @@ -1,20 +1,35 @@ +import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; import { formatDate } from '@/utils/date'; +import { useNavigate } from '@umijs/max'; import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; import styles from './index.less'; -type NodeTooltipsProps = { - data?: ModelDepsData | ProjectDependency | TrainDataset; - x: number; - y: number; - onMouseEnter?: () => void; - onMouseLeave?: () => void; +type ModelInfoProps = { + resourceId: number; + data: ModelDepsData; + onVersionChange: (version: string) => void; }; -function ModelInfo({ data }: { data: ModelDepsData }) { +function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { + const navigate = useNavigate(); + const gotoExperimentPage = () => { if (data.train_task?.ins_id) { const { origin } = location; - window.open(`${origin}/pipeline/experiment/144/${data.train_task.ins_id}`, '_blank'); + const url = `${origin}/pipeline/experiment/${data.workflow_id}/${data.train_task.ins_id}`; + window.open(url, '_blank'); + } + }; + + const gotoModelPage = () => { + if (data.model_type === NodeType.current) { + return; + } + if (data.current_model_id === resourceId) { + onVersionChange?.(data.version); + } else { + const path = `/dataset/model/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`; + navigate(path); } }; @@ -24,9 +39,18 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
模型名称: - - {data.model_version_dependcy_vo.name || '--'} - + {data.model_type === NodeType.current ? ( + + {data.model_version_dependcy_vo?.name || '--'} + + ) : ( + + )}
模型版本: @@ -61,13 +85,12 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
训练任务: - {data.train_task?.name ? ( - - {data.train_task?.name} - - ) : ( - '--' - )} +
@@ -75,13 +98,24 @@ function ModelInfo({ data }: { data: ModelDepsData }) { } function DatasetInfo({ data }: { data: TrainDataset }) { + const gotoDatasetPage = () => { + const { origin } = location; + const url = `${origin}/dataset/dataset/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`; + window.open(url, '_blank'); + }; + return ( <>
数据集信息
数据集名称: - {data.dataset_name || '--'} +
数据集版本: @@ -95,13 +129,23 @@ function DatasetInfo({ data }: { data: TrainDataset }) { } function ProjectInfo({ data }: { data: ProjectDependency }) { + const gotoProjectPage = () => { + const { url } = data; + window.open(url, '_blank'); + }; + return ( <>
项目信息
项目名称: - {data.name || '--'} +
项目分支: @@ -116,7 +160,42 @@ function ProjectInfo({ data }: { data: ProjectDependency }) { ); } -function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { +type ValueLinkProps = { + value: string | undefined; + onClick?: () => void; + className?: string; + nullClassName?: string; +}; + +const ValueLink = ({ value, onClick, className, nullClassName }: ValueLinkProps) => { + return value ? ( + + {value} + + ) : ( + -- + ); +}; + +type NodeTooltipsProps = { + resourceId: number; + data: ModelDepsData | ProjectDependency | TrainDataset; + x: number; + y: number; + onMouseEnter?: () => void; + onMouseLeave?: () => void; + onVersionChange: (version: string) => void; +}; + +function NodeTooltips({ + resourceId, + data, + x, + y, + onMouseEnter, + onMouseLeave, + onVersionChange, +}: NodeTooltipsProps) { if (!data) return null; let Component = null; const { model_type } = data; @@ -129,7 +208,7 @@ function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsPr model_type === NodeType.parent || model_type === NodeType.current ) { - Component = ; + Component = ; } return (
( undefined, ); + const [selectedMirror, setSelectedMirror] = useState( + undefined, + ); // 选择的镜像,为了再次打开时恢复原来的选择 const { message } = App.useApp(); useEffect(() => { @@ -84,16 +87,14 @@ function ModelDeploymentCreate() { }; // 选择模型、镜像 - const selectResource = (name: string, selectType: string) => { - let type; + const selectResource = (name: string, type: ResourceSelectorType) => { let resource: ResourceSelectorResponse | undefined; - switch (selectType) { - case 'model': - type = ResourceSelectorType.Model; + switch (type) { + case ResourceSelectorType.Model: resource = selectedModel; break; default: - type = ResourceSelectorType.Mirror; + resource = selectedMirror; break; } const { close } = openAntdModal(ResourceSelectorModal, { @@ -105,18 +106,20 @@ function ModelDeploymentCreate() { if (res) { if (type === ResourceSelectorType.Mirror) { form.setFieldValue(name, res.path); + setSelectedMirror(res); } else { - const response = res as ResourceSelectorResponse; - const showValue = `${response.name}:${response.version}`; + const showValue = `${res.name}:${res.version}`; form.setFieldValue(name, { - ...pick(response, ['id', 'version', 'path']), + ...pick(res, ['id', 'version', 'path']), showValue, }); - setSelectedModel(response); + setSelectedModel(res); } } else { if (type === ResourceSelectorType.Model) { setSelectedModel(undefined); + } else { + setSelectedMirror(undefined); } form.setFieldValue(name, ''); } @@ -248,7 +251,6 @@ function ModelDeploymentCreate() { image={require('@/assets/img/model-deployment.png')} style={{ marginTop: '20px', marginBottom: '24px' }} > -
selectResource('model', ResourceSelectorType.Model)} /> @@ -275,7 +278,7 @@ function ModelDeploymentCreate() { size="large" type="link" icon={getSelectBtnIcon(ResourceSelectorType.Model)} - onClick={() => selectResource('model', 'model')} + onClick={() => selectResource('model', ResourceSelectorType.Model)} > 选择模型 @@ -293,7 +296,12 @@ function ModelDeploymentCreate() { }, ]} > - + selectResource('image', ResourceSelectorType.Mirror)} + /> @@ -301,7 +309,7 @@ function ModelDeploymentCreate() { size="large" type="link" icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} - onClick={() => selectResource('image', 'image')} + onClick={() => selectResource('image', ResourceSelectorType.Mirror)} > 选择镜像 diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less index cd10e0d8..747d4da6 100644 --- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less +++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less @@ -11,7 +11,7 @@ :global { .anticon.anticon-question-circle { - margin-top: -14px; + margin-top: -12px; } } } diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx index 7336b333..7fe6440a 100644 --- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx @@ -76,9 +76,27 @@ const GlobalParamsDrawer = forwardRef( {...restField} name={[name, 'param_name']} label="参数名称" - rules={[{ required: true, message: '请输入参数名称' }]} + validateTrigger={[]} + rules={[ + { required: true, message: '请输入参数名称' }, + { + validator: (_, value) => { + const list = form.getFieldValue('global_param') || []; + const names = list.filter((item: any) => item?.param_name === value); + if (value && names.length > 1) { + return Promise.reject('参数名称不能重复'); + } else { + return Promise.resolve(); + } + }, + }, + ]} > - + form.validateFields()} + /> (null); + const config = selectorTypeConfig[type]; useEffect(() => { setExpandedKeys([]); @@ -143,9 +144,9 @@ function ResourceSelectorModal({ const params = { page: 0, size: 1000, - [selectorTypeConfig[type].litReqParamKey]: available_range, + [config.litReqParamKey]: available_range, }; - const getListReq = selectorTypeConfig[type].getList; + const getListReq = config.getList; const [res] = await to(getListReq(params)); if (res) { const list = res.data?.content || []; @@ -161,10 +162,10 @@ function ResourceSelectorModal({ // 获取数据集\模型\镜像版本列表 const getVersions = async (parentId: number) => { - const getVersionsReq = selectorTypeConfig[type].getVersions; + const getVersionsReq = config.getVersions; const [res, error] = await to(getVersionsReq(parentId)); if (res) { - const list = selectorTypeConfig[type].handleVersionResponse(res); + const list = config.handleVersionResponse(res); const children = list.map(convertVersionToTreeData(parentId)); // 更新 treeData children setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); @@ -183,8 +184,8 @@ function ResourceSelectorModal({ // 获取版本下的文件 const getFiles = async (id: number, version: string) => { - const getFilesReq = selectorTypeConfig[type].getFiles; - const paramsKey = selectorTypeConfig[type].fileReqParamKey; + const getFilesReq = config.getFiles; + const paramsKey = config.fileReqParamKey; const params = { version: version, [paramsKey]: id }; const [res] = await to(getFilesReq(params)); if (res) { @@ -282,14 +283,12 @@ function ResourceSelectorModal({ } }; - const title = `选择${selectorTypeConfig[type].name}`; - const palceholder = `请输入${selectorTypeConfig[type].name}名称`; + const title = `选择${config.name}`; + const palceholder = `请输入${config.name}名称`; const fileTitle = - type === ResourceSelectorType.Mirror - ? '已选镜像' - : `已选${selectorTypeConfig[type].name}文件(${files.length})`; - const tabItems = selectorTypeConfig[type].tabItems; - const titleImg = selectorTypeConfig[type].modalIcon; + type === ResourceSelectorType.Mirror ? '已选镜像' : `已选${config.name}文件(${files.length})`; + const tabItems = config.tabItems; + const titleImg = config.modalIcon; return ( diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx index 4cfdcacb..c51ce935 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx @@ -13,13 +13,12 @@ import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; import ModelMenu from '../components/ModelMenu'; import styles from './index.less'; import Props from './props'; -import { findAllParentNodes, findFirstDuplicate } from './utils'; +import { findAllParentNodes } from './utils'; let graph = null; const EditPipeline = () => { const navgite = useNavigate(); - let contextMenu = {}; const locationParams = useParams(); //新版本获取路由参数接口 const graphRef = useRef(); const paramsDrawerRef = useRef(); @@ -31,10 +30,23 @@ const EditPipeline = () => { let dragSourceNode; useEffect(() => { - initMenu(); + initGraph(); getFirstWorkflow(locationParams.id); + + const changeSize = () => { + if (!graph || graph.get('destroyed')) return; + if (!graphRef.current) return; + graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); + graph.fitView(); + }; + + window.addEventListener('resize', changeSize); + return () => { + window.removeEventListener('resize', changeSize); + }; }, []); + // 拖拽结束,添加新节点 const onDragEnd = (val) => { const { x, y } = val; const point = graph.getPointByClient(x, y); @@ -45,11 +57,14 @@ const EditPipeline = () => { y: point.y, id: val.component_name + '-' + s8(), isCluster: false, + formError: true, }; // console.log('model', model); graph.addItem('node', model, false); }; - const formChange = (val) => { + + // 节点数据发生变化 + const handleFormChange = (val) => { if (graph) { const data = graph.save(); const index = data.nodes.findIndex((item) => { @@ -68,6 +83,8 @@ const EditPipeline = () => { graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); } }; + + // 保存 const savePipeline = async (val) => { const [res, error] = await to(paramsDrawerRef.current.validateFields()); if (error) { @@ -76,13 +93,6 @@ const EditPipeline = () => { return; } - const duplicateName = findFirstDuplicate(res.global_param || []); - if (duplicateName) { - message.error('全局参数配置有重复的参数名称:' + duplicateName); - openParamsDrawer(); - return; - } - // const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); // if (propsError) { // message.error('基本信息必填项需配置'); @@ -108,6 +118,8 @@ const EditPipeline = () => { }); }, 500); }; + + // 渲染数据 const getGraphData = (data) => { if (graph) { // 修改历史数据有蓝色边框的问题 @@ -122,6 +134,8 @@ const EditPipeline = () => { }, 500); } }; + + // 处理并行边,暂时没有用 const processParallelEdgesOnAnchorPoint = ( edges, offsetDiff = 15, @@ -242,11 +256,11 @@ const EditPipeline = () => { } return false; }; + + // 复制节点 const cloneElement = (item) => { - console.log(item); let data = graph.save(); const nodeId = s8(); - console.log(item.getModel()); data.nodes.push({ ...item.getModel(), label: item.getModel().label + '-copy', @@ -256,66 +270,22 @@ const EditPipeline = () => { }); graph.changeData(data); }; - const getFirstWorkflow = (val) => { - getWorkflowById(val).then((ret) => { - if (ret && ret.data) { - setGlobalParam(ret.data.global_param || []); - } - if (graph && ret.data && ret.data.dag) { - getGraphData(JSON.parse(ret.data.dag)); - } - }); - }; - // 上下文菜单 - const initMenu = () => { - // const selectedNodes = this.selectedNodes; - contextMenu = new G6.Menu({ - getContent(evt) { - const type = evt.item.getType(); - const cloneDisplay = type === 'node' ? 'block' : 'none'; - return ` -
    -
  • 复制
  • -
  • 删除
  • -
`; - }, - handleMenuClick: (target, item) => { - switch (target.getAttribute('code')) { - case 'delete': - graph.removeItem(item); - break; - case 'clone': - cloneElement(item); - break; - default: - break; - } - }, - // offsetX and offsetY include the padding of the parent container - // 需要加上父级容器的 padding-left 16 与自身偏移量 10 - offsetX: 16 + 10, - // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10 - offsetY: 0, - // the types of items that allow the menu show up - // 在哪些类型的元素上响应 - itemTypes: ['node', 'edge'], - }); - initGraph(); + // 获取流水线详情 + const getFirstWorkflow = async (val) => { + const [res] = await to(getWorkflowById(val)); + if (res && res.data) { + const { global_param, dag } = res.data; + setGlobalParam(global_param || []); + if (dag) { + getGraphData(JSON.parse(dag)); + } + } }; + // 初始化图 const initGraph = () => { + const contextMenu = initMenu(); G6.registerNode( 'rect-node', { @@ -515,6 +485,7 @@ const EditPipeline = () => { }, cursor: 'pointer', lineWidth: 1, + lineAppendWidth: 4, opacity: 1, stroke: '#CDD0DC', radius: 1, @@ -527,19 +498,13 @@ const EditPipeline = () => { }, }, }, - defaultCombo: { - type: 'rect', - fixCollapseSize: 70, - style: { - fill: '#00e0ff0d', - stroke: '#00e0ff', - lineDash: [5, 10], - cursor: 'pointer', - }, - }, }); + + bindEvents(); + }; + + const bindEvents = () => { graph.on('node:click', (e) => { - e.stopPropagation(); if (e.target.get('name') !== 'anchor-point' && e.item) { // 获取所有的上游节点 const parentNodes = findAllParentNodes(graph, e.item); @@ -558,16 +523,6 @@ const EditPipeline = () => { type: targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal', }); - - // update the curveOffset for parallel edges - // const edges = graph.save().edges; - // processParallelEdgesOnAnchorPoint(edges); - // graph.getEdges().forEach((edge, i) => { - // graph.updateItem(edge, { - // curveOffset: edges[i].curveOffset, - // curvePosition: edges[i].curvePosition, - // }); - // }); }); // 删除边时,修改 anchor-point 的 links 值 graph.on('afterremoveitem', (e) => { @@ -639,13 +594,56 @@ const EditPipeline = () => { graph.setItemState(e.item, 'drop', false); dropAnchorIdx = undefined; }); - window.onresize = () => { - if (!graph || graph.get('destroyed')) return; - if (!graphRef.current) return; - graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); - graph.fitView(); - }; }; + + // 上下文菜单 + const initMenu = () => { + const contextMenu = new G6.Menu({ + getContent(evt) { + const type = evt.item.getType(); + const cloneDisplay = type === 'node' ? 'block' : 'none'; + return ` +
    +
  • 复制
  • +
  • 删除
  • +
`; + }, + handleMenuClick: (target, item) => { + switch (target.getAttribute('code')) { + case 'delete': + graph.removeItem(item); + break; + case 'clone': + cloneElement(item); + break; + default: + break; + } + }, + // offsetX and offsetY include the padding of the parent container + // 需要加上父级容器的 padding-left 16 与自身偏移量 10 + offsetX: 16 + 10, + // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10 + offsetY: 0, + // the types of items that allow the menu show up + // 在哪些类型的元素上响应 + itemTypes: ['node', 'edge'], + }); + + return contextMenu; + }; + return (
@@ -687,7 +685,7 @@ const EditPipeline = () => {
- + add(@RequestBody DevEnvironment devEnvironment) { - return ResponseEntity.ok(this.devEnvironmentService.insert(devEnvironment)); + public GenericsAjaxResult add(@RequestBody DevEnvironmentVo devEnvironmentVo) { + return genericsSuccess(this.devEnvironmentService.insert(devEnvironmentVo)); } /** @@ -71,8 +72,8 @@ public class DevEnvironmentController extends BaseController { * @return 编辑结果 */ @PutMapping - public ResponseEntity edit(@RequestBody DevEnvironment devEnvironment) { - return ResponseEntity.ok(this.devEnvironmentService.update(devEnvironment)); + public GenericsAjaxResult edit(@RequestBody DevEnvironment devEnvironment) { + return genericsSuccess(this.devEnvironmentService.update(devEnvironment)); } /** @@ -82,8 +83,8 @@ public class DevEnvironmentController extends BaseController { * @return 删除是否成功 */ @DeleteMapping("{id}") - public ResponseEntity deleteById(@PathVariable("id") Integer id) { - return ResponseEntity.ok(this.devEnvironmentService.removeById(id)); + public GenericsAjaxResult deleteById(@PathVariable("id") Integer id) { + return genericsSuccess(this.devEnvironmentService.removeById(id)); } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ExperimentIns.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ExperimentIns.java index 78926ebf..dd3dc2bf 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ExperimentIns.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ExperimentIns.java @@ -48,6 +48,9 @@ public class ExperimentIns implements Serializable { @ApiModelProperty(value = "实验实例全局参数", notes = "以JSON字符串格式存储") @JsonRawValue private String globalParam; + @ApiModelProperty(value = "参数记录", notes = "以JSON字符串格式存储") + @JsonRawValue + private String metricRecord; @ApiModelProperty(value = "开始时间") private Date startTime; @@ -210,5 +213,12 @@ public class ExperimentIns implements Serializable { public void setWorkflowId(Long workflowId) {this.workflowId = workflowId;} + public String getMetricRecord() { + return metricRecord; + } + + public void setMetricRecord(String metricRecord) { + this.metricRecord = metricRecord; + } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java index 36efd621..055b60dc 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java @@ -1,6 +1,7 @@ package com.ruoyi.platform.service; import com.ruoyi.platform.domain.DevEnvironment; +import com.ruoyi.platform.vo.DevEnvironmentVo; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -35,7 +36,7 @@ public interface DevEnvironmentService { * @param devEnvironment 实例对象 * @return 实例对象 */ - DevEnvironment insert(DevEnvironment devEnvironment); + DevEnvironment insert(DevEnvironmentVo devEnvironmentVo); /** * 修改数据 diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java index 8f211a56..4b8973b9 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java @@ -6,6 +6,7 @@ import com.ruoyi.platform.mapper.DevEnvironmentDao; import com.ruoyi.platform.service.DevEnvironmentService; import com.ruoyi.platform.service.JupyterService; import com.ruoyi.platform.utils.JacksonUtil; +import com.ruoyi.platform.vo.DevEnvironmentVo; import com.ruoyi.system.api.model.LoginUser; import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; import org.apache.commons.lang3.StringUtils; @@ -61,13 +62,27 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { * @return 实例对象 */ @Override - public DevEnvironment insert(DevEnvironment devEnvironment) { + public DevEnvironment insert(DevEnvironmentVo devEnvironmentVo) { //插入预备,此时不需要判断版本重复 + DevEnvironment devEnvironment = new DevEnvironment(); LoginUser loginUser = SecurityUtils.getLoginUser(); + devEnvironment.setName(devEnvironmentVo.getName()); + //状态先设为未知 + devEnvironment.setStatus("Unknown"); + devEnvironment.setComputingResource(devEnvironmentVo.getComputingResource()); + devEnvironment.setStandard(devEnvironmentVo.getStandard()); + devEnvironment.setEnvVariable(devEnvironmentVo.getEnvVariable()); + devEnvironment.setImage(devEnvironmentVo.getImage()); + // 将 dataset 和 model 转换成 JSON 字符串 + String datasetJson = JacksonUtil.toJSONString(devEnvironmentVo.getDataset()); + String modelJson = JacksonUtil.toJSONString(devEnvironmentVo.getModel()); + devEnvironment.setDataset(datasetJson); + devEnvironment.setModel(modelJson); devEnvironment.setCreateBy(loginUser.getUsername()); devEnvironment.setUpdateBy(loginUser.getUsername()); devEnvironment.setUpdateTime(new Date()); devEnvironment.setCreateTime(new Date()); + devEnvironment.setState(1); this.devEnvironmentDao.insert(devEnvironment); return devEnvironment; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java index 407ff6ae..893d23e7 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java @@ -251,6 +251,9 @@ public class ExperimentServiceImpl implements ExperimentService { if (data == null || MapUtils.isEmpty(data)) { throw new RuntimeException("Failed to run workflow."); } + //获取训练参数 + Map metricRecord = (Map) runResMap.get("metric_record"); + Map metadata = (Map) data.get("metadata"); // 插入记录到实验实例表 ExperimentIns experimentIns = new ExperimentIns(); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java index c57b498a..9f714524 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java @@ -43,6 +43,8 @@ public class JupyterServiceImpl implements JupyterService { private String masterIp; @Value("${k8s.storageClassName}") private String storageClassName; + @Value("${minio.pvcName}") + private String minioPvcName; private final MinioUtil minioUtil; @@ -89,14 +91,16 @@ public class JupyterServiceImpl implements JupyterService { String modelPath = (String) model.get("path"); LoginUser loginUser = SecurityUtils.getLoginUser(); - String podName = loginUser.getUsername().toLowerCase() + "-editor-pod"; + //手动构造pod名称 + String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id; String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc"; + //新建编辑器的pvc V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage, storageClassName); //TODO 设置镜像可配置,这里先用默认镜像启动pod // 调用修改后的 createPod 方法,传入额外的参数 - Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, datasetPath, modelPath); + Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, minioPvcName, datasetPath, modelPath); return masterIp + ":" + podPort; @@ -110,7 +114,8 @@ public class JupyterServiceImpl implements JupyterService { } LoginUser loginUser = SecurityUtils.getLoginUser(); - String podName = loginUser.getUsername().toLowerCase() + "-editor-pod"; + //手动构造pod名称 + String podName = loginUser.getUsername().toLowerCase() +"-editor-pod" + "-" + id; //得到pod V1Pod pod = k8sClientUtil.getNSPodList(namespace, podName); if(pod == null){ @@ -145,6 +150,8 @@ public class JupyterServiceImpl implements JupyterService { } catch (Exception e) { return JupyterStatusVo; + + } String url = redisService.getCacheObject(podName); JupyterStatusVo.setStatus(status); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkflowServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkflowServiceImpl.java index 22c736cc..9c36d277 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkflowServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkflowServiceImpl.java @@ -133,7 +133,9 @@ public class WorkflowServiceImpl implements WorkflowService { public String removeById(Long id) throws Exception { //先根据id提取出对应的流水线 Workflow workflow = workflowDao.queryById(id); - + if (workflow == null){ + throw new Exception("流水线不存在"); + } //判断权限,只有admin和创建者本身可以删除该流水线 LoginUser loginUser = SecurityUtils.getLoginUser(); String username = loginUser.getUsername(); @@ -142,9 +144,6 @@ public class WorkflowServiceImpl implements WorkflowService { throw new Exception("无权限删除该流水线"); } - if (workflow == null){ - throw new Exception("流水线不存在"); - } //判断这个流水线是否有相关实验存在 List experimentList = experimentService.queryByWorkflowId(id); if (experimentList!=null&&experimentList.size()>0){ diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java index 23fd0632..88071d4c 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java @@ -372,7 +372,7 @@ public class K8sClientUtil { } - public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, String image, String datasetPath, String modelPath) { + public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, String image, String dataPvcName, String datasetPath, String modelPath) { Map selector = new LinkedHashMap<>(); selector.put("k8s-jupyter", podName); @@ -398,13 +398,13 @@ public class K8sClientUtil { // 配置卷和卷挂载 List volumeMounts = new ArrayList<>(); volumeMounts.add(new V1VolumeMount().name("workspace").mountPath(mountPath)); - volumeMounts.add(new V1VolumeMount().name("dataset").mountPath("/datasets").subPath(datasetPath).readOnly(true)); - volumeMounts.add(new V1VolumeMount().name("model").mountPath("/model").subPath(modelPath).readOnly(true)); + volumeMounts.add(new V1VolumeMount().name("minio-pvc").mountPath("/datasets").subPath(datasetPath).readOnly(true)); + volumeMounts.add(new V1VolumeMount().name("minio-pvc").mountPath("/model").subPath(modelPath).readOnly(true)); List volumes = new ArrayList<>(); volumes.add(new V1Volume().name("workspace").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName()))); - volumes.add(new V1Volume().name("dataset").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName()))); - volumes.add(new V1Volume().name("model").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName()))); + volumes.add(new V1Volume().name("minio-pvc").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(dataPvcName))); + V1Pod pod = new V1PodBuilder() .withNewMetadata() diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/DevEnvironmentVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/DevEnvironmentVo.java new file mode 100644 index 00000000..14089f14 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/DevEnvironmentVo.java @@ -0,0 +1,44 @@ +package com.ruoyi.platform.vo; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.io.Serializable; +import java.util.Map; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class DevEnvironmentVo implements Serializable { + + private Integer id; + + private String name; + + /** + * 计算资源 + */ + private String computingResource; + /** + * 资源规格 + */ + private String standard; + + /** + * 环境变量 + */ + private String envVariable; + /** + * 所用镜像 + */ + private String image; + /** + * 对应数据集 + */ + private Map dataset; + /** + * 对应模型 + */ + private Map model; + +} diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentInsDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentInsDaoMapper.xml index 71dace3e..e5faba38 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentInsDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ExperimentInsDaoMapper.xml @@ -12,6 +12,7 @@ + @@ -23,7 +24,7 @@ - select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state + select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state from experiment_ins where id = #{id} and state = 1 - select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state + select id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs, global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state from experiment_ins where state = 1 order by create_time DESC @@ -58,7 +59,7 @@ select - id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state + id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status,nodes_result, nodes_logs,global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state from experiment_ins state = 1 @@ -138,6 +142,9 @@ and global_param = #{experimentIns.globalParam} + + and metric_record = #{experimentIns.metricRecord} + and start_time = #{experimentIns.startTime} @@ -191,6 +198,9 @@ and global_param = #{experimentIns.globalParam} + + and metric_record = #{experimentIns.metricRecord} + and start_time = #{experimentIns.startTime} @@ -213,23 +223,23 @@ - insert into experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,start_time,finish_time,create_by,create_time,update_by,update_time,state) - values (#{experimentIns.experimentId},#{experimentIns.argoInsName},#{experimentIns.argoInsNs},#{experimentIns.status},#{experimentIns.nodesStatus},#{experimentIns.nodesResult},#{experimentIns.nodesLogs},#{experimentIns.globalParam},#{experimentIns.startTime},#{experimentIns.finishTime},#{experimentIns.createBy},#{experimentIns.createTime},#{experimentIns.updateBy},#{experimentIns.updateTime},#{experimentIns.state}) + insert into experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,metric_record,start_time,finish_time,create_by,create_time,update_by,update_time,state) + values (#{experimentIns.experimentId},#{experimentIns.argoInsName},#{experimentIns.argoInsNs},#{experimentIns.status},#{experimentIns.nodesStatus},#{experimentIns.nodesResult},#{experimentIns.nodesLogs},#{experimentIns.globalParam},#{experimentIns.metricRecord},#{experimentIns.startTime},#{experimentIns.finishTime},#{experimentIns.createBy},#{experimentIns.createTime},#{experimentIns.updateBy},#{experimentIns.updateTime},#{experimentIns.state}) insert into - experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,start_time,finish_time,create_by,create_time,update_by,update_time,state) + experiment_ins(experiment_id,argo_ins_name,argo_ins_ns,status,nodes_status,nodes_result,nodes_logs,global_param,metric_record,start_time,finish_time,create_by,create_time,update_by,update_time,state) values - (#{entity.experimentId},#{entity.argoInsName},#{entity.argoInsNs},#{entity.status},#{entity.nodesStatus},#{entity.nodesResult},#{entity.nodesLogs},#{entity.globalParam},#{entity.startTime},#{entity.finishTime},#{entity.createBy},#{entity.createTime},#{entity.updateBy},#{entity.updateTime},#{entity.state}) + (#{entity.experimentId},#{entity.argoInsName},#{entity.argoInsNs},#{entity.status},#{entity.nodesStatus},#{entity.nodesResult},#{entity.nodesLogs},#{entity.globalParam},#{entity.metricRecord},#{entity.startTime},#{entity.finishTime},#{entity.createBy},#{entity.createTime},#{entity.updateBy},#{entity.updateTime},#{entity.state}) @@ -244,10 +254,10 @@ - insert into experiment_ins (id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status, nodes_result, nodes_logs, global_param, start_time, finish_time, create_by, create_time, update_by, update_time, state) + insert into experiment_ins (id, experiment_id, argo_ins_name, argo_ins_ns, status, nodes_status, nodes_result, nodes_logs, global_param,metric_record, start_time, finish_time, create_by, create_time, update_by, update_time, state) values - (#{item.id}, #{item.experimentId}, #{item.argoInsName}, #{item.argoInsNs}, #{item.status}, #{item.nodesStatus}, #{item.nodesResult}, #{item.nodesLogs}, #{item.globalParam}, #{item.startTime}, #{item.finishTime}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.state}) + (#{item.id}, #{item.experimentId}, #{item.argoInsName}, #{item.argoInsNs}, #{item.status}, #{item.nodesStatus}, #{item.nodesResult}, #{item.nodesLogs}, #{item.globalParam},#{item.metricRecord}, #{item.startTime}, #{item.finishTime}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.state}) ON DUPLICATE KEY UPDATE experiment_id = VALUES(experiment_id), @@ -258,6 +268,7 @@ nodes_result = VALUES(nodes_result), nodes_logs = VALUES(nodes_logs), global_param = VALUES(global_param), + metric_record = VALUES(metric_record), start_time = VALUES(start_time), finish_time = VALUES(finish_time), create_by = VALUES(create_by), @@ -294,6 +305,9 @@ global_param = #{experimentIns.globalParam}, + + and metric_record = #{experimentIns.metricRecord} + start_time = #{experimentIns.startTime},