Compare commits

...

24 Commits

Author SHA1 Message Date
fanshuai e2c5dd2b1b Merge pull request '423' (#30) from dev into master 2024-04-23 16:00:37 +08:00
fanshuai 4a1d8951d4 Merge remote-tracking branch 'origin/dev' into dev 2024-04-23 15:55:45 +08:00
fanshuai 5dcb95fa11 修改联网BUG 2024-04-23 15:55:40 +08:00
liu2592603532 f703551d2c Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into optimus 2024-04-23 15:52:56 +08:00
liu2592603532 fa83541d47 merge 2024-04-23 15:52:53 +08:00
fanshuai 27ed8daa43 Merge remote-tracking branch 'origin/dev' into dev 2024-04-23 15:23:39 +08:00
fanshuai 1acc84aa9c 修改联网BUG 2024-04-23 15:23:33 +08:00
liu2592603532 e27478072a merge 2024-04-23 14:05:59 +08:00
liu2592603532 7fa7eb67f2 merge 2024-04-23 14:03:54 +08:00
cp3hnu 708a5facb0 Merge pull request 'feat: 修改实验列表没有对齐' (#29) from dev-zw into dev 2024-04-23 13:51:52 +08:00
cp3hnu 8bdb4ff9ed feat: 修改实验列表没有对齐 2024-04-23 11:49:01 +08:00
fanshuai 6619f4082a 修改联网BUG 2024-04-23 11:17:53 +08:00
fanshuai b16bd44129 修改联网BUG 2024-04-23 11:16:40 +08:00
cp3hnu aa31d8224f feat: 完成新建镜像 2024-04-23 10:35:54 +08:00
cp3hnu b3f1521e24 Merge branch 'dev' into dev-zw 2024-04-23 10:31:05 +08:00
西大锐 00e5d32ea3 修改镜像删除mapper文件 2024-04-23 10:30:22 +08:00
cp3hnu 40c6b5ea00 feat: 完成新建镜像 2024-04-23 09:37:25 +08:00
liu2592603532 d7a29808c4 merge 2024-04-22 15:29:07 +08:00
西大锐 fbbf8ff8be Merge remote-tracking branch 'origin/dev' into dev 2024-04-22 14:16:32 +08:00
西大锐 eef2f3e4f5 镜像构建接口修改docker命令截取逻辑,修复bug 2024-04-22 14:16:19 +08:00
cp3hnu 4ee94e2960 feat: 统一icon的使用方式 2024-04-22 10:15:50 +08:00
liu2592603532 250f401b17 merge 2024-04-22 09:59:40 +08:00
cp3hnu ad18ae498a chore: merge dev 2024-04-22 09:22:07 +08:00
cp3hnu 701cfb55a2 feat: 统一icon的使用方式 2024-04-22 09:15:28 +08:00
102 changed files with 1642 additions and 918 deletions

View File

@ -39,7 +39,7 @@ export default defineConfig({
theme: {
// 如果不想要 configProvide 动态设置主题需要把这个设置为 default
// 只有设置为 variable 才能使用 configProvide 动态设置主色调
'root-entry-name': 'variable',
// 'root-entry-name': 'variable',
},
/**
* @name moment
@ -157,4 +157,10 @@ export default defineConfig({
},
requestRecord: {},
icons: {},
lessLoader: {
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
javascriptEnabled: true,
},
});

View File

@ -1,3 +1,8 @@
/*
* @Author:
* @Date: 2024-04-17 08:48:09
* @Description:
*/
/**
* @name
* @see

View File

@ -1,11 +0,0 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -156,7 +156,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
export async function onRouteChange({ clientRoutes, location }: any) {
const menus = getRemoteMenu();
console.log('onRouteChange', clientRoutes, location, menus);
// console.log('onRouteChange', clientRoutes, location, menus);
if (menus === null && location.pathname !== PageEnum.LOGIN) {
console.log('refresh');
history.go(0);
@ -164,16 +164,16 @@ export async function onRouteChange({ clientRoutes, location }: any) {
}
export function patchRoutes({ routes, routeComponents }: any) {
console.log('patchRoutes', routes, routeComponents);
//console.log('patchRoutes', routes, routeComponents);
}
export async function patchClientRoutes({ routes }: any) {
console.log('patchClientRoutes', routes);
// console.log('patchClientRoutes', routes);
patchRouteWithRemoteMenus(routes);
}
export function render(oldRender: () => void) {
console.log('render get routers', oldRender);
// console.log('render get routers', oldRender);
const token = getAccessToken();
if (!token || token?.length === 0) {
oldRender();
@ -190,10 +190,36 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme ??= {};
memo.theme.token = {
colorPrimary: themes['primaryColor'],
colorSuccess: themes['successColor'],
colorError: themes['errorColor'],
colorWarning: themes['warningColor'],
};
memo.theme.components ??= {};
memo.theme.components.Tabs = {};
// memo.theme.cssVar = true;
memo.theme.components.Button = {
defaultBg: 'rgba(22, 100, 255, 0.06)',
defaultBorderColor: 'rgba(22, 100, 255, 0.11)',
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF ',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']),
controlHeight: 34,
};
memo.theme.components.Input = {
inputFontSize: parseInt(themes['fontSize']),
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
};
memo.theme.components.Tabs = {
titleFontSize: 16,
};
memo.theme.cssVar = true;
// memo.theme.hashed = false;
// memo.appConfig = {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="currentColor"><path class="a" d="M872.8,755.637h0v-.012Z" transform="translate(-848.576 -735.009)"/><path class="a" d="M122.155,109.158a13,13,0,1,0-13,13,13.015,13.015,0,0,0,13-13m-13,11.135a11.134,11.134,0,1,1,11.134-11.135,11.149,11.149,0,0,1-11.134,11.135" transform="translate(-96.155 -96.158)"/><path class="a" d="M344.559,345.281l-4.141-4.154,4.137-4.091a.957.957,0,1,0-1.346-1.36l-4.141,4.1-4.08-4.092a.957.957,0,0,0-1.355,1.351l4.075,4.087-4.107,4.062a.956.956,0,0,0,1.345,1.36l4.113-4.068,4.145,4.16a.957.957,0,0,0,1.355-1.352" transform="translate(-326.072 -328.091)"/></svg>

Before

Width:  |  Height:  |  Size: 674 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14.893" height="13.83" viewBox="0 0 14.893 13.83" fill="currentColor"><path class="a" d="M60.718,92.155a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25v1.064a.532.532,0,0,1-1.064,0V92.687a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Zm-5.319-6.383a.532.532,0,0,1,.532.532v3.191a.532.532,0,1,1-1.064,0V90.027H50.08a.532.532,0,1,1,0-1.064h2.66V87.9a.532.532,0,0,1,.532-.532Zm10.638,1.6a.532.532,0,0,1,0,1.064H55.4a.532.532,0,1,1,0-1.064Zm-3.191-6.383a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25V86.3a.532.532,0,0,1-1.064,0V83.113a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Z" transform="translate(-49.548 -82.581)"/></svg>

Before

Width:  |  Height:  |  Size: 801 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13.545" height="15.046" viewBox="0 0 13.545 15.046"><defs><style>.a{fill:#1664ff;}</style></defs><path class="a" d="M143.094,101.214h5.614v3.363a1.006,1.006,0,0,0,1.139,1.139h3.363v7.115a.559.559,0,0,1-.075.248,1.247,1.247,0,0,1-.195.267.832.832,0,0,1-.542.3h-9.3a.832.832,0,0,1-.542-.3,1.247,1.247,0,0,1-.195-.267.559.559,0,0,1-.075-.248V102.025h0a.559.559,0,0,1,.075-.248,1.247,1.247,0,0,1,.195-.267.832.832,0,0,1,.542-.3Zm3.546,6.358.344-.344a.577.577,0,1,0-.816-.816l-1.315,1.315h0l0,0a.577.577,0,0,0,.4,1h3.037a.818.818,0,1,1,0,1.635H146.91a.577.577,0,0,0,0,1.154h1.374a1.971,1.971,0,1,0,0-3.943Zm3.327-7.4a.889.889,0,0,0-.634-.265h-6.239a2.116,2.116,0,0,0-2.12,2.12v10.805a2.116,2.116,0,0,0,2.12,2.12h9.3a2.166,2.166,0,0,0,2.12-2.12v-7.673a.889.889,0,0,0-.256-.624Zm.05,4.237v-2.317l2.317,2.317h-2.317Z" transform="translate(-140.974 -99.905)"/></svg>

Before

Width:  |  Height:  |  Size: 904 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13.247" height="13.247" viewBox="0 0 13.247 13.247" fill="currentColor"><g transform="translate(-370.617 -114.129)"><path class="a" d="M93.531,82H83.716A1.718,1.718,0,0,0,82,83.716v9.815a1.718,1.718,0,0,0,1.716,1.716h9.815a1.718,1.718,0,0,0,1.716-1.716V83.716A1.718,1.718,0,0,0,93.531,82Zm.792,11.531a.793.793,0,0,1-.792.792H83.716a.793.793,0,0,1-.792-.792V83.716a.793.793,0,0,1,.792-.792h9.815a.793.793,0,0,1,.792.792ZM84,86.549a.462.462,0,0,1,.462-.462h4.148a.462.462,0,1,1,0,.924H84.465A.462.462,0,0,1,84,86.549Zm9.215,0a.462.462,0,0,1-.462.462h-1.7V87.8a.462.462,0,1,1-.924,0V85.3a.462.462,0,0,1,.924,0v.787h1.7a.462.462,0,0,1,.462.462Zm0,4.31a.462.462,0,0,1-.462.462H87.54a.462.462,0,1,1,0-.924h5.215A.462.462,0,0,1,93.217,90.859ZM85.926,89.61v2.5a.462.462,0,0,1-.924,0v-.787h-.537a.462.462,0,1,1,0-.924H85V89.61a.462.462,0,0,1,.924,0Z" transform="translate(288.617 32.129)"/></g></svg>

Before

Width:  |  Height:  |  Size: 938 B

View File

@ -0,0 +1,5 @@
function CommonTableCell(text?: string | null) {
return <span>{text ?? '--'}</span>;
}
export default CommonTableCell;

View File

@ -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;

View File

@ -1,4 +1,4 @@
@import '@/styles/theme.less';
// @import '@/styles/theme.less';
.kf-radio {
display: flex;
@ -8,7 +8,7 @@
display: flex;
align-items: center;
padding: 12px 20px;
color: @text-color-second;
color: @text-color-secondary;
border: 1px solid #e0e0e0;
border-radius: 8px;
@ -17,9 +17,14 @@
border: 1px solid @primary-color-hover;
}
&:active {
color: @primary-color;
border: 1px solid @primary-color;
}
&--active {
color: @kf-primary-color;
border: 1px solid @kf-primary-color;
color: @primary-color;
border: 1px solid @primary-color;
}
& + & {

View File

@ -2,7 +2,7 @@
.modal_title {
display: flex;
align-items: center;
color: @kf-primary-color;
color: @primary-color;
font-weight: 400;
font-size: 20px;

View File

@ -1,7 +1,7 @@
.kf-page-title {
display: flex;
align-items: center;
height: 49px;
height: 50px;
padding-left: 30px;
background-image: url('../../assets/img/page-title-bg.png');
}

View File

@ -50,7 +50,7 @@ const GlobalHeaderRight: React.FC = () => {
<QuestionCircleOutlined />
</span>
<Avatar menu={true} />
<SelectLang className={actionClassName} />
{/* <SelectLang className={actionClassName} /> */}
</div>
);
};

View File

@ -3,3 +3,10 @@ export enum CommonTabKeys {
Private = 'Private', // 私有
Public = 'Public', // 公开
}
// 镜像状态
export enum MirrorVersionStatus {
Available = 'available', // 可用
Building = 'building', // 构建中
Failed = 'failed', // 构建中
}

View File

@ -4,6 +4,7 @@ body,
height: 100%;
margin: 0;
padding: 0;
overflow-y: hidden;
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 Color Emoji';
@ -31,54 +32,46 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
color: #1664ff;
}
.ant-btn-link {
color: #1664ff;
}
.ant-pro-layout .ant-pro-layout-content {
padding: 10px;
}
.ant-pro-layout .ant-pro-layout-bg-list {
background: #f9fafb;
}
.ant-table-wrapper .ant-table-thead > tr > th {
background-color: #fff;
}
.ant-table-wrapper .ant-table-thead > tr > td {
background-color: #fff;
}
.ant-menu-light .ant-menu-item-selected {
background: rgba(197, 232, 255, 0.8) !important;
}
.ant-menu-light .ant-menu-item-selected .ant-pro-base-menu-inline-item-text{
color:#1664ff;
.ant-menu-light .ant-menu-item-selected .ant-pro-base-menu-inline-item-text {
// color: #1664ff;
}
.ant-pro-layout .ant-pro-sider .ant-layout-sider-children {
background: #f2f5f7;
}
.ant-pro-base-menu-inline-item-title .ant-pro-base-menu-inline-item-text{
color:#1d1d20;
font-size:16px;
.ant-pro-base-menu-inline-item-title .ant-pro-base-menu-inline-item-text {
// color: #1d1d20;
font-size: 16px;
}
// .ant-menu-light .ant-menu-item-selected{
// color:#1664ff;
// }
.ant-pro-layout .ant-pro-sider-menu{
.ant-pro-layout .ant-pro-sider-menu {
padding-top: 40px;
}
.ant-pro-global-header-logo-mix{
height: 75px;
border-bottom: 1px solid rgba(233, 237, 240, 1);
margin-left: -16px;
width: 257px;
background:#f2f5f7;
border-top-right-radius: 20px;
padding-left: 28px;
.ant-table-wrapper .ant-table-container table>thead>tr:first-child >*:first-child,.ant-table-wrapper .ant-table-container table>tbody>tr:first-child{
padding: 0 30px;
}
.ant-pro-layout .ant-pro-sider .ant-layout-sider-children{
.ant-pro-global-header-logo-mix {
width: 257px;
height: 75px;
margin-left: -16px;
padding-left: 28px;
background: #f2f5f7;
border-bottom: 1px solid rgba(233, 237, 240, 1);
border-top-right-radius: 20px;
}
.ant-pro-layout .ant-pro-sider .ant-layout-sider-children {
border-right: unset;
border-bottom-right-radius: 20px;
}
@ -90,13 +83,13 @@ font-size:16px;
.ant-pro-layout .ant-pro-layout-content {
background-color: transparent;
}
.ant-drawer .ant-drawer-body{
.ant-drawer .ant-drawer-body {
padding: 0;
}
.ant-drawer .ant-drawer-body .ant-row{
.ant-drawer .ant-drawer-body .ant-row {
padding: 0 24px;
}
.ant-drawer .ant-drawer-body .ant-form-item{
.ant-drawer .ant-drawer-body .ant-form-item {
margin-bottom: 20px;
}
.ant-menu .ant-menu-submenu-title .anticon {
@ -107,9 +100,10 @@ font-size:16px;
padding: 21px 16px;
background-color: #fff;
}
.ant-table-wrapper .ant-table {
height: 81vh;
}
// .ant-table-wrapper .ant-table {
// height: 81vh;
// // overflow-y: auto;
// }
.ant-pro-global-header-logo img {
height: 21px;
}
@ -117,9 +111,9 @@ font-size:16px;
height: 94vh;
}
.ant-pro-layout .ant-pro-layout-container {
height: 98vh;
height: 100vh;
}
.ant-modal-confirm .ant-modal-confirm-paragraph{
.ant-modal-confirm .ant-modal-confirm-paragraph {
margin: 54px 0 auto;
text-align: center;
}
@ -130,38 +124,39 @@ font-size:16px;
margin-top: 30px;
text-align: center;
}
.ant-modal-confirm-btns .ant-btn-default{
width:110px;
height:40px;
background:rgba(22, 100, 255, 0.06);
border-radius:10px;
color:#1d1d20;
font-size:18px;
margin-right: 10px;
border-color: transparent;
.ant-modal-confirm-btns .ant-btn-default {
width: 110px;
height: 40px;
margin-right: 10px;
// color: #1d1d20;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-modal-confirm-btns .ant-btn-default:hover {
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
}
.ant-modal-confirm-btns .ant-btn-primary{
width:110px;
height:40px;
background:#1664ff;
border-radius:10px;
.ant-modal-confirm-btns .ant-btn-primary {
width: 110px;
height: 40px;
font-size: 18px;
border-radius: 10px;
border-radius: 10px;
}
.ant-modal .ant-input-affix-wrapper{
.ant-modal .ant-input-affix-wrapper {
height: 46px;
padding: 1px 11px;
}
.ant-modal .ant-select-single{
.ant-modal .ant-select-single {
height: 46px;
}
.ant-modal .ant-select-single .ant-select-selector .ant-select-selection-placeholder{
.ant-modal .ant-select-single .ant-select-selector .ant-select-selection-placeholder {
line-height: 46px;
}
.ant-modal .ant-modal-close-x {
width: 26px;
height: 26px;
@ -176,13 +171,13 @@ border-color: transparent;
.ant-modal .ant-modal-content {
padding: 0;
}
.ant-modal-confirm-body-wrapper{
height:303px;
background-image: url(/assets/images/modal-back.png);
background-repeat:no-repeat;
background-size:100%;
background-position: top center;
border-radius: 0;
.ant-modal-confirm-body-wrapper {
height: 303px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 0;
}
.ant-modal .ant-modal-content {
border-radius: 20px;
@ -195,14 +190,14 @@ border-radius: 0;
}
.ant-pagination .ant-pagination-item-active a {
color: #fff;
background: #1664ff;
border-color: #1664ff;
background: rgba(22, 100, 255, 0.8);
// color: #fff;
border-radius: 6px;
}
.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;
}
.ant-pagination .ant-pagination-item {
@ -259,7 +254,3 @@ ol {
}
}
}
.umi-local-svg {
vertical-align: -1px;
}

View File

@ -3,14 +3,14 @@
* @Date: 2024-04-15 10:01:29
* @Description:
*/
import { FormInstance } from 'antd';
import { debounce } from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
/**
* Generates a state reference with the initial value.
*
*
* @param initialValue - The initial value for the state
* @return An array containing the state value, state setter function, and a mutable reference object
* @param initialValue -
* @return
*/
export function useStateRef<T>(initialValue: T) {
const [value, setValue] = useState(initialValue);
@ -25,10 +25,10 @@ export function useStateRef<T>(initialValue: T) {
}
/**
* Generates a custom hook for managing the visibility state of a modal.
*
*
* @param initialValue - The initial visibility state of the modal.
* @return An array containing the visibility state and functions to open and close the modal.
* @param initialValue -
* @return
*/
export function useVisible(initialValue: boolean) {
const [visible, setVisible] = useState(initialValue);
@ -47,34 +47,34 @@ export function useVisible(initialValue: boolean) {
type Callback<T> = (state: T) => void;
/**
* Generates a stateful value and a function to update it that triggers callbacks.
*
*
* @param initialValue - The initial value of the state.
* @return A tuple containing the current state value and a function to update the state.
* @param initialValue -
* @return
*/
export function useCallbackState<T>(initialValue: T) {
const [state, _setState] = useState(initialValue);
const [state, _setState] = useState<T>(initialValue);
const callbackQueue = useRef<Callback<T>[]>([]);
useEffect(() => {
callbackQueue.current.forEach((cb) => cb(state));
callbackQueue.current = [];
}, [state]);
const setState = (newValue: T, callback: Callback<T>) => {
const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => {
_setState(newValue);
if (callback && typeof callback === 'function') {
callbackQueue.current.push(callback);
}
};
return [state, setState];
return [state, setState] as const;
}
/**
* A hook that tracks the size of a DOM element.
* DOM hook
*
* @param initialWidth - The initial width of the element.
* @param initialHeight - The initial height of the element.
* @param deps - dependency list.
* @return - A tuple containing the ref to the DOM element, the current width, and the current height.
* @param initialWidth -
* @param initialHeight -
* @param deps -
* @return DOM ref
*/
export function useDomSize<T extends HTMLElement>(
initialWidth: number,
@ -86,8 +86,6 @@ export function useDomSize<T extends HTMLElement>(
const [height, setHeight] = useState(initialHeight);
useEffect(() => {
console.log('dddddd');
const setDomHeight = () => {
if (domRef.current) {
setHeight(domRef.current.offsetHeight);
@ -106,3 +104,25 @@ export function useDomSize<T extends HTMLElement>(
return [domRef, { width, height }] as const;
}
/**
* modal Form hook
*
* @param form - Ant Design Form
* @param open - modal
*/
export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => {
const prevOpenRef = useRef<boolean>();
useEffect(() => {
prevOpenRef.current = open;
}, [open]);
const prevOpen = prevOpenRef.current;
useEffect(() => {
if (!open && prevOpen) {
form.resetFields();
}
}, [form, prevOpen, open]);
};

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14.902" height="14.902" viewBox="0 0 14.902 14.902"><defs><style>.a{fill:#1664ff;}</style></defs><g transform="translate(-75.735 -76.787)"><path class="a" d="M87.7,88.735a6.355,6.355,0,1,0-8.985,0A6.355,6.355,0,0,0,87.7,88.735Zm.763.763a7.451,7.451,0,1,1,0-10.519A7.451,7.451,0,0,1,88.464,89.5Z" transform="translate(0 0)"/><path class="a" d="M283.541,282.446H286.5a.552.552,0,0,1,0,1.1h-2.954V286.5a.552.552,0,0,1-1.1,0v-2.954h-2.963a.552.552,0,0,1,0-1.1h2.963v-2.963a.552.552,0,0,1,1.1,0Z" transform="translate(-199.785 -198.751)"/></g></svg>

Before

Width:  |  Height:  |  Size: 591 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12.572" height="12.993" viewBox="0 0 12.572 12.993" fill="currentColor"><path class="a" d="M88.236,67l-5.66-2.951a.627.627,0,0,0-.579,0l-5.655,2.906a.627.627,0,0,0-.342.558v6.009a.629.629,0,0,0,.348.563l5.658,2.82a.627.627,0,0,0,.561,0l5.657-2.82a.629.629,0,0,0,.348-.563V67.555A.626.626,0,0,0,88.236,67Zm-6,2.842-2.124-1.046,4.811-2.577L87,67.3Zm.045-5,1.742.909-4.836,2.59-1.9-.934Zm-5.447,3.276,2.007.988v2.2a.419.419,0,0,0,.838,0V69.516l2.184,1.075V75.9l-5.029-2.509ZM82.705,75.9V70.54l5.029-2.681v5.531Z" transform="translate(-76 -63.975)"/></svg>

Before

Width:  |  Height:  |  Size: 599 B

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1713324052180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1059" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M440.167543 988.161377h-269.463331c-24.790626 0-45.970444-8.784505-63.539453-26.299621A86.551622 86.551622 0 0 1 80.865138 898.322302v-808.389993C80.865138 65.141682 89.649642 43.961865 107.164759 26.446748A86.551622 86.551622 0 0 1 170.704212 0.147127h471.56083a44.407557 44.407557 0 0 1 33.35956 14.874376l202.097498 224.516847a44.515342 44.515342 0 0 1 11.533031 30.072108v202.097498h-89.839075v-202.097498h44.946484l-33.413453 30.018215-202.097498-224.57074 33.413453-30.018215v44.892591h-471.56083v808.389993h269.463331v89.839075z m45.000377-44.892591a44.946484 44.946484 0 0 1-82.45578 25.006197 44.946484 44.946484 0 1 1 74.856914-50.012394 44.730913 44.730913 0 0 1 7.544973 25.006197z m404.194996-471.56083a45.000376 45.000376 0 1 1-90.000752 0 45.000376 45.000376 0 0 1 90.000752 0zM838.757703 933.460321l-112.312317-89.892968 56.156159-70.114358 112.258423 89.839074-56.102265 70.114359z m73.024562-35.138019a44.892591 44.892591 0 0 1-62.192136 41.605139 44.892591 44.892591 0 0 1-7.814437-79.006649 44.892591 44.892591 0 0 1 66.611335 20.20975 44.892591 44.892591 0 0 1 3.395238 17.245653z m-112.258423-89.785182a44.892591 44.892591 0 0 1-53.784881 44.138094 44.892591 44.892591 0 0 1-32.766741-61.383747 44.892591 44.892591 0 0 1 66.557443-20.20975 44.892591 44.892591 0 0 1 19.994179 37.455403z" fill="#D47AEF" p-id="1060"></path><path d="M844.36254 314.503049H619.7918a44.784806 44.784806 0 0 1-44.892591-44.892591V45.039718h89.785182v224.57074H619.7918v-44.946484h224.57074v89.839075z m45.000376-44.892591a44.892591 44.892591 0 0 1-45.000376 45.000376 44.892591 44.892591 0 0 1-45.000376-45.000376 44.892591 44.892591 0 0 1 62.192137-41.605138 44.892591 44.892591 0 0 1 27.808615 41.605138zM664.792176 45.039718a44.892591 44.892591 0 0 1-53.784881 44.138094A44.892591 44.892591 0 0 1 578.294447 27.794065a44.892591 44.892591 0 0 1 66.557443-20.155857 44.892591 44.892591 0 0 1 19.994179 37.40151zM619.7918 494.127306a188.624332 188.624332 0 1 0 0 377.248663 188.624332 188.624332 0 0 0 0-377.248663z m0 88.922899a99.701433 99.701433 0 1 1 0 199.402865 99.701433 99.701433 0 0 1 0-199.402865z" fill="#D47AEF" p-id="1061"></path></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="11.246" height="11.246" viewBox="0 0 11.246 11.246" fill="currentColor"><path class="a" d="M70.025,64a.2.2,0,0,1,.2.2V75.045a.2.2,0,0,1-.2.2h-.8a.2.2,0,0,1-.2-.2V64.2a.2.2,0,0,1,.2-.2Zm-2.008,1.2a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H65.2v6.426h2.812a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H64.2a.2.2,0,0,1-.2-.2V65.406a.2.2,0,0,1,.2-.2Zm6.226,7.631v1.2h-1.2v-1.2Zm-2.008,0v1.2h-1.2v-1.2Zm3.012-1.807v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2ZM72.234,65.2v1.2h-1.2V65.2Zm2.008,0v1.2h-1.2V65.2Z" transform="translate(-64 -64)"/></svg>

Before

Width:  |  Height:  |  Size: 590 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="currentColor"><path class="a" d="M872.8,755.637h0v-.012Z" transform="translate(-848.576 -735.009)"/><path class="a" d="M122.155,109.158a13,13,0,1,0-13,13,13.015,13.015,0,0,0,13-13m-13,11.135a11.134,11.134,0,1,1,11.134-11.135,11.149,11.149,0,0,1-11.134,11.135" transform="translate(-96.155 -96.158)"/><path class="a" d="M344.559,345.281l-4.141-4.154,4.137-4.091a.957.957,0,1,0-1.346-1.36l-4.141,4.1-4.08-4.092a.957.957,0,0,0-1.355,1.351l4.075,4.087-4.107,4.062a.956.956,0,0,0,1.345,1.36l4.113-4.068,4.145,4.16a.957.957,0,0,0,1.355-1.352" transform="translate(-326.072 -328.091)"/></svg>

Before

Width:  |  Height:  |  Size: 674 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12.036" height="13.5" viewBox="0 0 12.036 13.5" fill="currentColor" stroke="currentColor"><defs><style>.a{stroke-width:0.1px;}</style></defs><g transform="translate(-205.25 -176.646)"><path class="a" d="M211.268,190.1a1.261,1.261,0,0,1-.626-.167l-4.717-2.721a1.255,1.255,0,0,1-.626-1.084v-5.442a1.258,1.258,0,0,1,.626-1.084l4.717-2.721a1.271,1.271,0,0,1,1.251,0l4.717,2.721a1.255,1.255,0,0,1,.626,1.084v5.442a1.255,1.255,0,0,1-.626,1.084l-4.717,2.721A1.276,1.276,0,0,1,211.268,190.1Zm0-12.6a.478.478,0,0,0-.232.062l-4.717,2.721a.466.466,0,0,0-.232.4v5.442a.464.464,0,0,0,.232.4l4.717,2.721a.469.469,0,0,0,.463,0l4.717-2.721a.466.466,0,0,0,.232-.4V180.68a.464.464,0,0,0-.232-.4l-4.717-2.721A.465.465,0,0,0,211.268,177.5Z" transform="translate(0 0)"/><path class="a" d="M282.168,379.033a1.242,1.242,0,0,1-.616-.163l-3.722-2.1a.394.394,0,1,1,.387-.685l3.722,2.1a.461.461,0,0,0,.451,0l3.847-2.105a.394.394,0,1,1,.377.691l-3.845,2.107A1.251,1.251,0,0,1,282.168,379.033Z" transform="translate(-70.894 -195.36)"/><path class="a" d="M486.392,383.935a.392.392,0,0,1-.393-.4l.016-4.236a1.252,1.252,0,0,1,.653-1.094l3.8-2.067a.393.393,0,1,1,.375.691l-3.8,2.067a.467.467,0,0,0-.242.405l-.016,4.236A.394.394,0,0,1,486.392,383.935Z" transform="translate(-275.124 -195.422)"/><path class="a" d="M282.23,383.9a.392.392,0,0,1-.393-.391l-.016-4.236a.463.463,0,0,0-.242-.407l-3.678-2.069a.394.394,0,0,1,.385-.687l3.676,2.067a1.245,1.245,0,0,1,.647,1.092l.016,4.234a.4.4,0,0,1-.4.4Z" transform="translate(-70.962 -195.384)"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14.893" height="13.83" viewBox="0 0 14.893 13.83" fill="currentColor"><path class="a" d="M60.718,92.155a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25v1.064a.532.532,0,0,1-1.064,0V92.687a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Zm-5.319-6.383a.532.532,0,0,1,.532.532v3.191a.532.532,0,1,1-1.064,0V90.027H50.08a.532.532,0,1,1,0-1.064h2.66V87.9a.532.532,0,0,1,.532-.532Zm10.638,1.6a.532.532,0,0,1,0,1.064H55.4a.532.532,0,1,1,0-1.064Zm-3.191-6.383a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25V86.3a.532.532,0,0,1-1.064,0V83.113a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Z" transform="translate(-49.548 -82.581)"/></svg>

Before

Width:  |  Height:  |  Size: 801 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14.313" height="13.129" viewBox="0 0 14.313 13.129" fill="currentColor" stroke="currentColor"><defs><style>.a{stroke-width:0.2px;}</style></defs><g transform="translate(-106.567 -129.178)"><path class="a" d="M114.2,378.558H108a1.332,1.332,0,0,1-1.328-1.328v-6.2A1.328,1.328,0,0,1,108,369.707h4.255v.724H108a.6.6,0,0,0-.6.6v6.2a.6.6,0,0,0,.6.6h6.2a.6.6,0,0,0,.6-.6v-3.1h.724v3.1a1.328,1.328,0,0,1-1.328,1.328Z" transform="translate(0 -236.351)"/><path class="a" d="M423.317,138.14h-6.2a1.332,1.332,0,0,1-1.328-1.328v-6.2a1.332,1.332,0,0,1,1.328-1.332h6.2a1.328,1.328,0,0,1,1.339,1.332v6.2a1.328,1.328,0,0,1-1.339,1.328Zm-6.2-8.138a.608.608,0,0,0-.6.608v6.2a.6.6,0,0,0,.6.6h6.2a.6.6,0,0,0,.615-.6v-6.2a.608.608,0,0,0-.615-.608Z" transform="translate(-303.876)"/></g></svg>

Before

Width:  |  Height:  |  Size: 817 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14.239" height="14.266" viewBox="0 0 14.239 14.266" fill="currentColor"><defs><style></style></defs><g transform="translate(59 -12320.6)"><path class="a" d="M325.069,355.086a6.46,6.46,0,0,1-4.983-2.344.445.445,0,1,1,.684-.57,5.573,5.573,0,0,0,7.689.869.447.447,0,0,1,.542.712A6.341,6.341,0,0,1,325.069,355.086Zm5.9-4.826a.388.388,0,0,1-.085-.006.445.445,0,0,1-.35-.526,5.641,5.641,0,0,0,.107-1.1,5.548,5.548,0,0,0-3.09-4.993.442.442,0,0,1-.2-.6.447.447,0,0,1,.6-.2,6.443,6.443,0,0,1,3.582,5.79,6.374,6.374,0,0,1-.129,1.276A.442.442,0,0,1,330.972,350.26Zm-11.926-1.389h-.016a.448.448,0,0,1-.435-.463,6.5,6.5,0,0,1,4.965-6.08.448.448,0,0,1,.214.869,5.586,5.586,0,0,0-4.272,5.242A.461.461,0,0,1,319.046,348.871Zm0,0" transform="translate(-376.902 11979.78)"/><path class="a" d="M469.186,298.366a1.783,1.783,0,1,1,1.786-1.78A1.787,1.787,0,0,1,469.186,298.366Z" transform="translate(-521.02 12025.8)"/><path class="a" d="M303.767,323a.891.891,0,1,0,.891.891A.893.893,0,0,0,303.767,323Zm-5.38,11.287a1.786,1.786,0,1,1,1.786-1.786A1.787,1.787,0,0,1,298.386,334.287Z" transform="translate(-355.6 11998.488)"/><path class="a" d="M325.891,568.795a.892.892,0,1,0,.891.892A.893.893,0,0,0,325.891,568.795Zm10.666,2.678a1.786,1.786,0,1,1,1.786-1.786A1.787,1.787,0,0,1,336.558,571.472Z" transform="translate(-383.105 11761.302)"/><path class="a" d="M664.492,596.3a.891.891,0,1,0,.891.891A.893.893,0,0,0,664.492,596.3Z" transform="translate(-711.039 11733.797)"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14.34" height="14.34" viewBox="0 0 14.34 14.34" fill="currentColor"><path class="a" d="M7.17,0a7.17,7.17,0,1,0,7.17,7.17.664.664,0,1,0-1.328,0A5.894,5.894,0,0,1,7.17,13.012,5.894,5.894,0,0,1,1.328,7.17,5.894,5.894,0,0,1,7.17,1.328a4.9,4.9,0,0,1,3.054.929V2.39h0l-.664.8c-.266.266,0,.8.4.8l3.054.133c.266,0,.531-.4.531-.664L12.614.531A.515.515,0,0,0,11.685.4l-.531.8A7.026,7.026,0,0,0,7.17,0Z"/></svg>

Before

Width:  |  Height:  |  Size: 447 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="17" viewBox="0 0 17 17"><defs><style>.a{fill:#fff;stroke:#707070;}.b{clip-path:url(#a);}.c,.d{fill:#f98e1b;}.c{stroke:#f98e1b;stroke-width:0.2px;}.d{opacity:0.21;}</style><clipPath id="a"><rect class="a" width="17" height="17" transform="translate(1789 324)"/></clipPath></defs><g class="b" transform="translate(-1789 -324)"><g transform="translate(1790.273 325.273)"><path class="c" d="M9.8,11.963H1.359A1.335,1.335,0,0,1,0,10.653V.522A.532.532,0,0,1,.542,0a.538.538,0,0,1,.542.522V10.653a.271.271,0,0,0,.276.266H9.788a.271.271,0,0,0,.276-.266V.522A.532.532,0,0,1,10.605,0a.538.538,0,0,1,.542.522V10.653A1.326,1.326,0,0,1,9.8,11.963Z" transform="translate(1.644 2.491)"/><path class="c" d="M13.9,1.044H.542A.532.532,0,0,1,0,.522.532.532,0,0,1,.542,0H13.912a.532.532,0,0,1,.542.522A.54.54,0,0,1,13.9,1.044Z" transform="translate(0 2.098)"/><path class="c" d="M.542,7.859A.532.532,0,0,1,0,7.337V.522A.532.532,0,0,1,.542,0a.538.538,0,0,1,.542.522V7.337A.538.538,0,0,1,.542,7.859Z" transform="translate(5.246 4.241)"/><path class="c" d="M.542,7.859A.532.532,0,0,1,0,7.337V.522A.532.532,0,0,1,.542,0a.538.538,0,0,1,.542.522V7.337A.526.526,0,0,1,.542,7.859Z" transform="translate(8.458 4.241)"/><path class="c" d="M2.708,1.044H.542A.532.532,0,0,1,0,.522.538.538,0,0,1,.542,0H2.718A.532.532,0,0,1,3.26.522.54.54,0,0,1,2.708,1.044Z" transform="translate(5.597)"/><rect class="d" width="6" height="7" transform="translate(6 2.454)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="17" viewBox="0 0 17 17" fill="currentColor"><defs><clipPath id="a"><rect class="a" width="17" height="17" transform="translate(1607 208)"/></clipPath></defs><g class="b" transform="translate(-1607 -208)"><g transform="translate(-0.136 -0.214)"><rect class="c" opacity="0.21" width="4.75" height="8.011" rx="2" transform="translate(1617.532 209.427)"/><g transform="translate(1511.991 145.627)"><path class="d" d="M282,256.622a.524.524,0,0,0-.522-.522h-6.657a.522.522,0,0,0,0,1.044h6.657A.524.524,0,0,0,282,256.622Zm-7.179,2.6a.522.522,0,0,0,0,1.044h3.109a.522.522,0,0,0,0-1.044Zm7.487,5.349a.464.464,0,0,0-.112-.085,2.565,2.565,0,0,0,.467-1.479,2.631,2.631,0,1,0-2.631,2.6,2.649,2.649,0,0,0,1.443-.425.521.521,0,0,0,.094.13l1.355,1.355a.522.522,0,0,0,.739-.739Zm-2.276.01a1.573,1.573,0,1,1,1.594-1.573A1.584,1.584,0,0,1,280.033,264.581Z" transform="translate(-174.416 -189.172)"/><path class="d" d="M104.819,77.329h-5.8a1.044,1.044,0,0,1-1.041-1.041V65.9a1.044,1.044,0,0,1,1.041-1.041h8.9a1.044,1.044,0,0,1,1.041,1.041v5.18h0v0a.524.524,0,0,0,1.048,0c0-.02,0-.037,0-.057V65.882c0-1.145-.449-2.082-1.594-2.082H99.082A2.089,2.089,0,0,0,97,65.882V76.291a2.089,2.089,0,0,0,2.082,2.082h5.73a.522.522,0,0,0,.007-1.044Z" transform="translate(0 0)"/><path class="d" d="M832.7,607.722a.524.524,0,1,0,.524-.522A.523.523,0,0,0,832.7,607.722Z" transform="translate(-723.731 -534.559)"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13.247" height="13.247" viewBox="0 0 13.247 13.247" fill="currentColor"><g transform="translate(-370.617 -114.129)"><path class="a" d="M93.531,82H83.716A1.718,1.718,0,0,0,82,83.716v9.815a1.718,1.718,0,0,0,1.716,1.716h9.815a1.718,1.718,0,0,0,1.716-1.716V83.716A1.718,1.718,0,0,0,93.531,82Zm.792,11.531a.793.793,0,0,1-.792.792H83.716a.793.793,0,0,1-.792-.792V83.716a.793.793,0,0,1,.792-.792h9.815a.793.793,0,0,1,.792.792ZM84,86.549a.462.462,0,0,1,.462-.462h4.148a.462.462,0,1,1,0,.924H84.465A.462.462,0,0,1,84,86.549Zm9.215,0a.462.462,0,0,1-.462.462h-1.7V87.8a.462.462,0,1,1-.924,0V85.3a.462.462,0,0,1,.924,0v.787h1.7a.462.462,0,0,1,.462.462Zm0,4.31a.462.462,0,0,1-.462.462H87.54a.462.462,0,1,1,0-.924h5.215A.462.462,0,0,1,93.217,90.859ZM85.926,89.61v2.5a.462.462,0,0,1-.924,0v-.787h-.537a.462.462,0,1,1,0-.924H85V89.61a.462.462,0,0,1,.924,0Z" transform="translate(288.617 32.129)"/></g></svg>

Before

Width:  |  Height:  |  Size: 938 B

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1713324237000" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1430" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M912.384 668.551529v88.545883a99.448471 99.448471 0 0 1-99.568941 99.508706H211.184941a99.448471 99.448471 0 0 1-99.568941-99.508706v-88.545883a49.814588 49.814588 0 0 0-99.568941 0v87.100236a199.800471 199.800471 0 0 0 199.80047 199.80047h600.184471a199.800471 199.800471 0 0 0 199.800471-199.80047v-87.100236a49.814588 49.814588 0 0 0-99.568942 0z" fill="#1664FF" p-id="1431"></path><path d="M728.003765 398.516706a50.778353 50.778353 0 0 0-71.740236 0l-95.171764 95.171765V125.530353a49.814588 49.814588 0 1 0-99.568941 0v368.158118l-95.171765-95.171765a50.718118 50.718118 0 1 0-71.740235 71.740235l177.995294 177.995294a54.753882 54.753882 0 0 0 77.462588 0l177.874823-177.995294a50.537412 50.537412 0 0 0 0.060236-71.740235z" fill="#1664FF" p-id="1432"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="17" viewBox="0 0 17 17"><defs><style>.a{fill:#fff;stroke:#1431b3;}.b{clip-path:url(#a);}.c,.d{fill:#1664ff;}.c{opacity:0.21;}</style><clipPath id="a"><rect class="a" width="17" height="17" transform="translate(1607 208)"/></clipPath></defs><g class="b" transform="translate(-1607 -208)"><g transform="translate(-0.136 -0.214)"><rect class="c" width="4.75" height="8.011" rx="2" transform="translate(1617.532 209.427)"/><g transform="translate(1511.991 145.627)"><path class="d" d="M282,256.622a.524.524,0,0,0-.522-.522h-6.657a.522.522,0,0,0,0,1.044h6.657A.524.524,0,0,0,282,256.622Zm-7.179,2.6a.522.522,0,0,0,0,1.044h3.109a.522.522,0,0,0,0-1.044Zm7.487,5.349a.464.464,0,0,0-.112-.085,2.565,2.565,0,0,0,.467-1.479,2.631,2.631,0,1,0-2.631,2.6,2.649,2.649,0,0,0,1.443-.425.521.521,0,0,0,.094.13l1.355,1.355a.522.522,0,0,0,.739-.739Zm-2.276.01a1.573,1.573,0,1,1,1.594-1.573A1.584,1.584,0,0,1,280.033,264.581Z" transform="translate(-174.416 -189.172)"/><path class="d" d="M104.819,77.329h-5.8a1.044,1.044,0,0,1-1.041-1.041V65.9a1.044,1.044,0,0,1,1.041-1.041h8.9a1.044,1.044,0,0,1,1.041,1.041v5.18h0v0a.524.524,0,0,0,1.048,0c0-.02,0-.037,0-.057V65.882c0-1.145-.449-2.082-1.594-2.082H99.082A2.089,2.089,0,0,0,97,65.882V76.291a2.089,2.089,0,0,0,2.082,2.082h5.73a.522.522,0,0,0,.007-1.044Z" transform="translate(0 0)"/><path class="d" d="M832.7,607.722a.524.524,0,1,0,.524-.522A.523.523,0,0,0,832.7,607.722Z" transform="translate(-723.731 -534.559)"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -36,3 +36,13 @@
margin-bottom: 0;
}
}
// 表格样式
.ant-table-header {
border: 1px solid rgba(167, 178, 194, 0.17);
border-bottom: none;
}
.ant-table-wrapper .ant-table-thead > tr > td {
background-color: #fff;
}

View File

@ -1,4 +1,5 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import {
addDatasetVersionDetail,
deleteDatasetVersion,
@ -7,12 +8,7 @@ import {
getDatasetVersionsById,
} from '@/services/dataset/index.js';
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 moment from 'moment';
import { useEffect, useRef, useState } from 'react';
@ -203,7 +199,7 @@ const Dataset = () => {
type="link"
size="small"
key="download"
icon={<DownloadOutlined />}
icon={<KFIcon type="icon-xiazai" />}
onClick={(e) => downloadAlone(e, record)}
>
下载
@ -227,9 +223,9 @@ const Dataset = () => {
<div className={Styles.datasetIntroTopBox}>
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<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_type}</div>
{/* <div className={Styles.tagItem}>English</div> */}
</div>
</div>
<div className={Styles.datasetIntroCneterBox}>
@ -257,10 +253,10 @@ const Dataset = () => {
options={versionList}
/>
<Button
type="primary"
type="default"
className={Styles.plusButton}
onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
>
创建新版本
</Button>
@ -269,21 +265,21 @@ const Dataset = () => {
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="primary"
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<DeleteOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
<Button
type="primary"
type="default"
disabled={!version}
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={handleExport}
icon={<DownloadOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xiazai" />}
>
下载
</Button>
@ -381,7 +377,7 @@ const Dataset = () => {
},
]}
>
<Upload {...props} data={{ uuid: uuid }}>
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',

View File

@ -5,9 +5,9 @@
height: 49px;
padding: 0 30px;
padding-right: 30px;
font-family: 'Alibaba';
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
font-family: 'Alibaba';
}
.datasetIntroTopBox {
display: flex;
@ -36,10 +36,10 @@
padding: 20px 30px;
color: #1d1d20;
font-size: 16px;
font-family: alibaba;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
font-family: alibaba;
.dataButtonList {
display: flex;
align-items: center;
@ -70,7 +70,7 @@
.datasetBox {
font-family: 'Alibaba';
background: #f9fafb;
:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
@ -89,20 +89,8 @@
}
.plusButton {
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 {
display: flex;
justify-content: space-between;
@ -119,8 +107,8 @@
height: 100%;
margin-right: 10px;
padding-top: 15px;
background: #ffffff;
font-family: 'Alibaba';
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
@ -210,8 +198,8 @@
display: flex;
flex: 1;
flex-direction: column;
font-family: 'Alibaba';
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
@ -229,68 +217,71 @@
flex: 1;
flex-wrap: wrap;
align-content: flex-start;
font-family: 'Alibaba';
width: 103%;
width: 100%;
.dataItem {
position: relative;
width: 23%;
width: 23.8%;
height:164px;
background:#ffffff;
border:1px solid;
border-color:#eaeaea;
border-radius:4px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.itemText {
position: absolute;
top: 20px;
left: 20px;
background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%);
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
color:#1d1d20;
font-size:16px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition{
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
padding-right: 28px;
color:#575757;
font-size:14px;
word-break: break-all;
overflow: hidden;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
display: flex;
align-items: center;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
display: flex;
align-items: center;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover{
border-color: #1664FF;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.2)
.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;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat:no-repeat;
background-size:100%;
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {

View File

@ -1,9 +1,10 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import KFIcon from '@/components/KFIcon';
import { addDatesetAndVesion, getAssetIcon, getDatasetList } from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict';
import { PlusCircleOutlined, UploadOutlined } from '@ant-design/icons';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
@ -261,10 +262,10 @@ const PublicData = (React.FC = () => {
}}
/>
<Button
type="primary"
className={Styles.plusButton}
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
>
新建数据集
</Button>
@ -424,7 +425,7 @@ const PublicData = (React.FC = () => {
</Radio.Group>
</Form.Item>
<Form.Item label="数据文件" name="dataset_version_vos">
<Upload {...props} data={{ uuid: uuid }}>
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',

View File

@ -3,13 +3,13 @@ import creatByImg from '@/assets/img/creatBy.png';
import { getAssetIcon, getDatasetList } from '@/services/dataset/index.js';
import { Form, Input, Pagination } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];
const PublicData = (React.FC = () => {
const PublicData = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
@ -256,5 +256,5 @@ const PublicData = (React.FC = () => {
</div>
</>
);
});
};
export default PublicData;

View File

@ -1,4 +1,3 @@
import { ReactComponent as ViewParam } from '@/assets/svg/view-param.svg';
import { useVisible } from '@/hooks';
import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
@ -425,11 +424,7 @@ function ExperimentText() {
{experimentStatusInfo[message.status]?.label}
</span>
</div>
<Button
icon={<ViewParam style={{ verticalAlign: '-1px' }}></ViewParam>}
className={styles.param_button}
onClick={openParamsModal}
>
<Button className={styles.param_button} onClick={openParamsModal}>
执行参数
</Button>
</div>

View File

@ -1,3 +1,4 @@
import KFIcon from '@/components/KFIcon';
import {
deleteExperimentById,
deleteQueryByExperimentInsId,
@ -12,16 +13,12 @@ import {
runTensorBoardReq,
} from '@/services/experiment/index.js';
import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { elapsedTime } from '@/utils/date';
import { to } from '@/utils/promise';
import {
DeleteOutlined,
EditOutlined,
FieldTimeOutlined,
PlayCircleOutlined,
PlusCircleOutlined,
} from '@ant-design/icons';
import { Button, Modal, Space, Table, message } from 'antd';
import { modalConfirm } from '@/utils/ui';
import { Button, ConfigProvider, Space, Table, message } from 'antd';
import classNames from 'classnames';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@ -227,6 +224,7 @@ function Experiment() {
};
const pageOption = useRef({ page: 1, size: 10 });
const paginationProps = {
showSizeChanger: true,
showQuickJumper: true,
showTotal: () => `${total}`,
total: total,
@ -277,12 +275,14 @@ function Experiment() {
dataIndex: 'name',
key: 'name',
render: (text) => <div>{text}</div>,
width: '20%',
},
{
title: '关联流水线名称',
dataIndex: 'workflow_name',
key: 'workflow_name',
render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>,
width: '20%',
},
{
title: '实验描述',
@ -293,6 +293,7 @@ function Experiment() {
title: '最近五次运行状态',
dataIndex: 'status_list',
key: 'status_list',
width: 200,
render: (text) => {
let newText = text && text.replace(/\s+/g, '').split(',');
return (
@ -323,7 +324,7 @@ function Experiment() {
type="link"
size="small"
key="run"
icon={<PlayCircleOutlined />}
icon={<KFIcon type="icon-yunhang" />}
onClick={() => {
runExperiment(record.id);
}}
@ -334,213 +335,197 @@ function Experiment() {
type="link"
size="small"
key="edit"
icon={<EditOutlined />}
icon={<KFIcon type="icon-bianji" />}
onClick={() => {
editExperiment(record.id);
}}
>
编辑
</Button>
<Button
type="link"
size="small"
danger
key="batchRemove"
style={{ color: '#f98e1b' }}
icon={<DeleteOutlined />}
onClick={async () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>
删除后该实验将不可恢复
</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
closable: true,
okText: '确认',
cancelText: '取消',
onOk: () => {
console.log(record);
deleteExperimentById(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getList();
} else {
message.error(ret.msg);
}
});
// if (success) {
// if (actionRef.current) {
// actionRef.current.reload();
// }
// }
},
});
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
删除
</Button>
<Button
type="link"
size="small"
key="batchRemove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => {
modalConfirm({
title: '删除后,该实验将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteExperimentById(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getList();
} else {
message.error(ret.msg);
}
});
},
});
}}
>
删除
</Button>
</ConfigProvider>
</Space>
),
},
];
return (
<div>
{/* <div >
<Button type="primary" onClick={createExperiment} icon = {< PlusOutlined />}>
新建实验
</Button>
</div> */}
<div className={Styles.experimentBox}>
<div className={Styles.pipelineTopBox}>
<Button
type="primary"
className={Styles.plusButton}
onClick={createExperiment}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
>
<Button type="default" onClick={createExperiment} icon={<KFIcon type="icon-xinjian2" />}>
新建实验
</Button>
</div>
<Table
columns={columns}
dataSource={experimentList}
pagination={paginationProps}
rowKey="id"
expandable={{
expandedRowRender: (record) => (
<div>
{experimentInList && experimentInList.length > 0 ? (
<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>
) : (
''
)}
{experimentInList && experimentInList.length > 0
? experimentInList.map((item, index) => (
<div
key={item.id}
className={Styles.tableExpandBox}
style={{
border: '1px solid #eaeaea',
backgroundColor: '#fff',
height: '45px',
}}
>
<a style={{ width: '150px' }} onClick={(e) => routerToText(e, item, record)}>
{index + 1}
</a>
<div style={{ width: '300px' }}>
{item.nodes_result?.tensorboard_log ? (
<TensorBoardStatus
status={item.tensorBoardStatus}
onClick={() => handleTensorboard(item)}
></TensorBoardStatus>
) : (
'-'
)}
</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>
<div className={Styles.statusBox} style={{ width: '200px' }}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[item.status]?.icon}
/>{' '}
<span
style={{ color: experimentStatusInfo[item.status]?.color }}
className={Styles.statusIcon}
>
{experimentStatusInfo[item.status]?.label}
</span>
</div>
<div style={{ width: '200px' }}>
<Button
type="link"
size="small"
key="stop"
disabled={
item.status === 'Succeeded' ||
item.status === 'Failed' ||
item.status === 'Terminated'
}
icon={<FieldTimeOutlined />}
onClick={async () => {
putQueryByExperimentInsId(item.id).then((ret) => {
if (ret.code === 200) {
message.success('终止成功');
getQueryByExperiment(record.id);
} else {
message.error(ret.msg);
}
});
}}
>
终止
</Button>
<Button
type="link"
size="small"
danger
key="batchRemove"
style={{ color: '#f98e1b' }}
disabled={item.status === 'Running' || item.status === 'Pending'}
icon={<DeleteOutlined />}
onClick={async () => {
Modal.confirm({
title: '删除',
content: '确定删除该条实例吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
deleteQueryByExperimentInsId(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getQueryByExperiment(record.id);
} else {
message.error(ret.msg);
}
});
},
});
}}
>
删除
</Button>
</div>
<div className={classNames('vertical-scroll-table', Styles.experimentTable)}>
<Table
columns={columns}
dataSource={experimentList}
pagination={paginationProps}
rowKey="id"
scroll={{ y: 'calc(100% - 55px)' }}
expandable={{
expandedRowRender: (record) => (
<div>
{experimentInList && experimentInList.length > 0 ? (
<div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<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>
),
onExpand: (e, a) => {
expandChange(e, a);
},
expandedRowKeys: [expandedRowKeys],
rowExpandable: (record) => true,
}}
/>
<div className={Styles.status}>状态</div>
<div className={Styles.operation}>操作</div>
</div>
) : (
''
)}
{experimentInList && experimentInList.length > 0
? experimentInList.map((item, index) => (
<div
key={item.id}
className={classNames(Styles.tableExpandBox, Styles.tableExpandBoxContent)}
>
<a
className={Styles.index}
style={{ padding: '0 16px' }}
onClick={(e) => routerToText(e, item, record)}
>
{index + 1}
</a>
<div className={Styles.tensorBoard}>
{item.nodes_result?.tensorboard_log ? (
<TensorBoardStatus
status={item.tensorBoardStatus}
onClick={() => handleTensorboard(item)}
></TensorBoardStatus>
) : (
'-'
)}
</div>
<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 className={Styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[item.status]?.icon}
/>{' '}
<span
style={{ color: experimentStatusInfo[item.status]?.color }}
className={Styles.statusIcon}
>
{experimentStatusInfo[item.status]?.label}
</span>
</div>
<div className={Styles.operation}>
<Button
type="link"
size="small"
key="stop"
disabled={
item.status === 'Succeeded' ||
item.status === 'Failed' ||
item.status === 'Terminated'
}
icon={<KFIcon type="icon-zhongzhi" />}
onClick={async () => {
putQueryByExperimentInsId(item.id).then((ret) => {
if (ret.code === 200) {
message.success('终止成功');
getQueryByExperiment(record.id);
} else {
message.error(ret.msg);
}
});
}}
>
终止
</Button>
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
<Button
type="link"
size="small"
key="batchRemove"
disabled={item.status === 'Running' || item.status === 'Pending'}
icon={<KFIcon type="icon-shanchu" />}
onClick={() => {
modalConfirm({
title: '确定删除该条实例吗?',
onOk: () => {
deleteQueryByExperimentInsId(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getQueryByExperiment(record.id);
} else {
message.error(ret.msg);
}
});
},
});
}}
>
删除
</Button>
</ConfigProvider>
</div>
</div>
))
: ''}
</div>
),
onExpand: (e, a) => {
expandChange(e, a);
},
expandedRowKeys: [expandedRowKeys],
rowExpandable: (record) => true,
}}
/>
</div>
{isModalOpen && (
<AddExperimentModal
isAdd={isAdd}

View File

@ -19,35 +19,54 @@
background-image: url(/assets/images/pipeline-back.png);
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 {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0 65px 0 40px;
padding: 0 0 0 33px;
color: #1d1d20;
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 {
display: flex;
align-items: center;
width: 200px;
.statusIcon {
visibility: hidden;
@ -57,3 +76,16 @@
.statusBox:hover .statusIcon {
visibility: visible;
}
.experimentBox{
height: calc(100% - 20px);
.experimentTable{
height: calc(100% - 60px);
:global{
.ant-table-wrapper .ant-table{
// overflow-y: auto;
height: calc(100% - 48px);
}
}
}
}

View File

@ -15,7 +15,10 @@ export enum ExperimentStatus {
Omitted = 'Omitted',
}
export const experimentStatusInfo: Record<string, StatusInfo | undefined> = {
type ExperimentStatusKeys = keyof typeof ExperimentStatus;
type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys];
export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | undefined> = {
Running: {
label: '运行中',
color: '#165bff',

View File

@ -0,0 +1,13 @@
//@import '@/styles/theme.less';
.mirror-status-cell {
color: @text-color;
&--success {
color: @success-color;
}
&--error {
color: @error-color;
}
}

View File

@ -0,0 +1,39 @@
/*
* @Author:
* @Date: 2024-04-18 18:35:41
* @Description:
*/
import { MirrorVersionStatus } from '@/enums';
import styles from './index.less';
type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus;
type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys];
export type MirrorVersionStatusInfo = {
text: string;
classname: string;
};
const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = {
[MirrorVersionStatus.Building]: {
text: '构建中',
classname: styles['mirror-status-cell'],
},
[MirrorVersionStatus.Available]: {
classname: styles['mirror-status-cell--success'],
text: '可用',
},
[MirrorVersionStatus.Failed]: {
classname: styles['mirror-status-cell--error'],
text: '构建失败',
},
};
function MirrorStatusCell(status: MirrorVersionStatus) {
if (status === null || status === undefined || !statusInfo[status]) {
return <span>--</span>;
}
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
}
export default MirrorStatusCell;

View File

@ -4,7 +4,7 @@
height: 100%;
&__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 10px;
overflow: auto;

View File

@ -1,16 +1,22 @@
/*
* @Author:
* @Date: 2024-04-16 13:58:08
* @Description:
* @Description:
*/
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFRadio, { type KFRadioItem } from '@/components/KFRadio';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { createPrivateMirrorReq, createPublicMirrorReq } from '@/services/mirror';
import { createMirrorReq } from '@/services/mirror';
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';
type FormData = {
@ -18,31 +24,83 @@ type FormData = {
tag: string;
description: string;
path?: string;
type: string;
upload_type: string;
fileList?: UploadFile[];
};
const mirrorRadioItems: KFRadioItem[] = [
{
key: CommonTabKeys.Public,
title: '基于公网镜像',
icon: <KFIcon type="icon-jiyugongwangjingxiang" />,
},
{
key: CommonTabKeys.Private,
title: '本地上传',
icon: <KFIcon type="icon-bendishangchuan" />,
},
];
function MirrorCreate() {
const navgite = useNavigate();
const [seachParams] = useSearchParams();
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) {
message.success('创建成功');
navgite(-1);
@ -51,13 +109,26 @@ function MirrorCreate() {
// 提交
const handleSubmit = (values: FormData) => {
console.log(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 (
<div className={styles['mirror-create']}>
<PageTitle title="基本信息"></PageTitle>
<PageTitle title="创建镜像"></PageTitle>
<div className={styles['mirror-create__content']}>
<div>
<Form
@ -66,7 +137,7 @@ function MirrorCreate() {
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
initialValues={{ type: CommonTabKeys.Public }}
initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit}
>
<SubAreaTitle
@ -86,13 +157,19 @@ function MirrorCreate() {
},
]}
>
<Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear />
<Input
placeholder="请输入镜像名称"
maxLength={64}
disabled={nameDisabled}
showCount
allowClear
/>
</Form.Item>
</Col>
<Col span={10}>
<Form.Item
label=" "
name="tag"
name="tag_name"
labelCol={{ flex: '20px' }}
wrapperCol={{ flex: 1 }}
required={false}
@ -138,7 +215,7 @@ function MirrorCreate() {
<Col span={10}>
<Form.Item
label="构建方式"
name="type"
name="upload_type"
rules={[
{
required: true,
@ -152,10 +229,12 @@ function MirrorCreate() {
</Row>
<Form.Item
noStyle
shouldUpdate={(prevValues, curValues) => prevValues.type !== curValues.type}
shouldUpdate={(prevValues, curValues) =>
prevValues.upload_type !== curValues.upload_type
}
>
{({ getFieldValue }) => {
const type = getFieldValue('type');
const type = getFieldValue('upload_type');
if (type === CommonTabKeys.Public) {
return (
<>
@ -196,7 +275,9 @@ function MirrorCreate() {
<Col span={10}>
<Form.Item
label="镜像文件"
name="path"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
@ -204,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>
</Upload> */}
</Upload>
</Form.Item>
</Col>
</Row>
@ -230,7 +303,12 @@ function MirrorCreate() {
<Button type="primary" htmlType="submit">
</Button>
<Button type="default" htmlType="reset" style={{ marginLeft: '20px' }}>
<Button
type="default"
htmlType="button"
onClick={cancel}
style={{ marginLeft: '20px' }}
>
</Button>
</Form.Item>

View File

@ -12,7 +12,7 @@
.label {
width: 80px;
color: @text-color-second;
color: @text-color-secondary;
}
.value {
@ -23,7 +23,7 @@
}
&__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 0;
background-color: white;

View File

@ -3,16 +3,37 @@
* @Date: 2024-04-16 13:58:08
* @Description:
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { useDomSize } from '@/hooks';
import { getMirrorInfoReq, getMirrorVersionListReq } from '@/services/mirror';
import {
deleteMirrorVersionReq,
getMirrorInfoReq,
getMirrorVersionListReq,
} from '@/services/mirror';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import { useParams, useSearchParams } from '@umijs/max';
import { Button, Col, 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 dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import MirrorStatusCell from './components/MirrorStatusCell';
import styles from './info.less';
type MirrorInfoData = {
@ -23,6 +44,7 @@ type MirrorInfoData = {
};
type MirrorVersionData = {
id: number;
version: string;
url: string;
status: string;
@ -31,23 +53,26 @@ type MirrorVersionData = {
};
function MirrorInfo() {
const navigate = useNavigate();
const urlParams = useParams();
const [seachParams] = useSearchParams();
const [searchParams] = useSearchParams();
const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({});
const [tableData, setTableData] = useState<MirrorVersionData[]>([]);
const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>({
showSizeChanger: true,
showQuickJumper: true,
current: 1,
pageSize: 10,
total: 0,
});
const isPublic = seachParams.get('isPublic') === 'true';
const isPublic = searchParams.get('isPublic') === 'true';
useEffect(() => {
getMirrorInfo();
getMirrorVersionList();
}, []);
useEffect(() => {
getMirrorVersionList();
}, [pagination]);
// 获取镜像详情
const getMirrorInfo = async () => {
@ -55,7 +80,8 @@ function MirrorInfo() {
const [res] = await to(getMirrorInfoReq(id));
if (res && 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({
name,
description,
@ -77,10 +103,26 @@ function MirrorInfo() {
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setPagination((prev) => ({
...prev,
total: totalElements,
}));
setTotal(totalElements);
}
};
// 删除镜像版本
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();
}
}
};
@ -88,75 +130,86 @@ function MirrorInfo() {
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
if (action === 'paginate') {
setPagination(pagination);
getMirrorVersionList();
}
console.log(pagination, filters, sorter, action);
};
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'] = [
{
title: '镜像版本',
dataIndex: 'version',
key: 'version',
width: '20%',
dataIndex: 'tag_name',
key: 'tag_name',
width: '25%',
render: CommonTableCell,
},
{
title: '镜像地址',
dataIndex: 'url',
key: 'url',
width: '26%',
render: CommonTableCell,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: '7%',
width: 150,
render: MirrorStatusCell,
},
{
title: '镜像大小',
dataIndex: 'file_size',
key: 'file_size',
width: '7%',
width: 150,
render: CommonTableCell,
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: '20%',
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
width: 200,
render: DateTableCell,
},
{
title: '操作',
dataIndex: 'operation',
width: '20%',
width: 150,
key: 'operation',
render: (_: any, record: any) => (
hidden: isPublic,
render: (_: any, record: MirrorVersionData) => (
<div>
<Button
type="link"
size="small"
key="info"
// icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-4px' }} />}
//icon={<MyIcon type="icon-shiyanduibi" style={{ fontSize: '16px' }}></MyIcon>}
onClick={() => downloadVersion(record)}
>
</Button>
{!isPublic && (
<Button
type="link"
size="small"
key="remove"
// icon={<MyIcon type="icon-shiyanduibi" style={{ fontSize: '16px' }}></MyIcon>}
// icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-2px' }} />}
onClick={() => removeVersion(record)}
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
</Button>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleVersionDelete(record)}
>
</Button>
</ConfigProvider>
)}
</div>
),
@ -184,7 +237,7 @@ function MirrorInfo() {
<Col span={10}>
<div className={styles['mirror-info__basic__item']}>
<div className={styles['label']}></div>
<div className={styles['value']}>{mirrorInfo.version_count}</div>
<div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div>
</div>
</Col>
</Row>
@ -203,12 +256,21 @@ function MirrorInfo() {
</Col>
</Row>
</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
className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])}
@ -218,7 +280,7 @@ function MirrorInfo() {
dataSource={tableData}
columns={columns}
scroll={{ y: 'calc(100% - 55px)' }}
pagination={pagination}
pagination={{ ...pagination, total }}
onChange={handleTableChange}
rowKey="id"
/>

View File

@ -1,15 +1,13 @@
@import '@/styles/theme.less';
.mirror-list {
height: 100%;
&__tabs-container {
height: 49px;
height: 50px;
padding-left: 27px;
background-image: url('../../assets/img/page-title-bg.png');
}
&__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px;
padding: 20px 30px 0;
background-color: white;
@ -19,12 +17,11 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
}
&__table {
height: calc(100% - 44px);
margin-top: 12px;
height: calc(100% - 34px - 28px);
margin-top: 28px;
}
}
}

View File

@ -3,14 +3,28 @@
* @Date: 2024-04-16 13:58:08
* @Description:
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import { getMirrorListReq } from '@/services/mirror';
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import { Icon, useNavigate } from '@umijs/max';
import { Button, Input, Table, TablePaginationConfig, TableProps, Tabs } from 'antd';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useSearchParams } from '@umijs/max';
import {
Button,
ConfigProvider,
Input,
Table,
Tabs,
message,
type TablePaginationConfig,
type TableProps,
type TabsProps,
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import styles from './list.less';
@ -18,12 +32,12 @@ const mirrorTabItems = [
{
key: CommonTabKeys.Public,
label: '公共镜像',
icon: <Icon icon="local:public-mirror-tab" className="umi-local-svg" />,
icon: <KFIcon type="icon-gonggongjingxiang" />,
},
{
key: CommonTabKeys.Private,
label: '个人镜像',
icon: <Icon icon="local:private-mirror-tab" className="umi-local-svg" />,
icon: <KFIcon type="icon-zidingyijingxiang" />,
},
];
@ -35,65 +49,118 @@ export type MirrorData = {
};
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 [tableData, setTableData] = useState<MirrorData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>({
showSizeChanger: true,
showQuickJumper: true,
current: 1,
pageSize: 10,
total: 0,
});
useEffect(() => {
getMirrorList();
}, [activeTab]);
}, [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,
size: pagination.pageSize,
name: searchText,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
...params,
};
const [res] = await to(getMirrorListReq(params));
const [res] = await to(getMirrorListReq(reqParams));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setPagination((prev) => ({
...prev,
total: totalElements,
}));
setTotal(totalElements);
}
};
// 删除镜像
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) => {
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 handleMirrorDelete = (record: MirrorData) => {
modalConfirm({
title: '删除后,该镜像将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteMirror(record.id);
},
});
};
// 创建镜像
const createMirror = () => {
navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` });
navigate(`/dataset/mirror/create`);
};
// 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
if (action === 'paginate') {
setPagination(pagination);
getMirrorList();
}
console.log(pagination, filters, sorter, action);
// console.log(pagination, filters, sorter, action);
};
const columns: TableProps<MirrorData>['columns'] = [
@ -101,57 +168,64 @@ function MirrorList() {
title: '镜像名称',
dataIndex: 'name',
key: 'name',
width: '10%',
width: '30%',
render: CommonTableCell,
},
{
title: '版本数据',
dataIndex: 'version_count',
key: 'version_count',
width: '10%',
width: 100,
render: CommonTableCell,
},
{
title: '镜像描述',
dataIndex: 'description',
key: 'description',
width: '50%',
//ellipsis: true,
render: CommonTableCell,
ellipsis: true,
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: '20%',
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
width: 200,
render: DateTableCell,
},
{
title: '操作',
dataIndex: 'operation',
width: '15%',
width: activeTab === CommonTabKeys.Private ? 200 : 150,
key: 'operation',
render: (_: any, record: any) => (
render: (_: any, record: MirrorData) => (
<div>
<Button
type="link"
size="small"
key="info"
// icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-4px' }} />}
icon={<KFIcon type="icon-chakanxiangqing" />}
onClick={() => toDetail(record)}
>
</Button>
{activeTab === CommonTabKeys.Private && (
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shiyanduibi1" />}
// icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-2px' }} />}
// onClick={(e) => downloadAlone(e, record)}
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
</Button>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleMirrorDelete(record)}
>
</Button>
</ConfigProvider>
)}
</div>
),
@ -164,7 +238,7 @@ function MirrorList() {
<Tabs
activeKey={activeTab}
items={mirrorTabItems}
onChange={setActiveTab}
onChange={hanleTabChange}
className={styles['model-tabs']}
/>
</div>
@ -183,7 +257,7 @@ function MirrorList() {
style={{ marginLeft: '20px' }}
type="default"
onClick={createMirror}
icon={<Icon icon="local:refresh" style={{ verticalAlign: '-2px' }} />}
icon={<KFIcon type="icon-xinjian2" />}
>
</Button>
@ -192,7 +266,7 @@ function MirrorList() {
style={{ marginRight: 0, marginLeft: 'auto' }}
type="default"
onClick={getMirrorList}
icon={<Icon icon="local:refresh" style={{ verticalAlign: '-2px' }} />}
icon={<KFIcon type="icon-shuaxin" />}
>
</Button>
@ -202,7 +276,7 @@ function MirrorList() {
dataSource={tableData}
columns={columns}
scroll={{ y: 'calc(100% - 55px)' }}
pagination={pagination}
pagination={{ ...pagination, total: total }}
onChange={handleTableChange}
rowKey="id"
/>

View File

@ -87,20 +87,8 @@
}
.plusButton {
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 {
display: flex;
justify-content: space-between;
@ -202,6 +190,7 @@
flex: 1;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
@ -222,64 +211,67 @@
width: 100%;
.dataItem {
position: relative;
width: 23%;
width: 23.8%;
height:164px;
background:#ffffff;
border:1px solid;
border-color:#eaeaea;
border-radius:4px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.itemText {
position: absolute;
top: 20px;
left: 20px;
background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%);
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
color:#1d1d20;
font-size:16px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition{
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
padding-right: 28px;
color:#575757;
font-size:14px;
word-break: break-all;
overflow: hidden;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
display: flex;
align-items: center;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
display: flex;
align-items: center;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover{
border-color: #1664FF;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.2)
.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;
}
}
}
@ -290,9 +282,9 @@
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat:no-repeat;
background-size:100%;
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {

View File

@ -1,4 +1,5 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import {
addModelsVersionDetail,
deleteModelVersion,
@ -7,12 +8,7 @@ import {
getModelVersionsById,
} from '@/services/dataset/index.js';
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 moment from 'moment';
import { useEffect, useRef, useState } from 'react';
@ -201,7 +197,7 @@ const Dataset = () => {
type="link"
size="small"
key="download"
icon={<DownloadOutlined />}
icon={<KFIcon type="icon-xiazai" />}
onClick={(e) => downloadAlone(e, record)}
>
下载
@ -225,9 +221,9 @@ const Dataset = () => {
<div className={Styles.datasetIntroTopBox}>
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<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_type}</div>
{/* <div className={Styles.tagItem}>English</div> */}
</div>
</div>
<div className={Styles.datasetIntroCneterBox}>
@ -255,10 +251,10 @@ const Dataset = () => {
options={versionList}
/>
<Button
type="primary"
type="default"
className={Styles.plusButton}
onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
>
创建新版本
</Button>
@ -267,21 +263,21 @@ const Dataset = () => {
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="primary"
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<DeleteOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
<Button
type="primary"
type="default"
className={Styles.plusButton}
disabled={!version}
style={{ margin: '0 20px 0 0' }}
onClick={handleExport}
icon={<DownloadOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xiazai" />}
>
下载
</Button>
@ -379,7 +375,7 @@ const Dataset = () => {
},
]}
>
<Upload {...props} data={{ uuid: uuid }}>
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',

View File

@ -1,8 +1,9 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import KFIcon from '@/components/KFIcon';
import { addModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { PlusCircleOutlined, UploadOutlined } from '@ant-design/icons';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd';
import moment from 'moment';
import { useEffect, useState } from 'react';
@ -264,10 +265,10 @@ const PublicData = () => {
}}
/>
<Button
type="primary"
className={Styles.plusButton}
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
>
模型注册
</Button>
@ -413,7 +414,7 @@ const PublicData = () => {
<Select allowClear placeholder="请选择模型标签" options={[]} />
</Form.Item>
<Form.Item label="模型文件" name="models_version_vos">
<Upload {...props} data={{ uuid: uuid }}>
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',

View File

@ -58,15 +58,11 @@
overflow-y: auto;
&__file {
height: 24px;
margin-bottom: 10px;
padding-left: 10px;
overflow: hidden;
color: #575757;
padding: 3px 10px;
color: @text-color-secondary;
font-size: 13px;
line-height: 24px;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
background: @background-color-gray;
border-radius: 4px;
}

View File

@ -5,9 +5,10 @@
*/
import datasetImg from '@/assets/img/modal-select-dataset.png';
import mirrorImg from '@/assets/img/modal-select-mirror.png';
import modelImg from '@/assets/img/modal-select-model.png';
import KFModal from '@/components/KFModal';
import { CommonTabKeys } from '@/enums';
import { CommonTabKeys, MirrorVersionStatus } from '@/enums';
import {
getDatasetList,
getDatasetVersionIdList,
@ -16,6 +17,7 @@ import {
getModelVersionIdList,
getModelVersionsById,
} from '@/services/dataset/index.js';
import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror';
import { to } from '@/utils/promise';
import { Icon } from '@umijs/max';
import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd';
@ -26,42 +28,51 @@ import styles from './index.less';
export enum ResourceSelectorType {
Model = 'Model', // 模型
Dataset = 'Dataset', // 数据集
Mirror = 'Mirror', //镜像
}
type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType;
type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys];
type GetModelFilesReqParam = {
models_id: number;
version: string;
};
type GetDatasetFilesReqParam = {
dataset_id: number;
version: string;
};
type GetFilesReqParam = GetModelFilesReqParam | GetDatasetFilesReqParam;
export type SelectorTypeInfo = {
getList: (params: { page: number; size: number; available_range: string }) => Promise<any>;
getVersions: (params: number) => Promise<any>;
getFiles: (params: GetFilesReqParam) => Promise<any>;
getList: (params: any) => Promise<any>;
getVersions: (params: any) => Promise<any>;
getFiles: (params: any) => Promise<any>;
handleVersionResponse: (res: any) => any[];
modalIcon: string;
buttonIcon: string;
name: string;
litReqParamKey: 'available_range' | 'image_type';
fileReqParamKey: 'models_id' | 'dataset_id';
tabItems: TabsProps['items'];
};
// 获取镜像列表,为了兼容之前的结构
const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => {
const index = version.indexOf('-');
const url = version.slice(index + 1);
return Promise.resolve({
data: {
content: [
{
id: `${id}-${version}`,
file_name: `${url}`,
},
],
},
});
};
export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = {
Model: {
[ResourceSelectorType.Model]: {
getList: getModelList,
getVersions: getModelVersionsById,
getFiles: getModelVersionIdList,
handleVersionResponse: (res) => res.data || [],
name: '模型',
modalIcon: modelImg,
buttonIcon: 'local:model-select-button',
litReqParamKey: 'available_range',
fileReqParamKey: 'models_id',
tabItems: [
{
@ -74,13 +85,15 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
},
],
},
Dataset: {
[ResourceSelectorType.Dataset]: {
getList: getDatasetList,
getVersions: getDatasetVersionsById,
getFiles: getDatasetVersionIdList,
handleVersionResponse: (res) => res.data || [],
name: '数据集',
modalIcon: datasetImg,
buttonIcon: 'local:dataset-select-button',
litReqParamKey: 'available_range',
fileReqParamKey: 'dataset_id',
tabItems: [
{
@ -93,6 +106,29 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
},
],
},
[ResourceSelectorType.Mirror]: {
getList: getMirrorListReq,
getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }),
getFiles: getMirrorFilesReq,
handleVersionResponse: (res) =>
res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) ||
[],
name: '镜像',
modalIcon: mirrorImg,
buttonIcon: 'local:mirror-select-button',
litReqParamKey: 'image_type',
fileReqParamKey: 'dataset_id',
tabItems: [
{
key: CommonTabKeys.Private,
label: '我的镜像',
},
{
key: CommonTabKeys.Public,
label: '公开镜像',
},
],
},
};
type ResourceSelectorResponse = {
@ -108,7 +144,7 @@ interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
defaultExpandedKeys: React.Key[];
defaultCheckedKeys: React.Key[];
defaultActiveTab: CommonTabKeys;
onOk?: (params: ResourceSelectorResponse | null) => void;
onOk?: (params: ResourceSelectorResponse | string | null) => void;
}
type ResourceGroup = {
@ -116,6 +152,13 @@ type ResourceGroup = {
name: string; // 数据集或者模型 id
};
type MirrorVersion = {
id: number; // 镜像版本id
status: MirrorVersionStatus; // 镜像版本状态
tag_name: string; // 镜像版本
url: string; // 镜像版本路径
};
type ResourceFile = {
id: number; // 文件 id
file_name: string; // 文件 name
@ -133,6 +176,27 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => {
}));
};
// 版本转成 treeData
const convertVersionToTreeData = (parentId: number) => {
return (item: string | MirrorVersion): TreeDataNode => {
if (typeof item === 'string') {
return {
title: item,
key: `${parentId}-${item}`,
isLeaf: true,
checkable: true,
};
} else {
return {
title: item.tag_name,
key: `${parentId}-${item.id}-${item.url}`,
isLeaf: true,
checkable: true,
};
}
};
};
// 更新树形结构的 children
const updateChildren = (parentId: number, children: TreeDataNode[]) => {
return (node: TreeDataNode) => {
@ -197,11 +261,11 @@ function ResourceSelectorModal({
// 获取数据集或模型列表
const getTreeData = async () => {
const available_range = activeTab === CommonTabKeys.Private ? '0' : '1';
const available_range = activeTab === CommonTabKeys.Private ? 0 : 1;
const params = {
page: 0,
size: 200,
available_range: available_range,
[selectorTypeData[type].litReqParamKey]: available_range,
};
const getListReq = selectorTypeData[type].getList;
const [res] = await to(getListReq(params));
@ -222,13 +286,8 @@ function ResourceSelectorModal({
const getVersionsReq = selectorTypeData[type].getVersions;
const [res, error] = await to(getVersionsReq(parentId));
if (res) {
const list = res.data || [];
const children = list.map((v: string) => ({
title: v,
key: `${parentId}-${v}`,
isLeaf: true,
checkable: true,
}));
const list = selectorTypeData[type].handleVersionResponse(res);
const children = list.map(convertVersionToTreeData(parentId));
// 更新 treeData children
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
// 缓存 loadedKeys
@ -248,7 +307,7 @@ function ResourceSelectorModal({
const getFiles = async (id: number, version: string) => {
const getFilesReq = selectorTypeData[type].getFiles;
const paramsKey = selectorTypeData[type].fileReqParamKey;
const params = { version: version, [paramsKey]: id } as GetFilesReqParam;
const params = { version: version, [paramsKey]: id };
const [res] = await to(getFilesReq(params));
if (res) {
setVersionPath(res.data?.path || '');
@ -329,17 +388,21 @@ function ResourceSelectorModal({
// 提交
const handleOk = () => {
if (checkedKeys.length > 0) {
const last = checkedKeys[0] as string;
const { id, version } = getIdAndVersion(last);
const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
const res = {
id,
name,
path: versionPath,
version,
activeTab: activeTab as CommonTabKeys,
};
onOk?.(res);
if (type === ResourceSelectorType.Mirror) {
onOk?.(files[0].file_name);
} else {
const last = checkedKeys[0] as string;
const { id, version } = getIdAndVersion(last);
const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
const res = {
id,
name,
path: versionPath,
version,
activeTab: activeTab as CommonTabKeys,
};
onOk?.(res);
}
} else {
onOk?.(null);
}
@ -347,7 +410,10 @@ function ResourceSelectorModal({
const title = `选择${selectorTypeData[type].name}`;
const palceholder = `请输入${selectorTypeData[type].name}名称`;
const fileTitle = `已选${selectorTypeData[type].name}文件(${files.length}`;
const fileTitle =
type === ResourceSelectorType.Mirror
? '已选镜像'
: `已选${selectorTypeData[type].name}文件(${files.length}`;
const tabItems = selectorTypeData[type].tabItems;
const titleImg = selectorTypeData[type].modalIcon;

View File

@ -4,8 +4,9 @@ import {
} from '@/pages/Experiment/experimentText/addExperimentModal';
import { type PipelineGlobalParam } from '@/types';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Drawer, Form, Input, Radio } from 'antd';
import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle } from 'react';
import styles from './globalParamsDrawer.less';
@ -22,9 +23,8 @@ const GlobalParamsDrawer = forwardRef(
useImperativeHandle(ref, () => ({
getFieldsValue: async () => {
const [res, error] = await to(form.validateFields());
if (res && !error) {
const values = form.getFieldsValue();
const [values, error] = await to(form.validateFields());
if (!error && values) {
return values;
} else {
return Promise.reject(error);
@ -32,10 +32,20 @@ const GlobalParamsDrawer = forwardRef(
},
}));
// 处理参数类型变化
const handleTypeChange = (name: NamePath) => {
form.setFieldValue(name, null);
};
const removeParameter = (name: number, remove: (param: number) => void) => {
modalConfirm({
title: '确认删除该参数吗?',
onOk: () => {
remove(name);
},
});
};
return (
<Drawer
rootStyle={{ marginTop: '45px' }}
@ -128,12 +138,14 @@ const GlobalParamsDrawer = forwardRef(
<Radio value={0}></Radio>
</Radio.Group>
</Form.Item>
<Button
className={styles.delete_button}
type="link"
onClick={() => remove(name)}
icon={<DeleteOutlined />}
></Button>
<Tooltip title="删除参数">
<Button
className={styles.delete_button}
type="link"
onClick={() => removeParameter(name, remove)}
icon={<DeleteOutlined />}
></Button>
</Tooltip>
</div>
))}
<Form.Item className={styles.add_button_form_item}>
@ -150,6 +162,7 @@ const GlobalParamsDrawer = forwardRef(
)}
</Form.List>
</Form>
{/* //{contextHolder} */}
</Drawer>
);
},

View File

@ -1,9 +1,7 @@
import { ReactComponent as ParameterIcon } from '@/assets/svg/parameter.svg';
import { ReactComponent as SaveAndReturn } from '@/assets/svg/save--return.svg';
import KFIcon from '@/components/KFIcon';
import { useVisible } from '@/hooks';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import { to } from '@/utils/promise';
import { SaveOutlined } from '@ant-design/icons';
import { useEmotionCss } from '@ant-design/use-emotion-css';
import G6 from '@antv/g6';
import { Button, message } from 'antd';
@ -716,7 +714,7 @@ const EditPipeline = () => {
<div className={Styles.buttonList}>
<Button
type="default"
icon={<ParameterIcon style={{ verticalAlign: '-2px' }} />}
icon={<KFIcon type="icon-quanjucanshu" />}
style={{ marginRight: '20px' }}
onClick={openParamsDrawer}
>
@ -724,7 +722,7 @@ const EditPipeline = () => {
</Button>
<Button
type="primary"
icon={<SaveOutlined />}
icon={<KFIcon type="icon-baocun" />}
style={{ marginRight: '20px' }}
onClick={() => {
savePipeline(false);
@ -740,7 +738,7 @@ const EditPipeline = () => {
background: '#fff',
color: '#1664ff',
}}
icon={<SaveAndReturn />}
icon={<KFIcon type="icon-baocunbingfanhui" />}
onClick={() => {
savePipeline(true);
}}

View File

@ -1,9 +1,10 @@
import { pick } from '@/utils/index';
import KFIcon from '@/components/KFIcon';
import { getComputingResourceReq } from '@/services/pipeline';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { Icon } from '@umijs/max';
import { Button, Drawer, Form, Input } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Button, Drawer, Form, Input, Select } from 'antd';
import { pick } from 'lodash';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal';
import Styles from './editPipeline.less';
const { TextArea } = Input;
@ -14,6 +15,23 @@ const Props = forwardRef(({ onParentChange }, ref) => {
const [open, setOpen] = useState(false);
const [selectedModel, setSelectedModel] = useState(undefined);
const [selectedDataset, setSelectedDataset] = useState(undefined);
const [resourceStandardList, setResourceStandardList] = useState([]);
useEffect(() => {
getComputingResource();
}, []);
const getComputingResource = async () => {
const params = {
page: 0,
size: 1000,
resource_type: '',
};
const [res] = await to(getComputingResourceReq(params));
if (res && res.data && res.data.content) {
setResourceStandardList(res.data.content);
}
};
const afterOpenChange = () => {
if (!open) {
@ -109,9 +127,21 @@ const Props = forwardRef(({ onParentChange }, ref) => {
//
const selectResource = (name, item) => {
const type =
item.item_type === 'dataset' ? ResourceSelectorType.Dataset : ResourceSelectorType.Model;
const resource = type === ResourceSelectorType.Dataset ? selectedDataset : selectedModel;
let type;
let resource = undefined;
switch (item.item_type) {
case 'dataset':
type = ResourceSelectorType.Dataset;
resource = selectedDataset;
break;
case 'model':
type = ResourceSelectorType.Model;
resource = selectedModel;
break;
default:
type = ResourceSelectorType.Mirror;
break;
}
const { close } = openAntdModal(
ResourceSelectorModal,
{
@ -121,18 +151,23 @@ const Props = forwardRef(({ onParentChange }, ref) => {
defaultActiveTab: resource?.activeTab,
onOk: (res) => {
if (res) {
const jsonObj = pick(res, ['id', 'version', 'path']);
const value = JSON.stringify(jsonObj);
form.setFieldValue(name, value);
if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res);
} else {
const jsonObj = pick(res, ['id', 'version', 'path']);
const value = JSON.stringify(jsonObj);
form.setFieldValue(name, value);
}
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
} else {
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
}
} else {
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(null);
} else {
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(null);
}
form.setFieldValue(name, '');
@ -148,14 +183,18 @@ const Props = forwardRef(({ onParentChange }, ref) => {
const getSelectBtnIcon = (item) => {
const type = item.item_type;
if (type === 'dataset') {
return <Icon icon="local:dataset-select-button" className="umi-local-svg" />;
return <KFIcon type="icon-xuanzeshujuji" />;
} else if (type === 'model') {
return <Icon icon="local:model-select-button" className="umi-local-svg" />;
return <KFIcon type="icon-xuanzemoxing" />;
} else {
return <Icon icon="local:mirror-select-button" className="umi-local-svg" />;
return <KFIcon type="icon-xuanzejingxiang" />;
}
};
const filterResourceStandard = (input, { computing_resource = '' }) => {
return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase());
};
//
const controlStrategy = stagingItem.control_strategy;
//
@ -237,17 +276,22 @@ const Props = forwardRef(({ onParentChange }, ref) => {
/>
任务信息
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<Input />
<Form.Item label="镜像" required>
<div className={Styles['ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入镜像" allowClear />
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={Styles['select-button']}
>
选择镜像
</Button>
</Form.Item>
</div>
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<Input />
@ -262,11 +306,20 @@ const Props = forwardRef(({ onParentChange }, ref) => {
rules={[
{
required: true,
message: '请输入资源规格',
message: '请选择资源规格',
},
]}
>
<Input />
<Select
showSearch
placeholder="请选择资源规格"
filterOption={filterResourceStandard}
options={resourceStandardList}
fieldNames={{
label: 'description',
value: 'standard',
}}
/>
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<Input />

View File

@ -1,3 +1,4 @@
import KFIcon from '@/components/KFIcon';
import {
addWorkflow,
cloneWorkflow,
@ -6,14 +7,15 @@ import {
getWorkflowById,
removeWorkflow,
} from '@/services/pipeline/index.js';
import { CopyOutlined, DeleteOutlined, EditOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Space, Table, message } from 'antd';
import themes from '@/styles/theme.less';
import { modalConfirm } from '@/utils/ui';
import { Button, ConfigProvider, Form, Input, Modal, Space, Table, message } from 'antd';
import classNames from 'classnames';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { TextArea } = Input;
const Pipeline = () => {
const [form] = Form.useForm();
const navgite = useNavigate();
@ -26,7 +28,7 @@ const Pipeline = () => {
const editTable = (e, record) => {
e.stopPropagation();
getWorkflowById(record.id).then((ret) => {
if (ret.code == 200) {
if (ret.code === 200) {
form.resetFields();
form.setFieldsValue({ ...ret.data });
setFormId(ret.data.id);
@ -72,6 +74,7 @@ const Pipeline = () => {
};
const pageOption = useRef({ page: 1, size: 10 });
const paginationProps = {
showSizeChanger: true,
showQuickJumper: true,
showTotal: () => `${total}`,
total: total,
@ -111,7 +114,7 @@ const Pipeline = () => {
title: '序号',
dataIndex: 'index',
key: 'index',
width: 140,
width: 120,
align: 'center',
render(text, record, index) {
return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>;
@ -152,7 +155,7 @@ const Pipeline = () => {
type="link"
size="small"
key="edit"
icon={<EditOutlined />}
icon={<KFIcon type="icon-bianji" />}
onClick={(e) => {
editTable(e, record);
}}
@ -163,7 +166,7 @@ const Pipeline = () => {
type="link"
size="small"
key="clone"
icon={<CopyOutlined />}
icon={<KFIcon type="icon-fuzhi" />}
onClick={async () => {
Modal.confirm({
title: '复制',
@ -173,7 +176,7 @@ const Pipeline = () => {
onOk: () => {
console.log(record);
cloneWorkflow(record.id).then((ret) => {
if (ret.code == 200) {
if (ret.code === 200) {
message.success('复制成功');
getList();
} else {
@ -192,71 +195,65 @@ const Pipeline = () => {
>
复制
</Button>
<Button
type="link"
size="small"
danger
style={{ color: '#f98e1b' }}
key="batchRemove"
icon={<DeleteOutlined />}
onClick={async () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>
删除后该流水线将不可恢复
</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
closable: true,
okText: '确认',
cancelText: '取消',
onOk: () => {
console.log(record);
removeWorkflow(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getList();
} else {
message.error(ret.msg);
}
});
// if (success) {
// if (actionRef.current) {
// actionRef.current.reload();
// }
// }
},
});
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
删除
</Button>
<Button
type="link"
size="small"
key="batchRemove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => {
modalConfirm({
title: '删除后,该流水线将不可恢复',
content: '是否确认删除?',
onOk: () => {
console.log(record);
removeWorkflow(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getList();
} else {
message.error(ret.msg);
}
});
// if (success) {
// if (actionRef.current) {
// actionRef.current.reload();
// }
// }
},
});
}}
>
删除
</Button>
</ConfigProvider>
</Space>
),
},
];
return (
<div>
<div className={Styles.PipelineBox}>
<div className={Styles.pipelineTopBox}>
<Button
type="primary"
className={Styles.plusButton}
onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
>
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
新建流水线
</Button>
</div>
<Table columns={columns} dataSource={pipeList} pagination={paginationProps} rowKey="id" />
<div className={classNames('vertical-scroll-table', Styles.PipelineTable)}>
<Table
columns={columns}
dataSource={pipeList}
pagination={paginationProps}
rowKey="id"
scroll={{ y: 'calc(100% - 55px)' }}
/>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
@ -309,7 +306,12 @@ const Pipeline = () => {
},
]}
>
<Input placeholder="请输入流水线描述" showCount maxLength={128} />
<TextArea
autoSize={{ minRows: 2, maxRows: 5 }}
placeholder="请输入流水线描述"
showCount
maxLength={128}
/>
</Form.Item>
</Form>
</Modal>

View File

@ -8,31 +8,18 @@
padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
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 {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat:no-repeat;
background-size:100%;
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
@ -62,6 +49,19 @@
.ant-btn-primary {
background: #1664ff;
}
}
}
.PipelineBox{
height: calc(100% - 20px);
.PipelineTable{
height: calc(100% - 60px);
:global{
.ant-table-wrapper .ant-table{
// overflow-y: auto;
height: calc(100% - 48px);
}
}
}
}

View File

@ -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',
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',
});
}

View File

@ -1,3 +1,8 @@
/*
* @Author: 赵伟
* @Date: 2024-03-25 13:52:54
* @Description:
*/
import { request } from '@umijs/max';
// 查询流水线列表
export function getWorkflow(params) {
@ -63,3 +68,11 @@ export function getWorkflowById(id) {
method: 'GET',
});
}
// 获取资源规格
export function getComputingResourceReq(params) {
return request(`/api/mmp/computingResource`, {
method: 'GET',
params
});
}

View File

@ -8,9 +8,7 @@ let remoteMenu: any = null;
export function getRemoteMenu() {
return remoteMenu;
}
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js', // 在 iconfont.cn 上生成
});
export function setRemoteMenu(data: any) {
remoteMenu = data;
}

View File

@ -1,17 +1,32 @@
// 全局颜色变量
// FIXME: 不能设置 @primary-color 不起作用,感觉是哪里被重置了
@kf-primary-color: #1664ff; // 主色调
@primary-color-hover: #4086ff;
@primary-color: #1664ff; // 主色调
@primary-color-hover: #69b1ff;
@background-color: #f9fafb; // 页面背景颜色
@text-color: #1d1d20;
@text-color-second: #575757;
@font-size: 15px;
@text-color-secondary: #575757;
@success-color: #1ace62;
@error-color: #c73131;
@warning-color: #f98e1b;
@border-color: rgba(22, 100, 255, 0.3);
@border-color-second: rgba(22, 100, 255, 0.1);
@background-color-primay: rgba(22, 100, 255, 0.03);
@background-color-gray: rgba(4, 3, 3, 0.06);
@heading-color: rgba(0, 0, 0, 0.85);
@input-icon-hover-color: rgba(0, 0, 0, 0.85);
@border-color-base: #d9d9d9;
@link-hover-color: #69b1ff;
// 字体大小
@font-size: 15px;
// 导出变量
:export {
primaryColor: @kf-primary-color;
primaryColor: @primary-color;
successColor: @success-color;
errorColor: @error-color;
warningColor: @warning-color;
textColor: @text-color;
fontSize: @font-size;
}

View File

@ -14,37 +14,3 @@ export function getNameByCode(list: any[], code: any) {
});
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>;
}

View File

@ -3,7 +3,8 @@
* @Date: 2024-04-13 10:08:35
* @Description:
*/
import { type ModalProps } from 'antd';
import { ConfigProvider, type ModalProps } from 'antd';
import { globalConfig } from 'antd/es/config-provider';
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
@ -19,19 +20,20 @@ export const openAntdModal = <T extends ModalProps>(
modalProps: T,
) => {
const CustomModel = modal;
const element = document.createElement('div');
element.id = 'modal-container';
document.body.appendChild(element);
const root = createRoot(element);
const container = document.createDocumentFragment();
const root = createRoot(container);
const { afterClose, onCancel } = modalProps;
const global = globalConfig();
let timeoutId: ReturnType<typeof setTimeout>;
function destroy() {
root.unmount();
document.body.removeChild(element);
}
function handleAfterClose() {
afterClose?.();
// Warning: Attempted to synchronously unmount a root while React was already rendering.
// React cannot finish unmounting the root until the current render has completed, which may lead to a race condition.
setTimeout(() => {
destroy();
}, 0);
@ -46,11 +48,26 @@ export const openAntdModal = <T extends ModalProps>(
}
function render(props: T) {
root.render(<CustomModel {...props} onCancel={handleCancel}></CustomModel>);
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
const rootPrefixCls = global.getPrefixCls();
const iconPrefixCls = global.getIconPrefixCls();
const theme = global.getTheme();
const dom = (
<CustomModel {...props} onCancel={handleCancel} afterClose={handleAfterClose}></CustomModel>
);
root.render(
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}>
{global.holderRender ? global.holderRender(dom) : dom}
</ConfigProvider>,
);
});
}
function close() {
render({ ...modalProps, open: false, afterClose: handleAfterClose });
render({ ...modalProps, open: false });
}
render({ ...modalProps, open: true });

View File

@ -0,0 +1,2 @@
// 用于新建镜像
export const mirrorNameKey = 'mirror-name';

45
react-ui/src/utils/ui.tsx Normal file
View File

@ -0,0 +1,45 @@
/*
* @Author:
* @Date: 2024-04-19 14:42:51
* @Description: UI
*/
import themes from '@/styles/theme.less';
import { Modal, type ModalFuncProps, type UploadFile } from 'antd';
// 自定义 Confirm 弹框
export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) {
Modal.confirm({
...rest,
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: themes.textColor, fontSize: '16px', fontWeight: 500 }}>{title}</div>
</div>
),
content: content && <div style={{ color: themes.textColor, fontSize: '15px' }}>{content}</div>,
okText: '确认',
cancelText: '取消',
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;
});
};

View File

@ -4,6 +4,7 @@ import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.GenericsAjaxResult;
import com.ruoyi.platform.domain.DatasetVersion;
import com.ruoyi.platform.service.DatasetVersionService;
import com.ruoyi.platform.vo.LabelDatasetVersion;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@ -135,5 +136,10 @@ public class DatasetVersionController extends BaseController {
return genericsSuccess(this.datasetVersionService.deleteDatasetVersion(datasetId, version));
}
@PostMapping("/addDatasetVersionsFromLabel")
@ApiOperation("从数据标注添加数据集版本")
public GenericsAjaxResult<Boolean> addDatasetVersionsFromLabel(@RequestBody LabelDatasetVersion labelDatasetVersion) throws Exception {
return genericsSuccess(true);
}
}

View File

@ -116,7 +116,7 @@ public class ImageController extends BaseController {
* @return 删除是否成功
*/
@DeleteMapping("{id}")
public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) {
public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) throws Exception {
return genericsSuccess(this.imageService.removeById(id));
}

View File

@ -80,5 +80,7 @@ public interface ImageDao {
int deleteById(Integer id);
List<Image> queryByName(String name);
Image getByName(String name);
}

View File

@ -5,6 +5,7 @@ package com.ruoyi.platform.service;
import com.ruoyi.platform.domain.Dataset;
import com.ruoyi.platform.domain.DatasetVersion;
import com.ruoyi.platform.vo.LabelDatasetVersion;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@ -73,4 +74,6 @@ public interface DatasetVersionService {
void checkDeclaredVersion(DatasetVersion insert) throws Exception;
String addDatasetVersions(List<DatasetVersion> datasetVersions) throws Exception;
void addDatasetVersionsFromLabel(LabelDatasetVersion labelDatasetVersion) throws Exception;
}

View File

@ -26,6 +26,20 @@ public interface ImageService {
*/
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);
String removeById(Integer id);
String removeById(Integer id) throws Exception;
Page<Image> queryByName(String name);
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> uploadImageFiles(MultipartFile file) throws Exception;
}

View File

@ -8,6 +8,8 @@ import com.ruoyi.platform.domain.ModelsVersion;
import com.ruoyi.platform.domain.Workflow;
import com.ruoyi.platform.mapper.DatasetVersionDao;
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 org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
@ -16,6 +18,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
@ -32,7 +35,6 @@ import java.util.Map;
public class DatasetVersionServiceImpl implements DatasetVersionService {
@Resource
private DatasetVersionDao datasetVersionDao;
// 固定存储桶名
private final String bucketName = "platform-data";
@ -193,8 +195,15 @@ public class DatasetVersionServiceImpl implements DatasetVersionService {
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 {

View File

@ -1,5 +1,6 @@
package com.ruoyi.platform.service.impl;
import com.alibaba.fastjson2.util.DateUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.Image;
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.vo.ImageVo;
import com.ruoyi.system.api.model.LoginUser;
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
import io.kubernetes.client.openapi.models.V1Pod;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@ -64,6 +66,15 @@ public class ImageServiceImpl implements ImageService {
private String deploymentName;
@Value("${harbor.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查询单条数据
*
@ -88,6 +99,8 @@ public class ImageServiceImpl implements ImageService {
return new PageImpl<>(this.imageDao.queryAllByLimit(image, pageRequest), pageRequest, total);
}
/**
* 新增数据
*
@ -138,10 +151,10 @@ public class ImageServiceImpl implements ImageService {
}
@Override
public String removeById(Integer id) {
public String removeById(Integer id) throws Exception {
Image image = this.imageDao.queryById(id);
if (image == null){
return "镜像不存在";
throw new Exception("镜像不存在");
}
//判断权限只有admin和创建者本身可以删除该数据集
@ -150,11 +163,11 @@ public class ImageServiceImpl implements ImageService {
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()){
return "请先删除该镜像下的版本文件";
throw new Exception("请先删除该镜像下的版本文件");
}
image.setState(0);
return this.imageDao.update(image)>0?"删除成功":"删除失败";
@ -167,27 +180,46 @@ public class ImageServiceImpl implements ImageService {
return new PageImpl<>(this.imageDao.queryByName(name));
}
/**
* 通过名字精确查询镜像
*
* @param name 名字
* @return 镜像
*/
@Override
public Image getByName(String name) {
return this.imageDao.getByName(name);
}
@Override
@Transactional
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.setImageId(imageInsert.getId());
imageVersion.setImageId(imageToUse.getId());
imageVersion.setVersion(imageVo.getVersion());
imageVersion.setUrl(imageVo.getUrl());
imageVersion.setTagName(imageVo.getTagName());
imageVersion.setFileSize(imageVo.getFileSize());
imageVersion.setStatus("building");
ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion);
if (imageVersionInsert == null) {
throw new Exception("新增镜像失败");
throw new Exception("新增镜像版本失败");
}
// 使用CompletableFuture异步执行不同的镜像构建逻辑
CompletableFuture.supplyAsync(() -> {
@ -207,7 +239,7 @@ public class ImageServiceImpl implements ImageService {
}).thenAccept(resultMap ->{
try {
String imageUrl = resultMap.get("url");
String fileSize = resultMap.get("filesize");
String fileSize = resultMap.get("fileSize");
imageVersion.setUrl(imageUrl);
imageVersion.setFileSize(fileSize);
imageVersion.setStatus("available");
@ -230,7 +262,8 @@ public class ImageServiceImpl implements ImageService {
// 得到容器
V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName);
if (pod == null) {
throw new Exception("镜像推送服务不存在");
String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date());
pod = k8sClientUtil.createPodWithEnv(podName,serviceNS,proxyUrl,mountPath,pvcName,image);
}
String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl;
// 执行命令 docker login -u admin -p Harbor12345 172.20.32.187
@ -241,18 +274,18 @@ public class ImageServiceImpl implements ImageService {
String logs2 = k8sClientUtil.executeCommand(pod,"docker pull "+ netPath);
// 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag
if (StringUtils.isNoneBlank(logs2)){
String substring = logs2.substring(logs2.indexOf(harborUrl), logs2.length());
String cleanedString = substring.replaceAll("(\\r|\\n)", "");
String cmd1 = "docker tag " + cleanedString+ " " + harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag;
String imageUrl = harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag;
String cmd2 = "docker push " + imageUrl;
String cmd3 = "docker inspect --format='{{.Size}}' " + imageUrl;
String s = k8sClientUtil.executeCommand(pod, cmd1);
if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, cmd2))){
String[] lines = logs2.split("\n");
String lastLine = lines[lines.length - 1].trim();
String tagCmd = "docker tag " + lastLine + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
String pushCmd = "docker push " + imageUrl;
String sizeCmd = "docker inspect --format='{{.Size}}' " + imageUrl;
String s = k8sClientUtil.executeCommand(pod, tagCmd);
if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, pushCmd))){
resultMap.put("url", imageUrl);
//得到镜像文件大小
long sizeInBytes = Long.parseLong(k8sClientUtil.executeCommand(pod, cmd3));
String imageSizeStr = k8sClientUtil.executeCommand(pod, sizeCmd);
long sizeInBytes = Long.parseLong(imageSizeStr.trim());
String formattedImageSize = FileUtil.formatFileSize(sizeInBytes); // 格式化镜像文件大小
resultMap.put("fileSize", formattedImageSize);
return resultMap;
@ -271,7 +304,8 @@ public class ImageServiceImpl implements ImageService {
// 得到容器
V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName);
if (pod == null) {
throw new Exception("镜像推送服务不存在");
String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date());
pod = k8sClientUtil.createPodWithEnv(podName,serviceNS,proxyUrl,mountPath,pvcName,image);
}
String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl;
// 执行命令 docker login -u admin -p Harbor12345 172.20.32.187
@ -283,17 +317,17 @@ public class ImageServiceImpl implements ImageService {
String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath);
// 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag
if (StringUtils.isNoneBlank(logs2)){
String substring = logs2.substring(logs2.indexOf(harborUrl), logs2.length());
String cleanedString = substring.replaceAll("(\\r|\\n)", "");
String cmd1 = "docker tag " + cleanedString+ " " + harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag;
String imageUrl = harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag;
String cmd2 = "docker push " + imageUrl;
String cmd3 = "docker inspect --format='{{.Size}}' " + imageUrl;
String s = k8sClientUtil.executeCommand(pod, cmd1);
if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, cmd2))){
String substring = logs2.substring(logs2.indexOf(":")+1).trim();
String tagCmd = "docker tag " + substring + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;
String pushCmd = "docker push " + imageUrl;
String sizeCmd = "docker inspect --format='{{.Size}}' " + imageUrl;
String s = k8sClientUtil.executeCommand(pod, tagCmd);
if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, pushCmd))){
resultMap.put("url", imageUrl);
//得到镜像文件大小
long sizeInBytes = Long.parseLong(k8sClientUtil.executeCommand(pod, cmd3));
String imageSizeStr = k8sClientUtil.executeCommand(pod, sizeCmd);
long sizeInBytes = Long.parseLong(imageSizeStr.trim());
String formattedImageSize = FileUtil.formatFileSize(sizeInBytes); // 格式化镜像文件大小
resultMap.put("fileSize", formattedImageSize);
return resultMap;

View File

@ -374,6 +374,29 @@ public class HttpUtils {
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 {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {

View File

@ -378,7 +378,7 @@ public class K8sClientUtil {
// invokes the CoreV1Api client
for (V1Pod item : v1PodList.getItems()) {
String generateName = item.getMetadata().getGenerateName();
String generateName = item.getMetadata().getName();
if (StringUtils.isNotEmpty(generateName) && generateName.startsWith(deploymentName)) {
// 找到匹配的Pod获取其名称
return item;
@ -402,10 +402,16 @@ public class K8sClientUtil {
builder.append(line);
builder.append(System.getProperty("line.separator"));
}
// 等待进程结束并获取退出值
int exitValue = proc.waitFor();
if (exitValue != 0) {
// 如果进程的退出值不为0表示命令执行失败
throw new RuntimeException("容器中命令执行失败,退出值:" + exitValue);
}
return builder.toString();
} catch (Exception e) {
log.error("执行命令异常", e);
throw new RuntimeException("执行命令异常");
log.error("容器执行命令异常", e);
throw new RuntimeException("容器执行命令异常");
}
}
@ -430,4 +436,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;
}
}

View File

@ -33,11 +33,13 @@ public class ImageVo implements Serializable {
*/
@ApiModelProperty(name = "version")
private String version;
/**
* 镜像推送地址
*/
@ApiModelProperty(name = "url")
private String url;
// @ApiModelProperty(name = "url")
// private String url;
/**
* 镜像tag名称
*/
@ -102,13 +104,13 @@ public class ImageVo implements Serializable {
this.version = version;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
// public String getUrl() {
// return url;
// }
//
// public void setUrl(String url) {
// this.url = url;
// }
public String getTagName() {
return tagName;

Some files were not shown because too many files have changed in this diff Show More