refactor: create cluster by steps
@@ -168,7 +168,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'clusterDetail',
|
name: 'clusterDetail',
|
||||||
path: '/cluster-management/cluster/detail',
|
path: '/cluster-management/clusters/detail',
|
||||||
key: 'clusterDetail',
|
key: 'clusterDetail',
|
||||||
icon: 'icon-cluster2-outline',
|
icon: 'icon-cluster2-outline',
|
||||||
selectedIcon: 'icon-cluster2-filled',
|
selectedIcon: 'icon-cluster2-filled',
|
||||||
@@ -176,6 +176,16 @@ export default [
|
|||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
component: './cluster-management/cluster-detail'
|
component: './cluster-management/cluster-detail'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'clusterCreate',
|
||||||
|
path: '/cluster-management/clusters/create',
|
||||||
|
key: 'clusterCreate',
|
||||||
|
icon: 'icon-cluster2-outline',
|
||||||
|
selectedIcon: 'icon-cluster2-filled',
|
||||||
|
defaultIcon: 'icon-cluster2-outline',
|
||||||
|
hideInMenu: true,
|
||||||
|
component: './cluster-management/cluster-create'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'credentials',
|
name: 'credentials',
|
||||||
path: '/cluster-management/credentials',
|
path: '/cluster-management/credentials',
|
||||||
|
|||||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -5,20 +5,6 @@ import React, { useEffect, useMemo } from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import FormWidget from './form-widget';
|
import FormWidget from './form-widget';
|
||||||
|
|
||||||
interface ListMapProps {
|
|
||||||
dataList: any[];
|
|
||||||
label?: React.ReactNode;
|
|
||||||
btnText?: string;
|
|
||||||
properties: Record<string, any>;
|
|
||||||
onChange?: (data: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ListItemProps {
|
|
||||||
schemaList: any[];
|
|
||||||
data: Record<string, any>;
|
|
||||||
onChange?: (data: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RowWrapper = styled.div`
|
const RowWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -32,6 +18,20 @@ const WidgetBox = styled.div`
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
interface ListMapProps {
|
||||||
|
minItems?: number;
|
||||||
|
dataList: any[];
|
||||||
|
label?: React.ReactNode;
|
||||||
|
btnText?: string;
|
||||||
|
properties: Record<string, any>;
|
||||||
|
onChange?: (data: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ListItemProps {
|
||||||
|
schemaList: any[];
|
||||||
|
data: Record<string, any>;
|
||||||
|
onChange?: (data: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const ListItem: React.FC<ListItemProps> = ({ schemaList, data, onChange }) => {
|
const ListItem: React.FC<ListItemProps> = ({ schemaList, data, onChange }) => {
|
||||||
const handleValueChange = (name: string, target: any) => {
|
const handleValueChange = (name: string, target: any) => {
|
||||||
@@ -65,6 +65,7 @@ const ListMap: React.FC<ListMapProps> = ({
|
|||||||
label,
|
label,
|
||||||
btnText,
|
btnText,
|
||||||
properties = {},
|
properties = {},
|
||||||
|
minItems = 0,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
const [items, setItems] = React.useState(dataList || []);
|
const [items, setItems] = React.useState(dataList || []);
|
||||||
@@ -99,11 +100,15 @@ const ListMap: React.FC<ListMapProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dataList.length) {
|
if (!dataList.length && minItems > 0) {
|
||||||
handleOnAdd();
|
handleOnAdd();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setItems(dataList);
|
||||||
|
}, [dataList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper label={label} btnText={btnText} onAdd={handleOnAdd}>
|
<Wrapper label={label} btnText={btnText} onAdd={handleOnAdd}>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
|
|||||||
@@ -35,7 +35,13 @@ const icons = {
|
|||||||
Deployment: React.createElement(IconFont, { type: 'icon-rocket-launch1' }),
|
Deployment: React.createElement(IconFont, { type: 'icon-rocket-launch1' }),
|
||||||
Docker: React.createElement(IconFont, { type: 'icon-docker' }),
|
Docker: React.createElement(IconFont, { type: 'icon-docker' }),
|
||||||
DigitalOcean: React.createElement(IconFont, { type: 'icon-digitalocean' }),
|
DigitalOcean: React.createElement(IconFont, { type: 'icon-digitalocean' }),
|
||||||
DetailInfo: React.createElement(IconFont, { type: 'icon-detail-info' })
|
DetailInfo: React.createElement(IconFont, { type: 'icon-detail-info' }),
|
||||||
|
HuaweiCloud: React.createElement(IconFont, { type: 'icon-huaweicloud' }),
|
||||||
|
AliCloud: React.createElement(IconFont, { type: 'icon-alicloud' }),
|
||||||
|
TencentCloud: React.createElement(IconFont, { type: 'icon-tencentcloud' }),
|
||||||
|
Nvidia: React.createElement(IconFont, { type: 'icon-nvidia' }),
|
||||||
|
Ascend: React.createElement(IconFont, { type: 'icon-ascend' }),
|
||||||
|
AMD: React.createElement(IconFont, { type: 'icon-amd' })
|
||||||
};
|
};
|
||||||
|
|
||||||
export default icons;
|
export default icons;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
|||||||
// import './iconfont/iconfont.js';
|
// import './iconfont/iconfont.js';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_wy0l8ooa03c.js'
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_l0y0uhurkz8.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
width: max-content;
|
width: max-content;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: 400;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.txt {
|
.txt {
|
||||||
|
|||||||
@@ -10,20 +10,23 @@ interface CardProps {
|
|||||||
ghost?: boolean;
|
ghost?: boolean;
|
||||||
header?: React.ReactNode;
|
header?: React.ReactNode;
|
||||||
footer?: React.ReactNode;
|
footer?: React.ReactNode;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
active?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardWrapper = styled.div`
|
const CardWrapper = styled.div`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
padding: 22px;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
border-radius: var(--border-radius-base);
|
border-radius: var(--border-radius-base);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
&:hover {
|
&.clickable:hover:not(.disabled) {
|
||||||
background-color: var(--ant-color-fill-tertiary);
|
background-color: var(--ant-color-fill-tertiary);
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
@@ -35,15 +38,47 @@ const CardWrapper = styled.div`
|
|||||||
&.active {
|
&.active {
|
||||||
background-color: var(--ant-color-fill-tertiary);
|
background-color: var(--ant-color-fill-tertiary);
|
||||||
}
|
}
|
||||||
&.clickable {
|
|
||||||
|
&.clickable:not(.disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: var(--ant-color-fill-quaternary);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CardContent = styled.div`
|
const CardContent = styled.div`
|
||||||
padding: 16px 20px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Inner = styled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
line-height: 1.5;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Icon = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 16px;
|
||||||
|
font-size: 46px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Card: React.FC<CardProps> = (props) => {
|
const Card: React.FC<CardProps> = (props) => {
|
||||||
@@ -55,6 +90,9 @@ const Card: React.FC<CardProps> = (props) => {
|
|||||||
ghost = false,
|
ghost = false,
|
||||||
header,
|
header,
|
||||||
footer,
|
footer,
|
||||||
|
icon,
|
||||||
|
active,
|
||||||
|
disabled,
|
||||||
onClick
|
onClick
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -62,14 +100,19 @@ const Card: React.FC<CardProps> = (props) => {
|
|||||||
<CardWrapper
|
<CardWrapper
|
||||||
className={classNames(className, {
|
className={classNames(className, {
|
||||||
clickable: clickable,
|
clickable: clickable,
|
||||||
|
active: active,
|
||||||
|
disabled: disabled,
|
||||||
ghost: ghost
|
ghost: ghost
|
||||||
})}
|
})}
|
||||||
style={{ height: height || '180px' }}
|
style={{ height: height || '180px' }}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{header}
|
{icon && <Icon>{icon}</Icon>}
|
||||||
<CardContent>{children}</CardContent>
|
<Inner>
|
||||||
{footer}
|
{header && <Header>{header}</Header>}
|
||||||
|
<CardContent>{children}</CardContent>
|
||||||
|
{footer}
|
||||||
|
</Inner>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,8 +63,9 @@ html {
|
|||||||
--ant-color-success: #54cc98;
|
--ant-color-success: #54cc98;
|
||||||
--ant-color-error-bg: #fff2f0;
|
--ant-color-error-bg: #fff2f0;
|
||||||
--ant-color-error: #ff4d4f;
|
--ant-color-error: #ff4d4f;
|
||||||
--ant-box-shadow-secondary: 0 6px 16px 0 rgba(0, 0, 0, 8%),
|
--ant-box-shadow-secondary:
|
||||||
0 3px 6px -4px rgba(0, 0, 0, 12%), 0 9px 28px 8px rgba(0, 0, 0, 5%);
|
0 6px 16px 0 rgba(0, 0, 0, 8%), 0 3px 6px -4px rgba(0, 0, 0, 12%),
|
||||||
|
0 9px 28px 8px rgba(0, 0, 0, 5%);
|
||||||
--color-chart-red: rgba(255, 77, 79, 80%);
|
--color-chart-red: rgba(255, 77, 79, 80%);
|
||||||
--color-chart-green: rgb(84, 204, 152, 80%);
|
--color-chart-green: rgb(84, 204, 152, 80%);
|
||||||
--color-chart-glod: rgba(250, 173, 20, 80%);
|
--color-chart-glod: rgba(250, 173, 20, 80%);
|
||||||
@@ -823,3 +824,11 @@ body {
|
|||||||
.ant-progress-line {
|
.ant-progress-line {
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-pro-page-container-affix .ant-affix .ant-pro-page-container-warp {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-page-header .ant-page-header-heading-extra {
|
||||||
|
margin-block: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ export default {
|
|||||||
'clusters.workerpool.volumes': 'Volumes',
|
'clusters.workerpool.volumes': 'Volumes',
|
||||||
'clusters.workerpool.format': 'Format',
|
'clusters.workerpool.format': 'Format',
|
||||||
'clusters.workerpool.size': 'Size (GiB)',
|
'clusters.workerpool.size': 'Size (GiB)',
|
||||||
'clusters.workerpool.title': 'Worker Pools'
|
'clusters.workerpool.title': 'Worker Pools',
|
||||||
|
'clusters.workerpool.cloudOptions': 'Add Cloud Options',
|
||||||
|
'clusters.workerpool.volumes.add': 'Add Volume'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -259,5 +259,6 @@ export default {
|
|||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
||||||
'common.button.edit.item': 'Edit {name}',
|
'common.button.edit.item': 'Edit {name}',
|
||||||
'common.button.terminal': 'Terminal'
|
'common.button.terminal': 'Terminal',
|
||||||
|
'common.button.addItem': 'Add Item'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,5 +30,6 @@ export default {
|
|||||||
'menu.clusterManagement': 'Cluster Management',
|
'menu.clusterManagement': 'Cluster Management',
|
||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.clusterManagement.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.credentials': 'Credentials',
|
'menu.clusterManagement.credentials': 'Credentials',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail'
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
|
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ export default {
|
|||||||
'clusters.workerpool.volumes': 'Volumes',
|
'clusters.workerpool.volumes': 'Volumes',
|
||||||
'clusters.workerpool.format': 'Format',
|
'clusters.workerpool.format': 'Format',
|
||||||
'clusters.workerpool.size': 'Size (GiB)',
|
'clusters.workerpool.size': 'Size (GiB)',
|
||||||
'clusters.workerpool.title': 'Worker Pools'
|
'clusters.workerpool.title': 'Worker Pools',
|
||||||
|
'clusters.workerpool.cloudOptions': 'Add Cloud Options',
|
||||||
|
'clusters.workerpool.volumes.add': 'Add Volume'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -51,5 +53,8 @@ export default {
|
|||||||
// 22. 'clusters.workerpool.format': 'Format',
|
// 22. 'clusters.workerpool.format': 'Format',
|
||||||
// 23. 'clusters.workerpool.size': 'Size (GiB)',
|
// 23. 'clusters.workerpool.size': 'Size (GiB)',
|
||||||
// 24. 'clusters.credential.title': 'Credential',
|
// 24. 'clusters.credential.title': 'Credential',
|
||||||
// 25. 'clusters.workerpool.title': 'Worker Pools'
|
// 25. 'clusters.workerpool.title': 'Worker Pools',
|
||||||
|
// 26. 'clusters.workerpool.cloudOptions': 'Add Cloud Options',
|
||||||
|
// 27. 'clusters.workerpool.volumes.add': 'Add Volume'
|
||||||
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -259,7 +259,8 @@ export default {
|
|||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
||||||
'common.button.edit.item': 'Edit {name}',
|
'common.button.edit.item': 'Edit {name}',
|
||||||
'common.button.terminal': 'Terminal'
|
'common.button.terminal': 'Terminal',
|
||||||
|
'common.button.addItem': 'Add Item'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -284,5 +285,6 @@ export default {
|
|||||||
// 19. 'common.login.password': 'Log in with Password',
|
// 19. 'common.login.password': 'Log in with Password',
|
||||||
// 20. 'common.sso.noConfig': 'Single sign-on is not enabled on this system. Please contact your administrator.'
|
// 20. 'common.sso.noConfig': 'Single sign-on is not enabled on this system. Please contact your administrator.'
|
||||||
// 21. 'common.button.edit.item': 'Edit {name}',
|
// 21. 'common.button.edit.item': 'Edit {name}',
|
||||||
// 22. 'common.button.terminal': 'Terminal'
|
// 22. 'common.button.terminal': 'Terminal',
|
||||||
|
// 23. 'common.button.addItem': 'Add Item'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export default {
|
|||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.clusterManagement.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.credentials': 'Credentials',
|
'menu.clusterManagement.credentials': 'Credentials',
|
||||||
'menu.models.userModels': 'My Models',
|
'menu.models.userModels': 'My Models',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail'
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
|
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -45,5 +46,6 @@ export default {
|
|||||||
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
||||||
// 10. 'menu.clusterManagement.credentials': 'Credentials',
|
// 10. 'menu.clusterManagement.credentials': 'Credentials',
|
||||||
// 11. 'menu.models.userModels': 'My Models'
|
// 11. 'menu.models.userModels': 'My Models'
|
||||||
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail'
|
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
|
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ export default {
|
|||||||
'clusters.workerpool.volumes': 'Volumes',
|
'clusters.workerpool.volumes': 'Volumes',
|
||||||
'clusters.workerpool.format': 'Format',
|
'clusters.workerpool.format': 'Format',
|
||||||
'clusters.workerpool.size': 'Size (GiB)',
|
'clusters.workerpool.size': 'Size (GiB)',
|
||||||
'clusters.workerpool.title': 'Worker Pools'
|
'clusters.workerpool.title': 'Worker Pools',
|
||||||
|
'clusters.workerpool.cloudOptions': 'Add Cloud Options',
|
||||||
|
'clusters.workerpool.volumes.add': 'Add Volume'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -51,5 +53,7 @@ export default {
|
|||||||
// 22. 'clusters.workerpool.volumes': 'Volumes',
|
// 22. 'clusters.workerpool.volumes': 'Volumes',
|
||||||
// 23. 'clusters.workerpool.format': 'Format',
|
// 23. 'clusters.workerpool.format': 'Format',
|
||||||
// 24. 'clusters.workerpool.size': 'Size (GiB)',
|
// 24. 'clusters.workerpool.size': 'Size (GiB)',
|
||||||
// 25. 'clusters.workerpool.title': 'Worker Pools'
|
// 25. 'clusters.workerpool.title': 'Worker Pools',
|
||||||
|
// 26. 'clusters.workerpool.cloudOptions': 'Add Cloud Options',
|
||||||
|
// 27. 'clusters.workerpool.volumes.add': 'Add Volume'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -258,7 +258,8 @@ export default {
|
|||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
||||||
'common.button.edit.item': 'Edit {name}',
|
'common.button.edit.item': 'Edit {name}',
|
||||||
'common.button.terminal': 'Terminal'
|
'common.button.terminal': 'Terminal',
|
||||||
|
'common.button.addItem': 'Add Item'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -266,5 +267,6 @@ export default {
|
|||||||
// 2. 'common.login.password': 'Log in with Password'
|
// 2. 'common.login.password': 'Log in with Password'
|
||||||
// 3. 'common.sso.noConfig': 'Single sign-on is not enabled on this system. Please contact your administrator.'
|
// 3. 'common.sso.noConfig': 'Single sign-on is not enabled on this system. Please contact your administrator.'
|
||||||
// 4. 'common.button.edit.item': 'Edit {name}',
|
// 4. 'common.button.edit.item': 'Edit {name}',
|
||||||
// 5. 'common.button.terminal': 'Terminal'
|
// 5. 'common.button.terminal': 'Terminal',
|
||||||
|
// 6. 'common.button.addItem': 'Add Item'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export default {
|
|||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.clusterManagement.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.credentials': 'Credentials',
|
'menu.clusterManagement.credentials': 'Credentials',
|
||||||
'menu.models.userModels': 'My Models',
|
'menu.models.userModels': 'My Models',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail'
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
|
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -45,5 +46,6 @@ export default {
|
|||||||
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
||||||
// 10. 'menu.clusterManagement.credentials': 'Credentials',
|
// 10. 'menu.clusterManagement.credentials': 'Credentials',
|
||||||
// 11. 'menu.models.userModels': 'My Models',
|
// 11. 'menu.models.userModels': 'My Models',
|
||||||
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail'
|
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
|
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ export default {
|
|||||||
'clusters.workerpool.volumes': '存储卷',
|
'clusters.workerpool.volumes': '存储卷',
|
||||||
'clusters.workerpool.format': '文件系统格式',
|
'clusters.workerpool.format': '文件系统格式',
|
||||||
'clusters.workerpool.size': '容量(GiB)',
|
'clusters.workerpool.size': '容量(GiB)',
|
||||||
'clusters.workerpool.title': '节点池'
|
'clusters.workerpool.title': '节点池',
|
||||||
|
'clusters.workerpool.cloudOptions': '添加云配置',
|
||||||
|
'clusters.workerpool.volumes.add': '添加存储卷'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -251,5 +251,6 @@ export default {
|
|||||||
'common.external.login': '使用 {type} 登录',
|
'common.external.login': '使用 {type} 登录',
|
||||||
'common.sso.noConfig': '该系统未启用单点登录,请联系管理员。',
|
'common.sso.noConfig': '该系统未启用单点登录,请联系管理员。',
|
||||||
'common.button.edit.item': '编辑 {name}',
|
'common.button.edit.item': '编辑 {name}',
|
||||||
'common.button.terminal': '终端'
|
'common.button.terminal': '终端',
|
||||||
|
'common.button.addItem': '添加项'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,5 +30,6 @@ export default {
|
|||||||
'menu.clusterManagement': '集群管理',
|
'menu.clusterManagement': '集群管理',
|
||||||
'menu.clusterManagement.clusters': '集群',
|
'menu.clusterManagement.clusters': '集群',
|
||||||
'menu.clusterManagement.credentials': '凭证',
|
'menu.clusterManagement.credentials': '凭证',
|
||||||
'menu.clusterManagement.clusterDetail': '集群详情'
|
'menu.clusterManagement.clusterDetail': '集群详情',
|
||||||
|
'menu.clusterManagement.clusterCreate': '创建集群'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,293 @@
|
|||||||
|
import { PageAction } from '@/config';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
|
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { queryClusterList } from './apis';
|
||||||
|
import ClusterSteps from './components/cluster-steps';
|
||||||
|
import FooterButtons from './components/footer-buttons';
|
||||||
|
import ProviderCatalog from './components/provider-catalog';
|
||||||
|
import { providerList, ProviderType } from './config';
|
||||||
|
import { ClusterFormData } from './config/types';
|
||||||
|
import { moduleMap, moduleRegistry } from './step-forms/module-registry';
|
||||||
|
import useStepList from './step-forms/use-step-list';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
width: 600px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HeaderContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
padding-inline: var(--layout-content-header-inlinepadding);
|
||||||
|
border-bottom: 1px solid var(--ant-color-split);
|
||||||
|
min-height: 72px;
|
||||||
|
.text {
|
||||||
|
margin-right: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
border-right: 1px solid var(--ant-color-split);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 32px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ClusterCreate = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const stepList = useStepList();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const action =
|
||||||
|
(searchParams.get('action') as PageActionType) || PageAction.CREATE;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [credentialList, setCredentialList] = useState<
|
||||||
|
Global.BaseOption<number>[]
|
||||||
|
>([]);
|
||||||
|
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||||
|
const [registrationInfo, setRegistrationInfo] = useState<{
|
||||||
|
token: string;
|
||||||
|
image: string;
|
||||||
|
server_url: string;
|
||||||
|
cluster_id: number;
|
||||||
|
}>({
|
||||||
|
token: '',
|
||||||
|
image: '',
|
||||||
|
server_url: '',
|
||||||
|
cluster_id: 0
|
||||||
|
});
|
||||||
|
const [extraData, setExtraData] = useState<ClusterFormData>({
|
||||||
|
provider: null
|
||||||
|
} as ClusterFormData);
|
||||||
|
const [formValues, setFormValues] = useState<Record<string, any>>({});
|
||||||
|
|
||||||
|
const formRefs: Record<string, any> = {
|
||||||
|
[moduleMap.BasicForm]: useRef<any>(null),
|
||||||
|
[moduleMap.WorkerPoolForm]: useRef<any>(null)
|
||||||
|
};
|
||||||
|
|
||||||
|
const steps = useMemo(() => {
|
||||||
|
if (!extraData.provider) {
|
||||||
|
return stepList.filter((step) => step.defaultShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stepList
|
||||||
|
.filter((step) =>
|
||||||
|
step.providers?.length
|
||||||
|
? step.providers.includes(extraData.provider as never)
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
.map((step, index) => ({
|
||||||
|
...step,
|
||||||
|
disabled: action === PageAction.CREATE
|
||||||
|
}));
|
||||||
|
}, [action, extraData.provider, stepList]);
|
||||||
|
|
||||||
|
const handleStepChange = (newStep: number) => {
|
||||||
|
setCurrentStep(newStep);
|
||||||
|
console.log('step========', newStep);
|
||||||
|
};
|
||||||
|
const customizer = (objValue: any, srcValue: any) => {
|
||||||
|
if (Array.isArray(objValue)) {
|
||||||
|
return srcValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getFormFieldsValue = () => {
|
||||||
|
setFormValues((prev) => {
|
||||||
|
const newFormValues = _.cloneDeep(prev);
|
||||||
|
for (const [formKey, formRef] of Object.entries(formRefs)) {
|
||||||
|
const formValues = formRef?.current?.getFieldsValue?.();
|
||||||
|
if (formValues) {
|
||||||
|
newFormValues[formKey] = formValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newFormValues;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPrevious = () => {
|
||||||
|
getFormFieldsValue();
|
||||||
|
setCurrentStep((prev) => Math.max(prev - 1, 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForms = async () => {
|
||||||
|
const step = steps[currentStep];
|
||||||
|
const formKeys = step?.showForms || [];
|
||||||
|
if (formKeys?.length > 0) {
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
formKeys.map((key) => formRefs[key].current?.validateFields())
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('results========', results);
|
||||||
|
|
||||||
|
const resultsMap = new Map<string, any>();
|
||||||
|
results.forEach((result, index) => {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
const values = result.reason?.values || {};
|
||||||
|
resultsMap.set(formKeys[index], values);
|
||||||
|
} else if (result.status === 'fulfilled') {
|
||||||
|
resultsMap.set(formKeys[index], result.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newValues = _.merge(formValues, Object.fromEntries(resultsMap));
|
||||||
|
|
||||||
|
setFormValues(newValues);
|
||||||
|
|
||||||
|
const isValid = results.every((result) => result.status === 'fulfilled');
|
||||||
|
if (!isValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Object.assign({}, ...Object.values(newValues));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onNext = async (callback?: (values: ClusterFormData) => void) => {
|
||||||
|
try {
|
||||||
|
const result = await validateForms();
|
||||||
|
console.log('results========2', result);
|
||||||
|
if (result) {
|
||||||
|
await callback?.(result as ClusterFormData);
|
||||||
|
const step = steps[currentStep];
|
||||||
|
step.beforeNext?.();
|
||||||
|
setCurrentStep((prev) => Math.min(prev + 1, steps.length - 1));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('next error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectProvider = (value: ProviderType) => {
|
||||||
|
setExtraData({
|
||||||
|
provider: value
|
||||||
|
} as ClusterFormData);
|
||||||
|
onNext();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const res = await queryClusterList({ page: 1, perPage: 100 });
|
||||||
|
const list = res.items?.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id
|
||||||
|
}));
|
||||||
|
setCredentialList(list);
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderForms = () => {
|
||||||
|
const step = steps[currentStep];
|
||||||
|
const formKeys = step?.showForms || [];
|
||||||
|
if (formKeys.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formKeys.map((key) => {
|
||||||
|
console.log('renderForms key========', key, formValues);
|
||||||
|
const FormComponent = moduleRegistry[key];
|
||||||
|
return FormComponent ? (
|
||||||
|
<FormComponent
|
||||||
|
key={key}
|
||||||
|
ref={formRefs[key]}
|
||||||
|
action={action}
|
||||||
|
provider={extraData.provider}
|
||||||
|
credentialList={credentialList}
|
||||||
|
currentData={formValues[key]}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderModules = () => {
|
||||||
|
const step = steps[currentStep];
|
||||||
|
const moduleKeys = step?.showModules || [];
|
||||||
|
if (moduleKeys.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return moduleKeys.map((key) => {
|
||||||
|
const ModuleComponent = moduleRegistry[key];
|
||||||
|
return ModuleComponent ? (
|
||||||
|
<ModuleComponent key={key} registrationInfo={registrationInfo} />
|
||||||
|
) : null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const showButtons = useMemo(() => {
|
||||||
|
const step = steps[currentStep];
|
||||||
|
return step?.showButtons ? step?.showButtons(extraData.provider) : {};
|
||||||
|
}, [currentStep, steps, extraData.provider]);
|
||||||
|
|
||||||
|
const submit = async (values: ClusterFormData) => {
|
||||||
|
const data = {
|
||||||
|
...extraData,
|
||||||
|
...(typeof values === 'object' ? values : {})
|
||||||
|
};
|
||||||
|
console.log('submit values====:', data);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
onNext(submit);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
ghost
|
||||||
|
fixedHeader
|
||||||
|
affixProps={{
|
||||||
|
offsetTop: 0
|
||||||
|
}}
|
||||||
|
footer={[
|
||||||
|
<FooterButtons
|
||||||
|
key="buttons"
|
||||||
|
onPrevious={onPrevious}
|
||||||
|
onNext={onNext}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
showButtons={showButtons}
|
||||||
|
/>
|
||||||
|
]}
|
||||||
|
pageHeaderRender={() => (
|
||||||
|
<HeaderContainer>
|
||||||
|
<span className="text">
|
||||||
|
<span>Create Cluster</span>
|
||||||
|
</span>
|
||||||
|
<ClusterSteps
|
||||||
|
steps={steps}
|
||||||
|
currentStep={currentStep}
|
||||||
|
onChange={handleStepChange}
|
||||||
|
></ClusterSteps>
|
||||||
|
</HeaderContainer>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentStep === 0 && (
|
||||||
|
<ProviderCatalog
|
||||||
|
dataList={providerList}
|
||||||
|
onSelect={handleSelectProvider}
|
||||||
|
clickable={true}
|
||||||
|
currentProvider={extraData.provider}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{renderModules()}
|
||||||
|
<Container>
|
||||||
|
<Content>{renderForms()}</Content>
|
||||||
|
</Container>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClusterCreate;
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { useSearchParams } from '@umijs/max';
|
import { useSearchParams } from '@umijs/max';
|
||||||
import ClusterMetrics from './components/cluster-metrics';
|
import ClusterMetrics from './components/cluster-metrics';
|
||||||
import WorkerPools from './components/worker-pools';
|
|
||||||
import { ProviderValueMap } from './config';
|
|
||||||
|
|
||||||
const ClusterDetailModal = () => {
|
const ClusterDetailModal = () => {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -25,7 +23,7 @@ const ClusterDetailModal = () => {
|
|||||||
extra={[]}
|
extra={[]}
|
||||||
>
|
>
|
||||||
<ClusterMetrics />
|
<ClusterMetrics />
|
||||||
{provider === ProviderValueMap.DigitalOcean && <WorkerPools />}
|
{/* {provider === ProviderValueMap.DigitalOcean && <WorkerPools />} */}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
import AddCluster from './components/add-cluster';
|
import AddCluster from './components/add-cluster';
|
||||||
import AddPool from './components/add-pool';
|
import AddPool from './components/add-pool';
|
||||||
import RegisterCluster from './components/register-cluster';
|
import RegisterCluster from './components/register-cluster';
|
||||||
import { ProviderLabelMap, ProviderValueMap, addActions } from './config';
|
import { ProviderLabelMap, ProviderValueMap } from './config';
|
||||||
import {
|
import {
|
||||||
ClusterFormData as FormData,
|
ClusterFormData as FormData,
|
||||||
ClusterListItem as ListItem,
|
ClusterListItem as ListItem,
|
||||||
@@ -146,7 +146,8 @@ const Credentials: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClickDropdown = (item: any) => {
|
const handleClickDropdown = (item: any) => {
|
||||||
handleAddCluster(item.key);
|
navigate(`/cluster-management/clusters/create?action=${PageAction.CREATE}`);
|
||||||
|
// handleAddCluster(item.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalOk = async (data: FormData) => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
@@ -298,8 +299,6 @@ const Credentials: React.FC = () => {
|
|||||||
marginTop={30}
|
marginTop={30}
|
||||||
width={{ input: 300 }}
|
width={{ input: 300 }}
|
||||||
buttonText={intl.formatMessage({ id: 'clusters.button.add' })}
|
buttonText={intl.formatMessage({ id: 'clusters.button.add' })}
|
||||||
actionType="dropdown"
|
|
||||||
actionItems={addActions}
|
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
handleInputChange={handleNameChange}
|
handleInputChange={handleNameChange}
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import ModalFooter from '@/components/modal-footer';
|
import ModalFooter from '@/components/modal-footer';
|
||||||
import ScrollerModal from '@/components/scroller-modal/index';
|
import ScrollerModal from '@/components/scroller-modal/index';
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import ContainerInstall from '@/pages/resources/components/container-install';
|
import React, { useRef } from 'react';
|
||||||
import { CheckCircleFilled } from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import React, { useEffect, useMemo } from 'react';
|
|
||||||
import { ProviderValueMap } from '../config';
|
|
||||||
import {
|
import {
|
||||||
ClusterFormData as FormData,
|
ClusterFormData as FormData,
|
||||||
ClusterListItem as ListItem
|
ClusterListItem as ListItem
|
||||||
} from '../config/types';
|
} from '../config/types';
|
||||||
import CloudProvider from './cloud-provider-form';
|
import ClusterForm from './cluster-form';
|
||||||
import RegisterClusterInner from './register-cluster-inner';
|
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -35,15 +28,10 @@ const AddCluster: React.FC<AddModalProps> = ({
|
|||||||
onOk,
|
onOk,
|
||||||
onCancel
|
onCancel
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const form = useRef<any>(null);
|
||||||
const intl = useIntl();
|
|
||||||
const [submissionStatus, setSubmissionStatus] = React.useState<{
|
|
||||||
success: boolean;
|
|
||||||
data: ListItem;
|
|
||||||
}>({ success: false, data: {} as ListItem });
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
form.submit();
|
form.current?.submit();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOk = async (data: FormData) => {
|
const handleOk = async (data: FormData) => {
|
||||||
@@ -54,63 +42,13 @@ const AddCluster: React.FC<AddModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
form.resetFields();
|
form.current?.resetFields();
|
||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderAddWorkerContent = () => {
|
|
||||||
if (provider === ProviderValueMap.Custom) {
|
|
||||||
return <ContainerInstall />;
|
|
||||||
}
|
|
||||||
if (provider === ProviderValueMap.Kubernetes) {
|
|
||||||
return <RegisterClusterInner></RegisterClusterInner>;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const modalTitle = useMemo(() => {
|
|
||||||
if (submissionStatus.success) {
|
|
||||||
return (
|
|
||||||
<div className="flex-center">
|
|
||||||
<CheckCircleFilled
|
|
||||||
className="text-success font-size-20"
|
|
||||||
style={{ marginRight: '8px' }}
|
|
||||||
/>
|
|
||||||
<span>Cluster added! Now you can register a worker.</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return title;
|
|
||||||
}, [submissionStatus.success, title]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentData) {
|
|
||||||
form.setFieldsValue(currentData);
|
|
||||||
}
|
|
||||||
}, [currentData]);
|
|
||||||
|
|
||||||
const renderFooter = () => {
|
|
||||||
if (submissionStatus.success) {
|
|
||||||
return (
|
|
||||||
<ModalFooter
|
|
||||||
onOk={onCancel}
|
|
||||||
onCancel={onCancel}
|
|
||||||
showCancelBtn={false}
|
|
||||||
okText="Skip for Now"
|
|
||||||
okBtnProps={{
|
|
||||||
style: {
|
|
||||||
width: 'auto'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
></ModalFooter>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <ModalFooter onOk={handleSubmit} onCancel={onCancel}></ModalFooter>;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollerModal
|
<ScrollerModal
|
||||||
title={modalTitle}
|
title={title}
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
destroyOnHidden={true}
|
destroyOnHidden={true}
|
||||||
@@ -118,49 +56,18 @@ const AddCluster: React.FC<AddModalProps> = ({
|
|||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={680}
|
width={680}
|
||||||
footer={renderFooter()}
|
footer={
|
||||||
|
<ModalFooter onOk={handleSubmit} onCancel={onCancel}></ModalFooter>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{submissionStatus.success ? (
|
<ClusterForm
|
||||||
renderAddWorkerContent()
|
ref={form}
|
||||||
) : (
|
provider={provider}
|
||||||
<Form
|
credentialList={credentialList}
|
||||||
form={form}
|
action={action}
|
||||||
onFinish={handleOk}
|
currentData={currentData}
|
||||||
preserve={false}
|
onFinish={handleOk}
|
||||||
initialValues={currentData}
|
/>
|
||||||
>
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="name"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: intl.formatMessage(
|
|
||||||
{ id: 'common.form.rule.input' },
|
|
||||||
{
|
|
||||||
name: intl.formatMessage({ id: 'common.table.name' })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealInput.Input
|
|
||||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
|
||||||
required
|
|
||||||
></SealInput.Input>
|
|
||||||
</Form.Item>
|
|
||||||
{provider === ProviderValueMap.DigitalOcean && (
|
|
||||||
<CloudProvider
|
|
||||||
provider={provider}
|
|
||||||
credentialList={credentialList}
|
|
||||||
></CloudProvider>
|
|
||||||
)}
|
|
||||||
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
|
||||||
<SealInput.TextArea
|
|
||||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
|
||||||
></SealInput.TextArea>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</ScrollerModal>
|
</ScrollerModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import RegisterClusterInner from './register-cluster-inner';
|
||||||
|
import SupportedHardware from './support-hardware';
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AddModalProps = {
|
||||||
|
registrationInfo: {
|
||||||
|
token: string;
|
||||||
|
image: string;
|
||||||
|
server_url: string;
|
||||||
|
cluster_id: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const AddWorkerStep: React.FC<AddModalProps> = ({ registrationInfo }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Title>Supported Hardware Platforms</Title>
|
||||||
|
<SupportedHardware />
|
||||||
|
<Title style={{ marginTop: 32 }}>Execute Command</Title>
|
||||||
|
<RegisterClusterInner registrationInfo={registrationInfo} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddWorkerStep;
|
||||||
@@ -2,6 +2,7 @@ import DropDownActions from '@/components/drop-down-actions';
|
|||||||
import ListMap from '@/components/dynamic-form/components/list-map';
|
import ListMap from '@/components/dynamic-form/components/list-map';
|
||||||
import { FieldSchema } from '@/components/dynamic-form/config/types';
|
import { FieldSchema } from '@/components/dynamic-form/config/types';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button, Form } from 'antd';
|
import { Button, Form } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -36,7 +37,7 @@ const ButtonWrapper = styled.span`
|
|||||||
const CloudOptions: React.FC<{
|
const CloudOptions: React.FC<{
|
||||||
ref?: any;
|
ref?: any;
|
||||||
}> = forwardRef((props, ref) => {
|
}> = forwardRef((props, ref) => {
|
||||||
// form instance
|
const intl = useIntl();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
const [selectedOptions, setSelectedOptions] = React.useState<Set<string>>(
|
const [selectedOptions, setSelectedOptions] = React.useState<Set<string>>(
|
||||||
new Set()
|
new Set()
|
||||||
@@ -102,7 +103,9 @@ const CloudOptions: React.FC<{
|
|||||||
<DropDownActions menu={menu}>
|
<DropDownActions menu={menu}>
|
||||||
<Button variant="filled" color="default">
|
<Button variant="filled" color="default">
|
||||||
<PlusOutlined />
|
<PlusOutlined />
|
||||||
<span>Add Cloud Options</span>
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'clusters.workerpool.cloudOptions' })}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropDownActions>
|
</DropDownActions>
|
||||||
</Title>
|
</Title>
|
||||||
@@ -113,7 +116,7 @@ const CloudOptions: React.FC<{
|
|||||||
name={['cloud_options', field.name as string]}
|
name={['cloud_options', field.name as string]}
|
||||||
>
|
>
|
||||||
<ListMap
|
<ListMap
|
||||||
btnText={'Add Item'}
|
btnText={intl.formatMessage({ id: 'common.button.addItem' })}
|
||||||
label={field.title}
|
label={field.title}
|
||||||
dataList={form.getFieldValue(['cloud_options', field.name]) || []}
|
dataList={form.getFieldValue(['cloud_options', field.name]) || []}
|
||||||
properties={field.properties || {}}
|
properties={field.properties || {}}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||||
|
import { ProviderType, ProviderValueMap } from '../config';
|
||||||
|
import {
|
||||||
|
ClusterFormData as FormData,
|
||||||
|
ClusterListItem as ListItem
|
||||||
|
} from '../config/types';
|
||||||
|
import CloudProvider from './cloud-provider-form';
|
||||||
|
|
||||||
|
type AddModalProps = {
|
||||||
|
action: PageActionType;
|
||||||
|
currentData?: ListItem; // Used when action is EDIT
|
||||||
|
provider: ProviderType;
|
||||||
|
credentialList: Global.BaseOption<number>[];
|
||||||
|
onFinish: (values: FormData) => void;
|
||||||
|
ref?: any;
|
||||||
|
};
|
||||||
|
const ClusterForm: React.FC<AddModalProps> = forwardRef(
|
||||||
|
({ action, provider, currentData, credentialList, onFinish }, ref) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentData) {
|
||||||
|
form.setFieldsValue(currentData);
|
||||||
|
}
|
||||||
|
}, [currentData]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
resetFields: () => {
|
||||||
|
form.resetFields();
|
||||||
|
},
|
||||||
|
submit: () => {
|
||||||
|
form.submit();
|
||||||
|
},
|
||||||
|
setFieldsValue: (values: any) => {
|
||||||
|
form.setFieldsValue(values);
|
||||||
|
},
|
||||||
|
getFieldsValue: () => {
|
||||||
|
return form.getFieldsValue();
|
||||||
|
},
|
||||||
|
validateFields: async () => {
|
||||||
|
return await form.validateFields();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
name="clusterForm"
|
||||||
|
form={form}
|
||||||
|
onFinish={onFinish}
|
||||||
|
preserve={false}
|
||||||
|
scrollToFirstError={true}
|
||||||
|
initialValues={currentData}
|
||||||
|
>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="name"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: intl.formatMessage({ id: 'common.table.name' })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
|
required
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
{provider === ProviderValueMap.DigitalOcean && (
|
||||||
|
<CloudProvider
|
||||||
|
provider={provider}
|
||||||
|
credentialList={credentialList}
|
||||||
|
></CloudProvider>
|
||||||
|
)}
|
||||||
|
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
||||||
|
<SealInput.TextArea
|
||||||
|
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||||
|
></SealInput.TextArea>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ClusterForm;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Steps } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const { Step } = Steps;
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
padding-block: 20px;
|
||||||
|
background-color: var(--ant-color-bg-container);
|
||||||
|
.ant-steps-item-description {
|
||||||
|
max-width: 300px !important;
|
||||||
|
color: var(--ant-color-text-description) !important;
|
||||||
|
}
|
||||||
|
.ant-steps-item-content > .ant-steps-item-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ClusterSteps: React.FC<{
|
||||||
|
currentStep: number;
|
||||||
|
onChange: (step: number) => void;
|
||||||
|
steps: any[];
|
||||||
|
}> = (props) => {
|
||||||
|
const { steps, currentStep, onChange } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Steps current={currentStep} size="small" onChange={onChange}>
|
||||||
|
{steps.map((step) => (
|
||||||
|
<Step
|
||||||
|
disabled={step.disabled}
|
||||||
|
key={step.title}
|
||||||
|
title={step.title}
|
||||||
|
description={step.content}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Steps>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClusterSteps;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Title = styled.span`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
.text {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
interface FooterButtonsProps {
|
||||||
|
onPrevious: () => void;
|
||||||
|
onNext: () => void;
|
||||||
|
handleCancel: () => void;
|
||||||
|
handleSubmit: () => void;
|
||||||
|
showButtons: Record<string, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FooterButtons: React.FC<FooterButtonsProps> = (props) => {
|
||||||
|
const { onPrevious, onNext, handleCancel, handleSubmit, showButtons } = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
const handleOnNext = () => {
|
||||||
|
onNext();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Title style={{ height: 64 }}>
|
||||||
|
<div className="flex-center gap-16">
|
||||||
|
{showButtons.previous && (
|
||||||
|
<Button type="link" icon={<LeftOutlined />} onClick={onPrevious}>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showButtons.next && (
|
||||||
|
<Button type="link" onClick={handleOnNext}>
|
||||||
|
Next
|
||||||
|
<RightOutlined />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showButtons.skip && (
|
||||||
|
<Button type="primary" onClick={handleCancel}>
|
||||||
|
Skip for now
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showButtons.save && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
style={{ minWidth: 88 }}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'common.button.save' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Title>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FooterButtons;
|
||||||
@@ -13,21 +13,20 @@ import React, {
|
|||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useRef
|
useRef
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {
|
import { ProviderType } from '../config';
|
||||||
NodePoolFormData as FormData,
|
import { NodePoolFormData as FormData } from '../config/types';
|
||||||
NodePoolListItem as ListItem
|
import VolumesConfig from './volumes-config';
|
||||||
} from '../config/types';
|
|
||||||
import CloudOptions from './cloud-options';
|
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
ref: any;
|
ref: any;
|
||||||
|
name?: string;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
provider: string; // 'kubernetes' | 'custom' | 'digitalocean';
|
provider: ProviderType; // 'kubernetes' | 'custom' | 'digitalocean';
|
||||||
currentData?: ListItem | null;
|
currentData?: FormData | null;
|
||||||
onFinish: (values: FormData) => void;
|
onFinish: (values: FormData) => void;
|
||||||
};
|
};
|
||||||
const PoolForm: React.FC<AddModalProps> = forwardRef(
|
const PoolForm: React.FC<AddModalProps> = forwardRef(
|
||||||
({ action, onFinish, currentData }, ref) => {
|
({ action, name = 'workerPoolForm', onFinish, currentData }, ref) => {
|
||||||
const cloudOptionsRef = useRef<any>(null);
|
const cloudOptionsRef = useRef<any>(null);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -35,21 +34,26 @@ const PoolForm: React.FC<AddModalProps> = forwardRef(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentData) {
|
if (currentData) {
|
||||||
|
console.log('currentData===========1=', currentData);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...currentData,
|
...currentData
|
||||||
volumes: currentData.cloud_options?.volumes?.[0] || {}
|
|
||||||
});
|
});
|
||||||
cloudOptionsRef.current?.initFieldList();
|
|
||||||
}
|
}
|
||||||
}, [currentData]);
|
}, [currentData]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
reset: () => {
|
resetFields: () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
},
|
},
|
||||||
submit: () => {
|
submit: () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
},
|
},
|
||||||
|
setFieldsValue: (values: any) => {
|
||||||
|
form.setFieldsValue(values);
|
||||||
|
},
|
||||||
|
getFieldsValue: () => {
|
||||||
|
return form.getFieldsValue();
|
||||||
|
},
|
||||||
validateFields: async () => {
|
validateFields: async () => {
|
||||||
return await form.validateFields();
|
return await form.validateFields();
|
||||||
}
|
}
|
||||||
@@ -57,9 +61,11 @@ const PoolForm: React.FC<AddModalProps> = forwardRef(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
name={name}
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
preserve={false}
|
preserve={false}
|
||||||
|
scrollToFirstError={true}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
replicas: 1,
|
replicas: 1,
|
||||||
batch_size: 1
|
batch_size: 1
|
||||||
@@ -158,7 +164,7 @@ const PoolForm: React.FC<AddModalProps> = forwardRef(
|
|||||||
btnText={intl.formatMessage({ id: 'common.button.addLabel' })}
|
btnText={intl.formatMessage({ id: 'common.button.addLabel' })}
|
||||||
></LabelSelector>
|
></LabelSelector>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<CloudOptions ref={cloudOptionsRef}></CloudOptions>
|
<VolumesConfig ref={cloudOptionsRef}></VolumesConfig>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import Card from '@/components/templates/card';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { ProviderType } from '../config';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 22px;
|
||||||
|
`;
|
||||||
|
interface ProviderCatalogProps {
|
||||||
|
onSelect?: (provider: ProviderType) => void;
|
||||||
|
currentProvider?: ProviderType;
|
||||||
|
clickable?: boolean;
|
||||||
|
dataList: {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
locale?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
description?: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
|
||||||
|
onSelect,
|
||||||
|
dataList,
|
||||||
|
clickable,
|
||||||
|
currentProvider
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
{dataList?.map((action) => (
|
||||||
|
<Card
|
||||||
|
height="auto"
|
||||||
|
key={action.key}
|
||||||
|
onClick={() => onSelect?.(action.key as ProviderType)}
|
||||||
|
active={currentProvider === action.key}
|
||||||
|
disabled={action.disabled}
|
||||||
|
clickable={clickable}
|
||||||
|
header={
|
||||||
|
action.locale
|
||||||
|
? intl.formatMessage({ id: action.label })
|
||||||
|
: action.label
|
||||||
|
}
|
||||||
|
icon={action.icon}
|
||||||
|
>
|
||||||
|
{action.description || 'This is a description'}
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProviderCatalog;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import ascendLogo from '@/assets/logo/ascend.png';
|
||||||
|
import CambriconPNG from '@/assets/logo/cambricon.png';
|
||||||
|
import hyponPNG from '@/assets/logo/hygon.png';
|
||||||
|
import iluvatarWEBP from '@/assets/logo/Iluvatar.png';
|
||||||
|
import metaxLogo from '@/assets/logo/metax.png';
|
||||||
|
import moorePNG from '@/assets/logo/moore _threads.png';
|
||||||
|
import nvidiaLogo from '@/assets/logo/nvidia.png';
|
||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import ProviderCatalog from './provider-catalog';
|
||||||
|
|
||||||
|
const ProviderImage = ({ src, showBg }: { src: string; showBg?: boolean }) => {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
style={{
|
||||||
|
width: 46,
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
width={46}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const supportedHardPlatforms = [
|
||||||
|
{
|
||||||
|
label: 'NVIDIA CUDA',
|
||||||
|
value: 'cuda',
|
||||||
|
key: 'nvidia',
|
||||||
|
locale: false,
|
||||||
|
icon: <ProviderImage src={nvidiaLogo} showBg />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'AMD ROCm',
|
||||||
|
value: 'rocm',
|
||||||
|
key: 'amd',
|
||||||
|
locale: false,
|
||||||
|
icon: (
|
||||||
|
<IconFont
|
||||||
|
type="icon-amd"
|
||||||
|
style={{ fontSize: 46, color: 'var(--ant-color-text)' }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Ascend CANN',
|
||||||
|
value: 'npu',
|
||||||
|
key: 'ascend',
|
||||||
|
locale: false,
|
||||||
|
icon: <ProviderImage src={ascendLogo} showBg />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hygon DTK',
|
||||||
|
value: 'dcu',
|
||||||
|
key: 'hygon',
|
||||||
|
locale: false,
|
||||||
|
icon: <ProviderImage src={hyponPNG} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Moore Threads MUSA',
|
||||||
|
value: 'musa',
|
||||||
|
key: 'musa',
|
||||||
|
locale: false,
|
||||||
|
icon: <ProviderImage src={moorePNG} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Iluvatar Corex',
|
||||||
|
value: 'corex',
|
||||||
|
key: 'corex',
|
||||||
|
locale: false,
|
||||||
|
icon: <ProviderImage src={iluvatarWEBP} showBg />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cambricon',
|
||||||
|
value: 'cambricon',
|
||||||
|
key: 'cambricon',
|
||||||
|
locale: false,
|
||||||
|
icon: <ProviderImage src={CambriconPNG} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Metax',
|
||||||
|
value: 'metax',
|
||||||
|
key: 'metax',
|
||||||
|
locale: false,
|
||||||
|
icon: <ProviderImage src={metaxLogo} showBg />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const SupportedHardware = () => {
|
||||||
|
return (
|
||||||
|
<ProviderCatalog dataList={supportedHardPlatforms} clickable={false} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SupportedHardware;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import ListMap from '@/components/dynamic-form/components/list-map';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
import { fieldConfig } from '../config/cloud-options-config';
|
||||||
|
|
||||||
|
const volumeOptions = fieldConfig.volumes;
|
||||||
|
|
||||||
|
const CloudOptions: React.FC<{
|
||||||
|
ref?: any;
|
||||||
|
}> = forwardRef((props, ref) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const volumes = Form.useWatch(['cloud_options', volumeOptions.name], form);
|
||||||
|
|
||||||
|
const handleOnChange = (name: string, value: any) => {
|
||||||
|
console.log('handleOnChange========', name, value);
|
||||||
|
form.setFieldValue(['cloud_options', name], value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item name={['cloud_options', volumeOptions.name as string]}>
|
||||||
|
<ListMap
|
||||||
|
btnText={intl.formatMessage({ id: 'clusters.workerpool.volumes.add' })}
|
||||||
|
label={intl.formatMessage({ id: 'clusters.workerpool.volumes' })}
|
||||||
|
dataList={volumes || []}
|
||||||
|
properties={volumeOptions.properties || {}}
|
||||||
|
onChange={(value) => handleOnChange(volumeOptions.name, value)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CloudOptions;
|
||||||
@@ -41,7 +41,7 @@ export const fieldConfig: Record<string, FieldSchema> = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
title: 'Format',
|
title: 'Format',
|
||||||
widget: 'Select',
|
widget: 'Select',
|
||||||
enum: ['ext4', 'xfs', 'btrfs'],
|
enum: ['ext4', 'xfs'],
|
||||||
style: { width: 150 }
|
style: { width: 150 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import icons from '@/components/icon-font/icons';
|
import icons from '@/components/icon-font/icons';
|
||||||
import { StatusMaps } from '@/config';
|
import { StatusMaps } from '@/config';
|
||||||
import { StatusType } from '@/config/types';
|
import { StatusType } from '@/config/types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export const ClusterStatusValueMap = {
|
export const ClusterStatusValueMap = {
|
||||||
Provisioning: 0,
|
Provisioning: 0,
|
||||||
@@ -23,12 +24,17 @@ export const ClusterStatus: Record<string, StatusType> = {
|
|||||||
export const ProviderValueMap = {
|
export const ProviderValueMap = {
|
||||||
Kubernetes: 'Kubernetes',
|
Kubernetes: 'Kubernetes',
|
||||||
DigitalOcean: 'DigitalOcean',
|
DigitalOcean: 'DigitalOcean',
|
||||||
Custom: 'Custom'
|
Custom: 'Custom',
|
||||||
|
HuaweiCloud: 'HuaweiCloud',
|
||||||
|
AliCloud: 'AliCloud',
|
||||||
|
TencentCloud: 'TencentCloud'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ProviderType = keyof typeof ProviderValueMap | null | undefined;
|
||||||
|
|
||||||
export const ProviderLabelMap = {
|
export const ProviderLabelMap = {
|
||||||
[ProviderValueMap.Kubernetes]: 'Kubernetes',
|
[ProviderValueMap.Kubernetes]: 'Kubernetes',
|
||||||
[ProviderValueMap.DigitalOcean]: 'Digital Ocean',
|
[ProviderValueMap.DigitalOcean]: 'DigitalOcean',
|
||||||
[ProviderValueMap.Custom]: 'Custom'
|
[ProviderValueMap.Custom]: 'Custom'
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,27 +47,62 @@ export const generateRegisterCommand = (params: {
|
|||||||
--header 'Authorization: Bearer ${params.registrationToken}' | kubectl apply -f -`;
|
--header 'Authorization: Bearer ${params.registrationToken}' | kubectl apply -f -`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addActions = [
|
export const providerList = [
|
||||||
{
|
{
|
||||||
label: 'clusters.provider.custom',
|
label: 'clusters.provider.custom',
|
||||||
locale: true,
|
locale: true,
|
||||||
value: ProviderValueMap.Custom,
|
value: ProviderValueMap.Custom,
|
||||||
key: ProviderValueMap.Custom,
|
key: ProviderValueMap.Custom,
|
||||||
icon: icons.Docker
|
icon: React.cloneElement(icons.Docker, {
|
||||||
|
style: { color: 'var(--ant-color-primary)' }
|
||||||
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Kubernetes',
|
label: 'Kubernetes',
|
||||||
locale: false,
|
locale: false,
|
||||||
value: ProviderValueMap.Kubernetes,
|
value: ProviderValueMap.Kubernetes,
|
||||||
key: ProviderValueMap.Kubernetes,
|
key: ProviderValueMap.Kubernetes,
|
||||||
icon: icons.KubernetesOutlined
|
icon: React.cloneElement(icons.KubernetesOutlined, {
|
||||||
|
style: { color: 'var(--ant-color-primary)' }
|
||||||
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Digital Ocean',
|
label: 'DigitalOcean',
|
||||||
locale: false,
|
locale: false,
|
||||||
value: ProviderValueMap.DigitalOcean,
|
value: ProviderValueMap.DigitalOcean,
|
||||||
key: ProviderValueMap.DigitalOcean,
|
key: ProviderValueMap.DigitalOcean,
|
||||||
icon: icons.DigitalOcean
|
icon: React.cloneElement(icons.DigitalOcean, {
|
||||||
|
style: {
|
||||||
|
color: 'var(--ant-color-primary)'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Huawei Cloud',
|
||||||
|
locale: false,
|
||||||
|
disabled: true,
|
||||||
|
value: ProviderValueMap.HuaweiCloud,
|
||||||
|
key: ProviderValueMap.HuaweiCloud,
|
||||||
|
icon: icons.HuaweiCloud,
|
||||||
|
description: 'Currently Not Supported'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Ali Cloud',
|
||||||
|
locale: false,
|
||||||
|
disabled: true,
|
||||||
|
value: ProviderValueMap.AliCloud,
|
||||||
|
key: ProviderValueMap.AliCloud,
|
||||||
|
icon: icons.AliCloud,
|
||||||
|
description: 'Currently Not Supported'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tencent Cloud',
|
||||||
|
locale: false,
|
||||||
|
disabled: true,
|
||||||
|
value: ProviderValueMap.TencentCloud,
|
||||||
|
key: ProviderValueMap.TencentCloud,
|
||||||
|
icon: icons.TencentCloud,
|
||||||
|
description: 'Currently Not Supported'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -101,13 +142,13 @@ export const clusterActionList = [
|
|||||||
locale: true,
|
locale: true,
|
||||||
icon: icons.KubernetesOutlined
|
icon: icons.KubernetesOutlined
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
key: 'addPool',
|
// key: 'addPool',
|
||||||
label: 'clusters.button.addNodePool',
|
// label: 'clusters.button.addNodePool',
|
||||||
provider: ProviderValueMap.DigitalOcean,
|
// provider: ProviderValueMap.DigitalOcean,
|
||||||
locale: true,
|
// locale: true,
|
||||||
icon: icons.Catalog
|
// icon: icons.Catalog
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: 'common.button.delete',
|
label: 'common.button.delete',
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ProviderType } from '.';
|
||||||
|
|
||||||
export interface CredentialFormData {
|
export interface CredentialFormData {
|
||||||
name: string;
|
name: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
@@ -20,15 +22,6 @@ export interface CredentialListItem {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClusterFormData {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
provider: string;
|
|
||||||
credential_id: number;
|
|
||||||
zone: string;
|
|
||||||
region: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodePoolListItem {
|
export interface NodePoolListItem {
|
||||||
id: number;
|
id: number;
|
||||||
instance_type: string;
|
instance_type: string;
|
||||||
@@ -47,7 +40,6 @@ export interface NodePoolFormData {
|
|||||||
batch_size: number;
|
batch_size: number;
|
||||||
labels: Record<string, string>;
|
labels: Record<string, string>;
|
||||||
cloud_options: Record<string, any>;
|
cloud_options: Record<string, any>;
|
||||||
volumes?: Record<string, any>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClusterListItem {
|
export interface ClusterListItem {
|
||||||
@@ -67,3 +59,13 @@ export interface ClusterListItem {
|
|||||||
state_message: string;
|
state_message: string;
|
||||||
worker_pools: NodePoolListItem[];
|
worker_pools: NodePoolListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClusterFormData {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
provider: ProviderType;
|
||||||
|
credential_id: number;
|
||||||
|
zone: string;
|
||||||
|
region: string;
|
||||||
|
worker_pools?: NodePoolFormData[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const useClusterColumns = (
|
|||||||
render: (text: string, record: ClusterListItem) => (
|
render: (text: string, record: ClusterListItem) => (
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
<Link
|
<Link
|
||||||
to={`/cluster-management/cluster/detail?id=${record.id}&provider=${record.provider}&name=${record.name}`}
|
to={`/cluster-management/clusters/detail?id=${record.id}&provider=${record.provider}&name=${record.name}`}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import PageTools from '@/components/page-tools';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import ClusterForm from '../components/cluster-form';
|
||||||
|
import { ProviderType } from '../config';
|
||||||
|
|
||||||
|
const Title = styled.span`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
.text {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
interface BasicFormProps {
|
||||||
|
provider: ProviderType;
|
||||||
|
action: PageActionType;
|
||||||
|
credentialList: Global.BaseOption<number>[];
|
||||||
|
currentData?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BasicForm = forwardRef((props: BasicFormProps, ref) => {
|
||||||
|
const { provider, credentialList, action, currentData } = props;
|
||||||
|
const formRef = useRef<any>(null);
|
||||||
|
|
||||||
|
const handleOnFinish = (values: any) => {
|
||||||
|
console.log(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
validateFields: formRef.current?.validateFields,
|
||||||
|
getFieldsValue: formRef.current?.getFieldsValue,
|
||||||
|
submit: formRef.current?.submit
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageTools
|
||||||
|
marginBottom={26}
|
||||||
|
left={<Title>Basic Configuration</Title>}
|
||||||
|
marginTop={0}
|
||||||
|
></PageTools>
|
||||||
|
<ClusterForm
|
||||||
|
provider={provider}
|
||||||
|
action={action}
|
||||||
|
ref={formRef}
|
||||||
|
credentialList={credentialList}
|
||||||
|
onFinish={handleOnFinish}
|
||||||
|
currentData={currentData}
|
||||||
|
></ClusterForm>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default BasicForm;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import AddWorkerStep from '../components/add-worker-step';
|
||||||
|
import ProviderCatalog from '../components/provider-catalog';
|
||||||
|
import BasicForm from './basic-form';
|
||||||
|
import WorkerPoolForm from './worker-pools-form';
|
||||||
|
|
||||||
|
export const moduleMap = {
|
||||||
|
BasicForm: 'BasicForm',
|
||||||
|
WorkerPoolForm: 'WorkerPoolForm',
|
||||||
|
ProviderCatalog: 'ProviderCatalog',
|
||||||
|
AddWorkerStep: 'AddWorkerStep'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const moduleRegistry: Record<string, React.ComponentType<any>> = {
|
||||||
|
[moduleMap.BasicForm]: BasicForm,
|
||||||
|
[moduleMap.WorkerPoolForm]: WorkerPoolForm,
|
||||||
|
[moduleMap.ProviderCatalog]: ProviderCatalog,
|
||||||
|
[moduleMap.AddWorkerStep]: AddWorkerStep
|
||||||
|
};
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { useNavigate } from '@umijs/max';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { ProviderType, ProviderValueMap } from '../config';
|
||||||
|
import { moduleMap } from './module-registry';
|
||||||
|
|
||||||
|
export default function useStepList() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleBack = useMemoizedFn(() => {
|
||||||
|
navigate(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
title: 'Select Cloud Provider',
|
||||||
|
content: 'Choose the cloud provider for your cluster.',
|
||||||
|
showButtons: (provider?: ProviderType) => {
|
||||||
|
return {
|
||||||
|
previous: false,
|
||||||
|
next: true,
|
||||||
|
save: false,
|
||||||
|
skip: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultShow: true,
|
||||||
|
showForms: [],
|
||||||
|
showModules: [moduleMap.ProviderCatalog],
|
||||||
|
providers: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Configure Cluster Settings',
|
||||||
|
content: 'Set up the basic configuration for your cluster.',
|
||||||
|
showButtons: (provider?: ProviderType) => {
|
||||||
|
return {
|
||||||
|
previous: true,
|
||||||
|
next: provider === ProviderValueMap.DigitalOcean,
|
||||||
|
save: provider !== ProviderValueMap.DigitalOcean,
|
||||||
|
skip: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultShow: true,
|
||||||
|
showForms: [moduleMap.BasicForm],
|
||||||
|
showModules: [],
|
||||||
|
providers: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Add Worker Pools',
|
||||||
|
content: 'Define the worker pools for your cluster.',
|
||||||
|
showButtons: (provider?: ProviderType) => {
|
||||||
|
return {
|
||||||
|
previous: true,
|
||||||
|
next: false,
|
||||||
|
save: true,
|
||||||
|
skip: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultShow: false,
|
||||||
|
showForms: [moduleMap.WorkerPoolForm],
|
||||||
|
showModules: [],
|
||||||
|
providers: [ProviderValueMap.DigitalOcean],
|
||||||
|
beforeNext: handleBack
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Add Worker',
|
||||||
|
content: 'Add a worker node to your cluster.',
|
||||||
|
showButtons: (provider?: ProviderType) => {
|
||||||
|
return {
|
||||||
|
previous: false,
|
||||||
|
next: false,
|
||||||
|
save: false,
|
||||||
|
skip: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultShow: false,
|
||||||
|
showForms: [],
|
||||||
|
showModules: [moduleMap.AddWorkerStep],
|
||||||
|
providers: [ProviderValueMap.Custom]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Register Cluster',
|
||||||
|
content: 'Register your cluster with the chosen provider.',
|
||||||
|
showButtons: (provider?: ProviderType) => {
|
||||||
|
return {
|
||||||
|
previous: false,
|
||||||
|
next: false,
|
||||||
|
save: false,
|
||||||
|
skip: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultShow: false,
|
||||||
|
showForms: [],
|
||||||
|
showModules: [moduleMap.AddWorkerStep],
|
||||||
|
providers: [ProviderValueMap.Kubernetes]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[handleBack]
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
import PageTools from '@/components/page-tools';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Divider, FormInstance } from 'antd';
|
||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import WorkerPoolForm from '../components/pool-form';
|
||||||
|
import { ProviderType } from '../config';
|
||||||
|
import { NodePoolFormData } from '../config/types';
|
||||||
|
|
||||||
|
const PoolContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PoolFormWrapper = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.span`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
.text {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
interface WorkerPoolsFormProps {
|
||||||
|
provider: ProviderType;
|
||||||
|
action: PageActionType;
|
||||||
|
currentData?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
|
||||||
|
const { provider, action, currentData } = props;
|
||||||
|
const countRef = useRef(1);
|
||||||
|
const formRefs = useRef<Record<number, FormInstance<any> | null>>({});
|
||||||
|
const [workerPoolList, setWorkerPoolList] = useState<
|
||||||
|
Map<number, NodePoolFormData>
|
||||||
|
>(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
instance_type: 'Pool-1'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]) as Map<number, NodePoolFormData>
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOnFinish = (values: any) => {};
|
||||||
|
|
||||||
|
const updateCount = () => {
|
||||||
|
countRef.current += 1;
|
||||||
|
return countRef.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddPool = () => {
|
||||||
|
const newId = updateCount();
|
||||||
|
setWorkerPoolList((prev) =>
|
||||||
|
new Map(prev).set(newId, {
|
||||||
|
instance_type: `Pool-${newId}`
|
||||||
|
} as NodePoolFormData)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemovePool = (id: number) => {
|
||||||
|
if (workerPoolList.size === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkerPoolList((prev) => {
|
||||||
|
const newList = new Map(prev);
|
||||||
|
newList.delete(id);
|
||||||
|
return newList;
|
||||||
|
});
|
||||||
|
formRefs.current[id] = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const gatherFormValues = (results: PromiseSettledResult<any>[]) => {
|
||||||
|
const resultList = results.map((result: PromiseSettledResult<any>) => {
|
||||||
|
if (result.status === 'fulfilled') {
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
return result.reason?.values || {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
console.log('gatherFormValues========', resultList);
|
||||||
|
return resultList;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFields = async () => {
|
||||||
|
const promises = Array.from(Object.values(formRefs.current)).map((form) =>
|
||||||
|
form?.validateFields()
|
||||||
|
);
|
||||||
|
const results = await Promise.allSettled(promises);
|
||||||
|
console.log('results========0', promises, results);
|
||||||
|
if (results.some((result) => result.status === 'rejected')) {
|
||||||
|
return Promise.reject({
|
||||||
|
values: {
|
||||||
|
worker_pools: gatherFormValues(results)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
worker_pools: gatherFormValues(results)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFieldsValue = (data: any) => {
|
||||||
|
const worker_pools = data.worker_pools || [];
|
||||||
|
const newWorkerPools = worker_pools.map(
|
||||||
|
(poolData: NodePoolFormData, index: number) => [index, poolData]
|
||||||
|
);
|
||||||
|
console.log('newWorkerPools========', newWorkerPools);
|
||||||
|
setWorkerPoolList(new Map(newWorkerPools));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFieldsValue = () => {
|
||||||
|
const values = Object.values(formRefs.current).map((form) =>
|
||||||
|
form?.getFieldsValue()
|
||||||
|
);
|
||||||
|
console.log('getFieldsValue========', values);
|
||||||
|
return {
|
||||||
|
worker_pools: values
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
submit: () => {},
|
||||||
|
validateFields: validateFields,
|
||||||
|
getFieldsValue: getFieldsValue
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentData) {
|
||||||
|
console.log('currentData===========1=', currentData);
|
||||||
|
setFieldsValue(currentData);
|
||||||
|
}
|
||||||
|
}, [currentData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageTools
|
||||||
|
marginBottom={22}
|
||||||
|
marginTop={0}
|
||||||
|
left={
|
||||||
|
<span className="flex-center gap-16">
|
||||||
|
<Title>Worker Pools Configuration</Title>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
right={
|
||||||
|
<Button
|
||||||
|
onClick={handleAddPool}
|
||||||
|
variant="filled"
|
||||||
|
color="default"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
Add Pool
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
></PageTools>
|
||||||
|
{Array.from(workerPoolList.keys()).map((key, index) => (
|
||||||
|
<div key={key}>
|
||||||
|
<PoolContainer>
|
||||||
|
<PoolFormWrapper>
|
||||||
|
<WorkerPoolForm
|
||||||
|
name={`workerPoolForm_${key}`}
|
||||||
|
action={action}
|
||||||
|
ref={(el: any) => {
|
||||||
|
if (el) {
|
||||||
|
formRefs.current[key] = el;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFinish={handleOnFinish}
|
||||||
|
provider={provider}
|
||||||
|
currentData={workerPoolList.get(key)}
|
||||||
|
></WorkerPoolForm>
|
||||||
|
</PoolFormWrapper>
|
||||||
|
</PoolContainer>
|
||||||
|
{index !== Array.from(workerPoolList.keys()).length - 1 && (
|
||||||
|
<Divider orientation="right">
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
color="default"
|
||||||
|
onClick={() => handleRemovePool(key)}
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
></Button>
|
||||||
|
</Divider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="flex-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleAddPool}
|
||||||
|
variant="filled"
|
||||||
|
color="default"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
Add Pool
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default WorkerPoolsForm;
|
||||||
@@ -21,24 +21,6 @@ const ModelItemContent = styled.div`
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
.title {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
.anticon {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: var(--font-size-middle);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -83,6 +65,25 @@ const ModelItemContent = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
.anticon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const sourceIconMap = {
|
const sourceIconMap = {
|
||||||
[modelSourceMap.local_path_value]: 'icon-hard-disk',
|
[modelSourceMap.local_path_value]: 'icon-hard-disk',
|
||||||
[modelSourceMap.huggingface_value]: 'icon-huggingface',
|
[modelSourceMap.huggingface_value]: 'icon-huggingface',
|
||||||
@@ -96,9 +97,12 @@ const ModelItem: React.FC<{
|
|||||||
const { model, onClick } = props;
|
const { model, onClick } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card onClick={() => onClick(model)} clickable={false} ghost>
|
<Card
|
||||||
<ModelItemContent>
|
onClick={() => onClick(model)}
|
||||||
<div className="title">
|
clickable={false}
|
||||||
|
ghost
|
||||||
|
header={
|
||||||
|
<Header>
|
||||||
<span className="text">
|
<span className="text">
|
||||||
<IconFont
|
<IconFont
|
||||||
type={sourceIconMap[model.source]}
|
type={sourceIconMap[model.source]}
|
||||||
@@ -114,7 +118,10 @@ const ModelItem: React.FC<{
|
|||||||
message: model.state_message
|
message: model.state_message
|
||||||
}}
|
}}
|
||||||
></StatusTag>
|
></StatusTag>
|
||||||
</div>
|
</Header>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ModelItemContent>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="extra-info">
|
<div className="extra-info">
|
||||||
<ThemeTag className="tag-item" color="blue">
|
<ThemeTag className="tag-item" color="blue">
|
||||||
|
|||||||