| @@ -271,7 +271,18 @@ export default [ | |||||
| { | { | ||||
| name: '镜像详情', | name: '镜像详情', | ||||
| path: 'info/:id', | path: 'info/:id', | ||||
| component: './Mirror/Info', | |||||
| routes: [ | |||||
| { | |||||
| name: '镜像详情', | |||||
| path: '', | |||||
| component: './Mirror/Info', | |||||
| }, | |||||
| { | |||||
| name: '新增镜像版本', | |||||
| path: 'add-version', | |||||
| component: './Mirror/Create', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| name: '创建镜像', | name: '创建镜像', | ||||
| @@ -67,6 +67,7 @@ | |||||
| "@types/crypto-js": "^4.2.2", | "@types/crypto-js": "^4.2.2", | ||||
| "@umijs/route-utils": "^4.0.1", | "@umijs/route-utils": "^4.0.1", | ||||
| "antd": "~5.21.4", | "antd": "~5.21.4", | ||||
| "caniuse-lite": "~1.0.30001707", | |||||
| "classnames": "^2.3.2", | "classnames": "^2.3.2", | ||||
| "crypto-js": "^4.2.0", | "crypto-js": "^4.2.0", | ||||
| "echarts": "^5.5.0", | "echarts": "^5.5.0", | ||||
| @@ -4,16 +4,6 @@ | |||||
| font-display: swap; | font-display: swap; | ||||
| } | } | ||||
| @font-face { | |||||
| font-family: 'TaoBaoMaiCaiTi'; | |||||
| src: url('./TaoBaoMaiCaiTi-Regular.woff2') format('woff2'), /* 最优先使用 woff2 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.woff') format('woff'), /* 兼容性较好的 woff */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.ttf') format('truetype'), /* ttf 作为备选 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.otf') format('opentype'); /* otf 作为最后选项 */ | |||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||||
| } | |||||
| @font-face { | @font-face { | ||||
| font-family: 'DingTalk-JinBuTi'; | font-family: 'DingTalk-JinBuTi'; | ||||
| src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | ||||
| @@ -2,6 +2,7 @@ import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { Flex, Typography } from 'antd'; | import { Flex, Typography } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CodeConfigItemProps = { | type CodeConfigItemProps = { | ||||
| @@ -10,6 +11,7 @@ type CodeConfigItemProps = { | |||||
| }; | }; | ||||
| function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | ||||
| const [isEllipsis, setIsEllipsis] = useState(false); | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | ||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | ||||
| @@ -32,11 +34,20 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| </Flex> | </Flex> | ||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| className={styles['code-config-item__url']} | className={styles['code-config-item__url']} | ||||
| ellipsis={{ rows: 2, tooltip: item.git_url }} | |||||
| ellipsis={{ | |||||
| rows: 2, | |||||
| tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip | |||||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), | |||||
| }} | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__branch']} | |||||
| ellipsis={{ tooltip: item.git_branch }} | |||||
| > | |||||
| {item.git_branch} | |||||
| </Typography.Paragraph> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -48,12 +48,9 @@ function IframePage({ type, className, style }: IframePageProps) { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const requestIframeUrl = async () => { | const requestIframeUrl = async () => { | ||||
| setLoading(true); | |||||
| const [res] = await to(getRequestAPI(type)()); | const [res] = await to(getRequestAPI(type)()); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setIframeUrl(res.data); | setIframeUrl(res.data); | ||||
| } else { | |||||
| setLoading(false); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -6,7 +6,7 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| ResourceSelectorResponse, | |||||
| type ResourceSelectorResponse, | |||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| } from '@/components/ResourceSelectorModal'; | } from '@/components/ResourceSelectorModal'; | ||||
| @@ -28,7 +28,6 @@ function AutoMLInstance() { | |||||
| const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | ||||
| const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | ||||
| const params = useParams(); | const params = useParams(); | ||||
| // const autoMLId = safeInvoke(Number)(params.autoMLId); | |||||
| const instanceId = safeInvoke(Number)(params.id); | const instanceId = safeInvoke(Number)(params.id); | ||||
| const evtSourceRef = useRef<EventSource | null>(null); | const evtSourceRef = useRef<EventSource | null>(null); | ||||
| @@ -187,6 +186,7 @@ function AutoMLInstance() { | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | icon: <KFIcon type="icon-Trialliebiao" />, | ||||
| children: ( | children: ( | ||||
| <ExperimentHistory | <ExperimentHistory | ||||
| calcMetrics={autoMLInfo?.scoring_functions} | |||||
| fileUrl={instanceInfo?.run_history_path} | fileUrl={instanceInfo?.run_history_path} | ||||
| isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | ||||
| /> | /> | ||||
| @@ -8,8 +8,9 @@ import TrialStatusCell from '../TrialStatusCell'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentHistoryProps = { | type ExperimentHistoryProps = { | ||||
| fileUrl?: string; | |||||
| isClassification: boolean; | |||||
| calcMetrics?: string; // 计算指标 | |||||
| fileUrl?: string; // 文件url | |||||
| isClassification: boolean; // 是否是分类 | |||||
| }; | }; | ||||
| type TableData = { | type TableData = { | ||||
| @@ -22,7 +23,7 @@ type TableData = { | |||||
| althorithm?: string; | althorithm?: string; | ||||
| }; | }; | ||||
| function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { | |||||
| function ExperimentHistory({ calcMetrics, fileUrl, isClassification }: ExperimentHistoryProps) { | |||||
| const [tableData, setTableData] = useState<TableData[]>([]); | const [tableData, setTableData] = useState<TableData[]>([]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取实验运行历史记录 | // 获取实验运行历史记录 | ||||
| @@ -33,7 +34,7 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| const list: TableData[] = data.map((item) => { | const list: TableData[] = data.map((item) => { | ||||
| return { | return { | ||||
| id: item[0]?.[0], | id: item[0]?.[0], | ||||
| accuracy: item[1]?.[5]?.accuracy, | |||||
| accuracy: calcMetrics ? item[1]?.[5]?.[calcMetrics] : undefined, | |||||
| duration: item[1]?.[5]?.duration, | duration: item[1]?.[5]?.duration, | ||||
| train_loss: item[1]?.[5]?.train_loss, | train_loss: item[1]?.[5]?.train_loss, | ||||
| status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | ||||
| @@ -64,12 +65,6 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| width: 80, | width: 80, | ||||
| render: tableCellRender(false), | render: tableCellRender(false), | ||||
| }, | }, | ||||
| { | |||||
| title: '准确率', | |||||
| dataIndex: 'accuracy', | |||||
| key: 'accuracy', | |||||
| render: tableCellRender(true), | |||||
| }, | |||||
| { | { | ||||
| title: '耗时', | title: '耗时', | ||||
| dataIndex: 'duration', | dataIndex: 'duration', | ||||
| @@ -103,6 +98,15 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| if (calcMetrics) { | |||||
| columns.splice(0, 0, { | |||||
| title: `指标:${calcMetrics}`, | |||||
| dataIndex: 'accuracy', | |||||
| key: 'accuracy', | |||||
| render: tableCellRender(true), | |||||
| }); | |||||
| } | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-history']}> | <div className={styles['experiment-history']}> | ||||
| <div className={styles['experiment-history__content']}> | <div className={styles['experiment-history__content']}> | ||||
| @@ -107,7 +107,7 @@ function ExperimentInstanceComponent({ | |||||
| }; | }; | ||||
| if (!experimentInsList || experimentInsList.length === 0) { | if (!experimentInsList || experimentInsList.length === 0) { | ||||
| return <div style={{ textAlign: 'center' }}>暂无实验实例</div>; | |||||
| return <div style={{ textAlign: 'center' }}>暂无数据</div>; | |||||
| } | } | ||||
| return ( | return ( | ||||
| @@ -188,6 +188,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| if (expanded) { | if (expanded) { | ||||
| setExpandedRowKeys([record.id]); | setExpandedRowKeys([record.id]); | ||||
| getExperimentInsList(record.id, 0); | getExperimentInsList(record.id, 0); | ||||
| refreshExperimentList(); | |||||
| } else { | } else { | ||||
| setExpandedRowKeys([]); | setExpandedRowKeys([]); | ||||
| } | } | ||||
| @@ -70,7 +70,12 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__branch']} | |||||
| ellipsis={{ tooltip: item.git_branch }} | |||||
| > | |||||
| {item.git_branch} | |||||
| </Typography.Paragraph> | |||||
| </div> | </div> | ||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['code-config-item__user']}> | <div className={styles['code-config-item__user']}> | ||||
| @@ -159,27 +159,42 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {/* <Form.Item label="集群版本" name="available_cluster"> | |||||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | |||||
| </Form.Item> */} | |||||
| <Form.Item | <Form.Item | ||||
| label="数据集简介" | |||||
| label="数据集描述" | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入数据集简介', | |||||
| message: '请输入数据集描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入数据集简介" | |||||
| placeholder="请输入数据集描述" | |||||
| showCount | showCount | ||||
| maxLength={200} | maxLength={200} | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="version_desc" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={200} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -143,23 +143,41 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型简介" | |||||
| label="模型描述" | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入模型简介', | |||||
| message: '请输入模型描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入模型简介" | |||||
| placeholder="请输入模型描述" | |||||
| maxLength={200} | maxLength={200} | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="version_desc" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={200} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -21,7 +21,7 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ | |||||
| value: data.name, | value: data.name, | ||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | |||||
| label: '数据集版本', | |||||
| value: data.version, | value: data.version, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -64,7 +64,7 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [ | |||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | |||||
| label: '模型版本', | |||||
| value: data.version, | value: data.version, | ||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| @@ -51,8 +51,8 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||||
| message: '请输入镜像名称', | message: '请输入镜像名称', | ||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-z0-9/_-]*$/, | |||||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||||
| pattern: /^[a-z0-9/._-]*$/, | |||||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| @@ -179,11 +179,12 @@ function ExperimentText() { | |||||
| if (!statusNode) { | if (!statusNode) { | ||||
| return; | return; | ||||
| } | } | ||||
| const { finishedAt, startedAt, phase, id } = statusNode; | |||||
| const { finishedAt, startedAt, phase, id, message } = statusNode; | |||||
| workflowNode.experimentStartTime = startedAt; | workflowNode.experimentStartTime = startedAt; | ||||
| workflowNode.experimentEndTime = finishedAt; | workflowNode.experimentEndTime = finishedAt; | ||||
| workflowNode.experimentStatus = phase; | workflowNode.experimentStatus = phase; | ||||
| workflowNode.workflowId = id; | workflowNode.workflowId = id; | ||||
| workflowNode.message = message; | |||||
| workflowNode.img = phase | workflowNode.img = phase | ||||
| ? `${workflowNode.imgName}-${phase}.png` | ? `${workflowNode.imgName}-${phase}.png` | ||||
| : `${workflowNode.imgName}.png`; | : `${workflowNode.imgName}.png`; | ||||
| @@ -3,7 +3,7 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Form, Input, Radio, Select, type FormRule } from 'antd'; | |||||
| import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | |||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -63,13 +63,14 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||||
| }; | }; | ||||
| // 根据参数设置 label | // 根据参数设置 label | ||||
| export const getParamType = (param: PipelineGlobalParam): string => { | |||||
| export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | |||||
| const paramTypes: Readonly<Record<number, string>> = { | const paramTypes: Readonly<Record<number, string>> = { | ||||
| 1: '字符串', | 1: '字符串', | ||||
| 2: '整型', | 2: '整型', | ||||
| 3: '布尔类型', | 3: '布尔类型', | ||||
| }; | }; | ||||
| return param.param_name + `(${paramTypes[param.param_type]})`; | |||||
| const label = param.param_name + `(${paramTypes[param.param_type]})`; | |||||
| return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | |||||
| }; | }; | ||||
| function AddExperimentModal({ | function AddExperimentModal({ | ||||
| @@ -99,8 +100,8 @@ function AddExperimentModal({ | |||||
| }; | }; | ||||
| const paramLayout = { | const paramLayout = { | ||||
| labelCol: { span: 8 }, | |||||
| wrapperCol: { span: 16 }, | |||||
| labelCol: { span: 6 }, | |||||
| wrapperCol: { span: 18 }, | |||||
| }; | }; | ||||
| // 除了流水线选择发生变化 | // 除了流水线选择发生变化 | ||||
| @@ -157,7 +158,6 @@ function AddExperimentModal({ | |||||
| form={form} | form={form} | ||||
| {...layout} | {...layout} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| labelWrap | |||||
| > | > | ||||
| <Form.Item | <Form.Item | ||||
| label="实验名称" | label="实验名称" | ||||
| @@ -215,9 +215,9 @@ function AddExperimentModal({ | |||||
| {...restField} | {...restField} | ||||
| {...paramLayout} | {...paramLayout} | ||||
| key={key} | key={key} | ||||
| label={getParamType(globalParam[name])} | |||||
| label={getParamLabel(globalParam[name])} | |||||
| name={[name, 'param_value']} | name={[name, 'param_value']} | ||||
| rules={getParamRules(globalParam[name]['param_type'])} | |||||
| rules={getParamRules(globalParam[name]['param_type'], true)} | |||||
| > | > | ||||
| {getParamComponent( | {getParamComponent( | ||||
| globalParam[name]['param_type'], | globalParam[name]['param_type'], | ||||
| @@ -13,7 +13,6 @@ | |||||
| } | } | ||||
| &__tabs { | &__tabs { | ||||
| height: calc(100% - 169px); | |||||
| :global { | :global { | ||||
| .ant-tabs-nav { | .ant-tabs-nav { | ||||
| padding-left: 24px; | padding-left: 24px; | ||||
| @@ -35,7 +34,7 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| margin-bottom: 15px; | margin-bottom: 15px; | ||||
| padding-left: 24px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 15px; | font-size: 15px; | ||||
| } | } | ||||
| @@ -3,7 +3,7 @@ import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | import { PipelineNodeModelSerialize } from '@/types'; | ||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | ||||
| import { Drawer, Tabs } from 'antd'; | |||||
| import { Drawer, Tabs, Typography } from 'antd'; | |||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import ExperimentParameter from '../ExperimentParameter'; | import ExperimentParameter from '../ExperimentParameter'; | ||||
| import ExperimentResult from '../ExperimentResult'; | import ExperimentResult from '../ExperimentResult'; | ||||
| @@ -129,6 +129,14 @@ const ExperimentDrawer = ({ | |||||
| '--' | '--' | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| {instanceNodeData.message && ( | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| <div style={{ flex: 'none' }}>消息:</div> | |||||
| <Typography.Text ellipsis={{ tooltip: instanceNodeData.message }}> | |||||
| {instanceNodeData.message ?? '--'} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| )} | |||||
| <div className={styles['experiment-drawer__info']}> | <div className={styles['experiment-drawer__info']}> | ||||
| 启动时间:{formatDate(instanceNodeStartTime)} | 启动时间:{formatDate(instanceNodeStartTime)} | ||||
| </div> | </div> | ||||
| @@ -137,7 +145,14 @@ const ExperimentDrawer = ({ | |||||
| {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} | {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} /> | |||||
| <Tabs | |||||
| defaultActiveKey="1" | |||||
| items={items} | |||||
| className={styles['experiment-drawer__tabs']} | |||||
| style={{ | |||||
| height: instanceNodeData.message ? 'calc(100% - 169px - 39px)' : 'calc(100% - 169px)', | |||||
| }} | |||||
| /> | |||||
| </Drawer> | </Drawer> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,31 +1,14 @@ | |||||
| .params_container { | |||||
| max-height: 230px; | |||||
| padding: 15px 15px 0; | |||||
| .params-container { | |||||
| max-height: calc(100vh - 300px); | |||||
| padding: 24px 24px 0; | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| &_line { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 15px; | |||||
| &_label { | |||||
| width: 180px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &_value { | |||||
| flex: 1; | |||||
| width: 100px; | |||||
| margin-left: 15px; | |||||
| padding: 10px 20px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 20px; | |||||
| background: #f6f6f6; | |||||
| border: 1px solid #e0e0e1; | |||||
| border-radius: 4px; | |||||
| } | |||||
| .params-empty { | |||||
| :global { | |||||
| .kf-empty__image { | |||||
| width: 300px; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,9 +4,11 @@ | |||||
| * @Description: 查看实验使用的参数 | * @Description: 查看实验使用的参数 | ||||
| */ | */ | ||||
| import parameterImg from '@/assets/img/modal-parameter.png'; | import parameterImg from '@/assets/img/modal-parameter.png'; | ||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { getParamType } from '../AddExperimentModal'; | |||||
| import { Form } from 'antd'; | |||||
| import { getParamComponent, getParamLabel } from '../AddExperimentModal'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ParamsModalProps = { | type ParamsModalProps = { | ||||
| @@ -26,14 +28,44 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| cancelButtonProps={{ style: { display: 'none' } }} | cancelButtonProps={{ style: { display: 'none' } }} | ||||
| width={825} | width={825} | ||||
| > | > | ||||
| <div className={styles.params_container}> | |||||
| {globalParam?.map((item) => ( | |||||
| <div key={item.param_name} className={styles.params_container_line}> | |||||
| <span className={styles.params_container_line_label}>{getParamType(item)}</span> | |||||
| <span className={styles.params_container_line_value}>{item.param_value}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| {Array.isArray(globalParam) && globalParam.length > 0 ? ( | |||||
| <div className={styles['params-container']}> | |||||
| <Form | |||||
| name="view_params_form" | |||||
| labelCol={{ span: 6 }} | |||||
| wrapperCol={{ span: 18 }} | |||||
| initialValues={{ global_param: globalParam }} | |||||
| labelAlign="left" | |||||
| disabled | |||||
| > | |||||
| <Form.List name="global_param"> | |||||
| {(fields) => | |||||
| fields.map(({ key, name, ...restField }) => ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| key={key} | |||||
| name={[name, 'param_value']} | |||||
| label={getParamLabel(globalParam[name])} | |||||
| > | |||||
| {getParamComponent( | |||||
| globalParam[name]['param_type'], | |||||
| globalParam[name]['is_sensitive'], | |||||
| )} | |||||
| </Form.Item> | |||||
| )) | |||||
| } | |||||
| </Form.List> | |||||
| </Form> | |||||
| </div> | |||||
| ) : ( | |||||
| <KFEmpty | |||||
| className={styles['params-empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content="该流水线没有设置全局参数" | |||||
| hasFooter={false} | |||||
| /> | |||||
| )} | |||||
| </KFModal> | </KFModal> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -206,6 +206,7 @@ function Experiment() { | |||||
| setExpandedRowKeys(null); | setExpandedRowKeys(null); | ||||
| } else { | } else { | ||||
| getQueryByExperiment(record.id, 0); | getQueryByExperiment(record.id, 0); | ||||
| refreshExperimentList(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -44,7 +44,7 @@ const mirrorRadioItems: KFRadioItem[] = [ | |||||
| function MirrorCreate() { | function MirrorCreate() { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [nameDisabled, setNameDisabled] = useState(false); | |||||
| const [isAddVersion, setIsAddVersion] = useState(false); // 是制作镜像还是新增镜像版本 | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -60,7 +60,7 @@ function MirrorCreate() { | |||||
| const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | ||||
| if (name) { | if (name) { | ||||
| form.setFieldValue('name', name); | form.setFieldValue('name', name); | ||||
| setNameDisabled(true); | |||||
| setIsAddVersion(true); | |||||
| } | } | ||||
| return () => { | return () => { | ||||
| SessionStorage.removeItem(SessionStorage.mirrorNameKey); | SessionStorage.removeItem(SessionStorage.mirrorNameKey); | ||||
| @@ -70,32 +70,36 @@ function MirrorCreate() { | |||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| const createPublicMirror = async (formData: FormData) => { | const createPublicMirror = async (formData: FormData) => { | ||||
| const upload_type = formData['upload_type']; | const upload_type = formData['upload_type']; | ||||
| let params; | |||||
| if (upload_type === CommonTabKeys.Public) { | if (upload_type === CommonTabKeys.Public) { | ||||
| params = { | |||||
| const params = { | |||||
| ...omit(formData, ['upload_type']), | ...omit(formData, ['upload_type']), | ||||
| upload_type: 0, | upload_type: 0, | ||||
| image_type: 0, | image_type: 0, | ||||
| }; | }; | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| } else { | } else { | ||||
| const fileList = formData['fileList'] ?? []; | const fileList = formData['fileList'] ?? []; | ||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const file = fileList[0]; | const file = fileList[0]; | ||||
| params = { | |||||
| const params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | ...omit(formData, ['fileList', 'upload_type']), | ||||
| path: file.response.data.url, | path: file.response.data.url, | ||||
| file_size: file.response.data.fileSize, | file_size: file.response.data.fileSize, | ||||
| upload_type: 1, | upload_type: 1, | ||||
| image_type: 0, | image_type: 0, | ||||
| }; | }; | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| }; | }; | ||||
| // 提交 | // 提交 | ||||
| @@ -118,14 +122,16 @@ function MirrorCreate() { | |||||
| return true; | return true; | ||||
| }; | }; | ||||
| const descTitle = isAddVersion ? '版本描述' : '镜像描述'; | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-create']}> | <div className={styles['mirror-create']}> | ||||
| <PageTitle title="创建镜像"></PageTitle> | |||||
| <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> | |||||
| <div className={styles['mirror-create__content']}> | <div className={styles['mirror-create__content']}> | ||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| name="mirror-create" | name="mirror-create" | ||||
| labelCol={{ flex: '130px' }} | |||||
| labelCol={{ flex: '135px' }} | |||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| @@ -142,7 +148,7 @@ function MirrorCreate() { | |||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像名称及Tag" | |||||
| label="镜像名称和版本" | |||||
| name="name" | name="name" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| @@ -150,15 +156,15 @@ function MirrorCreate() { | |||||
| message: '请输入镜像名称', | message: '请输入镜像名称', | ||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-z0-9/_-]*$/, | |||||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||||
| pattern: /^[a-z0-9/._-]*$/, | |||||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input | <Input | ||||
| placeholder="请输入镜像名称" | placeholder="请输入镜像名称" | ||||
| maxLength={64} | maxLength={64} | ||||
| disabled={nameDisabled} | |||||
| disabled={isAddVersion} | |||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| @@ -174,33 +180,33 @@ function MirrorCreate() { | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像Tag', | |||||
| message: '请输入镜像版本', | |||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-zA-Z0-9._-]+$/, | pattern: /^[a-zA-Z0-9._-]+$/, | ||||
| message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| message: '镜像版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={20}> | <Col span={20}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像描述" | |||||
| label={descTitle} | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像描述', | |||||
| message: `请输入${descTitle}`, | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| placeholder="请输入镜像描述,最长128字符" | |||||
| placeholder={`请输入${descTitle}`} | |||||
| maxLength={128} | maxLength={128} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| @@ -303,7 +309,7 @@ function MirrorCreate() { | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | ||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| 创建镜像 | |||||
| 确定 | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| @@ -155,7 +155,7 @@ function MirrorInfo() { | |||||
| }; | }; | ||||
| const createMirrorVersion = () => { | const createMirrorVersion = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`add-version`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| @@ -174,20 +174,27 @@ function MirrorInfo() { | |||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| render: tableCellRender(), | |||||
| width: '25%', | |||||
| render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }), | |||||
| }, | |||||
| { | |||||
| title: '版本描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: tableCellRender(true), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| key: 'status', | key: 'status', | ||||
| width: 150, | |||||
| width: 100, | |||||
| render: MirrorStatusCell, | render: MirrorStatusCell, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像大小', | title: '镜像大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: 150, | |||||
| width: 120, | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -200,7 +207,7 @@ function MirrorInfo() { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: 150, | |||||
| width: 120, | |||||
| key: 'operation', | key: 'operation', | ||||
| hidden: isPublic, | hidden: isPublic, | ||||
| render: (_: any, record: MirrorVersionData) => ( | render: (_: any, record: MirrorVersionData) => ( | ||||
| @@ -128,7 +128,7 @@ function MirrorList() { | |||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: MirrorData) => { | const toDetail = (record: MirrorData) => { | ||||
| navigate(`/dataset/mirror/info/${record.id}`); | |||||
| navigate(`info/${record.id}`); | |||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| pagination, | pagination, | ||||
| @@ -149,7 +149,7 @@ function MirrorList() { | |||||
| // 创建镜像 | // 创建镜像 | ||||
| const createMirror = () => { | const createMirror = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`create`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| @@ -262,7 +262,7 @@ function MirrorList() { | |||||
| onClick={createMirror} | onClick={createMirror} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 制作镜像 | |||||
| 创建镜像 | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| <Button | <Button | ||||
| @@ -18,6 +18,7 @@ | |||||
| &__table { | &__table { | ||||
| flex: 1; | flex: 1; | ||||
| min-height: 0; | |||||
| margin-top: 24px; | margin-top: 24px; | ||||
| } | } | ||||
| } | } | ||||
| @@ -133,17 +133,18 @@ function ServiceInfo() { | |||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | // 如果是一页的唯一数据,删除时,请求第一页的数据 | ||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| // 否则直接刷新这一页的数据,避免回到第一页 | |||||
| if (tableData.length === 1) { | |||||
| setPagination((prev) => ({ | setPagination((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| current: 1, | current: 1, | ||||
| })); | })); | ||||
| } else { | } else { | ||||
| getServiceInfo(); | |||||
| getServiceVersions(); | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| })); | |||||
| } | } | ||||
| getServiceInfo(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -432,7 +433,7 @@ function ServiceInfo() { | |||||
| onClick={() => createServiceVersion(ServiceOperationType.Create)} | onClick={() => createServiceVersion(ServiceOperationType.Create)} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 新增版本 | |||||
| 新增服务版本 | |||||
| </Button> | </Button> | ||||
| <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | ||||
| 版本对比 | 版本对比 | ||||
| @@ -136,31 +136,35 @@ const GlobalParamsDrawer = forwardRef( | |||||
| cur.global_param?.[name]?.param_type | cur.global_param?.[name]?.param_type | ||||
| } | } | ||||
| > | > | ||||
| {({ getFieldValue }) => ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_value']} | |||||
| label="值" | |||||
| rules={getParamRules( | |||||
| getFieldValue(['global_param', name, 'param_type']), | |||||
| true, | |||||
| )} | |||||
| > | |||||
| {getParamComponent(getFieldValue(['global_param', name, 'param_type']))} | |||||
| </Form.Item> | |||||
| )} | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'is_sensitive']} | |||||
| label="脱敏显示" | |||||
| rules={[{ required: true, message: '请选择' }]} | |||||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>是</Radio> | |||||
| <Radio value={0}>否</Radio> | |||||
| </Radio.Group> | |||||
| {({ getFieldValue }) => { | |||||
| const type = getFieldValue(['global_param', name, 'param_type']); | |||||
| return ( | |||||
| <> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_value']} | |||||
| label="值" | |||||
| rules={getParamRules(type, true)} | |||||
| > | |||||
| {getParamComponent(type)} | |||||
| </Form.Item> | |||||
| {type !== 3 && ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'is_sensitive']} | |||||
| label="脱敏显示" | |||||
| rules={[{ required: true, message: '请选择' }]} | |||||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>是</Radio> | |||||
| <Radio value={0}>否</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | </Form.Item> | ||||
| <Tooltip title="删除参数"> | <Tooltip title="删除参数"> | ||||
| <Button | <Button | ||||
| @@ -216,6 +216,11 @@ function PointsDetail() { | |||||
| label: '进行中', | label: '进行中', | ||||
| color: themes['primaryColor'], | color: themes['primaryColor'], | ||||
| }, | }, | ||||
| { | |||||
| value: 0, | |||||
| label: '准备中', | |||||
| color: themes['pendingColor'], | |||||
| }, | |||||
| ]), | ]), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -1,12 +1,18 @@ | |||||
| .total-statistics { | .total-statistics { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: center; | |||||
| height: 140px; | height: 140px; | ||||
| padding: 0 16px; | padding: 0 16px; | ||||
| // 媒体查询 | |||||
| @media screen and (max-width: 1600px) { | |||||
| flex: 1 1 content; | |||||
| } | |||||
| &__icon { | &__icon { | ||||
| width: 63px; | width: 63px; | ||||
| margin-right: 15px; | |||||
| margin-right: 16px; | |||||
| } | } | ||||
| &__title { | &__title { | ||||
| @@ -8,7 +8,6 @@ | |||||
| &__overview { | &__overview { | ||||
| flex: 1; | flex: 1; | ||||
| gap: 15px; | gap: 15px; | ||||
| margin-bottom: 16px; | |||||
| padding: 20px 30px; | padding: 20px 30px; | ||||
| background-color: white; | background-color: white; | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| @@ -22,7 +21,6 @@ | |||||
| &__content { | &__content { | ||||
| display: flex; | display: flex; | ||||
| flex-wrap: wrap; | flex-wrap: wrap; | ||||
| gap: 15px; | gap: 15px; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -35,6 +33,11 @@ | |||||
| rgba(22, 100, 255, 0.02) 58.35% | rgba(22, 100, 255, 0.02) 58.35% | ||||
| ); | ); | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| // 媒体查询 | |||||
| @media screen and (max-width: 1600px) { | |||||
| flex: 1 1 content; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -44,6 +47,7 @@ | |||||
| gap: 15px; | gap: 15px; | ||||
| align-items: flex-start; | align-items: flex-start; | ||||
| width: 100%; | width: 100%; | ||||
| margin-top: 16px; | |||||
| } | } | ||||
| &__user { | &__user { | ||||
| @@ -46,7 +46,7 @@ function Workspace() { | |||||
| return ( | return ( | ||||
| <div className={styles.workspace}> | <div className={styles.workspace}> | ||||
| <WorkspaceIntro></WorkspaceIntro> | <WorkspaceIntro></WorkspaceIntro> | ||||
| <Flex gap={'0 15px'} wrap> | |||||
| <Flex gap={'15px'} wrap> | |||||
| <div className={styles['workspace__overview']}> | <div className={styles['workspace__overview']}> | ||||
| <div className={styles['workspace__overview__title']}>运行概览</div> | <div className={styles['workspace__overview__title']}>运行概览</div> | ||||
| <div className={styles['workspace__overview__content']}> | <div className={styles['workspace__overview__content']}> | ||||
| @@ -59,7 +59,7 @@ function Workspace() { | |||||
| <Divider | <Divider | ||||
| type="vertical" | type="vertical" | ||||
| dashed | dashed | ||||
| style={{ color: '1px dashed rgba(96, 107, 122, 0.19)', height: 68 }} | |||||
| style={{ color: '1px dashed rgba(96, 107, 122, 0.19)', height: 68, margin: 0 }} | |||||
| /> | /> | ||||
| <TotalStatistics | <TotalStatistics | ||||
| icon={require('@/assets/img/workspace-experiment.png')} | icon={require('@/assets/img/workspace-experiment.png')} | ||||
| @@ -71,6 +71,7 @@ export type PipelineNodeModel = { | |||||
| component_label: string; | component_label: string; | ||||
| icon_path: string; | icon_path: string; | ||||
| workflowId?: string; | workflowId?: string; | ||||
| message?: string; | |||||
| }; | }; | ||||
| // 流水线节点模型数据 | // 流水线节点模型数据 | ||||
| @@ -41,7 +41,10 @@ export const elapsedTime = (begin?: string | null, end?: string | null): string | |||||
| if (hours !== 0) { | if (hours !== 0) { | ||||
| return `${hours}小时${minutes}分`; | return `${hours}小时${minutes}分`; | ||||
| } | } | ||||
| return `${minutes}分${seconds}秒`; | |||||
| if (minutes !== 0) { | |||||
| return `${minutes}分${seconds}秒`; | |||||
| } | |||||
| return `${seconds}秒`; | |||||
| }; | }; | ||||
| /** | /** | ||||
| @@ -25,6 +25,7 @@ export type TableCellValueOptions<T> = { | |||||
| dateFormat?: string; // 类型为 Date 时有效 | dateFormat?: string; // 类型为 Date 时有效 | ||||
| onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | ||||
| format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | ||||
| copyable?: boolean; // 省略时是否可以复制 | |||||
| }; | }; | ||||
| type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | ||||
| @@ -93,17 +94,17 @@ function tableCellRender<T>( | |||||
| } | } | ||||
| if (ellipsis === 'auto' && text) { | if (ellipsis === 'auto' && text) { | ||||
| return renderCell(type, text, 'auto', record, options?.onClick); | |||||
| return renderCell(type, text, 'auto', record, options); | |||||
| } else if (ellipsis && text) { | } else if (ellipsis && text) { | ||||
| const tooltipProps = typeof ellipsis === 'object' ? ellipsis : {}; | const tooltipProps = typeof ellipsis === 'object' ? ellipsis : {}; | ||||
| const { overlayStyle, ...rest } = tooltipProps; | const { overlayStyle, ...rest } = tooltipProps; | ||||
| return ( | return ( | ||||
| <Tooltip {...rest} overlayStyle={{ maxWidth: 400, ...overlayStyle }} title={text}> | <Tooltip {...rest} overlayStyle={{ maxWidth: 400, ...overlayStyle }} title={text}> | ||||
| {renderCell(type, text, true, record, options?.onClick)} | |||||
| {renderCell(type, text, true, record, options)} | |||||
| </Tooltip> | </Tooltip> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| return renderCell(type, text, false, record, options?.onClick); | |||||
| return renderCell(type, text, false, record, options); | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| @@ -113,31 +114,38 @@ function renderCell<T>( | |||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| ellipsis: boolean | 'auto', | ellipsis: boolean | 'auto', | ||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | ) { | ||||
| return type === TableCellValueType.Link | return type === TableCellValueType.Link | ||||
| ? renderLink(text, ellipsis, record, onClick) | |||||
| : renderText(text, ellipsis); | |||||
| ? renderLink(text, ellipsis, record, options) | |||||
| : renderText(text, ellipsis, options); | |||||
| } | } | ||||
| function renderLink<T>( | function renderLink<T>( | ||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| ellipsis: boolean | 'auto', | ellipsis: boolean | 'auto', | ||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | ) { | ||||
| const { onClick } = options ?? {}; | |||||
| return ( | return ( | ||||
| <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | ||||
| {renderText(text, ellipsis)} | |||||
| {renderText(text, ellipsis, options)} | |||||
| </a> | </a> | ||||
| ); | ); | ||||
| } | } | ||||
| function renderText(text: any | undefined | null, ellipsis: boolean | 'auto') { | |||||
| function renderText<T>( | |||||
| text: any | undefined | null, | |||||
| ellipsis: boolean | 'auto', | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | |||||
| const { copyable } = options ?? {}; | |||||
| if (ellipsis === 'auto') { | if (ellipsis === 'auto') { | ||||
| return ( | return ( | ||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| style={{ marginBottom: 0 }} | style={{ marginBottom: 0 }} | ||||
| copyable={copyable} | |||||
| ellipsis={{ | ellipsis={{ | ||||
| tooltip: { | tooltip: { | ||||
| title: text, | title: text, | ||||
| @@ -31,6 +31,9 @@ public class ImageVersion implements Serializable { | |||||
| @ApiModelProperty(value = "镜像版本") | @ApiModelProperty(value = "镜像版本") | ||||
| private String version; | private String version; | ||||
| @ApiModelProperty(value = "镜像版本描述") | |||||
| private String description; | |||||
| @ApiModelProperty(value = "镜像推送地址") | @ApiModelProperty(value = "镜像推送地址") | ||||
| private String url; | private String url; | ||||
| @@ -18,4 +18,12 @@ public interface RayDao { | |||||
| int save(@Param("ray") Ray ray); | int save(@Param("ray") Ray ray); | ||||
| int edit(@Param("ray") Ray ray); | int edit(@Param("ray") Ray ray); | ||||
| List<Ray> queryByDatasetId(@Param("datasetId") String datasetId); | |||||
| List<Ray> queryByModelId(@Param("modelId") String modelId); | |||||
| List<Ray> queryByImageId(@Param("imageId") String imageId); | |||||
| List<Ray> queryByCodeConfig(@Param("codeConfig") String codeConfig); | |||||
| } | } | ||||
| @@ -199,9 +199,9 @@ public class AutoMlServiceImpl implements AutoMlService { | |||||
| String outputPath = minioEndpoint + "/" + output2.get("path").replace("{{workflow.name}}", (String) metadata.get("name")) + "/"; | String outputPath = minioEndpoint + "/" + output2.get("path").replace("{{workflow.name}}", (String) metadata.get("name")) + "/"; | ||||
| autoMlIns.setModelPath(outputPath + "save_model.joblib"); | autoMlIns.setModelPath(outputPath + "save_model.joblib"); | ||||
| if (Constant.AutoMl_Classification.equals(autoMl.getTaskType())) { | if (Constant.AutoMl_Classification.equals(autoMl.getTaskType())) { | ||||
| autoMlIns.setImgPath(outputPath + "Auto-sklearn_accuracy_over_time.png" + "," + outputPath + "Train_Confusion_Matrix.png" + "," + outputPath + "Test_Confusion_Matrix.png"); | |||||
| autoMlIns.setImgPath(outputPath + "Auto-sklearn_metric_over_time.png" + "," + outputPath + "Train_Confusion_Matrix.png" + "," + outputPath + "Test_Confusion_Matrix.png"); | |||||
| } else { | } else { | ||||
| autoMlIns.setImgPath(outputPath + "Auto-sklearn_accuracy_over_time.png" + "," + outputPath + "regression.png"); | |||||
| autoMlIns.setImgPath(outputPath + "Auto-sklearn_metric_over_time.png" + "," + outputPath + "regression.png"); | |||||
| } | } | ||||
| autoMlIns.setResultPath(outputPath + "result.txt"); | autoMlIns.setResultPath(outputPath + "result.txt"); | ||||
| String seed = autoMl.getSeed() != null ? String.valueOf(autoMl.getSeed()) : "1"; | String seed = autoMl.getSeed() != null ? String.valueOf(autoMl.getSeed()) : "1"; | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ruoyi.platform.service.impl; | package com.ruoyi.platform.service.impl; | ||||
| import com.alibaba.fastjson2.JSON; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.constant.Constant; | import com.ruoyi.platform.constant.Constant; | ||||
| import com.ruoyi.platform.domain.AssetWorkflow; | import com.ruoyi.platform.domain.AssetWorkflow; | ||||
| @@ -13,9 +14,12 @@ import org.springframework.data.domain.Page; | |||||
| import org.springframework.data.domain.PageImpl; | import org.springframework.data.domain.PageImpl; | ||||
| import org.springframework.data.domain.PageRequest; | import org.springframework.data.domain.PageRequest; | ||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
| import com.ruoyi.platform.domain.Ray; | |||||
| import com.ruoyi.platform.mapper.RayDao; | |||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.util.Date; | import java.util.Date; | ||||
| import java.util.HashMap; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
| @@ -26,6 +30,8 @@ public class CodeConfigServiceImpl implements CodeConfigService { | |||||
| private CodeConfigDao codeConfigDao; | private CodeConfigDao codeConfigDao; | ||||
| @Resource | @Resource | ||||
| private AssetWorkflowDao assetWorkflowDao; | private AssetWorkflowDao assetWorkflowDao; | ||||
| @Resource | |||||
| private RayDao rayDao; | |||||
| @Override | @Override | ||||
| public Page<CodeConfig> queryByPage(CodeConfig codeConfig, PageRequest pageRequest) { | public Page<CodeConfig> queryByPage(CodeConfig codeConfig, PageRequest pageRequest) { | ||||
| @@ -88,6 +94,14 @@ public class CodeConfigServiceImpl implements CodeConfigService { | |||||
| throw new Exception("该代码配置被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | throw new Exception("该代码配置被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | ||||
| } | } | ||||
| HashMap<String, String> map = new HashMap<>(); | |||||
| map.put("code_path", codeConfig.getGitUrl()); | |||||
| List<Ray> rayList = rayDao.queryByCodeConfig(JSON.toJSONString(map)); | |||||
| if (rayList != null && !rayList.isEmpty()) { | |||||
| String rays = String.join(",", rayList.stream().map(Ray::getName).collect(Collectors.toSet())); | |||||
| throw new Exception("该代码配置被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||||
| } | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | LoginUser loginUser = SecurityUtils.getLoginUser(); | ||||
| String username = loginUser.getUsername(); | String username = loginUser.getUsername(); | ||||
| String createBy = codeConfig.getCreateBy(); | String createBy = codeConfig.getCreateBy(); | ||||
| @@ -1,16 +1,11 @@ | |||||
| package com.ruoyi.platform.service.impl; | package com.ruoyi.platform.service.impl; | ||||
| import com.alibaba.fastjson2.JSON; | |||||
| import com.alibaba.fastjson2.util.DateUtils; | import com.alibaba.fastjson2.util.DateUtils; | ||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.constant.Constant; | import com.ruoyi.platform.constant.Constant; | ||||
| import com.ruoyi.platform.domain.AssetWorkflow; | |||||
| import com.ruoyi.platform.domain.DevEnvironment; | |||||
| import com.ruoyi.platform.domain.Image; | |||||
| import com.ruoyi.platform.domain.ImageVersion; | |||||
| import com.ruoyi.platform.mapper.AssetWorkflowDao; | |||||
| import com.ruoyi.platform.mapper.DevEnvironmentDao; | |||||
| import com.ruoyi.platform.mapper.ImageDao; | |||||
| import com.ruoyi.platform.mapper.ImageVersionDao; | |||||
| import com.ruoyi.platform.domain.*; | |||||
| import com.ruoyi.platform.mapper.*; | |||||
| import com.ruoyi.platform.service.ImageService; | import com.ruoyi.platform.service.ImageService; | ||||
| import com.ruoyi.platform.service.ImageVersionService; | import com.ruoyi.platform.service.ImageVersionService; | ||||
| import com.ruoyi.platform.service.MinioService; | import com.ruoyi.platform.service.MinioService; | ||||
| @@ -53,10 +48,10 @@ public class ImageServiceImpl implements ImageService { | |||||
| private ImageVersionDao imageVersionDao; | private ImageVersionDao imageVersionDao; | ||||
| @Resource | @Resource | ||||
| private DevEnvironmentDao devEnvironmentDao; | private DevEnvironmentDao devEnvironmentDao; | ||||
| @Resource | @Resource | ||||
| private AssetWorkflowDao assetWorkflowDao; | private AssetWorkflowDao assetWorkflowDao; | ||||
| @Resource | |||||
| private RayDao rayDao; | |||||
| @Resource | @Resource | ||||
| private ImageVersionService imageVersionService; | private ImageVersionService imageVersionService; | ||||
| @@ -182,6 +177,14 @@ public class ImageServiceImpl implements ImageService { | |||||
| throw new Exception("该镜像被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | throw new Exception("该镜像被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | ||||
| } | } | ||||
| HashMap<String, String> map = new HashMap<>(); | |||||
| map.put("id", String.valueOf(id)); | |||||
| List<Ray> rayList = rayDao.queryByImageId(JSON.toJSONString(map)); | |||||
| if (rayList != null && !rayList.isEmpty()) { | |||||
| String rays = String.join(",", rayList.stream().map(Ray::getName).collect(Collectors.toSet())); | |||||
| throw new Exception("该镜像被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||||
| } | |||||
| //判断权限,只有admin和创建者本身可以删除该数据集 | //判断权限,只有admin和创建者本身可以删除该数据集 | ||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | LoginUser loginUser = SecurityUtils.getLoginUser(); | ||||
| String username = loginUser.getUsername(); | String username = loginUser.getUsername(); | ||||
| @@ -246,6 +249,7 @@ public class ImageServiceImpl implements ImageService { | |||||
| imageVersion.setVersion(imageVo.getVersion()); | imageVersion.setVersion(imageVo.getVersion()); | ||||
| imageVersion.setTagName(imageVo.getTagName()); | imageVersion.setTagName(imageVo.getTagName()); | ||||
| imageVersion.setFileSize(imageVo.getFileSize()); | imageVersion.setFileSize(imageVo.getFileSize()); | ||||
| imageVersion.setDescription(imageVo.getDescription()); | |||||
| imageVersion.setStatus("building"); | imageVersion.setStatus("building"); | ||||
| ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion); | ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion); | ||||
| if (imageVersionInsert == null) { | if (imageVersionInsert == null) { | ||||
| @@ -1,23 +1,25 @@ | |||||
| package com.ruoyi.platform.service.impl; | package com.ruoyi.platform.service.impl; | ||||
| import com.alibaba.fastjson2.JSON; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.constant.Constant; | import com.ruoyi.platform.constant.Constant; | ||||
| import com.ruoyi.platform.domain.AssetWorkflow; | import com.ruoyi.platform.domain.AssetWorkflow; | ||||
| import com.ruoyi.platform.domain.ImageVersion; | import com.ruoyi.platform.domain.ImageVersion; | ||||
| import com.ruoyi.platform.domain.Ray; | |||||
| import com.ruoyi.platform.mapper.AssetWorkflowDao; | import com.ruoyi.platform.mapper.AssetWorkflowDao; | ||||
| import com.ruoyi.platform.mapper.ImageDao; | |||||
| import com.ruoyi.platform.mapper.ImageVersionDao; | import com.ruoyi.platform.mapper.ImageVersionDao; | ||||
| import com.ruoyi.platform.domain.ModelsVersion; | |||||
| import com.ruoyi.platform.mapper.RayDao; | |||||
| import com.ruoyi.platform.service.ImageVersionService; | import com.ruoyi.platform.service.ImageVersionService; | ||||
| import com.ruoyi.system.api.model.LoginUser; | import com.ruoyi.system.api.model.LoginUser; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.stereotype.Service; | |||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageImpl; | import org.springframework.data.domain.PageImpl; | ||||
| import org.springframework.data.domain.PageRequest; | import org.springframework.data.domain.PageRequest; | ||||
| import org.springframework.stereotype.Service; | |||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.util.Date; | import java.util.Date; | ||||
| import java.util.HashMap; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
| @@ -31,9 +33,10 @@ import java.util.stream.Collectors; | |||||
| public class ImageVersionServiceImpl implements ImageVersionService { | public class ImageVersionServiceImpl implements ImageVersionService { | ||||
| @Resource | @Resource | ||||
| private ImageVersionDao imageVersionDao; | private ImageVersionDao imageVersionDao; | ||||
| @Resource | @Resource | ||||
| private AssetWorkflowDao assetWorkflowDao; | private AssetWorkflowDao assetWorkflowDao; | ||||
| @Resource | |||||
| private RayDao rayDao; | |||||
| /** | /** | ||||
| * 通过ID查询单条数据 | * 通过ID查询单条数据 | ||||
| @@ -50,7 +53,7 @@ public class ImageVersionServiceImpl implements ImageVersionService { | |||||
| * 分页查询 | * 分页查询 | ||||
| * | * | ||||
| * @param imageVersion 筛选条件 | * @param imageVersion 筛选条件 | ||||
| * @param pageRequest 分页对象 | |||||
| * @param pageRequest 分页对象 | |||||
| * @return 查询结果 | * @return 查询结果 | ||||
| */ | */ | ||||
| @Override | @Override | ||||
| @@ -60,14 +63,14 @@ public class ImageVersionServiceImpl implements ImageVersionService { | |||||
| } | } | ||||
| @Override | @Override | ||||
| public List<ImageVersion> queryByImageId(Integer imageId){ | |||||
| public List<ImageVersion> queryByImageId(Integer imageId) { | |||||
| return imageVersionDao.queryByImageId(imageId); | return imageVersionDao.queryByImageId(imageId); | ||||
| } | } | ||||
| @Override | @Override | ||||
| public String removeById(Integer id) throws Exception { | public String removeById(Integer id) throws Exception { | ||||
| ImageVersion imageVersion = this.imageVersionDao.queryById(id); | ImageVersion imageVersion = this.imageVersionDao.queryById(id); | ||||
| if (imageVersion == null){ | |||||
| if (imageVersion == null) { | |||||
| return "该版本下模型文件信息不存在"; | return "该版本下模型文件信息不存在"; | ||||
| } | } | ||||
| @@ -77,16 +80,24 @@ public class ImageVersionServiceImpl implements ImageVersionService { | |||||
| throw new Exception("该镜像版本被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | throw new Exception("该镜像版本被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | ||||
| } | } | ||||
| HashMap<String, String> map = new HashMap<>(); | |||||
| map.put("version", String.valueOf(id)); | |||||
| List<Ray> rayList = rayDao.queryByImageId(JSON.toJSONString(map)); | |||||
| if (rayList != null && !rayList.isEmpty()) { | |||||
| String rays = String.join(",", rayList.stream().map(Ray::getName).collect(Collectors.toSet())); | |||||
| throw new Exception("该镜像版本被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||||
| } | |||||
| //判断权限,只有admin和创建者本身可以删除该数据集 | //判断权限,只有admin和创建者本身可以删除该数据集 | ||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | LoginUser loginUser = SecurityUtils.getLoginUser(); | ||||
| String username = loginUser.getUsername(); | String username = loginUser.getUsername(); | ||||
| String createdBy = imageVersion.getCreateBy(); | String createdBy = imageVersion.getCreateBy(); | ||||
| if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ | |||||
| if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createdBy))) { | |||||
| return "无权限删除该版本下模型信息"; | return "无权限删除该版本下模型信息"; | ||||
| } | } | ||||
| imageVersion.setState(0); | imageVersion.setState(0); | ||||
| return this.imageVersionDao.update(imageVersion)>0?"删除成功":"删除失败"; | |||||
| return this.imageVersionDao.update(imageVersion) > 0 ? "删除成功" : "删除失败"; | |||||
| } | } | ||||
| @@ -117,7 +128,7 @@ public class ImageVersionServiceImpl implements ImageVersionService { | |||||
| @Override | @Override | ||||
| public ImageVersion update(ImageVersion imageVersion) { | public ImageVersion update(ImageVersion imageVersion) { | ||||
| int currentState = imageVersion.getState(); | int currentState = imageVersion.getState(); | ||||
| if(currentState == 0){ | |||||
| if (currentState == 0) { | |||||
| throw new RuntimeException("镜像版本已被删除,无法更新。"); | throw new RuntimeException("镜像版本已被删除,无法更新。"); | ||||
| } | } | ||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | LoginUser loginUser = SecurityUtils.getLoginUser(); | ||||
| @@ -9,10 +9,7 @@ import com.ruoyi.platform.annotations.CheckDuplicate; | |||||
| import com.ruoyi.platform.constant.Constant; | import com.ruoyi.platform.constant.Constant; | ||||
| import com.ruoyi.platform.domain.*; | import com.ruoyi.platform.domain.*; | ||||
| import com.ruoyi.platform.domain.dependencydomain.TrainTaskDepency; | import com.ruoyi.platform.domain.dependencydomain.TrainTaskDepency; | ||||
| import com.ruoyi.platform.mapper.AssetWorkflowDao; | |||||
| import com.ruoyi.platform.mapper.ModelDependency1Dao; | |||||
| import com.ruoyi.platform.mapper.ModelsDao; | |||||
| import com.ruoyi.platform.mapper.ModelsVersionDao; | |||||
| import com.ruoyi.platform.mapper.*; | |||||
| import com.ruoyi.platform.service.*; | import com.ruoyi.platform.service.*; | ||||
| import com.ruoyi.platform.utils.*; | import com.ruoyi.platform.utils.*; | ||||
| import com.ruoyi.platform.vo.*; | import com.ruoyi.platform.vo.*; | ||||
| @@ -67,7 +64,8 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| private ModelsDao modelsDao; | private ModelsDao modelsDao; | ||||
| @Resource | @Resource | ||||
| private ModelsVersionDao modelsVersionDao; | private ModelsVersionDao modelsVersionDao; | ||||
| @Resource | |||||
| private RayDao rayDao; | |||||
| @Resource | @Resource | ||||
| private ModelsVersionService modelsVersionService; | private ModelsVersionService modelsVersionService; | ||||
| @@ -634,7 +632,6 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| "</code></pre>"); | "</code></pre>"); | ||||
| modelMetaVo.setIdentifier(repositoryName); | modelMetaVo.setIdentifier(repositoryName); | ||||
| modelMetaVo.setOwner(owner); | modelMetaVo.setOwner(owner); | ||||
| modelMetaVo.setVersionDesc(modelMetaVo.getDescription()); | |||||
| modelMetaVo.setRelativePaths(relatePath); | modelMetaVo.setRelativePaths(relatePath); | ||||
| File folder = new File(modelPath); | File folder = new File(modelPath); | ||||
| long folderSize = FileUtil.getFolderSize(folder); | long folderSize = FileUtil.getFolderSize(folder); | ||||
| @@ -1154,6 +1151,14 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| throw new Exception("该模型被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | throw new Exception("该模型被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | ||||
| } | } | ||||
| HashMap<String, String> queryMap = new HashMap<>(); | |||||
| queryMap.put("id", String.valueOf(repoId)); | |||||
| 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 + "使用,不能删除,请先删除超参数自动寻优。"); | |||||
| } | |||||
| String token = gitService.checkoutToken(); | String token = gitService.checkoutToken(); | ||||
| gitService.deleteProject(token, owner, identifier); | gitService.deleteProject(token, owner, identifier); | ||||
| //删除模型依赖 | //删除模型依赖 | ||||
| @@ -1177,6 +1182,15 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| throw new Exception("该模型版本被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | throw new Exception("该模型版本被流水线:" + workflows + "使用,不能删除,请先删除流水线。"); | ||||
| } | } | ||||
| HashMap<String, String> queryMap = new HashMap<>(); | |||||
| queryMap.put("id", String.valueOf(repoId)); | |||||
| queryMap.put("version", version); | |||||
| 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 + "使用,不能删除,请先删除超参数自动寻优。"); | |||||
| } | |||||
| String token = gitService.checkoutToken(); | String token = gitService.checkoutToken(); | ||||
| String rootPath = Paths.get(localPath + "/" + relativePath).getParent().toString(); | String rootPath = Paths.get(localPath + "/" + relativePath).getParent().toString(); | ||||
| gitService.deleteBranch(token, owner, identifier, version, rootPath); | gitService.deleteBranch(token, owner, identifier, version, rootPath); | ||||
| @@ -4,12 +4,10 @@ import com.alibaba.fastjson2.JSON; | |||||
| import com.ruoyi.common.core.utils.DateUtils; | import com.ruoyi.common.core.utils.DateUtils; | ||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.constant.Constant; | import com.ruoyi.platform.constant.Constant; | ||||
| import com.ruoyi.platform.domain.AssetWorkflow; | |||||
| import com.ruoyi.platform.domain.AutoMl; | |||||
| import com.ruoyi.platform.domain.Dataset; | |||||
| import com.ruoyi.platform.domain.DatasetTempStorage; | |||||
| import com.ruoyi.platform.domain.*; | |||||
| import com.ruoyi.platform.mapper.AssetWorkflowDao; | import com.ruoyi.platform.mapper.AssetWorkflowDao; | ||||
| import com.ruoyi.platform.mapper.AutoMlDao; | import com.ruoyi.platform.mapper.AutoMlDao; | ||||
| import com.ruoyi.platform.mapper.RayDao; | |||||
| import com.ruoyi.platform.service.DatasetTempStorageService; | import com.ruoyi.platform.service.DatasetTempStorageService; | ||||
| import com.ruoyi.platform.service.GitService; | import com.ruoyi.platform.service.GitService; | ||||
| import com.ruoyi.platform.service.NewDatasetService; | import com.ruoyi.platform.service.NewDatasetService; | ||||
| @@ -56,6 +54,8 @@ public class NewDatasetServiceImpl implements NewDatasetService { | |||||
| private AssetWorkflowDao assetWorkflowDao; | private AssetWorkflowDao assetWorkflowDao; | ||||
| @Resource | @Resource | ||||
| private AutoMlDao autoMlDao; | private AutoMlDao autoMlDao; | ||||
| @Resource | |||||
| private RayDao rayDao; | |||||
| @Value("${spring.redis.host}") | @Value("${spring.redis.host}") | ||||
| private String redisHost; | private String redisHost; | ||||
| @@ -146,7 +146,6 @@ public class NewDatasetServiceImpl implements NewDatasetService { | |||||
| // 拼接生产的元数据后写入yaml文件 | // 拼接生产的元数据后写入yaml文件 | ||||
| datasetVo.setCreateBy(String.valueOf(StringUtils.isNotEmpty((String) userInfo.get("nickname")) ? userInfo.get("nickname") : userInfo.get("login"))); | datasetVo.setCreateBy(String.valueOf(StringUtils.isNotEmpty((String) userInfo.get("nickname")) ? userInfo.get("nickname") : userInfo.get("login"))); | ||||
| datasetVo.setUpdateTime(DateUtils.getTime()); | datasetVo.setUpdateTime(DateUtils.getTime()); | ||||
| datasetVo.setVersionDesc(datasetVo.getDescription()); | |||||
| datasetVo.setUsage("<pre><code>" + | datasetVo.setUsage("<pre><code>" + | ||||
| "# 克隆数据集配置文件与存储参数到本地\n" + | "# 克隆数据集配置文件与存储参数到本地\n" + | ||||
| "git clone -b " + branchName + " " + projectUrl + "\n" + | "git clone -b " + branchName + " " + projectUrl + "\n" + | ||||
| @@ -421,6 +420,12 @@ public class NewDatasetServiceImpl implements NewDatasetService { | |||||
| throw new Exception("该数据集被自动机器学习:" + autoMls + "使用,不能删除,请先删除自动机器学习。"); | throw new Exception("该数据集被自动机器学习:" + autoMls + "使用,不能删除,请先删除自动机器学习。"); | ||||
| } | } | ||||
| List<Ray> rayList = rayDao.queryByDatasetId(JSON.toJSONString(map)); | |||||
| if (rayList != null && !rayList.isEmpty()) { | |||||
| String rays = String.join(",", rayList.stream().map(Ray::getName).collect(Collectors.toSet())); | |||||
| throw new Exception("该数据集被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||||
| } | |||||
| String token = gitService.checkoutToken(); | String token = gitService.checkoutToken(); | ||||
| gitService.deleteProject(token, owner, repo); | gitService.deleteProject(token, owner, repo); | ||||
| @@ -446,6 +451,12 @@ public class NewDatasetServiceImpl implements NewDatasetService { | |||||
| throw new Exception("该数据集版本被自动机器学习:" + autoMls + "使用,不能删除,请先删除自动机器学习。"); | throw new Exception("该数据集版本被自动机器学习:" + autoMls + "使用,不能删除,请先删除自动机器学习。"); | ||||
| } | } | ||||
| List<Ray> rayList = rayDao.queryByDatasetId(JSON.toJSONString(map)); | |||||
| if (rayList != null && !rayList.isEmpty()) { | |||||
| String rays = String.join(",", rayList.stream().map(Ray::getName).collect(Collectors.toSet())); | |||||
| throw new Exception("该数据集版本被超参数自动寻优:" + rays + "使用,不能删除,请先删除超参数自动寻优。"); | |||||
| } | |||||
| String token = gitService.checkoutToken(); | String token = gitService.checkoutToken(); | ||||
| String rootPath = Paths.get(localPathlocal + "/" + relativePath).getParent().toString(); | String rootPath = Paths.get(localPathlocal + "/" + relativePath).getParent().toString(); | ||||
| gitService.deleteBranch(token, owner, repo, version, rootPath); | gitService.deleteBranch(token, owner, repo, version, rootPath); | ||||
| @@ -33,8 +33,8 @@ public class ResourceOccupyServiceImpl implements ResourceOccupyService { | |||||
| ComputingResource computingResource = computingResourceDao.queryById(computingResourceId); | ComputingResource computingResource = computingResourceDao.queryById(computingResourceId); | ||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | LoginUser loginUser = SecurityUtils.getLoginUser(); | ||||
| if (loginUser.getSysUser().getCredit() == 0) { | |||||
| throw new Exception("当前积分为零"); | |||||
| if (loginUser.getSysUser().getCredit() <= 0) { | |||||
| throw new Exception("当前积分不足"); | |||||
| } | } | ||||
| if (Constant.Computing_Resource_GPU.equals(computingResource.getComputingResource())) { | if (Constant.Computing_Resource_GPU.equals(computingResource.getComputingResource())) { | ||||
| if (resourceOccupyDao.haveResource(computingResource.getResourceId(), computingResource.getGpuNums() * replicas)) { | if (resourceOccupyDao.haveResource(computingResource.getResourceId(), computingResource.getGpuNums() * replicas)) { | ||||
| @@ -323,7 +323,7 @@ public class ServiceServiceImpl implements ServiceService { | |||||
| // 记录开始扣积分 | // 记录开始扣积分 | ||||
| if (reRun) { | if (reRun) { | ||||
| resourceOccupyService.startDeduce(serviceVersion.getComputingResourceId(), serviceVersion.getReplicas(), Constant.TaskType_Service, serviceVersion.getServiceId(), serviceVersion.getId(), null, service.getServiceName(), null, null); | resourceOccupyService.startDeduce(serviceVersion.getComputingResourceId(), serviceVersion.getReplicas(), Constant.TaskType_Service, serviceVersion.getServiceId(), serviceVersion.getId(), null, service.getServiceName(), null, null); | ||||
| } else if (Constant.Running.equals(serviceVersionById.getRunState())) { | |||||
| } else if (Constant.Running.equals(serviceVersionById.getRunState()) || Constant.Pending.equals(serviceVersionById.getRunState())) { | |||||
| resourceOccupyService.update(Constant.TaskType_Service, serviceVersion.getServiceId(), serviceVersion.getId(), serviceVersion.getComputingResourceId(), serviceVersion.getReplicas()); | resourceOccupyService.update(Constant.TaskType_Service, serviceVersion.getServiceId(), serviceVersion.getId(), serviceVersion.getComputingResourceId(), serviceVersion.getReplicas()); | ||||
| } | } | ||||
| return "修改成功"; | return "修改成功"; | ||||
| @@ -126,6 +126,13 @@ public class WorkflowServiceImpl implements WorkflowService { | |||||
| this.workflowDao.update(workflow); | this.workflowDao.update(workflow); | ||||
| assetWorkflowDao.deleteByWorkFlowId(workflow.getId()); | assetWorkflowDao.deleteByWorkFlowId(workflow.getId()); | ||||
| saveAssetWorkFlow(workflow); | saveAssetWorkFlow(workflow); | ||||
| if (StringUtils.isNotEmpty(workflow.getGlobalParam())) { | |||||
| List<Experiment> experimentList = experimentService.queryByWorkflowId(workflow.getId()); | |||||
| for (Experiment experiment : experimentList) { | |||||
| experiment.setGlobalParam(workflow.getGlobalParam()); | |||||
| experimentService.update(experiment); | |||||
| } | |||||
| } | |||||
| return this.queryById(workflow.getId()); | return this.queryById(workflow.getId()); | ||||
| } | } | ||||
| @@ -129,8 +129,8 @@ | |||||
| <!--新增所有列--> | <!--新增所有列--> | ||||
| <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | ||||
| insert into image_version(image_id, version, url, tag_name, file_size, status, create_by, create_time, update_by, update_time, state) | |||||
| values (#{imageVersion.imageId}, #{imageVersion.version}, #{imageVersion.url}, #{imageVersion.tagName}, #{imageVersion.fileSize}, #{imageVersion.status}, #{imageVersion.createBy}, #{imageVersion.createTime}, #{imageVersion.updateBy}, #{imageVersion.updateTime}, #{imageVersion.state}) | |||||
| insert into image_version(image_id, version, description, url, tag_name, file_size, status, create_by, create_time, update_by, update_time, state) | |||||
| values (#{imageVersion.imageId}, #{imageVersion.version}, #{imageVersion.description}, #{imageVersion.url}, #{imageVersion.tagName}, #{imageVersion.fileSize}, #{imageVersion.status}, #{imageVersion.createBy}, #{imageVersion.createTime}, #{imageVersion.updateBy}, #{imageVersion.updateTime}, #{imageVersion.state}) | |||||
| </insert> | </insert> | ||||
| @@ -109,6 +109,34 @@ | |||||
| where id = #{id} | where id = #{id} | ||||
| </select> | </select> | ||||
| <select id="queryByDatasetId" resultType="com.ruoyi.platform.domain.Ray"> | |||||
| select * | |||||
| from ray | |||||
| where JSON_CONTAINS(dataset, #{datasetId}) | |||||
| and state = 1 | |||||
| </select> | |||||
| <select id="queryByModelId" resultType="com.ruoyi.platform.domain.Ray"> | |||||
| select * | |||||
| from ray | |||||
| where JSON_CONTAINS(model, #{modelId}) | |||||
| and state = 1 | |||||
| </select> | |||||
| <select id="queryByImageId" resultType="com.ruoyi.platform.domain.Ray"> | |||||
| select * | |||||
| from ray | |||||
| where JSON_CONTAINS(image, #{imageId}) | |||||
| and state = 1 | |||||
| </select> | |||||
| <select id="queryByCodeConfig" resultType="com.ruoyi.platform.domain.Ray"> | |||||
| select * | |||||
| from ray | |||||
| where JSON_CONTAINS(code_config, #{codeConfig}) | |||||
| and state = 1 | |||||
| </select> | |||||
| <sql id="common_condition"> | <sql id="common_condition"> | ||||
| <where> | <where> | ||||
| state = 1 | state = 1 | ||||