diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 7d928861..3b468859 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -21,6 +21,7 @@ import './styles/menu.less'; export { requestConfig as request } from './requestConfig'; // const isDev = process.env.NODE_ENV === 'development'; import { menuItemRender } from '@/utils/menuRender'; +import { gotoLoginPage } from './utils/ui'; /** * @see https://umijs.org/zh-CN/plugins/plugin-initial-state * */ @@ -45,7 +46,7 @@ export async function getInitialState(): Promise<{ } as API.CurrentUser; } catch (error) { console.log(error); - history.push(PageEnum.LOGIN); + gotoLoginPage(); } return undefined; }; @@ -97,7 +98,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { const { location } = history; // 如果没有登录,重定向到 login if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) { - history.push(PageEnum.LOGIN); + gotoLoginPage(); } }, layoutBgImgList: [ diff --git a/react-ui/src/hooks/index.ts b/react-ui/src/hooks/index.ts index 1d56a24d..b34d5156 100644 --- a/react-ui/src/hooks/index.ts +++ b/react-ui/src/hooks/index.ts @@ -1,7 +1,7 @@ /* * @Author: 赵伟 * @Date: 2024-04-15 10:01:29 - * @Description: + * @Description: 自定义 hooks */ import { FormInstance } from 'antd'; import { debounce } from 'lodash'; @@ -126,3 +126,28 @@ export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => { } }, [form, prevOpen, open]); }; + +/** + * Executes the effect function when the specified condition is true. + * + * @param effect - The effect function to execute. + * @param deps - The dependencies for the effect. + * @param when - The condition to trigger the effect. + */ +export const useEffectWhen = (effect: () => void, deps: React.DependencyList, when: boolean) => { + const requestFns = useRef<(() => void)[]>([]); + useEffect(() => { + if (when) { + effect(); + } else { + requestFns.current.splice(0, 1, effect); + } + }, deps); + + useEffect(() => { + if (when) { + const fn = requestFns.current.pop(); + fn?.(); + } + }, [when]); +}; diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx index 8636e648..c057a64a 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx @@ -11,14 +11,21 @@ type ResourceIntroProps = { resourceType: ResourceType; }; +enum TabKeys { + Introduction = '1', + Version = '2', + Evolution = '3', +} + const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { const [info, setInfo] = useState({} as ResourceData); const locationParams = useParams(); const [searchParams] = useSearchParams(); - const [versionList, setVersionList] = useState([]); - const [version, setVersion] = useState(undefined); const defaultTab = searchParams.get('tab') || '1'; let versionParam = searchParams.get('version'); + const [versionList, setVersionList] = useState([]); + const [version, setVersion] = useState(undefined); + const [activeTab, setActiveTab] = useState(defaultTab); const resourceId = Number(locationParams.id); const typeName = resourceConfig[resourceType].name; // 数据集/模型 @@ -67,7 +74,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { const items = [ { - key: '1', + key: TabKeys.Introduction, label: `${typeName}简介`, children: ( <> @@ -77,7 +84,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { ), }, { - key: '2', + key: TabKeys.Version, label: `${typeName}文件/版本`, children: ( { isPublic={info.available_range === 1} versionList={versionList} version={version} + isActive={activeTab === TabKeys.Version} getVersionList={getVersionList} onVersionChange={handleVersionChange} > @@ -96,7 +104,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { if (resourceType === ResourceType.Model) { items.push({ - key: '3', + key: TabKeys.Evolution, label: `模型演化`, children: ( { resourceName={info.name} versionList={versionList} version={version} + isActive={activeTab === TabKeys.Evolution} onVersionChange={handleVersionChange} > ), @@ -136,7 +145,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
- + setActiveTab(key)}>
); diff --git a/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx b/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx index ec35387f..7871f5ef 100644 --- a/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx @@ -1,14 +1,20 @@ import CommonTableCell from '@/components/CommonTableCell'; import DateTableCell from '@/components/DateTableCell'; import KFIcon from '@/components/KFIcon'; +import { useEffectWhen } from '@/hooks'; import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; -import { ResourceFileData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; +import { + ResourceFileData, + ResourceType, + ResourceVersionData, + resourceConfig, +} from '@/pages/Dataset/config'; import { downLoadZip } from '@/utils/downloadfile'; import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; import { App, Button, Flex, Select, Table } from 'antd'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import styles from './index.less'; type ResourceVersionProps = { @@ -16,8 +22,9 @@ type ResourceVersionProps = { resourceId: number; resourceName: string; isPublic: boolean; - versionList: { label: string; value: string }[]; + versionList: ResourceVersionData[]; version?: string; + isActive: boolean; getVersionList: () => void; onVersionChange: (version: string) => void; }; @@ -28,6 +35,7 @@ function ResourceVersion({ isPublic, versionList, version, + isActive, getVersionList, onVersionChange, }: ResourceVersionProps) { @@ -35,13 +43,17 @@ function ResourceVersion({ const { message } = App.useApp(); // 获取版本文件列表 - useEffect(() => { - if (version) { - getFileList(version); - } else { - setFileList([]); - } - }, [resourceId, version]); + useEffectWhen( + () => { + if (version) { + getFileList(version); + } else { + setFileList([]); + } + }, + [resourceId, version], + isActive, + ); // 获取版本下的文件列表 const getFileList = async (version: string) => { diff --git a/react-ui/src/pages/Dataset/config.tsx b/react-ui/src/pages/Dataset/config.tsx index c013912a..be31f697 100644 --- a/react-ui/src/pages/Dataset/config.tsx +++ b/react-ui/src/pages/Dataset/config.tsx @@ -155,6 +155,12 @@ export type ResourceData = { dataset_tag_name?: string; }; +// 版本数据 +export type ResourceVersionData = { + label: string; + value: string; +}; + // 版本文件数据 export type ResourceFileData = { id: number; diff --git a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx index 3ba7f54a..a1f017e6 100644 --- a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx +++ b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx @@ -16,7 +16,7 @@ function DatasetAnnotation() { }; return (
- + {iframeUrl && }
); } diff --git a/react-ui/src/pages/Experiment/training/index.jsx b/react-ui/src/pages/Experiment/training/index.jsx index 08603e3c..6cbabfe0 100644 --- a/react-ui/src/pages/Experiment/training/index.jsx +++ b/react-ui/src/pages/Experiment/training/index.jsx @@ -1,12 +1,13 @@ import { useStateRef, useVisible } from '@/hooks'; import { getExperimentIns } from '@/services/experiment/index.js'; import { getWorkflowById } from '@/services/pipeline/index.js'; +import themes from '@/styles/theme.less'; +import { fittingString } from '@/utils'; import { elapsedTime, formatDate } from '@/utils/date'; import G6 from '@antv/g6'; import { Button } from 'antd'; import { useEffect, useRef } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { s8 } from '../../../utils'; import ParamsModal from '../components/ViewParamsModal'; import { experimentStatusInfo } from '../status'; import styles from './index.less'; @@ -22,27 +23,22 @@ function ExperimentText() { const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); const graphRef = useRef(); - const onDragEnd = (val) => { - console.log(val, 'eee'); - const _x = val.x; - const _y = val.y; - const point = graph.getPointByClient(_x, _y); - let model = {}; - // 元模型 - model = { - ...val, - x: point.x, - y: point.y, - id: val.component_name + '-' + s8(), - isCluster: false, - }; - graph.addItem('node', model, true); - }; - const handlerClick = (e) => { - if (e.target.get('name') !== 'anchor-point' && e.item) { - propsRef.current.showDrawer(e, locationParams.id, messageRef.current); - } - }; + // const onDragEnd = (val) => { + // console.log(val, 'eee'); + // const _x = val.x; + // const _y = val.y; + // const point = graph.getPointByClient(_x, _y); + // let model = {}; + // // 元模型 + // model = { + // ...val, + // x: point.x, + // y: point.y, + // id: val.component_name + '-' + s8(), + // isCluster: false, + // }; + // graph.addItem('node', model, true); + // }; const getGraphData = (data) => { if (graph) { graph.data(data); @@ -89,32 +85,6 @@ function ExperimentText() { }, []); const initGraph = () => { - const fittingString = (str, maxWidth, fontSize) => { - const ellipsis = '...'; - const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]; - let currentWidth = 0; - let res = str; - const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters - str.split('').forEach((letter, i) => { - if (currentWidth > maxWidth - ellipsisLength) return; - if (pattern.test(letter)) { - // Chinese charactors - currentWidth += fontSize; - } else { - // get the width of single letter according to the fontSize - currentWidth += G6.Util.getLetterWidth(letter, fontSize); - } - if (currentWidth > maxWidth - ellipsisLength) { - res = `${str.substr(0, i)}${ellipsis}`; - } - }); - return res; - }; - // 获取文本的长度 - const getTextSize = (str, maxWidth, fontSize) => { - let width = G6.Util.getTextSize(str, fontSize)[0]; - return width > maxWidth ? maxWidth : width; - }; G6.registerNode( 'rect-node', { @@ -129,7 +99,6 @@ function ExperimentText() { ); }, afterDraw(cfg, group) { - // console.log(group, cfg, 12312); const image = group.addShape('image', { attrs: { x: -45, @@ -158,7 +127,6 @@ function ExperimentText() { } const bbox = group.getBBox(); const anchorPoints = this.getAnchorPoints(cfg); - // console.log(anchorPoints); anchorPoints.forEach((anchorPos, i) => { group.addShape('circle', { attrs: { @@ -179,19 +147,19 @@ function ExperimentText() { // response the state changes and show/hide the link-point circles setState(name, value, item) { - const anchorPoints = item - .getContainer() - .findAll((ele) => ele.get('name') === 'anchor-point'); - anchorPoints.forEach((point) => { - if (value || point.get('links') > 0) point.show(); - else point.hide(); - }); - // } + const group = item.getContainer(); + const shape = group.get('children')[0]; + if (name === 'hover') { + if (value) { + shape.attr('stroke', themes['primaryColor']); + } else { + shape.attr('stroke', '#fff'); + } + } }, }, 'rect', ); - console.log(graphRef, 'graphRef'); graph = new G6.Graph({ container: graphRef.current, grid: true, @@ -209,10 +177,6 @@ function ExperimentText() { if (e.target.get('name') === 'anchor-point') return false; return true; }, - // shouldEnd: e => { - // console.log(e); - // return false; - // }, }, // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles 'drag-canvas', @@ -237,7 +201,6 @@ function ExperimentText() { style: { fill: '#000', fontSize: 10, - cursor: 'pointer', x: -20, y: 0, @@ -252,17 +215,6 @@ function ExperimentText() { lineWidth: 0.5, }, }, - nodeStateStyles: { - nodeSelected: { - fill: 'red', - shadowColor: 'red', - stroke: 'red', - 'text-shape': { - fill: 'red', - stroke: 'red', - }, - }, - }, defaultEdge: { // type: 'quadratic', type: 'cubic-vertical', @@ -308,15 +260,25 @@ function ExperimentText() { // linkCenter: true, fitView: true, minZoom: 0.5, - maxZoom: 3, - fitViewPadding: [320, 320, 220, 320], + maxZoom: 5, + fitViewPadding: 300, + }); + graph.on('node:click', (e) => { + if (e.target.get('name') !== 'anchor-point' && e.item) { + propsRef.current.showDrawer(e, locationParams.id, messageRef.current); + } + }); + graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'hover', true); + }); + graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'hover', false); }); - graph.on('node:click', handlerClick); window.onresize = () => { if (!graph || graph.get('destroyed')) return; - if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) - return; - graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); + if (!graphRef.current) return; + graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); + graph.fitView(); }; }; return ( diff --git a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx index a1de7f73..73e85be8 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx @@ -1,3 +1,5 @@ +import { useEffectWhen } from '@/hooks'; +import { ResourceVersionData } from '@/pages/Dataset/config'; import { getModelAtlasReq } from '@/services/dataset/index.js'; import themes from '@/styles/theme.less'; import { changePropertyName, fittingString } from '@/utils'; @@ -19,10 +21,11 @@ import { useEffect, useRef, useState } from 'react'; import GraphLegand from '../GraphLegand'; import NodeTooltips from '../NodeTooltips'; import styles from './index.less'; + const nodeWidth = 98; const nodeHeight = 58; -const vGap = 30; -const hGap = 30; +const vGap = 58; +const hGap = 58; enum NodeType { current = 'current', @@ -53,7 +56,18 @@ interface ProjectDependency extends NodeConfig { model_type: NodeType; } -export interface ModelDepsAPIData { +type ModalDetail = { + name: string; + available_range: number; + file_name: string; + file_size: string; + description: string; + model_type_name: string; + model_tag_name: string; + create_time: string; +}; + +interface ModelDepsAPIData { current_model_id: number; version: string; exp_ins_id: number; @@ -63,11 +77,12 @@ export interface ModelDepsAPIData { test_dataset: TrainDataset[]; train_dataset: TrainDataset[]; train_task: TrainTask; + model_version_dependcy_vo: ModalDetail; children_models: ModelDepsAPIData[]; parent_models: ModelDepsAPIData[]; } -interface ModelDepsData extends Omit, TreeGraphData { +export interface ModelDepsData extends Omit, TreeGraphData { children: ModelDepsData[]; } @@ -75,10 +90,10 @@ interface ModelDepsData extends Omit, TreeG function normalizeChildren(data: ModelDepsData[]) { if (Array.isArray(data)) { data.forEach((item) => { + item.model_type = NodeType.children; item.id = `$M_${item.current_model_id}_${item.version}`; item.label = getLabel(item); item.style = getStyle(NodeType.children); - item.model_type = NodeType.children; normalizeChildren(item.children); }); } @@ -107,7 +122,7 @@ function getStyle(model_type: NodeType) { fill = '#b7cfff'; break; case NodeType.project: - fill = '#0000ff'; + fill = '#FA8C16'; break; case NodeType.trainDataset: fill = '#ff0000'; @@ -131,11 +146,12 @@ function normalizeTreeData(apiData: ModelDepsAPIData, currentNodeName: string): }) as ModelDepsData; // 设置当前模型的数据 - normalizedData.label = getLabel(normalizedData); - normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; - normalizedData.style = getStyle(NodeType.current); normalizedData.model_type = NodeType.current; normalizedData.current_model_name = currentNodeName; + normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; + normalizedData.label = getLabel(normalizedData); + normalizedData.style = getStyle(NodeType.current); + normalizeChildren(normalizedData.children as ModelDepsData[]); // 将 parent_models 转换成树形结构 @@ -144,8 +160,8 @@ function normalizeTreeData(apiData: ModelDepsAPIData, currentNodeName: string): const parent = parent_models[0]; normalizedData = { ...parent, - id: `$M_${parent.current_model_id}_${parent.version}`, model_type: NodeType.parent, + id: `$M_${parent.current_model_id}_${parent.version}`, label: getLabel(parent), style: getStyle(NodeType.parent), children: [ @@ -166,8 +182,8 @@ function getGraphData(data: ModelDepsData): GraphData { direction: 'LR', getHeight: () => nodeHeight, getWidth: () => nodeWidth, - getVGap: () => vGap, - getHGap: () => hGap, + getVGap: () => vGap / 2, + getHGap: () => hGap / 2, }; // 树形布局计算出坐标 @@ -210,8 +226,8 @@ function getGraphData(data: ModelDepsData): GraphData { const len = train_dataset.length + test_dataset.length; [...train_dataset, ...test_dataset].forEach((item, index) => { const half = len / 2 - 0.5; - item.x = node.x! - (half - index) * (nodeWidth + 30); - item.y = node.y! - nodeHeight - 30; + item.x = node.x! - (half - index) * (nodeWidth + hGap); + item.y = node.y! - nodeHeight - vGap; nodes.push(item); edges.push({ source: node.id, @@ -222,7 +238,7 @@ function getGraphData(data: ModelDepsData): GraphData { }); }); - if (project_dependency.url) { + if (project_dependency?.url) { project_dependency.id = `$P_${project_dependency.url}`; project_dependency.model_type = NodeType.project; project_dependency.type = 'rect'; @@ -230,7 +246,7 @@ function getGraphData(data: ModelDepsData): GraphData { project_dependency.label = fittingString(project_dependency.name, 48, 8); project_dependency.style = getStyle(NodeType.project); project_dependency.x = node.x; - project_dependency.y = node.y! + nodeHeight + 30; + project_dependency.y = node.y! + nodeHeight + vGap; nodes.push(project_dependency); edges.push({ source: node.id, @@ -248,8 +264,9 @@ function getGraphData(data: ModelDepsData): GraphData { type modeModelEvolutionProps = { resourceId: number; resourceName: string; - versionList: { label: string; value: string }[]; + versionList: ResourceVersionData[]; version?: string; + isActive: boolean; onVersionChange: (version: string) => void; }; @@ -259,6 +276,7 @@ function ModelEvolution({ resourceName, versionList, version, + isActive, onVersionChange, }: modeModelEvolutionProps) { const graphRef = useRef(null); @@ -283,18 +301,17 @@ function ModelEvolution({ }; }, []); - useEffect(() => { - if (version) { - getModelAtlas(); - } else { - graph.data({ - nodes: [], - edges: [], - }); - graph.render(); - graph.fitView(); - } - }, [resourceId, version]); + useEffectWhen( + () => { + if (version) { + getModelAtlas(); + } else { + clearGraphData(); + } + }, + [resourceId, version], + isActive, + ); // 初始化图 const initGraph = () => { @@ -302,7 +319,6 @@ function ModelEvolution({ container: graphRef.current!, width: graphRef.current!.clientWidth, height: graphRef.current!.clientHeight, - // animate: false, fitView: true, fitViewPadding: [50, 100, 50, 100], minZoom: 0.5, @@ -380,10 +396,14 @@ function ModelEvolution({ if (point.x + 300 > canvasWidth) { point.x = canvasWidth - 300; } + const zoom = graph.getZoom(); + // 更加缩放,调整 tooltip 位置 + const offsetY = (nodeHeight * zoom) / 4; - setHoverNodeData(model as ModelDepsData); + setHoverNodeData(model); setNodeToolTipX(point.x); - setNodeToolTipY(point.y - 240); + // 92: 版本选择器的高度,296: tooltip的高度 + setNodeToolTipY(point.y + 92 - 296 - offsetY); setShowNodeTooltip(true); }); @@ -395,16 +415,15 @@ function ModelEvolution({ graph.on('node:click', (e: G6GraphEvent) => { const nodeItem = e.item; - const model = nodeItem.getModel() as ModelDepsChildren; + const model = nodeItem.getModel(); const { model_type } = model; const { origin } = location; let url: string = ''; switch (model_type) { case NodeType.children: - case NodeType.current: case NodeType.parent: { const { current_model_id, version } = model as ModelDepsData; - url = `${origin}/dataset/model/${current_model_id}?isPublic=true&tab=3&version=${version}`; + url = `${origin}/dataset/model/${current_model_id}?tab=3&version=${version}`; break; } case NodeType.project: { @@ -415,7 +434,7 @@ function ModelEvolution({ case NodeType.trainDataset: case NodeType.testDataset: { const { dataset_id, dataset_version } = model as TrainDataset; - url = `${origin}/dataset/dataset/${dataset_id}?isPublic=true&tab=2&version=${dataset_version}`; + url = `${origin}/dataset/dataset/${dataset_id}?tab=2&version=${dataset_version}`; break; } default: @@ -426,6 +445,12 @@ function ModelEvolution({ window.open(url, '_blank'); } }); + + // 鼠标滚轮缩放时,隐藏 tooltip + graph.on('wheelzoom', () => { + setShowNodeTooltip(false); + setEnterTooltip(false); + }); }; const handleTooltipsMouseEnter = () => { @@ -450,9 +475,21 @@ function ModelEvolution({ graph.data(graphData); graph.render(); graph.fitView(); + } else { + clearGraphData(); } }; + // 请求失败或者版本不存在时,清除图形 + function clearGraphData() { + graph.data({ + nodes: [], + edges: [], + }); + graph.render(); + graph.fitView(); + } + return (
diff --git a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx index 2dae10a3..2482a3b1 100644 --- a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx +++ b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx @@ -1,5 +1,4 @@ -import { useNavigate } from '@umijs/max'; -import { useEffect } from 'react'; +import { formatDate } from '@/utils/date'; import { ModelDepsData } from '../ModelEvolution'; import styles from './index.less'; @@ -12,12 +11,10 @@ type NodeTooltipsProps = { }; function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { - const navigate = useNavigate(); - useEffect(() => {}, []); - const gotoExperimentPage = () => { if (data.train_task?.ins_id) { - navigate(`/pipeline/experiment/144/${data.train_task.ins_id}`); + const { origin } = location; + window.open(`${origin}/pipeline/experiment/144/${data.train_task.ins_id}`, '_blank'); } }; @@ -35,24 +32,32 @@ function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsPr {data.current_model_name}
- 版本: + 模型版本: {data.version}
模型框架: - {data.version} + + {data.model_version_dependcy_vo?.model_type_name || '--'} +
模型大小: - {data.version} + + {data.model_version_dependcy_vo?.file_size || '--'} +
创建时间: - {data.version} + + {formatDate(data.model_version_dependcy_vo?.create_time)} +
模型权限: - {data.version} + + {data.model_version_dependcy_vo?.available_range === 1 ? '公开' : '私有'} +
训练相关信息
diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx index e02ecd0f..090822f2 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx @@ -1,6 +1,8 @@ import KFIcon from '@/components/KFIcon'; import { useStateRef, useVisible } from '@/hooks'; import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; +import themes from '@/styles/theme.less'; +import { fittingString } from '@/utils'; import { to } from '@/utils/promise'; import G6 from '@antv/g6'; import { App, Button } from 'antd'; @@ -27,6 +29,11 @@ const EditPipeline = () => { const { message } = App.useApp(); let sourceAnchorIdx, targetAnchorIdx; + useEffect(() => { + initMenu(); + getFirstWorkflow(locationParams.id); + }, []); + const onDragEnd = (val) => { console.log(val); const _x = val.x; @@ -103,20 +110,8 @@ const EditPipeline = () => { }); }, 500); }; - const handlerClick = (e) => { - e.stopPropagation(); - if (e.target.get('name') !== 'anchor-point' && e.item) { - graph.setItemState(e.item, 'nodeClicked', true); - const parentNodes = findAllParentNodes(graph, e.item); - // 如果没有打开过全局参数抽屉,获取不到全局参数 - const globalParams = - paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; - propsRef.current.showDrawer(e, globalParams, parentNodes); - } - }; const getGraphData = (data) => { if (graph) { - console.log(data); graph.data(data); graph.render(); } else { @@ -312,49 +307,8 @@ const EditPipeline = () => { initGraph(); }; - useEffect(() => { - initMenu(); - getFirstWorkflow(locationParams.id); - return () => { - graph.off('node:mouseenter', (e) => { - graph.setItemState(e.item, 'showAnchors', true); - graph.setItemState(e.item, 'nodeSelected', true); - }); - graph.off('node:mouseleave', (e) => { - // this.graph.setItemState(e.item, 'showAnchors', false); - graph.setItemState(e.item, 'nodeSelected', false); - }); - // graph.off('dblclick', handlerClick); - }; - }, []); const initGraph = () => { - const fittingString = (str, maxWidth, fontSize) => { - const ellipsis = '...'; - const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]; - let currentWidth = 0; - let res = str; - const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters - str.split('').forEach((letter, i) => { - if (currentWidth > maxWidth - ellipsisLength) return; - if (pattern.test(letter)) { - // Chinese charactors - currentWidth += fontSize; - } else { - // get the width of single letter according to the fontSize - currentWidth += G6.Util.getLetterWidth(letter, fontSize); - } - if (currentWidth > maxWidth - ellipsisLength) { - res = `${str.substr(0, i)}${ellipsis}`; - } - }); - return res; - }; - // 获取文本的长度 - const getTextSize = (str, maxWidth, fontSize) => { - let width = G6.Util.getTextSize(str, fontSize)[0]; - return width > maxWidth ? maxWidth : width; - }; G6.registerNode( 'rect-node', { @@ -407,6 +361,7 @@ const EditPipeline = () => { y: bbox.y + bbox.height * anchorPos[1], fill: '#fff', stroke: '#a4a4a5', + cursor: 'crosshair', }, name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') anchorPointIdx: i, // flag the idx of the anchor-point circle @@ -420,14 +375,30 @@ const EditPipeline = () => { // response the state changes and show/hide the link-point circles setState(name, value, item) { - const anchorPoints = item - .getContainer() - .findAll((ele) => ele.get('name') === 'anchor-point'); - anchorPoints.forEach((point) => { - if (value || point.get('links') > 0) point.show(); - else point.hide(); - }); - // } + // const anchorPoints = item + // .getContainer() + // .findAll((ele) => ele.get('name') === 'anchor-point'); + // anchorPoints.forEach((point) => { + // if (value || point.get('links') > 0) point.show(); + // else point.hide(); + // }); + + const group = item.getContainer(); + const shape = group.get('children')[0]; + const anchorPoints = group.findAll((ele) => ele.get('name') === 'anchor-point'); + if (name === 'hover') { + if (value) { + shape.attr('stroke', themes['primaryColor']); + anchorPoints.forEach((point) => { + point.show(); + }); + } else { + shape.attr('stroke', '#fff'); + anchorPoints.forEach((point) => { + point.hide(); + }); + } + } }, }, 'rect', @@ -435,7 +406,6 @@ const EditPipeline = () => { graph = new G6.Graph({ container: graphRef.current, - grid: true, width: graphRef.current.clientWidth || 500, height: graphRef.current.clientHeight || '100%', animate: false, @@ -519,20 +489,8 @@ const EditPipeline = () => { lineWidth: 0.5, }, }, - nodeStateStyles: { - nodeSelected: { - fill: 'red', - shadowColor: 'red', - stroke: 'red', - 'text-shape': { - fill: 'red', - stroke: 'red', - }, - }, - }, defaultEdge: { - // type: 'quadratic', - // type: 'cubic-vertical', + //type: 'cubic-vertical', style: { endArrow: { @@ -575,17 +533,20 @@ const EditPipeline = () => { // linkCenter: true, fitView: true, minZoom: 0.5, - maxZoom: 3, - fitViewPadding: [320, 320, 220, 320], + maxZoom: 5, + fitViewPadding: 300, + }); + graph.on('node:click', (e) => { + e.stopPropagation(); + if (e.target.get('name') !== 'anchor-point' && e.item) { + // graph.setItemState(e.item, 'nodeClicked', true); + const parentNodes = findAllParentNodes(graph, e.item); + // 如果没有打开过全局参数抽屉,获取不到全局参数 + const globalParams = + paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; + propsRef.current.showDrawer(e, globalParams, parentNodes); + } }); - // graph.on('dblclick', (e) => { - // console.log(e.item); - // if (e.item) { - // graph.setItemState(e.item, 'nodeClicked', true); - // handlerClick(e); - // } - // }); - graph.on('node:click', handlerClick); graph.on('aftercreateedge', (e) => { // update the sourceAnchor and targetAnchor for the newly added edge graph.updateItem(e.edge, { @@ -603,59 +564,6 @@ const EditPipeline = () => { }); }); }); - graph.on('node:mouseenter', (e) => { - // this.graph.setItemState(e.item, 'showAnchors', true); - graph.setItemState(e.item, 'nodeSelected', true); - graph.updateItem(e.item, { - // 节点的样式 - style: { - stroke: '#1664ff', - }, - }); - }); - graph.on('node:mouseleave', (e) => { - // this.graph.setItemState(e.item, 'showAnchors', false); - graph.setItemState(e.item, 'nodeSelected', false); - graph.updateItem(e.item, { - // 节点的样式 - style: { - stroke: 'transparent', - }, - }); - }); - graph.on('node:dragenter', (e) => { - console.log(e.target.get('name')); - console.log('node:dragenter'); - graph.setItemState(e.item, 'nodeSelected', true); - graph.updateItem(e.item, { - // 节点的样式 - style: { - stroke: '#1664ff', - }, - }); - }); - graph.on('node:dragleave', (e) => { - console.log(e.target.get('name')); - console.log('node:dragleave'); - graph.setItemState(e.item, 'nodeSelected', false); - graph.updateItem(e.item, { - // 节点的样式 - style: { - stroke: 'transparent', - }, - }); - }); - graph.on('node:dragstart', (e) => { - console.log('node:dragstart'); - graph.setItemState(e.item, 'nodeSelected', true); - graph.updateItem(e.item, { - // 节点的样式 - style: { - stroke: '#1664ff', - }, - }); - }); - graph.on('afterremoveitem', (e) => { if (e.item && e.item.source && e.item.target) { const sourceNode = graph.findById(e.item.source); @@ -681,7 +589,6 @@ const EditPipeline = () => { } } }); - // after clicking on the first node, the edge is created, update the sourceAnchor graph.on('afteradditem', (e) => { if (e.item && e.item.getType() === 'edge') { @@ -690,11 +597,29 @@ const EditPipeline = () => { }); } }); + graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'hover', true); + }); + graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'hover', false); + }); + graph.on('node:dragenter', (e) => { + graph.setItemState(e.item, 'hover', true); + }); + graph.on('node:dragleave', (e) => { + graph.setItemState(e.item, 'hover', false); + }); + graph.on('node:dragstart', (e) => { + graph.setItemState(e.item, 'hover', true); + }); + graph.on('node:drag', (e) => { + graph.setItemState(e.item, 'hover', true); + }); window.onresize = () => { if (!graph || graph.get('destroyed')) return; - if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) - return; - graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); + if (!graphRef.current) return; + graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); + graph.fitView(); }; }; return ( diff --git a/react-ui/src/utils/ui.tsx b/react-ui/src/utils/ui.tsx index 7eec5491..b970556e 100644 --- a/react-ui/src/utils/ui.tsx +++ b/react-ui/src/utils/ui.tsx @@ -50,16 +50,18 @@ export const getFileListFromEvent = (e: any) => { }); }; -// 去登录页面 +/** + * 跳转到登录页 + * @param toHome 是否跳转到首页 + */ export const gotoLoginPage = (toHome: boolean = true) => { - const { pathname, search } = window.location; + const { pathname, search } = location; const urlParams = new URLSearchParams(); urlParams.append('redirect', pathname + search); - const newSearch = - toHome && pathname !== PageEnum.LOGIN && pathname !== '/' ? '' : urlParams.toString(); - console.log('pathname', pathname); - console.log('search', search); - if (window.location.pathname !== PageEnum.LOGIN) { + const newSearch = toHome && pathname !== '/' ? '' : urlParams.toString(); + // console.log('pathname', pathname); + // console.log('search', search); + if (pathname !== PageEnum.LOGIN) { closeAllModals(); removeAllPageCacheState(); history.replace({ diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java new file mode 100644 index 00000000..3935d50f --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java @@ -0,0 +1,90 @@ +package com.ruoyi.platform.controller.devEnvironment; + +import com.ruoyi.common.core.web.controller.BaseController; +import com.ruoyi.common.core.web.domain.GenericsAjaxResult; +import com.ruoyi.platform.domain.DevEnvironment; +import com.ruoyi.platform.service.DevEnvironmentService; +import io.swagger.annotations.Api; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * (DevEnvironment)表控制层 + * + * @author Xidaray + * @since 2024-06-03 15:17:37 + */ +@RestController +@RequestMapping("devEnvironment") +@Api("开发环境管理") +public class DevEnvironmentController extends BaseController { + /** + * 服务对象 + */ + @Resource + private DevEnvironmentService devEnvironmentService; + + /** + * 分页查询 + * + * @param devEnvironment 筛选条件 + * @param page 页数 + * @param size 每页大小 + * @return 查询结果 + */ + @GetMapping + public GenericsAjaxResult> queryByPage(DevEnvironment devEnvironment, int page, int size) { + PageRequest pageRequest = PageRequest.of(page,size); + return genericsSuccess(this.devEnvironmentService.queryByPage(devEnvironment, pageRequest)); + } + + /** + * 通过主键查询单条数据 + * + * @param id 主键 + * @return 单条数据 + */ + @GetMapping("{id}") + public ResponseEntity queryById(@PathVariable("id") Integer id) { + return ResponseEntity.ok(this.devEnvironmentService.queryById(id)); + } + + /** + * 新增数据 + * + * @param devEnvironment 实体 + * @return 新增结果 + */ + @PostMapping + public ResponseEntity add(@RequestBody DevEnvironment devEnvironment) { + return ResponseEntity.ok(this.devEnvironmentService.insert(devEnvironment)); + } + + /** + * 编辑数据 + * + * @param devEnvironment 实体 + * @return 编辑结果 + */ + @PutMapping + public ResponseEntity edit(@RequestBody DevEnvironment devEnvironment) { + return ResponseEntity.ok(this.devEnvironmentService.update(devEnvironment)); + } + + /** + * 删除数据 + * + * @param id 主键 + * @return 删除是否成功 + */ + @DeleteMapping("{id}") + public ResponseEntity deleteById(@PathVariable("id") Integer id) { + return ResponseEntity.ok(this.devEnvironmentService.removeById(id)); + } + +} + diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentController.java index 230b77a4..7eba2b2c 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/experiment/ExperimentController.java @@ -91,7 +91,7 @@ public class ExperimentController extends BaseController { */ @PutMapping @ApiOperation("编辑实验") - public GenericsAjaxResult edit(@RequestBody Experiment experiment) throws IOException { + public GenericsAjaxResult edit(@RequestBody Experiment experiment) throws Exception { return genericsSuccess(this.experimentService.update(experiment)); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/jupyter/JupyterController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/jupyter/JupyterController.java index a533193d..19a3476c 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/jupyter/JupyterController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/jupyter/JupyterController.java @@ -4,11 +4,11 @@ import com.ruoyi.common.core.web.controller.BaseController; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.GenericsAjaxResult; import com.ruoyi.platform.service.JupyterService; +import com.ruoyi.platform.vo.FrameLogPathVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.io.File; @@ -28,6 +28,35 @@ public class JupyterController extends BaseController { return genericsSuccess(jupyterService.getJupyterServiceUrl()); } + + /** + * 启动jupyter容器接口 + * + * @param id 开发环境配置id + * @return url + */ + @PostMapping("/run/{id}") + @ApiOperation("根据开发环境id启动jupyter pod") + @ApiResponse + public GenericsAjaxResult runJupyter(@PathVariable("id") Integer id) throws Exception { + return genericsSuccess(this.jupyterService.runJupyterService(id)); + } + + + /** + * 停止jupyter容器接口 + * + * @param id 开发环境配置id + * @return 操作结果 + */ + @DeleteMapping("/stop/{id}") + @ApiOperation("根据开发环境id停止jupyter pod") + @ApiResponse + public GenericsAjaxResult stopJupyter(@PathVariable("id") Integer id) throws Exception { + return genericsSuccess(this.jupyterService.stopJupyterService(id)); + } + + @GetMapping(value = "/upload") public AjaxResult upload() throws Exception { File file = new File("D://nexus-deploy.yaml"); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/ModelDependencyController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/ModelDependencyController.java new file mode 100644 index 00000000..a62e8c61 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/ModelDependencyController.java @@ -0,0 +1,115 @@ +package com.ruoyi.platform.controller.model; + +import com.ruoyi.common.core.web.controller.BaseController; +import com.ruoyi.common.core.web.domain.GenericsAjaxResult; +import com.ruoyi.platform.domain.ModelDependency; +import com.ruoyi.platform.domain.ModelsVersion; +import com.ruoyi.platform.service.ModelDependencyService; +import com.ruoyi.platform.vo.ModelDependcyTreeVo; +import io.swagger.annotations.ApiOperation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.List; + +/** + * (ModelDependency)表控制层 + * + * @author Xidaray + * @since 2024-05-29 13:51:23 + */ +@RestController +@RequestMapping("modelDependency") +public class ModelDependencyController extends BaseController { + /** + * 服务对象 + */ + @Resource + private ModelDependencyService modelDependencyService; + + /** + * 分页查询 + * + * @param modelDependency 筛选条件 + * @param page 分页对象 + * @param size 分页对象 + * @return 查询结果 + */ + @GetMapping + @ApiOperation("分页查询") + public GenericsAjaxResult> queryByPage(ModelDependency modelDependency, int page ,int size) { + PageRequest pageRequest = PageRequest.of(page,size); + return genericsSuccess(this.modelDependencyService.queryByPage(modelDependency, pageRequest)); + } + + /** + * 根据对象查询 + * + * @param modelDependency 筛选条件 + * @return 查询结果 + */ + @GetMapping("/queryModelDependency") + @ApiOperation("根据对象查询") + public GenericsAjaxResult> queryByModelDependency(@RequestBody ModelDependency modelDependency) throws IOException { + modelDependency.setState(1); + return genericsSuccess(this.modelDependencyService.queryByModelDependency(modelDependency)); + } + + /** + * 通过主键查询单条数据 + * + * @param id 主键 + * @return 单条数据 + */ + @GetMapping("{id}") + @ApiOperation("根据id查询") + public GenericsAjaxResult queryById(@PathVariable("id") Integer id) { + return genericsSuccess(this.modelDependencyService.queryById(id)); + } + + /** + * 新增数据 + * + * @param modelDependency 实体 + * @return 新增结果 + */ + @PostMapping + public GenericsAjaxResult add(@RequestBody ModelDependency modelDependency) { + return genericsSuccess(this.modelDependencyService.insert(modelDependency)); + } + + /** + * 编辑数据 + * + * @param modelDependency 实体 + * @return 编辑结果 + */ + @PutMapping + public GenericsAjaxResult edit(@RequestBody ModelDependency modelDependency) { + return genericsSuccess(this.modelDependencyService.update(modelDependency)); + } + + /** + * 删除数据 + * + * @param id 主键 + * @return 删除是否成功 + */ + @DeleteMapping("{id}") + @ApiOperation("删除模型依赖") + public GenericsAjaxResult deleteById(@PathVariable("id") Integer id) { + return genericsSuccess(this.modelDependencyService.removeById(id)); + } + + + @PostMapping("/queryModelAtlas") + @ApiOperation("根据模型id与版本两个属性得到模型的演化图谱") + public GenericsAjaxResult queryModelAtlas(@RequestBody ModelDependency modelDependency) throws Exception { + return genericsSuccess(this.modelDependencyService.getModelDependencyTree(modelDependency)); + } +} + diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/ModelsVersionController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/ModelsVersionController.java index c966e92c..74a4ebf0 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/ModelsVersionController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/ModelsVersionController.java @@ -11,6 +11,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import java.io.IOException; import java.util.List; import java.util.Map; @@ -129,7 +130,7 @@ public class ModelsVersionController extends BaseController { @DeleteMapping("/deleteVersion") @ApiOperation(value = "逻辑删除模型版本", notes = "根据模型ID和版本逻辑删除模型版本记录。") public GenericsAjaxResult> deleteModelsVersion(@RequestParam("models_id") Integer modelsId, - @RequestParam("version") String version) { + @RequestParam("version") String version) throws IOException { return genericsSuccess(this.modelsVersionService.deleteModelsVersion(modelsId, version)); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/workflow/WorkflowController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/workflow/WorkflowController.java index eec963b2..426cd8c4 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/workflow/WorkflowController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/workflow/WorkflowController.java @@ -116,7 +116,7 @@ public class WorkflowController extends BaseController { */ @PutMapping @ApiOperation("编辑流水线") - public GenericsAjaxResult edit(@RequestBody Workflow workflow) { + public GenericsAjaxResult edit(@RequestBody Workflow workflow) throws Exception { return genericsSuccess(this.workflowService.update(workflow)); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Component.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Component.java index 2e1d5de2..12f89b16 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Component.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/Component.java @@ -1,6 +1,7 @@ package com.ruoyi.platform.domain; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRawValue; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import io.swagger.annotations.ApiModelProperty; @@ -58,6 +59,7 @@ public class Component implements Serializable { */ @JsonProperty("env_variables") @ApiModelProperty(name = "env_variables", value = "环境变量") + //@JsonRawValue private String envVirables; /** * 资源规格 @@ -78,12 +80,20 @@ public class Component implements Serializable { * 输入参数 */ @ApiModelProperty(name = "in_parameters" ,value = "输入参数") + //@JsonRawValue private String inParameters; /** * 输出参数 */ @ApiModelProperty(name = "out_parameters" ,value = "输出参数") + //@JsonRawValue private String outParameters; + + /** + * 可见范围 + */ + @ApiModelProperty(name = "available_range" ,value = "1公开0私有") + private int availableRange; /** * 描述 */ @@ -94,6 +104,8 @@ public class Component implements Serializable { * 图标路径 */ @ApiModelProperty(name = "icon_path" ,value = "图标路径") + + private String iconPath; /** * 创建者 @@ -124,6 +136,8 @@ public class Component implements Serializable { @ApiModelProperty(name = "state" , value = "状态") private Integer state; + + public Integer getId() { return id; } @@ -228,6 +242,14 @@ public class Component implements Serializable { this.outParameters = outParameters; } + public int getAvailableRange() { + return availableRange; + } + + public void setAvailableRange(int availableRange) { + this.availableRange = availableRange; + } + public String getDescription() { return description; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/DevEnvironment.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/DevEnvironment.java new file mode 100644 index 00000000..75d5ad8b --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/DevEnvironment.java @@ -0,0 +1,213 @@ +package com.ruoyi.platform.domain; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import java.util.Date; +import java.io.Serializable; + +/** + * (DevEnvironment)实体类 + * + * @author Xidaray + * @since 2024-06-03 15:17:37 + */ +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class DevEnvironment implements Serializable { + private static final long serialVersionUID = 936999018935545992L; +/** + * 主键 + */ + private Integer id; +/** + * 编辑器名称 + */ + private String name; +/** + * 状态 + */ + private String status; +/** + * 计算资源 + */ + private String computingResource; +/** + * 资源规格 + */ + private String standard; +/** + * 环境变量 + */ + private String envVariable; +/** + * 所用镜像 + */ + private String image; +/** + * 对应数据集 + */ + private String dataset; +/** + * 对应模型 + */ + private String model; +/** + * 备用字段1 + */ + private String altField1; +/** + * 备用字段2 + */ + private String altField2; +/** + * 创建者 + */ + private String createBy; +/** + * 创建时间 + */ + private Date createTime; +/** + * 更新者 + */ + private String updateBy; +/** + * 更新时间 + */ + private Date updateTime; +/** + * 状态,0失效1生效 + */ + private Integer state; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getComputingResource() { + return computingResource; + } + + public void setComputingResource(String computingResource) { + this.computingResource = computingResource; + } + + public String getStandard() { + return standard; + } + + public void setStandard(String standard) { + this.standard = standard; + } + + public String getEnvVariable() { + return envVariable; + } + + public void setEnvVariable(String envVariable) { + this.envVariable = envVariable; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public String getDataset() { + return dataset; + } + + public void setDataset(String dataset) { + this.dataset = dataset; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public String getAltField1() { + return altField1; + } + + public void setAltField1(String altField1) { + this.altField1 = altField1; + } + + public String getAltField2() { + return altField2; + } + + public void setAltField2(String altField2) { + this.altField2 = altField2; + } + + public String getCreateBy() { + return createBy; + } + + public void setCreateBy(String createBy) { + this.createBy = createBy; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public String getUpdateBy() { + return updateBy; + } + + public void setUpdateBy(String updateBy) { + this.updateBy = updateBy; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } + +} + diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ModelDependency.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ModelDependency.java new file mode 100644 index 00000000..18f9f872 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ModelDependency.java @@ -0,0 +1,247 @@ +package com.ruoyi.platform.domain; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.annotations.ApiModelProperty; + +import java.util.Date; +import java.io.Serializable; + +/** + * (ModelDependency)实体类 + * + * @author Xidaray + * @since 2024-05-29 13:51:23 + */ +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class ModelDependency implements Serializable { + private static final long serialVersionUID = -86753423714028539L; +/** + * 主键 + */ + private Integer id; +/** + * 当前模型id + */ + private Integer currentModelId; + /** + * 实验实例id + */ + private Integer expInsId; + + /** + * 父模型 + */ + private String parentModels; +/** + * 引用项目 + */ + private String refItem; +/** + * 训练任务 + */ + private String trainTask; +/** + * 训练数据集 + */ + private String trainDataset; + /** + * 训练参数 + */ + private String trainParams; + /** + * 训练镜像 + */ + private String trainImage; +/** + * 测试数据集 + */ + private String testDataset; + /** + * 依赖项目 + */ + private String projectDependency; + + /** + * 版本 + */ + private String version; + + + /** + * 创建者 + */ + private String createBy; +/** + * 创建时间 + */ + private Date createTime; +/** + * 更新者 + */ + private String updateBy; +/** + * 更新时间 + */ + private Date updateTime; +/** + * 状态,0失效1生效 + */ + private Integer state; + + private Long workflowId; + private Models models; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getCurrentModelId() { + return currentModelId; + } + + public void setCurrentModelId(Integer currentModelId) { + this.currentModelId = currentModelId; + } + + public Integer getExpInsId() { + return expInsId; + } + + public void setExpInsId(Integer expInsId) { + this.expInsId = expInsId; + } + + public String getParentModels() { + return parentModels; + } + + public void setParentModels(String parentModels) { + this.parentModels = parentModels; + } + + public String getRefItem() { + return refItem; + } + + public void setRefItem(String refItem) { + this.refItem = refItem; + } + + public String getTrainTask() { + return trainTask; + } + + public void setTrainTask(String trainTask) { + this.trainTask = trainTask; + } + + public String getTrainDataset() { + return trainDataset; + } + + public void setTrainDataset(String trainDataset) { + this.trainDataset = trainDataset; + } + + public String getTrainParams() { + return trainParams; + } + + public void setTrainParams(String trainParams) { + this.trainParams = trainParams; + } + + public String getTrainImage() { + return trainImage; + } + + public void setTrainImage(String trainImage) { + this.trainImage = trainImage; + } + + public String getTestDataset() { + return testDataset; + } + + public void setTestDataset(String testDataset) { + this.testDataset = testDataset; + } + + public String getProjectDependency() { + return projectDependency; + } + + public void setProjectDependency(String projectDependency) { + this.projectDependency = projectDependency; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getCreateBy() { + return createBy; + } + + public void setCreateBy(String createBy) { + this.createBy = createBy; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public String getUpdateBy() { + return updateBy; + } + + public void setUpdateBy(String updateBy) { + this.updateBy = updateBy; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } + + public Long getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(Long workflowId) { + this.workflowId = workflowId; + } + + public Models getModels() { + return models; + } + + public void setModels(Models models) { + this.models = models; + } +} + diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/dependencydomain/ProjectDepency.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/dependencydomain/ProjectDepency.java new file mode 100644 index 00000000..98502afb --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/dependencydomain/ProjectDepency.java @@ -0,0 +1,14 @@ +package com.ruoyi.platform.domain.dependencydomain; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.io.Serializable; +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class ProjectDepency implements Serializable { + private String url; + private String name; + private String branch; +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/dependencydomain/TrainTaskDepency.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/dependencydomain/TrainTaskDepency.java new file mode 100644 index 00000000..857306c8 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/dependencydomain/TrainTaskDepency.java @@ -0,0 +1,17 @@ +package com.ruoyi.platform.domain.dependencydomain; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.io.Serializable; +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class TrainTaskDepency implements Serializable { + //训练任务名 + private String name; + //实例id + private Integer insId; + //节点Id + private String taskId; +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/DevEnvironmentDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/DevEnvironmentDao.java new file mode 100644 index 00000000..e4f4bbe7 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/DevEnvironmentDao.java @@ -0,0 +1,84 @@ +package com.ruoyi.platform.mapper; + +import com.ruoyi.platform.domain.DevEnvironment; +import org.apache.ibatis.annotations.Param; +import org.springframework.data.domain.Pageable; +import java.util.List; + +/** + * (DevEnvironment)表数据库访问层 + * + * @author Xidaray + * @since 2024-06-03 15:17:37 + */ +public interface DevEnvironmentDao { + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + DevEnvironment queryById(Integer id); + + /** + * 查询指定行数据 + * + * @param devEnvironment 查询条件 + * @param pageable 分页对象 + * @return 对象列表 + */ + List queryAllByLimit(@Param("devEnvironment") DevEnvironment devEnvironment, @Param("pageable") Pageable pageable); + + /** + * 统计总行数 + * + * @param devEnvironment 查询条件 + * @return 总行数 + */ + long count(@Param("devEnvironment") DevEnvironment devEnvironment); + + /** + * 新增数据 + * + * @param devEnvironment 实例对象 + * @return 影响行数 + */ + int insert(@Param("devEnvironment") DevEnvironment devEnvironment); + + /** + * 批量新增数据(MyBatis原生foreach方法) + * + * @param entities List 实例对象列表 + * @return 影响行数 + */ + int insertBatch(@Param("entities") List entities); + + /** + * 批量新增或按主键更新数据(MyBatis原生foreach方法) + * + * @param entities List 实例对象列表 + * + * @return 影响行数 + * @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参 + */ + int insertOrUpdateBatch(@Param("entities") List entities); + + /** + * 修改数据 + * + * @param devEnvironment 实例对象 + * @return 影响行数 + */ + int update(@Param("devEnvironment") DevEnvironment devEnvironment); + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 影响行数 + */ + int deleteById(Integer id); + +} + diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/ModelDependencyDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/ModelDependencyDao.java new file mode 100644 index 00000000..3a999886 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/ModelDependencyDao.java @@ -0,0 +1,88 @@ +package com.ruoyi.platform.mapper; + +import com.ruoyi.platform.domain.ModelDependency; +import org.apache.ibatis.annotations.Param; +import org.springframework.data.domain.Pageable; +import java.util.List; + +/** + * (ModelDependency)表数据库访问层 + * + * @author Xidaray + * @since 2024-05-29 13:51:23 + */ +public interface ModelDependencyDao { + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + ModelDependency queryById(Integer id); + + /** + * 查询指定行数据 + * + * @param modelDependency 查询条件 + * @param pageable 分页对象 + * @return 对象列表 + */ + List queryAllByLimit(ModelDependency modelDependency, @Param("pageable") Pageable pageable); + + + + /** + * 统计总行数 + * + * @param modelDependency 查询条件 + * @return 总行数 + */ + long count(ModelDependency modelDependency); + + /** + * 新增数据 + * + * @param modelDependency 实例对象 + * @return 影响行数 + */ + int insert(ModelDependency modelDependency); + + /** + * 批量新增数据(MyBatis原生foreach方法) + * + * @param entities List 实例对象列表 + * @return 影响行数 + */ + int insertBatch(@Param("entities") List entities); + + /** + * 批量新增或按主键更新数据(MyBatis原生foreach方法) + * + * @param entities List 实例对象列表 + * @return 影响行数 + * @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参 + */ + int insertOrUpdateBatch(@Param("entities") List entities); + + /** + * 修改数据 + * + * @param modelDependency 实例对象 + * @return 影响行数 + */ + int update(ModelDependency modelDependency); + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 影响行数 + */ + int deleteById(Integer id); + + List queryByModelDependency(@Param("modelDependency") ModelDependency modelDependency); + + List queryChildrenByVersionId(@Param("model_id")String modelId, @Param("version")String version); +} + diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java index b7ea8648..91ed6847 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java @@ -1,24 +1,26 @@ package com.ruoyi.platform.scheduling; -import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.platform.domain.Experiment; import com.ruoyi.platform.domain.ExperimentIns; +import com.ruoyi.platform.domain.ModelDependency; import com.ruoyi.platform.mapper.ExperimentDao; import com.ruoyi.platform.mapper.ExperimentInsDao; +import com.ruoyi.platform.mapper.ModelDependencyDao; import com.ruoyi.platform.service.ExperimentInsService; -import com.ruoyi.platform.service.ExperimentService; -import com.ruoyi.platform.utils.JsonUtils; -import com.ruoyi.system.api.model.LoginUser; -import io.swagger.models.auth.In; +import com.ruoyi.platform.service.ModelDependencyService; +import com.ruoyi.platform.utils.JacksonUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; @Component() public class ExperimentInstanceStatusTask { @@ -28,7 +30,8 @@ public class ExperimentInstanceStatusTask { private ExperimentDao experimentDao; @Resource private ExperimentInsDao experimentInsDao; - + @Resource + private ModelDependencyDao modelDependencyDao; private List experimentIds = new ArrayList<>(); @Scheduled(cron = "0/30 * * * * ?") // 每30S执行一次 @@ -55,12 +58,50 @@ public class ExperimentInstanceStatusTask { updateList.add(experimentIns); } - experimentInsDao.update(experimentIns); +// experimentInsDao.update(experimentIns); } } if (updateList.size() > 0){ experimentInsDao.insertOrUpdateBatch(updateList); + + //遍历模型关系表,找到 + List modelDependencyList = new ArrayList(); + for (ExperimentIns experimentIns : updateList){ + ModelDependency modelDependencyquery = new ModelDependency(); + modelDependencyquery.setExpInsId(experimentIns.getId()); + modelDependencyquery.setState(2); + + List modelDependencyListquery = modelDependencyDao.queryByModelDependency(modelDependencyquery); + if (modelDependencyListquery==null||modelDependencyListquery.size()==0){ + continue; + } + ModelDependency modelDependency = modelDependencyListquery.get(0); + //查看状态, + if (StringUtils.equals("Failed",experimentIns.getStatus())){ + //取出节点状态 + String trainTask = modelDependency.getTrainTask(); + Map trainMap = JacksonUtil.parseJSONStr2Map(trainTask); + String task_id = (String) trainMap.get("task_id"); + if (StringUtils.isEmpty(task_id)){ + continue; + } + String nodesStatus = experimentIns.getNodesStatus(); + Map nodeMaps = JacksonUtil.parseJSONStr2Map(nodesStatus); + Map nodeMap = JacksonUtil.parseJSONStr2Map(JacksonUtil.toJSONString(nodeMaps.get(task_id))); + + if (nodeMap==null){ + continue; + } + if (!StringUtils.equals("Succeeded",(String)nodeMap.get("phase"))){ + modelDependency.setState(0); + modelDependencyList.add(modelDependency); + } + } + } + if (modelDependencyList.size()>0) { + modelDependencyDao.insertOrUpdateBatch(modelDependencyList); + } } } 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 new file mode 100644 index 00000000..36efd621 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java @@ -0,0 +1,57 @@ +package com.ruoyi.platform.service; + +import com.ruoyi.platform.domain.DevEnvironment; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +/** + * (DevEnvironment)表服务接口 + * + * @author Xidaray + * @since 2024-06-03 15:17:37 + */ +public interface DevEnvironmentService { + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + DevEnvironment queryById(Integer id); + + /** + * 分页查询 + * + * @param devEnvironment 筛选条件 + * @param pageRequest 分页对象 + * @return 查询结果 + */ + Page queryByPage(DevEnvironment devEnvironment, PageRequest pageRequest); + + /** + * 新增数据 + * + * @param devEnvironment 实例对象 + * @return 实例对象 + */ + DevEnvironment insert(DevEnvironment devEnvironment); + + /** + * 修改数据 + * + * @param devEnvironment 实例对象 + * @return 实例对象 + */ + DevEnvironment update(DevEnvironment devEnvironment); + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 是否成功 + */ + boolean deleteById(Integer id); + + String removeById(Integer id); +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentInsService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentInsService.java index e8020161..15c5d34e 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentInsService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentInsService.java @@ -100,7 +100,7 @@ public interface ExperimentInsService { /** * 查询非终止态的实例 - * @return + * */ List queryByExperimentIsNotTerminated(); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentService.java index 4f79f527..62100e56 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ExperimentService.java @@ -48,7 +48,7 @@ public interface ExperimentService { * @param experiment 实例对象 * @return 实例对象 */ - Experiment update(Experiment experiment) throws IOException; + Experiment update(Experiment experiment) throws Exception; /** * 通过主键删除数据 diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java index d41c6a31..af62a509 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java @@ -1,5 +1,7 @@ package com.ruoyi.platform.service; +import com.ruoyi.platform.vo.FrameLogPathVo; + import java.io.InputStream; public interface JupyterService { @@ -8,4 +10,8 @@ public interface JupyterService { void upload(InputStream inputStream); void mlflow(); + + String runJupyterService(Integer id); + + String stopJupyterService(Integer id) throws Exception; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelDependencyService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelDependencyService.java new file mode 100644 index 00000000..5c8b9d1d --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelDependencyService.java @@ -0,0 +1,65 @@ +package com.ruoyi.platform.service; + +import com.ruoyi.platform.domain.ModelDependency; +import com.ruoyi.platform.vo.ModelDependcyTreeVo; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import java.io.IOException; +import java.util.List; + +/** + * (ModelDependency)表服务接口 + * + * @author Xidaray + * @since 2024-05-29 13:51:23 + */ +public interface ModelDependencyService { + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + ModelDependency queryById(Integer id); + + /** + * 分页查询 + * + * @param modelDependency 筛选条件 + * @param pageRequest 分页对象 + * @return 查询结果 + */ + Page queryByPage(ModelDependency modelDependency, PageRequest pageRequest); + + /** + * 修改数据 + * + * @param modelDependency 实例对象 + * @return 实例对象 + */ + ModelDependency update(ModelDependency modelDependency); + + /** + * 新增数据 + * + * @param modelDependency 实例对象 + * @return 实例对象 + */ + ModelDependency insert(ModelDependency modelDependency); + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 是否成功 + */ + boolean deleteById(Integer id); + + String removeById(Integer id); + + List queryByModelDependency(ModelDependency modelDependency) throws IOException; + + ModelDependcyTreeVo getModelDependencyTree(ModelDependency modelDependency) throws Exception; +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsVersionService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsVersionService.java index 6938da95..723bc745 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsVersionService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsVersionService.java @@ -6,6 +6,7 @@ import com.ruoyi.platform.domain.ModelsVersion; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import java.io.IOException; import java.util.List; import java.util.Map; @@ -68,7 +69,7 @@ public interface ModelsVersionService { Map queryByModelsIdAndVersion(Integer modelsId, String version); - Map deleteModelsVersion(Integer modelsId, String version); + Map deleteModelsVersion(Integer modelsId, String version) throws IOException; String addModelVersions(List modelsVersions) throws Exception; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/WorkflowService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/WorkflowService.java index 935a8918..46627488 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/WorkflowService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/WorkflowService.java @@ -45,7 +45,7 @@ public interface WorkflowService { * @param workflow 实例对象 * @return 实例对象 */ - Workflow update(Workflow workflow); + Workflow update(Workflow workflow) throws Exception; /** * 通过主键删除数据 diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ComponentServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ComponentServiceImpl.java index f5da44cc..47b05f0d 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ComponentServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ComponentServiceImpl.java @@ -51,7 +51,7 @@ public class ComponentServiceImpl implements ComponentService { throw new RuntimeException("组件不存在"); } - return this.componentDao.queryById(id); + return component; } @Override @@ -149,6 +149,7 @@ public class ComponentServiceImpl implements ComponentService { String inParameters= gson.toJson(componentVo.getInParameters(), LinkedHashMap.class); String outParameters = gson.toJson(componentVo.getOutParameters(), LinkedHashMap.class); + String envVariable = gson.toJson(componentVo.getEnvVirables(), LinkedHashMap.class); component.setEnvVirables(envVariable); component.setInParameters(inParameters); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DatasetServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DatasetServiceImpl.java index 3e4f277b..835eb58b 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DatasetServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DatasetServiceImpl.java @@ -353,22 +353,15 @@ public class DatasetServiceImpl implements DatasetService { public void checkDeclaredName(Dataset insert) throws Exception { Dataset existingDataset = datasetDao.findByName(insert.getName()); if (existingDataset != null) { - // Check if the found dataset is not the same as the one being inserted - // This is important if you are using this method for both insert and update operations - // You may need an identifier check here, e.g., if 'insert' has an ID and it's the same as 'existingDataset' if (insert.getId() != null && insert.getId().equals(existingDataset.getId())) { - // This is the same dataset, no duplicate name issue for update operation + // 相同数据集,无需判断 return; } - // Now we know there's another dataset with the same name Field[] fields = Dataset.class.getDeclaredFields(); - for (Field field : fields) { - field.setAccessible(true); // Make private fields accessible - + field.setAccessible(true); if ("name".equals(field.getName()) && field.isAnnotationPresent(CheckDuplicate.class)) { - // If the field is 'name' and is marked with CheckDuplicate annotation CheckDuplicate annotation = field.getAnnotation(CheckDuplicate.class); throw new Exception("重复的数据集名称: " + insert.getName() + ". " + annotation.message()); } 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 new file mode 100644 index 00000000..d5afec8b --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java @@ -0,0 +1,125 @@ +package com.ruoyi.platform.service.impl; + +import com.ruoyi.common.security.utils.SecurityUtils; +import com.ruoyi.platform.domain.DevEnvironment; +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.system.api.model.LoginUser; +import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Map; + +/** + * (DevEnvironment)表服务实现类 + * + * @author Xidaray + * @since 2024-06-03 15:17:37 + */ +@Service("devEnvironmentService") +public class DevEnvironmentServiceImpl implements DevEnvironmentService { + @Resource + private DevEnvironmentDao devEnvironmentDao; + + + @Resource + private JupyterService jupyterService; + + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + @Override + public DevEnvironment queryById(Integer id) { + return this.devEnvironmentDao.queryById(id); + } + + /** + * 分页查询 + * + * @param devEnvironment 筛选条件 + * @param pageRequest 分页对象 + * @return 查询结果 + */ + @Override + public Page queryByPage(DevEnvironment devEnvironment, PageRequest pageRequest) { + long total = this.devEnvironmentDao.count(devEnvironment); + return new PageImpl<>(this.devEnvironmentDao.queryAllByLimit(devEnvironment, pageRequest), pageRequest, total); + } + + /** + * 新增数据 + * + * @param devEnvironment 实例对象 + * @return 实例对象 + */ + @Override + public DevEnvironment insert(DevEnvironment devEnvironment) { + //插入预备,此时不需要判断版本重复 + LoginUser loginUser = SecurityUtils.getLoginUser(); + devEnvironment.setCreateBy(loginUser.getUsername()); + devEnvironment.setUpdateBy(loginUser.getUsername()); + devEnvironment.setUpdateTime(new Date()); + devEnvironment.setCreateTime(new Date()); + this.devEnvironmentDao.insert(devEnvironment); + return devEnvironment; + } + + /** + * 修改数据 + * + * @param devEnvironment 实例对象 + * @return 实例对象 + */ + @Override + public DevEnvironment update(DevEnvironment devEnvironment) { + LoginUser loginUser = SecurityUtils.getLoginUser(); + devEnvironment.setUpdateBy(loginUser.getUsername()); + devEnvironment.setUpdateTime(new Date()); + this.devEnvironmentDao.update(devEnvironment); + return this.queryById(devEnvironment.getId()); + } + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 是否成功 + */ + @Override + public boolean deleteById(Integer id) { + return this.devEnvironmentDao.deleteById(id) > 0; + } + + @Override + public String removeById(Integer id) { + DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); + if (devEnvironment == null){ + return "开发环境信息不存在"; + } + + //判断权限,只有admin和创建者本身可以删除该数据集 + LoginUser loginUser = SecurityUtils.getLoginUser(); + String username = loginUser.getUsername(); + String createdBy = devEnvironment.getCreateBy(); + if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ + return "无权限删除该开发环境"; + } + + devEnvironment.setState(0); + return this.devEnvironmentDao.update(devEnvironment)>0?"删除成功":"删除失败"; + } + + +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentInsServiceImpl.java index 940c4a02..5a71ac66 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentInsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentInsServiceImpl.java @@ -427,7 +427,6 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { throw new RuntimeException("日志为空。"); } //返回日志内容 - return experimentInsLog; } catch (Exception e) { throw new RuntimeException("查询实验日志失败: " + e.getMessage(), e); @@ -554,7 +553,7 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { // 查询具有相同状态的实例数量 Long count = experimentInsDao.count(experimentIns); - // 将状态及其对应的实例数量放入映射中 + // 将状态及其对应的实例数量放入map中 statusCountMap.put(status.toString(), count); } @@ -580,8 +579,9 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { flag = StringUtils.equals("Terminated", (String) workflowMap.get("phase")); } } - return flag; } } + + 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 56eb44ba..407ff6ae 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 @@ -2,14 +2,12 @@ package com.ruoyi.platform.service.impl; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.platform.annotations.CheckDuplicate; -import com.ruoyi.platform.domain.Experiment; -import com.ruoyi.platform.domain.ExperimentIns; -import com.ruoyi.platform.domain.Workflow; +import com.ruoyi.platform.domain.*; +import com.ruoyi.platform.domain.dependencydomain.ProjectDepency; +import com.ruoyi.platform.domain.dependencydomain.TrainTaskDepency; import com.ruoyi.platform.mapper.ExperimentDao; import com.ruoyi.platform.mapper.ExperimentInsDao; -import com.ruoyi.platform.service.ExperimentInsService; -import com.ruoyi.platform.service.ExperimentService; -import com.ruoyi.platform.service.WorkflowService; +import com.ruoyi.platform.service.*; import com.ruoyi.platform.utils.HttpUtils; import com.ruoyi.platform.utils.JacksonUtil; import com.ruoyi.platform.utils.JsonUtils; @@ -42,8 +40,12 @@ public class ExperimentServiceImpl implements ExperimentService { @Resource private ExperimentInsDao experimentInsDao; - - + @Resource + private ModelsService modelsService; + @Resource + private DatasetService datasetService; + @Resource + private ModelDependencyService modelDependencyService; @Resource @Lazy @@ -151,8 +153,9 @@ public class ExperimentServiceImpl implements ExperimentService { * @return 实例对象 */ @Override - public Experiment update(Experiment experiment) throws IOException { + public Experiment update(Experiment experiment) throws Exception { LoginUser loginUser = SecurityUtils.getLoginUser(); + checkDeclaredName(experiment); experiment.setUpdateBy(loginUser.getUsername()); experiment.setUpdateTime(new Date()); this.experimentDao.update(experiment); @@ -207,12 +210,9 @@ public class ExperimentServiceImpl implements ExperimentService { public Experiment runExperiment(Integer id) throws Exception { //先查出实验记录 Experiment experiment = this.queryById(id); - if (experiment == null) { System.out.println("No experiment"); } - - Workflow workflow = workflowService.queryById(experiment.getWorkflowId()); if(workflow == null) { throw new RuntimeException("流水线不存在,请先创建流水线"); @@ -226,13 +226,14 @@ public class ExperimentServiceImpl implements ExperimentService { throw new RuntimeException("转换流水线失败"); } Map converMap = JsonUtils.jsonToMap(convertRes); + // 组装运行接口json Map runReqMap = new HashMap<>(); runReqMap.put("data", converMap.get("data")); //这里全局参数是一个json数组,需要转换成一个list List> params = JacksonUtil.parseJSONStr2MapList(StringUtils.isEmpty(experiment.getGlobalParam()) ? "[]" : experiment.getGlobalParam()); runReqMap.put("params", params); - //// 实验字段的Map,不要写成一行!否则会返回null + // 实验字段的Map,不要写成一行!否则会返回null Map experimentMap = new HashMap<>(); experimentMap.put("name", "experiment-"+experiment.getId()); runReqMap.put("experiment", experimentMap); @@ -246,14 +247,11 @@ public class ExperimentServiceImpl implements ExperimentService { } Map runResMap = JsonUtils.jsonToMap(runRes); Map data = (Map) runResMap.get("data"); - //判断data为空 if (data == null || MapUtils.isEmpty(data)) { throw new RuntimeException("Failed to run workflow."); } - Map metadata = (Map) data.get("metadata"); - // 插入记录到实验实例表 ExperimentIns experimentIns = new ExperimentIns(); experimentIns.setExperimentId(experiment.getId()); @@ -262,24 +260,237 @@ public class ExperimentServiceImpl implements ExperimentService { experimentIns.setStatus("Pending"); //传入实验全局参数 - experimentIns.setGlobalParam(experiment.getGlobalParam()); - //替换argoInsName String outputString = JsonUtils.mapToJson(output); experimentIns.setNodesResult(outputString.replace("{{workflow.name}}", (String) metadata.get("name"))); //插入ExperimentIns表中 - experimentInsService.insert(experimentIns); + ExperimentIns insert = experimentInsService.insert(experimentIns); + + //插入到模型依赖关系表 + //得到dependendcy + Map converMap2 = JsonUtils.jsonToMap(JacksonUtil.replaceInAarry(convertRes, params)); + Map dependendcy = (Map)converMap2.get("model_dependency"); + Map trainInfo = (Map)converMap2.get("component_info"); + insertModelDependency(dependendcy,trainInfo,insert.getId(),experiment.getName()); }catch (Exception e){ throw new RuntimeException(e); } List updatedExperimentInsList = experimentInsService.getByExperimentId(id); - experiment.setExperimentInsList(updatedExperimentInsList); - return experiment; } + private void insertModelDependency(Map dependendcy,Map trainInfo, Integer experimentInsId, String experimentName) throws Exception { + Iterator> dependendcyIterator = dependendcy.entrySet().iterator(); + Map modelTrain = (Map) trainInfo.get("model_train"); + Map modelEvaluate = (Map) trainInfo.get("model_evaluate"); + Map modelExport = (Map) trainInfo.get("model_export"); + while (dependendcyIterator.hasNext()) { + ModelDependency modelDependency = new ModelDependency(); + Map.Entry entry = dependendcyIterator.next(); + Map modelDel = (Map) entry.getValue(); + Map source = (Map) modelDel.get("source"); + List> test = (List>) modelDel.get("test"); + List> target = (List>) modelDel.get("target"); + String sourceTaskId = (String) source.get("task_id"); + + Map modelTrainMap = (Map)modelTrain.get(sourceTaskId); + //处理project数据 + Map projectMap = (Map) modelTrainMap.get("project"); + ProjectDepency projectDepency = new ProjectDepency(); + projectDepency.setBranch(projectMap.get("branch")); + String projectUrl = projectMap.get("url"); + projectDepency.setUrl(projectUrl); + projectDepency.setName(projectUrl.substring(projectUrl.lastIndexOf('/') + 1, projectUrl.length() - 4)); + //依赖项目 + modelDependency.setProjectDependency(JsonUtils.objectToJson(projectDepency)); + //处理镜像 + Map imagesMap = (Map) modelTrainMap.get("image"); + modelDependency.setTrainImage(imagesMap.get("name")); + List> trainParamList = (List>) modelTrainMap.get("params"); + modelDependency.setTrainParams(JsonUtils.objectToJson(trainParamList)); + //处理source数据 + List> modelsList = (List>) modelTrainMap.get("models"); + if (modelsList != null) { + for (int i = 0; i < modelsList.size(); i++) { + Map model = modelsList.get(i); + Models models = modelsService.queryById((Integer) model.get("model_id")); + if (models == null) { + throw new Exception("源模型不存在"); + } + model.put("model_name", models.getName()); + } + //父模型 + modelDependency.setParentModels(JsonUtils.objectToJson(modelsList)); + } + List> datasetsList = (List>) modelTrainMap.get("datasets"); + if (datasetsList != null) { + for (int i = 0; i < datasetsList.size(); i++) { + Map datasets = datasetsList.get(i); + Dataset dataset = datasetService.queryById((Integer) datasets.get("dataset_id")); + if (dataset == null) { + throw new Exception("源数据集不存在"); + } + datasets.put("dataset_name", dataset.getName()); + } + } + //训练数据集 + modelDependency.setTrainDataset(JsonUtils.objectToJson(datasetsList)); + TrainTaskDepency trainTaskDepency = new TrainTaskDepency(); + trainTaskDepency.setTaskId(sourceTaskId); + trainTaskDepency.setInsId(experimentInsId); + trainTaskDepency.setName(experimentName); + //训练任务 + modelDependency.setTrainTask(JsonUtils.objectToJson(trainTaskDepency)); + modelDependency.setExpInsId(experimentInsId); + List> resultTestDatasets = new ArrayList>(); + //处理test数据 + if (test != null) { + for(int i=0;i testMap = test.get(i); + String testTaskId = (String) testMap.get("task_id"); + Map evaluateMap = (Map) modelEvaluate.get(testTaskId); + List> realDataSetList = (List>) evaluateMap.get("datasets"); + for(int j=0;j realDataSet = realDataSetList.get(j); + Dataset dataset = datasetService.queryById((Integer) realDataSet.get("dataset_id")); + if (dataset == null){ + throw new Exception("源数据集不存在"); + } + realDataSet.put("dataset_name", dataset.getName()); + resultTestDatasets.add(realDataSet); + } + } + + //测试数据集 + modelDependency.setTestDataset(JsonUtils.objectToJson(resultTestDatasets)); + } + //处理target数据 + if (target != null) { + for (int i = 0; i < target.size(); i++) { + Map targetMap = target.get(i); + String targetaskId = (String) targetMap.get("task_id"); + Map exportMap = (Map) modelExport.get(targetaskId); + List> modelTargetList = (List>) exportMap.get("models"); + modelDependency.setState(2); + for (int j = 0; j < modelTargetList.size(); j++) { + Map model = modelTargetList.get(i); + modelDependency.setVersion((String) model.get("model_version")); + modelDependency.setCurrentModelId((Integer) model.get("model_id")); + //因为可能有多成果模型,多次插入 + modelDependencyService.insert(modelDependency); + } + } + }else { + modelDependency.setState(2); + modelDependencyService.insert(modelDependency); + } + + } + } + + /** + * 被废弃的旧JSON + * @param experiment + * @return + * @throws Exception + */ +// private void insertModelDependency(Map dependendcy,Map trainInfo, Integer experimentInsId, String experimentName, List> params) throws Exception { +// Iterator> dependendcyIterator = dependendcy.entrySet().iterator(); +// while (dependendcyIterator.hasNext()) { +// ModelDependency modelDependency = new ModelDependency(); +// Map.Entry entry = dependendcyIterator.next(); +// String key = entry.getKey(); +// Map modelDel = (Map) entry.getValue(); +// //处理project数据 +// Map projectMap = (Map) modelDel.get("project"); +// ProjectDepency projectDepency = new ProjectDepency(); +// projectDepency.setBranch(projectMap.get("branch")); +// String projectUrl = projectMap.get("url"); +// projectDepency.setUrl(projectUrl); +// projectDepency.setName(projectUrl.substring(projectUrl.lastIndexOf('/') + 1, projectUrl.length() - 4)); +// //依赖项目 +// modelDependency.setProjectDependency(JsonUtils.objectToJson(projectDepency)); +// //处理source数据 +// Map sourceMap = (Map) modelDel.get("source"); +// List> modelsList = (List>) sourceMap.get("models"); +// for(int i=0;i model = modelsList.get(i); +// Models models = modelsService.queryById((Integer) model.get("model_id")); +// if (models == null){ +// throw new Exception("源模型不存在"); +// } +// model.put("model_name", models.getName()); +// } +// //父模型 +// modelDependency.setParentModels(JsonUtils.objectToJson(modelsList)); +// +// List> datasetsList = (List>) sourceMap.get("datasets"); +// for(int i=0;i datasets = datasetsList.get(i); +// Dataset dataset = datasetService.queryById((Integer) datasets.get("dataset_id")); +// if (dataset == null){ +// throw new Exception("源数据集不存在"); +// } +// datasets.put("dataset_name", dataset.getName()); +// } +// //训练数据集 +// modelDependency.setTrainDataset(JsonUtils.objectToJson(datasetsList)); +// +// TrainTaskDepency trainTaskDepency = new TrainTaskDepency(); +// trainTaskDepency.setTaskId(key); +// trainTaskDepency.setInsId(experimentInsId); +// trainTaskDepency.setName(experimentName); +// //训练任务 +// modelDependency.setTrainTask(JsonUtils.objectToJson(trainTaskDepency)); +// modelDependency.setExpInsId(experimentInsId); +// //处理test数据 +// List> testDatasetsList = (List>) modelDel.get("test"); +// List> resultTestDatasets = new ArrayList>(); +// for(int i=0;i datasets = testDatasetsList.get(i); +// List> realDataSetList = (List>) datasets.get("datasets"); +// for(int j=0;j realDataSet = realDataSetList.get(j); +// Dataset dataset = datasetService.queryById((Integer) realDataSet.get("dataset_id")); +// if (dataset == null){ +// throw new Exception("源数据集不存在"); +// } +// realDataSet.put("dataset_name", dataset.getName()); +// resultTestDatasets.add(realDataSet); +// } +// +// } +// //测试数据集 +// modelDependency.setTestDataset(JsonUtils.objectToJson(resultTestDatasets)); +// +// //检查是否存在target,如果存在说明在流水线用了节点导入,如果没有说明没有导入,等待手动push +// List> modelTargetList = (List>) modelDel.get("target"); +// if (modelTargetList==null||modelTargetList.size()==0){ +// modelDependency.setState(1); +// modelDependencyService.insert(modelDependency); +// }else { +// modelDependency.setState(2); +// for(int i=0;i model = modelTargetList.get(i); +// String version = null; +// //可能是参数,必须从实验参数读取 +// if (params != null) { +// for (Map param : params) { +// if (param.containsKey("param_name") && StringUtils.equals("model_version",(String) param.get("param_name"))) { +// version = param.get("param_value").toString(); +// } +// } +// } +// modelDependency.setVersion(StringUtils.isEmpty(version)?(String)model.get("model_version"):version); +// modelDependency.setCurrentModelId((Integer) model.get("model_id")); +// //因为可能有多成果模型,多次插入 +// modelDependencyService.insert(modelDependency); +// } +// } +// } +// } @Override public Experiment addAndRunExperiment(Experiment experiment) throws Exception { @@ -332,7 +543,7 @@ public class ExperimentServiceImpl implements ExperimentService { // 现在我们知道还有另一个具有相同名称的流水线 Field[] fields = Experiment.class.getDeclaredFields(); for (Field field : fields) { - field.setAccessible(true); // 使私有字段可访问 + field.setAccessible(true); if ("name".equals(field.getName()) && field.isAnnotationPresent(CheckDuplicate.class)) { // 如果字段是“name”并且标记了CheckDuplicate注解 CheckDuplicate annotation = field.getAnnotation(CheckDuplicate.class); 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 5b94a350..01a41535 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 @@ -1,7 +1,12 @@ package com.ruoyi.platform.service.impl; +import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.security.utils.SecurityUtils; +import com.ruoyi.platform.domain.DevEnvironment; +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.utils.K8sClientUtil; import com.ruoyi.platform.utils.MinioUtil; import com.ruoyi.platform.utils.MlflowUtil; @@ -13,6 +18,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.InputStream; import java.util.List; +import java.util.Map; @Service public class JupyterServiceImpl implements JupyterService { @@ -39,6 +45,15 @@ public class JupyterServiceImpl implements JupyterService { @Resource private MlflowUtil mlflowUtil; + @Resource + private DevEnvironmentDao devEnvironmentDao; + + @Resource + private DevEnvironmentService devEnvironmentService; + + @Resource + private RedisService redisService; + public JupyterServiceImpl(MinioUtil minioUtil) { this.minioUtil = minioUtil; } @@ -53,6 +68,54 @@ public class JupyterServiceImpl implements JupyterService { return masterIp + ":" + podPort; } + @Override + public String runJupyterService(Integer id) { + DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); + String envName = devEnvironment.getName(); + //TODO 设置环境变量 + + // 提取数据集,模型信息,得到数据集模型的path + Map dataset = JacksonUtil.parseJSONStr2Map(devEnvironment.getDataset()); + String datasetPath = (String) dataset.get("path"); + Map model = JacksonUtil.parseJSONStr2Map(devEnvironment.getModel()); + String modelPath = (String) model.get("path"); + + LoginUser loginUser = SecurityUtils.getLoginUser(); + String podName = loginUser.getUsername().toLowerCase() + "-editor-pod"; + String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc"; + V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage, storageClassName); + + //TODO 设置镜像可配置,这里先用默认镜像启动pod + + // 调用修改后的 createPod 方法,传入额外的参数 + Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, datasetPath, modelPath); + return masterIp + ":" + podPort; + + + } + + @Override + public String stopJupyterService(Integer id) throws Exception { + DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); + if (devEnvironment==null){ + throw new Exception("开发环境配置不存在"); + } + + LoginUser loginUser = SecurityUtils.getLoginUser(); + String podName = loginUser.getUsername().toLowerCase() + "-editor-pod"; + + // 使用 Kubernetes API 删除 Pod + String deleteResult = k8sClientUtil.deletePod(podName, namespace); + + // 检查 Pod 是否存在 + boolean exists = k8sClientUtil.checkPodExists(podName, namespace); + if (exists) { + throw new Exception("Pod " + podName + " 删除失败"); + } + return deleteResult + ",编辑器已停止"; + } + + @Override public void upload(InputStream inputStream) { try { @@ -71,4 +134,6 @@ public class JupyterServiceImpl implements JupyterService { } + + } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelDependencyServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelDependencyServiceImpl.java new file mode 100644 index 00000000..91b071ca --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelDependencyServiceImpl.java @@ -0,0 +1,252 @@ +package com.ruoyi.platform.service.impl; + +import com.ruoyi.common.security.utils.SecurityUtils; +import com.ruoyi.platform.domain.*; +import com.ruoyi.platform.mapper.ModelDependencyDao; +import com.ruoyi.platform.service.*; +import com.ruoyi.platform.utils.JacksonUtil; +import com.ruoyi.platform.vo.ModelDependcyTreeVo; +import com.ruoyi.platform.vo.ModelVersionDependcyVo; +import com.ruoyi.system.api.model.LoginUser; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * (ModelDependency)表服务实现类 + * + * @author Xidaray + * @since 2024-05-29 13:51:23 + */ +@Service("modelDependencyService") +public class ModelDependencyServiceImpl implements ModelDependencyService { + @Resource + private ModelDependencyDao modelDependencyDao; + + @Resource + private ModelsService modelsService; + @Resource + private ModelsVersionService modelsVersionService; + @Lazy + @Resource + private ExperimentService experimentService; + @Lazy + @Resource + private ExperimentInsService experimentInsService; + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + @Override + public ModelDependency queryById(Integer id) { + return this.modelDependencyDao.queryById(id); + } + + /** + * 分页查询 + * + * @param modelDependency 筛选条件 + * @param pageRequest 分页对象 + * @return 查询结果 + */ + @Override + public Page queryByPage(ModelDependency modelDependency, PageRequest pageRequest) { + long total = this.modelDependencyDao.count(modelDependency); + return new PageImpl<>(this.modelDependencyDao.queryAllByLimit(modelDependency, pageRequest), pageRequest, total); + } + + /** + * 根据对象查询 + * + * @param modelDependency 筛选条件 + * @return 查询结果 + */ + @Override + public List queryByModelDependency(ModelDependency modelDependency) throws IOException { + List modelDependencyList = this.modelDependencyDao.queryByModelDependency(modelDependency); + return modelDependencyList; + } + + @Override + public ModelDependcyTreeVo getModelDependencyTree(ModelDependency modelDependencyQuery) throws Exception { + //查询当前模型 + modelDependencyQuery.setState(1); + List modelDependencyList = this.queryByModelDependency(modelDependencyQuery); + if (modelDependencyList==null || modelDependencyList.size()==0){ + throw new Exception("当前模型依赖关系不存在"); + } + ModelDependency modelDependency = modelDependencyList.get(0); + ModelDependcyTreeVo modelDependcyTreeVo = ModelDependencyConvertToTree(modelDependency); + //递归父模型 + processParentModel(modelDependcyTreeVo); + //递归子模型 + processChildrenModel(modelDependcyTreeVo); + return modelDependcyTreeVo; + } + + /** + * 递归父模型 + * @param modelDependcyTreeVo + */ + private void processParentModel(ModelDependcyTreeVo modelDependcyTreeVo) throws IOException { + if (modelDependcyTreeVo.getParentModelsMap() != null) { + List> parentMaps = modelDependcyTreeVo.getParentModelsMap(); + List ps = new ArrayList(); + for (Map parent:parentMaps) { + Integer model_id = (Integer) parent.get("model_id"); + String version = (String) parent.get("model_version"); + ModelDependency modelDependencyQuery = new ModelDependency(); + modelDependencyQuery.setVersion(version); + modelDependencyQuery.setCurrentModelId(model_id); + List modelDependencyList = this.queryByModelDependency(modelDependencyQuery); + if (modelDependencyList!=null&&modelDependencyList.size()>=0){ + for (ModelDependency modelDependency:modelDependencyList){ + ModelDependcyTreeVo modelDependencyTreeVoIn = ModelDependencyConvertToTree(modelDependency); + processParentModel(modelDependencyTreeVoIn); + ps.add(modelDependencyTreeVoIn); + } + } + + } + modelDependcyTreeVo.setParentModels(ps); + } + } + + + private void processChildrenModel(ModelDependcyTreeVo modelDependcyTreeVo) throws IOException { + String version = modelDependcyTreeVo.getVersion(); + Integer modelId = modelDependcyTreeVo.getCurrentModelId(); + List cs = new ArrayList(); + //查儿子们 + + List modelDependencyList = modelDependencyDao.queryChildrenByVersionId("\"model_id\":"+modelId, "\"model_version\":\""+version+"\""); + if (modelDependencyList!=null&&modelDependencyList.size()>=0){ + for (ModelDependency modelDependency:modelDependencyList){ + ModelDependcyTreeVo modelDependencyTreeVoIn = ModelDependencyConvertToTree(modelDependency); + processChildrenModel(modelDependencyTreeVoIn); + cs.add(modelDependencyTreeVoIn); + } + } + modelDependcyTreeVo.setChildrenModels(cs); + } + + private ModelDependcyTreeVo ModelDependencyConvertToTree(ModelDependency modelDependency) throws IOException { + ModelDependcyTreeVo modelDependcyTreeVo = new ModelDependcyTreeVo(); + modelDependcyTreeVo.setCurrentModelId(modelDependency.getCurrentModelId()); + modelDependcyTreeVo.setExpInsId(modelDependency.getExpInsId()); + modelDependcyTreeVo.setVersion(modelDependency.getVersion()); + modelDependcyTreeVo.setRefItem(modelDependency.getRefItem()); + modelDependcyTreeVo.setTrainTask(JacksonUtil.parseJSONStr2Map(modelDependency.getTrainTask())); + modelDependcyTreeVo.setTrainDataset(JacksonUtil.parseJSONStr2MapList(modelDependency.getTrainDataset())); + modelDependcyTreeVo.setTrainImage(modelDependency.getTrainImage()); + modelDependcyTreeVo.setTrainParams(JacksonUtil.parseJSONStr2TList(modelDependency.getTrainParams(),Object.class,null)); + modelDependcyTreeVo.setTestDataset(JacksonUtil.parseJSONStr2MapList(modelDependency.getTestDataset())); + modelDependcyTreeVo.setProjectDependency(JacksonUtil.parseJSONStr2Map(modelDependency.getProjectDependency())); + modelDependcyTreeVo.setParentModelsMap(JacksonUtil.parseJSONStr2MapList(modelDependency.getParentModels())); + + /** + * 补充workFlow_id + 是否共有 + */ + Integer currentModelId = modelDependency.getCurrentModelId(); + Integer expInsId = modelDependency.getExpInsId(); + Models models = modelsService.queryById(currentModelId); + ModelsVersion modelsVersionquery = new ModelsVersion(); + modelsVersionquery.setModelsId(currentModelId); + modelsVersionquery.setVersion(modelDependency.getVersion()); + ModelsVersion modelsVersion = modelsVersionService.queryByModelsVersion(modelsVersionquery); + ExperimentIns experimentIns = experimentInsService.queryById(expInsId); + Experiment experiment = experimentService.queryById(experimentIns.getExperimentId()); + ModelVersionDependcyVo modelVersionDependcyVo = new ModelVersionDependcyVo(); + modelVersionDependcyVo.setName(models.getName()); + modelVersionDependcyVo.setAvailableRange(models.getAvailableRange()); + modelVersionDependcyVo.setDescription(models.getDescription()); + modelVersionDependcyVo.setModelTag(models.getModelTag()); + modelVersionDependcyVo.setModelType(models.getModelType()); + modelVersionDependcyVo.setModelTagName(models.getModelTagName()); + modelVersionDependcyVo.setModelTypeName(models.getModelTypeName()); + modelVersionDependcyVo.setFileName(modelsVersion.getFileName()); + modelVersionDependcyVo.setFileSize(modelsVersion.getFileSize()); + modelVersionDependcyVo.setUrl(modelsVersion.getUrl()); + modelDependcyTreeVo.setWorkflowId(experiment.getWorkflowId()); + modelDependcyTreeVo.setModelVersionDependcyVo(modelVersionDependcyVo); + + return modelDependcyTreeVo; + } + + /** + * 新增数据 + * + * @param modelDependency 实例对象 + * @return 实例对象 + */ + @Override + public ModelDependency insert(ModelDependency modelDependency) { + //插入预备,此时不需要判断版本重复 + LoginUser loginUser = SecurityUtils.getLoginUser(); + modelDependency.setCreateBy(loginUser.getUsername()); + modelDependency.setUpdateBy(loginUser.getUsername()); + modelDependency.setUpdateTime(new Date()); + modelDependency.setCreateTime(new Date()); + this.modelDependencyDao.insert(modelDependency); + return modelDependency; + } + + /** + * 修改数据 + * + * @param modelDependency 实例对象 + * @return 实例对象 + */ + @Override + public ModelDependency update(ModelDependency modelDependency) { + LoginUser loginUser = SecurityUtils.getLoginUser(); + modelDependency.setUpdateBy(loginUser.getUsername()); + modelDependency.setUpdateTime(new Date()); + this.modelDependencyDao.update(modelDependency); + return this.queryById(modelDependency.getId()); + } + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 是否成功 + */ + @Override + public boolean deleteById(Integer id) { + return this.modelDependencyDao.deleteById(id) > 0; + } + + @Override + public String removeById(Integer id) { + ModelDependency modelDependency = this.modelDependencyDao.queryById(id); + if (modelDependency == null){ + return "模型依赖信息不存在"; + } + + //判断权限,只有admin和创建者本身可以删除该数据集 + LoginUser loginUser = SecurityUtils.getLoginUser(); + String username = loginUser.getUsername(); + String createdBy = modelDependency.getCreateBy(); + if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ + return "无权限删除"; + } + + modelDependency.setState(0); + return this.modelDependencyDao.update(modelDependency)>0?"删除成功":"删除失败"; + } + +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java index d0a504ee..b3603484 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java @@ -192,6 +192,7 @@ public class ModelsServiceImpl implements ModelsService { * * @param id models_version表的主键 * @return 文件内容 + * */ @Override diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsVersionServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsVersionServiceImpl.java index be20b969..08d94324 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsVersionServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsVersionServiceImpl.java @@ -3,20 +3,24 @@ package com.ruoyi.platform.service.impl; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.platform.annotations.CheckDuplicate; import com.ruoyi.platform.domain.Dataset; +import com.ruoyi.platform.domain.ModelDependency; import com.ruoyi.platform.domain.Models; import com.ruoyi.platform.domain.ModelsVersion; import com.ruoyi.platform.mapper.ModelsDao; import com.ruoyi.platform.mapper.ModelsVersionDao; +import com.ruoyi.platform.service.ModelDependencyService; import com.ruoyi.platform.service.ModelsVersionService; import com.ruoyi.system.api.model.LoginUser; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.io.IOException; import java.lang.reflect.Field; import java.util.Date; import java.util.HashMap; @@ -36,7 +40,9 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { @Resource private ModelsDao modelsDao; - + @Resource + @Lazy + private ModelDependencyService modelDependencyService; // 固定存储桶名 @Value("${minio.dataReleaseBucketName}") private String bucketName; @@ -81,6 +87,7 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { modelsVersion.setCreateTime(new Date()); modelsVersion.setState(1); this.modelsVersionDao.insert(modelsVersion); + insertModelsDependency(modelsVersion); return modelsVersion; } @@ -191,7 +198,7 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { } @Override - public Map deleteModelsVersion(Integer modelsId, String version) { + public Map deleteModelsVersion(Integer modelsId, String version) throws IOException { Map results = new HashMap(); // 根据模型ID和版本查询所有模型版本 List versions = this.modelsVersionDao.queryAllByModelsVersion(modelsId, version); @@ -200,6 +207,16 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { String result = this.removeById(modelsVersion.getId()); results.put(modelsVersion.getId(), result); } + // 删除依赖关系 + ModelDependency modelDependency = new ModelDependency(); + modelDependency.setCurrentModelId(modelsId); + modelDependency.setVersion(version); + modelDependency.setState(1); + List modelDependencyList = modelDependencyService.queryByModelDependency(modelDependency); + if (modelDependencyList.size()>0){ + ModelDependency modelDependency1 = modelDependencyList.get(0); + modelDependencyService.removeById(modelDependency1.getId()); + } return results; } @@ -210,6 +227,7 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { for(ModelsVersion modelsVersion : modelsVersions) { insertPrepare(modelsVersion); } + insertModelsDependency(modelsVersions.get(0)); this.modelsVersionDao.insertBatch(modelsVersions); return "新增模型版本成功"; } catch (Exception e) { @@ -248,4 +266,23 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { } + //新增模型依赖关系 + private void insertModelsDependency(ModelsVersion modelsVersion) throws Exception { + ModelDependency modelDependency = new ModelDependency(); + modelDependency.setCurrentModelId(modelsVersion.getModelsId()); + modelDependency.setVersion(modelsVersion.getVersion()); + modelDependency.setState(2); + List modelDependencyList = modelDependencyService.queryByModelDependency(modelDependency); + if (modelDependencyList != null && modelDependencyList.size()>0){ + //查到2,说明是之前流水线推送的,你就直接该状态生效就完了 + ModelDependency modelDependency1 = modelDependencyList.get(0); + modelDependency1.setState(1); + modelDependencyService.update(modelDependency1); + + }else { + modelDependency.setState(1); + modelDependencyService.insert(modelDependency); + } + } + } 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 18b487f5..22c736cc 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 @@ -110,8 +110,9 @@ public class WorkflowServiceImpl implements WorkflowService { * @return 实例对象 */ @Override - public Workflow update(Workflow workflow) { + public Workflow update(Workflow workflow) throws Exception { LoginUser loginUser = SecurityUtils.getLoginUser(); + checkDeclaredName(workflow); workflow.setUpdateBy(loginUser.getUsername()); workflow.setUpdateTime(new Date()); this.workflowDao.update(workflow); @@ -207,10 +208,9 @@ public class WorkflowServiceImpl implements WorkflowService { // 这是相同的流水线,更新操作中没有重复名称问题 return; } - // 现在我们知道还有另一个具有相同名称的流水线 Field[] fields = Workflow.class.getDeclaredFields(); for (Field field : fields) { - field.setAccessible(true); // 使私有字段可访问 + field.setAccessible(true); if ("name".equals(field.getName()) && field.isAnnotationPresent(CheckDuplicate.class)) { // 如果字段是“name”并且标记了CheckDuplicate注解 CheckDuplicate annotation = field.getAnnotation(CheckDuplicate.class); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkspaceServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkspaceServiceImpl.java index b5392261..d0f82ccf 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkspaceServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/WorkspaceServiceImpl.java @@ -103,6 +103,7 @@ public class WorkspaceServiceImpl implements WorkspaceService { assetCountMap.put("image", imageCount); //统计组件数量 Component component = new Component(); + component.setAvailableRange(availableRange); Integer componentCount = (int) this.componentDao.count(component); assetCountMap.put("component", componentCount); //统计流水线数量 diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/JacksonUtil.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/JacksonUtil.java index b413db90..6fd59389 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/JacksonUtil.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/JacksonUtil.java @@ -5,9 +5,12 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.type.CollectionType; +import com.ruoyi.common.core.utils.StringUtils; +import org.apache.xmlbeans.impl.xb.xsdschema.Public; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,6 +59,21 @@ public class JacksonUtil { } } + + + public static String replaceInAarry(String res, List> params) { + // 解析 JSON 数组 + if (params == null) { + return res; + } + for (int i = 0; i < params.size(); i++) { + Map stringObjectMap = params.get(i); + if (res.contains("${" + stringObjectMap.get("param_name") + "}")) { + res = res.replace("${" + stringObjectMap.get("param_name") + "}", String.valueOf( stringObjectMap.get("param_value"))); + } + } + return res; + } /** * 获取ObjectMapper。其本身是线程安全的,可以作为成员变量,但传入的参数不可能每次都一样,所以不使用成员变量。 * @@ -140,6 +158,7 @@ public class JacksonUtil { */ public static Map parseJSONStr2Map(String jsonStr) { try { + if (StringUtils.isEmpty(jsonStr)) {return new HashMap();} // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 ObjectMapper objectMapper = getObjectMapper(null, false, false, true); return objectMapper.readValue(jsonStr, Map.class); @@ -158,6 +177,7 @@ public class JacksonUtil { */ public static List> parseJSONStr2MapList(String jsonStr) { try { + if (StringUtils.isEmpty(jsonStr)) {return new ArrayList<>();} // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 ObjectMapper objectMapper = getObjectMapper(null, false, false, true); CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Map.class); 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 5f146711..9a5a2ab4 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 @@ -22,9 +22,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.BufferedReader; import java.io.InputStreamReader; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; /** * k8s客户端 @@ -282,13 +280,13 @@ public class K8sClientUtil { .endSpec() .build(); - try { pod = api.createNamespacedPod(namespace, pod, null, null, null); } catch (ApiException e) { log.error("创建pod异常:" + e.getResponseBody(), e); } catch (Exception e) { log.error("创建pod系统异常:", e); + } V1Service service = createService(namespace, podName + "-svc", port, selector); @@ -324,7 +322,6 @@ public class K8sClientUtil { for (V1Pod pod1 : v1PodList.getItems()) { if (StringUtils.equals(pod1.getMetadata().getName(), podName)) { // PVC 已存在 - V1Service service = createService(namespace, podName + "-svc", port, selector); if (service != null) { return service.getSpec().getPorts().get(0).getNodePort(); @@ -378,6 +375,73 @@ public class K8sClientUtil { return service.getSpec().getPorts().get(0).getNodePort(); } + + public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, String image, String datasetPath, String modelPath) { + Map selector = new LinkedHashMap<>(); + selector.put("k8s-jupyter", podName); + + CoreV1Api api = new CoreV1Api(apiClient); + V1PodList v1PodList = null; + try { + v1PodList = api.listNamespacedPod(namespace, null, null, null, null, null, null, null, null, null, null); + } catch (ApiException e) { + log.error("获取 POD 异常:", e); + } + if (v1PodList != null) { + for (V1Pod pod1 : v1PodList.getItems()) { + // PVC 已存在 + if (StringUtils.equals(pod1.getMetadata().getName(), podName)) { + V1Service service = createService(namespace, podName + "-svc", port, selector); + if (service != null) { + return service.getSpec().getPorts().get(0).getNodePort(); + } + } + } + } + + // 配置卷和卷挂载 + 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)); + + 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()))); + + V1Pod pod = new V1PodBuilder() + .withNewMetadata() + .withName(podName) + .withLabels(selector) + .endMetadata() + .withNewSpec() + .addNewContainer() + .withName(podName) + .withImage(image) + .withPorts(new V1ContainerPort().containerPort(port).protocol("TCP")) + .withVolumeMounts(volumeMounts) + .endContainer() + .withVolumes(volumes) + .withTerminationGracePeriodSeconds(14400L) + .endSpec() + .build(); + + try { + pod = api.createNamespacedPod(namespace, pod, null, null, null); + } catch (ApiException e) { + log.error("创建pod异常:" + e.getResponseBody(), e); + } catch (Exception e) { + log.error("创建pod系统异常:", e); + } + + V1Service service = createService(namespace, podName + "-svc", port, selector); + return service.getSpec().getPorts().get(0).getNodePort(); + } + + + + /** * 根据获取namespace,deploymentName的Pod Name * @@ -495,4 +559,51 @@ public class K8sClientUtil { } return pod; } + + /** + * 删除 Pod + * + * @param podName Pod 名称 + * @param namespace 命名空间 + * @throws ApiException 异常 + */ + public String deletePod(String podName, String namespace) throws ApiException { + CoreV1Api api = new CoreV1Api(apiClient); + try { + V1Pod pod = api.deleteNamespacedPod(podName, namespace, null, null, null, null, null, null); + return "Pod " + podName + " 删除请求已发送"; + } catch (ApiException e) { + if (e.getCode() == 404) { + return "Pod " + podName + " 不存在"; + } else { + log.error("删除pod异常:" + e.getResponseBody(), e); + throw e; + } + } + } + + /** + * 检查 Pod 是否存在 + * + * @param podName Pod 名称 + * @param namespace 命名空间 + * @return 是否存在 + * @throws ApiException 异常 + */ + public boolean checkPodExists(String podName, String namespace) throws ApiException { + CoreV1Api api = new CoreV1Api(apiClient); + try { + api.readNamespacedPod(podName, namespace, null,false,false); + return true; + } catch (ApiException e) { + if (e.getCode() == 404) { + return false; + } else { + log.error("检查pod存在性异常:" + e.getResponseBody(), e); + throw e; + } + } + } + + } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelDependcyTreeVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelDependcyTreeVo.java new file mode 100644 index 00000000..8abe4033 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelDependcyTreeVo.java @@ -0,0 +1,72 @@ +package com.ruoyi.platform.vo; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.ruoyi.platform.domain.Models; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class ModelDependcyTreeVo { + /** + * 当前模型id + */ + private Integer currentModelId; + /** + * 实验实例id + */ + private Integer expInsId; + + /** + * 版本 + */ + private String version; + + + /** + * 引用项目 + */ + private String refItem; + /** + * 训练任务 + */ + private Map trainTask; + /** + * 训练数据集 + */ + private List> trainDataset; + + /** + * 训练参数 + */ + private List trainParams; + /** + * 训练镜像 + */ + private String trainImage; + /** + * 测试数据集 + */ + private List> testDataset; + /** + * 依赖项目 + */ + private Map projectDependency; + + private List> parentModelsMap; + /** + * 父模型 + */ + private List parentModels; + + /** + * 子模型 + */ + private List childrenModels; + + private Long workflowId; + private ModelVersionDependcyVo modelVersionDependcyVo; +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelVersionDependcyVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelVersionDependcyVo.java new file mode 100644 index 00000000..9d835a41 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelVersionDependcyVo.java @@ -0,0 +1,48 @@ +package com.ruoyi.platform.vo; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.ruoyi.platform.annotations.CheckDuplicate; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class ModelVersionDependcyVo implements Serializable { + + + @ApiModelProperty(name = "name") + private String name; + + // private String version; + @ApiModelProperty(name = "description") + private String description; + + @ApiModelProperty(value = "模型可见范围,1表示公开,0表示私有") + private int availableRange; + + @ApiModelProperty(value = "模型类型(模型框架)") + private String modelType; + + @ApiModelProperty(value = "模型标签") + private String modelTag; + + + @ApiModelProperty(value = "模型类型名") + private String modelTypeName; + + @ApiModelProperty(value = "模型tag名") + private String modelTagName; + + @ApiModelProperty(value = "模型存储地址") + private String url; + + @ApiModelProperty(value = "文件名") + private String fileName; + + @ApiModelProperty(value = "文件大小") + private String fileSize; + +} diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ComponentDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ComponentDaoMapper.xml index d4af39d0..84b369ff 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ComponentDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ComponentDaoMapper.xml @@ -16,6 +16,7 @@ + @@ -28,7 +29,7 @@ @@ -42,7 +43,7 @@ + select + id,name,status,computing_resource,standard,env_variable,image,dataset,model,alt_field1,alt_field2,create_by,create_time,update_by,update_time,state + from dev_environment + where id = #{id} + + + + + + + + + + + + insert into dev_environment(name,status,computing_resource,standard,env_variable,image,dataset,model,alt_field1,alt_field2,create_by,create_time,update_by,update_time,state) + values (#{devEnvironment.name}, + #{devEnvironment.status}, + #{devEnvironment.computingResource}, + #{devEnvironment.standard}, + #{devEnvironment.envVariable}, + #{devEnvironment.image}, + #{devEnvironment.dataset}, + #{devEnvironment.model}, + #{devEnvironment.altField1}, + #{devEnvironment.altField2}, + #{devEnvironment.createBy}, + #{devEnvironment.createTime}, + #{devEnvironment.updateBy}, + #{devEnvironment.updateTime}, + #{devEnvironment.state} + ) + + + + insert into dev_environment(name,status,computing_resource,standard,env_variable,image,dataset,model,alt_field1,alt_field2,create_by,create_time,update_by,update_time,state ) + values + + (#{entity.name},#{entity.status},#{entity.computingResource},#{entity.standard},#{entity.envVariable},#{entity.image},#{entity.dataset},#{entity.model},#{entity.altField1},#{entity.altField2},#{entity.createBy},#{entity.createTime},#{entity.updateBy},#{entity.updateTime},#{entity.state}) + + + + + insert into dev_environment(name,status,computing_resource,standard,env_variable,image,dataset,model,alt_field1,alt_field2,create_by,create_time,update_by,update_time,state) + values + + (#{entity.name}#{entity.status}#{entity.computingResource}#{entity.standard}#{entity.envVariable}#{entity.image}#{entity.dataset}#{entity.model}#{entity.altField1}#{entity.altField2}#{entity.createBy}#{entity.createTime}#{entity.updateBy}#{entity.updateTime}#{entity.state}) + + on duplicate key update +name = values(name)status = values(status)computing_resource = values(computing_resource)standard = values(standard)env_variable = values(env_variable)image = values(image)dataset = values(dataset)model = values(model)alt_field1 = values(alt_field1)alt_field2 = values(alt_field2)create_by = values(create_by)create_time = values(create_time)update_by = values(update_by)update_time = values(update_time)state = values(state) + + + + + update dev_environment + + + name = #{devEnvironment.name}, + + + status = #{devEnvironment.status}, + + + computing_resource = #{devEnvironment.computingResource}, + + + standard = #{devEnvironment.standard}, + + + env_variable = #{devEnvironment.envVariable}, + + + image = #{devEnvironment.image}, + + + dataset = #{devEnvironment.dataset}, + + + model = #{devEnvironment.model}, + + + alt_field1 = #{devEnvironment.altField1}, + + + alt_field2 = #{devEnvironment.altField2}, + + + create_by = #{devEnvironment.createBy}, + + + create_time = #{devEnvironment.createTime}, + + + update_by = #{devEnvironment.updateBy}, + + + update_time = #{devEnvironment.updateTime}, + + + state = #{devEnvironment.state}, + + + where id = #{devEnvironment.id} + + + + + + delete from dev_environment where id = #{id} + + + + diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ModelDependencyDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ModelDependencyDaoMapper.xml new file mode 100644 index 00000000..939df96e --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ModelDependencyDaoMapper.xml @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into model_dependency(current_model_id, exp_ins_id, parent_models, ref_item, train_task, train_dataset, train_params, train_image, test_dataset, project_dependency, version, create_by, create_time, update_by, update_time, state) + values (#{currentModelId}, #{expInsId}, #{parentModels}, #{refItem}, #{trainTask}, #{trainDataset}, #{trainParams}, #{trainImage}, #{testDataset}, #{projectDependency}, #{version}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{state}) + + + + insert into model_dependency(current_model_id, exp_ins_id, parent_models, ref_item, train_task, train_dataset, + train_params, train_image, test_dataset, project_dependency, version, create_by, create_time, update_by, + update_time, state) + values + + (#{entity.currentModelId}, #{entity.expInsId}, #{entity.parentModels}, #{entity.refItem}, + #{entity.trainTask}, #{entity.trainDataset}, #{entity.trainParams}, #{entity.trainImage}, + #{entity.testDataset}, #{entity.projectDependency}, #{entity.version}, #{entity.createBy}, + #{entity.createTime}, #{entity.updateBy}, #{entity.updateTime}, #{entity.state}) + + + + + insert into model_dependency(current_model_id, exp_ins_id, parent_models, ref_item, train_task, train_dataset, + train_params, train_image, test_dataset, project_dependency, version, create_by, create_time, update_by, + update_time, state) + values + + (#{entity.currentModelId}, #{entity.expInsId}, #{entity.parentModels}, #{entity.refItem}, + #{entity.trainTask}, #{entity.trainDataset}, #{entity.trainParams}, #{entity.trainImage}, + #{entity.testDataset}, #{entity.projectDependency}, #{entity.version}, #{entity.createBy}, + #{entity.createTime}, #{entity.updateBy}, #{entity.updateTime}, #{entity.state}) + + on duplicate key update + current_model_id = values(current_model_id), + exp_ins_id = values(exp_ins_id), + parent_models = values(parent_models), + ref_item = values(ref_item), + train_task = values(train_task), + train_dataset = values(train_dataset), + train_params = values(train_params), + train_image = values(train_image), + test_dataset = values(test_dataset), + project_dependency = values(project_dependency), + version = values(version), + create_by = values(create_by), + create_time = values(create_time), + update_by = values(update_by), + update_time = values(update_time), + state = values(state) + + + + + update model_dependency + + + current_model_id = #{currentModelId}, + + + exp_ins_id = #{expInsId}, + + + parent_models = #{parentModels}, + + + ref_item = #{refItem}, + + + train_task = #{trainTask}, + + + train_dataset = #{trainDataset}, + + + train_params = #{trainParams}, + + + train_image = #{trainImage}, + + + test_dataset = #{testDataset}, + + + project_dependency = #{projectDependency}, + + + version = #{version}, + + + create_by = #{createBy}, + + + create_time = #{createTime}, + + + update_by = #{updateBy}, + + + update_time = #{updateTime}, + + + state = #{state}, + + + where id = #{id} and state = 1 + + + + + delete from model_dependency where id = #{id} + + + +