| @@ -20,13 +20,13 @@ export default { | |||
| // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | |||
| '/api/': { | |||
| // 要代理的地址 | |||
| target: 'http://172.20.32.181:31213', // 开发环境 | |||
| // target: 'http://172.20.32.98:8082', | |||
| // target: 'http://172.20.32.181:31213', // 开发环境 | |||
| target: 'http://172.168.15.80:8082', | |||
| // target: 'http://172.20.32.150:8082', | |||
| // 配置了这个可以从 http 代理到 https | |||
| // 依赖 origin 的功能可能需要这个,比如 cookie | |||
| changeOrigin: true, | |||
| // pathRewrite: { '^/api': '' }, | |||
| pathRewrite: { '^/api': '' }, | |||
| }, | |||
| '/profile/avatar/': { | |||
| target: 'http://172.20.32.185:31213', | |||
| @@ -181,7 +181,7 @@ export default [ | |||
| }, | |||
| { | |||
| name: '实验实例详情', | |||
| path: 'instance/:autoMLId/:id', | |||
| path: 'instance/:experimentId/:id', | |||
| component: './AutoML/Instance/index', | |||
| }, | |||
| ], | |||
| @@ -217,11 +217,47 @@ export default [ | |||
| }, | |||
| { | |||
| name: '实验实例详情', | |||
| path: 'instance/:autoMLId/:id', | |||
| path: 'instance/:experimentId/:id', | |||
| component: './HyperParameter/Instance/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '主动学习', | |||
| path: 'active-learn', | |||
| routes: [ | |||
| { | |||
| name: '超参数寻优', | |||
| path: '', | |||
| component: './ActiveLearn/List/index', | |||
| }, | |||
| { | |||
| name: '实验详情', | |||
| path: 'info/:id', | |||
| component: './ActiveLearn/Info/index', | |||
| }, | |||
| { | |||
| name: '创建实验', | |||
| path: 'create', | |||
| component: './ActiveLearn/Create/index', | |||
| }, | |||
| { | |||
| name: '编辑实验', | |||
| path: 'edit/:id', | |||
| component: './ActiveLearn/Create/index', | |||
| }, | |||
| { | |||
| name: '复制实验', | |||
| path: 'copy/:id', | |||
| component: './ActiveLearn/Create/index', | |||
| }, | |||
| { | |||
| name: '实验实例详情', | |||
| path: 'instance/:experimentId/:id', | |||
| component: './ActiveLearn/Instance/index', | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -525,6 +561,18 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '知识图谱', | |||
| path: '/knowledge', | |||
| routes: [ | |||
| { | |||
| name: '知识图谱', | |||
| path: '', | |||
| key: 'knowledge', | |||
| component: './Knowledge/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| path: '*', | |||
| layout: false, | |||
| @@ -140,7 +140,7 @@ | |||
| "umi-presets-pro": "^2.0.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">=16.14.0" | |||
| "node": ">=18.18.0" | |||
| }, | |||
| "create-umi": { | |||
| "ignoreScript": [ | |||
| @@ -25,7 +25,7 @@ export { requestConfig as request } from './requestConfig'; | |||
| /** | |||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | |||
| * */ | |||
| */ | |||
| export async function getInitialState(): Promise<GlobalInitialState> { | |||
| const fetchUserInfo = async () => { | |||
| try { | |||
| @@ -15,6 +15,7 @@ export enum IframePageType { | |||
| DevEnv = 'DevEnv', // 开发环境 | |||
| GitLink = 'GitLink', // git link | |||
| Aim = 'Aim', // 实验对比 | |||
| Knowledge = 'Knowledge', // 知识图谱 | |||
| } | |||
| const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| @@ -37,6 +38,10 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| code: 200, | |||
| data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '', | |||
| }); | |||
| case IframePageType.Knowledge: { // 知识图谱 | |||
| const { origin } = location; | |||
| return () => Promise.resolve({ code: 200, data: `http://${origin}:32701` }); | |||
| } | |||
| } | |||
| }; | |||
| @@ -58,9 +63,12 @@ function IframePage({ type, openInTab = false, className, style }: IframePagePro | |||
| useEffect(() => { | |||
| const requestIframeUrl = async () => { | |||
| setLoading(true); | |||
| const [res] = await to(getRequestAPI(type)()); | |||
| if (res && res.data) { | |||
| setIframeUrl(res.data); | |||
| } else { | |||
| setLoading(false); | |||
| } | |||
| }; | |||
| @@ -4,7 +4,7 @@ | |||
| right: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| z-index: 1001; | |||
| z-index: 1001; // 设置大于 Modal 的 z-index | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @@ -7,4 +7,25 @@ | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| &__tips { | |||
| position: relative; | |||
| margin-left: 18px; | |||
| padding: 3px 15px; | |||
| color: @primary-color; | |||
| background: .addAlpha(@primary-color, 0.1) []; | |||
| border-radius: 4px; | |||
| &::before { | |||
| position: absolute; | |||
| top: 10px; | |||
| left: -6px; | |||
| width: 0; | |||
| height: 0; | |||
| border-top: 4px solid transparent; | |||
| border-right: 6px solid .addAlpha(@primary-color, 0.1) []; | |||
| border-bottom: 4px solid transparent; | |||
| content: ''; | |||
| } | |||
| } | |||
| } | |||
| @@ -3,6 +3,7 @@ | |||
| * @Date: 2024-04-17 14:01:46 | |||
| * @Description: 页面标题 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import classNames from 'classnames'; | |||
| import React from 'react'; | |||
| import './index.less'; | |||
| @@ -10,19 +11,29 @@ import './index.less'; | |||
| type PageTitleProps = { | |||
| /** 标题 */ | |||
| title: React.ReactNode; | |||
| /** 图标 */ | |||
| tooltip?: string; | |||
| /** 自定义类名 */ | |||
| className?: string; | |||
| /** 自定义样式 */ | |||
| style?: React.CSSProperties; | |||
| /** 自定义标题 */ | |||
| titleRender?: () => React.ReactNode; | |||
| }; | |||
| /** | |||
| * 页面标题 | |||
| */ | |||
| function PageTitle({ title, style, className = '' }: PageTitleProps) { | |||
| function PageTitle({ title, style, className, tooltip }: PageTitleProps) { | |||
| return ( | |||
| <div className={classNames('kf-page-title', className)} style={style}> | |||
| {title} | |||
| <div>{title}</div> | |||
| {tooltip && ( | |||
| <div className="kf-page-title__tips"> | |||
| <KFIcon type="icon-tishi" font={14} /> | |||
| <span style={{ marginLeft: '8px', fontSize: '14px' }}>{tooltip}</span> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -26,6 +26,12 @@ | |||
| border: 1px solid rgba(22, 100, 255, 0.3); | |||
| border-radius: 8px; | |||
| :global { | |||
| .ant-tree-list-holder { | |||
| overflow-x: hidden; | |||
| } | |||
| } | |||
| &__search { | |||
| margin-bottom: 14px; | |||
| padding-left: 0; | |||
| @@ -2,6 +2,7 @@ import { clearSessionToken } from '@/access'; | |||
| import { setRemoteMenu } from '@/services/session'; | |||
| import { logout } from '@/services/system/auth'; | |||
| import { ClientInfo } from '@/types'; | |||
| import { sleep } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { gotoLoginPage, oauthLogout } from '@/utils/ui'; | |||
| import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | |||
| @@ -63,7 +64,8 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||
| */ | |||
| const loginOut = async () => { | |||
| oauthLogout('http://172.20.32.197:31209/oauth/logout'); | |||
| await logout(); | |||
| // 至少 1 秒后跳转,希望子系统能完成注销 | |||
| await Promise.all([logout(), sleep(1000)]); | |||
| clearSessionToken(); | |||
| setRemoteMenu(null); | |||
| gotoLoginPage(); | |||
| @@ -0,0 +1,55 @@ | |||
| .create-hyperparameter { | |||
| height: 100%; | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 30px 30px 10px; | |||
| overflow: auto; | |||
| color: @text-color; | |||
| font-size: @font-size-content; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__type { | |||
| color: @text-color; | |||
| font-size: @font-size-input-lg; | |||
| } | |||
| :global { | |||
| .ant-input-number { | |||
| width: 100%; | |||
| } | |||
| .ant-form-item { | |||
| margin-bottom: 20px; | |||
| } | |||
| .image-url { | |||
| margin-top: -15px; | |||
| .ant-form-item-label > label::after { | |||
| content: ''; | |||
| } | |||
| } | |||
| .ant-btn-variant-text:disabled { | |||
| color: @text-disabled-color; | |||
| } | |||
| .ant-btn-variant-text { | |||
| color: #565658; | |||
| } | |||
| .ant-btn.ant-btn-icon-only .anticon { | |||
| font-size: 20px; | |||
| } | |||
| .anticon-question-circle { | |||
| margin-top: -12px; | |||
| margin-left: 1px !important; | |||
| color: @text-color-tertiary !important; | |||
| font-size: 12px !important; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,142 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 创建实验 | |||
| */ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { AutoMLTaskType } from '@/enums'; | |||
| import { | |||
| addActiveLearnReq, | |||
| getActiveLearnInfoReq, | |||
| updateActiveLearnReq, | |||
| } from '@/services/activeLearn'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useLocation, useNavigate, useParams } from '@umijs/max'; | |||
| import { App, Button, Form } from 'antd'; | |||
| import { useEffect } from 'react'; | |||
| import BasicConfig from '../components/CreateForm/BasicConfig'; | |||
| import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; | |||
| import { ActiveLearnData, FormData } from '../types'; | |||
| import styles from './index.less'; | |||
| function CreateActiveLearn() { | |||
| const navigate = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const { message } = App.useApp(); | |||
| const params = useParams(); | |||
| const id = safeInvoke(Number)(params.id); | |||
| const { pathname } = useLocation(); | |||
| const isCopy = pathname.includes('copy'); | |||
| useEffect(() => { | |||
| // 编辑,复制 | |||
| if (id && !Number.isNaN(id)) { | |||
| getActiveLearnInfo(id); | |||
| } | |||
| }, [id]); | |||
| // 获取服务详情 | |||
| const getActiveLearnInfo = async (id: number) => { | |||
| const [res] = await to(getActiveLearnInfoReq({ id })); | |||
| if (res && res.data) { | |||
| const info: ActiveLearnData = res.data; | |||
| const { name: name_str, ...rest } = info; | |||
| const name = isCopy ? `${name_str}-copy` : name_str; | |||
| const formData = { | |||
| ...rest, | |||
| name, | |||
| }; | |||
| form.setFieldsValue(formData); | |||
| } | |||
| }; | |||
| // 创建、更新、复制实验 | |||
| const createExperiment = async (formData: FormData) => { | |||
| // 根据后台要求,修改表单数据 | |||
| const object = { | |||
| ...formData, | |||
| }; | |||
| const params = | |||
| id && !isCopy | |||
| ? { | |||
| id: id, | |||
| ...object, | |||
| } | |||
| : object; | |||
| const request = id && !isCopy ? updateActiveLearnReq : addActiveLearnReq; | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| navigate(-1); | |||
| } | |||
| }; | |||
| // 提交 | |||
| const handleSubmit = (values: FormData) => { | |||
| createExperiment(values); | |||
| }; | |||
| // 取消 | |||
| const cancel = () => { | |||
| navigate(-1); | |||
| }; | |||
| let buttonText = '新建'; | |||
| let title = '新建实验'; | |||
| if (id) { | |||
| if (isCopy) { | |||
| title = '复制实验'; | |||
| buttonText = '确定'; | |||
| } else { | |||
| title = '编辑实验'; | |||
| buttonText = '更新'; | |||
| } | |||
| } | |||
| return ( | |||
| <div className={styles['create-hyperparameter']}> | |||
| <PageTitle title={title}></PageTitle> | |||
| <div className={styles['create-hyperparameter__content']}> | |||
| <div> | |||
| <Form | |||
| name="create-active-learn" | |||
| labelCol={{ flex: '160px' }} | |||
| labelAlign="left" | |||
| form={form} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| autoComplete="off" | |||
| scrollToFirstError | |||
| initialValues={{ | |||
| task_type: AutoMLTaskType.Classification, | |||
| shuffle: false, | |||
| }} | |||
| > | |||
| <BasicConfig /> | |||
| <ExecuteConfig /> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }} style={{ marginTop: '40px' }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| {buttonText} | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| htmlType="button" | |||
| onClick={cancel} | |||
| style={{ marginLeft: '20px' }} | |||
| > | |||
| 取消 | |||
| </Button> | |||
| </Form.Item> | |||
| </Form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default CreateActiveLearn; | |||
| @@ -0,0 +1,40 @@ | |||
| .auto-ml-info { | |||
| position: relative; | |||
| height: 100%; | |||
| &__tabs { | |||
| height: 50px; | |||
| padding-left: 25px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| } | |||
| &__tips { | |||
| position: absolute; | |||
| top: 11px; | |||
| left: 256px; | |||
| padding: 3px 12px; | |||
| color: #565658; | |||
| font-size: @font-size-content; | |||
| background: .addAlpha(@primary-color, 0.09) []; | |||
| border-radius: 4px; | |||
| &::before { | |||
| position: absolute; | |||
| top: 10px; | |||
| left: -6px; | |||
| width: 0; | |||
| height: 0; | |||
| border-top: 4px solid transparent; | |||
| border-right: 6px solid .addAlpha(@primary-color, 0.09) []; | |||
| border-bottom: 4px solid transparent; | |||
| content: ''; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 主动学习实验详情 | |||
| */ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { getActiveLearnInfoReq } from '@/services/activeLearn'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useParams } from '@umijs/max'; | |||
| import { useEffect, useState } from 'react'; | |||
| import ActiveLearnBasic from '../components/ActiveLearnBasic'; | |||
| import { ActiveLearnData } from '../types'; | |||
| import styles from './index.less'; | |||
| function ActiveLearnInfo() { | |||
| const params = useParams(); | |||
| const id = safeInvoke(Number)(params.id); | |||
| const [info, setInfo] = useState<ActiveLearnData | undefined>(undefined); | |||
| useEffect(() => { | |||
| if (id) { | |||
| getActiveLearnInfo(); | |||
| } | |||
| }, []); | |||
| // 获取详情 | |||
| const getActiveLearnInfo = async () => { | |||
| const [res] = await to(getActiveLearnInfoReq({ id: id })); | |||
| if (res && res.data) { | |||
| setInfo(res.data); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles['auto-ml-info']}> | |||
| <PageTitle title="实验详情"></PageTitle> | |||
| <div className={styles['auto-ml-info__content']}> | |||
| <ActiveLearnBasic info={info} /> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ActiveLearnInfo; | |||
| @@ -0,0 +1,42 @@ | |||
| .active-learn-instance { | |||
| height: 100%; | |||
| &__tabs { | |||
| height: 100%; | |||
| :global { | |||
| .ant-tabs-nav-list { | |||
| width: 100%; | |||
| height: 50px; | |||
| padding-left: 15px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| .ant-tabs-content-holder { | |||
| height: calc(100% - 50px); | |||
| .ant-tabs-content { | |||
| height: 100%; | |||
| .ant-tabs-tabpane { | |||
| height: 100%; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &__basic { | |||
| height: calc(100% - 10px); | |||
| margin-top: 10px; | |||
| } | |||
| &__log { | |||
| height: calc(100% - 10px); | |||
| margin-top: 10px; | |||
| padding: 20px calc(@content-padding - 8px); | |||
| overflow-y: visible; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| } | |||
| } | |||
| @@ -0,0 +1,194 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { getActiveLearnInsReq } from '@/services/activeLearn'; | |||
| import { NodeStatus } from '@/types'; | |||
| import { parseJsonText } from '@/utils'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useParams } from '@umijs/max'; | |||
| import { Tabs } from 'antd'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import ActiveLearnBasic from '../components/ActiveLearnBasic'; | |||
| import ExperimentHistory from '../components/ExperimentHistory'; | |||
| import ExperimentLog from '../components/ExperimentLog'; | |||
| import ExperimentResult from '../components/ExperimentResult'; | |||
| import { ActiveLearnData, ActiveLearnInstanceData } from '../types'; | |||
| import styles from './index.less'; | |||
| enum TabKeys { | |||
| Params = 'params', | |||
| Log = 'log', | |||
| Result = 'result', | |||
| History = 'history', | |||
| } | |||
| const NodePrefix = 'workflow'; | |||
| function ActiveLearnInstance() { | |||
| const [experimentInfo, setExperimentInfo] = useState<ActiveLearnData | undefined>(undefined); | |||
| const [instanceInfo, setInstanceInfo] = useState<ActiveLearnInstanceData | undefined>(undefined); | |||
| // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态 | |||
| const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | |||
| const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined); | |||
| const params = useParams(); | |||
| const instanceId = safeInvoke(Number)(params.id); | |||
| const evtSourceRef = useRef<EventSource | null>(null); | |||
| useEffect(() => { | |||
| if (instanceId) { | |||
| getExperimentInsInfo(false); | |||
| } | |||
| return () => { | |||
| closeSSE(); | |||
| }; | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, [instanceId]); | |||
| // 获取实验实例详情 | |||
| const getExperimentInsInfo = async (isStatusDetermined: boolean) => { | |||
| const [res] = await to(getActiveLearnInsReq(instanceId)); | |||
| if (res && res.data) { | |||
| const info = res.data as ActiveLearnInstanceData; | |||
| const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; | |||
| // 解析配置参数 | |||
| const paramJson = parseJsonText(param); | |||
| if (paramJson) { | |||
| setExperimentInfo(paramJson.data); | |||
| } | |||
| setInstanceInfo(info); | |||
| // 这个接口返回的状态有延时,SSE 返回的状态是最新的 | |||
| // SSE 调用时,不需要解析 node_status,也不要重新建立 SSE | |||
| if (isStatusDetermined) { | |||
| return; | |||
| } | |||
| // 进行节点状态 | |||
| const nodeStatusJson = parseJsonText(node_status); | |||
| if (nodeStatusJson) { | |||
| setNodes(nodeStatusJson); | |||
| Object.keys(nodeStatusJson).some((key) => { | |||
| if (key.startsWith(NodePrefix)) { | |||
| const workflowStatus = nodeStatusJson[key]; | |||
| setWorkflowStatus(workflowStatus); | |||
| return true; | |||
| } | |||
| return false; | |||
| }); | |||
| } | |||
| // 运行中或者等待中,开启 SSE | |||
| if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { | |||
| setupSSE(argo_ins_name, argo_ins_ns); | |||
| } | |||
| } | |||
| }; | |||
| const setupSSE = (name: string, namespace: string) => { | |||
| const { origin } = location; | |||
| const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | |||
| const evtSource = new EventSource( | |||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | |||
| { withCredentials: false }, | |||
| ); | |||
| evtSource.onmessage = (event) => { | |||
| const data = event?.data; | |||
| if (!data) { | |||
| return; | |||
| } | |||
| const dataJson = parseJsonText(data); | |||
| if (dataJson) { | |||
| const nodes = dataJson?.result?.object?.status?.nodes; | |||
| if (nodes) { | |||
| const workflowStatus = Object.values(nodes).find((node: any) => | |||
| node.displayName.startsWith(NodePrefix), | |||
| ) as NodeStatus; | |||
| // 节点 | |||
| setNodes(nodes); | |||
| // 设置工作流状态 | |||
| if (workflowStatus) { | |||
| setWorkflowStatus(workflowStatus); | |||
| // 实验结束,关闭 SSE | |||
| if ( | |||
| workflowStatus.phase !== ExperimentStatus.Pending && | |||
| workflowStatus.phase !== ExperimentStatus.Running | |||
| ) { | |||
| closeSSE(); | |||
| getExperimentInsInfo(true); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| evtSource.onerror = (error) => { | |||
| console.error('SSE error: ', error); | |||
| }; | |||
| evtSourceRef.current = evtSource; | |||
| }; | |||
| const closeSSE = () => { | |||
| if (evtSourceRef.current) { | |||
| evtSourceRef.current.close(); | |||
| evtSourceRef.current = null; | |||
| } | |||
| }; | |||
| const basicTabItems = [ | |||
| { | |||
| key: TabKeys.Params, | |||
| label: '基本信息', | |||
| icon: <KFIcon type="icon-jibenxinxi" />, | |||
| children: ( | |||
| <ActiveLearnBasic | |||
| className={styles['active-learn-instance__basic']} | |||
| info={experimentInfo} | |||
| runStatus={workflowStatus} | |||
| isInstance | |||
| /> | |||
| ), | |||
| }, | |||
| { | |||
| key: TabKeys.Log, | |||
| label: '日志', | |||
| icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['active-learn-instance__log']}> | |||
| {instanceInfo && nodes && <ExperimentLog instanceInfo={instanceInfo} nodes={nodes} />} | |||
| </div> | |||
| ), | |||
| }, | |||
| ]; | |||
| const resultTabItems = [ | |||
| { | |||
| key: TabKeys.Result, | |||
| label: '实验结果', | |||
| icon: <KFIcon type="icon-shiyanjieguo1" />, | |||
| children: <ExperimentResult fileUrl={instanceInfo?.result_txt} />, | |||
| }, | |||
| { | |||
| key: TabKeys.History, | |||
| label: '寻优列表', | |||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||
| children: <ExperimentHistory trialList={instanceInfo?.trial_list ?? []} />, | |||
| }, | |||
| ]; | |||
| const tabItems = | |||
| instanceInfo?.status === ExperimentStatus.Succeeded | |||
| ? [...basicTabItems, ...resultTabItems] | |||
| : basicTabItems; | |||
| return ( | |||
| <div className={styles['active-learn-instance']}> | |||
| <Tabs className={styles['active-learn-instance__tabs']} items={tabItems} /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ActiveLearnInstance; | |||
| @@ -0,0 +1,13 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 超参数自动寻优 | |||
| */ | |||
| import ExperimentList, { ExperimentListType } from '@/pages/AutoML/components/ExperimentList'; | |||
| function ActiveLearn() { | |||
| return <ExperimentList type={ExperimentListType.ActiveLearn} />; | |||
| } | |||
| export default ActiveLearn; | |||
| @@ -0,0 +1,13 @@ | |||
| .active-learn-basic { | |||
| height: 100%; | |||
| padding: 20px @content-padding; | |||
| overflow-y: auto; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| :global { | |||
| .kf-basic-info__item__value__text { | |||
| white-space: pre; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,278 @@ | |||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | |||
| import { AutoMLTaskType, autoMLTaskTypeOptions } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { | |||
| classifierAlgorithms, | |||
| FrameworkType, | |||
| frameworkTypeOptions, | |||
| queryStrategies, | |||
| regressorAlgorithms, | |||
| } from '@/pages/ActiveLearn/components/CreateForm/utils'; | |||
| import { ActiveLearnData } from '@/pages/ActiveLearn/types'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { type NodeStatus } from '@/types'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { | |||
| formatBoolean, | |||
| formatCodeConfig, | |||
| formatDataset, | |||
| formatDate, | |||
| formatEnum, | |||
| formatMirror, | |||
| formatModel, | |||
| } from '@/utils/format'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useMemo } from 'react'; | |||
| import styles from './index.less'; | |||
| type BasicInfoProps = { | |||
| info?: ActiveLearnData; | |||
| className?: string; | |||
| isInstance?: boolean; | |||
| runStatus?: NodeStatus; | |||
| }; | |||
| function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfoProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| } | |||
| return [ | |||
| { | |||
| label: '实验名称', | |||
| value: info.name, | |||
| }, | |||
| { | |||
| label: '实验描述', | |||
| value: info.description, | |||
| }, | |||
| { | |||
| label: '创建人', | |||
| value: info.create_by, | |||
| }, | |||
| { | |||
| label: '创建时间', | |||
| value: info.create_time, | |||
| format: formatDate, | |||
| }, | |||
| { | |||
| label: '更新时间', | |||
| value: info.update_time, | |||
| format: formatDate, | |||
| }, | |||
| ]; | |||
| }, [info]); | |||
| const configDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| } | |||
| const modelInfo = [ | |||
| { | |||
| label: '预训练模型', | |||
| value: info.model, | |||
| format: formatModel, | |||
| }, | |||
| { | |||
| label: '模型文件路径', | |||
| value: info.model_py, | |||
| }, | |||
| { | |||
| label: '模型类名称', | |||
| value: info.model_class_name, | |||
| }, | |||
| { | |||
| label: 'epochs', | |||
| value: info.epochs, | |||
| }, | |||
| ]; | |||
| const lossInfo = [ | |||
| { | |||
| label: 'loss文件路径', | |||
| value: info.loss_py, | |||
| }, | |||
| { | |||
| label: 'loss类名', | |||
| value: info.loss_class_name, | |||
| }, | |||
| { | |||
| label: '学习率', | |||
| value: info.lr, | |||
| }, | |||
| ]; | |||
| const algorithmInfo = [ | |||
| { | |||
| label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', | |||
| value: | |||
| info.task_type === AutoMLTaskType.Regression ? info.regressor_alg : info.classifier_alg, | |||
| format: formatEnum( | |||
| info.task_type === AutoMLTaskType.Regression ? regressorAlgorithms : classifierAlgorithms, | |||
| ), | |||
| }, | |||
| ]; | |||
| const diffInfo = | |||
| info.framework_type === FrameworkType.Pytorch | |||
| ? [...modelInfo, ...lossInfo] | |||
| : info.framework_type === FrameworkType.Keras | |||
| ? modelInfo | |||
| : algorithmInfo; | |||
| return [ | |||
| { | |||
| label: '任务类型', | |||
| value: info.task_type, | |||
| format: formatEnum(autoMLTaskTypeOptions), | |||
| }, | |||
| { | |||
| label: '框架类型', | |||
| value: info.framework_type, | |||
| format: formatEnum(frameworkTypeOptions), | |||
| }, | |||
| ...diffInfo, | |||
| { | |||
| label: '代码配置', | |||
| value: info.code_config, | |||
| format: formatCodeConfig, | |||
| }, | |||
| { | |||
| label: '数据集', | |||
| value: info.dataset, | |||
| format: formatDataset, | |||
| }, | |||
| { | |||
| label: '数据集处理文件路径', | |||
| value: info.dataset_py, | |||
| }, | |||
| { | |||
| label: '数据集类名', | |||
| value: info.dataset_class_name, | |||
| }, | |||
| { | |||
| label: '镜像', | |||
| value: info.image, | |||
| format: formatMirror, | |||
| }, | |||
| { | |||
| label: '资源规格', | |||
| value: info.computing_resource_id, | |||
| format: getResourceDescription, | |||
| }, | |||
| { | |||
| label: '是否打乱', | |||
| value: info.shuffle, | |||
| format: formatBoolean, | |||
| }, | |||
| { | |||
| label: '数据量', | |||
| value: info.data_size, | |||
| }, | |||
| { | |||
| label: '训练集数据量', | |||
| value: info.train_size, | |||
| }, | |||
| { | |||
| label: '初始训练数据量', | |||
| value: info.initial_num, | |||
| }, | |||
| { | |||
| label: '查询次数', | |||
| value: info.queries_num, | |||
| }, | |||
| { | |||
| label: '每次查询数据量', | |||
| value: info.instances_num, | |||
| }, | |||
| { | |||
| label: '查询策略', | |||
| value: info.query_strategy, | |||
| format: formatEnum(queryStrategies), | |||
| }, | |||
| { | |||
| label: '检查点轮数', | |||
| value: info.checkpoint_num, | |||
| }, | |||
| { | |||
| label: 'batch_size', | |||
| value: info.batch_size, | |||
| }, | |||
| ]; | |||
| }, [info, getResourceDescription]); | |||
| const instanceDatas = useMemo(() => { | |||
| if (!runStatus) { | |||
| return []; | |||
| } | |||
| return [ | |||
| { | |||
| label: '启动时间', | |||
| value: formatDate(runStatus.startedAt), | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '执行时长', | |||
| value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '状态', | |||
| value: ( | |||
| <Flex align="center"> | |||
| <img | |||
| style={{ width: '17px', marginRight: '7px' }} | |||
| src={experimentStatusInfo[runStatus.phase]?.icon} | |||
| draggable={false} | |||
| alt="" | |||
| /> | |||
| <div | |||
| style={{ | |||
| color: experimentStatusInfo[runStatus?.phase]?.color, | |||
| fontSize: '15px', | |||
| lineHeight: 1.6, | |||
| }} | |||
| > | |||
| {experimentStatusInfo[runStatus?.phase]?.label} | |||
| </div> | |||
| </Flex> | |||
| ), | |||
| ellipsis: true, | |||
| }, | |||
| ]; | |||
| }, [runStatus]); | |||
| return ( | |||
| <div className={classNames(styles['active-learn-basic'], className)}> | |||
| {isInstance && runStatus && ( | |||
| <ConfigInfo | |||
| title="运行信息" | |||
| datas={instanceDatas} | |||
| labelWidth={70} | |||
| style={{ marginBottom: '20px' }} | |||
| /> | |||
| )} | |||
| {!isInstance && ( | |||
| <ConfigInfo | |||
| title="基本信息" | |||
| datas={basicDatas} | |||
| labelWidth={70} | |||
| style={{ marginBottom: '20px' }} | |||
| /> | |||
| )} | |||
| <ConfigInfo | |||
| title="配置信息" | |||
| datas={configDatas} | |||
| labelWidth={120} | |||
| style={{ marginBottom: '20px' }} | |||
| ></ConfigInfo> | |||
| </div> | |||
| ); | |||
| } | |||
| export default BasicInfo; | |||
| @@ -0,0 +1,54 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { Col, Form, Input, Row } from 'antd'; | |||
| function BasicConfig() { | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="实验名称" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入实验名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入实验名称" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={20}> | |||
| <Form.Item | |||
| label="实验描述" | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入实验描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| placeholder="请输入实验描述" | |||
| maxLength={256} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| export default BasicConfig; | |||
| @@ -0,0 +1,518 @@ | |||
| import CodeSelect from '@/components/CodeSelect'; | |||
| import ParameterSelect from '@/components/ParameterSelect'; | |||
| import ResourceSelect, { | |||
| requiredValidator, | |||
| ResourceSelectorType, | |||
| } from '@/components/ResourceSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { AutoMLTaskType, autoMLTaskTypeOptions } from '@/enums'; | |||
| import { Col, Form, Input, InputNumber, Radio, Row, Select, Switch } from 'antd'; | |||
| import { | |||
| classifierAlgorithms, | |||
| FrameworkType, | |||
| frameworkTypeOptions, | |||
| queryStrategies, | |||
| regressorAlgorithms, | |||
| } from './utils'; | |||
| function ExecuteConfig() { | |||
| const form = Form.useFormInstance(); | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| title="配置信息" | |||
| image={require('@/assets/img/model-deployment.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="任务类型" | |||
| name="task_type" | |||
| rules={[{ required: true, message: '请选择任务类型' }]} | |||
| > | |||
| <Radio.Group | |||
| options={autoMLTaskTypeOptions} | |||
| onChange={() => form.resetFields(['metrics'])} | |||
| ></Radio.Group> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="框架类型" | |||
| name="framework_type" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择框架类型', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| placeholder="请选择框架类型" | |||
| options={frameworkTypeOptions} | |||
| showSearch | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Form.Item dependencies={['task_type', 'framework_type']} noStyle> | |||
| {({ getFieldValue }) => { | |||
| const taskType = getFieldValue('task_type'); | |||
| const frameworkType = getFieldValue('framework_type'); | |||
| if (frameworkType === FrameworkType.Keras || frameworkType === FrameworkType.Pytorch) { | |||
| return ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="预训练模型" name="model"> | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Model} | |||
| placeholder="请选择模型" | |||
| canInput={false} | |||
| size="large" | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="模型文件路径" | |||
| name="model_py" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入模型文件路径', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入模型文件路径" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="模型类名称" | |||
| name="model_class_name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入模型类名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入模型类名称" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="epochs" | |||
| name="epochs" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入epochs', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入epochs" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| {frameworkType === FrameworkType.Pytorch ? ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="loss 文件路径" | |||
| name="loss_py" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入 loss 文件路径', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入 loss 文件路径" | |||
| maxLength={64} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="loss 类名" | |||
| name="loss_class_name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入 loss 类名', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入 loss 类名" | |||
| maxLength={64} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="学习率" | |||
| name="lr" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入学习率', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入学习率" min={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ) : null} | |||
| </> | |||
| ); | |||
| } else if (frameworkType === FrameworkType.Sklearn) { | |||
| if (taskType === AutoMLTaskType.Classification) { | |||
| return ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="分类算法" | |||
| name="classifier_alg" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择分类算法', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| placeholder="请选择分类算法" | |||
| options={classifierAlgorithms} | |||
| showSearch | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } else { | |||
| return ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="回归算法" | |||
| name="regressor_alg" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择回归算法', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| placeholder="请选择回归算法" | |||
| options={regressorAlgorithms} | |||
| showSearch | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| } else { | |||
| return null; | |||
| } | |||
| }} | |||
| </Form.Item> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="代码配置" | |||
| name="code_config" | |||
| rules={[ | |||
| { | |||
| validator: requiredValidator, | |||
| message: '请选择代码配置', | |||
| }, | |||
| ]} | |||
| required | |||
| > | |||
| <CodeSelect placeholder="请选择代码配置" canInput={false} size="large" /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="数据集" | |||
| name="dataset" | |||
| rules={[ | |||
| { | |||
| validator: requiredValidator, | |||
| message: '请选择数据集', | |||
| }, | |||
| ]} | |||
| required | |||
| > | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Dataset} | |||
| placeholder="请选择数据集" | |||
| canInput={false} | |||
| size="large" | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="数据集处理文件路径" | |||
| name="dataset_py" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入数据集处理文件路径', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入数据集处理文件路径" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="数据集类名" | |||
| name="dataset_class_name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入数据集类名', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入数据集类名" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="数据量" | |||
| name="data_size" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入数据量', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入数据量" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| validator: requiredValidator, | |||
| message: '请选择镜像', | |||
| }, | |||
| ]} | |||
| required | |||
| > | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Mirror} | |||
| placeholder="请选择镜像" | |||
| canInput={false} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="computing_resource_id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterSelect dataType="resource" placeholder="请选择资源规格" /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="是否随机打乱" name="shuffle" valuePropName="checked"> | |||
| <Switch /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="训练集数据量" | |||
| name="train_size" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入训练集数据量', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入训练集数据量" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="初始训练数据量" | |||
| name="initial_num" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入初始训练数据量', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入初始训练数据量" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="查询次数" | |||
| name="queries_num" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入查询次数量', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入查询次数" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="每次查询数据量" | |||
| name="instances_num" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入每次查询数据量', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入每次查询数据量" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="查询策略" | |||
| name="query_strategy" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择查询策略', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select placeholder="请选择查询策略" options={queryStrategies} showSearch allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="检查点轮数" | |||
| name="checkpoint_num" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入检查点轮数', | |||
| }, | |||
| ]} | |||
| tooltip="多少轮查询保存一次模型参数" | |||
| > | |||
| <InputNumber placeholder="请输入检查点轮数" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="batch_size" | |||
| name="batch_size" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入 batch_size', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入 batch_size" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| export default ExecuteConfig; | |||
| @@ -0,0 +1,92 @@ | |||
| // 分类算法 | |||
| export const classifierAlgorithms = [ | |||
| { | |||
| label: 'logistic_regression(逻辑回归)', | |||
| value: 'logistic_regression', | |||
| }, | |||
| { | |||
| label: 'decision_tree(决策树)', | |||
| value: 'decision_tree', | |||
| }, | |||
| { | |||
| label: 'random_forest(随机森林)', | |||
| value: 'random_forest', | |||
| }, | |||
| { | |||
| label: 'SVM(支持向量机)', | |||
| value: 'SVM', | |||
| }, | |||
| { | |||
| label: 'naive_bayes(朴素贝叶斯)', | |||
| value: 'naive_bayes', | |||
| }, | |||
| { | |||
| label: 'GBM(梯度提升树)', | |||
| value: 'GBM', | |||
| }, | |||
| ]; | |||
| // 回归算法 | |||
| export const regressorAlgorithms = [ | |||
| { | |||
| label: 'bayesian_ridge(岭回归)', | |||
| value: 'bayesian_ridge', | |||
| }, | |||
| { | |||
| label: 'ARD_regression(自动相关性确定回归)', | |||
| value: 'ARD_regression', | |||
| }, | |||
| { | |||
| label: 'gaussian_process(高斯回归)', | |||
| value: 'gaussian_process', | |||
| } | |||
| ]; | |||
| // 框架类型 | |||
| export enum FrameworkType { | |||
| Sklearn = 'sklearn', | |||
| Keras = 'keras', | |||
| Pytorch = 'pytorch', | |||
| } | |||
| // 框架类型选项 | |||
| export const frameworkTypeOptions = [ | |||
| { | |||
| label: FrameworkType.Sklearn, | |||
| value: FrameworkType.Sklearn, | |||
| }, | |||
| { | |||
| label: FrameworkType.Keras, | |||
| value: FrameworkType.Keras, | |||
| }, | |||
| { | |||
| label: FrameworkType.Pytorch, | |||
| value: FrameworkType.Pytorch, | |||
| }, | |||
| ]; | |||
| // 查询策略 | |||
| export const queryStrategies = [ | |||
| { | |||
| label: 'uncertainty_sampling', | |||
| value: 'uncertainty_sampling', | |||
| }, | |||
| { | |||
| label: 'uncertainty_batch_sampling', | |||
| value: 'uncertainty_batch_sampling', | |||
| }, | |||
| { | |||
| label: 'max_std_sampling', | |||
| value: 'max_std_sampling', | |||
| }, | |||
| { | |||
| label: 'expected_improvement', | |||
| value: 'expected_improvement', | |||
| }, | |||
| { | |||
| label: 'upper_confidence_bound', | |||
| value: 'upper_confidence_bound', | |||
| } | |||
| ]; | |||
| @@ -0,0 +1,14 @@ | |||
| .experiment-history { | |||
| height: calc(100% - 10px); | |||
| margin-top: 10px; | |||
| &__content { | |||
| height: 100%; | |||
| padding: 20px @content-padding; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__table { | |||
| height: 100%; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,132 @@ | |||
| import { getFileReq } from '@/services/file'; | |||
| import { to } from '@/utils/promise'; | |||
| import tableCellRender from '@/utils/table'; | |||
| import { Table, type TableProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| type ExperimentHistoryProps = { | |||
| fileUrl?: string; | |||
| isClassification: boolean; | |||
| }; | |||
| type TableData = { | |||
| id?: string; | |||
| accuracy?: number; | |||
| duration?: number; | |||
| train_loss?: number; | |||
| status?: string; | |||
| feature?: string; | |||
| althorithm?: string; | |||
| }; | |||
| function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { | |||
| const [tableData, setTableData] = useState<TableData[]>([]); | |||
| useEffect(() => { | |||
| if (fileUrl) { | |||
| getHistoryFile(); | |||
| } | |||
| }, [fileUrl]); | |||
| // 获取实验运行历史记录 | |||
| const getHistoryFile = async () => { | |||
| const [res] = await to(getFileReq(fileUrl)); | |||
| if (res) { | |||
| const data: any[] = res.data; | |||
| const list: TableData[] = data.map((item) => { | |||
| return { | |||
| id: item[0]?.[0], | |||
| accuracy: item[1]?.[5]?.accuracy, | |||
| duration: item[1]?.[5]?.duration, | |||
| train_loss: item[1]?.[5]?.train_loss, | |||
| status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | |||
| }; | |||
| }); | |||
| list.forEach((item) => { | |||
| if (!item.id) return; | |||
| const config = (res as any).configs?.[item.id]; | |||
| item.feature = config?.['feature_preprocessor:__choice__']; | |||
| item.althorithm = isClassification | |||
| ? config?.['classifier:__choice__'] | |||
| : config?.['regressor:__choice__']; | |||
| }); | |||
| setTableData(list); | |||
| } | |||
| }; | |||
| const columns: TableProps<TableData>['columns'] = [ | |||
| { | |||
| title: 'ID', | |||
| dataIndex: 'id', | |||
| key: 'id', | |||
| width: 80, | |||
| render: tableCellRender(false), | |||
| }, | |||
| { | |||
| title: '准确率', | |||
| dataIndex: 'accuracy', | |||
| key: 'accuracy', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '耗时', | |||
| dataIndex: 'duration', | |||
| key: 'duration', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '训练损失', | |||
| dataIndex: 'train_loss', | |||
| key: 'train_loss', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '特征处理', | |||
| dataIndex: 'feature', | |||
| key: 'feature', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '算法', | |||
| dataIndex: 'althorithm', | |||
| key: 'althorithm', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'status', | |||
| key: 'status', | |||
| width: 120, | |||
| render: tableCellRender(false), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['experiment-history']}> | |||
| <div className={styles['experiment-history__content']}> | |||
| <div | |||
| className={classNames( | |||
| 'vertical-scroll-table-no-page', | |||
| styles['experiment-history__content__table'], | |||
| )} | |||
| > | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| pagination={false} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| rowKey="id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentHistory; | |||
| @@ -0,0 +1,16 @@ | |||
| .experiment-log { | |||
| height: 100%; | |||
| &__tabs { | |||
| height: 100%; | |||
| :global { | |||
| .ant-tabs-nav-list { | |||
| padding-left: 0 !important; | |||
| background: none !important; | |||
| } | |||
| } | |||
| &__log { | |||
| height: 100%; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { ActiveLearnInstanceData } from '@/pages/ActiveLearn/types'; | |||
| import LogList from '@/pages/Experiment/components/LogList'; | |||
| import { NodeStatus } from '@/types'; | |||
| import { Tabs } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ExperimentLogProps = { | |||
| instanceInfo: ActiveLearnInstanceData; | |||
| nodes: Record<string, NodeStatus>; | |||
| }; | |||
| function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||
| let hpoNodeStatus: NodeStatus | undefined; | |||
| let frameworkCloneNodeStatus: NodeStatus | undefined; | |||
| let trainCloneNodeStatus: NodeStatus | undefined; | |||
| Object.keys(nodes) | |||
| .sort((key1, key2) => { | |||
| const node1 = nodes[key1]; | |||
| const node2 = nodes[key2]; | |||
| return new Date(node1.startedAt).getTime() - new Date(node2.startedAt).getTime(); | |||
| }) | |||
| .forEach((key) => { | |||
| const node = nodes[key]; | |||
| if (node.displayName.startsWith('active-learn')) { | |||
| hpoNodeStatus = node; | |||
| } else if (node.displayName.startsWith('git-clone') && !frameworkCloneNodeStatus) { | |||
| frameworkCloneNodeStatus = node; | |||
| } else if ( | |||
| node.displayName.startsWith('git-clone') && | |||
| frameworkCloneNodeStatus && | |||
| node.displayName !== frameworkCloneNodeStatus?.displayName | |||
| ) { | |||
| trainCloneNodeStatus = node; | |||
| } | |||
| }); | |||
| const tabItems = [ | |||
| // { | |||
| // key: 'git-clone-framework', | |||
| // label: '框架代码日志', | |||
| // // icon: <KFIcon type="icon-rizhi1" />, | |||
| // children: ( | |||
| // <div className={styles['experiment-log__tabs__log']}> | |||
| // {frameworkCloneNodeStatus && ( | |||
| // <LogList | |||
| // instanceName={instanceInfo.argo_ins_name} | |||
| // instanceNamespace={instanceInfo.argo_ins_ns} | |||
| // pipelineNodeId={frameworkCloneNodeStatus.displayName} | |||
| // workflowId={frameworkCloneNodeStatus.id} | |||
| // instanceNodeStartTime={frameworkCloneNodeStatus.startedAt} | |||
| // instanceNodeStatus={frameworkCloneNodeStatus.phase as ExperimentStatus} | |||
| // ></LogList> | |||
| // )} | |||
| // </div> | |||
| // ), | |||
| // }, | |||
| { | |||
| key: 'git-clone-train', | |||
| label: '系统日志', | |||
| // icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['experiment-log__tabs__log']}> | |||
| {trainCloneNodeStatus && ( | |||
| <LogList | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={trainCloneNodeStatus.displayName} | |||
| workflowId={trainCloneNodeStatus.id} | |||
| instanceNodeStartTime={trainCloneNodeStatus.startedAt} | |||
| instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| </div> | |||
| ), | |||
| }, | |||
| { | |||
| key: 'active-learn', | |||
| label: '主动学习日志', | |||
| // icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['experiment-log__tabs__log']}> | |||
| {hpoNodeStatus && ( | |||
| <LogList | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={hpoNodeStatus.displayName} | |||
| workflowId={hpoNodeStatus.id} | |||
| instanceNodeStartTime={hpoNodeStatus.startedAt} | |||
| instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| </div> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['experiment-log']}> | |||
| <Tabs className={styles['experiment-log__tabs']} items={tabItems} /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentLog; | |||
| @@ -0,0 +1,52 @@ | |||
| .experiment-result { | |||
| height: calc(100% - 10px); | |||
| margin-top: 10px; | |||
| padding: 20px @content-padding; | |||
| overflow-y: auto; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__download { | |||
| padding-top: 16px; | |||
| padding-bottom: 16px; | |||
| padding-left: @content-padding; | |||
| color: @text-color; | |||
| font-size: 13px; | |||
| background-color: #f8f8f9; | |||
| border-radius: 4px; | |||
| &__btn { | |||
| display: block; | |||
| height: 36px; | |||
| margin-top: 15px; | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| &__text { | |||
| white-space: pre-wrap; | |||
| } | |||
| &__images { | |||
| display: flex; | |||
| align-items: flex-start; | |||
| width: 100%; | |||
| overflow-x: auto; | |||
| :global { | |||
| .ant-image { | |||
| margin-right: 20px; | |||
| &:last-child { | |||
| margin-right: 0; | |||
| } | |||
| } | |||
| } | |||
| &__item { | |||
| height: 248px; | |||
| border: 1px solid rgba(96, 107, 122, 0.3); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,83 @@ | |||
| import InfoGroup from '@/components/InfoGroup'; | |||
| import { getFileReq } from '@/services/file'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Button, Image } from 'antd'; | |||
| import { useEffect, useMemo, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| type ExperimentResultProps = { | |||
| fileUrl?: string; | |||
| imageUrl?: string; | |||
| modelPath?: string; | |||
| }; | |||
| function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProps) { | |||
| const [result, setResult] = useState<string | undefined>(''); | |||
| const images = useMemo(() => { | |||
| if (imageUrl) { | |||
| return imageUrl.split(',').map((item) => item.trim()); | |||
| } | |||
| return []; | |||
| }, [imageUrl]); | |||
| useEffect(() => { | |||
| if (fileUrl) { | |||
| getResultFile(); | |||
| } | |||
| }, [fileUrl]); | |||
| // 获取实验运行历史记录 | |||
| const getResultFile = async () => { | |||
| const [res] = await to(getFileReq(fileUrl)); | |||
| if (res) { | |||
| setResult(res as any as string); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles['experiment-result']}> | |||
| <InfoGroup title="实验结果" height={420} width="100%"> | |||
| <div className={styles['experiment-result__text']}>{result}</div> | |||
| </InfoGroup> | |||
| <InfoGroup title="可视化结果" style={{ margin: '16px 0' }}> | |||
| <div className={styles['experiment-result__images']}> | |||
| <Image.PreviewGroup | |||
| preview={{ | |||
| onChange: (current, prev) => | |||
| console.log(`current index: ${current}, prev index: ${prev}`), | |||
| }} | |||
| > | |||
| {images.map((item) => ( | |||
| <Image | |||
| key={item} | |||
| className={styles['experiment-result__images__item']} | |||
| src={item} | |||
| height={248} | |||
| draggable={false} | |||
| alt="" | |||
| /> | |||
| ))} | |||
| </Image.PreviewGroup> | |||
| </div> | |||
| </InfoGroup> | |||
| {modelPath && ( | |||
| <div className={styles['experiment-result__download']}> | |||
| <span style={{ marginRight: '12px', color: '#606b7a' }}>文件名</span> | |||
| <span>save_model.joblib</span> | |||
| <Button | |||
| type="primary" | |||
| className={styles['experiment-result__download__btn']} | |||
| onClick={() => { | |||
| window.location.href = modelPath; | |||
| }} | |||
| > | |||
| 模型下载 | |||
| </Button> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentResult; | |||
| @@ -0,0 +1,120 @@ | |||
| { | |||
| "workflow-xwnb8": { | |||
| "id": "workflow-xwnb8", | |||
| "name": "workflow-xwnb8", | |||
| "type": "DAG", | |||
| "phase": "Failed", | |||
| "children": [ | |||
| "workflow-xwnb8-1083129199" | |||
| ], | |||
| "progress": "2/3", | |||
| "startedAt": "2025-04-18T06:56:18Z", | |||
| "finishedAt": "2025-04-18T06:57:32Z", | |||
| "displayName": "workflow-xwnb8", | |||
| "templateName": "ml-workflow", | |||
| "outboundNodes": [ | |||
| "workflow-xwnb8-1355608520" | |||
| ], | |||
| "templateScope": "local/workflow-xwnb8", | |||
| "resourcesDuration": { | |||
| "cpu": 42, | |||
| "memory": 851, | |||
| "nvidia.com/gpu": 10 | |||
| } | |||
| }, | |||
| "git-clone-9d0c5965": { | |||
| "id": "workflow-xwnb8-514970004", | |||
| "name": "workflow-xwnb8.git-clone-9d0c5965", | |||
| "type": "Pod", | |||
| "phase": "Succeeded", | |||
| "outputs": { | |||
| "exitCode": "0", | |||
| "artifacts": [ | |||
| { | |||
| "s3": { | |||
| "key": "workflow-xwnb8/workflow-xwnb8-git-clone-9d0c5965-514970004/main.log" | |||
| }, | |||
| "name": "main-logs" | |||
| } | |||
| ] | |||
| }, | |||
| "children": [ | |||
| "workflow-xwnb8-1355608520" | |||
| ], | |||
| "progress": "1/1", | |||
| "startedAt": "2025-04-18T06:56:38Z", | |||
| "boundaryID": "workflow-xwnb8", | |||
| "finishedAt": "2025-04-18T06:56:49Z", | |||
| "displayName": "git-clone-9d0c5965", | |||
| "hostNodeName": "k8s-node01", | |||
| "templateName": "git-clone-9d0c5965", | |||
| "templateScope": "local/workflow-xwnb8", | |||
| "resourcesDuration": { | |||
| "cpu": 1, | |||
| "memory": 11 | |||
| } | |||
| }, | |||
| "git-clone-e28c560c": { | |||
| "id": "workflow-xwnb8-1083129199", | |||
| "name": "workflow-xwnb8.git-clone-e28c560c", | |||
| "type": "Pod", | |||
| "phase": "Succeeded", | |||
| "outputs": { | |||
| "exitCode": "0", | |||
| "artifacts": [ | |||
| { | |||
| "s3": { | |||
| "key": "workflow-xwnb8/workflow-xwnb8-git-clone-e28c560c-1083129199/main.log" | |||
| }, | |||
| "name": "main-logs" | |||
| } | |||
| ] | |||
| }, | |||
| "children": [ | |||
| "workflow-xwnb8-514970004" | |||
| ], | |||
| "progress": "1/1", | |||
| "startedAt": "2025-04-18T06:56:18Z", | |||
| "boundaryID": "workflow-xwnb8", | |||
| "finishedAt": "2025-04-18T06:56:27Z", | |||
| "displayName": "git-clone-e28c560c", | |||
| "hostNodeName": "k8s-node01", | |||
| "templateName": "git-clone-e28c560c", | |||
| "templateScope": "local/workflow-xwnb8", | |||
| "resourcesDuration": { | |||
| "cpu": 1, | |||
| "memory": 11 | |||
| } | |||
| }, | |||
| "active-learn-b708ed0b": { | |||
| "id": "workflow-xwnb8-1355608520", | |||
| "name": "workflow-xwnb8.active-learn-b708ed0b", | |||
| "type": "Pod", | |||
| "phase": "Failed", | |||
| "message": "Error (exit code 1)", | |||
| "outputs": { | |||
| "exitCode": "1", | |||
| "artifacts": [ | |||
| { | |||
| "s3": { | |||
| "key": "workflow-xwnb8/workflow-xwnb8-active-learn-b708ed0b-1355608520/main.log" | |||
| }, | |||
| "name": "main-logs" | |||
| } | |||
| ] | |||
| }, | |||
| "progress": "0/1", | |||
| "startedAt": "2025-04-18T06:57:00Z", | |||
| "boundaryID": "workflow-xwnb8", | |||
| "finishedAt": "2025-04-18T06:57:27Z", | |||
| "displayName": "active-learn-b708ed0b", | |||
| "hostNodeName": "k8s-node01", | |||
| "templateName": "active-learn-b708ed0b", | |||
| "templateScope": "local/workflow-xwnb8", | |||
| "resourcesDuration": { | |||
| "cpu": 40, | |||
| "memory": 829, | |||
| "nvidia.com/gpu": 10 | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,85 @@ | |||
| /* | |||
| * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git | |||
| * @Date: 2025-04-18 08:40:03 | |||
| * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git | |||
| * @LastEditTime: 2025-04-18 11:30:21 | |||
| * @FilePath: \ci4s\react-ui\src\pages\ActiveLearn\types.ts | |||
| * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE | |||
| */ | |||
| import { type ParameterInputObject } from '@/components/ResourceSelect'; | |||
| import { type NodeStatus } from '@/types'; | |||
| import { AutoMLTaskType } from '@/enums'; | |||
| // 操作类型 | |||
| export enum OperationType { | |||
| Create = 'Create', // 创建 | |||
| Update = 'Update', // 更新 | |||
| } | |||
| // 表单数据 | |||
| export type FormData = { | |||
| name: string; // 实验名称 | |||
| description: string; // 实验描述 | |||
| task_type: AutoMLTaskType; // 任务类型 | |||
| framework_type: string; // 框架类型 | |||
| code_config: ParameterInputObject; // 代码配置 | |||
| model?: ParameterInputObject; // 模型 | |||
| model_py?: string; // 模型文件路径 | |||
| model_class_name?: string; // 模型类名称 | |||
| loss_py?: string; // loss文件路径 | |||
| loss_class_name?: string; // loss类名 | |||
| classifier_alg?: string; // 分类算法 | |||
| regressor_alg?: string; // 回归算法 | |||
| dataset: ParameterInputObject; // 数据集 | |||
| dataset_py: string; // dataset处理文件路径 | |||
| dataset_class_name: string; // dataset类名 | |||
| data_size: number; // 数据量 | |||
| train_size: number; // 训练集数据量 | |||
| initial_num: number; // 初始训练数据量 | |||
| queries_num: number; // 查询次数 | |||
| instances_num: number; // 每次查询数据量 | |||
| computing_resource_id: number; // 资源规格 | |||
| image: ParameterInputObject; // 镜像 | |||
| shuffle: boolean; // 是否随机打乱 | |||
| query_strategy: string; // 查询策略 | |||
| checkpoint_num: number; // 多少轮查询保存一次模型参数 | |||
| batch_size: number; // batch_size | |||
| epochs: number; // epochs | |||
| lr: number; // 学习率 | |||
| }; | |||
| // 主动学习 | |||
| export type ActiveLearnData = { | |||
| id: number; | |||
| progress: number; | |||
| run_state: string; | |||
| state: number; | |||
| create_by?: string; | |||
| create_time?: string; | |||
| update_by?: string; | |||
| update_time?: string; | |||
| status_list: string; // 最近五次运行状态 | |||
| } & FormData; | |||
| // 主动学习实验实例 | |||
| export type ActiveLearnInstanceData = { | |||
| id: number; | |||
| auto_ml_id: number; | |||
| result_path: string; | |||
| model_path: string; | |||
| img_path: string; | |||
| run_history_path: string; | |||
| state: number; | |||
| status: string; | |||
| node_status: string; | |||
| node_result: string; | |||
| param: string; | |||
| source: string | null; | |||
| argo_ins_name: string; | |||
| argo_ins_ns: string; | |||
| create_time: string; | |||
| update_time: string; | |||
| finish_time: string; | |||
| nodeStatus?: NodeStatus; | |||
| }; | |||
| @@ -4,6 +4,15 @@ | |||
| * @Description: 实验列表组件配置 | |||
| */ | |||
| import { | |||
| batchDeleteActiveLearnInsReq, | |||
| deleteActiveLearnInsReq, | |||
| deleteActiveLearnReq, | |||
| getActiveLearnInsListReq, | |||
| getActiveLearnListReq, | |||
| runActiveLearnReq, | |||
| stopActiveLearnInsReq, | |||
| } from '@/services/activeLearn'; | |||
| import { | |||
| batchDeleteExperimentInsReq, | |||
| deleteAutoMLReq, | |||
| @@ -26,6 +35,7 @@ import { | |||
| export enum ExperimentListType { | |||
| AutoML = 'AutoML', | |||
| HyperParameter = 'HyperParameter', | |||
| ActiveLearn = 'ActiveLearn', | |||
| } | |||
| type ExperimentListInfo = { | |||
| @@ -72,4 +82,18 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||
| descProperty: 'description', | |||
| idProperty: 'rayId', | |||
| }, | |||
| [ExperimentListType.ActiveLearn]: { | |||
| getListReq: getActiveLearnListReq, | |||
| getInsListReq: getActiveLearnInsListReq, | |||
| deleteRecordReq: deleteActiveLearnReq, | |||
| runRecordReq: runActiveLearnReq, | |||
| deleteInsReq: deleteActiveLearnInsReq, | |||
| batchDeleteInsReq: batchDeleteActiveLearnInsReq, | |||
| stopInsReq: stopActiveLearnInsReq, | |||
| title: '自动学习', | |||
| pathPrefix: 'active-learn', | |||
| nameProperty: 'name', | |||
| descProperty: 'description', | |||
| idProperty: 'activeLearnId', | |||
| }, | |||
| }; | |||
| @@ -63,7 +63,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| ml_name: searchText || undefined, | |||
| [config.nameProperty]: searchText || undefined, | |||
| }; | |||
| const request = config.getListReq; | |||
| const [res] = await to(request(params)); | |||
| @@ -248,7 +248,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| { | |||
| title: '实验名称', | |||
| dataIndex: config.nameProperty, | |||
| key: 'ml_name', | |||
| key: 'name', | |||
| width: '16%', | |||
| render: tableCellRender(false, TableCellValueType.Link, { | |||
| onClick: gotoDetail, | |||
| @@ -257,7 +257,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| { | |||
| title: '实验描述', | |||
| dataIndex: config.descProperty, | |||
| key: 'ml_description', | |||
| key: 'description', | |||
| render: tableCellRender(true), | |||
| }, | |||
| { | |||
| @@ -55,12 +55,10 @@ function EditorCreate() { | |||
| // 创建编辑器 | |||
| const createEditor = async (formData: FormData) => { | |||
| // 根据后台要求,修改表单数据 | |||
| const image = formData['image']; | |||
| const model = formData['model']; | |||
| const dataset = formData['dataset']; | |||
| const params = { | |||
| ...omit(formData, ['image', 'model', 'dataset']), | |||
| image: image.value, | |||
| ...omit(formData, ['model', 'dataset']), | |||
| model: model && pick(model, ['id', 'version', 'path', 'showValue']), | |||
| dataset: dataset && pick(dataset, ['id', 'version', 'path', 'showValue']), | |||
| }; | |||
| @@ -77,6 +77,7 @@ function EditorList() { | |||
| content.forEach((item: EditorData) => { | |||
| item.dataset = typeof item.dataset === 'string' ? parseJsonText(item.dataset) : null; | |||
| item.model = typeof item.model === 'string' ? parseJsonText(item.model) : null; | |||
| item.image = typeof item.image === 'string' ? parseJsonText(item.image) : null; | |||
| }); | |||
| setTableData(content); | |||
| setTotal(totalElements); | |||
| @@ -224,7 +225,7 @@ function EditorList() { | |||
| }, | |||
| { | |||
| title: '镜像', | |||
| dataIndex: ['image'], | |||
| dataIndex: ['image', 'showValue'], | |||
| key: 'image', | |||
| width: '15%', | |||
| render: tableCellRender(true), | |||
| @@ -54,10 +54,7 @@ function LogGroup({ | |||
| useEffect(() => { | |||
| // 建立 socket 连接 | |||
| const setupSockect = () => { | |||
| let { host } = location; | |||
| if (process.env.NODE_ENV === 'development') { | |||
| host = '172.20.32.197:31213'; | |||
| } | |||
| const { host } = location; | |||
| const socket = new WebSocket( | |||
| `ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`, | |||
| ); | |||
| @@ -20,7 +20,7 @@ function HyperparameterInfo() { | |||
| undefined, | |||
| ); | |||
| // 获取自动机器学习详情 | |||
| // 获取详情 | |||
| const getHyperparameterInfo = useCallback(async () => { | |||
| const [res] = await to(getRayInfoReq({ id: hyperparameterId })); | |||
| if (res && res.data) { | |||
| @@ -3,7 +3,6 @@ import LogList from '@/pages/Experiment/components/LogList'; | |||
| import { HyperParameterInstanceData } from '@/pages/HyperParameter/types'; | |||
| import { NodeStatus } from '@/types'; | |||
| import { Tabs } from 'antd'; | |||
| import { useEffect } from 'react'; | |||
| import styles from './index.less'; | |||
| type ExperimentLogProps = { | |||
| @@ -97,8 +96,6 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||
| }, | |||
| ]; | |||
| useEffect(() => {}, []); | |||
| return ( | |||
| <div className={styles['experiment-log']}> | |||
| <Tabs className={styles['experiment-log__tabs']} items={tabItems} /> | |||
| @@ -41,7 +41,7 @@ export type HyperParameterData = { | |||
| status_list: string; // 最近五次运行状态 | |||
| } & FormData; | |||
| // 自动机器学习实验实例 | |||
| // 实验实例 | |||
| export type HyperParameterInstanceData = { | |||
| id: number; | |||
| ray_id: number; | |||
| @@ -0,0 +1,12 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2025-04-21 16:38:59 | |||
| * @Description: 知识图谱 | |||
| */ | |||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||
| function KnowledgePage() { | |||
| return <IframePage type={IframePageType.Knowledge}></IframePage>; | |||
| } | |||
| export default KnowledgePage; | |||
| @@ -117,7 +117,6 @@ function CreateServiceVersion() { | |||
| // 创建版本 | |||
| const createServiceVersion = async (formData: FormData) => { | |||
| const envList = formData['env_variables']; | |||
| const image = formData['image']; | |||
| const model = formData['model']; | |||
| const codeConfig = formData['code_config']; | |||
| const envVariables = envList?.reduce((acc, cur) => { | |||
| @@ -127,10 +126,9 @@ function CreateServiceVersion() { | |||
| // 根据后台要求,修改表单数据 | |||
| const object = { | |||
| ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), | |||
| ...omit(formData, ['replicas', 'env_variables', 'model', 'code_config']), | |||
| replicas: Number(formData.replicas), | |||
| env_variables: envVariables, | |||
| image: image.value, | |||
| model: changePropertyName( | |||
| pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), | |||
| { showValue: 'show_value' }, | |||
| @@ -292,7 +292,7 @@ function ServiceInfo() { | |||
| }, | |||
| { | |||
| title: '版本镜像', | |||
| dataIndex: 'image', | |||
| dataIndex: ['image', 'showValue'], | |||
| key: 'image', | |||
| width: '20%', | |||
| render: tableCellRender(true), | |||
| @@ -11,7 +11,7 @@ | |||
| &__top { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: end; | |||
| justify-content: flex-end; | |||
| width: 100%; | |||
| height: 52px; | |||
| padding: 0 20px; | |||
| @@ -0,0 +1,93 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-11-18 10:18:27 | |||
| * @Description: 主动学习 | |||
| */ | |||
| import { request } from '@umijs/max'; | |||
| // 分页查询超参数自动寻优 | |||
| export function getActiveLearnListReq(params) { | |||
| return request(`/api/mmp/activeLearn`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 查询超参数自动寻优详情 | |||
| export function getActiveLearnInfoReq(params) { | |||
| return request(`/api/mmp/activeLearn/getActiveLearnDetail`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 新增超参数自动寻优 | |||
| export function addActiveLearnReq(data) { | |||
| return request(`/api/mmp/activeLearn`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 编辑超参数自动寻优 | |||
| export function updateActiveLearnReq(data) { | |||
| return request(`/api/mmp/activeLearn`, { | |||
| method: 'PUT', | |||
| data, | |||
| }); | |||
| } | |||
| // 删除超参数自动寻优 | |||
| export function deleteActiveLearnReq(id) { | |||
| return request(`/api/mmp/activeLearn/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 运行超参数自动寻优 | |||
| export function runActiveLearnReq(id) { | |||
| return request(`/api/mmp/activeLearn/run/${id}`, { | |||
| method: 'POST', | |||
| }); | |||
| } | |||
| // ----------------------- 实验实例 ----------------------- | |||
| // 获取实验实例列表 | |||
| export function getActiveLearnInsListReq(params) { | |||
| return request(`/api/mmp/activeLearnIns`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 查询实验实例详情 | |||
| export function getActiveLearnInsReq(id) { | |||
| return request(`/api/mmp/activeLearnIns/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 停止实验实例 | |||
| export function stopActiveLearnInsReq(id) { | |||
| return request(`/api/mmp/activeLearnIns/${id}`, { | |||
| method: 'PUT', | |||
| }); | |||
| } | |||
| // 删除实验实例 | |||
| export function deleteActiveLearnInsReq(id) { | |||
| return request(`/api/mmp/activeLearnIns/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 批量删除实验实例 | |||
| export function batchDeleteActiveLearnInsReq(data) { | |||
| return request(`/api/mmp/activeLearnIns/batchDelete`, { | |||
| method: 'DELETE', | |||
| data | |||
| }); | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import type { Meta, StoryObj } from '@storybook/react'; | |||
| import { Tabs } from 'antd'; | |||
| // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export | |||
| const meta = { | |||
| @@ -28,3 +29,25 @@ export const Primary: Story = { | |||
| title: '数据集列表', | |||
| }, | |||
| }; | |||
| /* 带有提示信息 */ | |||
| export const WithTooltip: Story = { | |||
| args: { | |||
| title: '数据集列表', | |||
| tooltip: '其它提示', | |||
| }, | |||
| }; | |||
| /* Title 可以是 ReactNode */ | |||
| export const Custom: Story = { | |||
| args: { | |||
| title: ( | |||
| <Tabs | |||
| items={[ | |||
| { label: 'Tab 1', key: '1' }, | |||
| { label: 'Tab 2', key: '2' }, | |||
| ]} | |||
| ></Tabs> | |||
| ), | |||
| }, | |||
| }; | |||
| @@ -20,7 +20,7 @@ export const elapsedTime = (begin?: string | null, end?: string | null): string | |||
| const timestamp = endDate.valueOf() - beginDate.valueOf(); | |||
| if (timestamp < 0) { | |||
| return '时间有误'; | |||
| return '0秒'; | |||
| } | |||
| const duration = dayjs.duration(timestamp); | |||
| const years = duration.years(); | |||
| @@ -13,3 +13,16 @@ export async function to<T, U = any>(promise: Promise<T>): Promise<[T, null] | [ | |||
| } | |||
| export default to; | |||
| /** | |||
| * 休眠 | |||
| * @param ms - The number of milliseconds to sleep. | |||
| * @return A promise that resolves after the specified amount of time. | |||
| */ | |||
| export function sleep(ms: number) { | |||
| return new Promise((resolve) => { | |||
| setTimeout(() => { | |||
| resolve(true); | |||
| }, ms); | |||
| }); | |||
| } | |||
| @@ -87,7 +87,7 @@ function formatArray(property?: string): TableCellFormatter { | |||
| * @param ellipsis - 是否省略 | |||
| * @param type - 类型 | |||
| * @param options - 选项 | |||
| * @returns React 节点 | |||
| * @returns Ant Design Table 的 render | |||
| */ | |||
| function tableCellRender<T>( | |||
| ellipsis: boolean | TooltipProps | 'auto' = false, | |||
| @@ -39,6 +39,10 @@ public class Constant { | |||
| public final static String Init = "Init"; | |||
| public final static String Stopped = "Stopped"; | |||
| public final static String Succeeded = "Succeeded"; | |||
| public final static String Error = "Error"; | |||
| public final static String Unknown = "Unknown"; | |||
| public final static String Available = "available"; | |||
| public final static String Type_Train = "train"; | |||
| public final static String Type_Evaluate = "evaluate"; | |||
| @@ -0,0 +1,62 @@ | |||
| package com.ruoyi.platform.controller.activeLearn; | |||
| import com.ruoyi.common.core.web.controller.BaseController; | |||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | |||
| import com.ruoyi.platform.domain.ActiveLearn; | |||
| import com.ruoyi.platform.service.ActiveLearnService; | |||
| import com.ruoyi.platform.vo.ActiveLearnVo; | |||
| import io.swagger.annotations.Api; | |||
| import io.swagger.annotations.ApiOperation; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| @RestController | |||
| @RequestMapping("activeLearn") | |||
| @Api("主动学习") | |||
| public class ActiveLearnController extends BaseController { | |||
| @Resource | |||
| private ActiveLearnService activeLearnService; | |||
| @GetMapping | |||
| @ApiOperation("分页查询") | |||
| public GenericsAjaxResult<Page<ActiveLearn>> queryByPage(@RequestParam("page") int page, | |||
| @RequestParam("size") int size, | |||
| @RequestParam(value = "name", required = false) String name) { | |||
| PageRequest pageRequest = PageRequest.of(page, size); | |||
| return genericsSuccess(this.activeLearnService.queryByPage(name, pageRequest)); | |||
| } | |||
| @PostMapping | |||
| @ApiOperation("新增主动学习") | |||
| public GenericsAjaxResult<ActiveLearn> addActiveLearn(@RequestBody ActiveLearnVo activeLearnVo) throws Exception { | |||
| return genericsSuccess(this.activeLearnService.save(activeLearnVo)); | |||
| } | |||
| @PutMapping | |||
| @ApiOperation("编辑主动学习") | |||
| public GenericsAjaxResult<String> editActiveLearn(@RequestBody ActiveLearnVo activeLearnVo) throws Exception { | |||
| return genericsSuccess(this.activeLearnService.edit(activeLearnVo)); | |||
| } | |||
| @GetMapping("/getActiveLearnDetail") | |||
| @ApiOperation("获取主动学习详细信息") | |||
| public GenericsAjaxResult<ActiveLearnVo> getActiveLearnDetail(@RequestParam("id") Long id) throws IOException { | |||
| return genericsSuccess(this.activeLearnService.getActiveLearnDetail(id)); | |||
| } | |||
| @DeleteMapping("{id}") | |||
| @ApiOperation("删除主动学习") | |||
| public GenericsAjaxResult<String> deleteActiveLearn(@PathVariable("id") Long id) { | |||
| return genericsSuccess(this.activeLearnService.delete(id)); | |||
| } | |||
| @PostMapping("/run/{id}") | |||
| @ApiOperation("运行主动学习实验") | |||
| public GenericsAjaxResult<String> runActiveLearn(@PathVariable("id") Long id) throws Exception { | |||
| return genericsSuccess(this.activeLearnService.runActiveLearnIns(id)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package com.ruoyi.platform.controller.activeLearn; | |||
| import com.ruoyi.common.core.web.controller.BaseController; | |||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | |||
| import com.ruoyi.platform.domain.ActiveLearnIns; | |||
| import com.ruoyi.platform.service.ActiveLearnInsService; | |||
| import io.swagger.annotations.Api; | |||
| import io.swagger.annotations.ApiOperation; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import org.springframework.data.domain.Page; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| import java.util.List; | |||
| @RestController | |||
| @RequestMapping("activeLearnIns") | |||
| @Api("主动学习实验实例") | |||
| public class ActiveLearnInsController extends BaseController { | |||
| @Resource | |||
| private ActiveLearnInsService activeLearnInsService; | |||
| @GetMapping | |||
| @ApiOperation("分页查询") | |||
| public GenericsAjaxResult<Page<ActiveLearnIns>> queryByPage(Long activeLearnId, int page, int size) throws IOException { | |||
| PageRequest pageRequest = PageRequest.of(page, size); | |||
| return genericsSuccess(this.activeLearnInsService.queryByPage(activeLearnId, pageRequest)); | |||
| } | |||
| @PostMapping | |||
| @ApiOperation("新增实验实例") | |||
| public GenericsAjaxResult<ActiveLearnIns> add(@RequestBody ActiveLearnIns activeLearnIns) { | |||
| return genericsSuccess(this.activeLearnInsService.insert(activeLearnIns)); | |||
| } | |||
| @DeleteMapping("{id}") | |||
| @ApiOperation("删除实验实例") | |||
| public GenericsAjaxResult<String> deleteById(@PathVariable("id") Long id) { | |||
| return genericsSuccess(this.activeLearnInsService.deleteById(id)); | |||
| } | |||
| @DeleteMapping("batchDelete") | |||
| @ApiOperation("批量删除实验实例") | |||
| public GenericsAjaxResult<String> batchDelete(@RequestBody List<Long> ids) { | |||
| return genericsSuccess(this.activeLearnInsService.batchDelete(ids)); | |||
| } | |||
| @PutMapping("{id}") | |||
| @ApiOperation("终止实验实例") | |||
| public GenericsAjaxResult<Boolean> terminateActiveLearnIns(@PathVariable("id") Long id) throws Exception { | |||
| return genericsSuccess(this.activeLearnInsService.terminateActiveLearnIns(id)); | |||
| } | |||
| @GetMapping("{id}") | |||
| @ApiOperation("查看实验实例详情") | |||
| public GenericsAjaxResult<ActiveLearnIns> getDetailById(@PathVariable("id") Long id) throws Exception { | |||
| return genericsSuccess(this.activeLearnInsService.getDetailById(id)); | |||
| } | |||
| } | |||
| @@ -115,6 +115,7 @@ public class ImageController extends BaseController { | |||
| * @return 删除是否成功 | |||
| */ | |||
| @DeleteMapping("{id}") | |||
| @ApiOperation("删除镜像") | |||
| public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) throws Exception { | |||
| return genericsSuccess(this.imageService.removeById(id)); | |||
| } | |||
| @@ -50,7 +50,7 @@ public class RayController extends BaseController { | |||
| @DeleteMapping("{id}") | |||
| @ApiOperation("删除自动超参数寻优") | |||
| public GenericsAjaxResult<String> deleteRay(@PathVariable("id") Long id) { | |||
| public GenericsAjaxResult<String> deleteRay(@PathVariable("id") Long id) { | |||
| return genericsSuccess(this.rayService.delete(id)); | |||
| } | |||
| @@ -85,13 +85,13 @@ public class ServiceController extends BaseController { | |||
| @GetMapping("/serviceVersionDetail/{id}") | |||
| @ApiOperation("查询服务版本详细信息") | |||
| public GenericsAjaxResult<ServiceVersionVo> getServiceVersion(@PathVariable("id") Long id) { | |||
| public GenericsAjaxResult<ServiceVersionVo> getServiceVersion(@PathVariable("id") Long id) throws IOException { | |||
| return genericsSuccess(serviceService.getServiceVersion(id)); | |||
| } | |||
| @GetMapping("serviceVersionCompare") | |||
| @ApiOperation("服务版本版本对比") | |||
| public GenericsAjaxResult<Map<String, Object>> serviceVersionCompare(@RequestParam("id1") Long id1, @RequestParam("id2") Long id2) throws IllegalAccessException { | |||
| public GenericsAjaxResult<Map<String, Object>> serviceVersionCompare(@RequestParam("id1") Long id1, @RequestParam("id2") Long id2) throws IllegalAccessException, IOException { | |||
| return genericsSuccess(serviceService.serviceVersionCompare(id1, id2)); | |||
| } | |||
| @@ -0,0 +1,113 @@ | |||
| package com.ruoyi.platform.domain; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import io.swagger.annotations.ApiModel; | |||
| import io.swagger.annotations.ApiModelProperty; | |||
| import lombok.Data; | |||
| import java.util.Date; | |||
| @Data | |||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||
| @ApiModel(description = "主动学习") | |||
| public class ActiveLearn { | |||
| private Long id; | |||
| @ApiModelProperty(value = "实验名称") | |||
| private String name; | |||
| @ApiModelProperty(value = "实验描述") | |||
| private String description; | |||
| @ApiModelProperty(value = "任务类型:classification或regression") | |||
| private String taskType; | |||
| @ApiModelProperty(value = "框架类型: sklearn, keras, pytorch") | |||
| private String frameworkType; | |||
| @ApiModelProperty(value = "代码配置") | |||
| private String codeConfig; | |||
| @ApiModelProperty(value = "预训练的模型") | |||
| private String model; | |||
| @ApiModelProperty(value = "模型文件路径") | |||
| private String modelPy; | |||
| @ApiModelProperty(value = "模型类名称") | |||
| private String modelClassName; | |||
| @ApiModelProperty(value = "分类算法") | |||
| private String classifierAlg; | |||
| @ApiModelProperty(value = "回归算法") | |||
| private String regressorAlg; | |||
| @ApiModelProperty(value = "dataset处理文件路径") | |||
| private String datasetPy; | |||
| @ApiModelProperty(value = "dataset类名") | |||
| private String datasetClassName; | |||
| @ApiModelProperty(value = "数据集") | |||
| private String dataset; | |||
| @ApiModelProperty(value = "数据量") | |||
| private Integer dataSize; | |||
| @ApiModelProperty(value = "镜像") | |||
| private String image; | |||
| @ApiModelProperty(value = "计算资源id") | |||
| private Integer computingResourceId; | |||
| @ApiModelProperty(value = "是否随机打乱") | |||
| private Boolean shuffle; | |||
| @ApiModelProperty(value = "训练集数据量") | |||
| private Integer trainSize; | |||
| @ApiModelProperty(value = "初始训练数据量") | |||
| private Integer initialNum; | |||
| @ApiModelProperty(value = "查询次数") | |||
| private Integer queriesNum; | |||
| @ApiModelProperty(value = "每次查询数据量") | |||
| private Integer instancesNum; | |||
| @ApiModelProperty(value = "查询策略:uncertainty_sampling, uncertainty_batch_sampling, max_std_sampling, expected_improvement, upper_confidence_bound") | |||
| private String queryStrategy; | |||
| @ApiModelProperty(value = "loss文件路径") | |||
| private String lossPy; | |||
| @ApiModelProperty(value = "loss类名") | |||
| private String lossClassName; | |||
| @ApiModelProperty(value = "多少轮查询保存一次模型参数") | |||
| private Integer checkpointNum; | |||
| @ApiModelProperty(value = "batch_size") | |||
| private Integer batchSize; | |||
| @ApiModelProperty(value = "epochs") | |||
| private Integer epochs; | |||
| @ApiModelProperty(value = "学习率") | |||
| private Float lr; | |||
| private String createBy; | |||
| private String updateBy; | |||
| private Date createTime; | |||
| private Date updateTime; | |||
| private Integer state; | |||
| @ApiModelProperty(value = "状态列表") | |||
| private String statusList; | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| package com.ruoyi.platform.domain; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import io.swagger.annotations.ApiModel; | |||
| import lombok.Data; | |||
| import java.util.Date; | |||
| @Data | |||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||
| @ApiModel(description = "主动学习实验实例") | |||
| public class ActiveLearnIns { | |||
| private Long id; | |||
| private Long activeLearnId; | |||
| private Integer state; | |||
| private String status; | |||
| private String param; | |||
| private Date createTime; | |||
| private Date updateTime; | |||
| private Date finishTime; | |||
| private String argoInsName; | |||
| private String argoInsNs; | |||
| private String resultPath; | |||
| private String nodeStatus; | |||
| private String nodeResult; | |||
| } | |||
| @@ -49,9 +49,12 @@ public class ResourceOccupy { | |||
| @ApiModelProperty("流水线id") | |||
| private Long workflowId; | |||
| @ApiModelProperty("流水线节点id") | |||
| private String nodeId; | |||
| @ApiModelProperty("任务名称") | |||
| private String taskName; | |||
| @ApiModelProperty("流水线节点id") | |||
| private String nodeId; | |||
| @ApiModelProperty("任务状态") | |||
| private Integer taskState; | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| package com.ruoyi.platform.mapper; | |||
| import com.ruoyi.platform.domain.ActiveLearn; | |||
| import org.apache.ibatis.annotations.Param; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import java.util.List; | |||
| public interface ActiveLearnDao { | |||
| long count(@Param("name") String name); | |||
| List<ActiveLearn> queryByPage(@Param("name") String name, @Param("pageable") PageRequest pageRequest); | |||
| ActiveLearn getActiveLearnByName(@Param("name") String name); | |||
| int save(@Param("activeLearn") ActiveLearn activeLearn); | |||
| int edit(@Param("activeLearn") ActiveLearn activeLearn); | |||
| ActiveLearn getActiveLearnById(@Param("id") Long id); | |||
| List<ActiveLearn> queryByDatasetId(@Param("datasetId") String datasetId); | |||
| List<ActiveLearn> queryByModelId(@Param("modelId") String modelId); | |||
| List<ActiveLearn> queryByImageId(@Param("imageId") String imageId); | |||
| List<ActiveLearn> queryByCodeConfig(@Param("codeConfig") String codeConfig); | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| package com.ruoyi.platform.mapper; | |||
| import com.ruoyi.platform.domain.ActiveLearnIns; | |||
| import org.apache.ibatis.annotations.Param; | |||
| import org.springframework.data.domain.Pageable; | |||
| import java.util.List; | |||
| public interface ActiveLearnInsDao { | |||
| long count(@Param("activeLearnId") Long activeLearnId); | |||
| List<ActiveLearnIns> queryAllByLimit(@Param("activeLearnId") Long activeLearnId, @Param("pageable") Pageable pageable); | |||
| int insert(@Param("activeLearnIns") ActiveLearnIns activeLearnIns); | |||
| int update(@Param("activeLearnIns") ActiveLearnIns activeLearnIns); | |||
| ActiveLearnIns queryById(@Param("id") Long id); | |||
| List<ActiveLearnIns> getByActiveLearnId(@Param("activeLearnId") Long activeLearnId); | |||
| List<ActiveLearnIns> queryActiveLearnInsIsNotTerminated(); | |||
| } | |||
| @@ -62,6 +62,14 @@ public interface DevEnvironmentDao { | |||
| */ | |||
| int deleteById(Integer id); | |||
| DevEnvironment getByName(@Param("name") String name); | |||
| List<DevEnvironment> getRunning(); | |||
| List<DevEnvironment> queryByDatasetId(@Param("datasetId") String datasetId); | |||
| List<DevEnvironment> queryByModelId(@Param("modelId") String modelId); | |||
| List<DevEnvironment> queryByImageId(@Param("imageId") String imageId); | |||
| } | |||
| @@ -14,7 +14,7 @@ public interface ResourceOccupyDao { | |||
| int edit(@Param("resourceOccupy") ResourceOccupy resourceOccupy); | |||
| List<ResourceOccupy> getResourceOccupyByTask(@Param("taskType") String taskType, @Param("taskId") Long taskId, @Param("taskInsId") Long taskInsId, @Param("nodeId") String nodeId); | |||
| List<ResourceOccupy> getResourceOccupyByTask(@Param("taskType") String taskType, @Param("taskId") Long taskId, @Param("taskInsId") Long taskInsId, @Param("nodeId") String nodeId); | |||
| int deduceCredit(@Param("credit") Double credit, @Param("userId") Long userId); | |||
| @@ -29,4 +29,6 @@ public interface ResourceOccupyDao { | |||
| Double getUserCredit(@Param("userId") Long userId); | |||
| Double getDeduceCredit(@Param("userId") Long userId); | |||
| int deleteTaskState(@Param("taskType") String taskType, @Param("taskId") Long taskId, @Param("taskInsId") Long taskInsId); | |||
| } | |||
| @@ -38,4 +38,10 @@ public interface ServiceDao { | |||
| ServiceVersion getSvByVersion(@Param("version") String version, @Param("serviceId") Long serviceId); | |||
| List<ServiceVersion> getRunning(); | |||
| List<String> queryByModelId(@Param("modelId") String modelId); | |||
| List<String> queryByImageId(@Param("imageId") String imageId); | |||
| List<String> queryByCodeConfig(@Param("codeConfig") String codeConfig); | |||
| } | |||
| @@ -0,0 +1,116 @@ | |||
| package com.ruoyi.platform.scheduling; | |||
| import com.ruoyi.platform.domain.ActiveLearn; | |||
| import com.ruoyi.platform.domain.ActiveLearnIns; | |||
| import com.ruoyi.platform.mapper.ActiveLearnDao; | |||
| import com.ruoyi.platform.mapper.ActiveLearnInsDao; | |||
| import com.ruoyi.platform.mapper.ResourceOccupyDao; | |||
| import com.ruoyi.platform.service.ActiveLearnInsService; | |||
| import com.ruoyi.platform.service.ResourceOccupyService; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.scheduling.annotation.Scheduled; | |||
| import org.springframework.stereotype.Component; | |||
| import javax.annotation.Resource; | |||
| import java.util.ArrayList; | |||
| import java.util.Iterator; | |||
| import java.util.List; | |||
| @Component() | |||
| public class ActiveLearnInsStatusTask { | |||
| @Resource | |||
| private ActiveLearnInsService activeLearnInsService; | |||
| @Resource | |||
| private ActiveLearnInsDao activeLearnInsDao; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private ResourceOccupyDao resourceOccupyDao; | |||
| @Resource | |||
| private ResourceOccupyService resourceOccupyService; | |||
| private List<Long> activeLearnIds = new ArrayList<>(); | |||
| @Scheduled(cron = "0/30 * * * * ?") // 每30S执行一次 | |||
| public void executeActiveLearnInsStatus() { | |||
| List<ActiveLearnIns> activeLearnInsList = activeLearnInsService.queryActiveLearnInsIsNotTerminated(); | |||
| // 去argo查询状态 | |||
| List<ActiveLearnIns> updateList = new ArrayList<>(); | |||
| if (activeLearnInsList != null && activeLearnInsList.size() > 0) { | |||
| for (ActiveLearnIns activeLearnIns : activeLearnInsList) { | |||
| //当原本状态为null或非终止态时才调用argo接口 | |||
| try { | |||
| Long userId = resourceOccupyDao.getResourceOccupyByTask(Constant.TaskType_ActiveLearn, activeLearnIns.getActiveLearnId(), activeLearnIns.getId(), null).get(0).getUserId(); | |||
| if (resourceOccupyDao.getUserCredit(userId) <= 0) { | |||
| activeLearnIns.setStatus(Constant.Failed); | |||
| activeLearnInsService.terminateActiveLearnIns(activeLearnIns.getId()); | |||
| } else { | |||
| activeLearnIns = activeLearnInsService.queryStatusFromArgo(activeLearnIns); | |||
| // 扣除积分 | |||
| if (Constant.Running.equals(activeLearnIns.getStatus())) { | |||
| resourceOccupyService.deducing(Constant.TaskType_ActiveLearn, null, activeLearnIns.getId(), null, null); | |||
| } else if (Constant.Failed.equals(activeLearnIns.getStatus()) || Constant.Terminated.equals(activeLearnIns.getStatus()) | |||
| || Constant.Succeeded.equals(activeLearnIns.getStatus())) { | |||
| resourceOccupyService.endDeduce(Constant.TaskType_ActiveLearn, null, activeLearnIns.getId(), null, null); | |||
| } | |||
| } | |||
| } catch (Exception e) { | |||
| activeLearnIns.setStatus(Constant.Failed); | |||
| // 结束扣除积分 | |||
| resourceOccupyService.endDeduce(Constant.TaskType_ActiveLearn, null, activeLearnIns.getId(), null, null); | |||
| } | |||
| // 线程安全的添加操作 | |||
| synchronized (activeLearnIds) { | |||
| activeLearnIds.add(activeLearnIns.getActiveLearnId()); | |||
| } | |||
| updateList.add(activeLearnIns); | |||
| } | |||
| if (updateList.size() > 0) { | |||
| for (ActiveLearnIns activeLearnIns : updateList) { | |||
| activeLearnInsDao.update(activeLearnIns); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @Scheduled(cron = "0/30 * * * * ?") // / 每30S执行一次 | |||
| public void executeActiveLearnStatus() { | |||
| if (activeLearnIds.size() == 0) { | |||
| return; | |||
| } | |||
| // 存储需要更新的实验对象列表 | |||
| List<ActiveLearn> updateActiveLearns = new ArrayList<>(); | |||
| for (Long activeLearnId : activeLearnIds) { | |||
| // 获取当前实验的所有实例列表 | |||
| List<ActiveLearnIns> insList = activeLearnInsDao.getByActiveLearnId(activeLearnId); | |||
| List<String> statusList = new ArrayList<>(); | |||
| // 更新实验状态列表 | |||
| for (int i = 0; i < insList.size(); i++) { | |||
| statusList.add(insList.get(i).getStatus()); | |||
| } | |||
| String subStatus = statusList.toString().substring(1, statusList.toString().length() - 1); | |||
| ActiveLearn activeLearn = activeLearnDao.getActiveLearnById(activeLearnId); | |||
| if (!StringUtils.equals(activeLearn.getStatusList(), subStatus)) { | |||
| activeLearn.setStatusList(subStatus); | |||
| updateActiveLearns.add(activeLearn); | |||
| activeLearnDao.edit(activeLearn); | |||
| } | |||
| } | |||
| if (!updateActiveLearns.isEmpty()) { | |||
| // 使用Iterator进行安全的删除操作 | |||
| Iterator<Long> iterator = activeLearnIds.iterator(); | |||
| while (iterator.hasNext()) { | |||
| Long activeLearnId = iterator.next(); | |||
| for (ActiveLearn activeLearn : updateActiveLearns) { | |||
| if (activeLearn.getId().equals(activeLearnId)) { | |||
| iterator.remove(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| package com.ruoyi.platform.service; | |||
| import com.ruoyi.platform.domain.ActiveLearnIns; | |||
| import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import java.io.IOException; | |||
| import java.util.List; | |||
| public interface ActiveLearnInsService { | |||
| Page<ActiveLearnIns> queryByPage(Long activeLearnId, PageRequest pageRequest) throws IOException; | |||
| ActiveLearnIns insert(ActiveLearnIns activeLearnIns); | |||
| String deleteById(Long id); | |||
| String batchDelete(List<Long> ids); | |||
| boolean terminateActiveLearnIns(Long id) throws Exception; | |||
| ActiveLearnIns getDetailById(Long id) throws Exception; | |||
| void updateActiveLearnStatus(Long activeLearnId); | |||
| ActiveLearnIns queryStatusFromArgo(ActiveLearnIns ins); | |||
| List<ActiveLearnIns> queryActiveLearnInsIsNotTerminated(); | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| package com.ruoyi.platform.service; | |||
| import com.ruoyi.platform.domain.ActiveLearn; | |||
| import com.ruoyi.platform.vo.ActiveLearnVo; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.data.domain.Page; | |||
| import java.io.IOException; | |||
| public interface ActiveLearnService { | |||
| Page<ActiveLearn> queryByPage(String name, PageRequest pageRequest); | |||
| ActiveLearn save(ActiveLearnVo activeLearnVo) throws Exception; | |||
| String edit(ActiveLearnVo activeLearnVo) throws Exception; | |||
| ActiveLearnVo getActiveLearnDetail(Long id) throws IOException; | |||
| String delete(Long id); | |||
| String runActiveLearnIns (Long id) throws Exception; | |||
| } | |||
| @@ -4,6 +4,7 @@ import org.springframework.data.domain.PageRequest; | |||
| import com.ruoyi.platform.domain.RayIns; | |||
| import java.io.IOException; | |||
| import java.util.List; | |||
| public interface RayInsService { | |||
| Page<RayIns> queryByPage(Long rayId, PageRequest pageRequest) throws IOException; | |||
| @@ -22,4 +22,6 @@ public interface ResourceOccupyService { | |||
| Map<String, Double> queryCredit(); | |||
| void update(String taskType, Long taskId, Long taskInsId, Integer computingResourceId, Integer replicas); | |||
| void deleteTaskState(String taskType, Long taskId, Long taskInsId); | |||
| } | |||
| @@ -26,9 +26,9 @@ public interface ServiceService { | |||
| Service getService(Long id); | |||
| ServiceVersionVo getServiceVersion(Long id); | |||
| ServiceVersionVo getServiceVersion(Long id) throws IOException; | |||
| Map<String, Object> serviceVersionCompare(Long id1, Long id2) throws IllegalAccessException; | |||
| Map<String, Object> serviceVersionCompare(Long id1, Long id2) throws IllegalAccessException, IOException; | |||
| String deleteService(Long id) throws Exception; | |||
| @@ -0,0 +1,263 @@ | |||
| package com.ruoyi.platform.service.impl; | |||
| import com.ruoyi.platform.domain.ActiveLearn; | |||
| import com.ruoyi.platform.domain.ActiveLearnIns; | |||
| import com.ruoyi.platform.mapper.ActiveLearnDao; | |||
| import com.ruoyi.platform.mapper.ActiveLearnInsDao; | |||
| import com.ruoyi.platform.service.ActiveLearnInsService; | |||
| import com.ruoyi.platform.service.ResourceOccupyService; | |||
| import com.ruoyi.platform.utils.DateUtils; | |||
| import com.ruoyi.platform.utils.HttpUtils; | |||
| import com.ruoyi.platform.utils.JsonUtils; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| import java.util.*; | |||
| @Service | |||
| public class ActiveLearnInsServiceImpl implements ActiveLearnInsService { | |||
| @Value("${argo.url}") | |||
| private String argoUrl; | |||
| @Value("${argo.workflowStatus}") | |||
| private String argoWorkflowStatus; | |||
| @Value("${argo.workflowTermination}") | |||
| private String argoWorkflowTermination; | |||
| @Resource | |||
| private ActiveLearnInsDao activeLearnInsDao; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private ResourceOccupyService resourceOccupyService; | |||
| @Override | |||
| public Page<ActiveLearnIns> queryByPage(Long activeLearnId, PageRequest pageRequest) throws IOException { | |||
| long total = this.activeLearnInsDao.count(activeLearnId); | |||
| List<ActiveLearnIns> activeLearnInsList = activeLearnInsDao.queryAllByLimit(activeLearnId, pageRequest); | |||
| return new PageImpl<>(activeLearnInsList, pageRequest, total); | |||
| } | |||
| @Override | |||
| public ActiveLearnIns insert(ActiveLearnIns activeLearnIns) { | |||
| activeLearnInsDao.insert(activeLearnIns); | |||
| return activeLearnIns; | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String deleteById(Long id) { | |||
| ActiveLearnIns activeLearnIns = activeLearnInsDao.queryById(id); | |||
| if (activeLearnIns == null) { | |||
| return "实验实例不存在"; | |||
| } | |||
| if (StringUtils.isEmpty(activeLearnIns.getStatus())) { | |||
| activeLearnIns = queryStatusFromArgo(activeLearnIns); | |||
| } | |||
| if (StringUtils.equals(activeLearnIns.getStatus(), Constant.Running)) { | |||
| return "实验实例正在运行,不可删除"; | |||
| } | |||
| activeLearnIns.setState(Constant.State_invalid); | |||
| int update = activeLearnInsDao.update(activeLearnIns); | |||
| if (update > 0) { | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_ActiveLearn, activeLearnIns.getActiveLearnId(), id); | |||
| updateActiveLearnStatus(activeLearnIns.getActiveLearnId()); | |||
| return "删除成功"; | |||
| } else { | |||
| return "删除失败"; | |||
| } | |||
| } | |||
| @Override | |||
| public String batchDelete(List<Long> ids) { | |||
| for (Long id : ids) { | |||
| String result = deleteById(id); | |||
| if (!"删除成功".equals(result)) { | |||
| return result; | |||
| } | |||
| } | |||
| return "删除成功"; | |||
| } | |||
| @Override | |||
| public boolean terminateActiveLearnIns(Long id) throws Exception { | |||
| ActiveLearnIns activeLearnIns = activeLearnInsDao.queryById(id); | |||
| if (activeLearnIns == null) { | |||
| throw new IllegalStateException("实验实例未查询到,id: " + id); | |||
| } | |||
| String currentStatus = activeLearnIns.getStatus(); | |||
| String name = activeLearnIns.getArgoInsName(); | |||
| String namespace = activeLearnIns.getArgoInsNs(); | |||
| // 获取当前状态,如果为空,则从Argo查询 | |||
| if (StringUtils.isEmpty(currentStatus)) { | |||
| currentStatus = queryStatusFromArgo(activeLearnIns).getStatus(); | |||
| } | |||
| // 只有状态是"Running"时才能终止实例 | |||
| if (!currentStatus.equalsIgnoreCase(Constant.Running)) { | |||
| throw new Exception("终止错误,只有运行状态的实例才能终止"); // 如果不是"Running"状态,则不执行终止操作 | |||
| } | |||
| // 创建请求数据map | |||
| Map<String, Object> requestData = new HashMap<>(); | |||
| requestData.put("namespace", namespace); | |||
| requestData.put("name", name); | |||
| // 创建发送数据map,将请求数据作为"data"键的值 | |||
| Map<String, Object> res = new HashMap<>(); | |||
| res.put("data", requestData); | |||
| try { | |||
| // 发送POST请求到Argo工作流状态查询接口,并将请求数据转换为JSON | |||
| String req = HttpUtils.sendPost(argoUrl + argoWorkflowTermination, null, JsonUtils.mapToJson(res)); | |||
| // 检查响应是否为空或无内容 | |||
| if (StringUtils.isEmpty(req)) { | |||
| throw new RuntimeException("终止响应内容为空。"); | |||
| } | |||
| // 将响应的JSON字符串转换为Map对象 | |||
| Map<String, Object> runResMap = JsonUtils.jsonToMap(req); | |||
| // 从响应Map中直接获取"errCode"的值 | |||
| Integer errCode = (Integer) runResMap.get("errCode"); | |||
| if (errCode != null && errCode == 0) { | |||
| //更新autoMlIns,确保状态更新被保存到数据库 | |||
| ActiveLearnIns ins = queryStatusFromArgo(activeLearnIns); | |||
| String nodeStatus = ins.getNodeStatus(); | |||
| Map<String, Object> nodeMap = JsonUtils.jsonToMap(nodeStatus); | |||
| // 遍历 map | |||
| for (Map.Entry<String, Object> entry : nodeMap.entrySet()) { | |||
| // 获取每个 Map 中的值并强制转换为 Map | |||
| Map<String, Object> innerMap = (Map<String, Object>) entry.getValue(); | |||
| // 检查 phase 的值 | |||
| if (innerMap.containsKey("phase")) { | |||
| String phaseValue = (String) innerMap.get("phase"); | |||
| // 如果值不等于 Succeeded,则赋值为 Failed | |||
| if (!StringUtils.equals(Constant.Succeeded, phaseValue)) { | |||
| innerMap.put("phase", Constant.Failed); | |||
| } | |||
| } | |||
| } | |||
| ins.setNodeStatus(JsonUtils.mapToJson(nodeMap)); | |||
| ins.setStatus(Constant.Terminated); | |||
| ins.setUpdateTime(new Date()); | |||
| activeLearnInsDao.update(ins); | |||
| updateActiveLearnStatus(ins.getActiveLearnId()); | |||
| // 结束扣积分 | |||
| resourceOccupyService.endDeduce(Constant.TaskType_ActiveLearn, null, id, null, null); | |||
| return true; | |||
| } else { | |||
| return false; | |||
| } | |||
| } catch (Exception e) { | |||
| throw new RuntimeException("终止实例错误: " + e.getMessage(), e); | |||
| } | |||
| } | |||
| @Override | |||
| public ActiveLearnIns getDetailById(Long id) throws Exception { | |||
| ActiveLearnIns activeLearnIns = activeLearnInsDao.queryById(id); | |||
| if (Constant.Running.equals(activeLearnIns.getStatus()) || Constant.Pending.equals(activeLearnIns.getStatus())) { | |||
| activeLearnIns = queryStatusFromArgo(activeLearnIns); | |||
| } | |||
| return activeLearnIns; | |||
| } | |||
| @Override | |||
| public void updateActiveLearnStatus(Long activeLearnId) { | |||
| List<ActiveLearnIns> insList = activeLearnInsDao.getByActiveLearnId(activeLearnId); | |||
| List<String> statusList = new ArrayList<>(); | |||
| // 更新实验状态列表 | |||
| for (int i = 0; i < insList.size(); i++) { | |||
| statusList.add(insList.get(i).getStatus()); | |||
| } | |||
| String subStatus = statusList.toString().substring(1, statusList.toString().length() - 1); | |||
| ActiveLearn activeLearn = activeLearnDao.getActiveLearnById(activeLearnId); | |||
| if (!StringUtils.equals(activeLearn.getStatusList(), subStatus)) { | |||
| activeLearn.setStatusList(subStatus); | |||
| activeLearnDao.edit(activeLearn); | |||
| } | |||
| } | |||
| @Override | |||
| public ActiveLearnIns queryStatusFromArgo(ActiveLearnIns ins) { | |||
| String namespace = ins.getArgoInsNs(); | |||
| String name = ins.getArgoInsName(); | |||
| // 创建请求数据map | |||
| Map<String, Object> requestData = new HashMap<>(); | |||
| requestData.put("namespace", namespace); | |||
| requestData.put("name", name); | |||
| // 创建发送数据map,将请求数据作为"data"键的值 | |||
| Map<String, Object> res = new HashMap<>(); | |||
| res.put("data", requestData); | |||
| try { | |||
| // 发送POST请求到Argo工作流状态查询接口,并将请求数据转换为JSON | |||
| String req = HttpUtils.sendPost(argoUrl + argoWorkflowStatus, null, JsonUtils.mapToJson(res)); | |||
| // 检查响应是否为空或无内容 | |||
| if (req == null || StringUtils.isEmpty(req)) { | |||
| throw new RuntimeException("工作流状态响应为空。"); | |||
| } | |||
| // 将响应的JSON字符串转换为Map对象 | |||
| Map<String, Object> runResMap = JsonUtils.jsonToMap(req); | |||
| // 从响应Map中获取"data"部分 | |||
| Map<String, Object> data = (Map<String, Object>) runResMap.get("data"); | |||
| if (data == null || data.isEmpty()) { | |||
| throw new RuntimeException("工作流数据为空."); | |||
| } | |||
| // 从"data"中获取"status"部分,并返回"phase"的值 | |||
| Map<String, Object> status = (Map<String, Object>) data.get("status"); | |||
| if (status == null || status.isEmpty()) { | |||
| throw new RuntimeException("工作流状态为空。"); | |||
| } | |||
| //解析流水线结束时间 | |||
| String finishedAtString = (String) status.get("finishedAt"); | |||
| if (finishedAtString != null && !finishedAtString.isEmpty()) { | |||
| Date finishTime = DateUtils.convertUTCtoShanghaiDate(finishedAtString); | |||
| ins.setFinishTime(finishTime); | |||
| } | |||
| // 解析nodes字段,提取节点状态并转换为JSON字符串 | |||
| Map<String, Object> nodes = (Map<String, Object>) status.get("nodes"); | |||
| Map<String, Object> modifiedNodes = new LinkedHashMap<>(); | |||
| if (nodes != null) { | |||
| for (Map.Entry<String, Object> nodeEntry : nodes.entrySet()) { | |||
| Map<String, Object> nodeDetails = (Map<String, Object>) nodeEntry.getValue(); | |||
| String templateName = (String) nodeDetails.get("displayName"); | |||
| modifiedNodes.put(templateName, nodeDetails); | |||
| } | |||
| } | |||
| String nodeStatusJson = JsonUtils.mapToJson(modifiedNodes); | |||
| ins.setNodeStatus(nodeStatusJson); | |||
| //终止态为终止不改 | |||
| if (!StringUtils.equals(ins.getStatus(), Constant.Terminated)) { | |||
| ins.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : Constant.Pending); | |||
| } | |||
| if (StringUtils.equals(ins.getStatus(), Constant.Error)) { | |||
| ins.setStatus(Constant.Failed); | |||
| } | |||
| return ins; | |||
| } catch (Exception e) { | |||
| throw new RuntimeException("查询状态失败: " + e.getMessage(), e); | |||
| } | |||
| } | |||
| @Override | |||
| public List<ActiveLearnIns> queryActiveLearnInsIsNotTerminated() { | |||
| return activeLearnInsDao.queryActiveLearnInsIsNotTerminated(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,201 @@ | |||
| package com.ruoyi.platform.service.impl; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.platform.domain.ActiveLearn; | |||
| import com.ruoyi.platform.domain.ActiveLearnIns; | |||
| import com.ruoyi.platform.mapper.ActiveLearnDao; | |||
| import com.ruoyi.platform.mapper.ActiveLearnInsDao; | |||
| import com.ruoyi.platform.service.ActiveLearnInsService; | |||
| import com.ruoyi.platform.service.ActiveLearnService; | |||
| import com.ruoyi.platform.service.ResourceOccupyService; | |||
| import com.ruoyi.platform.utils.HttpUtils; | |||
| import com.ruoyi.platform.utils.JacksonUtil; | |||
| import com.ruoyi.platform.utils.JsonUtils; | |||
| import com.ruoyi.platform.vo.ActiveLearnParamVo; | |||
| import com.ruoyi.platform.vo.ActiveLearnVo; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import org.apache.commons.collections4.MapUtils; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.beans.BeanUtils; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| import java.util.ArrayList; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| @Service | |||
| public class ActiveLearnServiceImpl implements ActiveLearnService { | |||
| @Value("${argo.url}") | |||
| private String argoUrl; | |||
| @Value("${argo.workflowRun}") | |||
| private String argoWorkflowRun; | |||
| @Value("${argo.convertActiveLearn}") | |||
| String convertActiveLearn; | |||
| @Resource | |||
| private ResourceOccupyService resourceOccupyService; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private ActiveLearnInsDao activeLearnInsDao; | |||
| @Resource | |||
| private ActiveLearnInsService activeLearnInsService; | |||
| @Override | |||
| public Page<ActiveLearn> queryByPage(String name, PageRequest pageRequest) { | |||
| long total = activeLearnDao.count(name); | |||
| List<ActiveLearn> activeLearns = activeLearnDao.queryByPage(name, pageRequest); | |||
| return new PageImpl<>(activeLearns, pageRequest, total); | |||
| } | |||
| @Override | |||
| public ActiveLearn save(ActiveLearnVo activeLearnVo) throws Exception { | |||
| ActiveLearn activeLearnByName = activeLearnDao.getActiveLearnByName(activeLearnVo.getName()); | |||
| if (activeLearnByName != null) { | |||
| throw new RuntimeException("实验名称已存在"); | |||
| } | |||
| ActiveLearn activeLearn = new ActiveLearn(); | |||
| BeanUtils.copyProperties(activeLearnVo, activeLearn); | |||
| String username = SecurityUtils.getLoginUser().getUsername(); | |||
| activeLearn.setCreateBy(username); | |||
| activeLearn.setUpdateBy(username); | |||
| activeLearn.setDataset(JacksonUtil.toJSONString(activeLearnVo.getDataset())); | |||
| activeLearn.setCodeConfig(JacksonUtil.toJSONString(activeLearnVo.getCodeConfig())); | |||
| activeLearn.setModel(JacksonUtil.toJSONString(activeLearnVo.getModel())); | |||
| activeLearn.setImage(JacksonUtil.toJSONString(activeLearnVo.getImage())); | |||
| activeLearnDao.save(activeLearn); | |||
| return activeLearn; | |||
| } | |||
| @Override | |||
| public String edit(ActiveLearnVo activeLearnVo) throws Exception { | |||
| ActiveLearn oldActiveLearn = activeLearnDao.getActiveLearnByName(activeLearnVo.getName()); | |||
| if (oldActiveLearn != null && !oldActiveLearn.getId().equals(activeLearnVo.getId())) { | |||
| throw new RuntimeException("实验名称已存在"); | |||
| } | |||
| ActiveLearn activeLearn = new ActiveLearn(); | |||
| BeanUtils.copyProperties(activeLearnVo, activeLearn); | |||
| activeLearn.setUpdateBy(SecurityUtils.getLoginUser().getUsername()); | |||
| activeLearn.setDataset(JacksonUtil.toJSONString(activeLearnVo.getDataset())); | |||
| activeLearn.setCodeConfig(JacksonUtil.toJSONString(activeLearnVo.getCodeConfig())); | |||
| activeLearn.setModel(JacksonUtil.toJSONString(activeLearnVo.getModel())); | |||
| activeLearn.setImage(JacksonUtil.toJSONString(activeLearnVo.getImage())); | |||
| activeLearnDao.edit(activeLearn); | |||
| return "修改成功"; | |||
| } | |||
| @Override | |||
| public ActiveLearnVo getActiveLearnDetail(Long id) throws IOException { | |||
| ActiveLearn activeLearn = activeLearnDao.getActiveLearnById(id); | |||
| ActiveLearnVo activeLearnVo = new ActiveLearnVo(); | |||
| BeanUtils.copyProperties(activeLearn, activeLearnVo); | |||
| if (StringUtils.isNotEmpty(activeLearn.getDataset())) { | |||
| activeLearnVo.setDataset(JsonUtils.jsonToMap(activeLearn.getDataset())); | |||
| } | |||
| if (StringUtils.isNotEmpty(activeLearn.getCodeConfig())) { | |||
| activeLearnVo.setCodeConfig(JsonUtils.jsonToMap(activeLearn.getCodeConfig())); | |||
| } | |||
| if (StringUtils.isNotEmpty(activeLearn.getModel())) { | |||
| activeLearnVo.setModel(JsonUtils.jsonToMap(activeLearn.getModel())); | |||
| } | |||
| if (StringUtils.isNotEmpty(activeLearn.getImage())) { | |||
| activeLearnVo.setImage(JsonUtils.jsonToMap(activeLearn.getImage())); | |||
| } | |||
| return activeLearnVo; | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String delete(Long id) { | |||
| ActiveLearn activeLearn = activeLearnDao.getActiveLearnById(id); | |||
| if (activeLearn == null) { | |||
| throw new RuntimeException("实验不存在"); | |||
| } | |||
| String username = SecurityUtils.getLoginUser().getUsername(); | |||
| String createBy = activeLearn.getCreateBy(); | |||
| if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) { | |||
| throw new RuntimeException("无权限删除该实验"); | |||
| } | |||
| activeLearn.setState(Constant.State_invalid); | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_ActiveLearn, id, null); | |||
| return activeLearnDao.edit(activeLearn) > 0 ? "删除成功" : "删除失败"; | |||
| } | |||
| @Override | |||
| public String runActiveLearnIns(Long id) throws Exception { | |||
| ActiveLearn activeLearn = activeLearnDao.getActiveLearnById(id); | |||
| if (activeLearn == null) { | |||
| throw new Exception("主动学习配置不存在"); | |||
| } | |||
| // 记录开始扣积分 | |||
| if (resourceOccupyService.haveResource(activeLearn.getComputingResourceId(), 1)) { | |||
| ActiveLearnParamVo activeLearnParamVo = new ActiveLearnParamVo(); | |||
| BeanUtils.copyProperties(activeLearn, activeLearnParamVo); | |||
| activeLearnParamVo.setCodeConfig(JsonUtils.jsonToMap(activeLearn.getCodeConfig())); | |||
| activeLearnParamVo.setDataset(JsonUtils.jsonToMap(activeLearn.getDataset())); | |||
| activeLearnParamVo.setModel(JsonUtils.jsonToMap(activeLearn.getModel())); | |||
| activeLearnParamVo.setImage(JsonUtils.jsonToMap(activeLearn.getImage())); | |||
| String param = JsonUtils.getConvertParam(activeLearnParamVo); | |||
| // 调argo转换接口 | |||
| try { | |||
| String convertRes = HttpUtils.sendPost(argoUrl + convertActiveLearn, param); | |||
| if (convertRes == null || StringUtils.isEmpty(convertRes)) { | |||
| throw new RuntimeException("转换流水线失败"); | |||
| } | |||
| Map<String, Object> converMap = JsonUtils.jsonToMap(convertRes); | |||
| // 组装运行接口json | |||
| Map<String, Object> output = (Map<String, Object>) converMap.get("output"); | |||
| Map<String, Object> runReqMap = new HashMap<>(); | |||
| runReqMap.put("data", converMap.get("data")); | |||
| // 调argo运行接口 | |||
| String runRes = HttpUtils.sendPost(argoUrl + argoWorkflowRun, JsonUtils.mapToJson(runReqMap)); | |||
| if (runRes == null || StringUtils.isEmpty(runRes)) { | |||
| throw new RuntimeException("运行失败"); | |||
| } | |||
| Map<String, Object> runResMap = JsonUtils.jsonToMap(runRes); | |||
| Map<String, Object> data = (Map<String, Object>) runResMap.get("data"); | |||
| //判断data为空 | |||
| if (data == null || MapUtils.isEmpty(data)) { | |||
| throw new RuntimeException("运行失败"); | |||
| } | |||
| Map<String, Object> metadata = (Map<String, Object>) data.get("metadata"); | |||
| // 插入记录到实验实例表 | |||
| ActiveLearnIns activeLearnIns = new ActiveLearnIns(); | |||
| activeLearnIns.setActiveLearnId(id); | |||
| activeLearnIns.setArgoInsNs((String) metadata.get("namespace")); | |||
| activeLearnIns.setArgoInsName((String) metadata.get("name")); | |||
| activeLearnIns.setParam(param); | |||
| activeLearnIns.setStatus(Constant.Pending); | |||
| //替换argoInsName | |||
| String outputString = JsonUtils.mapToJson(output); | |||
| activeLearnIns.setNodeResult(outputString.replace("{{workflow.name}}", (String) metadata.get("name"))); | |||
| Map<String, Object> param_output = (Map<String, Object>) output.get("param_output"); | |||
| List output1 = (ArrayList) param_output.values().toArray()[0]; | |||
| Map<String, String> output2 = (Map<String, String>) output1.get(0); | |||
| String outputPath = output2.get("path").replace("{{workflow.name}}", (String) metadata.get("name")) + "/hpo"; | |||
| activeLearnIns.setResultPath(outputPath); | |||
| activeLearnInsDao.insert(activeLearnIns); | |||
| activeLearnInsService.updateActiveLearnStatus(id); | |||
| // 记录开始扣除积分 | |||
| resourceOccupyService.startDeduce(activeLearn.getComputingResourceId(), 1, Constant.TaskType_ActiveLearn, id, activeLearnIns.getId(), null, activeLearn.getName(), null, null); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| return "执行成功"; | |||
| } | |||
| } | |||
| @@ -146,7 +146,7 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { | |||
| if (!StringUtils.equals(ins.getStatus(), Constant.Terminated)) { | |||
| ins.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : Constant.Pending); | |||
| } | |||
| if (StringUtils.equals(ins.getStatus(), "Error")) { | |||
| if (StringUtils.equals(ins.getStatus(), Constant.Error)) { | |||
| ins.setStatus(Constant.Failed); | |||
| } | |||
| return ins; | |||
| @@ -2,11 +2,11 @@ package com.ruoyi.platform.service.impl; | |||
| import com.alibaba.fastjson2.JSON; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.platform.domain.ActiveLearn; | |||
| import com.ruoyi.platform.mapper.*; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import com.ruoyi.platform.domain.AssetWorkflow; | |||
| import com.ruoyi.platform.domain.CodeConfig; | |||
| import com.ruoyi.platform.mapper.AssetWorkflowDao; | |||
| import com.ruoyi.platform.mapper.CodeConfigDao; | |||
| import com.ruoyi.platform.service.CodeConfigService; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| @@ -15,7 +15,6 @@ import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import com.ruoyi.platform.domain.Ray; | |||
| import com.ruoyi.platform.mapper.RayDao; | |||
| import javax.annotation.Resource; | |||
| import java.util.Date; | |||
| @@ -32,6 +31,10 @@ public class CodeConfigServiceImpl implements CodeConfigService { | |||
| private AssetWorkflowDao assetWorkflowDao; | |||
| @Resource | |||
| private RayDao rayDao; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private ServiceDao serviceDao; | |||
| @Override | |||
| public Page<CodeConfig> queryByPage(CodeConfig codeConfig, PageRequest pageRequest) { | |||
| @@ -102,6 +105,18 @@ public class CodeConfigServiceImpl implements CodeConfigService { | |||
| throw new Exception("该代码配置被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| } | |||
| List<ActiveLearn> activeLearnList = activeLearnDao.queryByCodeConfig(JSON.toJSONString(map)); | |||
| if (activeLearnList != null && !activeLearnList.isEmpty()) { | |||
| String activeLearns = String.join(",", activeLearnList.stream().map(ActiveLearn::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该代码配置被主动学习:" + activeLearns + "使用,不能删除,请先删除主动学习。"); | |||
| } | |||
| List<String> serviceVersionList = serviceDao.queryByCodeConfig(JSON.toJSONString(map)); | |||
| if (serviceVersionList != null && !serviceVersionList.isEmpty()) { | |||
| String serviceVersions = String.join(",", serviceVersionList.stream().collect(Collectors.toSet())); | |||
| throw new Exception("该代码配置被服务版本:" + serviceVersions + "使用,不能删除,请先删除服务版本。"); | |||
| } | |||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||
| String username = loginUser.getUsername(); | |||
| String createBy = codeConfig.getCreateBy(); | |||
| @@ -6,9 +6,11 @@ import com.ruoyi.platform.domain.PodStatus; | |||
| import com.ruoyi.platform.mapper.DevEnvironmentDao; | |||
| import com.ruoyi.platform.service.DevEnvironmentService; | |||
| import com.ruoyi.platform.service.JupyterService; | |||
| import com.ruoyi.platform.service.ResourceOccupyService; | |||
| import com.ruoyi.platform.utils.JacksonUtil; | |||
| import com.ruoyi.platform.vo.DevEnvironmentVo; | |||
| import com.ruoyi.platform.vo.PodStatusVo; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.context.annotation.Lazy; | |||
| @@ -16,6 +18,7 @@ import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.util.Date; | |||
| @@ -35,6 +38,8 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { | |||
| @Resource | |||
| @Lazy | |||
| private JupyterService jupyterService; | |||
| @Resource | |||
| private ResourceOccupyService resourceOccupyService; | |||
| /** | |||
| * 通过ID查询单条数据 | |||
| @@ -89,27 +94,28 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { | |||
| */ | |||
| @Override | |||
| public DevEnvironment insert(DevEnvironmentVo devEnvironmentVo) throws Exception { | |||
| DevEnvironment devByName = devEnvironmentDao.getByName(devEnvironmentVo.getName()); | |||
| if (devByName != null) { | |||
| throw new RuntimeException("开发环境名称已存在"); | |||
| } | |||
| //插入预备,此时不需要判断版本重复 | |||
| DevEnvironment devEnvironment = new DevEnvironment(); | |||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||
| devEnvironment.setName(devEnvironmentVo.getName()); | |||
| //状态先设为未知 | |||
| devEnvironment.setStatus("Unknown"); | |||
| devEnvironment.setStatus(Constant.Unknown); | |||
| devEnvironment.setComputingResource(devEnvironmentVo.getComputingResource()); | |||
| devEnvironment.setComputingResourceId(devEnvironmentVo.getComputingResourceId()); | |||
| devEnvironment.setStandard(devEnvironmentVo.getStandard()); | |||
| devEnvironment.setEnvVariable(devEnvironmentVo.getEnvVariable()); | |||
| devEnvironment.setImage(devEnvironmentVo.getImage()); | |||
| // 将 dataset 和 model 转换成 JSON 字符串 | |||
| String datasetJson = JacksonUtil.toJSONString(devEnvironmentVo.getDataset()); | |||
| String modelJson = JacksonUtil.toJSONString(devEnvironmentVo.getModel()); | |||
| devEnvironment.setDataset(datasetJson); | |||
| devEnvironment.setModel(modelJson); | |||
| devEnvironment.setImage(JacksonUtil.toJSONString(devEnvironmentVo.getImage())); | |||
| devEnvironment.setDataset(JacksonUtil.toJSONString(devEnvironmentVo.getDataset())); | |||
| devEnvironment.setModel(JacksonUtil.toJSONString(devEnvironmentVo.getModel())); | |||
| devEnvironment.setCreateBy(loginUser.getUsername()); | |||
| devEnvironment.setUpdateBy(loginUser.getUsername()); | |||
| devEnvironment.setUpdateTime(new Date()); | |||
| devEnvironment.setCreateTime(new Date()); | |||
| devEnvironment.setState(1); | |||
| devEnvironment.setState(Constant.State_valid); | |||
| this.devEnvironmentDao.insert(devEnvironment); | |||
| return devEnvironment; | |||
| } | |||
| @@ -140,6 +146,7 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String removeById(Integer id) throws Exception { | |||
| DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); | |||
| if (devEnvironment == null) { | |||
| @@ -155,9 +162,8 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { | |||
| } | |||
| jupyterService.stopJupyterService(id); | |||
| devEnvironment.setState(0); | |||
| devEnvironment.setState(Constant.State_invalid); | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_Dev, Long.valueOf(id), null); | |||
| return this.devEnvironmentDao.update(devEnvironment) > 0 ? "删除成功" : "删除失败"; | |||
| } | |||
| } | |||
| @@ -2,7 +2,6 @@ package com.ruoyi.platform.service.impl; | |||
| import com.alibaba.fastjson2.JSON; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import com.ruoyi.platform.domain.DatasetTempStorage; | |||
| import com.ruoyi.platform.domain.Experiment; | |||
| import com.ruoyi.platform.domain.ExperimentIns; | |||
| @@ -16,6 +15,7 @@ import com.ruoyi.platform.service.ResourceOccupyService; | |||
| import com.ruoyi.platform.utils.*; | |||
| import com.ruoyi.platform.vo.LogRequestVo; | |||
| import com.ruoyi.platform.vo.PodLogVo; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| @@ -23,6 +23,7 @@ import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| @@ -204,6 +205,7 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String removeById(Integer id) { | |||
| ExperimentIns experimentIns = experimentInsDao.queryById(id); | |||
| if (experimentIns == null) { | |||
| @@ -227,6 +229,7 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| experimentIns.setState(0); | |||
| int update = this.experimentInsDao.update(experimentIns); | |||
| if (update > 0) { | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_Workflow, Long.valueOf(experimentIns.getExperimentId()), Long.valueOf(id)); | |||
| updateExperimentStatus(experimentIns.getExperimentId()); | |||
| return "删除成功"; | |||
| } else { | |||
| @@ -323,7 +326,7 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||
| if (!StringUtils.equals(experimentIns.getStatus(), "Terminated")) { | |||
| experimentIns.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : "Pending"); | |||
| } | |||
| if (StringUtils.equals(experimentIns.getStatus(), "Error")) { | |||
| if (StringUtils.equals(experimentIns.getStatus(), Constant.Error)) { | |||
| experimentIns.setStatus("Failed"); | |||
| } | |||
| @@ -28,6 +28,7 @@ import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.http.ResponseEntity; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| @@ -184,6 +185,7 @@ public class ExperimentServiceImpl implements ExperimentService { | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String removeById(Integer id) throws Exception { | |||
| Experiment experiment = experimentDao.queryById(id); | |||
| if (experiment == null) { | |||
| @@ -202,7 +204,8 @@ public class ExperimentServiceImpl implements ExperimentService { | |||
| if (experimentInsList != null && experimentInsList.size() > 0) { | |||
| throw new Exception("该实验存在实例,无法删除"); | |||
| } | |||
| experiment.setState(0); | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_Workflow, Long.valueOf(id), null); | |||
| experiment.setState(Constant.State_invalid); | |||
| return this.experimentDao.update(experiment) > 0 ? "删除成功" : "删除失败"; | |||
| @@ -11,6 +11,7 @@ import com.ruoyi.platform.service.ImageVersionService; | |||
| import com.ruoyi.platform.service.MinioService; | |||
| import com.ruoyi.platform.utils.DockerClientUtil; | |||
| import com.ruoyi.platform.utils.FileUtil; | |||
| import com.ruoyi.platform.utils.JacksonUtil; | |||
| import com.ruoyi.platform.utils.K8sClientUtil; | |||
| import com.ruoyi.platform.vo.ImageVo; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| @@ -54,8 +55,11 @@ public class ImageServiceImpl implements ImageService { | |||
| @Resource | |||
| private RayDao rayDao; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private ImageVersionService imageVersionService; | |||
| @Resource | |||
| private ServiceDao serviceDao; | |||
| @Resource | |||
| private K8sClientUtil k8sClientUtil; | |||
| @Resource | |||
| @@ -84,8 +88,6 @@ public class ImageServiceImpl implements ImageService { | |||
| private String image; | |||
| @Value("${dockerpush.mountPath}") | |||
| private String mountPath; | |||
| @Value("${dockerpush.proxyUrl}") | |||
| private String proxyUrl; | |||
| @Value("${jupyter.namespace}") | |||
| private String namespace; | |||
| @@ -184,6 +186,24 @@ public class ImageServiceImpl implements ImageService { | |||
| throw new Exception("该镜像被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| } | |||
| List<ActiveLearn> activeLearnList = activeLearnDao.queryByImageId(JSON.toJSONString(map)); | |||
| if (activeLearnList != null && !activeLearnList.isEmpty()) { | |||
| String activeLearns = String.join(",", activeLearnList.stream().map(ActiveLearn::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该镜像被主动学习:" + activeLearns + "使用,不能删除,请先删除主动学习。"); | |||
| } | |||
| List<DevEnvironment> devEnvironmentList = devEnvironmentDao.queryByImageId(JSON.toJSONString(map)); | |||
| if (devEnvironmentList != null && !devEnvironmentList.isEmpty()) { | |||
| String devEnvironments = String.join(",", devEnvironmentList.stream().map(DevEnvironment::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该镜像被开发环境:" + devEnvironments + "使用,不能删除,请先删除开发环境。"); | |||
| } | |||
| List<String> serviceVersionList = serviceDao.queryByImageId(JSON.toJSONString(map)); | |||
| if (serviceVersionList != null && !serviceVersionList.isEmpty()) { | |||
| String serviceVersions = String.join(",", serviceVersionList.stream().collect(Collectors.toSet())); | |||
| throw new Exception("该镜像被服务版本:" + serviceVersions + "使用,不能删除,请先删除服务版本。"); | |||
| } | |||
| //判断权限,只有admin和创建者本身可以删除该数据集 | |||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||
| String username = loginUser.getUsername(); | |||
| @@ -302,7 +322,7 @@ public class ImageServiceImpl implements ImageService { | |||
| V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | |||
| if (pod == null) { | |||
| String podName = deploymentName + "-" + DateUtils.formatYMD10(new Date()); | |||
| pod = k8sClientUtil.createPodWithEnv(podName, serviceNS, proxyUrl, mountPath, image); | |||
| pod = k8sClientUtil.createPodWithEnv(podName, serviceNS, mountPath, image); | |||
| } | |||
| String loginCmd = "docker login -u " + harborUser + " -p " + harborpassword + " " + harborUrl; | |||
| // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | |||
| @@ -345,7 +365,7 @@ public class ImageServiceImpl implements ImageService { | |||
| V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | |||
| if (pod == null) { | |||
| String podName = deploymentName + "-" + DateUtils.formatYMD10(new Date()); | |||
| pod = k8sClientUtil.createPodWithEnv(podName, serviceNS, proxyUrl, mountPath, image); | |||
| pod = k8sClientUtil.createPodWithEnv(podName, serviceNS, mountPath, image); | |||
| } | |||
| String loginCmd = "docker login -u " + harborUser + " -p " + harborpassword + " " + harborUrl; | |||
| // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | |||
| @@ -435,13 +455,22 @@ public class ImageServiceImpl implements ImageService { | |||
| imageVersion.setUpdateTime(new Date()); | |||
| imageVersion.setCreateTime(new Date()); | |||
| imageVersion.setState(Constant.State_valid); | |||
| imageVersion.setStatus("available"); | |||
| imageVersion.setStatus(Constant.Available); | |||
| imageVersionDao.insert(imageVersion); | |||
| //更新dev环境的镜像信息 | |||
| DevEnvironment devEnvironment = new DevEnvironment(); | |||
| devEnvironment.setId(imageVo.getDevEnvironmentId()); | |||
| devEnvironment.setImage(resultMap.get("imageName")); | |||
| imageVo.setValue(resultMap.get("imageName")); | |||
| imageVo.setVersion(String.valueOf(imageVersion.getId())); | |||
| resultMap.put("id", String.valueOf(oldImage.getId())); | |||
| resultMap.put("version", String.valueOf(imageVersion.getId())); | |||
| resultMap.put("value",resultMap.get("imageName")); | |||
| devEnvironment.setImage(JacksonUtil.toJSONString(resultMap)); | |||
| devEnvironmentDao.update(devEnvironment); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException("保存镜像失败:" + e); | |||
| @@ -3,12 +3,8 @@ package com.ruoyi.platform.service.impl; | |||
| import com.alibaba.fastjson2.JSON; | |||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.platform.domain.AssetWorkflow; | |||
| import com.ruoyi.platform.domain.ImageVersion; | |||
| import com.ruoyi.platform.domain.Ray; | |||
| import com.ruoyi.platform.mapper.AssetWorkflowDao; | |||
| import com.ruoyi.platform.mapper.ImageVersionDao; | |||
| import com.ruoyi.platform.mapper.RayDao; | |||
| import com.ruoyi.platform.domain.*; | |||
| import com.ruoyi.platform.mapper.*; | |||
| import com.ruoyi.platform.service.ImageVersionService; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| @@ -38,7 +34,12 @@ public class ImageVersionServiceImpl implements ImageVersionService { | |||
| private AssetWorkflowDao assetWorkflowDao; | |||
| @Resource | |||
| private RayDao rayDao; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private DevEnvironmentDao devEnvironmentDao; | |||
| @Resource | |||
| private ServiceDao serviceDao; | |||
| /** | |||
| * 通过ID查询单条数据 | |||
| * | |||
| @@ -96,6 +97,24 @@ public class ImageVersionServiceImpl implements ImageVersionService { | |||
| return GenericsAjaxResult.error("该镜像版本被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| } | |||
| List<ActiveLearn> activeLearnList = activeLearnDao.queryByImageId(JSON.toJSONString(map)); | |||
| if (activeLearnList != null && !activeLearnList.isEmpty()) { | |||
| String activeLearns = String.join(",", activeLearnList.stream().map(ActiveLearn::getName).collect(Collectors.toSet())); | |||
| return GenericsAjaxResult.error("该镜像版本被主动学习:" + activeLearns + "使用,不能删除,请先删除主动学习。"); | |||
| } | |||
| List<DevEnvironment> devEnvironmentList = devEnvironmentDao.queryByImageId(JSON.toJSONString(map)); | |||
| if (devEnvironmentList != null && !devEnvironmentList.isEmpty()) { | |||
| String devEnvironments = String.join(",", devEnvironmentList.stream().map(DevEnvironment::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该镜像版本被开发环境:" + devEnvironments + "使用,不能删除,请先删除开发环境。"); | |||
| } | |||
| List<String> serviceVersionList = serviceDao.queryByImageId(JSON.toJSONString(map)); | |||
| if (serviceVersionList != null && !serviceVersionList.isEmpty()) { | |||
| String serviceVersions = String.join(",", serviceVersionList.stream().collect(Collectors.toSet())); | |||
| throw new Exception("该镜像版本被服务版本:" + serviceVersions + "使用,不能删除,请先删除服务版本。"); | |||
| } | |||
| //判断权限,只有admin和创建者本身可以删除该数据集 | |||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||
| String username = loginUser.getUsername(); | |||
| @@ -68,7 +68,12 @@ public class ModelsServiceImpl implements ModelsService { | |||
| private RayDao rayDao; | |||
| @Resource | |||
| private ModelsVersionService modelsVersionService; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private DevEnvironmentDao devEnvironmentDao; | |||
| @Resource | |||
| private ServiceDao serviceDao; | |||
| @Resource | |||
| private ModelDependency1Dao modelDependency1Dao; | |||
| @@ -1167,6 +1172,24 @@ public class ModelsServiceImpl implements ModelsService { | |||
| throw new Exception("该模型被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| } | |||
| List<ActiveLearn> activeLearnList = activeLearnDao.queryByModelId(JSON.toJSONString(queryMap)); | |||
| if (activeLearnList != null && !activeLearnList.isEmpty()) { | |||
| String activeLearns = String.join(",", activeLearnList.stream().map(ActiveLearn::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该模型被主动学习:" + activeLearns + "使用,不能删除,请先删除主动学习。"); | |||
| } | |||
| List<DevEnvironment> devEnvironmentList = devEnvironmentDao.queryByModelId(JSON.toJSONString(queryMap)); | |||
| if (devEnvironmentList != null && !devEnvironmentList.isEmpty()) { | |||
| String devEnvironments = String.join(",", devEnvironmentList.stream().map(DevEnvironment::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该模型被开发环境:" + devEnvironments + "使用,不能删除,请先删除开发环境。"); | |||
| } | |||
| List<String> serviceVersionList = serviceDao.queryByModelId(JSON.toJSONString(queryMap)); | |||
| if (serviceVersionList != null && !serviceVersionList.isEmpty()) { | |||
| String serviceVersions = String.join(",", serviceVersionList.stream().collect(Collectors.toSet())); | |||
| throw new Exception("该模型被服务版本:" + serviceVersions + "使用,不能删除,请先删除服务版本。"); | |||
| } | |||
| String token = gitService.checkoutToken(); | |||
| gitService.deleteProject(token, owner, identifier); | |||
| //删除模型依赖 | |||
| @@ -1196,7 +1219,25 @@ public class ModelsServiceImpl implements ModelsService { | |||
| List<Ray> rayList = rayDao.queryByModelId(JSON.toJSONString(queryMap)); | |||
| if (rayList != null && !rayList.isEmpty()) { | |||
| String rays = String.join(",", rayList.stream().map(Ray::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该数据集版本被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| throw new Exception("该模型版本被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| } | |||
| List<ActiveLearn> activeLearnList = activeLearnDao.queryByModelId(JSON.toJSONString(queryMap)); | |||
| if (activeLearnList != null && !activeLearnList.isEmpty()) { | |||
| String activeLearns = String.join(",", activeLearnList.stream().map(ActiveLearn::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该模型版本被主动学习:" + activeLearns + "使用,不能删除,请先删除主动学习。"); | |||
| } | |||
| List<DevEnvironment> devEnvironmentList = devEnvironmentDao.queryByModelId(JSON.toJSONString(queryMap)); | |||
| if (devEnvironmentList != null && !devEnvironmentList.isEmpty()) { | |||
| String devEnvironments = String.join(",", devEnvironmentList.stream().map(DevEnvironment::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该模型版本被开发环境:" + devEnvironments + "使用,不能删除,请先删除开发环境。"); | |||
| } | |||
| List<String> serviceVersionList = serviceDao.queryByModelId(JSON.toJSONString(queryMap)); | |||
| if (serviceVersionList != null && !serviceVersionList.isEmpty()) { | |||
| String serviceVersions = String.join(",", serviceVersionList.stream().collect(Collectors.toSet())); | |||
| throw new Exception("该模型版本被服务版本:" + serviceVersions + "使用,不能删除,请先删除服务版本。"); | |||
| } | |||
| String token = gitService.checkoutToken(); | |||
| @@ -4,9 +4,7 @@ import com.alibaba.fastjson2.JSON; | |||
| import com.ruoyi.common.core.utils.DateUtils; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.platform.domain.*; | |||
| import com.ruoyi.platform.mapper.AssetWorkflowDao; | |||
| import com.ruoyi.platform.mapper.AutoMlDao; | |||
| import com.ruoyi.platform.mapper.RayDao; | |||
| import com.ruoyi.platform.mapper.*; | |||
| import com.ruoyi.platform.service.DatasetTempStorageService; | |||
| import com.ruoyi.platform.service.GitService; | |||
| import com.ruoyi.platform.service.NewDatasetService; | |||
| @@ -56,6 +54,10 @@ public class NewDatasetServiceImpl implements NewDatasetService { | |||
| private AutoMlDao autoMlDao; | |||
| @Resource | |||
| private RayDao rayDao; | |||
| @Resource | |||
| private ActiveLearnDao activeLearnDao; | |||
| @Resource | |||
| private DevEnvironmentDao devEnvironmentDao; | |||
| @Value("${spring.redis.host}") | |||
| private String redisHost; | |||
| @@ -430,6 +432,18 @@ public class NewDatasetServiceImpl implements NewDatasetService { | |||
| throw new Exception("该数据集被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| } | |||
| List<ActiveLearn> activeLearnList = activeLearnDao.queryByDatasetId(JSON.toJSONString(map)); | |||
| if (activeLearnList != null && !activeLearnList.isEmpty()) { | |||
| String activeLearns = String.join(",", activeLearnList.stream().map(ActiveLearn::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该数据集被主动学习:" + activeLearns + "使用,不能删除,请先删除主动学习。"); | |||
| } | |||
| List<DevEnvironment> devEnvironmentList = devEnvironmentDao.queryByDatasetId(JSON.toJSONString(map)); | |||
| if (devEnvironmentList != null && !devEnvironmentList.isEmpty()) { | |||
| String devEnvironments = String.join(",", devEnvironmentList.stream().map(DevEnvironment::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该数据集被开发环境:" + devEnvironments + "使用,不能删除,请先删除开发环境。"); | |||
| } | |||
| String token = gitService.checkoutToken(); | |||
| gitService.deleteProject(token, owner, repo); | |||
| @@ -461,6 +475,18 @@ public class NewDatasetServiceImpl implements NewDatasetService { | |||
| throw new Exception("该数据集版本被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||
| } | |||
| List<ActiveLearn> activeLearnList = activeLearnDao.queryByDatasetId(JSON.toJSONString(map)); | |||
| if (activeLearnList != null && !activeLearnList.isEmpty()) { | |||
| String activeLearns = String.join(",", activeLearnList.stream().map(ActiveLearn::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该数据集版本被主动学习:" + activeLearns + "使用,不能删除,请先删除主动学习。"); | |||
| } | |||
| List<DevEnvironment> devEnvironmentList = devEnvironmentDao.queryByDatasetId(JSON.toJSONString(map)); | |||
| if (devEnvironmentList != null && !devEnvironmentList.isEmpty()) { | |||
| String devEnvironments = String.join(",", devEnvironmentList.stream().map(DevEnvironment::getName).collect(Collectors.toSet())); | |||
| throw new Exception("该数据集版本被开发环境:" + devEnvironments + "使用,不能删除,请先删除开发环境。"); | |||
| } | |||
| String token = gitService.checkoutToken(); | |||
| String rootPath = Paths.get(localPathlocal + "/" + relativePath).getParent().toString(); | |||
| gitService.deleteBranch(token, owner, repo, version, rootPath); | |||
| @@ -1,6 +1,5 @@ | |||
| package com.ruoyi.platform.service.impl; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import com.ruoyi.platform.domain.Ray; | |||
| import com.ruoyi.platform.domain.RayIns; | |||
| import com.ruoyi.platform.mapper.RayDao; | |||
| @@ -8,6 +7,7 @@ import com.ruoyi.platform.mapper.RayInsDao; | |||
| import com.ruoyi.platform.service.RayInsService; | |||
| import com.ruoyi.platform.service.ResourceOccupyService; | |||
| import com.ruoyi.platform.utils.*; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| @@ -16,6 +16,7 @@ import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| @@ -69,6 +70,7 @@ public class RayInsServiceImpl implements RayInsService { | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String deleteById(Long id) { | |||
| RayIns rayIns = rayInsDao.queryById(id); | |||
| if (rayIns == null) { | |||
| @@ -84,6 +86,7 @@ public class RayInsServiceImpl implements RayInsService { | |||
| rayIns.setState(Constant.State_invalid); | |||
| int update = rayInsDao.update(rayIns); | |||
| if (update > 0) { | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_Ray, rayIns.getRayId(), id); | |||
| updateRayStatus(rayIns.getRayId()); | |||
| return "删除成功"; | |||
| } else { | |||
| @@ -264,7 +267,7 @@ public class RayInsServiceImpl implements RayInsService { | |||
| if (!StringUtils.equals(ins.getStatus(), Constant.Terminated)) { | |||
| ins.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : Constant.Pending); | |||
| } | |||
| if (StringUtils.equals(ins.getStatus(), "Error")) { | |||
| if (StringUtils.equals(ins.getStatus(), Constant.Error)) { | |||
| ins.setStatus(Constant.Failed); | |||
| } | |||
| return ins; | |||
| @@ -3,7 +3,6 @@ package com.ruoyi.platform.service.impl; | |||
| import com.google.gson.Gson; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import com.ruoyi.platform.domain.Ray; | |||
| import com.ruoyi.platform.domain.RayIns; | |||
| import com.ruoyi.platform.mapper.RayDao; | |||
| @@ -16,6 +15,7 @@ import com.ruoyi.platform.utils.JacksonUtil; | |||
| import com.ruoyi.platform.utils.JsonUtils; | |||
| import com.ruoyi.platform.vo.RayParamVo; | |||
| import com.ruoyi.platform.vo.RayVo; | |||
| import com.ruoyi.system.api.constant.Constant; | |||
| import org.apache.commons.collections4.MapUtils; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.beans.BeanUtils; | |||
| @@ -24,6 +24,7 @@ import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| @@ -131,6 +132,7 @@ public class RayServiceImpl implements RayService { | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String delete(Long id) { | |||
| Ray ray = rayDao.getRayById(id); | |||
| if (ray == null) { | |||
| @@ -142,6 +144,7 @@ public class RayServiceImpl implements RayService { | |||
| throw new RuntimeException("无权限删除该实验"); | |||
| } | |||
| ray.setState(Constant.State_invalid); | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_Ray, id, null); | |||
| return rayDao.edit(ray) > 0 ? "删除成功" : "删除失败"; | |||
| } | |||
| @@ -156,12 +159,11 @@ public class RayServiceImpl implements RayService { | |||
| if (resourceOccupyService.haveResource(ray.getComputingResourceId(), 1)) { | |||
| RayParamVo rayParamVo = new RayParamVo(); | |||
| BeanUtils.copyProperties(ray, rayParamVo); | |||
| rayParamVo.setComputingResourceId(ray.getComputingResourceId()); | |||
| rayParamVo.setCodeConfig(JsonUtils.jsonToMap(ray.getCodeConfig())); | |||
| rayParamVo.setDataset(JsonUtils.jsonToMap(ray.getDataset())); | |||
| rayParamVo.setModel(JsonUtils.jsonToMap(ray.getModel())); | |||
| rayParamVo.setImage(JsonUtils.jsonToMap(ray.getImage())); | |||
| String param = JsonUtils.objectToJson(rayParamVo); | |||
| String param = JsonUtils.getConvertParam(rayParamVo); | |||
| // 调argo转换接口 | |||
| try { | |||
| @@ -189,7 +191,7 @@ public class RayServiceImpl implements RayService { | |||
| // 插入记录到实验实例表 | |||
| RayIns rayIns = new RayIns(); | |||
| rayIns.setRayId(ray.getId()); | |||
| rayIns.setRayId(id); | |||
| rayIns.setArgoInsNs((String) metadata.get("namespace")); | |||
| rayIns.setArgoInsName((String) metadata.get("name")); | |||
| rayIns.setParam(param); | |||
| @@ -168,4 +168,9 @@ public class ResourceOccupyServiceImpl implements ResourceOccupyService { | |||
| resourceOccupy.setComputingResourceId(computingResourceId); | |||
| resourceOccupyDao.edit(resourceOccupy); | |||
| } | |||
| @Override | |||
| public void deleteTaskState(String taskType, Long taskId, Long taskInsId) { | |||
| resourceOccupyDao.deleteTaskState(taskType,taskId,taskInsId); | |||
| } | |||
| } | |||
| @@ -14,6 +14,7 @@ import com.ruoyi.platform.service.ServiceService; | |||
| import com.ruoyi.platform.utils.ConvertUtil; | |||
| import com.ruoyi.platform.utils.HttpUtils; | |||
| import com.ruoyi.platform.utils.JacksonUtil; | |||
| import com.ruoyi.platform.utils.JsonUtils; | |||
| import com.ruoyi.platform.vo.serviceVos.ServiceCodeConfigVo; | |||
| import com.ruoyi.platform.vo.serviceVos.ServiceModelVo; | |||
| import com.ruoyi.platform.vo.serviceVos.ServiceVersionVo; | |||
| @@ -26,8 +27,10 @@ import org.springframework.data.domain.Page; | |||
| import org.springframework.data.domain.PageImpl; | |||
| import org.springframework.data.domain.PageRequest; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import javax.annotation.Resource; | |||
| import java.io.IOException; | |||
| import java.net.MalformedURLException; | |||
| import java.net.URL; | |||
| import java.util.ArrayList; | |||
| @@ -64,7 +67,7 @@ public class ServiceServiceImpl implements ServiceService { | |||
| } | |||
| @Override | |||
| public Page<ServiceVersionVo> queryByPageServiceVersion(ServiceVersion serviceVersion, int page, int size) { | |||
| public Page<ServiceVersionVo> queryByPageServiceVersion(ServiceVersion serviceVersion, int page, int size) throws IOException { | |||
| PageRequest pageRequest; | |||
| if (StringUtils.isNotEmpty(serviceVersion.getRunState())) { | |||
| pageRequest = PageRequest.of(page, Integer.MAX_VALUE); | |||
| @@ -169,7 +172,7 @@ public class ServiceServiceImpl implements ServiceService { | |||
| } | |||
| @Override | |||
| public ServiceVersionVo getServiceVersion(Long id) { | |||
| public ServiceVersionVo getServiceVersion(Long id) throws IOException { | |||
| ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id); | |||
| ServiceVersionVo serviceVersionVo = getServiceVersionVo(serviceVersion); | |||
| com.ruoyi.platform.domain.Service service = serviceDao.getServiceById(serviceVersion.getServiceId()); | |||
| @@ -195,7 +198,7 @@ public class ServiceServiceImpl implements ServiceService { | |||
| } | |||
| @Override | |||
| public Map<String, Object> serviceVersionCompare(Long id1, Long id2) throws IllegalAccessException { | |||
| public Map<String, Object> serviceVersionCompare(Long id1, Long id2) throws IllegalAccessException, IOException { | |||
| HashMap<String, Object> result = new HashMap<>(); | |||
| ServiceVersion serviceVersion1 = serviceDao.getServiceVersionById(id1); | |||
| @@ -243,11 +246,13 @@ public class ServiceServiceImpl implements ServiceService { | |||
| throw new RuntimeException("该服务下还有版本,不能删除"); | |||
| } | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_Service, id, null); | |||
| service.setState(Constant.State_invalid); | |||
| return serviceDao.updateService(service) > 0 ? "删除成功" : "删除失败"; | |||
| } | |||
| @Override | |||
| @Transactional | |||
| public String deleteServiceVersion(Long id) { | |||
| ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id); | |||
| serviceVersion.setState(Constant.State_invalid); | |||
| @@ -260,6 +265,7 @@ public class ServiceServiceImpl implements ServiceService { | |||
| // if ((Integer) reqMap.get("code") == 200) { | |||
| // 结束扣积分 | |||
| resourceOccupyService.endDeduce(Constant.TaskType_Service, null, id, null, null); | |||
| resourceOccupyService.deleteTaskState(Constant.TaskType_Service, serviceVersion.getServiceId(), id); | |||
| return serviceDao.updateServiceVersion(serviceVersion) > 0 ? "删除成功" : "删除失败"; | |||
| // } | |||
| } | |||
| @@ -278,7 +284,8 @@ public class ServiceServiceImpl implements ServiceService { | |||
| paramMap.put("replicas", serviceVersion.getReplicas()); | |||
| paramMap.put("env", JSONObject.parseObject(serviceVersion.getEnvVariables())); | |||
| paramMap.put("code_config", JSONObject.parseObject(serviceVersion.getCodeConfig())); | |||
| paramMap.put("image", serviceVersion.getImage()); | |||
| String image = (String)JsonUtils.jsonToMap(serviceVersion.getImage()).get("value"); | |||
| paramMap.put("image", image); | |||
| paramMap.put("model", JSONObject.parseObject(serviceVersion.getModel())); | |||
| paramMap.put("service_type", service.getServiceType()); | |||
| paramMap.put("deploy_type", serviceVersion.getDeployType()); | |||
| @@ -401,11 +408,12 @@ public class ServiceServiceImpl implements ServiceService { | |||
| serviceVersion.setModel(JSON.toJSONString(serviceVersionVo.getModel())); | |||
| serviceVersion.setCodeConfig(JSON.toJSONString(serviceVersionVo.getCodeConfig())); | |||
| serviceVersion.setEnvVariables(JSON.toJSONString(serviceVersionVo.getEnvVariables())); | |||
| serviceVersion.setImage(JacksonUtil.toJSONString(serviceVersionVo.getImage())); | |||
| return serviceVersion; | |||
| } | |||
| List<ServiceVersionVo> getServiceVersionVoList(List<ServiceVersion> serviceVersionList) { | |||
| List<ServiceVersionVo> getServiceVersionVoList(List<ServiceVersion> serviceVersionList) throws IOException { | |||
| List<ServiceVersionVo> result = new ArrayList<>(); | |||
| for (ServiceVersion sv : serviceVersionList) { | |||
| @@ -415,7 +423,7 @@ public class ServiceServiceImpl implements ServiceService { | |||
| return result; | |||
| } | |||
| ServiceVersionVo getServiceVersionVo(ServiceVersion sv) { | |||
| ServiceVersionVo getServiceVersionVo(ServiceVersion sv) throws IOException { | |||
| ServiceVersionVo serviceVersionVo = new ServiceVersionVo(); | |||
| BeanUtils.copyProperties(sv, serviceVersionVo); | |||
| ServiceModelVo serviceModelVo = JSON.parseObject(sv.getModel(), ServiceModelVo.class); | |||
| @@ -425,6 +433,7 @@ public class ServiceServiceImpl implements ServiceService { | |||
| serviceVersionVo.setCodeConfig(serviceCodeConfigVo); | |||
| serviceVersionVo.setEnvVariables(JacksonUtil.parseJSONStr2Map(sv.getEnvVariables())); | |||
| serviceVersionVo.setImage(JsonUtils.jsonToMap(sv.getImage())); | |||
| return serviceVersionVo; | |||
| } | |||
| @@ -1,11 +1,12 @@ | |||
| package com.ruoyi.platform.utils; | |||
| import com.alibaba.fastjson2.JSON; | |||
| import com.fasterxml.jackson.core.JsonProcessingException; | |||
| import com.fasterxml.jackson.databind.ObjectMapper; | |||
| import org.json.JSONObject; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import java.io.IOException; | |||
| import java.util.HashMap; | |||
| import java.util.Iterator; | |||
| import java.util.Map; | |||
| public class JsonUtils { | |||
| @@ -33,9 +34,8 @@ public class JsonUtils { | |||
| } | |||
| // 将JSON字符串转换为扁平化的Map | |||
| public static Map<String, Object> flattenJson(String prefix, Map<String, Object> map) { | |||
| public static Map<String, Object> flattenJson(String prefix, Map<String, Object> map) { | |||
| Map<String, Object> flatMap = new HashMap<>(); | |||
| for (Map.Entry<String, Object> entry : map.entrySet()) { | |||
| @@ -56,4 +56,17 @@ public class JsonUtils { | |||
| public static Map<String, Object> objectToMap(Object object) throws IOException { | |||
| return objectMapper.convertValue(object, Map.class); | |||
| } | |||
| public static String getConvertParam(Object object) throws JsonProcessingException { | |||
| HashMap<Object, Object> paramMap = new HashMap<>(); | |||
| paramMap.put("data", JSON.parseObject(objectToJson(object), Map.class)); | |||
| HashMap<Object, Object> userInfoMap = new HashMap<>(); | |||
| userInfoMap.put("name", SecurityUtils.getLoginUser().getUsername()); | |||
| userInfoMap.put("token", SecurityUtils.getLoginUser().getSysUser().getOriginPassword()); | |||
| HashMap<Object, Object> extraInfoMap = new HashMap<>(); | |||
| extraInfoMap.put("user_info", userInfoMap); | |||
| paramMap.put("extra_info", extraInfoMap); | |||
| return objectToJson(paramMap); | |||
| } | |||
| } | |||
| @@ -23,6 +23,7 @@ import org.springframework.stereotype.Component; | |||
| import javax.annotation.Resource; | |||
| import java.io.BufferedReader; | |||
| import java.io.IOException; | |||
| import java.io.InputStreamReader; | |||
| import java.util.*; | |||
| @@ -40,6 +41,8 @@ public class K8sClientUtil { | |||
| private String hostPath; | |||
| @Value("${dockerpush.proxyUrl}") | |||
| private String proxyUrl; | |||
| @Value("${proxy.useProxy:false}") | |||
| private boolean useProxy; | |||
| @Value("${git.localPath}") | |||
| String localPathlocal; | |||
| @@ -321,7 +324,6 @@ public class K8sClientUtil { | |||
| * @param port port | |||
| * @param mountPath 映射路径 | |||
| * @param subPath pvc子路径 | |||
| * @param pvcName 存储名 | |||
| * @param image 镜像 | |||
| * @return 创建成功的pod,的nodePort端口 | |||
| */ | |||
| @@ -352,40 +354,68 @@ public class K8sClientUtil { | |||
| int lastIndex = hostPath.lastIndexOf('/'); | |||
| String newPath = hostPath.substring(0, lastIndex); | |||
| V1Pod pod = new V1PodBuilder() | |||
| .withNewMetadata() | |||
| .withName(podName) | |||
| .withLabels(selector) | |||
| .endMetadata() | |||
| .withNewSpec() | |||
| .addNewContainer() | |||
| .withName(podName) | |||
| .withImage(image) | |||
| .withPorts(new V1ContainerPort().containerPort(port).protocol("TCP")) | |||
| .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath).subPath(subPath)) | |||
| .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() | |||
| .addNewEnv() | |||
| .withName("HTTP_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("HTTPS_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("NO_PROXY") | |||
| .withValue("localhost,kubernetes.default.svc") | |||
| .endEnv() | |||
| .endContainer() | |||
| .addNewVolume() | |||
| .withName("workspace") | |||
| .withHostPath(new V1HostPathVolumeSource().path(newPath).type("DirectoryOrCreate")) | |||
| V1Pod pod; | |||
| if (useProxy) { | |||
| pod = new V1PodBuilder() | |||
| .withNewMetadata() | |||
| .withName(podName) | |||
| .withLabels(selector) | |||
| .endMetadata() | |||
| .withNewSpec() | |||
| .addNewContainer() | |||
| .withName(podName) | |||
| .withImage(image) | |||
| .withPorts(new V1ContainerPort().containerPort(port).protocol("TCP")) | |||
| .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath).subPath(subPath)) | |||
| .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() | |||
| .addNewEnv() | |||
| .withName("HTTP_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("HTTPS_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("NO_PROXY") | |||
| .withValue("localhost,kubernetes.default.svc") | |||
| .endEnv() | |||
| .endContainer() | |||
| .addNewVolume() | |||
| .withName("workspace") | |||
| .withHostPath(new V1HostPathVolumeSource().path(newPath).type("DirectoryOrCreate")) | |||
| // .withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName)) | |||
| .endVolume() | |||
| .withTerminationGracePeriodSeconds(14400L) | |||
| .endSpec() | |||
| .build(); | |||
| .endVolume() | |||
| .withTerminationGracePeriodSeconds(14400L) | |||
| .endSpec() | |||
| .build(); | |||
| } else { | |||
| pod = new V1PodBuilder() | |||
| .withNewMetadata() | |||
| .withName(podName) | |||
| .withLabels(selector) | |||
| .endMetadata() | |||
| .withNewSpec() | |||
| .addNewContainer() | |||
| .withName(podName) | |||
| .withImage(image) | |||
| .withPorts(new V1ContainerPort().containerPort(port).protocol("TCP")) | |||
| .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath).subPath(subPath)) | |||
| .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() | |||
| .addNewEnv() | |||
| .withName("NO_PROXY") | |||
| .withValue("localhost,kubernetes.default.svc") | |||
| .endEnv() | |||
| .endContainer() | |||
| .addNewVolume() | |||
| .withName("workspace") | |||
| .withHostPath(new V1HostPathVolumeSource().path(newPath).type("DirectoryOrCreate")) | |||
| // .withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName)) | |||
| .endVolume() | |||
| .withTerminationGracePeriodSeconds(14400L) | |||
| .endSpec() | |||
| .build(); | |||
| } | |||
| try { | |||
| pod = api.createNamespacedPod(namespace, pod, null, null, null); | |||
| @@ -400,7 +430,7 @@ public class K8sClientUtil { | |||
| } | |||
| // 创建配置好的Pod | |||
| public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, DevEnvironment devEnvironment, String dataPvcName, String datasetPath, String modelPath) { | |||
| public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, DevEnvironment devEnvironment, String dataPvcName, String datasetPath, String modelPath) throws IOException { | |||
| //设置选择节点,pod反亲和性 | |||
| Map<String, String> selector = new LinkedHashMap<>(); | |||
| @@ -494,6 +524,8 @@ public class K8sClientUtil { | |||
| //配置资源 | |||
| V1ResourceRequirements v1ResourceRequirements = setPodResource(devEnvironment.getComputingResourceId()); | |||
| String image = (String) JsonUtils.jsonToMap(devEnvironment.getImage()).get("value"); | |||
| V1Pod pod = new V1PodBuilder() | |||
| .withNewMetadata() | |||
| .withName(podName) | |||
| @@ -502,7 +534,7 @@ public class K8sClientUtil { | |||
| .withNewSpec() | |||
| .addNewContainer() | |||
| .withName(podName) | |||
| .withImage(devEnvironment.getImage()) | |||
| .withImage(image) | |||
| .withPorts(new V1ContainerPort().containerPort(port).protocol("TCP")) | |||
| .withVolumeMounts(volumeMounts) | |||
| .withResources(v1ResourceRequirements) | |||
| @@ -641,7 +673,7 @@ public class K8sClientUtil { | |||
| } | |||
| public V1Pod createPodWithEnv(String podName, String namespace, String proxyUrl, String mountPath, String image) { | |||
| public V1Pod createPodWithEnv(String podName, String namespace, String mountPath, String image) { | |||
| CoreV1Api api = new CoreV1Api(apiClient); | |||
| V1SecurityContext v1SecurityContext = new V1SecurityContext(); | |||
| @@ -653,38 +685,65 @@ public class K8sClientUtil { | |||
| List<V1Volume> volumes = new ArrayList<>(); | |||
| volumes.add(new V1Volume().name("workspace").hostPath(new V1HostPathVolumeSource().path(hostPath + "/images").type("DirectoryOrCreate"))); | |||
| V1Pod pod = new V1PodBuilder() | |||
| .withNewMetadata() | |||
| .withName(podName) | |||
| .endMetadata() | |||
| .withNewSpec() | |||
| .addNewContainer() | |||
| .withName(podName) | |||
| .withImage(image) // 替换为您实际要使用的镜像名称 | |||
| .withSecurityContext(v1SecurityContext) | |||
| V1Pod pod; | |||
| if (useProxy) { | |||
| pod = new V1PodBuilder() | |||
| .withNewMetadata() | |||
| .withName(podName) | |||
| .endMetadata() | |||
| .withNewSpec() | |||
| .addNewContainer() | |||
| .withName(podName) | |||
| .withImage(image) // 替换为您实际要使用的镜像名称 | |||
| .withSecurityContext(v1SecurityContext) | |||
| // .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath)) | |||
| .withVolumeMounts(volumeMounts) | |||
| .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() | |||
| .addNewEnv() | |||
| .withName("HTTP_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("HTTPS_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("NO_PROXY") | |||
| .withValue("localhost,kubernetes.default.svc") | |||
| .endEnv() | |||
| .endContainer() | |||
| .withVolumeMounts(volumeMounts) | |||
| .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() | |||
| .addNewEnv() | |||
| .withName("HTTP_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("HTTPS_PROXY") | |||
| .withValue(proxyUrl) | |||
| .endEnv() | |||
| .addNewEnv() | |||
| .withName("NO_PROXY") | |||
| .withValue("localhost,kubernetes.default.svc") | |||
| .endEnv() | |||
| .endContainer() | |||
| // .addNewVolume() | |||
| // .withName("workspace").withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName)) | |||
| // .endVolume() | |||
| .withVolumes(volumes) | |||
| .endSpec() | |||
| .build(); | |||
| .withVolumes(volumes) | |||
| .endSpec() | |||
| .build(); | |||
| } else { | |||
| pod = new V1PodBuilder() | |||
| .withNewMetadata() | |||
| .withName(podName) | |||
| .endMetadata() | |||
| .withNewSpec() | |||
| .addNewContainer() | |||
| .withName(podName) | |||
| .withImage(image) // 替换为您实际要使用的镜像名称 | |||
| .withSecurityContext(v1SecurityContext) | |||
| // .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath)) | |||
| .withVolumeMounts(volumeMounts) | |||
| .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() | |||
| .addNewEnv() | |||
| .withName("NO_PROXY") | |||
| .withValue("localhost,kubernetes.default.svc") | |||
| .endEnv() | |||
| .endContainer() | |||
| // .addNewVolume() | |||
| // .withName("workspace").withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName)) | |||
| // .endVolume() | |||
| .withVolumes(volumes) | |||
| .endSpec() | |||
| .build(); | |||
| } | |||
| try { | |||
| pod = api.createNamespacedPod(namespace, pod, null, null, null); | |||
| } catch (ApiException e) { | |||
| @@ -0,0 +1,67 @@ | |||
| package com.ruoyi.platform.vo; | |||
| import com.fasterxml.jackson.annotation.JsonInclude; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import io.swagger.annotations.ApiModel; | |||
| import lombok.Data; | |||
| import java.util.Map; | |||
| @Data | |||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||
| @JsonInclude(JsonInclude.Include.NON_NULL) | |||
| @ApiModel(description = "主动学习参数") | |||
| public class ActiveLearnParamVo { | |||
| private Map<String,Object> codeConfig; | |||
| private Map<String,Object> dataset; | |||
| private Map<String,Object> image; | |||
| private Map<String,Object> model; | |||
| private String taskType; | |||
| private String frameworkType; | |||
| private String modelPy; | |||
| private String modelClassName; | |||
| private String classifierAlg; | |||
| private String regressorAlg; | |||
| private String datasetPy; | |||
| private String datasetClassName; | |||
| private Integer dataSize; | |||
| private Integer computingResourceId; | |||
| private Boolean shuffle; | |||
| private Integer trainSize; | |||
| private Integer initialNum; | |||
| private Integer queriesNum; | |||
| private Integer instancesNum; | |||
| private String queryStrategy; | |||
| private String lossPy; | |||
| private String lossClassName; | |||
| private Integer checkpointNum; | |||
| private Integer batchSize; | |||
| private Integer epochs; | |||
| private Float lr; | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| package com.ruoyi.platform.vo; | |||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||
| import io.swagger.annotations.ApiModel; | |||
| import io.swagger.annotations.ApiModelProperty; | |||
| import lombok.Data; | |||
| import java.util.Date; | |||
| import java.util.Map; | |||
| @Data | |||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||
| @ApiModel(description = "主动学习") | |||
| public class ActiveLearnVo { | |||
| private Long id; | |||
| @ApiModelProperty(value = "实验名称") | |||
| private String name; | |||
| @ApiModelProperty(value = "实验描述") | |||
| private String description; | |||
| @ApiModelProperty(value = "任务类型:classification或regression") | |||
| private String taskType; | |||
| @ApiModelProperty(value = "框架类型: sklearn, keras, pytorch") | |||
| private String frameworkType; | |||
| @ApiModelProperty(value = "代码") | |||
| private Map<String, Object> codeConfig; | |||
| @ApiModelProperty(value = "预训练的模型") | |||
| private Map<String, Object> model; | |||
| @ApiModelProperty(value = "模型文件路径") | |||
| private String modelPy; | |||
| @ApiModelProperty(value = "类名称") | |||
| private String modelClassName; | |||
| @ApiModelProperty(value = "分类算法") | |||
| private String classifierAlg; | |||
| @ApiModelProperty(value = "回归算法") | |||
| private String regressorAlg; | |||
| @ApiModelProperty(value = "dataset文件路径") | |||
| private String datasetPy; | |||
| @ApiModelProperty(value = "dataset类名") | |||
| private String datasetClassName; | |||
| @ApiModelProperty(value = "数据集文件路径") | |||
| private Map<String, Object> dataset; | |||
| @ApiModelProperty(value = "数据量") | |||
| private Integer dataSize; | |||
| @ApiModelProperty(value = "镜像") | |||
| private Map<String, Object> image; | |||
| @ApiModelProperty(value = "计算资源id") | |||
| private Integer computingResourceId; | |||
| @ApiModelProperty(value = "是否随机打乱") | |||
| private Boolean shuffle; | |||
| @ApiModelProperty(value = "训练集数据量") | |||
| private Integer trainSize; | |||
| @ApiModelProperty(value = "初始训练数据量") | |||
| private Integer initialNum; | |||
| @ApiModelProperty(value = "查询次数") | |||
| private Integer queriesNum; | |||
| @ApiModelProperty(value = "每次查询数据量") | |||
| private Integer instancesNum; | |||
| @ApiModelProperty(value = "查询策略:uncertainty_sampling, uncertainty_batch_sampling, max_std_sampling, expected_improvement, upper_confidence_bound") | |||
| private String queryStrategy; | |||
| @ApiModelProperty(value = "loss文件路径") | |||
| private String lossPy; | |||
| @ApiModelProperty(value = "loss类名") | |||
| private String lossClassName; | |||
| @ApiModelProperty(value = "多少轮查询保存一次模型参数") | |||
| private Integer checkpointNum; | |||
| @ApiModelProperty(value = "batch_size") | |||
| private Integer batchSize; | |||
| @ApiModelProperty(value = "epochs") | |||
| private Integer epochs; | |||
| @ApiModelProperty(value = "学习率") | |||
| private Float lr; | |||
| private String createBy; | |||
| private String updateBy; | |||
| private Date createTime; | |||
| private Date updateTime; | |||
| private Integer state; | |||
| @ApiModelProperty(value = "状态列表") | |||
| private String statusList; | |||
| } | |||
| @@ -34,7 +34,7 @@ public class DevEnvironmentVo implements Serializable { | |||
| /** | |||
| * 所用镜像 | |||
| */ | |||
| private String image; | |||
| private Map<String,Object> image; | |||
| /** | |||
| * 对应数据集 | |||
| */ | |||
| @@ -39,8 +39,8 @@ public class ImageVo implements Serializable { | |||
| /** | |||
| * 镜像推送地址 | |||
| */ | |||
| // @ApiModelProperty(name = "url") | |||
| // private String url; | |||
| @ApiModelProperty(name = "value") | |||
| private String value; | |||
| /** | |||
| * 镜像tag名称 | |||
| @@ -24,7 +24,7 @@ public class ServiceVersionVo { | |||
| private ServiceModelVo model; | |||
| private String image; | |||
| private Map<String, Object> image; | |||
| private String resource; | |||
| @@ -0,0 +1,187 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
| <mapper namespace="com.ruoyi.platform.mapper.ActiveLearnDao"> | |||
| <select id="count" resultType="java.lang.Long"> | |||
| select count(1) from active_learn | |||
| <include refid="common_condition"></include> | |||
| </select> | |||
| <select id="queryByPage" resultType="com.ruoyi.platform.domain.ActiveLearn"> | |||
| select * from active_learn | |||
| <include refid="common_condition"></include> | |||
| order by create_time desc limit #{pageable.offset}, #{pageable.pageSize} | |||
| </select> | |||
| <select id="getActiveLearnByName" resultType="com.ruoyi.platform.domain.ActiveLearn"> | |||
| select * | |||
| from active_learn | |||
| where name = #{name} | |||
| and state = 1 | |||
| </select> | |||
| <insert id="save" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into active_learn(name, description, task_type, framework_type, | |||
| code_config, | |||
| model, model_py, model_class_name, | |||
| classifier_alg, regressor_alg, dataset_py, dataset_class_name, | |||
| dataset, data_size, image, computing_resource_id, shuffle, | |||
| train_size, initial_num, queries_num, instances_num, query_strategy, | |||
| loss_py, loss_class_name, checkpoint_num, batch_size, epochs, | |||
| lr, create_by, update_by) | |||
| values (#{activeLearn.name}, #{activeLearn.description}, #{activeLearn.taskType}, #{activeLearn.frameworkType}, #{activeLearn.codeConfig}, | |||
| #{activeLearn.model}, | |||
| #{activeLearn.modelPy}, #{activeLearn.modelClassName}, #{activeLearn.classifierAlg}, | |||
| #{activeLearn.regressorAlg}, | |||
| #{activeLearn.datasetPy}, #{activeLearn.datasetClassName}, | |||
| #{activeLearn.dataset}, #{activeLearn.dataSize}, | |||
| #{activeLearn.image}, | |||
| #{activeLearn.computingResourceId}, #{activeLearn.shuffle}, | |||
| #{activeLearn.trainSize}, #{activeLearn.initialNum}, #{activeLearn.queriesNum}, #{activeLearn.instancesNum}, | |||
| #{activeLearn.queryStrategy}, #{activeLearn.lossPy}, | |||
| #{activeLearn.lossClassName}, #{activeLearn.checkpointNum}, #{activeLearn.batchSize}, | |||
| #{activeLearn.epochs}, #{activeLearn.lr}, #{activeLearn.createBy}, #{activeLearn.updateBy}) | |||
| </insert> | |||
| <update id="edit"> | |||
| update active_learn | |||
| <set> | |||
| <if test="activeLearn.name != null and activeLearn.name !=''"> | |||
| name = #{activeLearn.name}, | |||
| </if> | |||
| <if test="activeLearn.description != null and activeLearn.description !=''"> | |||
| description = #{activeLearn.description}, | |||
| </if> | |||
| <if test="activeLearn.taskType != null and activeLearn.taskType !=''"> | |||
| task_type = #{activeLearn.taskType}, | |||
| </if> | |||
| <if test="activeLearn.frameworkType != null and activeLearn.frameworkType !=''"> | |||
| framework_type = #{activeLearn.frameworkType}, | |||
| </if> | |||
| <if test="activeLearn.codeConfig != null and activeLearn.codeConfig !=''"> | |||
| code_config = #{activeLearn.codeConfig}, | |||
| </if> | |||
| <if test="activeLearn.model != null and activeLearn.model !=''"> | |||
| model = #{activeLearn.model}, | |||
| </if> | |||
| <if test="activeLearn.modelPy != null and activeLearn.modelPy !=''"> | |||
| model_py = #{activeLearn.modelPy}, | |||
| </if> | |||
| <if test="activeLearn.modelClassName != null and activeLearn.modelClassName !=''"> | |||
| model_class_name = #{activeLearn.modelClassName}, | |||
| </if> | |||
| <if test="activeLearn.classifierAlg != null and activeLearn.classifierAlg !=''"> | |||
| classifier_alg = #{activeLearn.classifierAlg}, | |||
| </if> | |||
| <if test="activeLearn.regressorAlg != null and activeLearn.regressorAlg !=''"> | |||
| regressor_alg = #{activeLearn.regressorAlg}, | |||
| </if> | |||
| <if test="activeLearn.dataset != null and activeLearn.dataset !=''"> | |||
| dataset = #{activeLearn.dataset}, | |||
| </if> | |||
| <if test="activeLearn.datasetPy != null and activeLearn.datasetPy !=''"> | |||
| dataset_py = #{activeLearn.datasetPy}, | |||
| </if> | |||
| <if test="activeLearn.datasetClassName != null and activeLearn.datasetClassName !=''"> | |||
| dataset_class_name = #{activeLearn.datasetClassName}, | |||
| </if> | |||
| <if test="activeLearn.dataSize != null"> | |||
| data_size = #{activeLearn.dataSize}, | |||
| </if> | |||
| <if test="activeLearn.image != null and activeLearn.image !=''"> | |||
| image = #{activeLearn.image}, | |||
| </if> | |||
| <if test="activeLearn.computingResourceId != null"> | |||
| computing_resource_id = #{activeLearn.computingResourceId}, | |||
| </if> | |||
| <if test="activeLearn.shuffle != null"> | |||
| shuffle = #{activeLearn.shuffle}, | |||
| </if> | |||
| <if test="activeLearn.trainSize != null"> | |||
| train_size = #{activeLearn.trainSize}, | |||
| </if> | |||
| <if test="activeLearn.initialNum != null"> | |||
| initial_num = #{activeLearn.initialNum}, | |||
| </if> | |||
| <if test="activeLearn.queriesNum != null"> | |||
| queries_num = #{activeLearn.queriesNum}, | |||
| </if> | |||
| <if test="activeLearn.instancesNum != null"> | |||
| instances_num = #{activeLearn.instancesNum}, | |||
| </if> | |||
| <if test="activeLearn.queryStrategy != null and activeLearn.queryStrategy !=''"> | |||
| query_strategy = #{activeLearn.queryStrategy}, | |||
| </if> | |||
| <if test="activeLearn.lossPy != null and activeLearn.lossPy !=''"> | |||
| loss_py = #{activeLearn.lossPy}, | |||
| </if> | |||
| <if test="activeLearn.lossClassName != null and activeLearn.lossClassName !=''"> | |||
| loss_class_name = #{activeLearn.lossClassName}, | |||
| </if> | |||
| <if test="activeLearn.checkpointNum != null"> | |||
| checkpoint_num = #{activeLearn.checkpointNum}, | |||
| </if> | |||
| <if test="activeLearn.batchSize != null"> | |||
| batch_size = #{activeLearn.batchSize}, | |||
| </if> | |||
| <if test="activeLearn.epochs != null"> | |||
| epochs = #{activeLearn.epochs}, | |||
| </if> | |||
| <if test="activeLearn.lr != null"> | |||
| lr = #{activeLearn.lr}, | |||
| </if> | |||
| <if test="activeLearn.updateBy != null and activeLearn.updateBy !=''"> | |||
| update_by = #{activeLearn.updateBy}, | |||
| </if> | |||
| <if test="activeLearn.statusList != null and activeLearn.statusList !=''"> | |||
| status_list = #{activeLearn.statusList}, | |||
| </if> | |||
| <if test="activeLearn.state != null"> | |||
| state = #{activeLearn.state}, | |||
| </if> | |||
| </set> | |||
| where id = #{activeLearn.id} | |||
| </update> | |||
| <select id="getActiveLearnById" resultType="com.ruoyi.platform.domain.ActiveLearn"> | |||
| select * | |||
| from active_learn | |||
| where id = #{id} | |||
| </select> | |||
| <select id="queryByDatasetId" resultType="com.ruoyi.platform.domain.ActiveLearn"> | |||
| select * | |||
| from active_learn | |||
| where JSON_CONTAINS(dataset, #{datasetId}) | |||
| and state = 1 | |||
| </select> | |||
| <select id="queryByModelId" resultType="com.ruoyi.platform.domain.ActiveLearn"> | |||
| select * | |||
| from active_learn | |||
| where JSON_CONTAINS(model, #{modelId}) | |||
| and state = 1 | |||
| </select> | |||
| <select id="queryByImageId" resultType="com.ruoyi.platform.domain.ActiveLearn"> | |||
| select * | |||
| from active_learn | |||
| where JSON_CONTAINS(image, #{imageId}) | |||
| and state = 1 | |||
| </select> | |||
| <select id="queryByCodeConfig" resultType="com.ruoyi.platform.domain.ActiveLearn"> | |||
| select * | |||
| from active_learn | |||
| where JSON_CONTAINS(code_config, #{codeConfig}) | |||
| and state = 1 | |||
| </select> | |||
| <sql id="common_condition"> | |||
| <where> | |||
| state = 1 | |||
| <if test="name != null and name != ''"> | |||
| and name like concat('%', #{name}, '%') | |||
| </if> | |||
| </where> | |||
| </sql> | |||
| </mapper> | |||
| @@ -0,0 +1,79 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
| <mapper namespace="com.ruoyi.platform.mapper.ActiveLearnInsDao"> | |||
| <select id="count" resultType="java.lang.Long"> | |||
| select count(1) | |||
| from active_learn_ins | |||
| <where> | |||
| state = 1 | |||
| and active_learn_id = #{activeLearnId} | |||
| </where> | |||
| </select> | |||
| <select id="queryAllByLimit" resultType="com.ruoyi.platform.domain.ActiveLearnIns"> | |||
| select * from active_learn_ins | |||
| <where> | |||
| state = 1 | |||
| and active_learn_id = #{activeLearnId} | |||
| </where> | |||
| order by update_time DESC | |||
| limit #{pageable.offset}, #{pageable.pageSize} | |||
| </select> | |||
| <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into active_learn_ins(active_learn_id, param, argo_ins_name, argo_ins_ns, node_status, node_result, | |||
| result_path, status) | |||
| values (#{activeLearnIns.activeLearnId}, #{activeLearnIns.param}, #{activeLearnIns.argoInsName}, | |||
| #{activeLearnIns.argoInsNs}, | |||
| #{activeLearnIns.nodeStatus}, #{activeLearnIns.nodeResult}, #{activeLearnIns.resultPath}, | |||
| #{activeLearnIns.status}) | |||
| </insert> | |||
| <update id="update"> | |||
| update active_learn_ins | |||
| <set> | |||
| <if test="activeLearnIns.resultPath != null and activeLearnIns.resultPath != ''"> | |||
| result_path = #{activeLearnIns.resultPath}, | |||
| </if> | |||
| <if test="activeLearnIns.status != null and activeLearnIns.status != ''"> | |||
| status = #{activeLearnIns.status}, | |||
| </if> | |||
| <if test="activeLearnIns.nodeStatus != null and activeLearnIns.nodeStatus != ''"> | |||
| node_status = #{activeLearnIns.nodeStatus}, | |||
| </if> | |||
| <if test="activeLearnIns.nodeResult != null and activeLearnIns.nodeResult != ''"> | |||
| node_result = #{activeLearnIns.nodeResult}, | |||
| </if> | |||
| <if test="activeLearnIns.state != null"> | |||
| state = #{activeLearnIns.state}, | |||
| </if> | |||
| <if test="activeLearnIns.finishTime != null"> | |||
| finish_time = #{activeLearnIns.finishTime}, | |||
| </if> | |||
| </set> | |||
| where id = #{activeLearnIns.id} | |||
| </update> | |||
| <select id="queryById" resultType="com.ruoyi.platform.domain.ActiveLearnIns"> | |||
| select * from active_learn_ins | |||
| <where> | |||
| state = 1 and id = #{id} | |||
| </where> | |||
| </select> | |||
| <select id="getByActiveLearnId" resultType="com.ruoyi.platform.domain.ActiveLearnIns"> | |||
| select * | |||
| from active_learn_ins | |||
| where active_learn_id = #{activeLearnId} | |||
| and state = 1 | |||
| order by update_time DESC limit 5 | |||
| </select> | |||
| <select id="queryActiveLearnInsIsNotTerminated" resultType="com.ruoyi.platform.domain.ActiveLearnIns"> | |||
| select * | |||
| from active_learn_ins | |||
| where (status NOT IN ('Terminated', 'Succeeded', 'Failed') | |||
| OR status IS NULL) | |||
| and state = 1 | |||
| </select> | |||
| </mapper> | |||
| @@ -120,6 +120,7 @@ | |||
| <select id="queryByPage" resultType="com.ruoyi.platform.domain.AutoMl"> | |||
| select * from auto_ml | |||
| <include refid="common_condition"></include> | |||
| order by create_time desc limit #{pageable.offset}, #{pageable.pageSize} | |||
| </select> | |||
| <select id="getAutoMlById" resultType="com.ruoyi.platform.domain.AutoMl"> | |||
| @@ -208,11 +208,38 @@ | |||
| where id = #{devEnvironment.id} | |||
| </update> | |||
| <select id="getByName" resultType="com.ruoyi.platform.domain.DevEnvironment"> | |||
| select * | |||
| from dev_environment | |||
| where name = #{name} | |||
| and state = 1 | |||
| </select> | |||
| <!--通过主键删除--> | |||
| <delete id="deleteById"> | |||
| delete from dev_environment where id = #{id} | |||
| </delete> | |||
| <select id="queryByDatasetId" resultType="com.ruoyi.platform.domain.DevEnvironment"> | |||
| select * | |||
| from dev_environment | |||
| where JSON_CONTAINS(dataset, #{datasetId}) | |||
| and state = 1 | |||
| </select> | |||
| <select id="queryByModelId" resultType="com.ruoyi.platform.domain.DevEnvironment"> | |||
| select * | |||
| from dev_environment | |||
| where JSON_CONTAINS(model, #{modelId}) | |||
| and state = 1 | |||
| </select> | |||
| <select id="queryByImageId" resultType="com.ruoyi.platform.domain.DevEnvironment"> | |||
| select * | |||
| from dev_environment | |||
| where JSON_CONTAINS(image, #{imageId}) | |||
| and state = 1 | |||
| </select> | |||
| </mapper> | |||
| @@ -1,7 +1,7 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
| <mapper namespace="com.ruoyi.platform.mapper.RayDao"> | |||
| <insert id="save"> | |||
| <insert id="save" keyProperty="id" useGeneratedKeys="true"> | |||
| insert into ray(name, description, dataset, model, code_config, main_py, num_samples, parameters, | |||
| points_to_evaluate, storage_path, | |||
| search_alg, scheduler, metric, mode, max_t, | |||
| @@ -94,11 +94,12 @@ | |||
| <select id="queryByPage" resultType="com.ruoyi.platform.domain.Ray"> | |||
| select * from ray | |||
| <include refid="common_condition"></include> | |||
| order by create_time desc limit #{pageable.offset}, #{pageable.pageSize} | |||
| </select> | |||
| <select id="getRayByName" resultType="com.ruoyi.platform.domain.Ray"> | |||
| select * | |||
| from ray _ | |||
| from ray | |||
| where name = #{name} | |||
| and state = 1 | |||
| </select> | |||
| @@ -60,6 +60,17 @@ | |||
| where id = #{id} | |||
| </update> | |||
| <update id="deleteTaskState"> | |||
| update resource_occupy set task_state = 0 | |||
| where task_type = #{taskType} | |||
| <if test="taskId != null and taskId !=''"> | |||
| and task_id = #{taskId} | |||
| </if> | |||
| <if test="taskInsId != null and taskInsId !=''"> | |||
| and task_ins_id = #{taskInsId} | |||
| </if> | |||
| </update> | |||
| <select id="haveResource" resultType="java.lang.Boolean"> | |||
| select case when used + #{need} <= total then TRUE else FALSE end | |||
| from resource | |||
| @@ -190,4 +190,34 @@ | |||
| set run_state = #{runState} | |||
| where deployment_name = #{deploymentName} | |||
| </update> | |||
| <select id="queryByModelId" resultType="java.lang.String"> | |||
| select concat(b.service_name, ':', a.version) | |||
| from service_version a, | |||
| service b | |||
| where JSON_CONTAINS(a.model, #{modelId}) | |||
| and a.state = 1 | |||
| and b.state = 1 | |||
| and a.service_id = b.id | |||
| </select> | |||
| <select id="queryByImageId" resultType="java.lang.String"> | |||
| select concat(b.service_name, ':', a.version) | |||
| from service_version a, | |||
| service b | |||
| where JSON_CONTAINS(a.image, #{imageId}) | |||
| and a.state = 1 | |||
| and b.state = 1 | |||
| and a.service_id = b.id | |||
| </select> | |||
| <select id="queryByCodeConfig" resultType="java.lang.String"> | |||
| select concat(b.service_name, ':', a.version) | |||
| from service_version a, | |||
| service b | |||
| where JSON_CONTAINS(a.code_config, #{codeConfig}) | |||
| and a.state = 1 | |||
| and b.state = 1 | |||
| and a.service_id = b.id | |||
| </select> | |||
| </mapper> | |||