| @@ -1,14 +1,12 @@ | |||||
| // https://umijs.org/config/ | // https://umijs.org/config/ | ||||
| import { defineConfig } from '@umijs/max'; | import { defineConfig } from '@umijs/max'; | ||||
| import { join, resolve } from 'path'; | |||||
| import { join } from 'path'; | |||||
| import defaultSettings from './defaultSettings'; | import defaultSettings from './defaultSettings'; | ||||
| import proxy from './proxy'; | import proxy from './proxy'; | ||||
| import routes from './routes'; | import routes from './routes'; | ||||
| const { REACT_APP_ENV = 'dev' } = process.env; | const { REACT_APP_ENV = 'dev' } = process.env; | ||||
| console.log('zzz', resolve(__dirname, '../src/styles/theme.less')); | |||||
| export default defineConfig({ | export default defineConfig({ | ||||
| /** | /** | ||||
| * @name 开启 hash 模式 | * @name 开启 hash 模式 | ||||
| @@ -42,7 +40,6 @@ export default defineConfig({ | |||||
| // 如果不想要 configProvide 动态设置主题需要把这个设置为 default | // 如果不想要 configProvide 动态设置主题需要把这个设置为 default | ||||
| // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 | // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 | ||||
| // 'root-entry-name': 'variable', | // 'root-entry-name': 'variable', | ||||
| 'kf-success-color': '#ff0000', | |||||
| }, | }, | ||||
| /** | /** | ||||
| * @name moment 的国际化配置 | * @name moment 的国际化配置 | ||||
| @@ -193,7 +193,6 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| colorSuccess: themes['successColor'], | colorSuccess: themes['successColor'], | ||||
| colorError: themes['errorColor'], | colorError: themes['errorColor'], | ||||
| colorWarning: themes['warningColor'], | colorWarning: themes['warningColor'], | ||||
| // fontSize: themes['fontSize'], | |||||
| }; | }; | ||||
| memo.theme.components ??= {}; | memo.theme.components ??= {}; | ||||
| memo.theme.components.Tabs = {}; | memo.theme.components.Tabs = {}; | ||||
| @@ -207,20 +206,19 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| defaultActiveBg: 'rgba(22, 100, 255, 0.12)', | defaultActiveBg: 'rgba(22, 100, 255, 0.12)', | ||||
| defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', | defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', | ||||
| defaultActiveColor: themes['primaryColor'], | defaultActiveColor: themes['primaryColor'], | ||||
| contentFontSize: themes['fontSize'], | |||||
| paddingBlock: 4, | |||||
| paddingInline: 15, | |||||
| contentFontSize: parseInt(themes['fontSize']), | |||||
| controlHeight: 34, | controlHeight: 34, | ||||
| }; | }; | ||||
| memo.theme.components.Input = { | memo.theme.components.Input = { | ||||
| inputFontSize: themes['fontSize'], | |||||
| paddingBlock: 4, | |||||
| paddingInline: 15, | |||||
| inputFontSize: parseInt(themes['fontSize']), | |||||
| }; | }; | ||||
| memo.theme.components.Table = { | memo.theme.components.Table = { | ||||
| headerBg: 'rgba(242, 244, 247, 0.36)', | headerBg: 'rgba(242, 244, 247, 0.36)', | ||||
| headerBorderRadius: 4, | headerBorderRadius: 4, | ||||
| }; | }; | ||||
| memo.theme.components.Tabs = { | |||||
| titleFontSize: 16, | |||||
| }; | |||||
| memo.theme.cssVar = true; | memo.theme.cssVar = true; | ||||
| // memo.theme.hashed = false; | // memo.theme.hashed = false; | ||||
| @@ -0,0 +1,5 @@ | |||||
| function CommonTableCell(text?: string | null) { | |||||
| return <span>{text ?? '--'}</span>; | |||||
| } | |||||
| export default CommonTableCell; | |||||
| @@ -0,0 +1,13 @@ | |||||
| import dayjs from 'dayjs'; | |||||
| function DateTableCell(text?: string | null) { | |||||
| if (text === undefined || text === null || text === '') { | |||||
| return <span>--</span>; | |||||
| } | |||||
| if (!dayjs(text).isValid()) { | |||||
| return <span>日期无效</span>; | |||||
| } | |||||
| return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>; | |||||
| } | |||||
| export default DateTableCell; | |||||
| @@ -17,6 +17,11 @@ | |||||
| border: 1px solid @primary-color-hover; | border: 1px solid @primary-color-hover; | ||||
| } | } | ||||
| &:active { | |||||
| color: @primary-color; | |||||
| border: 1px solid @primary-color; | |||||
| } | |||||
| &--active { | &--active { | ||||
| color: @primary-color; | color: @primary-color; | ||||
| border: 1px solid @primary-color; | border: 1px solid @primary-color; | ||||
| @@ -1,7 +1,7 @@ | |||||
| .kf-page-title { | .kf-page-title { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| height: 49px; | |||||
| height: 50px; | |||||
| padding-left: 30px; | padding-left: 30px; | ||||
| background-image: url('../../assets/img/page-title-bg.png'); | background-image: url('../../assets/img/page-title-bg.png'); | ||||
| } | } | ||||
| @@ -4,6 +4,7 @@ body, | |||||
| height: 100%; | height: 100%; | ||||
| margin: 0; | margin: 0; | ||||
| padding: 0; | padding: 0; | ||||
| overflow-y: hidden; | |||||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, | ||||
| 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', | 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', | ||||
| 'Noto Color Emoji'; | 'Noto Color Emoji'; | ||||
| @@ -190,12 +191,13 @@ body { | |||||
| .ant-pagination .ant-pagination-item-active a { | .ant-pagination .ant-pagination-item-active a { | ||||
| color: #fff; | color: #fff; | ||||
| background: rgba(22, 100, 255, 0.8); | background: rgba(22, 100, 255, 0.8); | ||||
| // color: #fff; | |||||
| border-radius: 6px; | border-radius: 6px; | ||||
| } | } | ||||
| .ant-pagination .ant-pagination-item-active:hover { | .ant-pagination .ant-pagination-item-active:hover { | ||||
| color: #fff; | |||||
| background: rgba(22, 100, 255, 0.8); | |||||
| border-color: rgba(22, 100, 255, 0.8); | |||||
| // color: #fff; | |||||
| // background: rgba(22, 100, 255, 0.8); | |||||
| // border-color: rgba(22, 100, 255, 0.8); | |||||
| border-radius: 6px; | border-radius: 6px; | ||||
| } | } | ||||
| .ant-pagination .ant-pagination-item { | .ant-pagination .ant-pagination-item { | ||||
| @@ -1,4 +1,5 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | import { | ||||
| addDatasetVersionDetail, | addDatasetVersionDetail, | ||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| @@ -7,12 +8,7 @@ import { | |||||
| getDatasetVersionsById, | getDatasetVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { | |||||
| DeleteOutlined, | |||||
| DownloadOutlined, | |||||
| PlusCircleOutlined, | |||||
| UploadOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| @@ -203,7 +199,7 @@ const Dataset = () => { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="download" | key="download" | ||||
| icon={<DownloadOutlined />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | onClick={(e) => downloadAlone(e, record)} | ||||
| > | > | ||||
| 下载 | 下载 | ||||
| @@ -227,9 +223,9 @@ const Dataset = () => { | |||||
| <div className={Styles.datasetIntroTopBox}> | <div className={Styles.datasetIntroTopBox}> | ||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | ||||
| <div className={Styles.smallTagBox}> | <div className={Styles.smallTagBox}> | ||||
| <div className={Styles.tagItem}>数据集 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | ||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | ||||
| {/* <div className={Styles.tagItem}>English</div> */} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className={Styles.datasetIntroCneterBox}> | <div className={Styles.datasetIntroCneterBox}> | ||||
| @@ -257,10 +253,10 @@ const Dataset = () => { | |||||
| options={versionList} | options={versionList} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| onClick={showModal} | onClick={showModal} | ||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | > | ||||
| 创建新版本 | 创建新版本 | ||||
| </Button> | </Button> | ||||
| @@ -269,21 +265,21 @@ const Dataset = () => { | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | ||||
| > | > | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={deleteDataset} | onClick={deleteDataset} | ||||
| icon={<DeleteOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| disabled={!version} | disabled={!version} | ||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={handleExport} | onClick={handleExport} | ||||
| icon={<DownloadOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | > | ||||
| 下载 | 下载 | ||||
| </Button> | </Button> | ||||
| @@ -5,9 +5,9 @@ | |||||
| height: 49px; | height: 49px; | ||||
| padding: 0 30px; | padding: 0 30px; | ||||
| padding-right: 30px; | padding-right: 30px; | ||||
| font-family: 'Alibaba'; | |||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| font-family: 'Alibaba'; | |||||
| } | } | ||||
| .datasetIntroTopBox { | .datasetIntroTopBox { | ||||
| display: flex; | display: flex; | ||||
| @@ -36,10 +36,10 @@ | |||||
| padding: 20px 30px; | padding: 20px 30px; | ||||
| color: #1d1d20; | color: #1d1d20; | ||||
| font-size: 16px; | font-size: 16px; | ||||
| font-family: alibaba; | |||||
| background: #ffffff; | background: #ffffff; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | ||||
| font-family: alibaba; | |||||
| .dataButtonList { | .dataButtonList { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -70,7 +70,7 @@ | |||||
| .datasetBox { | .datasetBox { | ||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| background: #f9fafb; | background: #f9fafb; | ||||
| :global { | :global { | ||||
| .ant-tabs-top > .ant-tabs-nav { | .ant-tabs-top > .ant-tabs-nav { | ||||
| margin: 0; | margin: 0; | ||||
| @@ -89,20 +89,8 @@ | |||||
| } | } | ||||
| .plusButton { | .plusButton { | ||||
| margin: 0 18px 0 20px; | margin: 0 18px 0 20px; | ||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | } | ||||
| .datasetCneterBox { | .datasetCneterBox { | ||||
| display: flex; | display: flex; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| @@ -119,8 +107,8 @@ | |||||
| height: 100%; | height: 100%; | ||||
| margin-right: 10px; | margin-right: 10px; | ||||
| padding-top: 15px; | padding-top: 15px; | ||||
| background: #ffffff; | |||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | ||||
| .custTab { | .custTab { | ||||
| display: flex; | display: flex; | ||||
| @@ -235,62 +223,65 @@ | |||||
| position: relative; | position: relative; | ||||
| width: 23.8%; | width: 23.8%; | ||||
| height:164px; | height:164px; | ||||
| background:#ffffff; | |||||
| border:1px solid; | |||||
| border-color:#eaeaea; | |||||
| border-radius:4px; | |||||
| margin: 0 20px 25px 0; | margin: 0 20px 25px 0; | ||||
| background: #ffffff; | |||||
| border: 1px solid; | |||||
| border-color: #eaeaea; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| .itemText { | .itemText { | ||||
| position: absolute; | position: absolute; | ||||
| top: 20px; | top: 20px; | ||||
| left: 20px; | left: 20px; | ||||
| background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%); | |||||
| height: 6px; | height: 6px; | ||||
| line-height: 0px; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| line-height: 0px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| rgba(22, 100, 255, 0.6) 0, | |||||
| rgba(22, 100, 255, 0) 100% | |||||
| ); | |||||
| } | } | ||||
| .itemDescripition{ | |||||
| .itemDescripition { | |||||
| position: absolute; | position: absolute; | ||||
| top: 57px; | top: 57px; | ||||
| left: 20px; | left: 20px; | ||||
| display: -webkit-box; | |||||
| padding-right: 28px; | padding-right: 28px; | ||||
| color:#575757; | |||||
| font-size:14px; | |||||
| word-break: break-all; | |||||
| overflow: hidden; | overflow: hidden; | ||||
| display: -webkit-box; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| word-break: break-all; | |||||
| -webkit-line-clamp: 2; | -webkit-line-clamp: 2; | ||||
| -webkit-box-orient: vertical; | -webkit-box-orient: vertical; | ||||
| } | } | ||||
| .itemTime { | .itemTime { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| bottom: 22px; | bottom: 22px; | ||||
| left: 20px; | left: 20px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| .itemIcon { | .itemIcon { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| right: 20px; | right: 20px; | ||||
| bottom: 22px; | bottom: 22px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| } | } | ||||
| .dataItem:hover{ | |||||
| border-color: #1664FF; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1) | |||||
| .dataItem:hover { | |||||
| border-color: #1664ff; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | } | ||||
| .dataItem:hover .itemText{ | |||||
| color: #1664FF; | |||||
| .dataItem:hover .itemText { | |||||
| color: #1664ff; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -301,9 +292,9 @@ | |||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background-image: url(/assets/images/modal-back.png); | background-image: url(/assets/images/modal-back.png); | ||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | background-position: top center; | ||||
| background-size: 100%; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -3,13 +3,13 @@ import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import { getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; | import { getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; | ||||
| import { Form, Input, Pagination } from 'antd'; | import { Form, Input, Pagination } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import React, { useEffect, useState } from 'react'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| const { Search } = Input; | const { Search } = Input; | ||||
| const leftdataList = [1, 2, 3]; | const leftdataList = [1, 2, 3]; | ||||
| const PublicData = (React.FC = () => { | |||||
| const PublicData = () => { | |||||
| const [queryFlow, setQueryFlow] = useState({ | const [queryFlow, setQueryFlow] = useState({ | ||||
| page: 0, | page: 0, | ||||
| size: 10, | size: 10, | ||||
| @@ -256,5 +256,5 @@ const PublicData = (React.FC = () => { | |||||
| </div> | </div> | ||||
| </> | </> | ||||
| ); | ); | ||||
| }); | |||||
| }; | |||||
| export default PublicData; | export default PublicData; | ||||
| @@ -18,6 +18,7 @@ import { elapsedTime } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { Button, ConfigProvider, Space, Table, message } from 'antd'; | import { Button, ConfigProvider, Space, Table, message } from 'antd'; | ||||
| import classNames from 'classnames'; | |||||
| import momnet from 'moment'; | import momnet from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| @@ -274,12 +275,14 @@ function Experiment() { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: (text) => <div>{text}</div>, | render: (text) => <div>{text}</div>, | ||||
| width: '20%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '关联流水线名称', | title: '关联流水线名称', | ||||
| dataIndex: 'workflow_name', | dataIndex: 'workflow_name', | ||||
| key: 'workflow_name', | key: 'workflow_name', | ||||
| render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | ||||
| width: '20%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '实验描述', | title: '实验描述', | ||||
| @@ -290,6 +293,7 @@ function Experiment() { | |||||
| title: '最近五次运行状态', | title: '最近五次运行状态', | ||||
| dataIndex: 'status_list', | dataIndex: 'status_list', | ||||
| key: 'status_list', | key: 'status_list', | ||||
| width: 200, | |||||
| render: (text) => { | render: (text) => { | ||||
| let newText = text && text.replace(/\s+/g, '').split(','); | let newText = text && text.replace(/\s+/g, '').split(','); | ||||
| return ( | return ( | ||||
| @@ -376,18 +380,8 @@ function Experiment() { | |||||
| ]; | ]; | ||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| {/* <div > | |||||
| <Button type="primary" onClick={createExperiment} icon = {< PlusOutlined />}> | |||||
| 新建实验 | |||||
| </Button> | |||||
| </div> */} | |||||
| <div className={Styles.pipelineTopBox}> | <div className={Styles.pipelineTopBox}> | ||||
| <Button | |||||
| type="primary" | |||||
| className={Styles.plusButton} | |||||
| onClick={createExperiment} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| <Button type="default" onClick={createExperiment} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 新建实验 | 新建实验 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| @@ -401,12 +395,14 @@ function Experiment() { | |||||
| <div> | <div> | ||||
| {experimentInList && experimentInList.length > 0 ? ( | {experimentInList && experimentInList.length > 0 ? ( | ||||
| <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | ||||
| <div style={{ width: '150px' }}>序号</div> | |||||
| <div style={{ width: '300px' }}>TensorBoard</div> | |||||
| <div style={{ width: '300px' }}>运行时长</div> | |||||
| <div style={{ width: '300px' }}>开始时间</div> | |||||
| <div style={{ width: '200px' }}>状态</div> | |||||
| <div style={{ width: '200px' }}>操作</div> | |||||
| <div className={Styles.index}>序号</div> | |||||
| <div className={Styles.tensorBoard}>可视化</div> | |||||
| <div className={Styles.description}> | |||||
| <div style={{ width: '50%' }}>运行时长</div> | |||||
| <div style={{ width: '50%' }}>开始时间</div> | |||||
| </div> | |||||
| <div className={Styles.status}>状态</div> | |||||
| <div className={Styles.operation}>操作</div> | |||||
| </div> | </div> | ||||
| ) : ( | ) : ( | ||||
| '' | '' | ||||
| @@ -416,17 +412,16 @@ function Experiment() { | |||||
| ? experimentInList.map((item, index) => ( | ? experimentInList.map((item, index) => ( | ||||
| <div | <div | ||||
| key={item.id} | key={item.id} | ||||
| className={Styles.tableExpandBox} | |||||
| style={{ | |||||
| border: '1px solid #eaeaea', | |||||
| backgroundColor: '#fff', | |||||
| height: '45px', | |||||
| }} | |||||
| className={classNames(Styles.tableExpandBox, Styles.tableExpandBoxContent)} | |||||
| > | > | ||||
| <a style={{ width: '150px' }} onClick={(e) => routerToText(e, item, record)}> | |||||
| <a | |||||
| className={Styles.index} | |||||
| style={{ padding: '0 16px' }} | |||||
| onClick={(e) => routerToText(e, item, record)} | |||||
| > | |||||
| {index + 1} | {index + 1} | ||||
| </a> | </a> | ||||
| <div style={{ width: '300px' }}> | |||||
| <div className={Styles.tensorBoard}> | |||||
| {item.nodes_result?.tensorboard_log ? ( | {item.nodes_result?.tensorboard_log ? ( | ||||
| <TensorBoardStatus | <TensorBoardStatus | ||||
| status={item.tensorBoardStatus} | status={item.tensorBoardStatus} | ||||
| @@ -436,15 +431,17 @@ function Experiment() { | |||||
| '-' | '-' | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| <div style={{ width: '300px' }}> | |||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| </div> | |||||
| <div style={{ width: '300px' }}> | |||||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| <div className={Styles.description}> | |||||
| <div style={{ width: '50%' }}> | |||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| </div> | |||||
| <div style={{ width: '50%' }}> | |||||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div className={Styles.statusBox} style={{ width: '200px' }}> | |||||
| <div className={Styles.statusBox}> | |||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '7px' }} | style={{ width: '17px', marginRight: '7px' }} | ||||
| src={experimentStatusInfo[item.status]?.icon} | src={experimentStatusInfo[item.status]?.icon} | ||||
| @@ -456,7 +453,7 @@ function Experiment() { | |||||
| {experimentStatusInfo[item.status]?.label} | {experimentStatusInfo[item.status]?.label} | ||||
| </span> | </span> | ||||
| </div> | </div> | ||||
| <div style={{ width: '200px' }}> | |||||
| <div className={Styles.operation}> | |||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| @@ -19,35 +19,54 @@ | |||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| .plusButton { | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | |||||
| .tableExpandBox { | .tableExpandBox { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: space-between; | |||||
| width: 100%; | width: 100%; | ||||
| padding: 0 65px 0 40px; | |||||
| padding: 0 0 0 33px; | |||||
| color: #1d1d20; | color: #1d1d20; | ||||
| font-size: 15px; | font-size: 15px; | ||||
| & > div { | |||||
| padding: 0 16px; | |||||
| } | |||||
| .index { | |||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| } | |||||
| .tensorBoard { | |||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| } | |||||
| .description { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| align-items: center; | |||||
| } | |||||
| .status { | |||||
| width: 200px; | |||||
| } | |||||
| .operation { | |||||
| width: 284px; | |||||
| } | |||||
| } | |||||
| .tableExpandBoxContent { | |||||
| height: 45px; | |||||
| background-color: #fff; | |||||
| border: 1px solid #eaeaea; | |||||
| & + & { | |||||
| border-top: none; | |||||
| } | |||||
| } | } | ||||
| .statusBox { | .statusBox { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| width: 200px; | |||||
| .statusIcon { | .statusIcon { | ||||
| visibility: hidden; | visibility: hidden; | ||||
| @@ -6,10 +6,6 @@ | |||||
| import { MirrorVersionStatus } from '@/enums'; | import { MirrorVersionStatus } from '@/enums'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type MirrorStatusCellProps = { | |||||
| status: MirrorVersionStatus; | |||||
| }; | |||||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | ||||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | ||||
| @@ -33,7 +29,10 @@ const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| function MirrorStatusCell({ status }: MirrorStatusCellProps) { | |||||
| function MirrorStatusCell(status: MirrorVersionStatus) { | |||||
| if (status === null || status === undefined || !statusInfo[status]) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | ||||
| } | } | ||||
| @@ -4,7 +4,7 @@ | |||||
| height: 100%; | height: 100%; | ||||
| &__content { | &__content { | ||||
| height: calc(100% - 59px); | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 10px; | padding: 30px 30px 10px; | ||||
| overflow: auto; | overflow: auto; | ||||
| @@ -3,15 +3,20 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 创建镜像 | * @Description: 创建镜像 | ||||
| */ | */ | ||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { createPrivateMirrorReq, createPublicMirrorReq } from '@/services/mirror'; | |||||
| import { createMirrorReq } from '@/services/mirror'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||||
| import { Button, Col, Form, Input, Row, message } from 'antd'; | |||||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||||
| import { getFileListFromEvent } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './create.less'; | import styles from './create.less'; | ||||
| type FormData = { | type FormData = { | ||||
| @@ -19,7 +24,8 @@ type FormData = { | |||||
| tag: string; | tag: string; | ||||
| description: string; | description: string; | ||||
| path?: string; | path?: string; | ||||
| type: string; | |||||
| upload_type: string; | |||||
| fileList?: UploadFile[]; | |||||
| }; | }; | ||||
| const mirrorRadioItems: KFRadioItem[] = [ | const mirrorRadioItems: KFRadioItem[] = [ | ||||
| @@ -37,15 +43,64 @@ const mirrorRadioItems: KFRadioItem[] = [ | |||||
| function MirrorCreate() { | function MirrorCreate() { | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| const [seachParams] = useSearchParams(); | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const isPublic = seachParams.get('isPublic') === 'true'; | |||||
| const [nameDisabled, setNameDisabled] = useState(false); | |||||
| const uploadProps: UploadProps = { | |||||
| action: '/api/mmp/image/upload', | |||||
| headers: { | |||||
| Authorization: getAccessToken() || '', | |||||
| }, | |||||
| maxCount: 1, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| useEffect(() => { | |||||
| const name = sessionStorage.getItem(mirrorNameKey); | |||||
| if (name) { | |||||
| form.setFieldValue('name', name); | |||||
| setNameDisabled(true); | |||||
| } | |||||
| return () => { | |||||
| sessionStorage.removeItem(mirrorNameKey); | |||||
| }; | |||||
| }, []); | |||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| const createPublicMirror = async (params: FormData) => { | |||||
| // createPrivateMirrorReq | |||||
| const req = isPublic ? createPublicMirrorReq : createPrivateMirrorReq; | |||||
| const [res] = await to(req(params)); | |||||
| const createPublicMirror = async (formData: FormData) => { | |||||
| const upload_type = formData['upload_type']; | |||||
| let params; | |||||
| if (upload_type === CommonTabKeys.Public) { | |||||
| params = { | |||||
| ...omit(formData, ['upload_type']), | |||||
| upload_type: 0, | |||||
| image_type: 0, | |||||
| }; | |||||
| } else { | |||||
| const fileList = formData['fileList'] ?? []; | |||||
| if (fileList.length === 0) { | |||||
| message.error('请上传文件'); | |||||
| return; | |||||
| } | |||||
| const file = fileList[0]; | |||||
| if (file.status === 'uploading') { | |||||
| message.error('请等待文件上传完成'); | |||||
| return; | |||||
| } else if (file.status === 'error') { | |||||
| message.error('文件上传失败,请重新上传文件'); | |||||
| return; | |||||
| } | |||||
| params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | |||||
| path: file.response.data.url, | |||||
| file_size: file.response.data.fileSize, | |||||
| upload_type: 1, | |||||
| image_type: 0, | |||||
| }; | |||||
| } | |||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| navgite(-1); | navgite(-1); | ||||
| @@ -54,13 +109,26 @@ function MirrorCreate() { | |||||
| // 提交 | // 提交 | ||||
| const handleSubmit = (values: FormData) => { | const handleSubmit = (values: FormData) => { | ||||
| console.log(values); | |||||
| createPublicMirror(values); | createPublicMirror(values); | ||||
| }; | }; | ||||
| // 取消 | |||||
| const cancel = () => { | |||||
| navgite(-1); | |||||
| }; | |||||
| const beforeUpload: UploadProps['beforeUpload'] = () => { | |||||
| const fileList = form.getFieldValue('fileList'); | |||||
| if (fileList.length >= 1) { | |||||
| message.error('只允许上传一个文件'); | |||||
| return Upload.LIST_IGNORE; | |||||
| } | |||||
| return true; | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-create']}> | <div className={styles['mirror-create']}> | ||||
| <PageTitle title="基本信息"></PageTitle> | |||||
| <PageTitle title="创建镜像"></PageTitle> | |||||
| <div className={styles['mirror-create__content']}> | <div className={styles['mirror-create__content']}> | ||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| @@ -69,7 +137,7 @@ function MirrorCreate() { | |||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| initialValues={{ type: CommonTabKeys.Public }} | |||||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||||
| onFinish={handleSubmit} | onFinish={handleSubmit} | ||||
| > | > | ||||
| <SubAreaTitle | <SubAreaTitle | ||||
| @@ -89,13 +157,19 @@ function MirrorCreate() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear /> | |||||
| <Input | |||||
| placeholder="请输入镜像名称" | |||||
| maxLength={64} | |||||
| disabled={nameDisabled} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label=" " | label=" " | ||||
| name="tag" | |||||
| name="tag_name" | |||||
| labelCol={{ flex: '20px' }} | labelCol={{ flex: '20px' }} | ||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| required={false} | required={false} | ||||
| @@ -141,7 +215,7 @@ function MirrorCreate() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="构建方式" | label="构建方式" | ||||
| name="type" | |||||
| name="upload_type" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -155,10 +229,12 @@ function MirrorCreate() { | |||||
| </Row> | </Row> | ||||
| <Form.Item | <Form.Item | ||||
| noStyle | noStyle | ||||
| shouldUpdate={(prevValues, curValues) => prevValues.type !== curValues.type} | |||||
| shouldUpdate={(prevValues, curValues) => | |||||
| prevValues.upload_type !== curValues.upload_type | |||||
| } | |||||
| > | > | ||||
| {({ getFieldValue }) => { | {({ getFieldValue }) => { | ||||
| const type = getFieldValue('type'); | |||||
| const type = getFieldValue('upload_type'); | |||||
| if (type === CommonTabKeys.Public) { | if (type === CommonTabKeys.Public) { | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -199,7 +275,9 @@ function MirrorCreate() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像文件" | label="镜像文件" | ||||
| name="path" | |||||
| name="fileList" | |||||
| valuePropName="fileList" | |||||
| getValueFromEvent={getFileListFromEvent} | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -207,19 +285,11 @@ function MirrorCreate() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| {/* <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button | |||||
| style={{ | |||||
| fontSize: '14px', | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| }} | |||||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||||
| > | |||||
| 上传文件 | |||||
| <Upload {...uploadProps} beforeUpload={beforeUpload}> | |||||
| <Button type="link" style={{ paddingLeft: 0, paddingRight: 0 }}> | |||||
| 选择镜像文件 | |||||
| </Button> | </Button> | ||||
| </Upload> */} | |||||
| </Upload> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -233,7 +303,12 @@ function MirrorCreate() { | |||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| 创建镜像 | 创建镜像 | ||||
| </Button> | </Button> | ||||
| <Button type="default" htmlType="reset" style={{ marginLeft: '20px' }}> | |||||
| <Button | |||||
| type="default" | |||||
| htmlType="button" | |||||
| onClick={cancel} | |||||
| style={{ marginLeft: '20px' }} | |||||
| > | |||||
| 取消 | 取消 | ||||
| </Button> | </Button> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -23,7 +23,7 @@ | |||||
| } | } | ||||
| &__content { | &__content { | ||||
| height: calc(100% - 59px); | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 0; | padding: 30px 30px 0; | ||||
| background-color: white; | background-color: white; | ||||
| @@ -3,16 +3,33 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像详情 | * @Description: 镜像详情 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { MirrorVersionStatus } from '@/enums'; | |||||
| import { useDomSize } from '@/hooks'; | import { useDomSize } from '@/hooks'; | ||||
| import { getMirrorInfoReq, getMirrorVersionListReq } from '@/services/mirror'; | |||||
| import { | |||||
| deleteMirrorVersionReq, | |||||
| getMirrorInfoReq, | |||||
| getMirrorVersionListReq, | |||||
| } from '@/services/mirror'; | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { Button, Col, ConfigProvider, Row, Table, TablePaginationConfig, TableProps } from 'antd'; | |||||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate, useParams, useSearchParams } from '@umijs/max'; | |||||
| import { | |||||
| Button, | |||||
| Col, | |||||
| ConfigProvider, | |||||
| Flex, | |||||
| Row, | |||||
| Table, | |||||
| message, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| @@ -27,6 +44,7 @@ type MirrorInfoData = { | |||||
| }; | }; | ||||
| type MirrorVersionData = { | type MirrorVersionData = { | ||||
| id: number; | |||||
| version: string; | version: string; | ||||
| url: string; | url: string; | ||||
| status: string; | status: string; | ||||
| @@ -35,8 +53,9 @@ type MirrorVersionData = { | |||||
| }; | }; | ||||
| function MirrorInfo() { | function MirrorInfo() { | ||||
| const navigate = useNavigate(); | |||||
| const urlParams = useParams(); | const urlParams = useParams(); | ||||
| const [seachParams] = useSearchParams(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | ||||
| const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | ||||
| const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]); | const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]); | ||||
| @@ -47,7 +66,7 @@ function MirrorInfo() { | |||||
| current: 1, | current: 1, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| }); | }); | ||||
| const isPublic = seachParams.get('isPublic') === 'true'; | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getMirrorInfo(); | getMirrorInfo(); | ||||
| }, []); | }, []); | ||||
| @@ -61,7 +80,8 @@ function MirrorInfo() { | |||||
| const [res] = await to(getMirrorInfoReq(id)); | const [res] = await to(getMirrorInfoReq(id)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { name = '', description = '', version_count = '', create_time: time } = res.data; | const { name = '', description = '', version_count = '', create_time: time } = res.data; | ||||
| const create_time = time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : ''; | |||||
| let create_time = | |||||
| time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--'; | |||||
| setMirrorInfo({ | setMirrorInfo({ | ||||
| name, | name, | ||||
| description, | description, | ||||
| @@ -87,6 +107,25 @@ function MirrorInfo() { | |||||
| } | } | ||||
| }; | }; | ||||
| // 删除镜像版本 | |||||
| const deleteMirrorVersion = async (id: number) => { | |||||
| const [res] = await to(deleteMirrorVersionReq(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getMirrorVersionList(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | ||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| @@ -94,8 +133,21 @@ function MirrorInfo() { | |||||
| } | } | ||||
| }; | }; | ||||
| const downloadVersion = (record: MirrorVersionData) => {}; | |||||
| const removeVersion = (record: MirrorVersionData) => {}; | |||||
| // 处理删除 | |||||
| const handleVersionDelete = (record: MirrorVersionData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该镜像版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteMirrorVersion(record.id); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const createMirrorVersion = () => { | |||||
| navigate(`/dataset/mirror/create`); | |||||
| sessionStorage.setItem(mirrorNameKey, mirrorInfo.name || ''); | |||||
| }; | |||||
| const columns: TableProps<MirrorVersionData>['columns'] = [ | const columns: TableProps<MirrorVersionData>['columns'] = [ | ||||
| { | { | ||||
| @@ -103,31 +155,34 @@ function MirrorInfo() { | |||||
| dataIndex: 'tag_name', | dataIndex: 'tag_name', | ||||
| key: 'tag_name', | key: 'tag_name', | ||||
| width: '25%', | width: '25%', | ||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| key: 'status', | key: 'status', | ||||
| width: 150, | width: 150, | ||||
| render: (text: string) => <MirrorStatusCell status={text as MirrorVersionStatus} />, | |||||
| render: MirrorStatusCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像大小', | title: '镜像大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: 150, | width: 150, | ||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: 200, | width: 200, | ||||
| render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| render: DateTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -135,7 +190,7 @@ function MirrorInfo() { | |||||
| width: 150, | width: 150, | ||||
| key: 'operation', | key: 'operation', | ||||
| hidden: isPublic, | hidden: isPublic, | ||||
| render: (_: any, record: any) => ( | |||||
| render: (_: any, record: MirrorVersionData) => ( | |||||
| <div> | <div> | ||||
| {!isPublic && ( | {!isPublic && ( | ||||
| <ConfigProvider | <ConfigProvider | ||||
| @@ -150,7 +205,7 @@ function MirrorInfo() { | |||||
| size="small" | size="small" | ||||
| key="remove" | key="remove" | ||||
| icon={<KFIcon type="icon-shanchu" />} | icon={<KFIcon type="icon-shanchu" />} | ||||
| onClick={() => removeVersion(record)} | |||||
| onClick={() => handleVersionDelete(record)} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| @@ -182,7 +237,7 @@ function MirrorInfo() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <div className={styles['mirror-info__basic__item']}> | <div className={styles['mirror-info__basic__item']}> | ||||
| <div className={styles['label']}>版本数:</div> | <div className={styles['label']}>版本数:</div> | ||||
| <div className={styles['value']}>{mirrorInfo.version_count || '--'}</div> | |||||
| <div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div> | |||||
| </div> | </div> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -201,12 +256,21 @@ function MirrorInfo() { | |||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| </div> | </div> | ||||
| <SubAreaTitle | |||||
| title="镜像版本" | |||||
| image={require('@/assets/img/mirror-version.png')} | |||||
| style={{ marginTop: '40px' }} | |||||
| ></SubAreaTitle> | |||||
| <Flex justify="space-between" align="center" style={{ marginTop: '40px' }}> | |||||
| <SubAreaTitle | |||||
| title="镜像版本" | |||||
| image={require('@/assets/img/mirror-version.png')} | |||||
| ></SubAreaTitle> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| onClick={createMirrorVersion} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 新增镜像版本 | |||||
| </Button> | |||||
| )} | |||||
| </Flex> | |||||
| </div> | </div> | ||||
| <div | <div | ||||
| className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | ||||
| @@ -1,13 +1,13 @@ | |||||
| .mirror-list { | .mirror-list { | ||||
| height: 100%; | height: 100%; | ||||
| &__tabs-container { | &__tabs-container { | ||||
| height: 49px; | |||||
| height: 50px; | |||||
| padding-left: 27px; | padding-left: 27px; | ||||
| background-image: url('../../assets/img/page-title-bg.png'); | background-image: url('../../assets/img/page-title-bg.png'); | ||||
| } | } | ||||
| &__content { | &__content { | ||||
| height: calc(100% - 59px); | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 20px 30px 0; | padding: 20px 30px 0; | ||||
| background-color: white; | background-color: white; | ||||
| @@ -17,12 +17,11 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| padding: 0 10px; | |||||
| } | } | ||||
| &__table { | &__table { | ||||
| height: calc(100% - 44px); | |||||
| margin-top: 12px; | |||||
| height: calc(100% - 34px - 28px); | |||||
| margin-top: 28px; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,23 +3,28 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像列表 | * @Description: 镜像列表 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { getMirrorListReq } from '@/services/mirror'; | |||||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||||
| import { | import { | ||||
| Button, | Button, | ||||
| ConfigProvider, | ConfigProvider, | ||||
| Input, | Input, | ||||
| Table, | Table, | ||||
| TablePaginationConfig, | |||||
| TableProps, | |||||
| Tabs, | Tabs, | ||||
| message, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| type TabsProps, | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import dayjs from 'dayjs'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './list.less'; | import styles from './list.less'; | ||||
| @@ -44,8 +49,12 @@ export type MirrorData = { | |||||
| }; | }; | ||||
| function MirrorList() { | function MirrorList() { | ||||
| const navgite = useNavigate(); | |||||
| const [activeTab, setActiveTab] = useState('Public'); | |||||
| const navigate = useNavigate(); | |||||
| const [searchParams, setSearchParams] = useSearchParams(); | |||||
| const isPrivate = searchParams.get('isPublic') === 'false'; | |||||
| const [activeTab, setActiveTab] = useState<string>( | |||||
| isPrivate ? CommonTabKeys.Private : CommonTabKeys.Public, | |||||
| ); | |||||
| const [searchText, setSearchText] = useState(''); | const [searchText, setSearchText] = useState(''); | ||||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | const [tableData, setTableData] = useState<MirrorData[]>([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| @@ -55,19 +64,37 @@ function MirrorList() { | |||||
| current: 1, | current: 1, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| }); | }); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getMirrorList(); | getMirrorList(); | ||||
| }, [activeTab, pagination]); | }, [activeTab, pagination]); | ||||
| // 切换 Tab,重置数据 | |||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||||
| setSearchText(''); | |||||
| setPagination({ | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| setTotal(0); | |||||
| setTableData([]); | |||||
| setActiveTab(value); | |||||
| setSearchParams([['isPublic', value === CommonTabKeys.Public ? 'true' : 'false']], { | |||||
| replace: true, | |||||
| }); | |||||
| }; | |||||
| // 获取镜像列表 | // 获取镜像列表 | ||||
| const getMirrorList = async () => { | |||||
| const params = { | |||||
| const getMirrorList = async (params?: Record<string, any>) => { | |||||
| const reqParams = { | |||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| name: searchText, | name: searchText, | ||||
| image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | ||||
| ...params, | |||||
| }; | }; | ||||
| const [res] = await to(getMirrorListReq(params)); | |||||
| const [res] = await to(getMirrorListReq(reqParams)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| setTableData(content); | setTableData(content); | ||||
| @@ -75,25 +102,57 @@ function MirrorList() { | |||||
| } | } | ||||
| }; | }; | ||||
| // 删除镜像 | |||||
| const deleteMirror = async (id: number) => { | |||||
| const [res] = await to(deleteMirrorReq(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getMirrorList(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch = () => { | |||||
| getMirrorList(); | |||||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||||
| // 带参数是为了点清除时,searchText 更新不及时的问题 | |||||
| getMirrorList({ | |||||
| name: value, | |||||
| }); | |||||
| }; | }; | ||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: MirrorData) => { | const toDetail = (record: MirrorData) => { | ||||
| console.log('record', record); | console.log('record', record); | ||||
| navgite({ | |||||
| pathname: `/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, | |||||
| navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, { | |||||
| state: { | |||||
| isPublic: activeTab === CommonTabKeys.Public, | |||||
| }, | |||||
| }); | }); | ||||
| }; | }; | ||||
| // 删除镜像 | |||||
| const deleteMirror = (record: MirrorData) => {}; | |||||
| // 处理删除 | |||||
| const handleMirrorDelete = (record: MirrorData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该镜像将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteMirror(record.id); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建镜像 | // 创建镜像 | ||||
| const createMirror = () => { | const createMirror = () => { | ||||
| navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` }); | |||||
| navigate(`/dataset/mirror/create`); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -101,7 +160,7 @@ function MirrorList() { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| console.log(pagination, filters, sorter, action); | |||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | }; | ||||
| const columns: TableProps<MirrorData>['columns'] = [ | const columns: TableProps<MirrorData>['columns'] = [ | ||||
| @@ -110,32 +169,35 @@ function MirrorList() { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| width: '30%', | width: '30%', | ||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '版本数据', | title: '版本数据', | ||||
| dataIndex: 'version_count', | dataIndex: 'version_count', | ||||
| key: 'version_count', | key: 'version_count', | ||||
| width: 100, | width: 100, | ||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像描述', | title: '镜像描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| //ellipsis: true, | |||||
| render: CommonTableCell, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: 200, | width: 200, | ||||
| render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| render: DateTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: activeTab === CommonTabKeys.Private ? 200 : 150, | width: activeTab === CommonTabKeys.Private ? 200 : 150, | ||||
| key: 'operation', | key: 'operation', | ||||
| render: (_: any, record: any) => ( | |||||
| render: (_: any, record: MirrorData) => ( | |||||
| <div> | <div> | ||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| @@ -159,7 +221,7 @@ function MirrorList() { | |||||
| size="small" | size="small" | ||||
| key="remove" | key="remove" | ||||
| icon={<KFIcon type="icon-shanchu" />} | icon={<KFIcon type="icon-shanchu" />} | ||||
| onClick={() => deleteMirror(record)} | |||||
| onClick={() => handleMirrorDelete(record)} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| @@ -176,7 +238,7 @@ function MirrorList() { | |||||
| <Tabs | <Tabs | ||||
| activeKey={activeTab} | activeKey={activeTab} | ||||
| items={mirrorTabItems} | items={mirrorTabItems} | ||||
| onChange={setActiveTab} | |||||
| onChange={hanleTabChange} | |||||
| className={styles['model-tabs']} | className={styles['model-tabs']} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -87,20 +87,8 @@ | |||||
| } | } | ||||
| .plusButton { | .plusButton { | ||||
| margin: 0 18px 0 20px; | margin: 0 18px 0 20px; | ||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | } | ||||
| .datasetCneterBox { | .datasetCneterBox { | ||||
| display: flex; | display: flex; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| @@ -223,64 +211,76 @@ | |||||
| width: 100%; | width: 100%; | ||||
| .dataItem { | .dataItem { | ||||
| position: relative; | position: relative; | ||||
| <<<<<<< HEAD | |||||
| width: 23.8%; | width: 23.8%; | ||||
| height:164px; | height:164px; | ||||
| background:#ffffff; | background:#ffffff; | ||||
| border:1px solid; | border:1px solid; | ||||
| border-color:#eaeaea; | border-color:#eaeaea; | ||||
| border-radius:4px; | border-radius:4px; | ||||
| ======= | |||||
| width: 23.5%; | |||||
| height: 164px; | |||||
| >>>>>>> 708a5facb090bd542e06a0356466fa159720a223 | |||||
| margin: 0 20px 25px 0; | margin: 0 20px 25px 0; | ||||
| background: #ffffff; | |||||
| border: 1px solid; | |||||
| border-color: #eaeaea; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| .itemText { | .itemText { | ||||
| position: absolute; | position: absolute; | ||||
| top: 20px; | top: 20px; | ||||
| left: 20px; | left: 20px; | ||||
| background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%); | |||||
| height: 6px; | height: 6px; | ||||
| line-height: 0px; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| line-height: 0px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| rgba(22, 100, 255, 0.6) 0, | |||||
| rgba(22, 100, 255, 0) 100% | |||||
| ); | |||||
| } | } | ||||
| .itemDescripition{ | |||||
| .itemDescripition { | |||||
| position: absolute; | position: absolute; | ||||
| top: 57px; | top: 57px; | ||||
| left: 20px; | left: 20px; | ||||
| display: -webkit-box; | |||||
| padding-right: 28px; | padding-right: 28px; | ||||
| color:#575757; | |||||
| font-size:14px; | |||||
| word-break: break-all; | |||||
| overflow: hidden; | overflow: hidden; | ||||
| display: -webkit-box; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| word-break: break-all; | |||||
| -webkit-line-clamp: 2; | -webkit-line-clamp: 2; | ||||
| -webkit-box-orient: vertical; | -webkit-box-orient: vertical; | ||||
| } | } | ||||
| .itemTime { | .itemTime { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| bottom: 22px; | bottom: 22px; | ||||
| left: 20px; | left: 20px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| .itemIcon { | .itemIcon { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| right: 20px; | right: 20px; | ||||
| bottom: 22px; | bottom: 22px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| } | } | ||||
| .dataItem:hover{ | |||||
| border-color: #1664FF; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1) | |||||
| .dataItem:hover { | |||||
| border-color: #1664ff; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | } | ||||
| .dataItem:hover .itemText{ | |||||
| color: #1664FF; | |||||
| .dataItem:hover .itemText { | |||||
| color: #1664ff; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -291,9 +291,9 @@ | |||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background-image: url(/assets/images/modal-back.png); | background-image: url(/assets/images/modal-back.png); | ||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | background-position: top center; | ||||
| background-size: 100%; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -1,4 +1,5 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | import { | ||||
| addModelsVersionDetail, | addModelsVersionDetail, | ||||
| deleteModelVersion, | deleteModelVersion, | ||||
| @@ -7,12 +8,7 @@ import { | |||||
| getModelVersionsById, | getModelVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { | |||||
| DeleteOutlined, | |||||
| DownloadOutlined, | |||||
| PlusCircleOutlined, | |||||
| UploadOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| @@ -201,7 +197,7 @@ const Dataset = () => { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="download" | key="download" | ||||
| icon={<DownloadOutlined />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | onClick={(e) => downloadAlone(e, record)} | ||||
| > | > | ||||
| 下载 | 下载 | ||||
| @@ -225,9 +221,9 @@ const Dataset = () => { | |||||
| <div className={Styles.datasetIntroTopBox}> | <div className={Styles.datasetIntroTopBox}> | ||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | ||||
| <div className={Styles.smallTagBox}> | <div className={Styles.smallTagBox}> | ||||
| <div className={Styles.tagItem}>模型 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | ||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | ||||
| {/* <div className={Styles.tagItem}>English</div> */} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className={Styles.datasetIntroCneterBox}> | <div className={Styles.datasetIntroCneterBox}> | ||||
| @@ -255,10 +251,10 @@ const Dataset = () => { | |||||
| options={versionList} | options={versionList} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| onClick={showModal} | onClick={showModal} | ||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | > | ||||
| 创建新版本 | 创建新版本 | ||||
| </Button> | </Button> | ||||
| @@ -267,21 +263,21 @@ const Dataset = () => { | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | ||||
| > | > | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={deleteDataset} | onClick={deleteDataset} | ||||
| icon={<DeleteOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| disabled={!version} | disabled={!version} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={handleExport} | onClick={handleExport} | ||||
| icon={<DownloadOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | > | ||||
| 下载 | 下载 | ||||
| </Button> | </Button> | ||||
| @@ -1,9 +1,9 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { getComputingResourceReq } from '@/services/pipeline'; | import { getComputingResourceReq } from '@/services/pipeline'; | ||||
| import { pick } from '@/utils/index'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | import { Button, Drawer, Form, Input, Select } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | ||||
| import Styles from './editPipeline.less'; | import Styles from './editPipeline.less'; | ||||
| @@ -241,12 +241,7 @@ const Pipeline = () => { | |||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| <div className={Styles.pipelineTopBox}> | <div className={Styles.pipelineTopBox}> | ||||
| <Button | |||||
| type="primary" | |||||
| className={Styles.plusButton} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 新建流水线 | 新建流水线 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| @@ -9,30 +9,16 @@ | |||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| .plusButton { | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | |||||
| .modal { | .modal { | ||||
| :global { | :global { | ||||
| .ant-modal-content { | .ant-modal-content { | ||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background-image: url(/assets/images/modal-back.png); | background-image: url(/assets/images/modal-back.png); | ||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | background-position: top center; | ||||
| background-size: 100%; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -62,6 +48,5 @@ | |||||
| .ant-btn-primary { | .ant-btn-primary { | ||||
| background: #1664ff; | background: #1664ff; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -28,18 +28,24 @@ export function getMirrorVersionListReq(params: any) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 创建公网镜像 | |||||
| export function createPublicMirrorReq(data: any) { | |||||
| return request(`/api/mmp/image/net`, { | |||||
| // 创建镜像 | |||||
| export function createMirrorReq(data: any) { | |||||
| return request(`/api/mmp/image/addImageAndVersion`, { | |||||
| method: 'POST', | method: 'POST', | ||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 创建本地镜像 | |||||
| export function createPrivateMirrorReq(data: any) { | |||||
| return request(`/api/mmp/image/local`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| // 删除镜像 | |||||
| export function deleteMirrorReq(id: number) { | |||||
| return request(`/api/mmp/image/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 删除镜像 | |||||
| export function deleteMirrorVersionReq(id: number) { | |||||
| return request(`/api/mmp/imageVersion/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -14,37 +14,3 @@ export function getNameByCode(list: any[], code: any) { | |||||
| }); | }); | ||||
| return name; | return name; | ||||
| } | } | ||||
| /** | |||||
| * Picks specified properties from an object and returns a new object with only those properties. | |||||
| * | |||||
| * @param obj - The object to pick properties from. | |||||
| * @param properties - An array of property names to pick from the object. | |||||
| * @return A new object with only the picked properties. | |||||
| */ | |||||
| export function pick<T extends object, K extends keyof T>(obj: T, properties: K[]): Pick<T, K> { | |||||
| const picked: Partial<T> = {}; | |||||
| for (const key of properties) { | |||||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | |||||
| picked[key] = obj[key]; | |||||
| } | |||||
| } | |||||
| return picked as Pick<T, K>; | |||||
| } | |||||
| /** | |||||
| * Omit properties from an object and return a new object without those properties. | |||||
| * | |||||
| * @param obj - The object to omit properties from. | |||||
| * @param properties - An array of property names to omit from the object. | |||||
| * @return A new object without the omitted properties. | |||||
| */ | |||||
| export function omit<T extends object, K extends keyof T>(obj: T, properties: K[]): Omit<T, K> { | |||||
| const omitted: Partial<T> = { ...obj }; | |||||
| for (const key of properties) { | |||||
| if (Object.prototype.hasOwnProperty.call(omitted, key)) { | |||||
| delete omitted[key]; | |||||
| } | |||||
| } | |||||
| return omitted as Omit<T, K>; | |||||
| } | |||||
| @@ -0,0 +1,2 @@ | |||||
| // 用于新建镜像 | |||||
| export const mirrorNameKey = 'mirror-name'; | |||||
| @@ -4,7 +4,7 @@ | |||||
| * @Description: UI 公共方法 | * @Description: UI 公共方法 | ||||
| */ | */ | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { Modal, type ModalFuncProps } from 'antd'; | |||||
| import { Modal, type ModalFuncProps, type UploadFile } from 'antd'; | |||||
| // 自定义 Confirm 弹框 | // 自定义 Confirm 弹框 | ||||
| export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) { | export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) { | ||||
| @@ -26,3 +26,20 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) | |||||
| onOk: onOk, | onOk: onOk, | ||||
| }); | }); | ||||
| } | } | ||||
| // 从事件中获取上传文件列表,用于 Upload + Form 中 | |||||
| export const getFileListFromEvent = (e: any) => { | |||||
| const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || []; | |||||
| return fileList.map((item) => { | |||||
| if (item.status === 'done') { | |||||
| const { response } = item; | |||||
| if (response?.code !== 200) { | |||||
| return { | |||||
| ...item, | |||||
| status: 'error', | |||||
| }; | |||||
| } | |||||
| } | |||||
| return item; | |||||
| }); | |||||
| }; | |||||
| @@ -4,6 +4,7 @@ import com.ruoyi.common.core.web.controller.BaseController; | |||||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | ||||
| import com.ruoyi.platform.domain.DatasetVersion; | import com.ruoyi.platform.domain.DatasetVersion; | ||||
| import com.ruoyi.platform.service.DatasetVersionService; | import com.ruoyi.platform.service.DatasetVersionService; | ||||
| import com.ruoyi.platform.vo.LabelDatasetVersion; | |||||
| import io.swagger.annotations.ApiOperation; | import io.swagger.annotations.ApiOperation; | ||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageRequest; | import org.springframework.data.domain.PageRequest; | ||||
| @@ -135,5 +136,10 @@ public class DatasetVersionController extends BaseController { | |||||
| return genericsSuccess(this.datasetVersionService.deleteDatasetVersion(datasetId, version)); | return genericsSuccess(this.datasetVersionService.deleteDatasetVersion(datasetId, version)); | ||||
| } | } | ||||
| @PostMapping("/addDatasetVersionsFromLabel") | |||||
| @ApiOperation("从数据标注添加数据集版本") | |||||
| public GenericsAjaxResult<Boolean> addDatasetVersionsFromLabel(@RequestBody LabelDatasetVersion labelDatasetVersion) throws Exception { | |||||
| return genericsSuccess(true); | |||||
| } | |||||
| } | } | ||||
| @@ -80,5 +80,7 @@ public interface ImageDao { | |||||
| int deleteById(Integer id); | int deleteById(Integer id); | ||||
| List<Image> queryByName(String name); | List<Image> queryByName(String name); | ||||
| Image getByName(String name); | |||||
| } | } | ||||
| @@ -5,6 +5,7 @@ package com.ruoyi.platform.service; | |||||
| import com.ruoyi.platform.domain.Dataset; | import com.ruoyi.platform.domain.Dataset; | ||||
| import com.ruoyi.platform.domain.DatasetVersion; | import com.ruoyi.platform.domain.DatasetVersion; | ||||
| import com.ruoyi.platform.vo.LabelDatasetVersion; | |||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageRequest; | import org.springframework.data.domain.PageRequest; | ||||
| @@ -73,4 +74,6 @@ public interface DatasetVersionService { | |||||
| void checkDeclaredVersion(DatasetVersion insert) throws Exception; | void checkDeclaredVersion(DatasetVersion insert) throws Exception; | ||||
| String addDatasetVersions(List<DatasetVersion> datasetVersions) throws Exception; | String addDatasetVersions(List<DatasetVersion> datasetVersions) throws Exception; | ||||
| void addDatasetVersionsFromLabel(LabelDatasetVersion labelDatasetVersion) throws Exception; | |||||
| } | } | ||||
| @@ -26,6 +26,20 @@ public interface ImageService { | |||||
| */ | */ | ||||
| Image queryById(Integer id); | Image queryById(Integer id); | ||||
| Page<Image> queryByName(String name); | |||||
| /** | |||||
| * 通过名字精确查询单条数据 | |||||
| * | |||||
| * @param name 名字 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| Image getByName(String name); | |||||
| /** | /** | ||||
| * 分页查询 | * 分页查询 | ||||
| * | * | ||||
| @@ -59,9 +73,11 @@ public interface ImageService { | |||||
| */ | */ | ||||
| boolean deleteById(Integer id); | boolean deleteById(Integer id); | ||||
| String removeById(Integer id); | |||||
| String removeById(Integer id) throws Exception; | |||||
| Page<Image> queryByName(String name); | |||||
| String insertImageAndVersion(ImageVo imageVo) throws Exception; | String insertImageAndVersion(ImageVo imageVo) throws Exception; | ||||
| @@ -77,4 +93,6 @@ public interface ImageService { | |||||
| Map<String, String> createImageFromNet(String imageName, String imageTag, String NetPath) throws Exception; | Map<String, String> createImageFromNet(String imageName, String imageTag, String NetPath) throws Exception; | ||||
| Map<String, String> uploadImageFiles(MultipartFile file) throws Exception; | Map<String, String> uploadImageFiles(MultipartFile file) throws Exception; | ||||
| } | } | ||||
| @@ -8,6 +8,8 @@ import com.ruoyi.platform.domain.ModelsVersion; | |||||
| import com.ruoyi.platform.domain.Workflow; | import com.ruoyi.platform.domain.Workflow; | ||||
| import com.ruoyi.platform.mapper.DatasetVersionDao; | import com.ruoyi.platform.mapper.DatasetVersionDao; | ||||
| import com.ruoyi.platform.service.DatasetVersionService; | import com.ruoyi.platform.service.DatasetVersionService; | ||||
| import com.ruoyi.platform.utils.HttpUtils; | |||||
| import com.ruoyi.platform.vo.LabelDatasetVersion; | |||||
| 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.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| @@ -16,6 +18,7 @@ import org.springframework.data.domain.PageRequest; | |||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.io.InputStream; | |||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||
| import java.util.Date; | import java.util.Date; | ||||
| import java.util.HashMap; | import java.util.HashMap; | ||||
| @@ -32,7 +35,6 @@ import java.util.Map; | |||||
| public class DatasetVersionServiceImpl implements DatasetVersionService { | public class DatasetVersionServiceImpl implements DatasetVersionService { | ||||
| @Resource | @Resource | ||||
| private DatasetVersionDao datasetVersionDao; | private DatasetVersionDao datasetVersionDao; | ||||
| // 固定存储桶名 | // 固定存储桶名 | ||||
| private final String bucketName = "platform-data"; | private final String bucketName = "platform-data"; | ||||
| @@ -193,8 +195,15 @@ public class DatasetVersionServiceImpl implements DatasetVersionService { | |||||
| throw new Exception("新增数据集版本失败: " + e.getMessage()); | throw new Exception("新增数据集版本失败: " + e.getMessage()); | ||||
| } | } | ||||
| } | |||||
| @Override | |||||
| public void addDatasetVersionsFromLabel(LabelDatasetVersion labelDatasetVersion) throws Exception{ | |||||
| // 获取label-studio数据流 | |||||
| InputStream inputStream = HttpUtils.getInputStream("http://127.0.0.1:8080/api/projects/"+labelDatasetVersion.getProject_id()+"/export?exportType="+labelDatasetVersion.getExportType(), labelDatasetVersion.getToken()); | |||||
| // 上传镜像至minio | |||||
| //保存DatasetVersion | |||||
| } | } | ||||
| private void insertPrepare(DatasetVersion datasetVersion) throws Exception { | private void insertPrepare(DatasetVersion datasetVersion) throws Exception { | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ruoyi.platform.service.impl; | package com.ruoyi.platform.service.impl; | ||||
| import com.alibaba.fastjson2.util.DateUtils; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.domain.Image; | import com.ruoyi.platform.domain.Image; | ||||
| import com.ruoyi.platform.domain.ImageVersion; | import com.ruoyi.platform.domain.ImageVersion; | ||||
| @@ -12,6 +13,7 @@ import com.ruoyi.platform.utils.FileUtil; | |||||
| import com.ruoyi.platform.utils.K8sClientUtil; | import com.ruoyi.platform.utils.K8sClientUtil; | ||||
| import com.ruoyi.platform.vo.ImageVo; | import com.ruoyi.platform.vo.ImageVo; | ||||
| import com.ruoyi.system.api.model.LoginUser; | import com.ruoyi.system.api.model.LoginUser; | ||||
| import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; | |||||
| import io.kubernetes.client.openapi.models.V1Pod; | import io.kubernetes.client.openapi.models.V1Pod; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||
| @@ -64,6 +66,15 @@ public class ImageServiceImpl implements ImageService { | |||||
| private String deploymentName; | private String deploymentName; | ||||
| @Value("${harbor.serviceNS}") | @Value("${harbor.serviceNS}") | ||||
| private String serviceNS; | private String serviceNS; | ||||
| @Value("${dockerpush.image}") | |||||
| private String image; | |||||
| @Value("${dockerpush.mountPath}") | |||||
| private String mountPath; | |||||
| @Value("${dockerpush.proxyUrl}") | |||||
| private String proxyUrl; | |||||
| @Value("${minio.pvcName}") | |||||
| private String pvcName; | |||||
| /** | /** | ||||
| * 通过ID查询单条数据 | * 通过ID查询单条数据 | ||||
| * | * | ||||
| @@ -88,6 +99,8 @@ public class ImageServiceImpl implements ImageService { | |||||
| return new PageImpl<>(this.imageDao.queryAllByLimit(image, pageRequest), pageRequest, total); | return new PageImpl<>(this.imageDao.queryAllByLimit(image, pageRequest), pageRequest, total); | ||||
| } | } | ||||
| /** | /** | ||||
| * 新增数据 | * 新增数据 | ||||
| * | * | ||||
| @@ -138,10 +151,10 @@ public class ImageServiceImpl implements ImageService { | |||||
| } | } | ||||
| @Override | @Override | ||||
| public String removeById(Integer id) { | |||||
| public String removeById(Integer id) throws Exception { | |||||
| Image image = this.imageDao.queryById(id); | Image image = this.imageDao.queryById(id); | ||||
| if (image == null){ | if (image == null){ | ||||
| return "镜像不存在"; | |||||
| throw new Exception("镜像不存在"); | |||||
| } | } | ||||
| //判断权限,只有admin和创建者本身可以删除该数据集 | //判断权限,只有admin和创建者本身可以删除该数据集 | ||||
| @@ -150,11 +163,11 @@ public class ImageServiceImpl implements ImageService { | |||||
| String createdBy = image.getCreateBy(); | String createdBy = image.getCreateBy(); | ||||
| if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ | |||||
| return "无权限删除该镜像"; | |||||
| if (!(StringUtils.equals(username,"admin") || !StringUtils.equals(username,createdBy))){ | |||||
| throw new Exception("无权限删除该镜像"); | |||||
| } | } | ||||
| if (!imageVersionService.queryByImageId(id).isEmpty()){ | if (!imageVersionService.queryByImageId(id).isEmpty()){ | ||||
| return "请先删除该镜像下的版本文件"; | |||||
| throw new Exception("请先删除该镜像下的版本文件"); | |||||
| } | } | ||||
| image.setState(0); | image.setState(0); | ||||
| return this.imageDao.update(image)>0?"删除成功":"删除失败"; | return this.imageDao.update(image)>0?"删除成功":"删除失败"; | ||||
| @@ -167,26 +180,46 @@ public class ImageServiceImpl implements ImageService { | |||||
| return new PageImpl<>(this.imageDao.queryByName(name)); | return new PageImpl<>(this.imageDao.queryByName(name)); | ||||
| } | } | ||||
| /** | |||||
| * 通过名字精确查询镜像 | |||||
| * | |||||
| * @param name 名字 | |||||
| * @return 镜像 | |||||
| */ | |||||
| @Override | |||||
| public Image getByName(String name) { | |||||
| return this.imageDao.getByName(name); | |||||
| } | |||||
| @Override | @Override | ||||
| @Transactional | @Transactional | ||||
| public String insertImageAndVersion(ImageVo imageVo) throws Exception { | public String insertImageAndVersion(ImageVo imageVo) throws Exception { | ||||
| Image image = new Image(); | |||||
| image.setName(imageVo.getName()); | |||||
| image.setDescription(imageVo.getDescription()); | |||||
| image.setImageType(imageVo.getImageType()); | |||||
| Image imageInsert = this.insert(image); | |||||
| if (imageInsert == null){ | |||||
| throw new Exception("新增镜像失败"); | |||||
| Image existingImage = getByName(imageVo.getName()); | |||||
| Image imageToUse; | |||||
| if(existingImage == null) { | |||||
| // 如果不存在相同名称的镜像,则创建新的镜像记录 | |||||
| Image newImage = new Image(); | |||||
| newImage.setName(imageVo.getName()); | |||||
| newImage.setDescription(imageVo.getDescription()); | |||||
| newImage.setImageType(imageVo.getImageType()); | |||||
| imageToUse = this.insert(newImage); | |||||
| if (imageToUse == null) { | |||||
| throw new Exception("新增镜像失败"); | |||||
| } | |||||
| }else{ | |||||
| // 如果已存在相同名称的镜像,使用已存在的镜像 | |||||
| imageToUse = existingImage; | |||||
| } | } | ||||
| ImageVersion imageVersion = new ImageVersion(); | ImageVersion imageVersion = new ImageVersion(); | ||||
| imageVersion.setImageId(imageInsert.getId()); | |||||
| imageVersion.setImageId(imageToUse.getId()); | |||||
| 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.setStatus("building"); | imageVersion.setStatus("building"); | ||||
| ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion); | ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion); | ||||
| if (imageVersionInsert == null) { | if (imageVersionInsert == null) { | ||||
| throw new Exception("新增镜像失败"); | |||||
| throw new Exception("新增镜像版本失败"); | |||||
| } | } | ||||
| // 使用CompletableFuture异步执行不同的镜像构建逻辑 | // 使用CompletableFuture异步执行不同的镜像构建逻辑 | ||||
| CompletableFuture.supplyAsync(() -> { | CompletableFuture.supplyAsync(() -> { | ||||
| @@ -206,7 +239,7 @@ public class ImageServiceImpl implements ImageService { | |||||
| }).thenAccept(resultMap ->{ | }).thenAccept(resultMap ->{ | ||||
| try { | try { | ||||
| String imageUrl = resultMap.get("url"); | String imageUrl = resultMap.get("url"); | ||||
| String fileSize = resultMap.get("filesize"); | |||||
| String fileSize = resultMap.get("fileSize"); | |||||
| imageVersion.setUrl(imageUrl); | imageVersion.setUrl(imageUrl); | ||||
| imageVersion.setFileSize(fileSize); | imageVersion.setFileSize(fileSize); | ||||
| imageVersion.setStatus("available"); | imageVersion.setStatus("available"); | ||||
| @@ -229,7 +262,8 @@ public class ImageServiceImpl implements ImageService { | |||||
| // 得到容器 | // 得到容器 | ||||
| V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | ||||
| if (pod == null) { | if (pod == null) { | ||||
| throw new Exception("镜像推送服务不存在"); | |||||
| String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date()); | |||||
| pod = createPod(serviceNS, podName); | |||||
| } | } | ||||
| String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | ||||
| // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | ||||
| @@ -240,7 +274,7 @@ public class ImageServiceImpl implements ImageService { | |||||
| String logs2 = k8sClientUtil.executeCommand(pod,"docker pull "+ netPath); | String logs2 = k8sClientUtil.executeCommand(pod,"docker pull "+ netPath); | ||||
| // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | ||||
| if (StringUtils.isNoneBlank(logs2)){ | if (StringUtils.isNoneBlank(logs2)){ | ||||
| String substring = logs2.substring(logs2.lastIndexOf(harborUrl), logs2.length()); | |||||
| String substring = logs2.substring(logs2.lastIndexOf(harborUrl)); | |||||
| String cleanedString = substring.replaceAll("(\\r|\\n)", ""); | String cleanedString = substring.replaceAll("(\\r|\\n)", ""); | ||||
| String tagCmd = "docker tag " + cleanedString + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | String tagCmd = "docker tag " + cleanedString + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | ||||
| String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | ||||
| @@ -270,7 +304,8 @@ public class ImageServiceImpl implements ImageService { | |||||
| // 得到容器 | // 得到容器 | ||||
| V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | ||||
| if (pod == null) { | if (pod == null) { | ||||
| throw new Exception("镜像推送服务不存在"); | |||||
| String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date()); | |||||
| pod = createPod(serviceNS, podName); | |||||
| } | } | ||||
| String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | ||||
| // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | ||||
| @@ -282,7 +317,7 @@ public class ImageServiceImpl implements ImageService { | |||||
| String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath); | String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath); | ||||
| // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | ||||
| if (StringUtils.isNoneBlank(logs2)){ | if (StringUtils.isNoneBlank(logs2)){ | ||||
| String substring = logs2.substring(logs2.lastIndexOf(harborUrl), logs2.length()); | |||||
| String substring = logs2.substring(logs2.lastIndexOf(harborUrl)); | |||||
| String cleanedString = substring.replaceAll("(\\r|\\n)", ""); | String cleanedString = substring.replaceAll("(\\r|\\n)", ""); | ||||
| String tagCmd = "docker tag " + cleanedString + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | String tagCmd = "docker tag " + cleanedString + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | ||||
| String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | ||||
| @@ -311,4 +346,8 @@ public class ImageServiceImpl implements ImageService { | |||||
| String path = loginUser.getUsername()+"/"+file.getOriginalFilename(); | String path = loginUser.getUsername()+"/"+file.getOriginalFilename(); | ||||
| return minioService.uploadFile(bucketName, path, file); | return minioService.uploadFile(bucketName, path, file); | ||||
| } | } | ||||
| private V1Pod createPod(String namespace, String podName){ | |||||
| return k8sClientUtil.createPodWithEnv(podName,namespace,proxyUrl,mountPath,pvcName,image); | |||||
| } | |||||
| } | } | ||||
| @@ -374,6 +374,29 @@ public class HttpUtils { | |||||
| return httpClient; | return httpClient; | ||||
| } | } | ||||
| /** | |||||
| * 发送 HTTP 请求并返回二进制数据流(InputStream)。 | |||||
| * | |||||
| * @param url 请求的 URL 地址。 | |||||
| * @param token 要携带的 Token。 | |||||
| * @return 服务器响应的二进制数据流(InputStream)。 | |||||
| * @throws IOException 如果请求失败或发生其他 I/O 错误。 | |||||
| */ | |||||
| public static InputStream getInputStream(String url, String token) throws IOException { | |||||
| URL requestUrl = new URL(url); | |||||
| HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection(); | |||||
| connection.setRequestMethod("GET"); | |||||
| connection.setRequestProperty("Authorization", "Bearer " + token); // 添加 Authorization 头部,携带 Token | |||||
| int responseCode = connection.getResponseCode(); | |||||
| if (responseCode == HttpURLConnection.HTTP_OK) { | |||||
| return connection.getInputStream(); // 获取响应的输入流 | |||||
| } else { | |||||
| throw new IOException("HTTP 请求失败,状态码:" + responseCode); | |||||
| } | |||||
| } | |||||
| private static class TrustAnyTrustManager implements X509TrustManager { | private static class TrustAnyTrustManager implements X509TrustManager { | ||||
| @Override | @Override | ||||
| public void checkClientTrusted(X509Certificate[] chain, String authType) { | public void checkClientTrusted(X509Certificate[] chain, String authType) { | ||||
| @@ -382,7 +382,7 @@ public class K8sClientUtil { | |||||
| // invokes the CoreV1Api client | // invokes the CoreV1Api client | ||||
| for (V1Pod item : v1PodList.getItems()) { | for (V1Pod item : v1PodList.getItems()) { | ||||
| String generateName = item.getMetadata().getGenerateName(); | |||||
| String generateName = item.getMetadata().getName(); | |||||
| if (StringUtils.isNotEmpty(generateName) && generateName.startsWith(deploymentName)) { | if (StringUtils.isNotEmpty(generateName) && generateName.startsWith(deploymentName)) { | ||||
| // 找到匹配的Pod,获取其名称 | // 找到匹配的Pod,获取其名称 | ||||
| return item; | return item; | ||||
| @@ -410,13 +410,12 @@ public class K8sClientUtil { | |||||
| int exitValue = proc.waitFor(); | int exitValue = proc.waitFor(); | ||||
| if (exitValue != 0) { | if (exitValue != 0) { | ||||
| // 如果进程的退出值不为0,表示命令执行失败 | // 如果进程的退出值不为0,表示命令执行失败 | ||||
| throw new RuntimeException("命令执行失败,退出值:" + exitValue); | |||||
| throw new RuntimeException("容器中命令执行失败,退出值:" + exitValue); | |||||
| } | } | ||||
| return builder.toString(); | return builder.toString(); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| log.error("执行命令异常", e); | |||||
| throw new RuntimeException("执行命令异常"); | |||||
| log.error("容器执行命令异常", e); | |||||
| throw new RuntimeException("容器执行命令异常"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -441,4 +440,46 @@ public class K8sClientUtil { | |||||
| } | } | ||||
| } | } | ||||
| public V1Pod createPodWithEnv(String podName,String namespace,String proxyUrl ,String mountPath,String pvcName, String image){ | |||||
| CoreV1Api api = new CoreV1Api(apiClient); | |||||
| V1PodList v1PodList = null; | |||||
| V1Pod pod = new V1PodBuilder() | |||||
| .withNewMetadata() | |||||
| .withName(podName) | |||||
| .endMetadata() | |||||
| .withNewSpec() | |||||
| .addNewContainer() | |||||
| .withName(podName) | |||||
| .withImage(image) // 替换为您实际要使用的镜像名称 | |||||
| .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath)) | |||||
| .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() | |||||
| .endSpec() | |||||
| .build(); | |||||
| try { | |||||
| pod = api.createNamespacedPod(namespace, pod, null, null, null); | |||||
| } catch (ApiException e) { | |||||
| log.error("创建pod异常:" + e.getResponseBody(), e); | |||||
| } catch (Exception e) { | |||||
| log.error("创建pod系统异常:", e); | |||||
| } | |||||
| return pod; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,63 @@ | |||||
| package com.ruoyi.platform.vo; | |||||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||||
| import java.io.Serializable; | |||||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||||
| public class LabelDatasetVersion implements Serializable { | |||||
| private String token; | |||||
| private String project_id; | |||||
| private String dataset_id; | |||||
| private String version; | |||||
| private String desc; | |||||
| private String exportType; | |||||
| public String getToken() { | |||||
| return token; | |||||
| } | |||||
| public void setToken(String token) { | |||||
| this.token = token; | |||||
| } | |||||
| public String getProject_id() { | |||||
| return project_id; | |||||
| } | |||||
| public void setProject_id(String project_id) { | |||||
| this.project_id = project_id; | |||||
| } | |||||
| public String getDataset_id() { | |||||
| return dataset_id; | |||||
| } | |||||
| public void setDataset_id(String dataset_id) { | |||||
| this.dataset_id = dataset_id; | |||||
| } | |||||
| public String getVersion() { | |||||
| return version; | |||||
| } | |||||
| public void setVersion(String version) { | |||||
| this.version = version; | |||||
| } | |||||
| public String getDesc() { | |||||
| return desc; | |||||
| } | |||||
| public void setDesc(String desc) { | |||||
| this.desc = desc; | |||||
| } | |||||
| public String getExportType() { | |||||
| return exportType; | |||||
| } | |||||
| public void setExportType(String exportType) { | |||||
| this.exportType = exportType; | |||||
| } | |||||
| } | |||||
| @@ -26,7 +26,7 @@ | |||||
| <select id="queryAllByLimit" resultMap="ImageMap"> | <select id="queryAllByLimit" resultMap="ImageMap"> | ||||
| select | select | ||||
| img.id, img.name, img.description, img.image_type, img.create_by, img.create_time, img.update_by, img.update_time, img.state, | img.id, img.name, img.description, img.image_type, img.create_by, img.create_time, img.update_by, img.update_time, img.state, | ||||
| (SELECT COUNT(*) FROM image_version WHERE image_version.image_id = img.id) as versionCount | |||||
| (SELECT COUNT(*) FROM image_version WHERE image_version.image_id = img.id and image_version.state = 1) as versionCount | |||||
| from image img | from image img | ||||
| <where> | <where> | ||||
| img.state = 1 | img.state = 1 | ||||
| @@ -75,6 +75,18 @@ | |||||
| </where> | </where> | ||||
| </select> | </select> | ||||
| <select id="getByName" resultMap="ImageMap"> | |||||
| select | |||||
| id, name, description, image_type, create_by, create_time, update_by, update_time, state | |||||
| from image | |||||
| <where> | |||||
| state = 1 | |||||
| <if test="name != null and name != ''"> | |||||
| and name = #{name} | |||||
| </if> | |||||
| </where> | |||||
| </select> | |||||
| <!--统计总行数--> | <!--统计总行数--> | ||||
| <select id="count" resultType="java.lang.Long"> | <select id="count" resultType="java.lang.Long"> | ||||
| @@ -111,6 +123,7 @@ | |||||
| </if> | </if> | ||||
| </where> | </where> | ||||
| </select> | </select> | ||||
| <!--新增所有列--> | <!--新增所有列--> | ||||
| <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | <insert id="insert" keyProperty="id" useGeneratedKeys="true"> | ||||
| insert into image(name,description,image_type,create_by,create_time,update_by,update_time,state) | insert into image(name,description,image_type,create_by,create_time,update_by,update_time,state) | ||||
| @@ -161,7 +174,7 @@ name = values(name)description = values(description)image_type = values(image_ty | |||||
| <if test="image.updateTime != null"> | <if test="image.updateTime != null"> | ||||
| update_time = #{image.updateTime}, | update_time = #{image.updateTime}, | ||||
| </if> | </if> | ||||
| <if test="image.state != null and image.state != ''"> | |||||
| <if test="image.state != null"> | |||||
| state = #{image.state}, | state = #{image.state}, | ||||
| </if> | </if> | ||||
| </set> | </set> | ||||